summaryrefslogtreecommitdiff
path: root/dviware/dvisvgm/src
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /dviware/dvisvgm/src
Initial commit
Diffstat (limited to 'dviware/dvisvgm/src')
-rw-r--r--dviware/dvisvgm/src/AGLTable.hpp4084
-rw-r--r--dviware/dvisvgm/src/BasicDVIReader.cpp333
-rw-r--r--dviware/dvisvgm/src/BasicDVIReader.hpp113
-rw-r--r--dviware/dvisvgm/src/Bezier.cpp256
-rw-r--r--dviware/dvisvgm/src/Bezier.hpp52
-rw-r--r--dviware/dvisvgm/src/BgColorSpecialHandler.cpp68
-rw-r--r--dviware/dvisvgm/src/BgColorSpecialHandler.hpp45
-rw-r--r--dviware/dvisvgm/src/Bitmap.cpp161
-rw-r--r--dviware/dvisvgm/src/Bitmap.hpp111
-rw-r--r--dviware/dvisvgm/src/BoundingBox.cpp274
-rw-r--r--dviware/dvisvgm/src/BoundingBox.hpp95
-rw-r--r--dviware/dvisvgm/src/CLCommandLine.cpp242
-rw-r--r--dviware/dvisvgm/src/CLCommandLine.hpp69
-rw-r--r--dviware/dvisvgm/src/CLOption.hpp198
-rw-r--r--dviware/dvisvgm/src/CMap.cpp71
-rw-r--r--dviware/dvisvgm/src/CMap.hpp108
-rw-r--r--dviware/dvisvgm/src/CMapManager.cpp139
-rw-r--r--dviware/dvisvgm/src/CMapManager.hpp51
-rw-r--r--dviware/dvisvgm/src/CMapReader.cpp263
-rw-r--r--dviware/dvisvgm/src/CMapReader.hpp79
-rw-r--r--dviware/dvisvgm/src/CRC32.cpp112
-rw-r--r--dviware/dvisvgm/src/CRC32.hpp47
-rw-r--r--dviware/dvisvgm/src/Calculator.cpp185
-rw-r--r--dviware/dvisvgm/src/Calculator.hpp55
-rw-r--r--dviware/dvisvgm/src/CharMapID.cpp36
-rw-r--r--dviware/dvisvgm/src/CharMapID.hpp59
-rw-r--r--dviware/dvisvgm/src/Character.hpp44
-rw-r--r--dviware/dvisvgm/src/Color.cpp681
-rw-r--r--dviware/dvisvgm/src/Color.hpp96
-rw-r--r--dviware/dvisvgm/src/ColorSpecialHandler.cpp120
-rw-r--r--dviware/dvisvgm/src/ColorSpecialHandler.hpp44
-rw-r--r--dviware/dvisvgm/src/CommandLine.hpp154
-rw-r--r--dviware/dvisvgm/src/DLLoader.cpp59
-rw-r--r--dviware/dvisvgm/src/DLLoader.hpp72
-rw-r--r--dviware/dvisvgm/src/DVIActions.hpp46
-rw-r--r--dviware/dvisvgm/src/DVIReader.cpp706
-rw-r--r--dviware/dvisvgm/src/DVIReader.hpp174
-rw-r--r--dviware/dvisvgm/src/DVIToSVG.cpp575
-rw-r--r--dviware/dvisvgm/src/DVIToSVG.hpp112
-rw-r--r--dviware/dvisvgm/src/DVIToSVGActions.cpp353
-rw-r--r--dviware/dvisvgm/src/DVIToSVGActions.hpp95
-rw-r--r--dviware/dvisvgm/src/Directory.cpp131
-rw-r--r--dviware/dvisvgm/src/Directory.hpp55
-rw-r--r--dviware/dvisvgm/src/DvisvgmSpecialHandler.cpp539
-rw-r--r--dviware/dvisvgm/src/DvisvgmSpecialHandler.hpp105
-rw-r--r--dviware/dvisvgm/src/EPSFile.cpp114
-rw-r--r--dviware/dvisvgm/src/EPSFile.hpp44
-rw-r--r--dviware/dvisvgm/src/EPSToSVG.hpp43
-rw-r--r--dviware/dvisvgm/src/EmSpecialHandler.cpp265
-rw-r--r--dviware/dvisvgm/src/EmSpecialHandler.hpp62
-rw-r--r--dviware/dvisvgm/src/EncFile.cpp132
-rw-r--r--dviware/dvisvgm/src/EncFile.hpp48
-rw-r--r--dviware/dvisvgm/src/FileFinder.cpp262
-rw-r--r--dviware/dvisvgm/src/FileFinder.hpp55
-rw-r--r--dviware/dvisvgm/src/FilePath.cpp259
-rw-r--r--dviware/dvisvgm/src/FilePath.hpp69
-rw-r--r--dviware/dvisvgm/src/FileSystem.cpp369
-rw-r--r--dviware/dvisvgm/src/FileSystem.hpp59
-rw-r--r--dviware/dvisvgm/src/FixWord.hpp38
-rw-r--r--dviware/dvisvgm/src/Font.cpp676
-rw-r--r--dviware/dvisvgm/src/Font.hpp397
-rw-r--r--dviware/dvisvgm/src/FontCache.cpp377
-rw-r--r--dviware/dvisvgm/src/FontCache.hpp64
-rw-r--r--dviware/dvisvgm/src/FontEncoding.cpp89
-rw-r--r--dviware/dvisvgm/src/FontEncoding.hpp61
-rw-r--r--dviware/dvisvgm/src/FontEngine.cpp413
-rw-r--r--dviware/dvisvgm/src/FontEngine.hpp82
-rw-r--r--dviware/dvisvgm/src/FontManager.cpp353
-rw-r--r--dviware/dvisvgm/src/FontManager.hpp83
-rw-r--r--dviware/dvisvgm/src/FontMap.cpp296
-rw-r--r--dviware/dvisvgm/src/FontMap.hpp74
-rw-r--r--dviware/dvisvgm/src/FontMetrics.cpp41
-rw-r--r--dviware/dvisvgm/src/FontMetrics.hpp73
-rw-r--r--dviware/dvisvgm/src/FontStyle.hpp32
-rw-r--r--dviware/dvisvgm/src/FontWriter.cpp269
-rw-r--r--dviware/dvisvgm/src/FontWriter.hpp66
-rw-r--r--dviware/dvisvgm/src/GFGlyphTracer.cpp91
-rw-r--r--dviware/dvisvgm/src/GFGlyphTracer.hpp60
-rw-r--r--dviware/dvisvgm/src/GFReader.cpp359
-rw-r--r--dviware/dvisvgm/src/GFReader.hpp106
-rw-r--r--dviware/dvisvgm/src/GFTracer.cpp93
-rw-r--r--dviware/dvisvgm/src/GFTracer.hpp45
-rw-r--r--dviware/dvisvgm/src/Ghostscript.cpp382
-rw-r--r--dviware/dvisvgm/src/Ghostscript.hpp104
-rw-r--r--dviware/dvisvgm/src/Glyph.hpp28
-rw-r--r--dviware/dvisvgm/src/GlyphTracerMessages.hpp78
-rw-r--r--dviware/dvisvgm/src/GraphicsPath.hpp467
-rw-r--r--dviware/dvisvgm/src/HashFunction.cpp84
-rw-r--r--dviware/dvisvgm/src/HashFunction.hpp47
-rw-r--r--dviware/dvisvgm/src/HtmlSpecialHandler.cpp86
-rw-r--r--dviware/dvisvgm/src/HtmlSpecialHandler.hpp47
-rw-r--r--dviware/dvisvgm/src/HyperlinkManager.cpp265
-rw-r--r--dviware/dvisvgm/src/HyperlinkManager.hpp79
-rw-r--r--dviware/dvisvgm/src/ImageToSVG.cpp199
-rw-r--r--dviware/dvisvgm/src/ImageToSVG.hpp85
-rw-r--r--dviware/dvisvgm/src/InputBuffer.cpp137
-rw-r--r--dviware/dvisvgm/src/InputBuffer.hpp139
-rw-r--r--dviware/dvisvgm/src/InputReader.cpp413
-rw-r--r--dviware/dvisvgm/src/InputReader.hpp107
-rw-r--r--dviware/dvisvgm/src/JFM.cpp103
-rw-r--r--dviware/dvisvgm/src/JFM.hpp45
-rw-r--r--dviware/dvisvgm/src/Length.cpp157
-rw-r--r--dviware/dvisvgm/src/Length.hpp92
-rw-r--r--dviware/dvisvgm/src/MD5HashFunction.hpp58
-rw-r--r--dviware/dvisvgm/src/Makefile.am226
-rw-r--r--dviware/dvisvgm/src/Makefile.in1424
-rw-r--r--dviware/dvisvgm/src/MapLine.cpp283
-rw-r--r--dviware/dvisvgm/src/MapLine.hpp72
-rw-r--r--dviware/dvisvgm/src/Matrix.cpp609
-rw-r--r--dviware/dvisvgm/src/Matrix.hpp119
-rw-r--r--dviware/dvisvgm/src/Message.cpp270
-rw-r--r--dviware/dvisvgm/src/Message.hpp112
-rw-r--r--dviware/dvisvgm/src/MessageException.hpp37
-rw-r--r--dviware/dvisvgm/src/MetafontWrapper.cpp122
-rw-r--r--dviware/dvisvgm/src/MetafontWrapper.hpp42
-rw-r--r--dviware/dvisvgm/src/MiKTeXCom.cpp111
-rw-r--r--dviware/dvisvgm/src/MiKTeXCom.hpp50
-rw-r--r--dviware/dvisvgm/src/NoPsSpecialHandler.cpp45
-rw-r--r--dviware/dvisvgm/src/NoPsSpecialHandler.hpp41
-rw-r--r--dviware/dvisvgm/src/NumericRanges.hpp109
-rw-r--r--dviware/dvisvgm/src/PDFParser.cpp469
-rw-r--r--dviware/dvisvgm/src/PDFParser.hpp185
-rw-r--r--dviware/dvisvgm/src/PDFToSVG.hpp63
-rw-r--r--dviware/dvisvgm/src/PSFilter.hpp41
-rw-r--r--dviware/dvisvgm/src/PSInterpreter.cpp371
-rw-r--r--dviware/dvisvgm/src/PSInterpreter.hpp132
-rw-r--r--dviware/dvisvgm/src/PSPattern.cpp175
-rw-r--r--dviware/dvisvgm/src/PSPattern.hpp97
-rw-r--r--dviware/dvisvgm/src/PSPreviewFilter.cpp110
-rw-r--r--dviware/dvisvgm/src/PSPreviewFilter.hpp54
-rw-r--r--dviware/dvisvgm/src/PageRanges.cpp84
-rw-r--r--dviware/dvisvgm/src/PageRanges.hpp36
-rw-r--r--dviware/dvisvgm/src/PageSize.cpp146
-rw-r--r--dviware/dvisvgm/src/PageSize.hpp46
-rw-r--r--dviware/dvisvgm/src/Pair.hpp81
-rw-r--r--dviware/dvisvgm/src/PapersizeSpecialHandler.cpp98
-rw-r--r--dviware/dvisvgm/src/PapersizeSpecialHandler.hpp50
-rw-r--r--dviware/dvisvgm/src/PathClipper.cpp331
-rw-r--r--dviware/dvisvgm/src/PathClipper.hpp55
-rw-r--r--dviware/dvisvgm/src/PdfSpecialHandler.cpp289
-rw-r--r--dviware/dvisvgm/src/PdfSpecialHandler.hpp54
-rw-r--r--dviware/dvisvgm/src/PreScanDVIReader.cpp44
-rw-r--r--dviware/dvisvgm/src/PreScanDVIReader.hpp43
-rw-r--r--dviware/dvisvgm/src/Process.cpp325
-rw-r--r--dviware/dvisvgm/src/Process.hpp40
-rw-r--r--dviware/dvisvgm/src/PsSpecialHandler.cpp1297
-rw-r--r--dviware/dvisvgm/src/PsSpecialHandler.hpp185
-rw-r--r--dviware/dvisvgm/src/RangeMap.cpp197
-rw-r--r--dviware/dvisvgm/src/RangeMap.hpp142
-rw-r--r--dviware/dvisvgm/src/SVGCharHandler.cpp92
-rw-r--r--dviware/dvisvgm/src/SVGCharHandler.hpp108
-rw-r--r--dviware/dvisvgm/src/SVGCharHandlerFactory.cpp36
-rw-r--r--dviware/dvisvgm/src/SVGCharHandlerFactory.hpp32
-rw-r--r--dviware/dvisvgm/src/SVGCharPathHandler.cpp136
-rw-r--r--dviware/dvisvgm/src/SVGCharPathHandler.hpp46
-rw-r--r--dviware/dvisvgm/src/SVGCharTspanTextHandler.cpp87
-rw-r--r--dviware/dvisvgm/src/SVGCharTspanTextHandler.hpp43
-rw-r--r--dviware/dvisvgm/src/SVGOutput.cpp197
-rw-r--r--dviware/dvisvgm/src/SVGOutput.hpp76
-rw-r--r--dviware/dvisvgm/src/SVGSingleCharTextHandler.cpp37
-rw-r--r--dviware/dvisvgm/src/SVGSingleCharTextHandler.hpp34
-rw-r--r--dviware/dvisvgm/src/SVGTree.cpp346
-rw-r--r--dviware/dvisvgm/src/SVGTree.hpp98
-rw-r--r--dviware/dvisvgm/src/ShadingPatch.cpp65
-rw-r--r--dviware/dvisvgm/src/ShadingPatch.hpp70
-rw-r--r--dviware/dvisvgm/src/SignalHandler.cpp153
-rw-r--r--dviware/dvisvgm/src/SignalHandler.hpp54
-rw-r--r--dviware/dvisvgm/src/SourceInput.cpp150
-rw-r--r--dviware/dvisvgm/src/SourceInput.hpp58
-rw-r--r--dviware/dvisvgm/src/SpecialActions.hpp92
-rw-r--r--dviware/dvisvgm/src/SpecialHandler.hpp55
-rw-r--r--dviware/dvisvgm/src/SpecialManager.cpp179
-rw-r--r--dviware/dvisvgm/src/SpecialManager.hpp62
-rw-r--r--dviware/dvisvgm/src/StreamReader.cpp154
-rw-r--r--dviware/dvisvgm/src/StreamReader.hpp68
-rw-r--r--dviware/dvisvgm/src/StreamWriter.cpp97
-rw-r--r--dviware/dvisvgm/src/StreamWriter.hpp46
-rw-r--r--dviware/dvisvgm/src/Subfont.cpp255
-rw-r--r--dviware/dvisvgm/src/Subfont.hpp90
-rw-r--r--dviware/dvisvgm/src/System.cpp49
-rw-r--r--dviware/dvisvgm/src/System.hpp29
-rw-r--r--dviware/dvisvgm/src/TFM.cpp189
-rw-r--r--dviware/dvisvgm/src/TFM.hpp74
-rw-r--r--dviware/dvisvgm/src/TTFAutohint.cpp139
-rw-r--r--dviware/dvisvgm/src/TTFAutohint.hpp51
-rw-r--r--dviware/dvisvgm/src/TensorProductPatch.cpp548
-rw-r--r--dviware/dvisvgm/src/TensorProductPatch.hpp92
-rw-r--r--dviware/dvisvgm/src/Terminal.cpp208
-rw-r--r--dviware/dvisvgm/src/Terminal.hpp58
-rw-r--r--dviware/dvisvgm/src/ToUnicodeMap.cpp106
-rw-r--r--dviware/dvisvgm/src/ToUnicodeMap.hpp38
-rw-r--r--dviware/dvisvgm/src/TpicSpecialHandler.cpp364
-rw-r--r--dviware/dvisvgm/src/TpicSpecialHandler.hpp53
-rw-r--r--dviware/dvisvgm/src/TriangularPatch.cpp214
-rw-r--r--dviware/dvisvgm/src/TriangularPatch.hpp62
-rw-r--r--dviware/dvisvgm/src/TrueTypeFont.cpp175
-rw-r--r--dviware/dvisvgm/src/TrueTypeFont.hpp74
-rw-r--r--dviware/dvisvgm/src/Unicode.cpp186
-rw-r--r--dviware/dvisvgm/src/Unicode.hpp34
-rw-r--r--dviware/dvisvgm/src/VFActions.hpp37
-rw-r--r--dviware/dvisvgm/src/VFReader.cpp178
-rw-r--r--dviware/dvisvgm/src/VFReader.hpp59
-rw-r--r--dviware/dvisvgm/src/VectorIterator.hpp108
-rw-r--r--dviware/dvisvgm/src/VectorStream.hpp86
-rw-r--r--dviware/dvisvgm/src/XMLDocument.cpp62
-rw-r--r--dviware/dvisvgm/src/XMLDocument.hpp44
-rw-r--r--dviware/dvisvgm/src/XMLNode.cpp456
-rw-r--r--dviware/dvisvgm/src/XMLNode.hpp226
-rw-r--r--dviware/dvisvgm/src/XMLString.cpp105
-rw-r--r--dviware/dvisvgm/src/XMLString.hpp39
-rw-r--r--dviware/dvisvgm/src/XXHashFunction.hpp88
-rw-r--r--dviware/dvisvgm/src/ZLibOutputStream.hpp181
-rw-r--r--dviware/dvisvgm/src/dvisvgm.cpp478
-rw-r--r--dviware/dvisvgm/src/ffwrapper.c84
-rw-r--r--dviware/dvisvgm/src/ffwrapper.h37
-rw-r--r--dviware/dvisvgm/src/iapi.h300
-rw-r--r--dviware/dvisvgm/src/ierrors.h153
-rw-r--r--dviware/dvisvgm/src/macros.hpp39
-rw-r--r--dviware/dvisvgm/src/optimizer/AttributeExtractor.cpp176
-rw-r--r--dviware/dvisvgm/src/optimizer/AttributeExtractor.hpp64
-rw-r--r--dviware/dvisvgm/src/optimizer/DependencyGraph.hpp133
-rw-r--r--dviware/dvisvgm/src/optimizer/GroupCollapser.cpp165
-rw-r--r--dviware/dvisvgm/src/optimizer/GroupCollapser.hpp36
-rw-r--r--dviware/dvisvgm/src/optimizer/Makefile.am12
-rw-r--r--dviware/dvisvgm/src/optimizer/Makefile.in690
-rw-r--r--dviware/dvisvgm/src/optimizer/OptimizerModule.hpp30
-rw-r--r--dviware/dvisvgm/src/optimizer/RedundantElementRemover.cpp68
-rw-r--r--dviware/dvisvgm/src/optimizer/RedundantElementRemover.hpp29
-rw-r--r--dviware/dvisvgm/src/optimizer/SVGOptimizer.cpp105
-rw-r--r--dviware/dvisvgm/src/optimizer/SVGOptimizer.hpp59
-rw-r--r--dviware/dvisvgm/src/optimizer/TransformSimplifier.cpp149
-rw-r--r--dviware/dvisvgm/src/optimizer/TransformSimplifier.hpp36
-rw-r--r--dviware/dvisvgm/src/optimizer/WSNodeRemover.cpp45
-rw-r--r--dviware/dvisvgm/src/optimizer/WSNodeRemover.hpp30
-rw-r--r--dviware/dvisvgm/src/options.dtd49
-rw-r--r--dviware/dvisvgm/src/options.xml203
-rw-r--r--dviware/dvisvgm/src/psdefs.cpp119
-rw-r--r--dviware/dvisvgm/src/utility.cpp139
-rw-r--r--dviware/dvisvgm/src/utility.hpp117
-rw-r--r--dviware/dvisvgm/src/version.hpp.in28
-rw-r--r--dviware/dvisvgm/src/windows.hpp32
241 files changed, 40633 insertions, 0 deletions
diff --git a/dviware/dvisvgm/src/AGLTable.hpp b/dviware/dvisvgm/src/AGLTable.hpp
new file mode 100644
index 0000000000..d973d1186b
--- /dev/null
+++ b/dviware/dvisvgm/src/AGLTable.hpp
@@ -0,0 +1,4084 @@
+/*************************************************************************
+** AGLTable.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+/* The following table provides a compact mapping from PostScript character names
+ * to Unicode points. Instead of using the character names directly it maps the
+ * hash values (xxhash32) of the names to the corresponding code points.
+ * The character mapping is derived from the Adobe Glyph List (AGL):
+ * https://github.com/adobe-type-tools/agl-aglfn
+ * http://tug.ctan.org/macros/latex/contrib/pdfx/glyphtounicode-cmr.tex */
+
+#include <array>
+
+struct HashCodepointPair {
+ uint32_t hash;
+ uint32_t codepoint;
+};
+
+std::array<HashCodepointPair, 4048> hash2unicode {{
+ {0x001cf4a9, 0x0118}, // Eogonek
+ {0x003b11fb, 0x055f}, // abbreviationmarkarmenian
+ {0x0050a316, 0x0444}, // afii10086
+ {0x0066ddfe, 0x03a5}, // Upsilon
+ {0x0067a87d, 0x0026}, // ampersand
+ {0x007399ea, 0x2118}, // weierstrass
+ {0x009a8cbc, 0x30c8}, // tokatakana
+ {0x00a8ecc1, 0xfb93}, // gaffinalarabic
+ {0x00b0f8d6, 0x05b2}, // hatafpatahwidehebrew
+ {0x00b1b890, 0x2486}, // nineteenparen
+ {0x00b92975, 0x05a7}, // dargalefthebrew
+ {0x00c458a3, 0x2154}, // twothirds
+ {0x00e73988, 0x09b8}, // sabengali
+ {0x00f475c4, 0x03ee}, // Deicoptic
+ {0x00fa9974, 0x0303}, // tildecmb
+ {0x01012c9a, 0x05b4}, // hiriq
+ {0x0107d6e3, 0x0933}, // lladeva
+ {0x01127238, 0x3237}, // ideographiccongratulationparen
+ {0x011abc50, 0x0547}, // Shaarmenian
+ {0x01297545, 0x25aa}, // H18543
+ {0x012d44bc, 0x05d6}, // afii57670
+ {0x013044fb, 0x2205}, // emptyset
+ {0x01422ce1, 0x0486}, // psilipneumatacyrilliccmb
+ {0x01571757, 0xfe6b}, // atsmall
+ {0x01678eeb, 0x066b}, // decimalseparatorpersian
+ {0x018643fe, 0x091a}, // cadeva
+ {0x019e6772, 0x3148}, // cieuckorean
+ {0x019fa822, 0x05de}, // mem
+ {0x01cfa7b3, 0x0a3e}, // aamatragurmukhi
+ {0x01d7c979, 0x3007}, // ideographiczero
+ {0x020d70ad, 0x278b}, // twocircleinversesansserif
+ {0x0239eab1, 0x3131}, // kiyeokkorean
+ {0x023b0c79, 0xff87}, // nukatakanahalfwidth
+ {0x023deac8, 0x3183}, // yesieungpansioskorean
+ {0x02409db2, 0x1ed1}, // ocircumflexacute
+ {0x02574652, 0x0e1b}, // poplathai
+ {0x025b8acc, 0x0a47}, // eematragurmukhi
+ {0x02610d57, 0x2668}, // hotsprings
+ {0x0292f83b, 0x0e11}, // thonangmonthothai
+ {0x0293c9db, 0xff49}, // imonospace
+ {0x029e63cc, 0x21e7}, // arrowupwhite
+ {0x02a32a9d, 0x0275}, // obarred
+ {0x02a97908, 0x09f7}, // fournumeratorbengali
+ {0x02abe0be, 0x2556}, // SF210000
+ {0x02acd87a, 0x2461}, // twocircle
+ {0x02bb8927, 0x0154}, // Racute
+ {0x02bd043f, 0x3392}, // mhzsquare
+ {0x02bdc138, 0x304a}, // ohiragana
+ {0x02c40de7, 0xffe6}, // wonmonospace
+ {0x02dab625, 0x310c}, // lbopomofo
+ {0x02df8edf, 0x30e7}, // yosmallkatakana
+ {0x02e3dfc8, 0x3062}, // dihiragana
+ {0x0306380d, 0xff35}, // Umonospace
+ {0x03170204, 0x0495}, // ghemiddlehookcyrillic
+ {0x03182c9f, 0x25b4}, // blackuppointingsmalltriangle
+ {0x0319d343, 0x329e}, // ideographicprintcircle
+ {0x031ad266, 0x331e}, // kooposquare
+ {0x03274b72, 0x0018}, // controlCAN
+ {0x0334c4d5, 0x0aa7}, // dhagujarati
+ {0x03396436, 0x1ed0}, // Ocircumflexacute
+ {0x033b64c4, 0x047f}, // otcyrillic
+ {0x033e74dc, 0x1e44}, // Ndotaccent
+ {0x034ec8b1, 0x30b0}, // gukatakana
+ {0x037b5e30, 0x00b9}, // onesuperior
+ {0x03993e60, 0xff57}, // wmonospace
+ {0x03a136c3, 0x0e0b}, // sosothai
+ {0x03b0103c, 0x2270}, // notlessnorequal
+ {0x03c2a8d8, 0x1eb3}, // abrevehookabove
+ {0x03d234c7, 0xfea7}, // khahinitialarabic
+ {0x03f4da1a, 0x05b8}, // qamatsnarrowhebrew
+ {0x0410b525, 0x0138}, // kgreenlandic
+ {0x0433eb22, 0x014e}, // Obreve
+ {0x043f3fc5, 0x040a}, // afii10059
+ {0x044589fe, 0x0646}, // afii57446
+ {0x044ba421, 0x0585}, // oharmenian
+ {0x0454dddc, 0x0432}, // afii10067
+ {0x048b164e, 0x01c3}, // clickretroflex
+ {0x048e8b97, 0x0161}, // scaron
+ {0x0491732e, 0x3186}, // yeorinhieuhkorean
+ {0x049c65ac, 0x2121}, // telephone
+ {0x04a45907, 0x041a}, // afii10028
+ {0x04c4d94b, 0x05b5}, // tsere1e
+ {0x04c8ee7d, 0x042d}, // Ereversedcyrillic
+ {0x04de1db0, 0x0950}, // omdeva
+ {0x04f4d676, 0x0027}, // quotesingle
+ {0x0500f909, 0x0407}, // Yicyrillic
+ {0x0503fcb5, 0x00b1}, // plusminus
+ {0x05116c6a, 0x30fc}, // prolongedkana
+ {0x05302abd, 0x2025}, // twodotleader
+ {0x053ece0c, 0x3050}, // guhiragana
+ {0x05574c05, 0x09a3}, // nnabengali
+ {0x056bac6c, 0x30a3}, // ismallkatakana
+ {0x058218bb, 0x0386}, // Alphatonos
+ {0x058691a9, 0x33d2}, // squarelog
+ {0x059c61cd, 0x0436}, // zhecyrillic
+ {0x059eb4a3, 0x2085}, // fiveinferior
+ {0x05a47299, 0x320d}, // hieuhparenkorean
+ {0x05a53e96, 0x0282}, // shook
+ {0x05b0f8c3, 0x02b5}, // rhookturnedsuperior
+ {0x05cee53c, 0x201d}, // quotedblright
+ {0x05de47fd, 0x1e7f}, // vdotbelow
+ {0x05e340f3, 0x1e70}, // Tcircumflexbelow
+ {0x05e8321d, 0x0325}, // ringbelowcmb
+ {0x05ec5d36, 0x2471}, // eighteencircle
+ {0x05f03fff, 0x0ae0}, // rrvocalicgujarati
+ {0x060beb03, 0x0175}, // wcircumflex
+ {0x061ad8fc, 0x24ca}, // Ucircle
+ {0x0642035b, 0x0010}, // controlDLE
+ {0x0646584a, 0x003d}, // equal
+ {0x064874b1, 0x05d3}, // afii57667
+ {0x066433cf, 0x20a4}, // lira
+ {0x06823c6b, 0xfb4d}, // kafrafehebrew
+ {0x06894954, 0xff5e}, // asciitildemonospace
+ {0x069a405d, 0x00e6}, // ae
+ {0x06b00ffc, 0x0101}, // amacron
+ {0x06b72f51, 0x27e8}, // angbracketleftbig
+ {0x06be8647, 0x0442}, // afii10084
+ {0x06ec3366, 0xfe42}, // cornerbracketrightvertical
+ {0x0700a693, 0x0475}, // izhitsacyrillic
+ {0x07019244, 0xfb02}, // fl
+ {0x07072da3, 0x2299}, // circleot
+ {0x07099ef9, 0xfeae}, // rehfinalarabic
+ {0x0710dd39, 0x02de}, // rhotichookmod
+ {0x074aba74, 0x09af}, // yabengali
+ {0x07562010, 0x09bc}, // nuktabengali
+ {0x075a830a, 0x21e6}, // arrowleftwhite
+ {0x076312db, 0x2497}, // sixteenperiod
+ {0x0767cf10, 0x1ea8}, // Acircumflexhookabove
+ {0x076c3b34, 0x1ec3}, // ecircumflexhookabove
+ {0x076dbf41, 0x05b7}, // patah11
+ {0x07726745, 0x0e25}, // lolingthai
+ {0x078184fa, 0x00f7}, // divide
+ {0x0790751c, 0x2466}, // sevencircle
+ {0x0793d50d, 0x30bb}, // sekatakana
+ {0x07a1ce35, 0x0906}, // aadeva
+ {0x07ab20a8, 0x0ab3}, // llagujarati
+ {0x07e20c30, 0x017b}, // Zdot
+ {0x07e38c67, 0x33bb}, // nwsquare
+ {0x081dd122, 0x0a38}, // sagurmukhi
+ {0x082543e5, 0x33a0}, // cmsquaredsquare
+ {0x083d0b54, 0x3227}, // eightideographicparen
+ {0x08429fa7, 0x2591}, // ltshade
+ {0x084b888b, 0x311a}, // abopomofo
+ {0x085499c4, 0x0925}, // thadeva
+ {0x086a99d9, 0x01af}, // Uhorn
+ {0x087038eb, 0xfb20}, // ayinaltonehebrew
+ {0x089d739a, 0x005a}, // Z
+ {0x08a131c8, 0x096d}, // sevendeva
+ {0x08a6b099, 0x02a6}, // ts
+ {0x08b5de5a, 0x038a}, // Iotatonos
+ {0x08b78f6b, 0xff86}, // nikatakanahalfwidth
+ {0x08d57b6a, 0x0019}, // controlEM
+ {0x08ddb521, 0x3226}, // sevenideographicparen
+ {0x092aa224, 0x0a90}, // aigujarati
+ {0x092cd86d, 0x03d6}, // omega1
+ {0x09310ab8, 0x027f}, // rfishhookreversed
+ {0x094ceadc, 0x0047}, // G
+ {0x09751504, 0x038c}, // Omicrontonos
+ {0x09790f28, 0x33be}, // kwsquare
+ {0x09853aa3, 0x01c1}, // clicklateral
+ {0x09a03740, 0xfe5b}, // braceleftsmall
+ {0x09a4b050, 0x0ae8}, // twogujarati
+ {0x09d4b5eb, 0x3388}, // calsquare
+ {0x09f2217d, 0x00a9}, // copyright
+ {0x09f9df24, 0x1e0c}, // Ddotbelow
+ {0x0a040d76, 0x098a}, // uubengali
+ {0x0a1d800c, 0x0291}, // zcurl
+ {0x0a3b8eb5, 0x044b}, // yericyrillic
+ {0x0a46f2f1, 0x0284}, // dotlessjstrokehook
+ {0x0a5cb3b1, 0x30d6}, // bukatakana
+ {0x0a5ff1a8, 0xff6b}, // osmallkatakanahalfwidth
+ {0x0a67f8fb, 0x24b2}, // wparen
+ {0x0a704676, 0xfccc}, // lammeeminitialarabic
+ {0x0a8ba8e8, 0x0112}, // Emacron
+ {0x0a9b47dd, 0x306f}, // hahiragana
+ {0x0aa2156d, 0xfc0c}, // tehhahisolatedarabic
+ {0x0abb4ec1, 0x0441}, // afii10083
+ {0x0adbba15, 0x21c0}, // harpoonrightbarbup
+ {0x0ae79191, 0x01e1}, // adotmacron
+ {0x0af77d49, 0x09ea}, // fourbengali
+ {0x0b367d7a, 0x0421}, // Escyrillic
+ {0x0b4b7082, 0xff62}, // cornerbracketlefthalfwidth
+ {0x0b6abf22, 0x20aa}, // sheqel
+ {0x0b7f2b2d, 0x0a5a}, // ghhagurmukhi
+ {0x0b92d660, 0x32a6}, // ideographiclowcircle
+ {0x0b9e2621, 0x2665}, // heartsuitblack
+ {0x0ba5f00c, 0x03cc}, // omicrontonos
+ {0x0bae12ff, 0xff2a}, // Jmonospace
+ {0x0bd4abb3, 0x0254}, // oopen
+ {0x0bd8d304, 0x3215}, // ieungaparenkorean
+ {0x0bdad647, 0x0970}, // abbreviationsigndeva
+ {0x0bdb550e, 0x0669}, // ninehackarabic
+ {0x0be3cda3, 0x1e0b}, // ddotaccent
+ {0x0bf8ed4a, 0x09f2}, // rupeemarkbengali
+ {0x0bfa9d4e, 0x05b6}, // afii57795
+ {0x0c138c8e, 0x308f}, // wahiragana
+ {0x0c17017e, 0x02a5}, // dzcurl
+ {0x0c19fd92, 0x037a}, // ypogegrammeni
+ {0x0c255ae5, 0x0553}, // Piwrarmenian
+ {0x0c356707, 0x0625}, // afii57413
+ {0x0c678de3, 0x032a}, // bridgebelowcmb
+ {0x0c810887, 0x0a88}, // iigujarati
+ {0x0c83c594, 0x1e63}, // sdotbelow
+ {0x0c8f5261, 0x0164}, // Tcaron
+ {0x0cacee48, 0xfba8}, // hehinitialaltonearabic
+ {0x0cbb507c, 0x3036}, // circlepostalmark
+ {0x0cd99820, 0x05c0}, // paseqhebrew
+ {0x0cdb81c4, 0x24a2}, // gparen
+ {0x0ce8bb7e, 0x30d5}, // hukatakana
+ {0x0cf04968, 0x02be}, // ringhalfright
+ {0x0d0eb2f0, 0x315d}, // weokorean
+ {0x0d21bb72, 0x2550}, // SF430000
+ {0x0d47308f, 0x05d4}, // he
+ {0x0d747cfe, 0x04c3}, // Kahookcyrillic
+ {0x0d932b5b, 0x30d2}, // hikatakana
+ {0x0da4d862, 0x05b6}, // segol13
+ {0x0db7d6e4, 0x05d4}, // hehebrew
+ {0x0dc03ecb, 0x0a9c}, // jagujarati
+ {0x0dd6f75d, 0x09f6}, // threenumeratorbengali
+ {0x0de664af, 0x01fa}, // Aringacute
+ {0x0deddd7b, 0x017a}, // zacute
+ {0x0df6966e, 0x1e4f}, // otildedieresis
+ {0x0e0870a7, 0x2713}, // checkmark
+ {0x0e0aefc5, 0x05af}, // masoracirclehebrew
+ {0x0e15512a, 0xff43}, // cmonospace
+ {0x0e157c7d, 0x0166}, // Tbar
+ {0x0e34eac4, 0x06ba}, // afii57514
+ {0x0e359de3, 0x332b}, // paasentosquare
+ {0x0e35e57d, 0x01f4}, // Gacute
+ {0x0e6ec8aa, 0x0a40}, // iimatragurmukhi
+ {0x0e8140cb, 0x2318}, // propellor
+ {0x0e8e8ac7, 0x25aa}, // blacksmallsquare
+ {0x0e8ed92c, 0x05b3}, // hatafqamatsquarterhebrew
+ {0x0e9c1a93, 0x0149}, // quoterightn
+ {0x0eb0ce00, 0xff30}, // Pmonospace
+ {0x0ec7e019, 0xfc4b}, // noonjeemisolatedarabic
+ {0x0ed8b040, 0x33b5}, // nvsquare
+ {0x0edd0c59, 0x0e35}, // saraiithai
+ {0x0ee06289, 0x05b0}, // shevaquarterhebrew
+ {0x0efc1459, 0x09b6}, // shabengali
+ {0x0f02712c, 0x00ea}, // ecircumflex
+ {0x0f066e82, 0x33b3}, // mssquare
+ {0x0f1a6991, 0x24c4}, // Ocircle
+ {0x0f2768b1, 0x05b1}, // hatafsegolwidehebrew
+ {0x0f332d5e, 0x002e}, // period
+ {0x0f35dd15, 0x04b0}, // Ustraightstrokecyrillic
+ {0x0f432338, 0x0020}, // space
+ {0x0f433f21, 0xff7d}, // sukatakanahalfwidth
+ {0x0f610d68, 0x1e53}, // omacronacute
+ {0x0f61debf, 0x33b9}, // mvmegasquare
+ {0x0f8de5e5, 0xff4b}, // kmonospace
+ {0x0f984d6e, 0xfec0}, // dadmedialarabic
+ {0x0fcfb12d, 0x016c}, // Ubreve
+ {0x0fdfc487, 0x02a8}, // tccurl
+ {0x0ff64b0b, 0x0665}, // fivehackarabic
+ {0x1001b5d7, 0xfc48}, // meemmeemisolatedarabic
+ {0x100f790a, 0x20ac}, // Euro
+ {0x101477b7, 0x216a}, // Elevenroman
+ {0x1030f103, 0x0918}, // ghadeva
+ {0x103612b8, 0x2287}, // supersetorequal
+ {0x10459048, 0x2217}, // asteriskmath
+ {0x10529f46, 0x0e59}, // ninethai
+ {0x10656b29, 0x042a}, // afii10044
+ {0x10659a4d, 0x0041}, // A
+ {0x106ab99c, 0x0943}, // rvocalicvowelsigndeva
+ {0x10827219, 0x01e4}, // Gstroke
+ {0x1087cbb6, 0x318d}, // araeakorean
+ {0x10c49213, 0x094d}, // viramadeva
+ {0x10c6b04c, 0xfee8}, // noonmedialarabic
+ {0x10e1204e, 0x224c}, // allequal
+ {0x1112335e, 0x0e24}, // ruthai
+ {0x1113e9a7, 0x0a87}, // igujarati
+ {0x11140e23, 0xff50}, // pmonospace
+ {0x112533be, 0x0446}, // tsecyrillic
+ {0x1128968b, 0x03dc}, // Digammagreek
+ {0x113f9725, 0x1ea1}, // adotbelow
+ {0x11572f90, 0x3240}, // ideographicfestivalparen
+ {0x1159e298, 0x2484}, // seventeenparen
+ {0x115b5935, 0xff78}, // kukatakanahalfwidth
+ {0x115c3cb2, 0x3179}, // kapyeounssangpieupkorean
+ {0x11672a1e, 0x3051}, // kehiragana
+ {0x1179ac42, 0x00a0}, // nbspace
+ {0x1198b8ba, 0x05f1}, // vavyodhebrew
+ {0x11adf5a7, 0x24a9}, // nparen
+ {0x11aeb63a, 0x2272}, // lessorequivalent
+ {0x11ba40f5, 0x05ea}, // tavhebrew
+ {0x11c11092, 0x1e2c}, // Itildebelow
+ {0x11c8e4b3, 0x2660}, // spadesuitblack
+ {0x11d3eac3, 0x3060}, // dahiragana
+ {0x11d67798, 0xfb33}, // daletdagesh
+ {0x11e401c1, 0x05d6}, // zayin
+ {0x1216fef8, 0x328e}, // ideographmetalcircle
+ {0x1219f723, 0xff6e}, // yosmallkatakanahalfwidth
+ {0x1224d569, 0xfee4}, // meemmedialarabic
+ {0x1228548c, 0x05d0}, // alefhebrew
+ {0x1233afe9, 0x30df}, // mikatakana
+ {0x123a07fe, 0x05e6}, // tsadihebrew
+ {0x1242de49, 0x042b}, // Yericyrillic
+ {0x1246d709, 0x315f}, // wikorean
+ {0x126c42a3, 0x21d0}, // arrowleftdbl
+ {0x1278a8d5, 0x3087}, // yosmallhiragana
+ {0x129b0140, 0x0445}, // afii10087
+ {0x12a29be8, 0x03c5}, // upsilon
+ {0x12bdd9c7, 0x21a6}, // mapsto
+ {0x12d3e8f7, 0x30c0}, // dakatakana
+ {0x12def4bb, 0xfeba}, // sadfinalarabic
+ {0x12e22bb8, 0x33c2}, // amsquare
+ {0x1329e467, 0x0311}, // breveinvertedcmb
+ {0x132b7bd0, 0x2a04}, // unionmultitext
+ {0x132e0697, 0x01b3}, // Yhook
+ {0x134d9c31, 0x304c}, // gahiragana
+ {0x13597fb3, 0x04d9}, // afii10846
+ {0x135d6341, 0x33c8}, // dbsquare
+ {0x13635045, 0x0300}, // gravecmb
+ {0x1369554a, 0x207f}, // nsuperior
+ {0x1376f153, 0x0403}, // afii10052
+ {0x1384d3da, 0x05b4}, // hiriqnarrowhebrew
+ {0x13868087, 0x0636}, // afii57430
+ {0x138fca68, 0x0a30}, // ragurmukhi
+ {0x13a69297, 0x0917}, // gadeva
+ {0x13bcc567, 0x25d1}, // circlewithrighthalfblack
+ {0x13ccaf5f, 0x3061}, // tihiragana
+ {0x13dc1f9e, 0x05b1}, // hatafsegol
+ {0x13e2dbb5, 0x1e3a}, // Llinebelow
+ {0x140e7a7e, 0x0551}, // Coarmenian
+ {0x14117f5a, 0x1e77}, // ucircumflexbelow
+ {0x141d63ad, 0x1eda}, // Ohornacute
+ {0x142c024d, 0x013a}, // lacute
+ {0x143d707c, 0x05a6}, // merkhakefulalefthebrew
+ {0x146c75cf, 0x3275}, // ieungacirclekorean
+ {0x146f18bb, 0xfb36}, // zayindagesh
+ {0x147a2240, 0x1e81}, // wgrave
+ {0x1486cc9b, 0x054b}, // Jheharmenian
+ {0x14ac715c, 0x2473}, // twentycircle
+ {0x14b421a5, 0x0638}, // zaharabic
+ {0x14c795cf, 0x0051}, // Q
+ {0x14d1cd4b, 0x2284}, // notsubset
+ {0x14d2cd8a, 0x012f}, // iogonek
+ {0x14f2bc91, 0x1eab}, // acircumflextilde
+ {0x15045632, 0x05d1}, // bet
+ {0x1505dc02, 0x3083}, // yasmallhiragana
+ {0x152785c0, 0x30a1}, // asmallkatakana
+ {0x15642935, 0x23a9}, // braceleftbt
+ {0x1564e3f1, 0x05b9}, // holam26
+ {0x15771621, 0x042c}, // Softsigncyrillic
+ {0x159ac6ab, 0x0591}, // etnahtafoukhlefthebrew
+ {0x159b9dc9, 0x24c6}, // Qcircle
+ {0x15d1c25e, 0x04bf}, // chedescenderabkhasiancyrillic
+ {0x15dd6b0c, 0x25d9}, // whitecircleinverse
+ {0x15f64606, 0x33c5}, // cdsquare
+ {0x15f8ec13, 0x037e}, // questiongreek
+ {0x1602acd3, 0xff38}, // Xmonospace
+ {0x1610c2ad, 0x3181}, // yesieungkorean
+ {0x16393f6d, 0xfef6}, // lamalefmaddaabovefinalarabic
+ {0x164a5cd1, 0x0058}, // X
+ {0x165341dd, 0x02e0}, // gammasuperior
+ {0x167b1fac, 0xfebc}, // sadmedialarabic
+ {0x168b9d05, 0x04d5}, // aiecyrillic
+ {0x169cceb3, 0x005b}, // bracketleft
+ {0x16ad260d, 0x0253}, // bhook
+ {0x16b23c67, 0x3135}, // nieuncieuckorean
+ {0x16ba0a7a, 0x30a9}, // osmallkatakana
+ {0x16d5ac91, 0x3018}, // whitetortoiseshellbracketleft
+ {0x16f9045d, 0x0a16}, // khagurmukhi
+ {0x17093caa, 0x01e3}, // aemacron
+ {0x170fc75c, 0x306c}, // nuhiragana
+ {0x171b516a, 0x0a39}, // hagurmukhi
+ {0x17301afd, 0x3023}, // threehangzhou
+ {0x173c8456, 0x338b}, // nfsquare
+ {0x178d45ae, 0x337e}, // meizierasquare
+ {0x17a9e49b, 0x322a}, // ideographicmoonparen
+ {0x17ad5313, 0x09ab}, // phabengali
+ {0x17da2afa, 0x0335}, // strokeshortoverlaycmb
+ {0x17deda0e, 0x00ed}, // iacute
+ {0x17e36acb, 0x05b8}, // qamats
+ {0x17ef9c62, 0x0a05}, // agurmukhi
+ {0x180419cd, 0xfc62}, // shaddakasraarabic
+ {0x18120be8, 0x200b}, // zerowidthspace
+ {0x18175789, 0x05b3}, // hatafqamatswidehebrew
+ {0x181f5c91, 0x091f}, // ttadeva
+ {0x182bd096, 0x0148}, // ncaron
+ {0x183028bb, 0x04da}, // Schwadieresiscyrillic
+ {0x1840ed9a, 0xfea0}, // jeemmedialarabic
+ {0x18492236, 0x0e5a}, // angkhankhuthai
+ {0x1855a5e3, 0xfe5f}, // numbersignsmall
+ {0x185b816d, 0x0146}, // ncommaaccent
+ {0x187ab455, 0x0e42}, // saraothai
+ {0x1885706c, 0x3157}, // okorean
+ {0x18923bff, 0x2135}, // aleph
+ {0x18a5c10a, 0x2584}, // dnblock
+ {0x18a72a69, 0xfb3a}, // finalkafdageshhebrew
+ {0x18b82c53, 0x045c}, // afii10109
+ {0x18c46fec, 0x033c}, // seagullbelowcmb
+ {0x18d47bfd, 0x3298}, // ideographiclaborcircle
+ {0x18dbddd2, 0x0993}, // obengali
+ {0x18df8652, 0x2286}, // subsetorequal
+ {0x18ea53c7, 0x0663}, // afii57395
+ {0x18ec3f78, 0x05e8}, // afii57688
+ {0x18f0d507, 0xfe9f}, // jeeminitialarabic
+ {0x190a56aa, 0xfe5a}, // parenrightsmall
+ {0x1920c2c2, 0xff65}, // middledotkatakanahalfwidth
+ {0x192c8826, 0x2283}, // superset
+ {0x1940b4fd, 0x2251}, // geometricallyequal
+ {0x19427103, 0x2a01}, // circleplustext
+ {0x1946a31f, 0x05ae}, // zinorhebrew
+ {0x194ec3dd, 0xfb38}, // tetdageshhebrew
+ {0x195fdeed, 0x3013}, // getamark
+ {0x197f461f, 0x1e03}, // bdotaccent
+ {0x1994a487, 0x0481}, // koppacyrillic
+ {0x19985117, 0x339a}, // nmsquare
+ {0x19a1a98b, 0x00ad}, // sfthyphen
+ {0x19ad4aac, 0xfef2}, // yehfinalarabic
+ {0x19b22272, 0x3269}, // chieuchcirclekorean
+ {0x19b344e4, 0x0123}, // gcedilla
+ {0x19b3f208, 0x05b8}, // qamats27
+ {0x19d833fe, 0x02ba}, // dblprimemod
+ {0x19dea593, 0x0428}, // afii10042
+ {0x19f7c320, 0x04d1}, // abrevecyrillic
+ {0x19fa29b1, 0x327f}, // koreanstandardsymbol
+ {0x1a00d3da, 0x032b}, // dblarchinvertedbelowcmb
+ {0x1a260946, 0x0447}, // checyrillic
+ {0x1a287ed9, 0x0922}, // ddhadeva
+ {0x1a3b33cb, 0xfed7}, // qafinitialarabic
+ {0x1a3bf649, 0x05b2}, // hatafpatahhebrew
+ {0x1a4f3484, 0x0913}, // odeva
+ {0x1a533d39, 0x0306}, // brevecmb
+ {0x1ac76244, 0x0425}, // Khacyrillic
+ {0x1ac90440, 0x010f}, // dcaron
+ {0x1b240a74, 0x00d8}, // Oslash
+ {0x1b2b4015, 0xfe65}, // greatersmall
+ {0x1b33167a, 0xfe3e}, // dblanglebracketrightvertical
+ {0x1b342691, 0x017c}, // zdot
+ {0x1b39339b, 0x253c}, // SF050000
+ {0x1b3f9c21, 0x1e72}, // Udieresisbelow
+ {0x1b4399b2, 0x04c7}, // Enhookcyrillic
+ {0x1b5874ea, 0x091b}, // chadeva
+ {0x1b8c8992, 0x2122}, // trademark
+ {0x1ba72293, 0xff03}, // numbersignmonospace
+ {0x1bbb8fb3, 0x0477}, // izhitsadblgravecyrillic
+ {0x1bcb2bfd, 0x337d}, // taisyouerasquare
+ {0x1be98812, 0x24d4}, // ecircle
+ {0x1c079308, 0x09cc}, // auvowelsignbengali
+ {0x1c16ebae, 0x0126}, // Hbar
+ {0x1c2631dd, 0x3385}, // KBsquare
+ {0x1c56e166, 0xfd3f}, // parenrightaltonearabic
+ {0x1c94529b, 0x21b5}, // carriagereturn
+ {0x1cb10674, 0x0a27}, // dhagurmukhi
+ {0x1cb5367a, 0x0532}, // Benarmenian
+ {0x1cdb3e05, 0x2514}, // SF020000
+ {0x1ce17c08, 0xfea6}, // khahfinalarabic
+ {0x1ce29209, 0x02cc}, // verticallinelowmod
+ {0x1ce40996, 0xff3b}, // bracketleftmonospace
+ {0x1cf31612, 0x0598}, // zarqahebrew
+ {0x1d2559c8, 0x03b9}, // iota
+ {0x1d25e3ef, 0x043c}, // emcyrillic
+ {0x1d420ccb, 0x0430}, // afii10065
+ {0x1d954d85, 0x09a4}, // tabengali
+ {0x1da7ae8c, 0x00d7}, // multiply
+ {0x1db92094, 0x053d}, // Xeharmenian
+ {0x1dbbea92, 0x02e6}, // tonebarhighmod
+ {0x1dbc2a14, 0x2245}, // congruent
+ {0x1dbe86c5, 0x246d}, // fourteencircle
+ {0x1dc0644e, 0x2012}, // figuredash
+ {0x1ded853e, 0x01cc}, // nj
+ {0x1df7638a, 0x00e4}, // adieresis
+ {0x1dfc2837, 0x217a}, // elevenroman
+ {0x1e0312fa, 0x0290}, // zretroflexhook
+ {0x1e1332fd, 0x0287}, // tturned
+ {0x1e25355a, 0x0461}, // omegacyrillic
+ {0x1e38c8d8, 0x012a}, // Imacron
+ {0x1e56afb4, 0xfc61}, // shaddadammaarabic
+ {0x1e645fd0, 0x00b2}, // twosuperior
+ {0x1e6cddcb, 0x0563}, // gimarmenian
+ {0x1e7c9862, 0x338f}, // squarekg
+ {0x1e82a7c3, 0x2302}, // house
+ {0x1e860782, 0x0983}, // visargabengali
+ {0x1e917582, 0x09a2}, // ddhabengali
+ {0x1ea7d113, 0x031c}, // ringhalfleftbelowcmb
+ {0x1eae034a, 0x3122}, // anbopomofo
+ {0x1eae1716, 0x0434}, // afii10069
+ {0x1edf9913, 0x1e16}, // Emacronacute
+ {0x1ef123d5, 0x0413}, // Gecyrillic
+ {0x1f06ae25, 0xfb31}, // betdageshhebrew
+ {0x1f1df6d1, 0x3381}, // nasquare
+ {0x1f2383d4, 0x0004}, // controlEOT
+ {0x1f2a4214, 0xfdfa}, // sallallahoualayhewasallamarabic
+ {0x1f60dad4, 0x05e8}, // reshhebrew
+ {0x1f686fbe, 0xff2c}, // Lmonospace
+ {0x1f90aeb4, 0xff7b}, // sakatakanahalfwidth
+ {0x1f93ce2b, 0xff33}, // Smonospace
+ {0x1f9b52d4, 0x0323}, // dotbelowcmb
+ {0x1faa2fdf, 0x203e}, // overline
+ {0x1faf20cc, 0x02b9}, // primemod
+ {0x1fba9d24, 0x1e4a}, // Ncircumflexbelow
+ {0x1fc00e7e, 0x0050}, // P
+ {0x1fc99492, 0x3126}, // erbopomofo
+ {0x1feea4ab, 0x0623}, // afii57411
+ {0x20061138, 0x1e02}, // Bdotaccent
+ {0x200e9513, 0x030f}, // dblgravecmb
+ {0x201a6676, 0x2310}, // logicalnotreversed
+ {0x201e3ee9, 0x0310}, // candrabinducmb
+ {0x2024cfd1, 0x0475}, // afii10196
+ {0x2038e6d5, 0x307a}, // pehiragana
+ {0x205d0ec7, 0x03a0}, // Pi
+ {0x207bf81e, 0xff84}, // tokatakanahalfwidth
+ {0x2086c569, 0x00d2}, // Ograve
+ {0x208b3b40, 0xfed0}, // ghainmedialarabic
+ {0x209bfca6, 0x24d5}, // fcircle
+ {0x20a31ebd, 0x2489}, // twoperiod
+ {0x20abefc1, 0x0385}, // dialytikatonos
+ {0x20b8cfa5, 0x0443}, // afii10085
+ {0x20bc528f, 0x2042}, // asterism
+ {0x20ca67b9, 0x09a1}, // ddabengali
+ {0x20ce09b9, 0xfb7b}, // tchehfinalarabic
+ {0x20dd36ea, 0x207e}, // parenrightsuperior
+ {0x20e840a0, 0x30d4}, // pikatakana
+ {0x2111e869, 0x092a}, // padeva
+ {0x21185c0e, 0x028e}, // yturned
+ {0x2130f98f, 0x0aa0}, // tthagujarati
+ {0x215fc096, 0x000d}, // controlCR
+ {0x21622652, 0x249e}, // cparen
+ {0x21669982, 0x0181}, // Bhook
+ {0x217143de, 0x05b3}, // hatafqamats34
+ {0x2173a28b, 0x0295}, // glottalstopreversed
+ {0x218d5b62, 0x3232}, // ideographichaveparen
+ {0x21b37808, 0x2477}, // fourparen
+ {0x21bef9d7, 0x0e06}, // khorakhangthai
+ {0x21c0f31f, 0x013b}, // Lcommaaccent
+ {0x21ce0071, 0x2179}, // tenroman
+ {0x21d28f9e, 0x05e9}, // shin
+ {0x21f74f30, 0x043e}, // afii10080
+ {0x2203033d, 0x019d}, // Nhookleft
+ {0x22037801, 0x044b}, // afii10093
+ {0x220f1331, 0x05db}, // kafhebrew
+ {0x2221c36b, 0x06f6}, // sixpersian
+ {0x22351581, 0x059f}, // qarneyparahebrew
+ {0x223cb30f, 0xfc9f}, // behmeeminitialarabic
+ {0x225099c3, 0x3149}, // ssangcieuckorean
+ {0x22542ce4, 0x2207}, // nabla
+ {0x22702420, 0x3153}, // eokorean
+ {0x2274db8f, 0xff08}, // parenleftmonospace
+ {0x2282e86d, 0x00fa}, // uacute
+ {0x22a883d2, 0x25c7}, // whitediamond
+ {0x22c93a2b, 0x338d}, // mugsquare
+ {0x22e3ac9e, 0xff4c}, // lmonospace
+ {0x2320c842, 0x0277}, // omegalatinclosed
+ {0x2324e69c, 0x2665}, // heart
+ {0x2351e945, 0x0a8a}, // uugujarati
+ {0x2356706f, 0x0541}, // Jaarmenian
+ {0x23679572, 0x1ef1}, // uhorndotbelow
+ {0x236d79e4, 0x301e}, // quotedblprime
+ {0x237979f3, 0xff73}, // ukatakanahalfwidth
+ {0x23947e9f, 0x0266}, // hhook
+ {0x23b6327c, 0x304f}, // kuhiragana
+ {0x23b9408d, 0x2208}, // element
+ {0x23d291f5, 0x0639}, // ainarabic
+ {0x23d42e51, 0xff0b}, // plusmonospace
+ {0x23e15f1c, 0xff90}, // mikatakanahalfwidth
+ {0x23e70b3f, 0x0664}, // afii57396
+ {0x23e9f46b, 0xff80}, // takatakanahalfwidth
+ {0x23ea33c4, 0x30f1}, // wekatakana
+ {0x23f0f542, 0xff2f}, // Omonospace
+ {0x2415f58f, 0x3069}, // dohiragana
+ {0x2419b09a, 0x038f}, // Omegatonos
+ {0x241b0583, 0x0a9e}, // nyagujarati
+ {0x241f6494, 0x3105}, // bbopomofo
+ {0x2459d6f7, 0x0aea}, // fourgujarati
+ {0x24688db0, 0x02c1}, // glottalstopreversedmod
+ {0x24776c38, 0x0343}, // koroniscmb
+ {0x2479e0d2, 0x3021}, // onehangzhou
+ {0x249dd6ee, 0x0665}, // afii57397
+ {0x24c0efcc, 0x316d}, // rieulyeorinhieuhkorean
+ {0x24ccbd1b, 0x09e6}, // zerobengali
+ {0x24d11d48, 0x02c2}, // arrowheadleftmod
+ {0x24d6b19b, 0xfee7}, // nooninitialarabic
+ {0x24deab24, 0x25a4}, // squarehorizontalfill
+ {0x24e9b4e5, 0x21e4}, // arrowtableft
+ {0x24f5be18, 0x0308}, // dieresiscmb
+ {0x2527d2da, 0x300c}, // cornerbracketleft
+ {0x252ef6ac, 0x01de}, // Adieresismacron
+ {0x253f33be, 0x006e}, // n
+ {0x2543ce81, 0x05c1}, // shindothebrew
+ {0x254b8857, 0x0929}, // nnnadeva
+ {0x25725d01, 0x0496}, // Zhedescendercyrillic
+ {0x2592eb8c, 0x1e39}, // ldotbelowmacron
+ {0x25a29a9e, 0x000a}, // controlLF
+ {0x25bfabf1, 0x056c}, // liwnarmenian
+ {0x25c22a8d, 0x0927}, // dhadeva
+ {0x25c3f8ae, 0xff3e}, // asciicircummonospace
+ {0x25ca4638, 0x0576}, // nowarmenian
+ {0x25d83051, 0x0e19}, // nonuthai
+ {0x25e64388, 0x1ee5}, // udotbelow
+ {0x25eb8755, 0xff34}, // Tmonospace
+ {0x25ef68be, 0x0103}, // abreve
+ {0x25f0117e, 0x095f}, // yyadeva
+ {0x2614c40e, 0x0aaf}, // yagujarati
+ {0x262be20f, 0x0471}, // psicyrillic
+ {0x2637caf5, 0x0932}, // ladeva
+ {0x263e0b92, 0x09ac}, // babengali
+ {0x2652690c, 0x3137}, // tikeutkorean
+ {0x265be8ad, 0x01cf}, // Icaron
+ {0x265fdad4, 0x05b8}, // qamatshebrew
+ {0x26621b4d, 0x0072}, // r
+ {0x2669a6cb, 0x211c}, // Rfraktur
+ {0x26837556, 0x0048}, // H
+ {0x26a72a71, 0x3188}, // yoyaekorean
+ {0x26ab6d3a, 0x1ed9}, // ocircumflexdotbelow
+ {0x26af8089, 0x304e}, // gihiragana
+ {0x26b9c1d9, 0x0e30}, // saraathai
+ {0x26c43ea8, 0x0911}, // ocandradeva
+ {0x26caa803, 0x21d4}, // arrowdblboth
+ {0x26cb382e, 0x23ab}, // bracerighttp
+ {0x26f6f992, 0x04e4}, // Idieresiscyrillic
+ {0x26fb496b, 0x3351}, // rittorusquare
+ {0x27075678, 0x012e}, // Iogonek
+ {0x2731451f, 0x0408}, // afii10057
+ {0x273977ae, 0x0435}, // afii10070
+ {0x27487450, 0x007e}, // asciitilde
+ {0x275654ea, 0x099b}, // chabengali
+ {0x275a3e46, 0x09a0}, // tthabengali
+ {0x275a5b3b, 0x1ee1}, // ohorntilde
+ {0x27607db6, 0x05b4}, // hiriq2d
+ {0x27a2d18f, 0x05b9}, // holam32
+ {0x27b7bf17, 0x031f}, // plusbelowcmb
+ {0x27b7cc70, 0xfb2d}, // shindageshsindothebrew
+ {0x27d62d65, 0x200d}, // afii301
+ {0x27d8dd28, 0x00e8}, // egrave
+ {0x27e6d9df, 0x0271}, // mhook
+ {0x27f31ec3, 0x3271}, // rieulacirclekorean
+ {0x283f3216, 0x2075}, // fivesuperior
+ {0x28898020, 0x0281}, // Rsmallinverted
+ {0x288edd59, 0x24cc}, // Wcircle
+ {0x28d9fe2f, 0x0e41}, // saraaethai
+ {0x28dd4931, 0x3234}, // ideographicnameparen
+ {0x28e31924, 0x0a6c}, // sixgurmukhi
+ {0x28e4057d, 0xfc58}, // yehmeemisolatedarabic
+ {0x28f71acd, 0x0633}, // afii57427
+ {0x290ed42e, 0x1e18}, // Ecircumflexbelow
+ {0x29287ce1, 0x03d2}, // Upsilon1
+ {0x29346ac3, 0x2021}, // daggerdbl
+ {0x29371ded, 0x1e8f}, // ydotaccent
+ {0x29734fd7, 0x092d}, // bhadeva
+ {0x298e1b46, 0x01ea}, // Oogonek
+ {0x29958ce9, 0x0005}, // controlENQ
+ {0x29a8f130, 0x04d4}, // Aiecyrillic
+ {0x29ab0eda, 0x0146}, // ncedilla
+ {0x29c07bd0, 0x305e}, // zohiragana
+ {0x29d0597d, 0x262f}, // yinyang
+ {0x29d51dd2, 0x06d1}, // yehthreedotsbelowarabic
+ {0x29d5f944, 0x03e4}, // Feicoptic
+ {0x29e31b8f, 0x05b1}, // hatafsegolhebrew
+ {0x29e6e487, 0x30cc}, // nukatakana
+ {0x29e7f6bb, 0x0035}, // five
+ {0x2a0fca4c, 0x3085}, // yusmallhiragana
+ {0x2a239937, 0x0375}, // numeralsignlowergreek
+ {0x2a28d54e, 0x30aa}, // okatakana
+ {0x2a619f09, 0x2660}, // spade
+ {0x2a645de3, 0x3091}, // wehiragana
+ {0x2a64e363, 0x226e}, // notless
+ {0x2a6a5dc5, 0x3088}, // yohiragana
+ {0x2a72414c, 0x0e44}, // saraaimaimalaithai
+ {0x2a8e7779, 0x0ad0}, // omgujarati
+ {0x2ac0e180, 0x33a6}, // kmcubedsquare
+ {0x2ac17d0f, 0x00f3}, // oacute
+ {0x2ae749c7, 0x00e1}, // aacute
+ {0x2ae8215f, 0x001d}, // controlGS
+ {0x2ae8c5a6, 0x21c4}, // arrowrightoverleft
+ {0x2aedcd2c, 0x09fa}, // issharbengali
+ {0x2afb134c, 0xfb03}, // ffi
+ {0x2b27655e, 0x261c}, // pointingindexleftwhite
+ {0x2b3ff353, 0x2557}, // SF250000
+ {0x2b42ad49, 0xfe55}, // colonsmall
+ {0x2b5ac1a6, 0x0045}, // E
+ {0x2b84f841, 0x30a6}, // ukatakana
+ {0x2b91651d, 0x0319}, // righttackbelowcmb
+ {0x2b93dd53, 0x0661}, // afii57393
+ {0x2b9750e3, 0x2200}, // universal
+ {0x2b9c968a, 0x0315}, // commaaboverightcmb
+ {0x2bb5189c, 0x25e3}, // blacklowerlefttriangle
+ {0x2bd253b1, 0x09bf}, // ivowelsignbengali
+ {0x2be6415c, 0x05b3}, // hatafqamatshebrew
+ {0x2beb7c62, 0x013c}, // lcommaaccent
+ {0x2c0f9c0b, 0x0912}, // oshortdeva
+ {0x2c15e9f4, 0x091c}, // jadeva
+ {0x2c1b74f9, 0x2262}, // notidentical
+ {0x2c4944e4, 0x0393}, // Gamma
+ {0x2c91b61a, 0x0640}, // kashidaautonosidebearingarabic
+ {0x2c964b66, 0x334a}, // miribaarusquare
+ {0x2cb17e35, 0xfec6}, // zahfinalarabic
+ {0x2cd9d9ec, 0xfc6d}, // behnoonfinalarabic
+ {0x2cdfcd2a, 0x30cb}, // nikatakana
+ {0x2cf9daf5, 0x3228}, // nineideographicparen
+ {0x2d2e3883, 0x2190}, // arrowleft
+ {0x2d329c65, 0xfb3c}, // lameddageshhebrew
+ {0x2d39ea69, 0x019e}, // nlegrightlong
+ {0x2d3b565d, 0x05ea}, // afii57690
+ {0x2d456f79, 0x25e2}, // blacklowerrighttriangle
+ {0x2d52bd2b, 0x3326}, // dorusquare
+ {0x2d84140a, 0x0293}, // ezhcurl
+ {0x2d8d5b1d, 0x33d0}, // lmsquare
+ {0x2d95d169, 0x3155}, // yeokorean
+ {0x2d975eca, 0xfb04}, // ffl
+ {0x2d9ae85a, 0x0661}, // onehackarabic
+ {0x2d9b14ff, 0xfb6d}, // vehmedialarabic
+ {0x2da2ea79, 0x313d}, // rieulsioskorean
+ {0x2daa1b6f, 0x090a}, // uudeva
+ {0x2e04353d, 0x1ea9}, // acircumflexhookabove
+ {0x2e043b05, 0xff0a}, // asteriskmonospace
+ {0x2e10a2b1, 0x0374}, // numeralsigngreek
+ {0x2e1eaa7d, 0xfb49}, // shindagesh
+ {0x2e2c25c0, 0x0392}, // Beta
+ {0x2e356485, 0x09c2}, // uuvowelsignbengali
+ {0x2e4224af, 0x05e4}, // afii57684
+ {0x2e5e0023, 0x0a20}, // tthagurmukhi
+ {0x2e647759, 0x33d6}, // molsquare
+ {0x2e7c6436, 0x3048}, // ehiragana
+ {0x2e915a9d, 0xfe3b}, // blacklenticularbracketleftvertical
+ {0x2e9e14a6, 0x0454}, // ecyrillic
+ {0x2e9ef541, 0x0192}, // florin
+ {0x2ea8b970, 0x327a}, // phieuphacirclekorean
+ {0x2eb22aa1, 0x01dc}, // udieresisgrave
+ {0x2eb3bdc7, 0x027e}, // rfishhook
+ {0x2eb455b0, 0x25b6}, // blackrightpointingtriangle
+ {0x2ec430ea, 0x0078}, // x
+ {0x2ec8352a, 0x0143}, // Nacute
+ {0x2eea1838, 0x0170}, // Uhungarumlaut
+ {0x2efaa14d, 0x3015}, // tortoiseshellbracketright
+ {0x2f06c380, 0x0a91}, // ocandragujarati
+ {0x2f0e722f, 0x0021}, // exclam
+ {0x2f14e4ad, 0x0183}, // btopbar
+ {0x2f22b335, 0x305f}, // tahiragana
+ {0x2f247a45, 0x00b5}, // mu
+ {0x2f42e9c9, 0x0405}, // afii10054
+ {0x2f4b01e9, 0x03e0}, // Sampigreek
+ {0x2f51a2a5, 0x0591}, // etnahtahebrew
+ {0x2f5af1cc, 0xfec4}, // tahmedialarabic
+ {0x2f5c74c9, 0x30a4}, // ikatakana
+ {0x2f5e2692, 0x033f}, // dbloverlinecmb
+ {0x2f7e3ce0, 0xfb35}, // vavdagesh65
+ {0x2f7fe7da, 0x04ea}, // Obarreddieresiscyrillic
+ {0x2f8f84ed, 0x3019}, // whitetortoiseshellbracketright
+ {0x2f9c7ff4, 0x221f}, // orthogonal
+ {0x2fa13b0c, 0x0309}, // hookcmb
+ {0x2fa6d960, 0x0e05}, // khokhonthai
+ {0x2fb88e89, 0x0492}, // Ghestrokecyrillic
+ {0x2fe066dc, 0x24c8}, // Scircle
+ {0x2ff9eee3, 0x3231}, // ideographicstockparen
+ {0x30132e73, 0x2474}, // oneparen
+ {0x302d72c2, 0xfb2b}, // shinsindothebrew
+ {0x3033e257, 0x0416}, // Zhecyrillic
+ {0x3034a6d0, 0x0213}, // rinvertedbreve
+ {0x3043436d, 0x0208}, // Idblgrave
+ {0x3046485b, 0x0a41}, // umatragurmukhi
+ {0x30585e10, 0x0631}, // reharabic
+ {0x305b2089, 0x006d}, // m
+ {0x305dc9d7, 0x2480}, // thirteenparen
+ {0x3097f64c, 0x010c}, // Ccaron
+ {0x30b4b18c, 0x0e28}, // sosalathai
+ {0x30c0161e, 0x01d3}, // Ucaron
+ {0x30c9bc6e, 0x0a1e}, // nyagurmukhi
+ {0x30cd38cb, 0x33d8}, // pmsquare
+ {0x30d25d42, 0x2089}, // nineinferior
+ {0x30eada85, 0x2219}, // bulletoperator
+ {0x30f64fef, 0x20aa}, // sheqelhebrew
+ {0x310a4774, 0x337f}, // corporationsquare
+ {0x31158bc3, 0x339f}, // mmsquaredsquare
+ {0x3116a838, 0x0647}, // heharabic
+ {0x312db4ff, 0x2040}, // tie
+ {0x313237dd, 0xff32}, // Rmonospace
+ {0x313f3c20, 0x09df}, // yyabengali
+ {0x3154a912, 0x04ab}, // esdescendercyrillic
+ {0x31f1489a, 0x0643}, // afii57443
+ {0x31f928de, 0x05df}, // finalnun
+ {0x31fab77d, 0x1eea}, // Uhorngrave
+ {0x3207407e, 0x046b}, // yusbigcyrillic
+ {0x3238fa28, 0x3004}, // jis
+ {0x323ea229, 0x33ac}, // gpasquare
+ {0x324496b3, 0x33bf}, // mwmegasquare
+ {0x3248fa12, 0xfe5e}, // tortoiseshellbracketrightsmall
+ {0x324bc39e, 0x22da}, // lessequalorgreater
+ {0x3266451e, 0x30f2}, // wokatakana
+ {0x326ca1fd, 0x013f}, // Ldot
+ {0x32930f95, 0x046f}, // ksicyrillic
+ {0x329a975f, 0x0a0a}, // uugurmukhi
+ {0x329ed55c, 0x04e1}, // dzeabkhasiancyrillic
+ {0x32d855b8, 0x0ae9}, // threegujarati
+ {0x32e77f07, 0x22c2}, // intersectiontext
+ {0x32fd46a7, 0x041e}, // afii10032
+ {0x32fe41c6, 0x0667}, // afii57399
+ {0x330263f2, 0x3136}, // nieunhieuhkorean
+ {0x3303dbcb, 0x04b2}, // Hadescendercyrillic
+ {0x33042de7, 0x1e90}, // Zcircumflex
+ {0x335e3259, 0x3022}, // twohangzhou
+ {0x336106b8, 0x05d7}, // afii57671
+ {0x33821f87, 0x05e2}, // ayinhebrew
+ {0x33849fcd, 0x304d}, // kihiragana
+ {0x339cb29c, 0x03cb}, // upsilondieresis
+ {0x339dfd30, 0x2200}, // forall
+ {0x33a4598f, 0x0e10}, // thothanthai
+ {0x33dd39ff, 0x0437}, // zecyrillic
+ {0x33ea63c8, 0x24df}, // pcircle
+ {0x33f59002, 0x0285}, // eshsquatreversed
+ {0x340746a2, 0xff4a}, // jmonospace
+ {0x3425ba3a, 0x056d}, // xeharmenian
+ {0x342d52b5, 0x25cb}, // whitecircle
+ {0x34399add, 0x0034}, // four
+ {0x343b0ff5, 0x1e8b}, // xdotaccent
+ {0x343f59f3, 0x0437}, // afii10073
+ {0x344b950b, 0x031d}, // uptackbelowcmb
+ {0x34515ec0, 0x01ca}, // NJ
+ {0x345791f2, 0x027c}, // rlongleg
+ {0x346086da, 0x2286}, // reflexsubset
+ {0x3463147c, 0x1e69}, // sdotbelowdotaccent
+ {0x3490ad97, 0x047a}, // Omegaroundcyrillic
+ {0x34a7b989, 0x006f}, // o
+ {0x34a88183, 0x2640}, // female
+ {0x34b5f401, 0x016a}, // Umacron
+ {0x34bab99c, 0x24e0}, // qcircle
+ {0x34c88fb4, 0x2312}, // arc
+ {0x34c908a5, 0x1eb4}, // Abrevetilde
+ {0x34df3d1c, 0x00a1}, // exclamdown
+ {0x34e969fb, 0x05a0}, // telishagedolahebrew
+ {0x34f9cd37, 0x010b}, // cdot
+ {0x35188ac4, 0xfeac}, // thalfinalarabic
+ {0x351bf85e, 0x0257}, // dhook
+ {0x351e7136, 0x0482}, // thousandcyrillic
+ {0x352bce90, 0x00a3}, // sterling
+ {0x35378756, 0x2591}, // shadelight
+ {0x35440d94, 0x040c}, // Kjecyrillic
+ {0x354dad21, 0x0129}, // itilde
+ {0x35514624, 0x00d6}, // Odieresis
+ {0x3596098d, 0xfb9f}, // noonghunnafinalarabic
+ {0x359e9c03, 0x0418}, // Iicyrillic
+ {0x359f6846, 0x1ebc}, // Etilde
+ {0x35a9ba78, 0x0a95}, // kagujarati
+ {0x35b65af3, 0x33af}, // radoverssquaredsquare
+ {0x35ce2a2d, 0x066a}, // afii57381
+ {0x35ddec6f, 0x00eb}, // edieresis
+ {0x35e5fe3b, 0x3118}, // cbopomofo
+ {0x3613bad2, 0x011b}, // ecaron
+ {0x361cb4c9, 0xfb6b}, // vehfinalarabic
+ {0x361e70b8, 0x0043}, // C
+ {0x362459fb, 0xfe97}, // tehinitialarabic
+ {0x362c9d3a, 0x02b0}, // hsuperior
+ {0x36678fdf, 0x2326}, // deleteright
+ {0x3676afc0, 0x0ac9}, // ocandravowelsigngujarati
+ {0x368bf72e, 0x0038}, // eight
+ {0x36aaad0e, 0x307b}, // hohiragana
+ {0x36d3bc14, 0x0491}, // afii10098
+ {0x36d54fb7, 0x015a}, // Sacute
+ {0x37117bac, 0x059b}, // tevirlefthebrew
+ {0x372986de, 0x05f3}, // gereshhebrew
+ {0x373e5e3f, 0xff27}, // Gmonospace
+ {0x37866f5c, 0x00df}, // germandbls
+ {0x37a73b5a, 0xfee0}, // lammedialarabic
+ {0x37a77cc1, 0x030b}, // hungarumlautcmb
+ {0x37b36429, 0xff68}, // ismallkatakanahalfwidth
+ {0x37b7557e, 0x001c}, // controlFS
+ {0x37c2175b, 0xfcca}, // lamhahinitialarabic
+ {0x37c23820, 0x24c5}, // Pcircle
+ {0x37d669b4, 0x090e}, // eshortdeva
+ {0x37e460db, 0x308d}, // rohiragana
+ {0x37e5061e, 0x04a3}, // endescendercyrillic
+ {0x37f67ca7, 0x029a}, // eopenclosed
+ {0x381040c4, 0x310d}, // gbopomofo
+ {0x381640e0, 0xffe3}, // macronmonospace
+ {0x381c7e4d, 0x1ece}, // Ohookabove
+ {0x38201bde, 0x0961}, // llvocalicdeva
+ {0x38291591, 0x0e2e}, // honokhukthai
+ {0x3839681b, 0x3055}, // sahiragana
+ {0x3863c9a6, 0x208e}, // parenrightinferior
+ {0x386cda71, 0x01f0}, // jcaron
+ {0x38746563, 0x01b0}, // uhorn
+ {0x38880d1d, 0x0198}, // Khook
+ {0x38885f68, 0x091d}, // jhadeva
+ {0x388ccfdd, 0x30e3}, // yasmallkatakana
+ {0x3896be1c, 0x0025}, // percent
+ {0x38a80af1, 0x306d}, // nehiragana
+ {0x38bcbef5, 0x05b7}, // afii57798
+ {0x38bebcf5, 0x2552}, // SF510000
+ {0x38ce8c39, 0x059a}, // yetivhebrew
+ {0x38cea50a, 0x24d6}, // gcircle
+ {0x38d9b559, 0x3003}, // dittomark
+ {0x38de4662, 0x1e7c}, // Vtilde
+ {0x38e73ed2, 0x01c0}, // clickdental
+ {0x38efc9e4, 0x04a6}, // Pemiddlehookcyrillic
+ {0x391e728b, 0x099a}, // cabengali
+ {0x392c00af, 0x3305}, // intisquare
+ {0x3934b5de, 0x0e43}, // saraaimaimuanthai
+ {0x3946429a, 0x3010}, // blacklenticularbracketleft
+ {0x396642a3, 0x322d}, // ideographicwoodparen
+ {0x396b44dd, 0x0a3c}, // nuktagurmukhi
+ {0x396d8b52, 0x0930}, // radeva
+ {0x3989511a, 0xfc5e}, // shaddadammatanarabic
+ {0x39918d40, 0x30b6}, // zakatakana
+ {0x399ac15a, 0x0283}, // esh
+ {0x39aa6d90, 0x0a83}, // visargagujarati
+ {0x39b06752, 0x004a}, // J
+ {0x39d0b19b, 0x00ae}, // registered
+ {0x39d5a1b1, 0x019c}, // Mturned
+ {0x39e226d5, 0x05e3}, // afii57683
+ {0x39f864d5, 0x2462}, // threecircle
+ {0x39fd88e8, 0x05a8}, // qadmahebrew
+ {0x3a029acd, 0x338e}, // squaremg
+ {0x3a0e66b8, 0x1ee6}, // Uhookabove
+ {0x3a1089b2, 0x0073}, // s
+ {0x3a3b69ae, 0x317c}, // siostikeutkorean
+ {0x3a3c0e00, 0xff9d}, // nkatakanahalfwidth
+ {0x3a40856d, 0x25a0}, // blacksquare
+ {0x3a7498f3, 0x05e2}, // ayin
+ {0x3a777405, 0x0945}, // ecandravowelsigndeva
+ {0x3a846086, 0xff9c}, // wakatakanahalfwidth
+ {0x3aa3cfcb, 0x0960}, // rrvocalicdeva
+ {0x3ab26d21, 0x062f}, // dalarabic
+ {0x3ade6670, 0x0440}, // afii10082
+ {0x3afe4407, 0x25a9}, // squarediagonalcrosshatchfill
+ {0x3b0367b4, 0x0a6b}, // fivegurmukhi
+ {0x3b43910b, 0x0e36}, // sarauethai
+ {0x3b4774fb, 0xfb35}, // vavdageshhebrew
+ {0x3b6394a8, 0x30e0}, // mukatakana
+ {0x3b722aff, 0x02bf}, // ringhalfleft
+ {0x3b86faf7, 0x1e31}, // kacute
+ {0x3b8c9510, 0x0926}, // dadeva
+ {0x3b8f536a, 0x0564}, // daarmenian
+ {0x3b919910, 0x02b4}, // rturnedsuperior
+ {0x3b92b9ea, 0x1e4b}, // ncircumflexbelow
+ {0x3b9a0136, 0x2468}, // ninecircle
+ {0x3b9a26e8, 0x0637}, // taharabic
+ {0x3bf4dcc7, 0xfea3}, // hahinitialarabic
+ {0x3bf5a3f3, 0x301d}, // quotedblprimereversed
+ {0x3c2679f3, 0x25c3}, // whiteleftpointingsmalltriangle
+ {0x3c4101c7, 0x0a2b}, // phagurmukhi
+ {0x3c47c401, 0x0196}, // Iotaafrican
+ {0x3c5c7654, 0x0910}, // aideva
+ {0x3c6261b4, 0x322e}, // ideographicmetalparen
+ {0x3c6e58f4, 0x05d5}, // vavhebrew
+ {0x3c89c6b7, 0x2192}, // arrowright
+ {0x3c9425ca, 0x0666}, // sixhackarabic
+ {0x3c9b6897, 0xff0e}, // periodmonospace
+ {0x3ca31461, 0x01d1}, // Ocaron
+ {0x3ca4227b, 0x0120}, // Gdot
+ {0x3ca9c5ab, 0x0345}, // ypogegrammenigreekcmb
+ {0x3cb5e1bf, 0x3146}, // ssangsioskorean
+ {0x3cb6098f, 0x25b9}, // whiterightpointingsmalltriangle
+ {0x3ccd3832, 0xfc60}, // shaddafathaarabic
+ {0x3cfd6c60, 0x305a}, // zuhiragana
+ {0x3cfe2d05, 0x018f}, // Schwa
+ {0x3d0581a8, 0x313c}, // rieulpieupkorean
+ {0x3d200141, 0x30c9}, // dokatakana
+ {0x3d25bc32, 0x2116}, // numero
+ {0x3d292466, 0x05b1}, // hatafsegol17
+ {0x3d30abad, 0x0279}, // rturned
+ {0x3d3a35a3, 0x0252}, // ascriptturned
+ {0x3d3c2f4b, 0x309d}, // iterationhiragana
+ {0x3d44ad12, 0x3210}, // tikeutaparenkorean
+ {0x3d4a2f11, 0x0187}, // Chook
+ {0x3d50ceda, 0x21a8}, // arrowupdnbse
+ {0x3d59a63c, 0x30e2}, // mokatakana
+ {0x3d6d45f8, 0x0688}, // ddalarabic
+ {0x3d794ead, 0x04c8}, // enhookcyrillic
+ {0x3d888246, 0x300d}, // cornerbracketright
+ {0x3d93ad95, 0x0171}, // uhungarumlaut
+ {0x3d987773, 0x03c6}, // phi
+ {0x3da1e076, 0xfb2a}, // shinshindot
+ {0x3da24bf2, 0x004d}, // M
+ {0x3da45134, 0xff3d}, // bracketrightmonospace
+ {0x3dac6cb7, 0x212e}, // estimated
+ {0x3db24f7f, 0x04eb}, // obarreddieresiscyrillic
+ {0x3ddbfa17, 0xfe64}, // lesssmall
+ {0x3de84dee, 0x3387}, // GBsquare
+ {0x3de8cd4d, 0x02c9}, // firsttonechinese
+ {0x3deb36dc, 0x0570}, // hoarmenian
+ {0x3e08b864, 0x1e1c}, // Ecedillabreve
+ {0x3e2966c3, 0x25a7}, // squareupperlefttolowerrightfill
+ {0x3e2ad069, 0x0125}, // hcircumflex
+ {0x3e2b18e7, 0x05d2}, // gimelhebrew
+ {0x3e2ddf1a, 0x00fb}, // ucircumflex
+ {0x3e4b0d7f, 0x2478}, // fiveparen
+ {0x3e5c6b2e, 0x2086}, // sixinferior
+ {0x3e78c213, 0x03f3}, // yotgreek
+ {0x3e7bef16, 0x063a}, // ghainarabic
+ {0x3e9c0cab, 0x0e0d}, // yoyingthai
+ {0x3ea9e6c5, 0x044e}, // afii10096
+ {0x3ec30c4d, 0x0168}, // Utilde
+ {0x3ed6d505, 0x3108}, // fbopomofo
+ {0x3edc9801, 0x031e}, // downtackbelowcmb
+ {0x3edf2653, 0x04ba}, // Shhacyrillic
+ {0x3edfbd48, 0x0959}, // khhadeva
+ {0x3ee5a28f, 0x0342}, // perispomenigreekcmb
+ {0x3ef2c5ca, 0x05df}, // finalnunhebrew
+ {0x3efcef55, 0x09a7}, // dhabengali
+ {0x3f19d4d4, 0x30e5}, // yusmallkatakana
+ {0x3f35ecb4, 0x0a0f}, // eegurmukhi
+ {0x3f36dce9, 0x061b}, // afii57403
+ {0x3f5ead94, 0x0456}, // icyrillic
+ {0x3f61f37a, 0x1ea3}, // ahookabove
+ {0x3f76f3d1, 0x0214}, // Udblgrave
+ {0x3f817391, 0x314b}, // khieukhkorean
+ {0x3f8b0c34, 0x30c7}, // dekatakana
+ {0x3fa4349b, 0x0e1d}, // fofathai
+ {0x3fa5f151, 0x03de}, // Koppagreek
+ {0x3fbf7ccb, 0x01ae}, // Tretroflexhook
+ {0x3fddfa91, 0x22ce}, // curlyor
+ {0x3fe060fb, 0x0147}, // Ncaron
+ {0x3fe534eb, 0x0e12}, // thophuthaothai
+ {0x4016947c, 0x246b}, // twelvecircle
+ {0x401a74d3, 0x06f3}, // threepersian
+ {0x40663d0c, 0xfeb2}, // seenfinalarabic
+ {0x4069b3b1, 0x24ab}, // pparen
+ {0x407a7b83, 0x0531}, // Aybarmenian
+ {0x407de2ef, 0x06d5}, // afii57534
+ {0x40882350, 0x0305}, // overlinecmb
+ {0x4096d7d2, 0x006a}, // j
+ {0x409d1b5a, 0xff97}, // rakatakanahalfwidth
+ {0x40b0365e, 0x092f}, // yadeva
+ {0x40e21552, 0x30de}, // makatakana
+ {0x40eb54f4, 0x046d}, // yusbigiotifiedcyrillic
+ {0x413f6e7c, 0x05b4}, // hiriq21
+ {0x4144d56c, 0x016d}, // ubreve
+ {0x414507c4, 0x05b9}, // holamnarrowhebrew
+ {0x414a0074, 0x3336}, // hekutaarusquare
+ {0x4156eb7c, 0x3123}, // enbopomofo
+ {0x415d9061, 0xff42}, // bmonospace
+ {0x415fae27, 0x0398}, // Theta
+ {0x4161c806, 0x09e1}, // llvocalicbengali
+ {0x416a2ede, 0x0e04}, // khokhwaithai
+ {0x4178dd24, 0x2243}, // asymptoticallyequal
+ {0x41a22b2c, 0x007c}, // bar
+ {0x41a40813, 0xff2d}, // Mmonospace
+ {0x41a4780c, 0x0472}, // afii10147
+ {0x41c544c2, 0x1ee7}, // uhookabove
+ {0x41c76cd5, 0x05d7}, // het
+ {0x41cb5b30, 0x02db}, // ogonek
+ {0x41da6e39, 0x098c}, // lvocalicbengali
+ {0x41e154b7, 0x1edd}, // ohorngrave
+ {0x41e44ef7, 0x0aaa}, // pagujarati
+ {0x41efdfc2, 0x33c1}, // mohmsquare
+ {0x42320627, 0x2495}, // fourteenperiod
+ {0x4235d221, 0xfecf}, // ghaininitialarabic
+ {0x423f9221, 0x05a7}, // dargahebrew
+ {0x4247685c, 0x01ff}, // oslashacute
+ {0x4252dd77, 0x02c7}, // caron
+ {0x42716524, 0x0069}, // i
+ {0x42737aaf, 0x3081}, // mehiragana
+ {0x427d3f50, 0x04a9}, // haabkhasiancyrillic
+ {0x42803db2, 0x0030}, // zero
+ {0x42813ae4, 0x1e24}, // Hdotbelow
+ {0x428fedda, 0xfb44}, // pedagesh
+ {0x42bba9f5, 0x30f0}, // wikatakana
+ {0x42bc1b07, 0x25c4}, // triaglf
+ {0x42bed72c, 0x33c0}, // kohmsquare
+ {0x42bf360e, 0x1e6e}, // Tlinebelow
+ {0x42d74152, 0x0578}, // voarmenian
+ {0x42e554b2, 0x013b}, // Lcedilla
+ {0x42ed7ca6, 0x0698}, // jeharabic
+ {0x42f02b62, 0xfb95}, // gafmedialarabic
+ {0x42f35290, 0x0064}, // d
+ {0x42fb2842, 0x3120}, // aubopomofo
+ {0x42fc57be, 0x3110}, // jbopomofo
+ {0x42fd43ba, 0x044c}, // afii10094
+ {0x42fdb31a, 0x0070}, // p
+ {0x4305bc9e, 0x0920}, // tthadeva
+ {0x4306eed3, 0x015d}, // scircumflex
+ {0x430c20fb, 0x215e}, // seveneighths
+ {0x430ddad4, 0xff20}, // atmonospace
+ {0x431e0706, 0x00dc}, // Udieresis
+ {0x43221a39, 0x1e91}, // zcircumflex
+ {0x4328cb01, 0x339d}, // squarecm
+ {0x432e75ab, 0x055d}, // commaarmenian
+ {0x43399322, 0x026c}, // lbelt
+ {0x434b10a7, 0x0a86}, // aagujarati
+ {0x435f906e, 0xfb3e}, // memdageshhebrew
+ {0x436008b6, 0x3064}, // tuhiragana
+ {0x436f4b68, 0xfeaa}, // dalfinalarabic
+ {0x439bf74d, 0xff8b}, // hikatakanahalfwidth
+ {0x43a7e4d1, 0x0635}, // afii57429
+ {0x43d0d1da, 0x0627}, // afii57415
+ {0x43d651b4, 0x2663}, // clubsuitblack
+ {0x43d755d6, 0x2212}, // minus
+ {0x43dfb761, 0x0592}, // segoltahebrew
+ {0x4405f04b, 0x05be}, // afii57645
+ {0x4425746a, 0x0a18}, // ghagurmukhi
+ {0x44317cf4, 0x2662}, // diamondsuitwhite
+ {0x443620cd, 0x00fe}, // thorn
+ {0x444b0abf, 0x0467}, // yuslittlecyrillic
+ {0x444f60e7, 0x007f}, // controlDEL
+ {0x445a3b6e, 0x0447}, // afii10089
+ {0x4461957b, 0x041c}, // Emcyrillic
+ {0x4464a19f, 0x0acb}, // ovowelsigngujarati
+ {0x4483f355, 0x300f}, // whitecornerbracketright
+ {0x4487c491, 0x3342}, // hoonsquare
+ {0x4492e703, 0x0624}, // afii57412
+ {0x44979567, 0x0a25}, // thagurmukhi
+ {0x449b4678, 0xfec3}, // tahinitialarabic
+ {0x44a78c72, 0x1eef}, // uhorntilde
+ {0x44b414b4, 0xff0c}, // commamonospace
+ {0x44d687fa, 0x1e06}, // Blinebelow
+ {0x45116064, 0x2592}, // shade
+ {0x453aa0af, 0x09f0}, // ramiddlediagonalbengali
+ {0x4546a724, 0x05d0}, // afii57664
+ {0x454cef44, 0x2642}, // mars
+ {0x455bcfc1, 0x041d}, // afii10031
+ {0x457de97e, 0x3184}, // kapyeounphieuphkorean
+ {0x457e9e97, 0x1ee8}, // Uhornacute
+ {0x459f0c78, 0x23a7}, // bracelefttp
+ {0x45b3d9fd, 0x0a22}, // ddhagurmukhi
+ {0x45b6c88f, 0x029b}, // Gsmallhook
+ {0x45b98e95, 0x2163}, // Fourroman
+ {0x45c29649, 0x1ef4}, // Ydotbelow
+ {0x45c5ae6b, 0x0165}, // tcaron
+ {0x45d73e08, 0x0a09}, // ugurmukhi
+ {0x45e50e5b, 0x0384}, // tonos
+ {0x45f6e82c, 0x057f}, // tiwnarmenian
+ {0x45f7d5e0, 0x2084}, // fourinferior
+ {0x46038ece, 0x00b6}, // paragraph
+ {0x460c35ae, 0x05b8}, // qamats10
+ {0x460ca9f0, 0x30e8}, // yokatakana
+ {0x4611c6d0, 0x000b}, // controlVT
+ {0x461bc854, 0x0194}, // Gammaafrican
+ {0x46271982, 0x23ac}, // bracerightmid
+ {0x4652be4f, 0x1e6d}, // tdotbelow
+ {0x46541398, 0x0111}, // dmacron
+ {0x465ea2f8, 0x3202}, // tikeutparenkorean
+ {0x46611d40, 0x05bb}, // qubuts31
+ {0x466452b7, 0x321b}, // hieuhaparenkorean
+ {0x467a9a55, 0x055b}, // emphasismarkarmenian
+ {0x46ab407a, 0x322b}, // ideographicfireparen
+ {0x46ab921d, 0x3076}, // buhiragana
+ {0x46ba3911, 0x1e45}, // ndotaccent
+ {0x46bce40e, 0xfb4f}, // aleflamedhebrew
+ {0x46c4dd5b, 0x0433}, // afii10068
+ {0x46ca3a49, 0x2791}, // eightcircleinversesansserif
+ {0x46e3006c, 0x055c}, // exclamarmenian
+ {0x46f10ed2, 0x222a}, // union
+ {0x46f3948a, 0x05d1}, // bethebrew
+ {0x46f5f918, 0x05c3}, // afii57658
+ {0x46f9c8ca, 0x2195}, // arrowupdn
+ {0x470d662e, 0x042e}, // IUcyrillic
+ {0x470da4b8, 0x0453}, // gjecyrillic
+ {0x471d219d, 0x00f4}, // ocircumflex
+ {0x472c971d, 0x1e4d}, // otildeacute
+ {0x47542f2d, 0x0107}, // cacute
+ {0x47849b51, 0x05e5}, // finaltsadi
+ {0x478eb915, 0x014f}, // obreve
+ {0x47b12f1d, 0x3009}, // anglebracketright
+ {0x47b78334, 0x09a5}, // thabengali
+ {0x47ee62a0, 0x04f1}, // udieresiscyrillic
+ {0x480265ce, 0x06f2}, // twopersian
+ {0x48175191, 0x0130}, // Idotaccent
+ {0x481e50de, 0xfb32}, // gimeldagesh
+ {0x4825c60d, 0x02c3}, // arrowheadrightmod
+ {0x482626d7, 0x09ee}, // eightbengali
+ {0x4826d3e4, 0x3391}, // khzsquare
+ {0x48270352, 0x1e42}, // Mdotbelow
+ {0x4848966d, 0x05b2}, // hatafpatah
+ {0x485d5052, 0x057d}, // seharmenian
+ {0x48908e05, 0xff28}, // Hmonospace
+ {0x48a3aad1, 0x1ec8}, // Ihookabove
+ {0x48adcc47, 0x0160}, // Scaron
+ {0x48b11825, 0x1e3f}, // macute
+ {0x48b31eb3, 0x02a3}, // dzaltone
+ {0x48bbab6b, 0x1e68}, // Sdotbelowdotaccent
+ {0x48d0cd97, 0x0460}, // Omegacyrillic
+ {0x48ed1289, 0x3180}, // ssangieungkorean
+ {0x48f1ea86, 0x007c}, // verticalbar
+ {0x49188fb6, 0x261d}, // pointingindexupwhite
+ {0x491e8c30, 0x2562}, // SF200000
+ {0x4928f75b, 0x1e3d}, // lcircumflexbelow
+ {0x49299271, 0x1eee}, // Uhorntilde
+ {0x49314f7c, 0x1e96}, // hlinebelow
+ {0x493c6957, 0x01ec}, // Oogonekmacron
+ {0x497b2a29, 0x03c8}, // psi
+ {0x49877605, 0x1ecf}, // ohookabove
+ {0x49a6c904, 0x00d5}, // Otilde
+ {0x49a8fbe4, 0x227a}, // precedes
+ {0x49b28bf6, 0x011f}, // gbreve
+ {0x49c941c8, 0x0630}, // thalarabic
+ {0x49cf949f, 0x0011}, // controlDC1
+ {0x49d53679, 0x053e}, // Caarmenian
+ {0x49e41b40, 0x0117}, // edotaccent
+ {0x4a059748, 0x2329}, // angleleft
+ {0x4a0a939e, 0x02dc}, // ilde
+ {0x4a0dc7cd, 0x0273}, // nhookretroflex
+ {0x4a1b8688, 0x0a8f}, // egujarati
+ {0x4a67a4cd, 0x33c6}, // coverkgsquare
+ {0x4a6dc3e0, 0x017c}, // zdotaccent
+ {0x4a8f25d9, 0x0134}, // Jcircumflex
+ {0x4a911686, 0x042a}, // Hardsigncyrillic
+ {0x4aa9a643, 0x220c}, // notcontains
+ {0x4ab871b2, 0x05a9}, // telishaqetanahebrew
+ {0x4ad4b644, 0x066c}, // thousandsseparatorpersian
+ {0x4adaae02, 0x30bc}, // zekatakana
+ {0x4b13bfc7, 0x261f}, // pointingindexdownwhite
+ {0x4b146e46, 0x0076}, // v
+ {0x4b1cfc1b, 0x0052}, // R
+ {0x4b69c8eb, 0x3200}, // kiyeokparenkorean
+ {0x4b7a4380, 0x326b}, // thieuthcirclekorean
+ {0x4b8cb1d3, 0x315a}, // oekorean
+ {0x4b904ad7, 0x09ec}, // sixbengali
+ {0x4bc3db0d, 0xfe8e}, // aleffinalarabic
+ {0x4bcb1484, 0x0997}, // gabengali
+ {0x4bf3941c, 0x04e7}, // odieresiscyrillic
+ {0x4c1231bd, 0xfebe}, // dadfinalarabic
+ {0x4c224e3d, 0x0216}, // Uinvertedbreve
+ {0x4c31d446, 0x05b6}, // segolwidehebrew
+ {0x4c330dc7, 0x2303}, // control
+ {0x4c539c26, 0xff45}, // emonospace
+ {0x4c550d84, 0x0286}, // eshcurl
+ {0x4c63022b, 0x00ac}, // logicalnot
+ {0x4c636f96, 0xfe4c}, // overlinedblwavy
+ {0x4ca2293a, 0x0549}, // Chaarmenian
+ {0x4ca721bb, 0x24d9}, // jcircle
+ {0x4cbb6976, 0x054c}, // Raarmenian
+ {0x4cc2766b, 0x0474}, // Izhitsacyrillic
+ {0x4ccaa98f, 0x0292}, // ezh
+ {0x4cda32dd, 0x2592}, // shademedium
+ {0x4cf1d7c1, 0xfb2c}, // shindageshshindot
+ {0x4d08f8de, 0x278c}, // threecircleinversesansserif
+ {0x4d18f1c3, 0x01e5}, // gstroke
+ {0x4d3fee14, 0x2207}, // gradient
+ {0x4d5e2eea, 0x09c7}, // evowelsignbengali
+ {0x4d66ad61, 0x0259}, // schwa
+ {0x4d6f0f44, 0x03b6}, // zeta
+ {0x4d76cbca, 0x0939}, // hadeva
+ {0x4da2ea17, 0x30f7}, // vakatakana
+ {0x4dad3b1f, 0xff47}, // gmonospace
+ {0x4db4092d, 0x05b2}, // hatafpatahquarterhebrew
+ {0x4dc635ef, 0x03c2}, // sigmafinal
+ {0x4dd49001, 0x04ae}, // Ustraightcyrillic
+ {0x4dd4e51e, 0x05e3}, // finalpe
+ {0x4ddb0ff8, 0x3014}, // tortoiseshellbracketleft
+ {0x4ddbe970, 0x0054}, // T
+ {0x4ddd9ef4, 0x3296}, // ideographicfinancialcircle
+ {0x4dedf33d, 0xff8d}, // hekatakanahalfwidth
+ {0x4def9c7c, 0x2327}, // clear
+ {0x4dfb4b2a, 0x247b}, // eightparen
+ {0x4e03617a, 0x0ac1}, // uvowelsigngujarati
+ {0x4e0fdced, 0x0a2d}, // bhagurmukhi
+ {0x4e1cdd9c, 0x247d}, // tenparen
+ {0x4e63a83b, 0x0642}, // qafarabic
+ {0x4e7d8096, 0xff53}, // smonospace
+ {0x4e8356bc, 0x0108}, // Ccircumflex
+ {0x4eb853e7, 0x3384}, // kasquare
+ {0x4ec3d2dc, 0xfccb}, // lamkhahinitialarabic
+ {0x4ec752cf, 0x2022}, // bullet
+ {0x4ef94777, 0x323b}, // ideographicstudyparen
+ {0x4efcaf3c, 0x01ff}, // ostrokeacute
+ {0x4f1d81af, 0x05bb}, // qubuts
+ {0x4f1d9a74, 0x1e15}, // emacrongrave
+ {0x4f1fbad2, 0x33b2}, // mussquare
+ {0x4f238367, 0x0270}, // mlonglegturned
+ {0x4f23d8fd, 0xfeda}, // kaffinalarabic
+ {0x4f2d09de, 0x0662}, // twohackarabic
+ {0x4f2efda5, 0xff07}, // quotesinglemonospace
+ {0x4f30c414, 0x01c6}, // dzcaron
+ {0x4f4be9c8, 0x3129}, // iubopomofo
+ {0x4f6c2078, 0x00e5}, // aring
+ {0x4f9b207b, 0x06c1}, // hehaltonearabic
+ {0x4f9deafc, 0x03c4}, // tau
+ {0x4fb92256, 0x216b}, // Twelveroman
+ {0x4fdd1a2b, 0x300a}, // dblanglebracketleft
+ {0x4feaecfe, 0x22c3}, // uniontext
+ {0x5004c9ab, 0x09ed}, // sevenbengali
+ {0x50166be8, 0x049a}, // Kadescendercyrillic
+ {0x501dd48e, 0x2229}, // intersection
+ {0x5024fa7b, 0x260f}, // whitetelephone
+ {0x502bdceb, 0x3290}, // ideographsuncircle
+ {0x503133b5, 0x0009}, // controlHT
+ {0x507713d7, 0x2a02}, // circlemultiplytext
+ {0x50794cf3, 0x255d}, // SF260000
+ {0x508090a0, 0xff82}, // tukatakanahalfwidth
+ {0x50993bc3, 0x05dd}, // finalmemhebrew
+ {0x509ec6af, 0x0490}, // Gheupturncyrillic
+ {0x50a87245, 0x05b9}, // holamwidehebrew
+ {0x50be3a5b, 0x3189}, // yoikorean
+ {0x50cc5524, 0x00be}, // threequarters
+ {0x50cc8cef, 0x24a3}, // hparen
+ {0x50fb6106, 0x30c1}, // tikatakana
+ {0x510f444c, 0x0662}, // afii57394
+ {0x511118c0, 0x05b8}, // qamatsqatanquarterhebrew
+ {0x51250a43, 0x0ab9}, // hagujarati
+ {0x5125d1fa, 0x25a1}, // whitesquare
+ {0x513a52c9, 0x2510}, // SF030000
+ {0x51439af3, 0x064e}, // afii57454
+ {0x5147986a, 0xff5b}, // braceleftmonospace
+ {0x514d7298, 0x3238}, // ideographiclaborparen
+ {0x515692ea, 0x0414}, // Decyrillic
+ {0x51616742, 0x328c}, // ideographwatercircle
+ {0x51817d65, 0xfca1}, // tehjeeminitialarabic
+ {0x51969939, 0x03ba}, // kappa
+ {0x51ca7ab6, 0x00f6}, // odieresis
+ {0x51cbc424, 0x014d}, // omacron
+ {0x51d34569, 0x00e9}, // eacute
+ {0x51e6847c, 0x05b2}, // hatafpatah16
+ {0x51ed3cb2, 0x00af}, // macron
+ {0x51f006ea, 0x24af}, // tparen
+ {0x51f3c5bb, 0x2082}, // twoinferior
+ {0x51fee10e, 0x2210}, // coproducttext
+ {0x52099e7d, 0x3267}, // ieungcirclekorean
+ {0x5241ded3, 0x0428}, // Shacyrillic
+ {0x5247cafc, 0x0691}, // afii57513
+ {0x524c924c, 0x02d6}, // plusmod
+ {0x525a3324, 0x2606}, // whitestar
+ {0x5282fafa, 0x0e09}, // chochingthai
+ {0x528afecc, 0xfb89}, // ddalfinalarabic
+ {0x52beac4f, 0x25bf}, // whitedownpointingsmalltriangle
+ {0x52e15cc8, 0x24b6}, // Acircle
+ {0x52f72574, 0x3322}, // sentisquare
+ {0x530e1856, 0x05dd}, // finalmem
+ {0x531472bf, 0x05bb}, // qubutswidehebrew
+ {0x531963a3, 0x0a73}, // uragurmukhi
+ {0x531c6e1f, 0x315b}, // yokorean
+ {0x532f469f, 0x093d}, // avagrahadeva
+ {0x533e9388, 0x02bc}, // afii57929
+ {0x537b0d36, 0x0466}, // Yuslittlecyrillic
+ {0x53b3b784, 0xfb4a}, // tavdagesh
+ {0x53ca8524, 0x099d}, // jhabengali
+ {0x53d8dfb9, 0x3115}, // shbopomofo
+ {0x53e66e1a, 0x0476}, // Izhitsadblgravecyrillic
+ {0x53f49c2a, 0x066c}, // thousandsseparatorarabic
+ {0x53f951b5, 0x056f}, // kenarmenian
+ {0x540493c8, 0x03b1}, // alpha
+ {0x540c5f40, 0x0552}, // Yiwnarmenian
+ {0x542576f9, 0x0a07}, // igurmukhi
+ {0x542f1e7a, 0x0151}, // ohungarumlaut
+ {0x54761f15, 0x0631}, // afii57425
+ {0x54820079, 0x04df}, // zedieresiscyrillic
+ {0x548a6dde, 0x0451}, // iocyrillic
+ {0x5494ff15, 0x053c}, // Liwnarmenian
+ {0x54cc0e6b, 0x0212}, // Rinvertedbreve
+ {0x54dfda54, 0xfe44}, // whitecornerbracketrightvertical
+ {0x54f8c0f2, 0x1e78}, // Utildeacute
+ {0x55003750, 0x05b0}, // shevanarrowhebrew
+ {0x55021a5a, 0x0644}, // lamarabic
+ {0x550a9f23, 0x0452}, // djecyrillic
+ {0x550d7456, 0x0061}, // a
+ {0x5512ec97, 0x0067}, // g
+ {0x552e72ea, 0x1e8d}, // xdieresis
+ {0x5532b75e, 0x3044}, // ihiragana
+ {0x554f67fa, 0x1e4e}, // Otildedieresis
+ {0x556bcf7c, 0xff1f}, // questionmonospace
+ {0x5575c7a8, 0x09a8}, // nabengali
+ {0x557f8e27, 0x1ee0}, // Ohorntilde
+ {0x558d2385, 0x0318}, // lefttackbelowcmb
+ {0x55aa99d5, 0xfe3f}, // anglebracketleftvertical
+ {0x55b44317, 0x1ef2}, // Ygrave
+ {0x55c6e8cd, 0x0aac}, // bagujarati
+ {0x55e74cbe, 0x23a6}, // bracketrightbt
+ {0x55e81ebb, 0x3124}, // angbopomofo
+ {0x56000715, 0x33b0}, // pssquare
+ {0x560f90ad, 0x25e4}, // blackupperlefttriangle
+ {0x56200891, 0x092b}, // phadeva
+ {0x56217879, 0x0596}, // tipehalefthebrew
+ {0x56362764, 0x24d0}, // acircle
+ {0x565aa859, 0x30a8}, // ekatakana
+ {0x565d95fc, 0x3109}, // dbopomofo
+ {0x56a0101f, 0x04ac}, // Tedescendercyrillic
+ {0x56d036b9, 0x000f}, // controlSI
+ {0x56deae12, 0x0102}, // Abreve
+ {0x57050efe, 0x0145}, // Ncommaaccent
+ {0x5708e98e, 0x3154}, // ekorean
+ {0x5713d355, 0x2790}, // sevencircleinversesansserif
+ {0x575226bc, 0x239f}, // parenrightex
+ {0x57687403, 0x0e08}, // chochanthai
+ {0x576959da, 0x04b7}, // chedescendercyrillic
+ {0x577fdcc5, 0x033a}, // bridgeinvertedbelowcmb
+ {0x578594f4, 0x0e47}, // maitaikhuthai
+ {0x579e8de2, 0x2074}, // foursuperior
+ {0x57a10bfe, 0x0435}, // iecyrillic
+ {0x57c4d153, 0xfb1f}, // yodyodpatahhebrew
+ {0x57c8c90f, 0x313b}, // rieulmieumkorean
+ {0x57cea503, 0xff17}, // sevenmonospace
+ {0x57fc3d5e, 0x255b}, // SF280000
+ {0x58064efc, 0x0448}, // shacyrillic
+ {0x580bc6b8, 0x2320}, // integraltop
+ {0x580e0aa6, 0x005e}, // asciicircum
+ {0x5817c838, 0x3141}, // mieumkorean
+ {0x581d6ffc, 0x1ef8}, // Ytilde
+ {0x58246165, 0x0152}, // OE
+ {0x582e4f2f, 0x02a1}, // glottalstopstroke
+ {0x58408a1e, 0x2321}, // integralbottom
+ {0x584e8397, 0x263a}, // whitesmilingface
+ {0x5855c496, 0x0465}, // eiotifiedcyrillic
+ {0x587d22eb, 0x208d}, // parenleftinferior
+ {0x58a61c85, 0x1e9a}, // arighthalfring
+ {0x58ab0a67, 0x0544}, // Menarmenian
+ {0x58ba15a9, 0x24c9}, // Tcircle
+ {0x58c52193, 0x221a}, // radical
+ {0x58df0572, 0x0301}, // acutecmb
+ {0x58dfd388, 0x3187}, // yoyakorean
+ {0x58e61a1f, 0x05e8}, // resh
+ {0x58ea1dd9, 0x0650}, // afii57456
+ {0x593b3f38, 0x0aa1}, // ddagujarati
+ {0x5951351c, 0x0109}, // ccircumflex
+ {0x59664498, 0xff21}, // Amonospace
+ {0x597231b2, 0x1e00}, // Aringbelow
+ {0x598309ec, 0x1edc}, // Ohorngrave
+ {0x598631c5, 0x24c1}, // Lcircle
+ {0x59b5003e, 0x3274}, // siosacirclekorean
+ {0x59b9b187, 0x0075}, // u
+ {0x59be0f3a, 0xff7f}, // sokatakanahalfwidth
+ {0x59c46f70, 0x3299}, // ideographicsecretcircle
+ {0x59c80d40, 0x2482}, // fifteenparen
+ {0x59d6d87b, 0xff74}, // ekatakanahalfwidth
+ {0x59d7f689, 0x3393}, // ghzsquare
+ {0x59eccfd0, 0x05a3}, // munahlefthebrew
+ {0x5a1aca3c, 0xfe34}, // wavyunderscorevertical
+ {0x5a24f67c, 0x03b4}, // delta
+ {0x5a3b6461, 0xfeb8}, // sheenmedialarabic
+ {0x5a532aa6, 0x0586}, // feharmenian
+ {0x5a575dc4, 0x0582}, // yiwnarmenian
+ {0x5a62c8e8, 0x005c}, // backslash
+ {0x5a8d2a4a, 0x09c3}, // rvocalicvowelsignbengali
+ {0x5a963c7c, 0x0e02}, // khokhaithai
+ {0x5acd345c, 0x339c}, // squaremm
+ {0x5af634c5, 0x059e}, // gershayimaccenthebrew
+ {0x5b041347, 0x0581}, // coarmenian
+ {0x5b0fd985, 0x0360}, // tildedoublecmb
+ {0x5b1907c0, 0x30a5}, // usmallkatakana
+ {0x5b1da33a, 0x2206}, // increment
+ {0x5b1dbca0, 0x0aa6}, // dagujarati
+ {0x5b426364, 0x30cf}, // hakatakana
+ {0x5b426591, 0xfeb7}, // sheeninitialarabic
+ {0x5b46f9e7, 0x2080}, // zeroinferior
+ {0x5b54a5c3, 0x05e1}, // afii57681
+ {0x5b5f52e7, 0x0421}, // afii10035
+ {0x5b6ab184, 0x25ef}, // largecircle
+ {0x5b6f6c30, 0x0003}, // controlETX
+ {0x5b720455, 0x014c}, // Omacron
+ {0x5b72ad21, 0x03d5}, // phi1
+ {0x5b785975, 0x02a7}, // tesh
+ {0x5b7a64f2, 0x0597}, // reviahebrew
+ {0x5bbfa15f, 0x25c4}, // blackleftpointingpointer
+ {0x5bc1fa37, 0xfb47}, // qofdageshhebrew
+ {0x5be94211, 0xff24}, // Dmonospace
+ {0x5beb1e0f, 0x24cf}, // Zcircle
+ {0x5c0e8b47, 0x0426}, // afii10040
+ {0x5c185e06, 0x0250}, // aturned
+ {0x5c299659, 0x250c}, // SF010000
+ {0x5c337c81, 0xff8a}, // hakatakanahalfwidth
+ {0x5c3478b9, 0xff3a}, // Zmonospace
+ {0x5c349e9d, 0x30c5}, // dukatakana
+ {0x5c352033, 0x092c}, // badeva
+ {0x5c3b9279, 0x307e}, // mahiragana
+ {0x5c3eecc5, 0x2561}, // SF190000
+ {0x5c4a060e, 0x0e29}, // sorusithai
+ {0x5c6b0ced, 0x05b8}, // qamats33
+ {0x5c71c76c, 0x24b8}, // Ccircle
+ {0x5c7f4966, 0x01b8}, // Ezhreversed
+ {0x5c97be88, 0x0a2a}, // pagurmukhi
+ {0x5c9a7487, 0x30ec}, // rekatakana
+ {0x5ca0edc2, 0x01e7}, // gcaron
+ {0x5cad2e17, 0x0679}, // tteharabic
+ {0x5cb64e9e, 0x0150}, // Ohungarumlaut
+ {0x5cb98a11, 0x05e9}, // afii57689
+ {0x5cc203b0, 0x09eb}, // fivebengali
+ {0x5cde4fa9, 0xff39}, // Ymonospace
+ {0x5ce216d6, 0x02bb}, // commaturnedmod
+ {0x5ce89c18, 0x028c}, // vturned
+ {0x5cee9de2, 0x333b}, // peezisquare
+ {0x5d053ab9, 0x334d}, // meetorusquare
+ {0x5d06b34a, 0x01f5}, // gacute
+ {0x5d185c29, 0x0aec}, // sixgujarati
+ {0x5d23e967, 0x019a}, // lbar
+ {0x5d24fed7, 0x047d}, // omegatitlocyrillic
+ {0x5d26ca2e, 0x0996}, // khabengali
+ {0x5d32256c, 0x25c6}, // blackdiamond
+ {0x5d333915, 0x2234}, // therefore
+ {0x5d42ce05, 0x30c2}, // dikatakana
+ {0x5d590cb1, 0x05bb}, // qubutsnarrowhebrew
+ {0x5d71a05b, 0x0017}, // controlETB
+ {0x5d85b369, 0x05b8}, // qamats29
+ {0x5d8c507f, 0xfb47}, // qofdagesh
+ {0x5da58253, 0x30d0}, // bakatakana
+ {0x5dba07ed, 0x22a5}, // perpendicular
+ {0x5dbeec87, 0x01cd}, // Acaron
+ {0x5de3b63c, 0x09cb}, // ovowelsignbengali
+ {0x5e0aac56, 0x0200}, // Adblgrave
+ {0x5e1c8dfa, 0x05b0}, // sheva2e
+ {0x5e27fa57, 0xff6d}, // yusmallkatakanahalfwidth
+ {0x5e36a670, 0xfe33}, // underscorevertical
+ {0x5e483ddc, 0x3277}, // chieuchacirclekorean
+ {0x5e4f2fbb, 0x1e65}, // sacutedotaccent
+ {0x5ea7176f, 0x02d9}, // dotaccent
+ {0x5edd1e19, 0xff25}, // Emonospace
+ {0x5edd9086, 0x003a}, // colon
+ {0x5eddf92b, 0x0044}, // D
+ {0x5ee2af9c, 0x0e58}, // eightthai
+ {0x5f03252d, 0x0136}, // Kcedilla
+ {0x5f0ec9e9, 0x039a}, // Kappa
+ {0x5f1bf33a, 0x1e17}, // emacronacute
+ {0x5f4a5f07, 0x0abe}, // aavowelsigngujarati
+ {0x5f5c5d5f, 0x041b}, // Elcyrillic
+ {0x5f63748c, 0x03e6}, // Kheicoptic
+ {0x5f7dc76d, 0x033b}, // squarebelowcmb
+ {0x5f881d5c, 0x3160}, // yukorean
+ {0x5f8f63e2, 0x24a0}, // eparen
+ {0x5f99c0ac, 0x01ad}, // thook
+ {0x5fa5f5cc, 0xfefa}, // lamalefhamzabelowfinalarabic
+ {0x5fb56903, 0x095d}, // rhadeva
+ {0x5fbeac33, 0x028b}, // vhook
+ {0x5fe9065e, 0x042e}, // afii10048
+ {0x6037ae88, 0x1ef3}, // ygrave
+ {0x603b5882, 0x2287}, // reflexsuperset
+ {0x603b9d93, 0x00d3}, // Oacute
+ {0x603ff393, 0x1e6c}, // Tdotbelow
+ {0x60753fe5, 0x066d}, // afii63167
+ {0x607c93ed, 0x01b2}, // Vhook
+ {0x60a46930, 0x0197}, // Istroke
+ {0x60a4c80a, 0x30ee}, // wasmallkatakana
+ {0x60ac2314, 0x25cc}, // dottedcircle
+ {0x60ba7236, 0x000e}, // controlSO
+ {0x60d57bed, 0x0aa4}, // tagujarati
+ {0x60e1bf57, 0x1e0d}, // ddotbelow
+ {0x60ebe651, 0x1ed8}, // Ocircumflexdotbelow
+ {0x60f5a9fa, 0x03b7}, // eta
+ {0x6106119c, 0xff37}, // Wmonospace
+ {0x610b31e6, 0x01f1}, // DZ
+ {0x6114c811, 0x1ef0}, // Uhorndotbelow
+ {0x6121a3f6, 0x0e4d}, // nikhahitthai
+ {0x61239a5a, 0x01c2}, // clickalveolar
+ {0x6143b142, 0x1e67}, // scarondotaccent
+ {0x61503571, 0x027b}, // rhookturned
+ {0x6154bc05, 0x043b}, // afii10077
+ {0x617c687c, 0x01a2}, // Oi
+ {0x6198a771, 0xfef3}, // alefmaksurainitialarabic
+ {0x61a21109, 0x01c9}, // lj
+ {0x61a2d0df, 0x258c}, // lfblock
+ {0x61a6f1a4, 0x0264}, // ramshorn
+ {0x61b4ed39, 0x25ba}, // triagrt
+ {0x61b7afbf, 0x05b5}, // tserenarrowhebrew
+ {0x61b9f022, 0x04c4}, // kahookcyrillic
+ {0x61ce131a, 0x0647}, // afii57470
+ {0x61d46fc2, 0x1ed6}, // Ocircumflextilde
+ {0x61d7bcdb, 0x1e57}, // pdotaccent
+ {0x61e75298, 0x0417}, // afii10025
+ {0x61f36361, 0x06f8}, // eightpersian
+ {0x61fe712f, 0x20a4}, // afii08941
+ {0x621057dd, 0x1ef5}, // ydotbelow
+ {0x62127977, 0x266a}, // musicalnote
+ {0x62161c15, 0x05da}, // finalkafhebrew
+ {0x6229838d, 0x05d0}, // alef
+ {0x62447ae3, 0x0314}, // commareversedabovecmb
+ {0x6248b5e6, 0x33dd}, // wbsquare
+ {0x625320aa, 0x30b2}, // gekatakana
+ {0x6260c6fc, 0x0589}, // periodarmenian
+ {0x6265e881, 0x2266}, // lessoverequal
+ {0x626cbaa4, 0x01bf}, // wynn
+ {0x62724d89, 0x09ad}, // bhabengali
+ {0x62896f4a, 0x05f2}, // yodyodhebrew
+ {0x628a5951, 0xff56}, // vmonospace
+ {0x62a11b25, 0x1e87}, // wdotaccent
+ {0x62accaf5, 0x239d}, // parenleftbt
+ {0x62b2cd2d, 0x00c2}, // Acircumflex
+ {0x62b6e7ac, 0x01a5}, // phook
+ {0x62c361d5, 0x05e2}, // afii57682
+ {0x62c52689, 0x05d5}, // afii57669
+ {0x62ca59ed, 0x03aa}, // Iotadieresis
+ {0x62cfccee, 0x017d}, // Zcaron
+ {0x62d27ffc, 0x0567}, // eharmenian
+ {0x62f7161d, 0x3020}, // postalmarkface
+ {0x630680b1, 0x1e20}, // Gmacron
+ {0x63070542, 0x0401}, // afii10023
+ {0x630758ff, 0x317e}, // sioscieuckorean
+ {0x632ae410, 0x0acc}, // auvowelsigngujarati
+ {0x634ac34b, 0x2297}, // timescircle
+ {0x634e42e5, 0xff2b}, // Kmonospace
+ {0x635a9554, 0x04e5}, // idieresiscyrillic
+ {0x636fabd5, 0x093c}, // nuktadeva
+ {0x637ae7ca, 0x0344}, // dialytikatonoscmb
+ {0x637d3539, 0x00bc}, // onequarter
+ {0x6389d9bb, 0x0641}, // afii57441
+ {0x63a82931, 0x0297}, // cstretched
+ {0x63d84bb6, 0x2281}, // notsucceeds
+ {0x63dfed74, 0x311f}, // eibopomofo
+ {0x63e274d2, 0xfc0b}, // tehjeemisolatedarabic
+ {0x63e60b13, 0x0149}, // napostrophe
+ {0x63f6cd8f, 0x049d}, // kaverticalstrokecyrillic
+ {0x642b78c5, 0x1ed5}, // ocircumflexhookabove
+ {0x642e193e, 0x0469}, // yuslittleiotifiedcyrillic
+ {0x6434f04a, 0xfd88}, // lammeemhahinitialarabic
+ {0x64468e36, 0x0e18}, // thothongthai
+ {0x64517fe8, 0x004b}, // K
+ {0x6453c78e, 0x064e}, // fathalowarabic
+ {0x6460d798, 0x3025}, // fivehangzhou
+ {0x646b157e, 0x3242}, // ideographicselfparen
+ {0x646c5c19, 0x0ab5}, // vagujarati
+ {0x647eca4f, 0x09ae}, // mabengali
+ {0x64a1b76b, 0x30fa}, // vokatakana
+ {0x64d00b32, 0x0114}, // Ebreve
+ {0x64d7a402, 0x0e40}, // saraethai
+ {0x64d94f4a, 0x0e01}, // kokaithai
+ {0x64e7dff2, 0x09c4}, // rrvocalicvowelsignbengali
+ {0x64eb016b, 0x2245}, // approximatelyequal
+ {0x64fd7a48, 0x2078}, // eightsuperior
+ {0x650678be, 0x02cb}, // fourthtonechinese
+ {0x65070663, 0x0459}, // ljecyrillic
+ {0x6510d99a, 0x0938}, // sadeva
+ {0x651d5722, 0x04c2}, // zhebrevecyrillic
+ {0x6525cdb1, 0x00ad}, // softhyphen
+ {0x6543f12c, 0x01d5}, // Udieresismacron
+ {0x654d08a0, 0x0e3a}, // phinthuthai
+ {0x657ffabd, 0x22ee}, // ellipsisvertical
+ {0x6598fbfe, 0x3112}, // xbopomofo
+ {0x65a5bd1b, 0x24e1}, // rcircle
+ {0x65be15d1, 0x0a08}, // iigurmukhi
+ {0x65c095c5, 0x200e}, // afii299
+ {0x65cc1f56, 0x0545}, // Yiarmenian
+ {0x662a6586, 0x23a0}, // parenrightbt
+ {0x662a831c, 0x1eeb}, // uhorngrave
+ {0x663a2c1a, 0x3300}, // apaatosquare
+ {0x6642f834, 0x0361}, // breveinverteddoublecmb
+ {0x665bba62, 0x2017}, // dbllowline
+ {0x665e930f, 0x3264}, // mieumcirclekorean
+ {0x666ae75e, 0x0159}, // rcaron
+ {0x666e8927, 0xfc4e}, // noonmeemisolatedarabic
+ {0x666faf51, 0xfba5}, // hehhamzaabovefinalarabic
+ {0x6689dbc7, 0x33cb}, // HPsquare
+ {0x668e9764, 0x30a2}, // akatakana
+ {0x66bb979c, 0x246c}, // thirteencircle
+ {0x66c590a5, 0x2236}, // ratio
+ {0x66e9c5c7, 0x3084}, // yahiragana
+ {0x66ef8fdf, 0x040f}, // afii10145
+ {0x66fb9b49, 0x33c9}, // gysquare
+ {0x67188e74, 0x0066}, // f
+ {0x6740a4ac, 0x25cf}, // H18533
+ {0x67427e4a, 0x2079}, // ninesuperior
+ {0x674a0210, 0x02b6}, // Rsmallinvertedsuperior
+ {0x674c80b7, 0x03da}, // Stigmagreek
+ {0x67569fa6, 0x05b5}, // tsere
+ {0x6758c83c, 0x2588}, // block
+ {0x6776a85a, 0x0ac7}, // evowelsigngujarati
+ {0x6779be95, 0x3113}, // zhbopomofo
+ {0x6785194b, 0x221f}, // rightangle
+ {0x679d9205, 0x02c5}, // arrowheaddownmod
+ {0x679dcadd, 0x0e54}, // fourthai
+ {0x679ffc49, 0x2024}, // onedotenleader
+ {0x67ea250f, 0x1e2d}, // itildebelow
+ {0x67ebcea4, 0x3209}, // chieuchparenkorean
+ {0x67f3db47, 0x3073}, // bihiragana
+ {0x67f3ecac, 0xfb4b}, // afii57700
+ {0x67f7f220, 0x309c}, // semivoicedmarkkana
+ {0x67fa1db6, 0x203a}, // guilsinglright
+ {0x68070609, 0x0916}, // khadeva
+ {0x6808ec86, 0x02d8}, // breve
+ {0x6811e3e1, 0x0404}, // Ecyrillic
+ {0x6814026d, 0x02c8}, // verticallinemod
+ {0x682b08dd, 0x01ac}, // Thook
+ {0x6853e235, 0x0652}, // sukunarabic
+ {0x685932be, 0xfe36}, // parenrightvertical
+ {0x68a76955, 0x00c7}, // Ccedilla
+ {0x68b3215e, 0x0339}, // ringhalfrightbelowcmb
+ {0x68b98a65, 0x0679}, // afii57511
+ {0x68ccd1d8, 0x3212}, // mieumaparenkorean
+ {0x68eb418e, 0x3011}, // blacklenticularbracketright
+ {0x690f8118, 0x320b}, // thieuthparenkorean
+ {0x692bc1f7, 0x0a42}, // uumatragurmukhi
+ {0x693ccd9c, 0x0432}, // vecyrillic
+ {0x6962e430, 0x0322}, // hookretroflexbelowcmb
+ {0x6977f4b7, 0xff05}, // percentmonospace
+ {0x697925af, 0x05d2}, // gimel
+ {0x697feb80, 0x030d}, // verticallineabovecmb
+ {0x6991a62a, 0x0935}, // vadeva
+ {0x699d525a, 0x0111}, // dcroat
+ {0x69aabc4d, 0x06af}, // gafarabic
+ {0x69d31152, 0x0e51}, // onethai
+ {0x69faaae4, 0x33a7}, // moverssquare
+ {0x6a104703, 0x2569}, // SF400000
+ {0x6a16e743, 0xfef0}, // alefmaksurafinalarabic
+ {0x6a3d8bdc, 0x0a99}, // ngagujarati
+ {0x6a511868, 0x03a7}, // Chi
+ {0x6a5f0a3f, 0x334e}, // yaadosquare
+ {0x6a721907, 0xfecc}, // ainmedialarabic
+ {0x6a8d644d, 0x1ead}, // acircumflexdotbelow
+ {0x6a972227, 0x0401}, // Iocyrillic
+ {0x6a988d53, 0x2035}, // primereversed
+ {0x6a9b2b92, 0x0414}, // afii10021
+ {0x6aa62850, 0x0662}, // twoarabic
+ {0x6aa82ca6, 0x3218}, // khieukhaparenkorean
+ {0x6abb1490, 0x005d}, // bracketright
+ {0x6ad19d29, 0xfb1f}, // doubleyodpatahhebrew
+ {0x6b214948, 0xff6f}, // tusmallkatakanahalfwidth
+ {0x6b2d374c, 0xfcdd}, // yehmeeminitialarabic
+ {0x6b3f02ab, 0x211e}, // prescription
+ {0x6b58bab9, 0x0963}, // llvocalicvowelsigndeva
+ {0x6b83db74, 0x2295}, // circleplus
+ {0x6b85cd6e, 0x00a4}, // currency
+ {0x6b8b924f, 0x30d8}, // hekatakana
+ {0x6b908b1b, 0x09e7}, // onebengali
+ {0x6b98f18c, 0x212b}, // angstrom
+ {0x6b9adcc4, 0x25cf}, // blackcircle
+ {0x6b9f4fbe, 0x0940}, // iivowelsigndeva
+ {0x6baa5ba4, 0x03ce}, // omegatonos
+ {0x6bbd76c8, 0x0936}, // shadeva
+ {0x6bca6ff5, 0xfe4a}, // overlinecenterline
+ {0x6bd59d12, 0x0915}, // kadeva
+ {0x6bd609d3, 0x00f0}, // eth
+ {0x6be13af7, 0x2199}, // arrowdownleft
+ {0x6bf10a81, 0x2015}, // horizontalbar
+ {0x6c0e37b1, 0x30ce}, // nokatakana
+ {0x6c11086e, 0x0a1c}, // jagurmukhi
+ {0x6c180b9f, 0x0462}, // Yatcyrillic
+ {0x6c429460, 0xfb40}, // nundageshhebrew
+ {0x6c5c9da6, 0x0457}, // yicyrillic
+ {0x6c781e8a, 0x25bd}, // whitedownpointingtriangle
+ {0x6c8584d5, 0x3065}, // duhiragana
+ {0x6c95b865, 0x0941}, // uvowelsigndeva
+ {0x6c98a67f, 0x0479}, // ukcyrillic
+ {0x6cbb9b7a, 0xfe5d}, // tortoiseshellbracketleftsmall
+ {0x6cce2d3d, 0x0aa5}, // thagujarati
+ {0x6cce7f58, 0x043b}, // elcyrillic
+ {0x6d02864a, 0x049c}, // Kaverticalstrokecyrillic
+ {0x6d1b554b, 0x0982}, // anusvarabengali
+ {0x6d3fc0d7, 0x040f}, // Dzhecyrillic
+ {0x6d519305, 0x2169}, // Tenroman
+ {0x6d63a915, 0x02bc}, // apostrophemod
+ {0x6d6c6ece, 0x307f}, // mihiragana
+ {0x6d794da3, 0x0057}, // W
+ {0x6d7a8d87, 0x00fd}, // yacute
+ {0x6d88930b, 0x044a}, // afii10092
+ {0x6d89653d, 0x24e8}, // ycircle
+ {0x6d8f5cd4, 0xff95}, // yukatakanahalfwidth
+ {0x6d931b7f, 0x255c}, // SF270000
+ {0x6d9b9c3e, 0x05dc}, // afii57676
+ {0x6dd186b9, 0x0575}, // yiarmenian
+ {0x6ddcf118, 0x22a4}, // tackdown
+ {0x6ddd69da, 0x05b1}, // hatafsegolquarterhebrew
+ {0x6df68266, 0x0388}, // Epsilontonos
+ {0x6df8e451, 0x2203}, // existential
+ {0x6dfa6cea, 0x0a66}, // zerogurmukhi
+ {0x6dff449c, 0xff79}, // kekatakanahalfwidth
+ {0x6e298b2c, 0x3380}, // paampssquare
+ {0x6e2b4011, 0x2213}, // minusplus
+ {0x6e2e0a97, 0xfe66}, // equalsmall
+ {0x6e53b9b3, 0xfb00}, // ff
+ {0x6e5f0868, 0x30ed}, // rokatakana
+ {0x6e7cf223, 0x3071}, // pahiragana
+ {0x6e81816d, 0x1e5d}, // rdotbelowmacron
+ {0x6e897228, 0x0324}, // dieresisbelowcmb
+ {0x6e8ccbbb, 0xfb48}, // reshdageshhebrew
+ {0x6eb16a59, 0x2166}, // Sevenroman
+ {0x6eb40b0d, 0x04db}, // schwadieresiscyrillic
+ {0x6eba69ba, 0xfe9e}, // jeemfinalarabic
+ {0x6ebb53ea, 0x2261}, // equivalence
+ {0x6ecb4ba4, 0x0449}, // afii10091
+ {0x6ecc1f68, 0x25bc}, // triagdn
+ {0x6ed918ed, 0xfcd1}, // meemmeeminitialarabic
+ {0x6ee27b35, 0x0321}, // hookpalatalizedbelowcmb
+ {0x6f028e08, 0x0571}, // jaarmenian
+ {0x6f1f2583, 0x25ce}, // bullseye
+ {0x6f2573bb, 0x30eb}, // rukatakana
+ {0x6f4833d3, 0x0a5c}, // rragurmukhi
+ {0x6f57587a, 0x227b}, // succeeds
+ {0x6f64edc9, 0x054f}, // Tiwnarmenian
+ {0x6f6b6e30, 0x0e0e}, // dochadathai
+ {0x6f75692f, 0x0ac8}, // aivowelsigngujarati
+ {0x6f7744aa, 0x2196}, // arrowupleft
+ {0x6fae2151, 0x0aef}, // ninegujarati
+ {0x6fbeef2a, 0x044d}, // ereversedcyrillic
+ {0x6fc01d9f, 0x00f8}, // oslash
+ {0x702ecc19, 0x3054}, // gohiragana
+ {0x70534c83, 0x05a4}, // mahapakhlefthebrew
+ {0x7061d5ad, 0x255a}, // SF380000
+ {0x707057b4, 0xfb2e}, // alefpatahhebrew
+ {0x7070c1c4, 0x0333}, // dbllowlinecmb
+ {0x7077f8df, 0x0163}, // tcommaaccent
+ {0x708cf64a, 0x0944}, // rrvocalicvowelsigndeva
+ {0x709b2bbe, 0x0056}, // V
+ {0x70a0d365, 0xfeee}, // wawfinalarabic
+ {0x70a7b34b, 0x05b5}, // tsere12
+ {0x70c1bcae, 0x1ede}, // Ohornhookabove
+ {0x70d74f05, 0x04b1}, // ustraightstrokecyrillic
+ {0x70d8442c, 0xff3c}, // backslashmonospace
+ {0x70e9d43c, 0x2202}, // partialdiff
+ {0x70f78dac, 0x24b5}, // zparen
+ {0x70f8a40d, 0x05b8}, // afii57797
+ {0x71062567, 0xfeea}, // hehfinalalttwoarabic
+ {0x710fbfc8, 0x0179}, // Zacute
+ {0x712afb91, 0x310b}, // nbopomofo
+ {0x7138408a, 0x04cc}, // chekhakassiancyrillic
+ {0x71411a8f, 0x3156}, // yekorean
+ {0x7154079a, 0x1e50}, // Omacrongrave
+ {0x715a0e07, 0xfef4}, // alefmaksuramedialarabic
+ {0x715aaa7e, 0x0122}, // Gcommaaccent
+ {0x71693ad6, 0x0919}, // ngadeva
+ {0x718d2f07, 0xff16}, // sixmonospace
+ {0x71a4a95d, 0x1eaa}, // Acircumflextilde
+ {0x71b12b2f, 0x01e0}, // Adotmacron
+ {0x71b2c74d, 0x00b7}, // periodcentered
+ {0x71b2e09c, 0x0e32}, // saraaathai
+ {0x71e104c6, 0x3041}, // asmallhiragana
+ {0x71ebf117, 0x0640}, // afii57440
+ {0x7222af0c, 0x25cb}, // circle
+ {0x722d9aa3, 0x2153}, // onethird
+ {0x72446324, 0x3164}, // hangulfiller
+ {0x7256dcb9, 0x0ac2}, // uuvowelsigngujarati
+ {0x725da2a5, 0x0548}, // Voarmenian
+ {0x7274464b, 0x0e31}, // maihanakatthai
+ {0x7276cdf0, 0x02d7}, // minusmod
+ {0x7278753d, 0x04d7}, // iebrevecyrillic
+ {0x728cf068, 0x020f}, // oinvertedbreve
+ {0x728f761b, 0x3111}, // qbopomofo
+ {0x72b3c6a8, 0x00b4}, // acute
+ {0x72ba7ff6, 0x3093}, // nhiragana
+ {0x72c6f1c4, 0xff5c}, // barmonospace
+ {0x72c8f209, 0x0136}, // Kcommaaccent
+ {0x72e33f41, 0x0419}, // afii10027
+ {0x72f1f5cf, 0x0438}, // iicyrillic
+ {0x72fc7974, 0x2111}, // Ifraktur
+ {0x72fd959a, 0x002a}, // asterisk
+ {0x73032cb1, 0x3268}, // cieuccirclekorean
+ {0x731be3b3, 0x3140}, // rieulhieuhkorean
+ {0x73200dbd, 0x0e20}, // phosamphaothai
+ {0x7322d42e, 0x24b9}, // Dcircle
+ {0x73282fcd, 0x1ee3}, // ohorndotbelow
+ {0x732cf2ca, 0x2485}, // eighteenparen
+ {0x7338db3a, 0x066b}, // decimalseparatorarabic
+ {0x73392eb6, 0x1ed4}, // Ocircumflexhookabove
+ {0x733b0480, 0xfe94}, // tehmarbutafinalarabic
+ {0x734f6c6b, 0x3090}, // wihiragana
+ {0x7375cb46, 0x0686}, // tcheharabic
+ {0x73843708, 0x0434}, // decyrillic
+ {0x7387f092, 0x2088}, // eightinferior
+ {0x73b2bbd4, 0xfe63}, // hyphensmall
+ {0x73b70a88, 0x05d8}, // afii57672
+ {0x73b7c451, 0x095a}, // ghhadeva
+ {0x73c5e60b, 0x004e}, // N
+ {0x73ee6902, 0x0965}, // dbldanda
+ {0x73f5578c, 0x093f}, // ivowelsigndeva
+ {0x74030714, 0x311c}, // ebopomofo
+ {0x7411ab47, 0x00ba}, // ordmasculine
+ {0x74144417, 0x311e}, // aibopomofo
+ {0x74157df0, 0x0667}, // sevenhackarabic
+ {0x741e8130, 0x00cf}, // Idieresis
+ {0x7435abfe, 0x057c}, // raarmenian
+ {0x743d1e25, 0x0456}, // afii10103
+ {0x745c44bf, 0x02e3}, // xsuperior
+ {0x746e784f, 0xff48}, // hmonospace
+ {0x7476bb3d, 0x21d2}, // dblarrowright
+ {0x74814589, 0x05b9}, // holam19
+ {0x748db30c, 0x33b1}, // nssquare
+ {0x7495de6c, 0x0008}, // controlBS
+ {0x74a2fb64, 0x2280}, // notprecedes
+ {0x74abeab2, 0x05ac}, // iluyhebrew
+ {0x74b1cbfb, 0x22c5}, // dotmath
+ {0x74caf1a5, 0x01b4}, // yhook
+ {0x74d74b92, 0x2209}, // notelement
+ {0x74f1fca9, 0x01db}, // Udieresisgrave
+ {0x7506e8c8, 0x24d1}, // bcircle
+ {0x751253a5, 0x2168}, // Nineroman
+ {0x7544678b, 0x307d}, // pohiragana
+ {0x754982de, 0x06a4}, // veharabic
+ {0x755a250b, 0x09dd}, // rhabengali
+ {0x755f707c, 0x1ebf}, // ecircumflexacute
+ {0x75841530, 0x2002}, // enspace
+ {0x75907eb0, 0x322c}, // ideographicwaterparen
+ {0x759ddc3d, 0x3068}, // tohiragana
+ {0x759f9daf, 0x00c4}, // Adieresis
+ {0x75aa325f, 0x3047}, // esmallhiragana
+ {0x75b73269, 0x27e9}, // angbracketrightbig
+ {0x75b9e64d, 0xff46}, // fmonospace
+ {0x75bf74c1, 0x0a59}, // khhagurmukhi
+ {0x75f4d85d, 0x0a2c}, // bagurmukhi
+ {0x7603459c, 0x094c}, // auvowelsigndeva
+ {0x760d83de, 0x222b}, // integral
+ {0x7612320f, 0x3273}, // pieupacirclekorean
+ {0x76186d8e, 0x01f3}, // dz
+ {0x7619aedc, 0xfb4c}, // betrafehebrew
+ {0x762dd6ce, 0x2260}, // notequal
+ {0x763e1836, 0x30c4}, // tukatakana
+ {0x7656c681, 0x01c4}, // DZcaron
+ {0x767695a3, 0xfed4}, // fehmedialarabic
+ {0x76811ae6, 0x0169}, // utilde
+ {0x7681e1d4, 0xfe9b}, // thehinitialarabic
+ {0x76a40aac, 0x001e}, // controlRS
+ {0x76a57fc1, 0x0117}, // edot
+ {0x76aa35a2, 0x1e27}, // hdieresis
+ {0x76abdb93, 0xfb41}, // samekhdageshhebrew
+ {0x76af2566, 0x05c1}, // afii57804
+ {0x76b279b9, 0x1e76}, // Ucircumflexbelow
+ {0x76be5e67, 0x0389}, // Etatonos
+ {0x76ec167e, 0x3128}, // ubopomofo
+ {0x76ee8ef9, 0x326a}, // khieukhcirclekorean
+ {0x76fa92ba, 0x2225}, // parallel
+ {0x7722080a, 0x21e2}, // arrowdashright
+ {0x772d6b37, 0x3294}, // ideographnamecircle
+ {0x774eb078, 0x0328}, // ogonekcmb
+ {0x774fe771, 0x064d}, // kasratanarabic
+ {0x7752bcc4, 0x239e}, // parenrighttp
+ {0x775a46a9, 0xffe0}, // centmonospace
+ {0x77651620, 0x007b}, // braceleft
+ {0x776629fd, 0x044a}, // hardsigncyrillic
+ {0x7766a0eb, 0x041e}, // Ocyrillic
+ {0x776cd4d6, 0x1e89}, // wdotbelow
+ {0x77741a8d, 0x0473}, // fitacyrillic
+ {0x7782c966, 0x1e46}, // Ndotbelow
+ {0x7786e0a6, 0x246f}, // sixteencircle
+ {0x779ce17f, 0x0390}, // iotadieresistonos
+ {0x77a6f721, 0x2165}, // Sixroman
+ {0x77aefb3f, 0x3027}, // sevenhangzhou
+ {0x77b3e914, 0x3106}, // pbopomofo
+ {0x77d27346, 0x0110}, // Dcroat
+ {0x77e1d263, 0x0442}, // tecyrillic
+ {0x77ee824a, 0xfba7}, // hehfinalaltonearabic
+ {0x77f415ae, 0x03eb}, // gangiacoptic
+ {0x77f608a5, 0x00ec}, // igrave
+ {0x78151b28, 0x04f8}, // Yerudieresiscyrillic
+ {0x781b1710, 0x3175}, // pieupsiostikeutkorean
+ {0x782f5323, 0x2297}, // circlemultiply
+ {0x783bb2b4, 0xfeea}, // hehfinalarabic
+ {0x7849decd, 0x05f0}, // vavvavhebrew
+ {0x7865471d, 0x30f8}, // vikatakana
+ {0x7868522a, 0x0172}, // Uogonek
+ {0x78728968, 0x0e3f}, // bahtthai
+ {0x7877a9ea, 0x05d9}, // yodhebrew
+ {0x7878b2f0, 0x323c}, // ideographicsuperviseparen
+ {0x788e7d3e, 0x05b3}, // hatafqamats
+ {0x78965447, 0x0593}, // shalshelethebrew
+ {0x78d4db12, 0x032e}, // brevebelowcmb
+ {0x78da6d23, 0x046e}, // Ksicyrillic
+ {0x78f4df25, 0x04d9}, // schwacyrillic
+ {0x79042ee6, 0x30e9}, // rakatakana
+ {0x79116479, 0x0039}, // nine
+ {0x7919f419, 0x0416}, // afii10024
+ {0x792d6db9, 0x0272}, // nhookleft
+ {0x793801c3, 0x3158}, // wakorean
+ {0x79577113, 0x2565}, // SF480000
+ {0x795917d1, 0x0583}, // piwrarmenian
+ {0x79638654, 0x1ecd}, // odotbelow
+ {0x796eb2f8, 0x3389}, // kcalsquare
+ {0x796edb96, 0x0387}, // anoteleia
+ {0x797aa124, 0x0445}, // khacyrillic
+ {0x7980bc25, 0x00c5}, // Aring
+ {0x7987b173, 0x03e8}, // Horicoptic
+ {0x79961e28, 0x20a7}, // peseta
+ {0x79c6a044, 0xfe3a}, // tortoiseshellbracketrightvertical
+ {0x79d35b17, 0x3229}, // tenideographicparen
+ {0x79dfa961, 0xfb2d}, // shindageshsindot
+ {0x79e5e3c1, 0x0e2f}, // paiyannoithai
+ {0x79fc5691, 0x24ce}, // Ycircle
+ {0x7a196523, 0x0451}, // afii10071
+ {0x7a29214e, 0x006c}, // l
+ {0x7a32a546, 0x202d}, // afii61574
+ {0x7a3f45e9, 0x0ab8}, // sagujarati
+ {0x7a49bde9, 0xfca4}, // tehmeeminitialarabic
+ {0x7a49dc21, 0xfee2}, // meemfinalarabic
+ {0x7a56cace, 0x33a1}, // squaremsquared
+ {0x7a6cfa6a, 0x0a3f}, // imatragurmukhi
+ {0x7a75be4d, 0x2559}, // SF490000
+ {0x7a8736f8, 0x320c}, // phieuphparenkorean
+ {0x7aa071da, 0x0217}, // uinvertedbreve
+ {0x7aa95d24, 0xfb34}, // hedageshhebrew
+ {0x7aac463a, 0x0669}, // afii57401
+ {0x7ab8ba14, 0x1ebe}, // Ecircumflexacute
+ {0x7ad78099, 0x05b5}, // afii57794
+ {0x7b074f4f, 0x062c}, // jeemarabic
+ {0x7b0aca00, 0x3315}, // kiroguramusquare
+ {0x7b443626, 0x3331}, // birusquare
+ {0x7b5e0418, 0xff10}, // zeromonospace
+ {0x7b7b9d4d, 0x0424}, // afii10038
+ {0x7b80aee5, 0x05bb}, // qubutsquarterhebrew
+ {0x7b8c3862, 0x02b8}, // ysuperior
+ {0x7bac8f33, 0x0016}, // controlSYN
+ {0x7bc269dd, 0x0040}, // at
+ {0x7bd5ca88, 0x33ca}, // hasquare
+ {0x7bd8b82b, 0x3005}, // ideographiciterationmark
+ {0x7c301325, 0x0472}, // Fitacyrillic
+ {0x7c3f4566, 0x00e7}, // ccedilla
+ {0x7c4777ca, 0xfcc9}, // lamjeeminitialarabic
+ {0x7c4f7ece, 0x0ab2}, // lagujarati
+ {0x7c53c130, 0x0a13}, // oogurmukhi
+ {0x7c613499, 0x2566}, // SF410000
+ {0x7c8ffdc8, 0x24e7}, // xcircle
+ {0x7c903e9b, 0x1ec7}, // ecircumflexdotbelow
+ {0x7cb539e8, 0x096e}, // eightdeva
+ {0x7cc006ef, 0x222c}, // dblintegral
+ {0x7cdf3e4a, 0x0a2f}, // yagurmukhi
+ {0x7cf2e5f4, 0x0029}, // parenright
+ {0x7cf2ebbd, 0xff96}, // yokatakanahalfwidth
+ {0x7cf8ce2e, 0x3394}, // thzsquare
+ {0x7d32ca29, 0x0127}, // hbar
+ {0x7d44a576, 0x24b1}, // vparen
+ {0x7d4ddd33, 0x2554}, // SF390000
+ {0x7d86a046, 0xff93}, // mokatakanahalfwidth
+ {0x7d8bc7c0, 0x255f}, // SF370000
+ {0x7d981506, 0x0a9f}, // ttagujarati
+ {0x7dc79678, 0x062a}, // afii57418
+ {0x7dc79708, 0x223c}, // tildeoperator
+ {0x7dc7cd45, 0x33b6}, // muvsquare
+ {0x7dcf75fb, 0x223c}, // similar
+ {0x7dd21a95, 0x30b5}, // sakatakana
+ {0x7dd63b6c, 0x1ec1}, // ecircumflexgrave
+ {0x7de5edf4, 0x05b9}, // afii57806
+ {0x7df90cb9, 0x249c}, // aparen
+ {0x7e46df3d, 0x30d7}, // pukatakana
+ {0x7e4f805e, 0x3067}, // dehiragana
+ {0x7e5c9fda, 0x042f}, // IAcyrillic
+ {0x7e701b6d, 0xff0d}, // hyphenmonospace
+ {0x7e739224, 0x3046}, // uhiragana
+ {0x7ebf5260, 0x0031}, // one
+ {0x7eddac19, 0x09f4}, // onenumeratorbengali
+ {0x7f43cdd7, 0x0ab0}, // ragujarati
+ {0x7f56c599, 0x0141}, // Lslash
+ {0x7f5b3579, 0x1e74}, // Utildebelow
+ {0x7f630a55, 0x1ef9}, // ytilde
+ {0x7f7aa31e, 0x0648}, // afii57448
+ {0x7f82e2ff, 0x098f}, // ebengali
+ {0x7f851974, 0x0568}, // etarmenian
+ {0x7f92fe37, 0x066d}, // asteriskarabic
+ {0x7f97f320, 0x301c}, // wavedash
+ {0x7f99701f, 0xfb2a}, // shinshindothebrew
+ {0x7fbd4335, 0x2309}, // ceilingrightbig
+ {0x7ff2087d, 0x01df}, // adieresismacron
+ {0x7ffdad4c, 0x326e}, // kiyeokacirclekorean
+ {0x801f311e, 0x1ebb}, // ehookabove
+ {0x8020003f, 0x05e9}, // shinhebrew
+ {0x8027a085, 0x03d2}, // Upsilonhooksymbol
+ {0x8029e67d, 0x049b}, // kadescendercyrillic
+ {0x803a882f, 0x0a8b}, // rvocalicgujarati
+ {0x80417827, 0x33a9}, // pasquare
+ {0x8048e51c, 0x24e6}, // wcircle
+ {0x80690312, 0x0155}, // racute
+ {0x8076b638, 0xfedf}, // laminitialarabic
+ {0x80792dfa, 0x0478}, // Ukcyrillic
+ {0x8082c40e, 0x251c}, // SF080000
+ {0x8086d5c5, 0x0490}, // afii10050
+ {0x80890e42, 0x0316}, // gravebelowcmb
+ {0x808de596, 0x2492}, // elevenperiod
+ {0x808eef1b, 0x0068}, // h
+ {0x80b21ab0, 0x00f5}, // otilde
+ {0x80b54bce, 0x3170}, // mieumpansioskorean
+ {0x80c36a57, 0x0480}, // Koppacyrillic
+ {0x80f8e4b8, 0x017f}, // slong
+ {0x80fb86d5, 0x00ff}, // ydieresis
+ {0x810ea19f, 0x02bd}, // afii64937
+ {0x810f1d5a, 0x30c6}, // tekatakana
+ {0x812767fa, 0x0555}, // Oharmenian
+ {0x8140027d, 0x24d3}, // dcircle
+ {0x815134d6, 0x00cd}, // Iacute
+ {0x81589a47, 0x2220}, // angle
+ {0x81658581, 0x0207}, // einvertedbreve
+ {0x816871c9, 0x1ec2}, // Ecircumflexhookabove
+ {0x8169da43, 0x0483}, // titlocyrilliccmb
+ {0x8170d563, 0x256b}, // SF530000
+ {0x817f112a, 0x1e37}, // ldotbelow
+ {0x817f9471, 0x1e92}, // Zdotbelow
+ {0x81806da2, 0x328d}, // ideographwoodcircle
+ {0x8184073f, 0x053b}, // Iniarmenian
+ {0x818aac3d, 0x3165}, // ssangnieunkorean
+ {0x81b94a51, 0x3233}, // ideographicsocietyparen
+ {0x81e18c26, 0x30c3}, // tusmallkatakana
+ {0x81eec5b0, 0x320e}, // kiyeokaparenkorean
+ {0x8215ea5e, 0x02b2}, // jsuperior
+ {0x82173176, 0x05d9}, // afii57673
+ {0x8218cc3e, 0x039c}, // Mu
+ {0x822cf82c, 0x3070}, // bahiragana
+ {0x8230e7a0, 0x32a7}, // ideographicleftcircle
+ {0x823da9d2, 0x0413}, // afii10020
+ {0x825a2038, 0x3045}, // usmallhiragana
+ {0x8260fa87, 0x1ea5}, // acircumflexacute
+ {0x8272e0b5, 0x249b}, // twentyperiod
+ {0x827d6960, 0xff40}, // gravemonospace
+ {0x828072a0, 0x2666}, // diamond
+ {0x8281c05a, 0x00b5}, // mu1
+ {0x8291bd7d, 0x248e}, // sevenperiod
+ {0x82ab3b91, 0x03a3}, // Sigma
+ {0x82b182a5, 0x057e}, // vewarmenian
+ {0x82b58e6a, 0x1e94}, // Zlinebelow
+ {0x82b5e1a0, 0x266b}, // eighthnotebeamed
+ {0x82b5fc2f, 0x05bc}, // afii57807
+ {0x82c39f59, 0x1e62}, // Sdotbelow
+ {0x82c3da09, 0x0a1a}, // cagurmukhi
+ {0x82d03f46, 0x056e}, // caarmenian
+ {0x82d4758a, 0x25e6}, // whitebullet
+ {0x82db51c1, 0x05b6}, // segolnarrowhebrew
+ {0x82dffbf3, 0xfe9c}, // thehmedialarabic
+ {0x8322270c, 0x1e49}, // nlinebelow
+ {0x832d9a03, 0x263b}, // invsmileface
+ {0x83393d74, 0xfef7}, // lamalefhamzaaboveisolatedarabic
+ {0x83589346, 0x1eb0}, // Abrevegrave
+ {0x837601e2, 0x0ae7}, // onegujarati
+ {0x8377c3f2, 0x026b}, // lmiddletilde
+ {0x837a2830, 0x2113}, // lsquare
+ {0x837c1552, 0x2172}, // threeroman
+ {0x8388f2be, 0x00c9}, // Eacute
+ {0x83a89a03, 0x045f}, // dzhecyrillic
+ {0x83aeaef2, 0x1e23}, // hdotaccent
+ {0x83c5c486, 0x05e7}, // afii57687
+ {0x83cd4320, 0x0565}, // echarmenian
+ {0x83e3c3a4, 0x3075}, // huhiragana
+ {0x83f86a79, 0x339e}, // squarekm
+ {0x83fc4077, 0x0037}, // seven
+ {0x83fe9093, 0x0441}, // escyrillic
+ {0x840c8507, 0x0313}, // commaabovecmb
+ {0x8411d0c3, 0x1e3e}, // Macute
+ {0x84132896, 0x0140}, // ldot
+ {0x841d72ff, 0x02e4}, // glottalstopreversedsuperior
+ {0x841d9ceb, 0x0572}, // ghadarmenian
+ {0x8424a64d, 0x05b5}, // tserewidehebrew
+ {0x843c7aca, 0x323f}, // ideographicallianceparen
+ {0x8445bb28, 0x0905}, // adeva
+ {0x8459e5f4, 0x2248}, // approxequal
+ {0x84776c26, 0x329d}, // ideographicexcellentcircle
+ {0x847d9b8a, 0x1ec5}, // ecircumflextilde
+ {0x847ed2d4, 0x0540}, // Hoarmenian
+ {0x8487ed37, 0x0621}, // hamzaarabic
+ {0x848baa07, 0xfc5f}, // shaddakasratanarabic
+ {0x8494dfda, 0x05b1}, // hatafsegol24
+ {0x8498dbae, 0xfe86}, // wawhamzaabovefinalarabic
+ {0x849b0297, 0x1e2e}, // Idieresisacute
+ {0x84a67fee, 0x323a}, // ideographiccallparen
+ {0x84b1d089, 0x0430}, // acyrillic
+ {0x84de35f4, 0x0210}, // Rdblgrave
+ {0x84e0b580, 0x04ef}, // umacroncyrillic
+ {0x8501599a, 0x30f6}, // kesmallkatakana
+ {0x85103d59, 0xfb39}, // yoddagesh
+ {0x853a4f53, 0x217b}, // twelveroman
+ {0x855c805d, 0xff1a}, // colonmonospace
+ {0x856013c8, 0x248a}, // threeperiod
+ {0x8567c154, 0x01bc}, // Tonefive
+ {0x85a6c8bc, 0x310a}, // tbopomofo
+ {0x85b41716, 0x201a}, // quotesinglbase
+ {0x85b6b018, 0x2228}, // logicalor
+ {0x85c83eb6, 0xff77}, // kikatakanahalfwidth
+ {0x85ce16f4, 0x05da}, // afii57674
+ {0x85e92466, 0x2282}, // propersubset
+ {0x85ee23d7, 0x311b}, // obopomofo
+ {0x85f325ac, 0x064c}, // dammatanaltonearabic
+ {0x85f47ad1, 0x0660}, // zerohackarabic
+ {0x85f89b18, 0x3049}, // osmallhiragana
+ {0x86129c18, 0x2209}, // notelementof
+ {0x861e8048, 0xff54}, // tmonospace
+ {0x861ef200, 0x1e75}, // utildebelow
+ {0x8654695b, 0x0020}, // spacehackarabic
+ {0x865c7659, 0x0312}, // commaturnedabovecmb
+ {0x8665f7ab, 0x201b}, // quotereversed
+ {0x8686768e, 0x093e}, // aavowelsigndeva
+ {0x8694e2ae, 0x3132}, // ssangkiyeokkorean
+ {0x8699e716, 0x09ef}, // ninebengali
+ {0x869fef4c, 0x064b}, // afii57451
+ {0x86a662d1, 0x05e7}, // qofhebrew
+ {0x86a80595, 0x0535}, // Echarmenian
+ {0x86c2d4a4, 0x01ef}, // ezhcaron
+ {0x86d0b880, 0x01c7}, // LJ
+ {0x86d66230, 0x04ad}, // tedescendercyrillic
+ {0x86e222a0, 0x05d9}, // yod
+ {0x86eea6f9, 0x03a4}, // Tau
+ {0x870315b6, 0x30e1}, // mekatakana
+ {0x8713ac2f, 0xfb4b}, // vavholam
+ {0x87634c8c, 0x05bd}, // siluqhebrew
+ {0x877afbbb, 0xfb35}, // afii57723
+ {0x877f7c7a, 0xfeb4}, // seenmedialarabic
+ {0x8787b92b, 0x02c0}, // glottalstopmod
+ {0x87a041c7, 0x0446}, // afii10088
+ {0x87a4b30d, 0x263c}, // compass
+ {0x87b6c8ba, 0x00c3}, // Atilde
+ {0x87bb7ac5, 0x020b}, // iinvertedbreve
+ {0x87e03b4f, 0x0642}, // afii57442
+ {0x87e97d46, 0x04dd}, // zhedieresiscyrillic
+ {0x87ef58ab, 0xfe30}, // twodotleadervertical
+ {0x87fc47e5, 0x24d2}, // ccircle
+ {0x88044bf5, 0x2167}, // Eightroman
+ {0x880a9911, 0x1e34}, // Klinebelow
+ {0x881905a5, 0x0e22}, // yoyakthai
+ {0x881a4ba0, 0x0931}, // rradeva
+ {0x88264250, 0x1e71}, // tcircumflexbelow
+ {0x8826a561, 0x03cd}, // upsilontonos
+ {0x883bae04, 0x248c}, // fiveperiod
+ {0x8841d986, 0x30b8}, // zikatakana
+ {0x8853f322, 0x339b}, // mumsquare
+ {0x885dcb80, 0x00a7}, // section
+ {0x886c5d13, 0x0636}, // dadarabic
+ {0x8876a700, 0x1e98}, // wring
+ {0x888833ba, 0x02e7}, // tonebarmidmod
+ {0x888e1142, 0x026d}, // lhookretroflex
+ {0x88a12621, 0x05d4}, // afii57668
+ {0x88b6884b, 0x06f7}, // sevenpersian
+ {0x88c2fc70, 0x0a6a}, // fourgurmukhi
+ {0x88c96d26, 0xfb4a}, // tavdages
+ {0x88cc32a3, 0x1ef6}, // Yhookabove
+ {0x88da326f, 0x0546}, // Nowarmenian
+ {0x88e4df33, 0x1e1b}, // etildebelow
+ {0x88e6eee8, 0x3119}, // sbopomofo
+ {0x88ea0124, 0x0949}, // ocandravowelsigndeva
+ {0x88f38eed, 0x21d0}, // arrowdblleft
+ {0x88f62270, 0x314f}, // akorean
+ {0x88fdc1bb, 0x308e}, // wasmallhiragana
+ {0x88fdcf2e, 0x039e}, // Xi
+ {0x893a8f13, 0x09b9}, // habengali
+ {0x89468742, 0x0e1f}, // fofanthai
+ {0x897340ea, 0x3207}, // ieungparenkorean
+ {0x897854cd, 0x060c}, // afii57388
+ {0x897adc4f, 0x0128}, // Itilde
+ {0x89aa67b7, 0x2161}, // Tworoman
+ {0x89ace505, 0xff06}, // ampersandmonospace
+ {0x89d8daae, 0x25c9}, // fisheye
+ {0x89e05206, 0x0022}, // quotedbl
+ {0x89e2c74b, 0x062d}, // afii57421
+ {0x89ed1e17, 0x3171}, // kapyeounmieumkorean
+ {0x89f4c981, 0x02dc}, // tilde
+ {0x89f7042b, 0xfe38}, // bracerightvertical
+ {0x89fc7dc4, 0x05bf}, // rafehebrew
+ {0x8a1958e1, 0x03d1}, // theta1
+ {0x8a27f623, 0x030e}, // dblverticallineabovecmb
+ {0x8a36e0e0, 0x0a89}, // ugujarati
+ {0x8a64ee32, 0x23a2}, // bracketleftex
+ {0x8a743e71, 0x3279}, // thieuthacirclekorean
+ {0x8a7ff438, 0x0632}, // zainarabic
+ {0x8ab11ede, 0x0907}, // ideva
+ {0x8ab50af4, 0xfedb}, // kafinitialarabic
+ {0x8ac9c3df, 0x00cb}, // Edieresis
+ {0x8ae82e18, 0x05c2}, // sindothebrew
+ {0x8aebef01, 0x064f}, // afii57455
+ {0x8af00e69, 0x3396}, // mlsquare
+ {0x8af0299d, 0x2465}, // sixcircle
+ {0x8af3a8ba, 0x1e55}, // pacute
+ {0x8afdb385, 0x05b2}, // hatafpatahnarrowhebrew
+ {0x8b04e879, 0x0573}, // cheharmenian
+ {0x8b2ea84b, 0x045f}, // afii10193
+ {0x8b336b03, 0x33c3}, // bqsquare
+ {0x8b3d9ff3, 0x313a}, // rieulkiyeokkorean
+ {0x8b46ba2c, 0x1e10}, // Dcedilla
+ {0x8b5fbe71, 0x0209}, // idblgrave
+ {0x8b6e36e5, 0x0001}, // controlSTX
+ {0x8b91eefb, 0x0135}, // jcircumflex
+ {0x8ba07e30, 0x3236}, // ideographicfinancialparen
+ {0x8ba61ca6, 0x0170}, // Udblacute
+ {0x8bb0c7c5, 0x04a4}, // Enghecyrillic
+ {0x8bb9fe95, 0x1e35}, // klinebelow
+ {0x8bc6e552, 0x027a}, // rlonglegturned
+ {0x8bd60707, 0x33ce}, // squarekmcapital
+ {0x8bdd4dfa, 0x05a1}, // pazerhebrew
+ {0x8be0aeeb, 0x1e28}, // Hcedilla
+ {0x8bf3f9ec, 0x0aed}, // sevengujarati
+ {0x8bfc7ed2, 0x02e9}, // tonebarextralowmod
+ {0x8c09cf1f, 0x0251}, // ascript
+ {0x8c169f16, 0x0a4c}, // aumatragurmukhi
+ {0x8c1a91ec, 0x1ed2}, // Ocircumflexgrave
+ {0x8c21575a, 0x2175}, // sixroman
+ {0x8c3dc32f, 0x0632}, // afii57426
+ {0x8c5fd3c7, 0x05dd}, // afii57677
+ {0x8c7614fd, 0x0046}, // F
+ {0x8c789c98, 0x0255}, // ccurl
+ {0x8c7eba77, 0x3114}, // chbopomofo
+ {0x8c941f03, 0x0639}, // afii57433
+ {0x8c9c3f66, 0x0e37}, // saraueethai
+ {0x8cb6cb59, 0xfb6c}, // vehinitialarabic
+ {0x8cbe2a76, 0x0951}, // udattadeva
+ {0x8cc838c1, 0x099f}, // ttabengali
+ {0x8ccab94e, 0x311d}, // ehbopomofo
+ {0x8cd2ceac, 0x322f}, // ideographicearthparen
+ {0x8cdd7a5e, 0xfefb}, // lamalefisolatedarabic
+ {0x8cfeaaad, 0x04b4}, // Tetsecyrillic
+ {0x8d0e3bcc, 0x0162}, // Tcommaaccent
+ {0x8d15f0f9, 0x01d8}, // udieresisacute
+ {0x8d17055f, 0xff23}, // Cmonospace
+ {0x8d2183aa, 0x00ee}, // icircumflex
+ {0x8d24b8c2, 0x0457}, // afii10104
+ {0x8d262f6a, 0x33ad}, // radsquare
+ {0x8d3bc051, 0x1eb1}, // abrevegrave
+ {0x8d430411, 0x0288}, // tretroflexhook
+ {0x8d4c7b99, 0x0473}, // afii10195
+ {0x8d557bba, 0xfe98}, // tehmedialarabic
+ {0x8d5ae6d4, 0x0440}, // ercyrillic
+ {0x8d62c566, 0x21e0}, // arrowdashleft
+ {0x8d7f2614, 0x2164}, // Fiveroman
+ {0x8db3e76c, 0x1e97}, // tdieresis
+ {0x8dbd6f57, 0xffe1}, // sterlingmonospace
+ {0x8dc41abc, 0x1e33}, // kdotbelow
+ {0x8de51633, 0x1ed7}, // ocircumflextilde
+ {0x8df7afeb, 0x0968}, // twodeva
+ {0x8e0d9fbd, 0x0337}, // solidusshortoverlaycmb
+ {0x8e2538f6, 0x0420}, // Ercyrillic
+ {0x8e325301, 0x0133}, // ij
+ {0x8e3386c6, 0x0439}, // iishortcyrillic
+ {0x8e360b54, 0x027d}, // rhook
+ {0x8e40b292, 0x01fb}, // aringacute
+ {0x8e467ab5, 0x1ecc}, // Odotbelow
+ {0x8e474342, 0x1e2a}, // Hbrevebelow
+ {0x8e5ae93f, 0x0909}, // udeva
+ {0x8e5f575a, 0x0458}, // jecyrillic
+ {0x8e5f7ac5, 0x01d2}, // ocaron
+ {0x8e6f5aa1, 0x3074}, // pihiragana
+ {0x8e757f60, 0x308a}, // rihiragana
+ {0x8e7aa1b5, 0x207c}, // equalsuperior
+ {0x8e8e0711, 0x042f}, // afii10049
+ {0x8e91af6c, 0x3000}, // ideographicspace
+ {0x8ec9ea4f, 0xfb40}, // nundagesh
+ {0x8eca9ee8, 0x045c}, // kjecyrillic
+ {0x8ed1765d, 0xff36}, // Vmonospace
+ {0x8ed539a2, 0x278a}, // onecircleinversesansserif
+ {0x8f084bdd, 0x3204}, // mieumparenkorean
+ {0x8f19bc84, 0x0e4a}, // maitrithai
+ {0x8f289d06, 0x0444}, // efcyrillic
+ {0x8f28eae1, 0x013f}, // Ldotaccent
+ {0x8f4b9c51, 0x0404}, // afii10053
+ {0x8f505863, 0x04f2}, // Uhungarumlautcyrillic
+ {0x8f52df2f, 0x0256}, // dtail
+ {0x8f5e284f, 0xfed6}, // qaffinalarabic
+ {0x8f760fbe, 0x25e5}, // blackupperrighttriangle
+ {0x8f7f3f67, 0x0645}, // afii57445
+ {0x8f89b56f, 0x05b8}, // qamatsqatanhebrew
+ {0x8fa69b6c, 0x0331}, // macronbelowcmb
+ {0x8fbba331, 0x0205}, // edblgrave
+ {0x8fc968d8, 0x01b9}, // ezhreversed
+ {0x8fce94ba, 0x0395}, // Epsilon
+ {0x8fd18473, 0x2593}, // shadedark
+ {0x8fe2c390, 0x0e53}, // threethai
+ {0x8fe329b9, 0x266c}, // beamedsixteenthnotes
+ {0x8fe85541, 0x0637}, // afii57431
+ {0x8ff897b6, 0x0042}, // B
+ {0x900e8281, 0x1e3b}, // llinebelow
+ {0x900fb5c0, 0x0144}, // nacute
+ {0x902443c2, 0xfe52}, // periodsmall
+ {0x9024a760, 0x029d}, // jcrossedtail
+ {0x90307534, 0x3059}, // suhiragana
+ {0x9059f738, 0x00b7}, // middot
+ {0x906746a4, 0xff75}, // okatakanahalfwidth
+ {0x907d968c, 0x0a9b}, // chagujarati
+ {0x90872973, 0x0538}, // Etarmenian
+ {0x9098fbd4, 0x0002}, // controlSOT
+ {0x90995fc1, 0x1e09}, // ccedillaacute
+ {0x90a162b6, 0x05b4}, // hiriqwidehebrew
+ {0x90b86ad8, 0x30dd}, // pokatakana
+ {0x90b9c076, 0xff0f}, // slashmonospace
+ {0x90c2be85, 0x0268}, // istroke
+ {0x910a1b16, 0x03f1}, // rhosymbolgreek
+ {0x9132f814, 0xff22}, // Bmonospace
+ {0x9134ebbc, 0x01cb}, // Nj
+ {0x913ff5ff, 0x3125}, // engbopomofo
+ {0x9141d43c, 0x2126}, // Ohm
+ {0x914ce494, 0x05e6}, // afii57686
+ {0x9166eec8, 0x33ba}, // pwsquare
+ {0x916cdeb8, 0xfed2}, // fehfinalarabic
+ {0x917f2f3f, 0x0438}, // afii10074
+ {0x9181b388, 0x01eb}, // oogonek
+ {0x9184e24f, 0x30ab}, // kakatakana
+ {0x919f5679, 0x00ca}, // Ecircumflex
+ {0x91acc220, 0xff91}, // mukatakanahalfwidth
+ {0x91accd4b, 0x0937}, // ssadeva
+ {0x91c3e17e, 0x05f2}, // afii57718
+ {0x91d99037, 0x1eb9}, // edotbelow
+ {0x91de3939, 0x064f}, // dammalowarabic
+ {0x91e65480, 0x0abf}, // ivowelsigngujarati
+ {0x91eaac20, 0x3214}, // siosaparenkorean
+ {0x920dae79, 0x039f}, // Omicron
+ {0x9215b042, 0x3147}, // ieungkorean
+ {0x9220d7f0, 0xff69}, // usmallkatakanahalfwidth
+ {0x923767e3, 0x3133}, // kiyeoksioskorean
+ {0x9239e7fb, 0x062b}, // theharabic
+ {0x923bf3d0, 0x0330}, // tildebelowcmb
+ {0x926b691e, 0x0100}, // Amacron
+ {0x92aa52d3, 0x30fb}, // dotkatakana
+ {0x92e2ffd9, 0x21e8}, // arrowrightwhite
+ {0x92f283dc, 0x25d8}, // bulletinverse
+ {0x92f96dbe, 0x1e56}, // Pdotaccent
+ {0x930724f6, 0x06ba}, // noonghunnaarabic
+ {0x930c1a0b, 0x001b}, // controlESC
+ {0x932512ee, 0x03ea}, // Gangiacoptic
+ {0x9330a2fc, 0x0336}, // strokelongoverlaycmb
+ {0x934b1595, 0x062d}, // haharabic
+ {0x93959445, 0x263a}, // smileface
+ {0x939a56c4, 0x03ad}, // epsilontonos
+ {0x939b5eb8, 0xfe92}, // behmedialarabic
+ {0x93bca3b6, 0x099e}, // nyabengali
+ {0x93e00dc4, 0x2193}, // arrowdown
+ {0x93eef318, 0x0263}, // gammalatinsmall
+ {0x9404d5fc, 0x33d5}, // squaremil
+ {0x941a6b5f, 0x0a23}, // nnagurmukhi
+ {0x941b20fa, 0xfe4b}, // overlinewavy
+ {0x942ad1c7, 0x09be}, // aavowelsignbengali
+ {0x9453959c, 0x24bd}, // Hcircle
+ {0x9464bc2e, 0x1e66}, // Scarondotaccent
+ {0x94724b66, 0x21bc}, // harpoonleftbarbup
+ {0x94803386, 0x09d7}, // aulengthmarkbengali
+ {0x948a9ce4, 0x05de}, // afii57678
+ {0x949bc805, 0x01da}, // udieresiscaron
+ {0x94ae0441, 0x0410}, // Acyrillic
+ {0x94b7f6ea, 0x0463}, // yatcyrillic
+ {0x94c36e74, 0x3261}, // nieuncirclekorean
+ {0x94c9571f, 0x25ac}, // blackrectangle
+ {0x94d13d1c, 0xfe39}, // tortoiseshellbracketleftvertical
+ {0x94d74b96, 0x1e64}, // Sacutedotaccent
+ {0x94e6f584, 0x3058}, // zihiragana
+ {0x94ee5ae7, 0x2792}, // ninecircleinversesansserif
+ {0x94f9a508, 0x3265}, // pieupcirclekorean
+ {0x9518a20d, 0x30d9}, // bekatakana
+ {0x951a0238, 0xfdf2}, // lamlamhehisolatedarabic
+ {0x951ae869, 0x09dc}, // rrabengali
+ {0x952cce64, 0x04ee}, // Umacroncyrillic
+ {0x952ec009, 0x0988}, // iibengali
+ {0x953a0a51, 0x2211}, // summation
+ {0x954a8776, 0x03d1}, // thetasymbolgreek
+ {0x95526ac8, 0x2500}, // SF100000
+ {0x9559e176, 0x05b7}, // patah2a
+ {0x955dbbe7, 0x23aa}, // braceex
+ {0x957765bc, 0xfe8a}, // yehhamzaabovefinalarabic
+ {0x958830cb, 0x2669}, // quarternote
+ {0x9588e4f1, 0xff99}, // rukatakanahalfwidth
+ {0x959cf6c1, 0x203b}, // referencemark
+ {0x95af6475, 0x05e3}, // finalpehebrew
+ {0x95aff05f, 0x03ca}, // iotadieresis
+ {0x95b3bc07, 0xfb46}, // tsadidagesh
+ {0x95bed968, 0x0e15}, // totaothai
+ {0x95cabf3f, 0x21cf}, // arrowrightdblstroke
+ {0x95d7e2f4, 0x0032}, // two
+ {0x95ed768c, 0x05e0}, // nun
+ {0x960140f0, 0x2496}, // fifteenperiod
+ {0x961b2e15, 0x1e6f}, // tlinebelow
+ {0x96220dd7, 0x318a}, // yuyeokorean
+ {0x962b0c72, 0x3323}, // sentosquare
+ {0x9638605a, 0x0669}, // ninearabic
+ {0x967b01ac, 0x05b9}, // holamhebrew
+ {0x967d0326, 0x3134}, // nieunkorean
+ {0x96a5e022, 0x25d9}, // invcircle
+ {0x96b677d5, 0x0153}, // oe
+ {0x96c05d98, 0x01f2}, // Dz
+ {0x96c1ab16, 0x247f}, // twelveparen
+ {0x96d9cc68, 0x0427}, // Checyrillic
+ {0x96fd8ec6, 0x1e7a}, // Umacrondieresis
+ {0x9711eb31, 0x21aa}, // arrowhookleft
+ {0x9741ad45, 0x05bf}, // rafe
+ {0x975dc1dc, 0x32a5}, // ideographiccentrecircle
+ {0x9776a4ba, 0x0007}, // controlBEL
+ {0x977737b3, 0x0265}, // hturned
+ {0x9778a35b, 0x0562}, // benarmenian
+ {0x977e0dfa, 0x25d0}, // circlewithlefthalfblack
+ {0x97843a2e, 0x09f8}, // denominatorminusonenumeratorbengali
+ {0x978c8c89, 0x03ac}, // alphatonos
+ {0x97ae16ea, 0x23a1}, // bracketlefttp
+ {0x97b3e7db, 0x24c3}, // Ncircle
+ {0x97e45478, 0x05b6}, // segol2c
+ {0x97ea0cb5, 0x04d8}, // Schwacyrillic
+ {0x97ebb44e, 0x015f}, // scedilla
+ {0x97f03f9c, 0x0419}, // Iishortcyrillic
+ {0x97f6721e, 0x05bf}, // afii57841
+ {0x980e76a2, 0x1e32}, // Kdotbelow
+ {0x98148d7b, 0xff14}, // fourmonospace
+ {0x981fc90b, 0x0a1b}, // chagurmukhi
+ {0x982585a7, 0x260e}, // telephoneblack
+ {0x982718e0, 0x003c}, // less
+ {0x982eb09a, 0x0e5b}, // khomutthai
+ {0x9853033e, 0x04b5}, // tetsecyrillic
+ {0x987e6d13, 0x0411}, // Becyrillic
+ {0x9896e370, 0x0402}, // afii10051
+ {0x98b02dc0, 0x0a48}, // aimatragurmukhi
+ {0x98d74b1c, 0x01c8}, // Lj
+ {0x98d9aba5, 0x03ae}, // etatonos
+ {0x98eba766, 0x018b}, // Dtopbar
+ {0x98f4783f, 0x24ac}, // qparen
+ {0x98f4b751, 0x0651}, // afii57457
+ {0x99104281, 0x02e5}, // tonebarextrahighmod
+ {0x99235205, 0x05dc}, // lamedhebrew
+ {0x994ebac3, 0x05c0}, // afii57842
+ {0x99725844, 0x0320}, // minusbelowcmb
+ {0x9982855c, 0x0686}, // afii57507
+ {0x99830dc7, 0x062c}, // afii57420
+ {0x99863852, 0x03ed}, // shimacoptic
+ {0x99997c4f, 0x3314}, // kirosquare
+ {0x999c619c, 0x3078}, // hehiragana
+ {0x999f4db4, 0x05b4}, // afii57793
+ {0x99cca883, 0xff04}, // dollarmonospace
+ {0x99e63f81, 0x0962}, // lvocalicvowelsigndeva
+ {0x9a069ea3, 0x2267}, // greateroverequal
+ {0x9a098276, 0xfea2}, // hahfinalarabic
+ {0x9a157ece, 0x246a}, // elevencircle
+ {0x9a1c929d, 0x043c}, // afii10078
+ {0x9a310f17, 0xff51}, // qmonospace
+ {0x9a3391f5, 0x0190}, // Eopen
+ {0x9a464a33, 0x0174}, // Wcircumflex
+ {0x9a50ec2e, 0x05b6}, // segolquarterhebrew
+ {0x9a7aab21, 0x05c3}, // sofpasuqhebrew
+ {0x9ac6c137, 0x0e0f}, // topatakthai
+ {0x9aea680b, 0x3029}, // ninehangzhou
+ {0x9af6d63b, 0x054a}, // Peharmenian
+ {0x9b09b61d, 0x0a10}, // aigurmukhi
+ {0x9b0db21d, 0x0402}, // Djecyrillic
+ {0x9b100042, 0x0e21}, // momathai
+ {0x9b29e68e, 0x0278}, // philatin
+ {0x9b3ff954, 0x0e56}, // sixthai
+ {0x9b5a3eb3, 0x0626}, // afii57414
+ {0x9b712e01, 0x1eba}, // Ehookabove
+ {0x9b73811a, 0x2227}, // logicaland
+ {0x9b76648b, 0x041f}, // afii10033
+ {0x9b7712b3, 0x1e5a}, // Rdotbelow
+ {0x9b8591a5, 0x30ca}, // nakatakana
+ {0x9b950b60, 0x095e}, // fadeva
+ {0x9ba02025, 0x01e9}, // kcaron
+ {0x9bca0720, 0x1e93}, // zdotbelow
+ {0x9bcccde6, 0x0e4e}, // yamakkanthai
+ {0x9bd59a36, 0x300e}, // whitecornerbracketleft
+ {0x9bdb98a4, 0x1e5e}, // Rlinebelow
+ {0x9be54046, 0x0e0a}, // chochangthai
+ {0x9bfe067d, 0x0405}, // Dzecyrillic
+ {0x9c14c866, 0x0484}, // palatalizationcyrilliccmb
+ {0x9c1ff986, 0x05f1}, // afii57717
+ {0x9c30e64e, 0x0121}, // gdot
+ {0x9c3d076c, 0x002d}, // hyphen
+ {0x9c5df589, 0x03b2}, // beta
+ {0x9c5e488c, 0x05d3}, // dalethebrew
+ {0x9c743ddb, 0x3239}, // ideographicrepresentparen
+ {0x9cc9b890, 0x2a00}, // circledottext
+ {0x9cd2074a, 0x0a36}, // shagurmukhi
+ {0x9ce0dacf, 0xfebb}, // sadinitialarabic
+ {0x9ce3d2fe, 0x06d2}, // afii57519
+ {0x9ce9cdfc, 0x0408}, // Jecyrillic
+ {0x9ce9f027, 0x0426}, // Tsecyrillic
+ {0x9cf54095, 0x20aa}, // newsheqelsign
+ {0x9d1b1141, 0x25d8}, // invbullet
+ {0x9d1ed2c0, 0x0120}, // Gdotaccent
+ {0x9d25f804, 0x0294}, // glottalstop
+ {0x9d3a5187, 0x03bb}, // lambda
+ {0x9d4507ca, 0x00a0}, // nonbreakingspace
+ {0x9d4ea24d, 0x045a}, // njecyrillic
+ {0x9d5ba323, 0x3145}, // sioskorean
+ {0x9d5eb9a4, 0x001f}, // controlUS
+ {0x9d662219, 0x332a}, // haitusquare
+ {0x9d760ad7, 0x3318}, // guramusquare
+ {0x9d770652, 0x1e2b}, // hbrevebelow
+ {0x9db9ebc8, 0x30ba}, // zukatakana
+ {0x9dee7277, 0x2015}, // afii00208
+ {0x9df531bb, 0x059b}, // tevirhebrew
+ {0x9e021469, 0x22cf}, // curlyand
+ {0x9e062707, 0x02a2}, // glottalstopstrokereversed
+ {0x9e0bf218, 0x02b1}, // hhooksuperior
+ {0x9e0d1458, 0x02c4}, // arrowheadupmod
+ {0x9e1247f8, 0x0033}, // three
+ {0x9e248728, 0x3053}, // kohiragana
+ {0x9e2d5a68, 0x053f}, // Kenarmenian
+ {0x9e5de325, 0x222e}, // contourintegral
+ {0x9e65e800, 0x248b}, // fourperiod
+ {0x9e98d52c, 0x2483}, // sixteenparen
+ {0x9ea14168, 0x05b7}, // patahquarterhebrew
+ {0x9ea23fe1, 0x00f1}, // ntilde
+ {0x9eac193b, 0x00ef}, // idieresis
+ {0x9eb5aea3, 0x3142}, // pieupkorean
+ {0x9ebea1a0, 0x3150}, // aekorean
+ {0x9ee7bbd1, 0x094b}, // ovowelsigndeva
+ {0x9eedaba9, 0x0113}, // emacron
+ {0x9f023815, 0x20ac}, // euro
+ {0x9f30fc87, 0xfec2}, // tahfinalarabic
+ {0x9f37894c, 0x040e}, // afii10062
+ {0x9f53036c, 0x0a19}, // ngagurmukhi
+ {0x9f65cf71, 0x1e25}, // hdotbelow
+ {0x9f69147e, 0x1e61}, // sdotaccent
+ {0x9f6f9105, 0x0433}, // gecyrillic
+ {0x9f739695, 0x04f9}, // yerudieresiscyrillic
+ {0x9f79f6eb, 0xfb33}, // daletdageshhebrew
+ {0x9f8cff14, 0x003e}, // greater
+ {0x9f94b2e4, 0x04a1}, // kabashkircyrillic
+ {0x9fa5f7ad, 0x0e49}, // maithothai
+ {0x9fc7ffac, 0x05b4}, // hiriqhebrew
+ {0x9fd406b1, 0xfed8}, // qafmedialarabic
+ {0x9fd7c50e, 0x05bb}, // afii57796
+ {0x9fdfc7a1, 0x00b0}, // degree
+ {0x9ffeaad9, 0x01ed}, // oogonekmacron
+ {0xa0144bc6, 0xfc8d}, // noonnoonfinalarabic
+ {0xa0166e3d, 0x3159}, // waekorean
+ {0xa016fb2d, 0x016b}, // umacron
+ {0xa0286aa8, 0x1e04}, // Bdotbelow
+ {0xa03db58b, 0x02d2}, // ringhalfrightcentered
+ {0xa05ccf71, 0x05e1}, // samekhhebrew
+ {0xa069fd2d, 0x012b}, // imacron
+ {0xa08ca5a7, 0x2491}, // tenperiod
+ {0xa0a317f9, 0x0574}, // menarmenian
+ {0xa0c2ffe3, 0x247a}, // sevenparen
+ {0xa0e40fac, 0x337b}, // heiseierasquare
+ {0xa0e487b8, 0x33cd}, // KKsquare
+ {0xa0ee672b, 0x3143}, // ssangpieupkorean
+ {0xa100bc11, 0x043e}, // ocyrillic
+ {0xa10462a8, 0x0394}, // Deltagreek
+ {0xa11f6f39, 0x0071}, // q
+ {0xa12507ea, 0x2177}, // eightroman
+ {0xa12d2230, 0x05b5}, // tsere2b
+ {0xa134a191, 0xff11}, // onemonospace
+ {0xa14f5367, 0x05b0}, // sheva15
+ {0xa157c7c6, 0x0643}, // kafarabic
+ {0xa15811a3, 0x061f}, // questionarabic
+ {0xa1697005, 0x0015}, // controlNAK
+ {0xa16fa8a4, 0x22a3}, // tackleft
+ {0xa1703e0a, 0x3219}, // thieuthaparenkorean
+ {0xa1850262, 0x05c2}, // afii57803
+ {0xa1a14a63, 0x1eb8}, // Edotbelow
+ {0xa1ed89db, 0x0202}, // Ainvertedbreve
+ {0xa20cadbf, 0x0062}, // b
+ {0xa20ea9da, 0x24aa}, // oparen
+ {0xa212ed2d, 0x0e38}, // sarauthai
+ {0xa2448aa1, 0x01e6}, // Gcaron
+ {0xa2543878, 0x326f}, // nieunacirclekorean
+ {0xa254ebdd, 0xfb3e}, // memdagesh
+ {0xa259bfe7, 0xff19}, // ninemonospace
+ {0xa262edc1, 0xfe49}, // overlinedashed
+ {0xa26bc10f, 0x061b}, // semicolonarabic
+ {0xa27876ee, 0xfe35}, // parenleftvertical
+ {0xa28a5f58, 0xfec8}, // zahmedialarabic
+ {0xa28ba8ac, 0x317d}, // siospieupkorean
+ {0xa2972ad9, 0x2305}, // projective
+ {0xa2be0dd9, 0x062b}, // afii57419
+ {0xa2c2120e, 0x0e52}, // twothai
+ {0xa2d967e3, 0x2083}, // threeinferior
+ {0xa2e1fb7a, 0x221e}, // infinity
+ {0xa2f62d95, 0x306e}, // nohiragana
+ {0xa3004c6f, 0x092e}, // madeva
+ {0xa316ccc6, 0x2044}, // fraction
+ {0xa32a4538, 0xfb1f}, // afii57705
+ {0xa334b2d1, 0x043d}, // afii10079
+ {0xa34b5d2f, 0x0e57}, // seventhai
+ {0xa36dbdee, 0x30db}, // hokatakana
+ {0xa386d6fe, 0x3327}, // tonsquare
+ {0xa3903917, 0x3270}, // tikeutacirclekorean
+ {0xa39b2570, 0xfefc}, // lamaleffinalarabic
+ {0xa3b51a89, 0x0597}, // reviamugrashhebrew
+ {0xa3cc74fa, 0x3056}, // zahiragana
+ {0xa3d9a90d, 0x0499}, // zedescendercyrillic
+ {0xa3e95215, 0xfef9}, // lamalefhamzabelowisolatedarabic
+ {0xa3ec709c, 0x0569}, // toarmenian
+ {0xa3fbf1d9, 0x0497}, // zhedescendercyrillic
+ {0xa3fe88d1, 0x0104}, // Aogonek
+ {0xa40369ed, 0x32a9}, // ideographicmedicinecircle
+ {0xa4259ac9, 0xff76}, // kakatakanahalfwidth
+ {0xa43cdc2e, 0xfb44}, // pedageshhebrew
+ {0xa446d45f, 0x3224}, // fiveideographicparen
+ {0xa450f946, 0x0454}, // afii10101
+ {0xa456f11e, 0x09e8}, // twobengali
+ {0xa457c062, 0x30ad}, // kikatakana
+ {0xa45b3183, 0x0aeb}, // fivegujarati
+ {0xa45b7f21, 0x010e}, // Dcaron
+ {0xa4627c0f, 0x0204}, // Edblgrave
+ {0xa478f921, 0x09e0}, // rrvocalicbengali
+ {0xa4863185, 0x30f4}, // vukatakana
+ {0xa4922e7c, 0x0341}, // acutetonecmb
+ {0xa4aa1092, 0x05b1}, // hatafsegol30
+ {0xa4aa8935, 0xff58}, // xmonospace
+ {0xa4af8f73, 0x2282}, // subset
+ {0xa4decb10, 0x0a72}, // irigurmukhi
+ {0xa50a3a99, 0x0630}, // afii57424
+ {0xa50cf621, 0xff7e}, // sekatakanahalfwidth
+ {0xa512e58d, 0xff83}, // tekatakanahalfwidth
+ {0xa517b724, 0xfee6}, // noonfinalarabic
+ {0xa52168e0, 0x2325}, // option
+ {0xa54253fb, 0x05e7}, // qof
+ {0xa545c2a6, 0xfb2a}, // afii57694
+ {0xa553cf3e, 0x003b}, // semicolon
+ {0xa56dfce7, 0x0e03}, // khokhuatthai
+ {0xa58382dd, 0x0137}, // kcedilla
+ {0xa5b93826, 0x2555}, // SF220000
+ {0xa5ecbdaa, 0x03b5}, // epsilon
+ {0xa6056425, 0x05b8}, // qamatswidehebrew
+ {0xa60745ee, 0x2250}, // approaches
+ {0xa6281f81, 0x0142}, // lslash
+ {0xa62afc92, 0x0534}, // Daarmenian
+ {0xa6454b66, 0x2498}, // seventeenperiod
+ {0xa64723d6, 0x30af}, // kukatakana
+ {0xa6522894, 0x24da}, // kcircle
+ {0xa652cff2, 0x24a8}, // mparen
+ {0xa65ca284, 0x314d}, // phieuphkorean
+ {0xa65ea7b9, 0x00ab}, // guillemotleft
+ {0xa6644796, 0xfc94}, // yehnoonfinalarabic
+ {0xa66b3ab3, 0x00b8}, // cedilla
+ {0xa675e0d6, 0x0156}, // Rcommaaccent
+ {0xa683217b, 0x0ab7}, // ssagujarati
+ {0xa6a017fd, 0x0415}, // Iecyrillic
+ {0xa6ae34a8, 0x3028}, // eighthangzhou
+ {0xa6b19efc, 0x0591}, // etnahtafoukhhebrew
+ {0xa6b46028, 0xff72}, // ikatakanahalfwidth
+ {0xa6bd2b95, 0x1e6b}, // tdotaccent
+ {0xa6c10839, 0x0431}, // becyrillic
+ {0xa6e68e9f, 0x0966}, // zerodeva
+ {0xa6f16c03, 0x03c2}, // sigma1
+ {0xa6f2df0d, 0x0a32}, // lagurmukhi
+ {0xa716a470, 0x2464}, // fivecircle
+ {0xa71dfe13, 0x1ea2}, // Ahookabove
+ {0xa73026ce, 0x007a}, // z
+ {0xa73199c0, 0x05bb}, // qubutshebrew
+ {0xa731e944, 0x0411}, // afii10018
+ {0xa7320cb3, 0x3139}, // rieulkorean
+ {0xa74014fc, 0x003f}, // question
+ {0xa745be27, 0x2308}, // ceilingleftbig
+ {0xa74cd67a, 0x025f}, // jdotlessstroke
+ {0xa756caf5, 0x04d2}, // Adieresiscyrillic
+ {0xa7769b7a, 0x22c1}, // logicalortext
+ {0xa7895d88, 0x252c}, // SF060000
+ {0xa796d5bb, 0x0267}, // henghook
+ {0xa7bff3d5, 0x032c}, // caronbelowcmb
+ {0xa7dcd836, 0xfb68}, // ttehinitialarabic
+ {0xa7e01c26, 0x01a7}, // Tonetwo
+ {0xa7e7d702, 0x05b8}, // qamats1a
+ {0xa7edca33, 0x25b7}, // whiterightpointingtriangle
+ {0xa7fb6ee6, 0x226f}, // notgreater
+ {0xa7fe97d8, 0x3166}, // nieuntikeutkorean
+ {0xa815fa8a, 0x309e}, // voicediterationhiragana
+ {0xa819fe3e, 0x315e}, // wekorean
+ {0xa81ee743, 0x0542}, // Ghadarmenian
+ {0xa826b713, 0xfeb6}, // sheenfinalarabic
+ {0xa842618b, 0x025b}, // eopen
+ {0xa84fdde5, 0x0340}, // gravetonecmb
+ {0xa851c76f, 0x05a5}, // merkhahebrew
+ {0xa8547bdd, 0x0455}, // afii10102
+ {0xa8665e8d, 0x05b1}, // afii57801
+ {0xa877e561, 0x2642}, // male
+ {0xa8982f3b, 0x09b2}, // labengali
+ {0xa8a24959, 0x00d0}, // Eth
+ {0xa8b6c7f5, 0x0a2e}, // magurmukhi
+ {0xa8b7f35e, 0x06c1}, // haaltonearabic
+ {0xa8bb13d5, 0x0a70}, // tippigurmukhi
+ {0xa8ccc65c, 0x3316}, // kiromeetorusquare
+ {0xa8db2b93, 0x017e}, // zcaron
+ {0xa93204a5, 0x03a6}, // Phi
+ {0xa9350b3f, 0x20a1}, // colonmonetary
+ {0xa93a2a4f, 0x1e47}, // ndotbelow
+ {0xa94e0303, 0x33ab}, // mpasquare
+ {0xa94eeaee, 0x01aa}, // eshreversedloop
+ {0xa95e2711, 0x1e21}, // gmacron
+ {0xa96cef91, 0x3169}, // rieulkiyeoksioskorean
+ {0xa981562d, 0x0668}, // eightarabic
+ {0xa98e771c, 0x0634}, // sheenarabic
+ {0xa9985803, 0x30dc}, // bokatakana
+ {0xa99c94c2, 0x01be}, // glottalinvertedstroke
+ {0xa9a0932f, 0x2077}, // sevensuperior
+ {0xa9af18f2, 0x30b4}, // gokatakana
+ {0xa9c0b182, 0x2264}, // lessequal
+ {0xaa13efde, 0x007d}, // braceright
+ {0xaa4e278b, 0x0967}, // onedeva
+ {0xaa51d75f, 0x2476}, // threeparen
+ {0xaa69d0f1, 0x256c}, // SF440000
+ {0xaa863ce3, 0x0629}, // tehmarbutaarabic
+ {0xaa8c5eeb, 0x03e5}, // feicoptic
+ {0xaa96b9dc, 0x0665}, // fivearabic
+ {0xaaabcf5c, 0x04b9}, // cheverticalstrokecyrillic
+ {0xaab6b9a5, 0x0211}, // rdblgrave
+ {0xaabfed05, 0xff85}, // nakatakanahalfwidth
+ {0xaac3a76a, 0x0559}, // ringhalfleftarmenian
+ {0xaac3adf0, 0x3121}, // oubopomofo
+ {0xaacaffc4, 0x2162}, // Threeroman
+ {0xaad54f7c, 0x3002}, // ideographicperiod
+ {0xaaf6eb21, 0x01ce}, // acaron
+ {0xab0b499a, 0x099c}, // jabengali
+ {0xab126f69, 0x5344}, // twentyhangzhou
+ {0xab1f1bb7, 0x05df}, // afii57679
+ {0xab24577f, 0x0667}, // sevenarabic
+ {0xab2b4200, 0x2105}, // careof
+ {0xab3b4b27, 0x24e2}, // scircle
+ {0xab808d1e, 0x2087}, // seveninferior
+ {0xab8a6656, 0x01d0}, // icaron
+ {0xaba55a59, 0xfb32}, // gimeldageshhebrew
+ {0xabb7eb8f, 0x2081}, // oneinferior
+ {0xabd373e8, 0x055e}, // questionarmenian
+ {0xac05d874, 0x1e84}, // Wdieresis
+ {0xac259f58, 0x0448}, // afii10090
+ {0xac2c323e, 0x0191}, // Fhook
+ {0xac32a034, 0x316f}, // mieumsioskorean
+ {0xac483cb3, 0x338c}, // mufsquare
+ {0xac4f1094, 0x059c}, // gereshaccenthebrew
+ {0xac50a082, 0x33a3}, // mmcubedsquare
+ {0xac5faca7, 0x230b}, // floorrightbig
+ {0xac67aca2, 0x045b}, // tshecyrillic
+ {0xacb92bab, 0x0691}, // rreharabic
+ {0xacd00f05, 0x21c6}, // arrowleftoverright
+ {0xacd11e18, 0x2010}, // hyphentwo
+ {0xace7d07a, 0x0921}, // ddadeva
+ {0xacfcbdb9, 0x3042}, // ahiragana
+ {0xad01f787, 0xff1d}, // equalmonospace
+ {0xad1b58f9, 0x0595}, // zaqefgadolhebrew
+ {0xad29738f, 0x05f4}, // gershayimhebrew
+ {0xad33f4b3, 0x04c1}, // Zhebrevecyrillic
+ {0xad37f8e0, 0x0a24}, // tagurmukhi
+ {0xad38bc31, 0x2194}, // arrowboth
+ {0xad5fe438, 0x0924}, // tadeva
+ {0xad781e89, 0x200c}, // afii61664
+ {0xad8ff38a, 0x0a21}, // ddagurmukhi
+ {0xadb1b19e, 0x0206}, // Einvertedbreve
+ {0xadb53f6b, 0xfbaf}, // yehbarreefinalarabic
+ {0xadbea3e4, 0x20aa}, // afii57636
+ {0xadc3ff5e, 0x0649}, // afii57449
+ {0xadf402a9, 0x0989}, // ubengali
+ {0xadf4d422, 0x0625}, // alefhamzabelowarabic
+ {0xae04e5ad, 0x0a15}, // kagurmukhi
+ {0xae1f7b0a, 0x2076}, // sixsuperior
+ {0xae23dd7b, 0x1eaf}, // abreveacute
+ {0xae30147f, 0x0908}, // iideva
+ {0xae56317c, 0x24ae}, // sparen
+ {0xae6f7e74, 0x031b}, // horncmb
+ {0xae7c975f, 0x249a}, // nineteenperiod
+ {0xae8f4e4c, 0x0406}, // afii10055
+ {0xaeb06274, 0x0423}, // Ucyrillic
+ {0xaec173e8, 0x03b8}, // theta
+ {0xaee16fb6, 0xfe4f}, // underscorewavy
+ {0xaef4b475, 0x1edf}, // ohornhookabove
+ {0xaef8393d, 0x0664}, // fourarabic
+ {0xaf01f370, 0x278e}, // fivecircleinversesansserif
+ {0xaf2073fd, 0x2103}, // centigrade
+ {0xaf3552ce, 0x3072}, // hihiragana
+ {0xaf36c6b1, 0x30cd}, // nekatakana
+ {0xaf4df0df, 0xfc0e}, // tehmeemisolatedarabic
+ {0xaf5710c9, 0x00c0}, // Agrave
+ {0xaf5b123d, 0x032d}, // circumflexbelowcmb
+ {0xaf5ce08f, 0x00e0}, // agrave
+ {0xaf788850, 0x00fc}, // udieresis
+ {0xaf7abcb1, 0x05b9}, // holam
+ {0xaf8a8524, 0x0455}, // dzecyrillic
+ {0xafa14924, 0x33d3}, // lxsquare
+ {0xafaa365c, 0x30be}, // zokatakana
+ {0xafb28009, 0x03e2}, // Sheicoptic
+ {0xafb8e89a, 0x0a68}, // twogurmukhi
+ {0xafbbfcac, 0xff71}, // akatakanahalfwidth
+ {0xafbd0738, 0x305d}, // sohiragana
+ {0xafc9b657, 0x0e17}, // thothahanthai
+ {0xb000150a, 0xfe96}, // tehfinalarabic
+ {0xb01f8020, 0x0666}, // sixarabic
+ {0xb026a3ef, 0x30b3}, // kokatakana
+ {0xb0309f24, 0xfee3}, // meeminitialarabic
+ {0xb032be97, 0x047b}, // omegaroundcyrillic
+ {0xb033a837, 0x0079}, // y
+ {0xb03640f2, 0x2252}, // approxequalorimage
+ {0xb0522c01, 0x278f}, // sixcircleinversesansserif
+ {0xb0791aaf, 0x09f3}, // rupeesignbengali
+ {0xb0a20aff, 0x22db}, // greaterequalorless
+ {0xb0c33454, 0x028a}, // upsilonlatin
+ {0xb0efaba6, 0x0a97}, // gagujarati
+ {0xb107bdf6, 0x304b}, // kahiragana
+ {0xb1240d86, 0x0474}, // afii10148
+ {0xb145d406, 0x327b}, // hieuhacirclekorean
+ {0xb169c9ac, 0x1eed}, // uhornhookabove
+ {0xb1722e49, 0x3006}, // ideographicclose
+ {0xb199f9f3, 0x314a}, // chieuchkorean
+ {0xb1a83745, 0x246e}, // fifteencircle
+ {0xb1a9eaa4, 0x1eb7}, // abrevedotbelow
+ {0xb1b08c26, 0x2020}, // dagger
+ {0xb1b2e578, 0xff13}, // threemonospace
+ {0xb1c3eac2, 0x1e8c}, // Xdieresis
+ {0xb1e18633, 0x0e2d}, // oangthai
+ {0xb1eda93c, 0x09e2}, // lvocalicvowelsignbengali
+ {0xb1fa6226, 0x0a9a}, // cagujarati
+ {0xb225a8bc, 0x0947}, // evowelsigndeva
+ {0xb243894e, 0x21d1}, // arrowdblup
+ {0xb25639c1, 0x2534}, // SF070000
+ {0xb2566e08, 0x064d}, // afii57453
+ {0xb256786e, 0x03f2}, // sigmalunatesymbolgreek
+ {0xb25b34dc, 0x0e1a}, // bobaimaithai
+ {0xb26943db, 0x0420}, // afii10034
+ {0xb27e91f4, 0x01bb}, // twostroke
+ {0xb290d64a, 0xfe88}, // alefhamzabelowfinalarabic
+ {0xb2bbd0d4, 0xfeb0}, // zainfinalarabic
+ {0xb2cc02c2, 0x1e38}, // Ldotbelowmacron
+ {0xb2d79f3e, 0x00f9}, // ugrave
+ {0xb2d7f27b, 0x0652}, // afii57458
+ {0xb2d95c63, 0x2518}, // SF040000
+ {0xb2f3aff0, 0x0946}, // eshortvowelsigndeva
+ {0xb3178333, 0x00da}, // Uacute
+ {0xb321fe9c, 0x21e3}, // arrowdashdown
+ {0xb3329e90, 0x313e}, // rieulthieuthkorean
+ {0xb33c41bc, 0x018c}, // dtopbar
+ {0xb340e2fe, 0x04a5}, // enghecyrillic
+ {0xb341da2f, 0x1e36}, // Ldotbelow
+ {0xb345c512, 0xfea4}, // hahmedialarabic
+ {0xb369c9bd, 0x2423}, // blank
+ {0xb36f3f4e, 0x054d}, // Seharmenian
+ {0xb3880287, 0x05d6}, // zayinhebrew
+ {0xb38b59bc, 0x0ac0}, // iivowelsigngujarati
+ {0xb3a3592e, 0x21e5}, // arrowtabright
+ {0xb3dbcf55, 0x323d}, // ideographicenterpriseparen
+ {0xb3e6b497, 0x03c9}, // omega
+ {0xb3ed41be, 0x328f}, // ideographearthcircle
+ {0xb40169ac, 0x05b0}, // sheva22
+ {0xb405e3e3, 0x0439}, // afii10075
+ {0xb41baecc, 0x2487}, // twentyparen
+ {0xb427632e, 0x05b3}, // hatafqamats1b
+ {0xb4344c30, 0x090f}, // edeva
+ {0xb43bb55a, 0x3263}, // rieulcirclekorean
+ {0xb448d464, 0x0391}, // Alpha
+ {0xb45a5763, 0x201b}, // quoteleftreversed
+ {0xb45ef9b7, 0x03c0}, // pi
+ {0xb497903a, 0x01dd}, // eturned
+ {0xb4a55071, 0x1e7d}, // vtilde
+ {0xb4a6b289, 0x2235}, // because
+ {0xb4a7f99d, 0x0954}, // acutedeva
+ {0xb4a9d27d, 0x05d2}, // afii57666
+ {0xb4c0dc86, 0x0e48}, // maiekthai
+ {0xb4c2484c, 0x05dc}, // lamed
+ {0xb4c667bc, 0xff61}, // periodhalfwidth
+ {0xb4c72b2a, 0x0215}, // udblgrave
+ {0xb4e21f31, 0x2198}, // arrowdownright
+ {0xb500deca, 0x1e41}, // mdotaccent
+ {0xb510d684, 0x1e80}, // Wgrave
+ {0xb526b685, 0x22bf}, // righttriangle
+ {0xb52e7c1d, 0x091e}, // nyadeva
+ {0xb53ca7e2, 0x326c}, // phieuphcirclekorean
+ {0xb58e59d7, 0x24ad}, // rparen
+ {0xb5af274f, 0x064a}, // afii57450
+ {0xb5b94593, 0x010d}, // ccaron
+ {0xb5c458a3, 0x05b8}, // qamatsqatanwidehebrew
+ {0xb5f24e31, 0xff5a}, // zmonospace
+ {0xb600bed1, 0x3024}, // fourhangzhou
+ {0xb6052cdb, 0x010b}, // cdotaccent
+ {0xb61c54b4, 0x24e4}, // ucircle
+ {0xb64312f2, 0x00a2}, // cent
+ {0xb6443d26, 0x0622}, // afii57410
+ {0xb647ed9e, 0x01d7}, // Udieresisacute
+ {0xb652184e, 0x0e4b}, // maichattawathai
+ {0xb6588f1c, 0x247c}, // nineparen
+ {0xb66bf9b9, 0x33dc}, // svsquare
+ {0xb673fbb5, 0x038e}, // Upsilontonos
+ {0xb67e35c8, 0x0121}, // gdotaccent
+ {0xb6bb2a6b, 0x0116}, // Edot
+ {0xb6d45c54, 0xff7a}, // kokatakanahalfwidth
+ {0xb6f322b3, 0x05e5}, // afii57685
+ {0xb6f9554e, 0x3205}, // pieupparenkorean
+ {0xb6f9c67c, 0x0464}, // Eiotifiedcyrillic
+ {0xb6fea9e7, 0xff3f}, // underscoremonospace
+ {0xb70f3f60, 0xff4e}, // nmonospace
+ {0xb711b601, 0x039d}, // Nu
+ {0xb719922a, 0x067e}, // afii57506
+ {0xb71d84e2, 0x3167}, // nieunsioskorean
+ {0xb726c42e, 0xfb49}, // shindageshhebrew
+ {0xb72e5846, 0x1edb}, // ohornacute
+ {0xb73606f5, 0x0463}, // afii10194
+ {0xb7400632, 0x33aa}, // kpasquare
+ {0xb747ebc8, 0x2310}, // revlogicalnot
+ {0xb755a24e, 0x310e}, // kbopomofo
+ {0xb7934eea, 0x01c5}, // Dzcaron
+ {0xb7affc1f, 0x0418}, // afii10026
+ {0xb7b78fdd, 0x2463}, // fourcircle
+ {0xb7bd89d3, 0x0491}, // gheupturncyrillic
+ {0xb7d20c6c, 0x215c}, // threeeighths
+ {0xb7e9bb2b, 0x30fe}, // voicediterationkatakana
+ {0xb80991a9, 0x04d6}, // Iebrevecyrillic
+ {0xb81d8e8c, 0xfb31}, // betdagesh
+ {0xb855cda8, 0x3397}, // dlsquare
+ {0xb85a6427, 0x0650}, // kasraarabic
+ {0xb8632720, 0x0e07}, // ngonguthai
+ {0xb879d78f, 0xfe32}, // endashvertical
+ {0xb894f4d6, 0x01a0}, // Ohorn
+ {0xb8972176, 0x04bb}, // shhacyrillic
+ {0xb89948ac, 0x0a93}, // ogujarati
+ {0xb8be7e03, 0x2039}, // guilsinglleft
+ {0xb8fa96e6, 0x0110}, // Dslash
+ {0xb8ff412c, 0xfef8}, // lamalefhamzaabovefinalarabic
+ {0xb902d285, 0x053a}, // Zhearmenian
+ {0xb90dcf8c, 0xff6a}, // esmallkatakanahalfwidth
+ {0xb910864a, 0x2032}, // minute
+ {0xb921c241, 0xfeca}, // ainfinalarabic
+ {0xb92ccc5d, 0x2469}, // tencircle
+ {0xb9305b2b, 0x3174}, // pieupsioskiyeokkorean
+ {0xb96268cb, 0x00de}, // Thorn
+ {0xb9808b18, 0x09c1}, // uvowelsignbengali
+ {0xb9927e88, 0x3382}, // muasquare
+ {0xb99f8f9e, 0xfeeb}, // hehinitialarabic
+ {0xb9b2e314, 0x0498}, // Zedescendercyrillic
+ {0xb9b4563d, 0x24b3}, // xparen
+ {0xb9c5eece, 0x05bc}, // dageshhebrew
+ {0xb9d8b5d9, 0xfba4}, // hehhamzaaboveisolatedarabic
+ {0xb9dbedd2, 0x317b}, // siosnieunkorean
+ {0xb9e5ea71, 0x314c}, // thieuthkorean
+ {0xb9e8b13e, 0x25a0}, // filledbox
+ {0xb9f42560, 0x0964}, // danda
+ {0xb9f5b462, 0x0990}, // aibengali
+ {0xba1bcecd, 0x0176}, // Ycircumflex
+ {0xba1f80d6, 0x21e1}, // arrowdashup
+ {0xba21ad27, 0xfb3a}, // finalkafdagesh
+ {0xba4eb5f9, 0x0055}, // U
+ {0xba544632, 0x05db}, // afii57675
+ {0xba5871eb, 0x033e}, // tildeverticalcmb
+ {0xba60a3b8, 0x0902}, // anusvaradeva
+ {0xba7e1049, 0x01d9}, // Udieresiscaron
+ {0xba8d69d2, 0x232a}, // angleright
+ {0xbaa24d97, 0x05e0}, // afii57680
+ {0xbaa7aa4c, 0xfedc}, // kafmedialarabic
+ {0xbac7de75, 0x1ed3}, // ocircumflexgrave
+ {0xbad44ddc, 0x004f}, // O
+ {0xbad7d685, 0x04b8}, // Cheverticalstrokecyrillic
+ {0xbad97612, 0x03af}, // iotatonos
+ {0xbadf80e2, 0x06f5}, // fivepersian
+ {0xbafeb301, 0x0422}, // Tecyrillic
+ {0xbb28e2ea, 0x1eb2}, // Abrevehookabove
+ {0xbb30f37e, 0x0218}, // Scommaaccent
+ {0xbb6353b2, 0x0332}, // lowlinecmb
+ {0xbb72d76e, 0x0139}, // Lacute
+ {0xbb906e01, 0x3266}, // sioscirclekorean
+ {0xbb970588, 0x266f}, // musicsharpsign
+ {0xbba252f7, 0x0106}, // Cacute
+ {0xbbae7b40, 0x3172}, // pieupkiyeokkorean
+ {0xbbb9d0f4, 0x0934}, // llladeva
+ {0xbbd8677b, 0x330d}, // karoriisquare
+ {0xbbdbcb55, 0x0a6f}, // ninegurmukhi
+ {0xbbdc86be, 0xfb2b}, // shinsindot
+ {0xbc0d2781, 0x0a1f}, // ttagurmukhi
+ {0xbc1b1166, 0x2558}, // SF500000
+ {0xbc280da2, 0x1e79}, // utildeacute
+ {0xbc3510eb, 0x317f}, // pansioskorean
+ {0xbc45cf9a, 0x02a4}, // dezh
+ {0xbc75336c, 0x21a9}, // arrowhookright
+ {0xbc78e14f, 0x24b0}, // uparen
+ {0xbcd2c61b, 0x064c}, // dammatanarabic
+ {0xbd0f6f71, 0x004c}, // L
+ {0xbd1a9441, 0x3235}, // ideographicspecialparen
+ {0xbd1abdb6, 0x0184}, // Tonesix
+ {0xbd2d2e5f, 0x054e}, // Vewarmenian
+ {0xbd30ce0c, 0x2641}, // earth
+ {0xbd4d0860, 0x320a}, // khieukhparenkorean
+ {0xbd54bd2d, 0x3208}, // cieucparenkorean
+ {0xbd569183, 0x0182}, // Btopbar
+ {0xbd5dbcc1, 0x0151}, // odblacute
+ {0xbd5ee257, 0x01b7}, // Ezh
+ {0xbd85b57b, 0x21a8}, // arrowupdownbase
+ {0xbd8f8d24, 0x064b}, // fathatanarabic
+ {0xbd906fab, 0x041f}, // Pecyrillic
+ {0xbd98b80f, 0x25c8}, // whitediamondcontainingblacksmalldiamond
+ {0xbdc1280a, 0x24be}, // Icircle
+ {0xbdd372da, 0x318b}, // yuyekorean
+ {0xbde8281d, 0x3276}, // cieucacirclekorean
+ {0xbe3e45cf, 0xfb58}, // pehinitialarabic
+ {0xbe663ca6, 0x2470}, // seventeencircle
+ {0xbe7a58ae, 0x3203}, // rieulparenkorean
+ {0xbea937fd, 0x25bc}, // blackdownpointingtriangle
+ {0xbec6916e, 0x011c}, // Gcircumflex
+ {0xbedd6640, 0x00d9}, // Ugrave
+ {0xbf12496a, 0x0985}, // abengali
+ {0xbf156070, 0x04e2}, // Imacroncyrillic
+ {0xbf26dc61, 0x041d}, // Encyrillic
+ {0xbf2dca30, 0x3082}, // mohiragana
+ {0xbf3cff90, 0x0425}, // afii10039
+ {0xbf5cef43, 0x01ee}, // Ezhcaron
+ {0xbf673175, 0x01fd}, // aeacute
+ {0xbf87a284, 0x00bf}, // questiondown
+ {0xbf897387, 0x2273}, // greaterorequivalent
+ {0xbf8f3598, 0x25a6}, // squareorthogonalcrosshatchfill
+ {0xbf934ed3, 0x04e0}, // Dzeabkhasiancyrillic
+ {0xbf97194e, 0x010a}, // Cdot
+ {0xbf9bc7bd, 0xfb59}, // pehmedialarabic
+ {0xbf9c1926, 0x0627}, // alefarabic
+ {0xbfc69ab7, 0xff18}, // eightmonospace
+ {0xbfd3ede4, 0x0396}, // Zeta
+ {0xbffa52a3, 0x33c7}, // cosquare
+ {0xc006a810, 0x062a}, // teharabic
+ {0xc008508a, 0xff1c}, // lessmonospace
+ {0xc00a3b07, 0x24bc}, // Gcircle
+ {0xc0126352, 0x0661}, // onearabic
+ {0xc03e102c, 0x3012}, // postalmark
+ {0xc059a094, 0x0626}, // yehhamzaabovearabic
+ {0xc0668ba8, 0x202e}, // afii61575
+ {0xc07e7e42, 0x24a6}, // kparen
+ {0xc084bd84, 0x1e29}, // hcedilla
+ {0xc092fb91, 0x1e95}, // zlinebelow
+ {0xc09823db, 0x016f}, // uring
+ {0xc09889a1, 0x1e11}, // dcedilla
+ {0xc0a2bc69, 0x2564}, // SF470000
+ {0xc0a93f4f, 0x0e2c}, // lochulathai
+ {0xc0bd9f90, 0x094a}, // oshortvowelsigndeva
+ {0xc0c043bd, 0x3008}, // anglebracketleft
+ {0xc0c1496c, 0x029e}, // kturned
+ {0xc0cbe66a, 0x33b7}, // mvsquare
+ {0xc0d444a4, 0x3220}, // oneideographicparen
+ {0xc0dcb90f, 0x062e}, // khaharabic
+ {0xc0efe98c, 0x2191}, // arrowup
+ {0xc0fb3832, 0x25ab}, // H18551
+ {0xc131664b, 0x30e4}, // yakatakana
+ {0xc1425417, 0x317a}, // sioskiyeokkorean
+ {0xc1641f79, 0xfe4d}, // lowlinedashed
+ {0xc1835ca2, 0x02d3}, // ringhalfleftcentered
+ {0xc191ae65, 0x0470}, // Psicyrillic
+ {0xc1aa3399, 0x2a06}, // unionsqtext
+ {0xc1c1cd87, 0xff2e}, // Nmonospace
+ {0xc1d325d6, 0x05d8}, // tet
+ {0xc1e02cdd, 0x0594}, // zaqefqatanhebrew
+ {0xc1e3fd2d, 0x05d5}, // vav
+ {0xc1e4c743, 0x3057}, // sihiragana
+ {0xc1ec1451, 0x03c3}, // sigma
+ {0xc1f9ede7, 0x05a3}, // munahhebrew
+ {0xc2089a04, 0x05b7}, // patah1d
+ {0xc21675c5, 0x0317}, // acutebelowcmb
+ {0xc21e92b6, 0x2265}, // greaterequal
+ {0xc2352e98, 0x0554}, // Keharmenian
+ {0xc25217c2, 0xfeff}, // zerowidthjoiner
+ {0xc25a58a0, 0x0a28}, // nagurmukhi
+ {0xc25a7d39, 0x2663}, // club
+ {0xc2658d7c, 0x3395}, // mulsquare
+ {0xc26d6fce, 0x20a9}, // won
+ {0xc283f2c3, 0x03bc}, // mugreek
+ {0xc29c269a, 0x326d}, // hieuhcirclekorean
+ {0xc2c391b3, 0x25b2}, // triagup
+ {0xc2d0d165, 0x0137}, // kcommaaccent
+ {0xc2db7e0f, 0x04a0}, // Kabashkircyrillic
+ {0xc327f9ff, 0x239b}, // parenlefttp
+ {0xc32ea721, 0x061f}, // afii57407
+ {0xc36634d0, 0x09f9}, // sixteencurrencydenominatorbengali
+ {0xc3722d19, 0x23ad}, // bracerightbt
+ {0xc3751bae, 0x25ab}, // whitesmallsquare
+ {0xc375a046, 0x05b0}, // shevahebrew
+ {0xc391e9a8, 0xfe50}, // commasmall
+ {0xc3c94bba, 0x306a}, // nahiragana
+ {0xc3d9dd70, 0xfe43}, // whitecornerbracketleftvertical
+ {0xc3df4586, 0x0903}, // visargadeva
+ {0xc3eb9abd, 0x02c6}, // circumflex
+ {0xc3edb597, 0x24bb}, // Fcircle
+ {0xc3f1c1ff, 0x00e3}, // atilde
+ {0xc3f76044, 0xff7c}, // sikatakanahalfwidth
+ {0xc3f8f5bb, 0x04e3}, // imacroncyrillic
+ {0xc3fbb2ce, 0x025e}, // eopenreversedclosed
+ {0xc4004762, 0x328a}, // ideographmooncircle
+ {0xc4289b41, 0x25a5}, // squareverticalfill
+ {0xc43e9d0c, 0x1e26}, // Hdieresis
+ {0xc4494fb1, 0x098b}, // rvocalicbengali
+ {0xc459127a, 0x2174}, // fiveroman
+ {0xc460226d, 0xfb34}, // hedagesh
+ {0xc4607843, 0x25ca}, // lozenge
+ {0xc4991f6f, 0x03d0}, // betasymbolgreek
+ {0xc49bedba, 0x1e22}, // Hdotaccent
+ {0xc4bd396f, 0xff98}, // rikatakanahalfwidth
+ {0xc4cf0400, 0x0ac3}, // rvocalicvowelsigngujarati
+ {0xc4d48fb7, 0x0a06}, // aagurmukhi
+ {0xc510ccfb, 0x0327}, // cedillacmb
+ {0xc51e03d1, 0x0e2a}, // sosuathai
+ {0xc5233a99, 0xff41}, // amonospace
+ {0xc52d9608, 0x0409}, // afii10058
+ {0xc541abe3, 0x0409}, // Ljecyrillic
+ {0xc560e83e, 0x1e4c}, // Otildeacute
+ {0xc575fbd8, 0x3303}, // aarusquare
+ {0xc577ce75, 0xff5d}, // bracerightmonospace
+ {0xc579feb1, 0x03bf}, // omicron
+ {0xc5904240, 0x1ee9}, // uhornacute
+ {0xc594a84a, 0x0a85}, // agujarati
+ {0xc5a57fbd, 0x0178}, // Ydieresis
+ {0xc5b52809, 0x25ac}, // filledrect
+ {0xc5c558c0, 0x2593}, // dkshade
+ {0xc5df0b88, 0x0aae}, // magujarati
+ {0xc5e2935b, 0x30bf}, // takatakana
+ {0xc616ce34, 0x1e83}, // wacute
+ {0xc618c356, 0x005f}, // underscore
+ {0xc6231f67, 0x3117}, // zbopomofo
+ {0xc62d494f, 0x042b}, // afii10045
+ {0xc65b9029, 0x0012}, // controlDC2
+ {0xc65e4473, 0x0300}, // gravecomb
+ {0xc6649ea6, 0x263b}, // blacksmilingface
+ {0xc6730a47, 0x3230}, // ideographicsunparen
+ {0xc6a18663, 0x2173}, // fourroman
+ {0xc6a33bcf, 0x23a4}, // bracketrighttp
+ {0xc6bb9cde, 0x03c1}, // rho
+ {0xc6c2ca67, 0x0537}, // Eharmenian
+ {0xc6db1db1, 0x0a94}, // augujarati
+ {0xc6ddc5a6, 0x0334}, // tildeoverlaycmb
+ {0xc6e9fa39, 0x064a}, // yeharabic
+ {0xc6fbfdac, 0x025a}, // schwahook
+ {0xc703d8e7, 0x33b8}, // kvsquare
+ {0xc71e49b3, 0x23a5}, // bracketrightex
+ {0xc74688aa, 0x24dc}, // mcircle
+ {0xc74a8cc7, 0x05b5}, // tserequarterhebrew
+ {0xc74f4c99, 0x0298}, // bilabialclick
+ {0xc7515b37, 0x0132}, // IJ
+ {0xc755c80f, 0xff67}, // asmallkatakanahalfwidth
+ {0xc75bc6a7, 0x01e8}, // Kcaron
+ {0xc7618f62, 0x05b3}, // hatafqamats28
+ {0xc76816e4, 0x0566}, // zaarmenian
+ {0xc76c439e, 0x0115}, // ebreve
+ {0xc76ec8b2, 0x0e1c}, // phophungthai
+ {0xc77c2828, 0x2206}, // Delta
+ {0xc786ef31, 0x095b}, // zadeva
+ {0xc78887c7, 0x1e08}, // Ccedillaacute
+ {0xc78fe323, 0x0698}, // afii57508
+ {0xc798936b, 0xfc73}, // tehnoonfinalarabic
+ {0xc7b3573a, 0x1e2f}, // idieresisacute
+ {0xc7daee30, 0x09b7}, // ssabengali
+ {0xc7e20869, 0x0aee}, // eightgujarati
+ {0xc7ed01af, 0x33a5}, // mcubedsquare
+ {0xc7f07bcf, 0x248f}, // eightperiod
+ {0xc7f0b4c8, 0x0406}, // Icyrillic
+ {0xc7fb5fe5, 0x05b2}, // afii57800
+ {0xc812d1b7, 0x041b}, // afii10029
+ {0xc82c0a32, 0x026f}, // mturned
+ {0xc834804d, 0x2475}, // twoparen
+ {0xc8488aa4, 0x05b0}, // shevawidehebrew
+ {0xc84db89c, 0x2013}, // endash
+ {0xc85fce80, 0x0901}, // candrabindudeva
+ {0xc86d3a57, 0xff31}, // Qmonospace
+ {0xc875e6fd, 0x040c}, // afii10061
+ {0xc88f7d57, 0x05b3}, // hatafqamatsnarrowhebrew
+ {0xc89d4f1f, 0x0006}, // controlACK
+ {0xc8ae6fb2, 0x0e26}, // luthai
+ {0xc8b50d48, 0x21d4}, // dblarrowleft
+ {0xc8c2c42c, 0x0162}, // Tcedilla
+ {0xc8c687a4, 0xfb4b}, // vavholamhebrew
+ {0xc8cf912e, 0xfe59}, // parenleftsmall
+ {0xc8fcce82, 0x0424}, // Efcyrillic
+ {0xc8fdfe4b, 0x0914}, // audeva
+ {0xc9315b90, 0x0338}, // soliduslongoverlaycmb
+ {0xc93304ec, 0x2176}, // sevenroman
+ {0xc9366418, 0x1e7e}, // Vdotbelow
+ {0xc948d9f2, 0x01fe}, // Oslashacute
+ {0xc948dd49, 0x0417}, // Zecyrillic
+ {0xc94ac55a, 0xfb4e}, // perafehebrew
+ {0xc94ceb7c, 0x1eca}, // Idotbelow
+ {0xc956ff7e, 0x1e7b}, // umacrondieresis
+ {0xc997284d, 0x03d3}, // Upsilonacutehooksymbolgreek
+ {0xc997d626, 0xfe3d}, // dblanglebracketleftvertical
+ {0xc9d02325, 0x2283}, // propersuperset
+ {0xc9d7e9b6, 0x064c}, // afii57452
+ {0xc9d96803, 0x3278}, // khieukhacirclekorean
+ {0xc9e8cf5f, 0xfebf}, // dadinitialarabic
+ {0xc9ea8b89, 0x305b}, // sehiragana
+ {0xc9eb1227, 0x321c}, // cieucuparenkorean
+ {0xca07e9ca, 0x02dd}, // hungarumlaut
+ {0xca0b3331, 0x1e5f}, // rlinebelow
+ {0xca2dd9fa, 0x2590}, // rtblock
+ {0xca5ed753, 0x0269}, // iotalatin
+ {0xca65e972, 0x0646}, // noonarabic
+ {0xcab40374, 0x1e1f}, // fdotaccent
+ {0xcabe62a6, 0x0e16}, // thothungthai
+ {0xcac3bcb6, 0x33a8}, // moverssquaredsquare
+ {0xcac4b1a9, 0x32a8}, // ideographicrightcircle
+ {0xcad1f345, 0x01bd}, // tonefive
+ {0xcae44cee, 0x0013}, // controlDC3
+ {0xcaf66d1c, 0x33b4}, // pvsquare
+ {0xcb1a38a0, 0x226b}, // muchgreater
+ {0xcb1b6c03, 0x0a5e}, // fagurmukhi
+ {0xcb1e3324, 0x042d}, // afii10047
+ {0xcb267db3, 0x3221}, // twoideographicparen
+ {0xcb281438, 0x33ae}, // radoverssquare
+ {0xcb2edf2d, 0x220f}, // product
+ {0xcb4fc444, 0x03f0}, // kappasymbolgreek
+ {0xcb56efb7, 0x015c}, // Scircumflex
+ {0xcb5b537a, 0x0412}, // afii10019
+ {0xcb669b9c, 0x1ee2}, // Ohorndotbelow
+ {0xcb72660a, 0xff1e}, // greatermonospace
+ {0xcb75245d, 0x1e48}, // Nlinebelow
+ {0xcb88e590, 0x013c}, // lcedilla
+ {0xcb8bc7f1, 0x002b}, // plus
+ {0xcbada403, 0x0429}, // Shchacyrillic
+ {0xcc11d7b0, 0x000c}, // controlFF
+ {0xcc261604, 0x04a2}, // Endescendercyrillic
+ {0xcc3139de, 0x33d1}, // squareln
+ {0xcc3accb9, 0x05d8}, // tethebrew
+ {0xcc3b7b5c, 0x0649}, // alefmaksuraarabic
+ {0xcc447b1d, 0x0296}, // glottalstopinverted
+ {0xcc8c13e9, 0x25c0}, // blackleftpointingtriangle
+ {0xcc993e5c, 0x025c}, // eopenreversed
+ {0xcca8fd16, 0x09e3}, // llvocalicvowelsignbengali
+ {0xccaa74e1, 0xfb7d}, // tchehmedialarabic
+ {0xccac0ec6, 0x300b}, // dblanglebracketright
+ {0xccc389ea, 0xfb30}, // alefdageshhebrew
+ {0xccc85a27, 0x0a81}, // candrabindugujarati
+ {0xcd07b41d, 0x05bc}, // dagesh
+ {0xcd30953c, 0x203c}, // exclamdbl
+ {0xcd37d58c, 0x24e3}, // tcircle
+ {0xcd415c99, 0x0e55}, // fivethai
+ {0xcd499038, 0x33a2}, // kmsquaredsquare
+ {0xcd54eec2, 0x06f4}, // fourpersian
+ {0xcd5fb77d, 0x0185}, // tonesix
+ {0xcd64e087, 0x266b}, // musicalnotedbl
+ {0xcd75d5eb, 0x2551}, // SF240000
+ {0xcd7ce3d0, 0x24a7}, // lparen
+ {0xcd85d846, 0x1eec}, // Uhornhookabove
+ {0xcd9d27ad, 0x24c7}, // Rcircle
+ {0xcd9f5a2c, 0x0028}, // parenleft
+ {0xcda0c667, 0x2018}, // quoteleft
+ {0xcdae6ea2, 0xff59}, // ymonospace
+ {0xcdbb64f4, 0x2105}, // afii61248
+ {0xcdd2cfab, 0x24b4}, // yparen
+ {0xcdd44c7f, 0x33a4}, // cmcubedsquare
+ {0xcde56fe0, 0x05b6}, // segol
+ {0xcdea4ff0, 0x03d4}, // Upsilondieresishooksymbolgreek
+ {0xcdf0bcd8, 0x0e1e}, // phophanthai
+ {0xcdfd40ec, 0x0180}, // bstroke
+ {0xce074882, 0x0668}, // afii57400
+ {0xce4d2d1e, 0x2563}, // SF230000
+ {0xce6d06c6, 0x0aab}, // phagujarati
+ {0xce6f0d59, 0x3272}, // mieumacirclekorean
+ {0xce79a056, 0x3339}, // herutusquare
+ {0xce8dac39, 0x25a8}, // squareupperrighttolowerleftfill
+ {0xce942ad9, 0x09a6}, // dabengali
+ {0xcea39f20, 0x02d5}, // downtackmod
+ {0xcedf2a68, 0x0157}, // rcedilla
+ {0xcee9759d, 0x043a}, // afii10076
+ {0xcef01870, 0x0171}, // udblacute
+ {0xcef10e83, 0x046a}, // Yusbigcyrillic
+ {0xcef67881, 0x200c}, // zerowidthnonjoiner
+ {0xcf235874, 0x030c}, // caroncmb
+ {0xcf4c6b71, 0x02d4}, // uptackmod
+ {0xcf6550b0, 0xfb4a}, // tavdageshhebrew
+ {0xcf6a5e7c, 0x323e}, // ideographicresourceparen
+ {0xcf738908, 0x011d}, // gcircumflex
+ {0xcf848334, 0x0453}, // afii10100
+ {0xcf8cbf6b, 0x0195}, // hv
+ {0xcf9b96b5, 0x03bd}, // nu
+ {0xcfb1da5d, 0xff89}, // nokatakanahalfwidth
+ {0xcfc386a6, 0x00db}, // Ucircumflex
+ {0xcfc400d6, 0x06d2}, // yehbarreearabic
+ {0xcfd8a703, 0xfe61}, // asterisksmall
+ {0xcfe519cb, 0x309b}, // voicedmarkkana
+ {0xcfe64e44, 0x0668}, // eighthackarabic
+ {0xcff88b7d, 0x05b8}, // qamatsqatannarrowhebrew
+ {0xd0096386, 0x25b3}, // whiteuppointingtriangle
+ {0xd0227bd1, 0x0199}, // khook
+ {0xd02a9cfe, 0x0105}, // aogonek
+ {0xd031b297, 0x24e9}, // zcircle
+ {0xd03ef2b0, 0x316b}, // rieulpieupsioskorean
+ {0xd0417b9a, 0x0494}, // Ghemiddlehookcyrillic
+ {0xd044dddd, 0x0a9d}, // jhagujarati
+ {0xd04ad0f0, 0x263c}, // sun
+ {0xd04cc01e, 0x0561}, // aybarmenian
+ {0xd0511b87, 0x0e4c}, // thanthakhatthai
+ {0xd056aca3, 0xff9a}, // rekatakanahalfwidth
+ {0xd06b6bb7, 0x096a}, // fourdeva
+ {0xd07a803b, 0x09aa}, // pabengali
+ {0xd0847e20, 0x05c4}, // upperdothebrew
+ {0xd087e60f, 0x0158}, // Rcaron
+ {0xd0897bb6, 0x0579}, // chaarmenian
+ {0xd0c5df61, 0x05b4}, // hiriq14
+ {0xd0ce4edc, 0x09e9}, // threebengali
+ {0xd0d6e6c0, 0xff6c}, // yasmallkatakanahalfwidth
+ {0xd0e3648a, 0xfd3e}, // parenleftaltonearabic
+ {0xd133ff70, 0x01a1}, // ohorn
+ {0xd1373ca2, 0x0648}, // wawarabic
+ {0xd13d9bf5, 0xfe37}, // braceleftvertical
+ {0xd13faec5, 0x05e1}, // samekh
+ {0xd14fc185, 0x045b}, // afii10108
+ {0xd17987dd, 0x0543}, // Cheharmenian
+ {0xd18447d8, 0x0663}, // threearabic
+ {0xd18966b1, 0x0a82}, // anusvaragujarati
+ {0xd18d83de, 0x0aa3}, // nnagujarati
+ {0xd190d310, 0x0a35}, // vagurmukhi
+ {0xd191827c, 0x0e39}, // sarauuthai
+ {0xd1acdf44, 0xfb94}, // gafinitialarabic
+ {0xd1d7231f, 0x04bc}, // Cheabkhasiancyrillic
+ {0xd1d9da71, 0x30ea}, // rikatakana
+ {0xd2337241, 0x05b6}, // segol1f
+ {0xd247ef8b, 0x3243}, // ideographicreachparen
+ {0xd254c368, 0x0443}, // ucyrillic
+ {0xd2658bcb, 0x05b9}, // holamquarterhebrew
+ {0xd26b0e16, 0x2321}, // integralbt
+ {0xd26ef570, 0x310f}, // hbopomofo
+ {0xd293868c, 0x019b}, // lambdastroke
+ {0xd297cb79, 0x30b7}, // sikatakana
+ {0xd2b4c516, 0x30da}, // pekatakana
+ {0xd2c0e1bc, 0x0150}, // Odblacute
+ {0xd2cbfc99, 0x05b7}, // patahhebrew
+ {0xd2e2a716, 0x03ec}, // Shimacoptic
+ {0xd2eaddf5, 0x0633}, // seenarabic
+ {0xd2f253f5, 0xfe40}, // anglebracketrightvertical
+ {0xd2f3cdf3, 0x1e0e}, // Dlinebelow
+ {0xd304784a, 0x05d3}, // dalet
+ {0xd308b167, 0x1e5b}, // rdotbelow
+ {0xd310fcc1, 0x04e9}, // obarredcyrillic
+ {0xd3145153, 0xfeec}, // hehmedialarabic
+ {0xd326ec6d, 0x2237}, // proportion
+ {0xd3342503, 0x30bd}, // sokatakana
+ {0xd33cb244, 0x02cd}, // macronlowmod
+ {0xd35ba6fb, 0x04be}, // Chedescenderabkhasiancyrillic
+ {0xd3730282, 0xff64}, // ideographiccommaleft
+ {0xd3797e0f, 0x24db}, // lcircle
+ {0xd37b4bcb, 0x041c}, // afii10030
+ {0xd3a5ba29, 0x062e}, // afii57422
+ {0xd3c22da1, 0x01b5}, // Zstroke
+ {0xd3c6a66e, 0x05e4}, // pe
+ {0xd3cfef4c, 0x0427}, // afii10041
+ {0xd3e84c23, 0x201c}, // quotedblleft
+ {0xd421361f, 0x067e}, // peharabic
+ {0xd4273f62, 0x30fd}, // iterationkatakana
+ {0xd44f2d4c, 0xff9b}, // rokatakanahalfwidth
+ {0xd45c6c89, 0x3079}, // behiragana
+ {0xd467b0a3, 0x0131}, // dotlessi
+ {0xd48c064b, 0x0599}, // pashtahebrew
+ {0xd48dad4d, 0x01e2}, // AEmacron
+ {0xd4b2bb68, 0x0145}, // Ncedilla
+ {0xd4d3eb56, 0x255e}, // SF360000
+ {0xd4e86e58, 0x1ef7}, // yhookabove
+ {0xd4e92fa8, 0x0623}, // alefhamzaabovearabic
+ {0xd514a0e0, 0x318c}, // yuikorean
+ {0xd5189135, 0x1e73}, // udieresisbelow
+ {0xd52f1d8f, 0x24cb}, // Vcircle
+ {0xd54b71bd, 0x0a6e}, // eightgurmukhi
+ {0xd574af4c, 0x318e}, // araeaekorean
+ {0xd57a206a, 0x00f2}, // ograve
+ {0xd58ee561, 0x04f3}, // uhungarumlautcyrillic
+ {0xd5b6f4bf, 0x3163}, // ikorean
+ {0xd5b7a706, 0x1e13}, // dcircumflexbelow
+ {0xd5da03ab, 0x0123}, // gcommaaccent
+ {0xd5dc1f0e, 0x24a5}, // jparen
+ {0xd5e6a2b4, 0x200f}, // afii300
+ {0xd6067104, 0x05bd}, // siluqlefthebrew
+ {0xd6180af1, 0x0023}, // numbersign
+ {0xd6191adc, 0x02ce}, // gravelowmod
+ {0xd6480a61, 0x0e46}, // maiyamokthai
+ {0xd65815d1, 0x04f5}, // chedieresiscyrillic
+ {0xd6674587, 0xfea8}, // khahmedialarabic
+ {0xd66f3b98, 0x1ebd}, // etilde
+ {0xd67d357f, 0x0e23}, // roruathai
+ {0xd67dc19d, 0x1e05}, // bdotbelow
+ {0xd682be7e, 0xfe54}, // semicolonsmall
+ {0xd689f58d, 0x0024}, // dollar
+ {0xd68be98a, 0xff8f}, // makatakanahalfwidth
+ {0xd6a99b0e, 0x05aa}, // yerahbenyomolefthebrew
+ {0xd6c4c66e, 0x3262}, // tikeutcirclekorean
+ {0xd6c7e5a6, 0x03b0}, // upsilondieresistonos
+ {0xd6df6252, 0xfef4}, // yehmedialarabic
+ {0xd6e234de, 0x044d}, // afii10095
+ {0xd7151c8a, 0x040b}, // afii10060
+ {0xd71af0cf, 0x00af}, // overscore
+ {0xd72c6112, 0x0036}, // six
+ {0xd73b3901, 0x02da}, // ring
+ {0xd7425de1, 0x0a1d}, // jhagurmukhi
+ {0xd771b953, 0x3107}, // mbopomofo
+ {0xd7a40cc3, 0x315c}, // ukorean
+ {0xd7b7f8a3, 0x3094}, // vuhiragana
+ {0xd7b8c7af, 0x05b8}, // qamatsquarterhebrew
+ {0xd7bf0d2a, 0x308b}, // ruhiragana
+ {0xd7cebade, 0x01b1}, // Upsilonafrican
+ {0xd7d268b5, 0x314e}, // hieuhkorean
+ {0xd7ece605, 0x2553}, // SF520000
+ {0xd813ab1a, 0x3176}, // pieupcieuckorean
+ {0xd816387d, 0x2178}, // nineroman
+ {0xd8171429, 0x013e}, // lcaron
+ {0xd817c39d, 0x0664}, // fourhackarabic
+ {0xd824acfb, 0x05de}, // memhebrew
+ {0xd849e14d, 0x3052}, // gehiragana
+ {0xd84afb0a, 0x00d1}, // Ntilde
+ {0xd85534fc, 0x02ca}, // secondtonechinese
+ {0xd8708805, 0x01ba}, // ezhtail
+ {0xd890928b, 0x0053}, // S
+ {0xd893adf8, 0x0580}, // reharmenian
+ {0xd8964f73, 0x043d}, // encyrillic
+ {0xd89879e8, 0x2116}, // afii61352
+ {0xd8a1ab6d, 0x03ab}, // Upsilondieresis
+ {0xd8b1d2bf, 0x0177}, // ycircumflex
+ {0xd8faed0d, 0x30ef}, // wakatakana
+ {0xd9038cdb, 0x32a4}, // ideographichighcircle
+ {0xd90a7039, 0x06af}, // afii57509
+ {0xd91584cd, 0xfe31}, // emdashvertical
+ {0xd92072b9, 0x0493}, // ghestrokecyrillic
+ {0xd92d9608, 0x0acd}, // viramagujarati
+ {0xd932c15c, 0x30ac}, // gakatakana
+ {0xd93c2940, 0x33cf}, // ktsquare
+ {0xd94d846e, 0x321a}, // phieuphaparenkorean
+ {0xd94faf13, 0x0188}, // chook
+ {0xd95c2f59, 0x04bd}, // cheabkhasiancyrillic
+ {0xd9697a13, 0x328b}, // ideographfirecircle
+ {0xd98cc91f, 0x0307}, // dotaccentcmb
+ {0xd991004f, 0x0e4f}, // fongmanthai
+ {0xd99e3976, 0x249f}, // dparen
+ {0xd9ba695c, 0xff70}, // katahiraprolongmarkhalfwidth
+ {0xd9d63664, 0x0189}, // Dafrican
+ {0xd9e83df4, 0x096b}, // fivedeva
+ {0xd9eba56d, 0x03b3}, // gamma
+ {0xda15411c, 0x0304}, // macroncmb
+ {0xda2037e1, 0x1e01}, // aringbelow
+ {0xda3670ae, 0x3347}, // mansyonsquare
+ {0xda39b9df, 0x013d}, // Lcaron
+ {0xda48ff7e, 0x00bb}, // guillemotright
+ {0xda4e1891, 0x228b}, // supersetnotequal
+ {0xda55d0f3, 0x0a69}, // threegurmukhi
+ {0xda620e6e, 0xfca2}, // tehhahinitialarabic
+ {0xda734cc8, 0x232b}, // deleteleft
+ {0xda85eaa3, 0x2070}, // zerosuperior
+ {0xda94576a, 0x0999}, // ngabengali
+ {0xda94a677, 0x0634}, // afii57428
+ {0xda99b3d7, 0x30d1}, // pakatakana
+ {0xdab46527, 0x21e9}, // arrowdownwhite
+ {0xdac4a95a, 0x002f}, // slash
+ {0xdac8670b, 0x011e}, // Gbreve
+ {0xdad5813e, 0x24bf}, // Jcircle
+ {0xdaf9ae21, 0x03e7}, // kheicoptic
+ {0xdb00acb2, 0x04dc}, // Zhedieresiscyrillic
+ {0xdb07430c, 0x1eac}, // Acircumflexdotbelow
+ {0xdb15243d, 0xfb38}, // tetdagesh
+ {0xdb180684, 0x059d}, // gereshmuqdamhebrew
+ {0xdb19f222, 0x1e30}, // Kacute
+ {0xdb36c0cb, 0x00c1}, // Aacute
+ {0xdb46a061, 0x044f}, // afii10097
+ {0xdb4843d8, 0x0140}, // ldotaccent
+ {0xdb491e12, 0x3201}, // nieunparenkorean
+ {0xdb4ecb82, 0x06a4}, // afii57505
+ {0xdb5fdfb2, 0x09cd}, // viramabengali
+ {0xdb9c2f74, 0x3063}, // tusmallhiragana
+ {0xdb9dda85, 0xfb3b}, // kafdageshhebrew
+ {0xdba170e8, 0x0998}, // ghabengali
+ {0xdbae2c8c, 0x2277}, // greaterorless
+ {0xdbc3c473, 0x001a}, // controlSUB
+ {0xdbc6ef9a, 0x05b2}, // hatafpatah23
+ {0xdbc71338, 0xff01}, // exclammonospace
+ {0xdbcb0069, 0x338a}, // pfsquare
+ {0xdbf12380, 0x2460}, // onecircle
+ {0xdc0071a3, 0xfb46}, // tsadidageshhebrew
+ {0xdc05ec50, 0x30f5}, // kasmallkatakana
+ {0xdc0ad3ae, 0x012d}, // ibreve
+ {0xdc0c240d, 0xff9e}, // voicedmarkkanahalfwidth
+ {0xdc0c9e85, 0xff9f}, // semivoicedmarkkanahalfwidth
+ {0xdc3d7ac8, 0x04a7}, // pemiddlehookcyrillic
+ {0xdc41d3b3, 0x05ab}, // olehebrew
+ {0xdc54447c, 0x307c}, // bohiragana
+ {0xdc6ca9b3, 0x0584}, // keharmenian
+ {0xdc7756d1, 0x0077}, // w
+ {0xdc7d1de8, 0xfba9}, // hehmedialaltonearabic
+ {0xdc7f6ca5, 0x0624}, // wawhamzaabovearabic
+ {0xdcc5c006, 0x055a}, // apostrophearmenian
+ {0xdce03f6b, 0x261e}, // pointingindexrightwhite
+ {0xdcefaeeb, 0x228a}, // subsetnotequal
+ {0xdd07775c, 0x3223}, // fourideographicparen
+ {0xdd07a474, 0x00b3}, // threesuperior
+ {0xdd21d4c1, 0x039b}, // Lambda
+ {0xdd2fee63, 0x0aad}, // bhagujarati
+ {0xdd4e62a4, 0x0a96}, // khagujarati
+ {0xdd55f861, 0x2560}, // SF420000
+ {0xdd64bab7, 0x3080}, // muhiragana
+ {0xdd68d3ef, 0x00a5}, // yen
+ {0xdd8a8538, 0x0a26}, // dagurmukhi
+ {0xdd9a009b, 0x2016}, // dblverticalbar
+ {0xdda2fef7, 0x33db}, // srsquare
+ {0xdda8f1e0, 0x33d4}, // mbsquare
+ {0xddd89deb, 0xff4d}, // mmonospace
+ {0xdde406ed, 0x23a8}, // braceleftmid
+ {0xddfdd08a, 0x30a7}, // esmallkatakana
+ {0xddfea657, 0x0049}, // I
+ {0xddffcb32, 0x1e60}, // Sdotaccent
+ {0xde111430, 0x2271}, // notgreaternorequal
+ {0xde159412, 0x2605}, // blackstar
+ {0xde3de1bb, 0xfede}, // lamfinalarabic
+ {0xde4643cf, 0x0ac5}, // ecandravowelsigngujarati
+ {0xde5450d1, 0xff88}, // nekatakanahalfwidth
+ {0xde6c8dd1, 0x2223}, // divides
+ {0xde91c7ac, 0xfb35}, // vavdagesh
+ {0xdea63325, 0x0e0c}, // chochoethai
+ {0xdea93241, 0x0385}, // dieresistonos
+ {0xdeab4b8b, 0x05ad}, // dehihebrew
+ {0xdebc4010, 0xfcd5}, // noonmeeminitialarabic
+ {0xdebf0df4, 0x05f0}, // afii57716
+ {0xdecde878, 0x1e52}, // Omacronacute
+ {0xdee80462, 0xffe5}, // yenmonospace
+ {0xdee969b3, 0x3001}, // ideographiccomma
+ {0xdef14eee, 0x020e}, // Oinvertedbreve
+ {0xdef351c2, 0x020c}, // Odblgrave
+ {0xdef92b6a, 0x3349}, // mirisquare
+ {0xdf08e8b4, 0xff15}, // fivemonospace
+ {0xdf09c757, 0x0186}, // Oopen
+ {0xdf1ee74b, 0xff81}, // tikatakanahalfwidth
+ {0xdf243dad, 0x044c}, // softsigncyrillic
+ {0xdf46fba9, 0x01a4}, // Phook
+ {0xdf80589a, 0x3217}, // chieuchaparenkorean
+ {0xdf8c6402, 0x0e45}, // lakkhangyaothai
+ {0xdf9eaf7a, 0x33bc}, // muwsquare
+ {0xdfad5d93, 0x22c0}, // logicalandtext
+ {0xdfaf476d, 0x0167}, // tbar
+ {0xdfb9632c, 0x3144}, // pieupsioskorean
+ {0xdff819d0, 0xfe8c}, // yehhamzaabovemedialarabic
+ {0xdffe3761, 0x1e14}, // Emacrongrave
+ {0xe0130535, 0x316c}, // rieulpansioskorean
+ {0xe019189f, 0x0994}, // aubengali
+ {0xe0197d92, 0x0a4b}, // oomatragurmukhi
+ {0xe0343a59, 0x0323}, // dotbelowcomb
+ {0xe03a2368, 0x019f}, // Ocenteredtilde
+ {0xe0489c79, 0x017f}, // longs
+ {0xe0560cdf, 0x1e19}, // ecircumflexbelow
+ {0xe065671a, 0x1e0a}, // Ddotaccent
+ {0xe07dfee5, 0x04f4}, // Chedieresiscyrillic
+ {0xe0800244, 0xfb69}, // ttehmedialarabic
+ {0xe0987417, 0x24dd}, // ncircle
+ {0xe0ac4869, 0x3225}, // sixideographicparen
+ {0xe0c42e1f, 0x3357}, // wattosquare
+ {0xe0cf3aca, 0x1e1d}, // ecedillabreve
+ {0xe0d114bf, 0x04b6}, // Chedescendercyrillic
+ {0xe0dbd3b5, 0x2494}, // thirteenperiod
+ {0xe0ec0106, 0x05a6}, // merkhakefulahebrew
+ {0xe0ec7a9f, 0x01a6}, // yr
+ {0xe0f957bb, 0x0644}, // afii57444
+ {0xe10a53c6, 0x0aa8}, // nagujarati
+ {0xe13f2d93, 0x3026}, // sixhangzhou
+ {0xe173c1f2, 0x1e12}, // Dcircumflexbelow
+ {0xe180ca73, 0x1e43}, // mdotbelow
+ {0xe1b37094, 0x0629}, // afii57417
+ {0xe1bbda55, 0x040e}, // Ushortcyrillic
+ {0xe1bf1035, 0x0536}, // Zaarmenian
+ {0xe20234a2, 0x012c}, // Ibreve
+ {0xe20c937f, 0x041a}, // Kacyrillic
+ {0xe20deadd, 0x047c}, // Omegatitlocyrillic
+ {0xe2234dec, 0xff52}, // rmonospace
+ {0xe22682ea, 0x3260}, // kiyeokcirclekorean
+ {0xe22a6510, 0x1e8e}, // Ydotaccent
+ {0xe23968a4, 0x04b3}, // hadescendercyrillic
+ {0xe25773d9, 0x04d3}, // adieresiscyrillic
+ {0xe259edda, 0x0628}, // afii57416
+ {0xe25f57e9, 0xfe9a}, // thehfinalarabic
+ {0xe26168f6, 0x1e82}, // Wacute
+ {0xe28a564c, 0x044f}, // iacyrillic
+ {0xe28eea2e, 0x1e51}, // omacrongrave
+ {0xe2924f7e, 0x0a67}, // onegurmukhi
+ {0xe2a7b092, 0x3213}, // pieupaparenkorean
+ {0xe2b99909, 0x0e14}, // dodekthai
+ {0xe2cfeeb5, 0x0301}, // acutecomb
+ {0xe2eccaa5, 0x0059}, // Y
+ {0xe2ef1bbf, 0x2197}, // arrowupright
+ {0xe2fc74df, 0x0533}, // Gimarmenian
+ {0xe2ff3ec5, 0xfb8d}, // rrehfinalarabic
+ {0xe2ffc4d4, 0x2661}, // heartsuitwhite
+ {0xe3356dd7, 0x1e54}, // Pacute
+ {0xe35f1369, 0xfe5c}, // bracerightsmall
+ {0xe3708e14, 0xfed3}, // fehinitialarabic
+ {0xe37c75f9, 0x03c7}, // chi
+ {0xe38423f1, 0x01ab}, // tpalatalhook
+ {0xe387ebf8, 0xff94}, // yakatakanahalfwidth
+ {0xe39adf52, 0x05b7}, // patahnarrowhebrew
+ {0xe39bb5ba, 0x0258}, // ereversed
+ {0xe3a00fb0, 0x0aa2}, // ddhagujarati
+ {0xe3a0394c, 0x05b3}, // afii57802
+ {0xe3bf40b5, 0x21c5}, // arrowupleftofdown
+ {0xe3cbb73f, 0x0459}, // afii10106
+ {0xe3cddac1, 0x0621}, // hamzalowarabic
+ {0xe3d5ad06, 0x1ec0}, // Ecircumflexgrave
+ {0xe3dd99f7, 0x0596}, // tipehahebrew
+ {0xe3fae787, 0x0587}, // echyiwnarmenian
+ {0xe3fe00d6, 0x2170}, // oneroman
+ {0xe401701f, 0x3016}, // whitelenticularbracketleft
+ {0xe41d9109, 0x3089}, // rahiragana
+ {0xe424f4cb, 0x0969}, // threedeva
+ {0xe42a588e, 0x0e27}, // wowaenthai
+ {0xe42a6647, 0x0a71}, // addakgurmukhi
+ {0xe433c6bf, 0x042c}, // afii10046
+ {0xe44aea0b, 0x0219}, // scommaaccent
+ {0xe44ed7aa, 0x2481}, // fourteenparen
+ {0xe468e60e, 0x33bd}, // mwsquare
+ {0xe47b4b2e, 0xfecb}, // aininitialarabic
+ {0xe4896ee4, 0x09c0}, // iivowelsignbengali
+ {0xe4924345, 0xff63}, // cornerbracketrighthalfwidth
+ {0xe49ba568, 0x090c}, // lvocalicdeva
+ {0xe4a8920f, 0x0014}, // controlDC4
+ {0xe4c6b94a, 0x0550}, // Reharmenian
+ {0xe4c91eec, 0x1ea7}, // acircumflexgrave
+ {0xe4d0a5c5, 0x2171}, // tworoman
+ {0xe4de0824, 0x090b}, // rvocalicdeva
+ {0xe4e78bbf, 0x0e33}, // saraamthai
+ {0xe4e90251, 0xfb41}, // samekhdagesh
+ {0xe4f1acea, 0x24d7}, // hcircle
+ {0xe5034999, 0xff55}, // umonospace
+ {0xe504c22f, 0x01a9}, // Esh
+ {0xe506e1eb, 0x2493}, // twelveperiod
+ {0xe5107e85, 0xff92}, // mekatakanahalfwidth
+ {0xe5116fc8, 0x2472}, // nineteencircle
+ {0xe514f37f, 0x03be}, // xi
+ {0xe51bd3a3, 0x0163}, // tcedilla
+ {0xe5287de7, 0x3173}, // pieuptikeutkorean
+ {0xe5322bbf, 0x25b2}, // blackuppointingtriangle
+ {0xe5431590, 0x2296}, // minuscircle
+ {0xe54a2cdd, 0x201e}, // quotedblbase
+ {0xe5694805, 0x3161}, // eukorean
+ {0xe56f3e08, 0x1e9b}, // slongdotaccent
+ {0xe5779de7, 0x0666}, // afii57398
+ {0xe578bf9d, 0x066d}, // asteriskaltonearabic
+ {0xe5850206, 0x2226}, // notparallel
+ {0xe59889e5, 0x014b}, // eng
+ {0xe59f1c1d, 0x2664}, // spadesuitwhite
+ {0xe59f1f71, 0x2203}, // thereexists
+ {0xe5dca65a, 0x04aa}, // Esdescendercyrillic
+ {0xe5e0ac19, 0x0a74}, // ekonkargurmukhi
+ {0xe5eb1828, 0x25e6}, // openbullet
+ {0xe5f403ac, 0x0981}, // candrabindubengali
+ {0xe5f51e2d, 0x0156}, // Rcedilla
+ {0xe61a323e, 0x21d2}, // arrowdblright
+ {0xe61dad29, 0x017b}, // Zdotaccent
+ {0xe62fb889, 0x2524}, // SF090000
+ {0xe6699f03, 0x025d}, // eopenreversedhook
+ {0xe6743f55, 0x011a}, // Ecaron
+ {0xe6861695, 0x018a}, // Dhook
+ {0xe68d35b4, 0x0436}, // afii10072
+ {0xe68e2cfd, 0x306b}, // nihiragana
+ {0xe6910141, 0x06f1}, // onepersian
+ {0xe6936418, 0xfb2f}, // alefqamatshebrew
+ {0xe6a0587d, 0x3222}, // threeideographicparen
+ {0xe6a445c8, 0xfcd2}, // noonjeeminitialarabic
+ {0xe6b00dbd, 0x060c}, // commaarabic
+ {0xe6bd7d55, 0x04f0}, // Udieresiscyrillic
+ {0xe6beeaab, 0x2667}, // clubsuitwhite
+ {0xe6d26878, 0x0663}, // threehackarabic
+ {0xe6eee43a, 0x031a}, // leftangleabovecmb
+ {0xe70bdf5d, 0x1e85}, // wdieresis
+ {0xe72762a6, 0x3127}, // ibopomofo
+ {0xe74e8d5b, 0x02d0}, // colontriangularmod
+ {0xe75de72d, 0x0a4d}, // halantgurmukhi
+ {0xe796e6c1, 0x032f}, // breveinvertedbelowcmb
+ {0xe7a9201c, 0x24e5}, // vcircle
+ {0xe7abb869, 0x05b0}, // sheva115
+ {0xe7f3b395, 0x24d8}, // icircle
+ {0xe80a2426, 0x3116}, // rbopomofo
+ {0xe80eec90, 0x0942}, // uuvowelsigndeva
+ {0xe832e25c, 0x03ef}, // deicoptic
+ {0xe836846a, 0x20a1}, // colonsign
+ {0xe8445eb9, 0x05e5}, // finaltsadihebrew
+ {0xe84e29b1, 0xfef5}, // lamalefmaddaaboveisolatedarabic
+ {0xe853fc35, 0x3177}, // pieupthieuthkorean
+ {0xe871a9d4, 0x03e3}, // sheicoptic
+ {0xe872f83d, 0x02a0}, // qhook
+ {0xe87409fd, 0xfe8b}, // yehhamzaaboveinitialarabic
+ {0xe885d69b, 0x1e58}, // Rdotaccent
+ {0xe893423b, 0x202c}, // afii61573
+ {0xe894f25c, 0x03a1}, // Rho
+ {0xe89d944f, 0x043a}, // kacyrillic
+ {0xe8aff9ae, 0x24b7}, // Bcircle
+ {0xe8d4db79, 0x2320}, // integraltp
+ {0xe8d6bd29, 0x0621}, // afii57409
+ {0xe8ec3154, 0x2490}, // nineperiod
+ {0xe8ef5782, 0x3092}, // wohiragana
+ {0xe8f0c584, 0x05d1}, // afii57665
+ {0xe8f5ca9b, 0x05b1}, // hatafsegolnarrowhebrew
+ {0xe8f7e9bf, 0x320f}, // nieunaparenkorean
+ {0xe903bc23, 0xfb36}, // zayindageshhebrew
+ {0xe90424fe, 0x2479}, // sixparen
+ {0xe927829f, 0x0953}, // gravedeva
+ {0xe92a9522, 0x0060}, // grave
+ {0xe93b2c93, 0x09c8}, // aivowelsignbengali
+ {0xe9598c36, 0x018e}, // Ereversed
+ {0xe96c43a5, 0x00d4}, // Ocircumflex
+ {0xe977a63b, 0x0591}, // etnahtalefthebrew
+ {0xe9860674, 0x2467}, // eightcircle
+ {0xe994ec07, 0x1e88}, // Wdotbelow
+ {0xe9955c1b, 0x2109}, // fahrenheit
+ {0xe997ce80, 0x03d6}, // pisymbolgreek
+ {0xe9b8fb39, 0x256a}, // SF540000
+ {0xe9c5c9e3, 0x0928}, // nadeva
+ {0xe9dc1b2b, 0x3162}, // yikorean
+ {0xe9f5bb0b, 0x1e40}, // Mdotaccent
+ {0xe9fd5bd7, 0x02d1}, // colontriangularhalfmod
+ {0xe9fe0986, 0xfe91}, // behinitialarabic
+ {0xea0e9b0d, 0x3077}, // puhiragana
+ {0xea11b5f2, 0x05d7}, // hethebrew
+ {0xea281c50, 0xfb3b}, // kafdagesh
+ {0xea281f0f, 0xfb43}, // pefinaldageshhebrew
+ {0xea4521ba, 0x1e99}, // yring
+ {0xea469ad4, 0x0641}, // feharabic
+ {0xea4bf6ca, 0x25b5}, // whiteuppointingsmalltriangle
+ {0xea505c24, 0x006b}, // k
+ {0xea723c32, 0x05b7}, // patahwidehebrew
+ {0xea828d24, 0x0ac4}, // rrvocalicvowelsigngujarati
+ {0xea8df8fe, 0xff09}, // parenrightmonospace
+ {0xeaaa8586, 0x05b6}, // segolhebrew
+ {0xeabb8dad, 0xfeb3}, // seeninitialarabic
+ {0xeabdd2cd, 0xff02}, // quotedblmonospace
+ {0xeacd0b4a, 0xfb1f}, // doubleyodpatah
+ {0xeade9ba5, 0x2113}, // afii61289
+ {0xeb0445d5, 0x043f}, // afii10081
+ {0xeb095515, 0x0452}, // afii10099
+ {0xeb4a0523, 0x056a}, // zhearmenian
+ {0xeb4fdea3, 0xfc08}, // behmeemisolatedarabic
+ {0xeb576d94, 0x21de}, // pageup
+ {0xeb701704, 0x2014}, // emdash
+ {0xeb71d801, 0x30f3}, // nkatakana
+ {0xebeccb02, 0x00dd}, // Yacute
+ {0xec20a331, 0x096c}, // sixdeva
+ {0xec450aad, 0x3211}, // rieulaparenkorean
+ {0xec4fc0c5, 0x0173}, // uogonek
+ {0xec78ec45, 0x05a4}, // mahapakhhebrew
+ {0xec820a21, 0x05b4}, // hiriqquarterhebrew
+ {0xec8ae366, 0x0987}, // ibengali
+ {0xec967081, 0x040a}, // Njecyrillic
+ {0xecad584f, 0x01a8}, // tonetwo
+ {0xecc21039, 0x0a98}, // ghagujarati
+ {0xecce5cae, 0x056b}, // iniarmenian
+ {0xecd385c5, 0x03a8}, // Psi
+ {0xecda4c6b, 0x0622}, // alefmaddaabovearabic
+ {0xecddb27b, 0x23d0}, // vextendsingle
+ {0xecddb519, 0x05db}, // kaf
+ {0xecef01e0, 0x1e59}, // rdotaccent
+ {0xed102125, 0x24c0}, // Kcircle
+ {0xed1afc7c, 0x05b2}, // hatafpatah2f
+ {0xed2b4a43, 0x00aa}, // ordfeminine
+ {0xed4bb321, 0x313f}, // rieulphieuphkorean
+ {0xed65e3bb, 0x057a}, // peharmenian
+ {0xed729d82, 0x049e}, // Kastrokecyrillic
+ {0xed796a9e, 0x0407}, // afii10056
+ {0xed8b66db, 0x040b}, // Tshecyrillic
+ {0xed9bf511, 0x063a}, // afii57434
+ {0xedfaec74, 0xfe69}, // dollarsmall
+ {0xee22e47f, 0x00bd}, // onehalf
+ {0xee3352d3, 0x05b0}, // afii57799
+ {0xee339d2e, 0x2025}, // twodotenleader
+ {0xee421e32, 0x0660}, // zeroarabic
+ {0xee6c8858, 0x3168}, // nieunpansioskorean
+ {0xee7a31f8, 0x220b}, // suchthat
+ {0xee8d09d6, 0xff12}, // twomonospace
+ {0xee900f0f, 0x0462}, // afii10146
+ {0xee9f1e99, 0x02b7}, // wsuperior
+ {0xeeb00f1b, 0x0063}, // c
+ {0xeeb5ef47, 0x24c2}, // Mcircle
+ {0xeeda5b48, 0x00a6}, // brokenbar
+ {0xeeeef128, 0x062f}, // afii57423
+ {0xeeef7f7c, 0x01b6}, // zstroke
+ {0xef03e03f, 0x32a3}, // ideographiccorrectcircle
+ {0xef22f61a, 0x06f0}, // zeropersian
+ {0xef24cf3c, 0x00a8}, // dieresis
+ {0xef2800a0, 0x00cc}, // Igrave
+ {0xef33d78e, 0x25a1}, // H22073
+ {0xef3a179c, 0x020a}, // Iinvertedbreve
+ {0xef636ee0, 0x05da}, // finalkaf
+ {0xefa03eab, 0x00c6}, // AE
+ {0xefc57067, 0x0260}, // ghook
+ {0xefd65ddd, 0x2033}, // second
+ {0xefd69119, 0x044e}, // iucyrillic
+ {0xefd88572, 0x1ecb}, // idotbelow
+ {0xefe0e3fd, 0xfb01}, // fi
+ {0xeff59b38, 0x0397}, // Eta
+ {0xeff843d0, 0x25a3}, // squarewhitewithsmallblack
+ {0xf00181f3, 0x305c}, // zehiragana
+ {0xf00455e8, 0x1e86}, // Wdotaccent
+ {0xf0045976, 0x3178}, // kapyeounpieupkorean
+ {0xf0213847, 0x3383}, // masquare
+ {0xf0248bca, 0x0640}, // tatweelarabic
+ {0xf029041c, 0x3017}, // whitelenticularbracketright
+ {0xf0412bb8, 0x04d0}, // Abrevecyrillic
+ {0xf04a093b, 0xff66}, // wokatakanahalfwidth
+ {0xf04c3677, 0xfe62}, // plussmall
+ {0xf04d9cf4, 0x308c}, // rehiragana
+ {0xf064a013, 0x047e}, // Otcyrillic
+ {0xf0673e49, 0x096f}, // ninedeva
+ {0xf0757f39, 0x030a}, // ringcmb
+ {0xf08a28a9, 0xfe4e}, // lowlinecenterline
+ {0xf09092b0, 0x3185}, // ssanghieuhkorean
+ {0xf091911a, 0x223d}, // reversedtilde
+ {0xf098620b, 0x066a}, // percentarabic
+ {0xf0a5507a, 0x03d5}, // phisymbolgreek
+ {0xf0ac995b, 0x337c}, // syouwaerasquare
+ {0xf0b806fd, 0xfb7c}, // tchehinitialarabic
+ {0xf0c10455, 0x0645}, // meemarabic
+ {0xf0d79471, 0x0201}, // adblgrave
+ {0xf0e2f076, 0x04cb}, // Chekhakassiancyrillic
+ {0xf0ec6a42, 0x0302}, // circumflexcmb
+ {0xf0f2a82b, 0x1e8a}, // Xdotaccent
+ {0xf0fcc511, 0x3398}, // klsquare
+ {0xf1116d2e, 0xfe41}, // cornerbracketleftvertical
+ {0xf1200f87, 0x05e6}, // tsadi
+ {0xf13a2d0d, 0x0119}, // eogonek
+ {0xf1410096, 0x1e1a}, // Etildebelow
+ {0xf144c7a3, 0x249d}, // bparen
+ {0xf15ab600, 0x05bb}, // qubuts18
+ {0xf16238a6, 0x04de}, // Zedieresiscyrillic
+ {0xf1aad12e, 0x0638}, // afii57432
+ {0xf1b08e52, 0x20ab}, // dong
+ {0xf1b0be56, 0x20a2}, // cruzeiro
+ {0xf1b5f5d1, 0x05b5}, // tserehebrew
+ {0xf1dd7830, 0x033d}, // xabovecmb
+ {0xf1ddaa7d, 0x0995}, // kabengali
+ {0xf1e94d64, 0x064e}, // fathaarabic
+ {0xf1eb4f66, 0x0116}, // Edotaccent
+ {0xf1f78ce7, 0x226a}, // muchless
+ {0xf2118c6c, 0x0635}, // sadarabic
+ {0xf232181a, 0x2640}, // venus
+ {0xf237f0f1, 0xff29}, // Imonospace
+ {0xf23a5b68, 0x0130}, // Idot
+ {0xf24a3a6b, 0x05b8}, // qamats1c
+ {0xf2558e7a, 0x230a}, // floorleftbig
+ {0xf2620ee8, 0x1e07}, // blinebelow
+ {0xf26e5910, 0x045e}, // ushortcyrillic
+ {0xf2b4963c, 0x09b0}, // rabengali
+ {0xf2b826ec, 0x0449}, // shchacyrillic
+ {0xf2c1d44e, 0x05be}, // maqafhebrew
+ {0xf2c69081, 0x016e}, // Uring
+ {0xf2dd8deb, 0x248d}, // sixperiod
+ {0xf2e23a0c, 0xfb57}, // pehfinalarabic
+ {0xf2e7f536, 0x0329}, // verticallinebelowcmb
+ {0xf2f52e6c, 0x00ce}, // Icircumflex
+ {0xf2f6e905, 0x24cd}, // Xcircle
+ {0xf3086f4b, 0x2030}, // perthousand
+ {0xf311fe21, 0xfece}, // ghainfinalarabic
+ {0xf315dbae, 0xfe3c}, // blacklenticularbracketrightvertical
+ {0xf336d994, 0xfef3}, // yehinitialarabic
+ {0xf3527249, 0xfb67}, // ttehfinalarabic
+ {0xf3935843, 0xff26}, // Fmonospace
+ {0xf399cd14, 0x0074}, // t
+ {0xf3c08521, 0x0431}, // afii10066
+ {0xf3ce4ef0, 0x014a}, // Eng
+ {0xf3cfe996, 0x05b8}, // qamatsde
+ {0xf3ef0654, 0x2160}, // Oneroman
+ {0xf41c3e87, 0x3182}, // yesieungsioskorean
+ {0xf4266df0, 0x2253}, // imageorapproximatelyequal
+ {0xf4584280, 0x05ea}, // tav
+ {0xf4637345, 0x207a}, // plussuperior
+ {0xf467a09a, 0x3066}, // tehiragana
+ {0xf4728f62, 0x06f9}, // ninepersian
+ {0xf47778a3, 0x0958}, // qadeva
+ {0xf47c7f06, 0x05a5}, // merkhalefthebrew
+ {0xf498c20b, 0xfb2c}, // shindageshshindothebrew
+ {0xf4a2e6c9, 0xff8e}, // hokatakanahalfwidth
+ {0xf4c721dd, 0x0415}, // afii10022
+ {0xf4d1afd1, 0x015b}, // sacute
+ {0xf4d731e8, 0x0e2b}, // hohipthai
+ {0xf4d7dcfe, 0x05b0}, // sheva
+ {0xf4ea5918, 0x0423}, // afii10037
+ {0xf4f5b85f, 0x05bd}, // afii57839
+ {0xf4fec4c5, 0x2026}, // ellipsis
+ {0xf4fecbee, 0x3152}, // yaekorean
+ {0xf526b2bc, 0x09f5}, // twonumeratorbengali
+ {0xf53d898f, 0x0122}, // Gcedilla
+ {0xf54df907, 0x02e8}, // tonebarlowmod
+ {0xf573def2, 0x25ba}, // blackrightpointingpointer
+ {0xf573f1ec, 0x24a4}, // iparen
+ {0xf59704d9, 0xff4f}, // omonospace
+ {0xf59943f5, 0x01d4}, // ucaron
+ {0xf59f95da, 0x0399}, // Iota
+ {0xf5a6729d, 0x247e}, // elevenparen
+ {0xf5ab4f6d, 0x0458}, // afii10105
+ {0xf5c2a87b, 0x0a14}, // augurmukhi
+ {0xf5c40812, 0x2019}, // quoteright
+ {0xf5cad972, 0x1ea6}, // Acircumflexgrave
+ {0xf5e83826, 0x0986}, // aabengali
+ {0xf5f606a8, 0x316e}, // mieumpieupkorean
+ {0xf5f79af6, 0x23a3}, // bracketleftbt
+ {0xf5fe99ee, 0x2017}, // underscoredbl
+ {0xf61328eb, 0x2580}, // upblock
+ {0xf61a2336, 0x0157}, // rcommaaccent
+ {0xf6228c58, 0x20a3}, // franc
+ {0xf6271ec7, 0x0429}, // afii10043
+ {0xf630815e, 0x0577}, // shaarmenian
+ {0xf64f0a5d, 0x30f9}, // vekatakana
+ {0xf64f6666, 0xff44}, // dmonospace
+ {0xf6545660, 0x3333}, // huiitosquare
+ {0xf655e1cb, 0x316a}, // rieultikeutkorean
+ {0xf66aa028, 0x215d}, // fiveeighths
+ {0xf67e1ed1, 0x01a3}, // oi
+ {0xf6886180, 0x3216}, // cieucaparenkorean
+ {0xf68c8679, 0x3043}, // ismallhiragana
+ {0xf68fb68d, 0x215b}, // oneeighth
+ {0xf6909b76, 0x24a1}, // fparen
+ {0xf6b386e5, 0x1e3c}, // Lcircumflexbelow
+ {0xf6c0ec85, 0x1e5c}, // Rdotbelowmacron
+ {0xf6f2a8b6, 0x0289}, // ubar
+ {0xf7114d7b, 0x2502}, // SF110000
+ {0xf7440454, 0x045e}, // afii10110
+ {0xf757213f, 0x01d6}, // udieresismacron
+ {0xf779fd74, 0x1ea0}, // Adotbelow
+ {0xf7887f64, 0x24ba}, // Ecircle
+ {0xf7994ed0, 0x0a6d}, // sevengurmukhi
+ {0xf7c65164, 0x266d}, // musicflatsign
+ {0xf7d4f2e3, 0x1e1e}, // Fdotaccent
+ {0xf7ddf3cd, 0x0a17}, // gagurmukhi
+ {0xf7de3a36, 0x3086}, // yuhiragana
+ {0xf7edf1a3, 0x0403}, // Gjecyrillic
+ {0xf7fe7207, 0x279e}, // arrowrightheavy
+ {0xf7fec616, 0x1ec4}, // Ecircumflextilde
+ {0xf81e4626, 0x0a02}, // bindigurmukhi
+ {0xf8245f14, 0x30e6}, // yukatakana
+ {0xf82ad190, 0x05e4}, // pehebrew
+ {0xf8376f18, 0x0410}, // afii10017
+ {0xf885c738, 0x30d3}, // bikatakana
+ {0xf8868f94, 0xff8c}, // hukatakanahalfwidth
+ {0xf8892150, 0x04a8}, // Haabkhasiancyrillic
+ {0xf89be814, 0x0abc}, // nuktagujarati
+ {0xf8e483f7, 0x1eb5}, // abrevetilde
+ {0xf8ef289b, 0x21df}, // pagedown
+ {0xf90377b2, 0x3151}, // yakorean
+ {0xf90f516b, 0x0a5b}, // zagurmukhi
+ {0xf9190810, 0x0203}, // ainvertedbreve
+ {0xf929be43, 0x0ab6}, // shagujarati
+ {0xf93a01ea, 0xfe6a}, // percentsmall
+ {0xf952cde5, 0x1eae}, // Abreveacute
+ {0xf95ad1c7, 0x0065}, // e
+ {0xf95b34b0, 0x0660}, // afii57392
+ {0xf976011b, 0x3399}, // fmsquare
+ {0xf99ebcf4, 0x25c1}, // whiteleftpointingtriangle
+ {0xf9bdabb3, 0x00e2}, // acircumflex
+ {0xf9d67642, 0x00c8}, // Egrave
+ {0xf9e5170b, 0x1eb6}, // Abrevedotbelow
+ {0xf9e8161d, 0x3206}, // siosparenkorean
+ {0xf9eeaebc, 0x002c}, // comma
+ {0xf9f4a348, 0x2279}, // notgreaternorless
+ {0xf9f6f2fe, 0xfe84}, // alefhamzaabovefinalarabic
+ {0xf9f909db, 0x09f1}, // ralowerdiagonalbengali
+ {0xfa1f37f7, 0x207d}, // parenleftsuperior
+ {0xfa3ebdeb, 0xfb8b}, // jehfinalarabic
+ {0xfa46e08a, 0x010a}, // Cdotaccent
+ {0xfa6e953f, 0x0e13}, // nonenthai
+ {0xfa7aad8b, 0x0e34}, // saraithai
+ {0xfa85b29d, 0x0ae6}, // zerogujarati
+ {0xfa8f771c, 0xfe82}, // alefmaddaabovefinalarabic
+ {0xfa932832, 0x1ec9}, // ihookabove
+ {0xfa9cd43f, 0x2488}, // oneperiod
+ {0xfa9f7510, 0x1e6a}, // Tdotaccent
+ {0xfaa7f693, 0xff1b}, // semicolonmonospace
+ {0xfab3dba6, 0x3138}, // ssangtikeutkorean
+ {0xfac03db8, 0x015e}, // Scedilla
+ {0xfac092ef, 0x24de}, // ocircle
+ {0xfad44b21, 0x278d}, // fourcircleinversesansserif
+ {0xfadde282, 0x0539}, // Toarmenian
+ {0xfaf8abd4, 0x05bb}, // qubuts25
+ {0xfb0a35fb, 0xfb2b}, // afii57695
+ {0xfb0e1bad, 0x2567}, // SF450000
+ {0xfb1373b2, 0x30b9}, // sukatakana
+ {0xfb161300, 0x049f}, // kastrokecyrillic
+ {0xfb206015, 0x30b1}, // kekatakana
+ {0xfb2465d5, 0x0468}, // Yuslittleiotifiedcyrillic
+ {0xfb3e0b48, 0x01fc}, // AEacute
+ {0xfb4678bc, 0xfe90}, // behfinalarabic
+ {0xfb5bf4b4, 0x02cf}, // acutelowmod
+ {0xfb5cfdc8, 0x095c}, // dddhadeva
+ {0xfb764dd2, 0x21d3}, // arrowdbldown
+ {0xfb7c1fd7, 0x0485}, // dasiapneumatacyrilliccmb
+ {0xfbcf44c4, 0x018d}, // deltaturned
+ {0xfbd1b93f, 0x33c4}, // squarecc
+ {0xfbd50511, 0x04e8}, // Obarredcyrillic
+ {0xfbf1fcde, 0x03e9}, // horicoptic
+ {0xfc161b2f, 0x2499}, // eighteenperiod
+ {0xfc18556b, 0x02bd}, // commareversedmod
+ {0xfc1a2c97, 0x046c}, // Yusbigiotifiedcyrillic
+ {0xfc2caf5c, 0x2285}, // notsuperset
+ {0xfc3393bc, 0x0124}, // Hcircumflex
+ {0xfc3a32c2, 0x0a8d}, // ecandragujarati
+ {0xfc75d31c, 0x1ec6}, // Ecircumflexdotbelow
+ {0xfc7e1ef8, 0x0952}, // anudattadeva
+ {0xfc7ea01a, 0x04e6}, // Odieresiscyrillic
+ {0xfc8020b6, 0xfb39}, // yoddageshhebrew
+ {0xfc828b2d, 0x1ee4}, // Udotbelow
+ {0xfc9cf271, 0x020d}, // odblgrave
+ {0xfce47bc6, 0x05b7}, // patah
+ {0xfce8ddc1, 0x1e0f}, // dlinebelow
+ {0xfce9ddb5, 0x05aa}, // yerahbenyomohebrew
+ {0xfcf6e2a9, 0x21ea}, // capslock
+ {0xfd00e31a, 0x0303}, // tildecomb
+ {0xfd0eac29, 0x0261}, // gscript
+ {0xfd1397ce, 0x0412}, // Vecyrillic
+ {0xfd166ead, 0x05e0}, // nunhebrew
+ {0xfd2c8feb, 0x1ea4}, // Acircumflexacute
+ {0xfd5ace9a, 0x057b}, // jheharmenian
+ {0xfd6ac237, 0x221d}, // proportional
+ {0xfd77296d, 0x04af}, // ustraightcyrillic
+ {0xfd891a4c, 0x0948}, // aivowelsigndeva
+ {0xfd8944f5, 0x0309}, // hookabovecomb
+ {0xfd89977d, 0x0e50}, // zerothai
+ {0xfd93a170, 0x239c}, // parenleftex
+ {0xfd99bb06, 0xfb3c}, // lameddagesh
+ {0xfdb6c57c, 0x2215}, // divisionslash
+ {0xfdbbbec8, 0x090d}, // ecandradeva
+ {0xfdc83f1f, 0x028d}, // wturned
+ {0xfdd37935, 0x0422}, // afii10036
+ {0xfdec640d, 0x0640}, // kashidaautoarabic
+ {0xfdf32442, 0x3390}, // Hzsquare
+ {0xfdf4c83e, 0x026e}, // lezh
+ {0xfe3d55df, 0x064f}, // dammaarabic
+ {0xfe407199, 0x2276}, // lessorgreater
+ {0xfe7515f3, 0x03a9}, // Omegagreek
+ {0xfe779a6a, 0x045a}, // afii10107
+ {0xfea7088a, 0x0628}, // beharabic
+ {0xfeb66fd9, 0xfec7}, // zahinitialarabic
+ {0xfeb7f263, 0x0556}, // Feharmenian
+ {0xfec7bc3b, 0x0651}, // shaddaarabic
+ {0xfee2004d, 0x01fe}, // Ostrokeacute
+ {0xfee5f25e, 0x2126}, // Omega
+ {0xfee9d86c, 0x2295}, // pluscircle
+ {0xfef651f8, 0x0688}, // afii57512
+ {0xff5dadf4, 0x0193}, // Ghook
+ {0xff81c116, 0x21cd}, // arrowleftdblstroke
+ {0xff8c00d8, 0x3386}, // MBsquare
+ {0xff8f2931, 0x30ae}, // gikatakana
+ {0xff90fc92, 0x0923}, // nnadeva
+ {0xff94689d, 0x04c0}, // palochkacyrillic
+ {0xffe38169, 0x043f}, // pecyrillic
+ {0xfffadc30, 0x2568}, // SF460000
+}};
diff --git a/dviware/dvisvgm/src/BasicDVIReader.cpp b/dviware/dvisvgm/src/BasicDVIReader.cpp
new file mode 100644
index 0000000000..350e130166
--- /dev/null
+++ b/dviware/dvisvgm/src/BasicDVIReader.cpp
@@ -0,0 +1,333 @@
+/*************************************************************************
+** BasicDVIReader.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <sstream>
+#include "BasicDVIReader.hpp"
+
+using namespace std;
+
+
+BasicDVIReader::BasicDVIReader (std::istream &is) : StreamReader(is), _dviVersion(DVI_NONE)
+{
+}
+
+
+/** Evaluates the next DVI command, and computes the corresponding handler.
+ * @param[out] handler handler for current DVI command
+ * @param[out] param the handler must be called with this parameter
+ * @return opcode of current DVI command */
+int BasicDVIReader::evalCommand (CommandHandler &handler, int &param) {
+ struct DVICommand {
+ CommandHandler handler;
+ int length; // number of parameter bytes
+ };
+
+ /* Each cmdFOO command reads the necessary number of bytes from the stream, so executeCommand
+ doesn't need to know the exact DVI command format. Some cmdFOO methods are used for multiple
+ DVI commands because they only differ in length of their parameters. */
+ static const DVICommand commands[] = {
+ {&BasicDVIReader::cmdSetChar, 1}, {&BasicDVIReader::cmdSetChar, 2}, // 128-129
+ {&BasicDVIReader::cmdSetChar, 3}, {&BasicDVIReader::cmdSetChar, 4}, // 130-131
+ {&BasicDVIReader::cmdSetRule, 8}, // 132
+ {&BasicDVIReader::cmdPutChar, 1}, {&BasicDVIReader::cmdPutChar, 2}, // 133-134
+ {&BasicDVIReader::cmdPutChar, 3}, {&BasicDVIReader::cmdPutChar, 4}, // 135-136
+ {&BasicDVIReader::cmdPutRule, 8}, // 137
+ {&BasicDVIReader::cmdNop, 0}, // 138
+ {&BasicDVIReader::cmdBop, 44}, {&BasicDVIReader::cmdEop, 0}, // 139-140
+ {&BasicDVIReader::cmdPush, 0}, {&BasicDVIReader::cmdPop, 0}, // 141-142
+ {&BasicDVIReader::cmdRight, 1}, {&BasicDVIReader::cmdRight, 2}, // 143-144
+ {&BasicDVIReader::cmdRight, 3}, {&BasicDVIReader::cmdRight, 4}, // 145-146
+ {&BasicDVIReader::cmdW0, 0}, // 147
+ {&BasicDVIReader::cmdW, 1}, {&BasicDVIReader::cmdW, 2}, // 148-149
+ {&BasicDVIReader::cmdW, 3}, {&BasicDVIReader::cmdW, 4}, // 150-151
+ {&BasicDVIReader::cmdX0, 0}, // 152
+ {&BasicDVIReader::cmdX, 1}, {&BasicDVIReader::cmdX, 2}, // 153-154
+ {&BasicDVIReader::cmdX, 3}, {&BasicDVIReader::cmdX, 4}, // 155-156
+ {&BasicDVIReader::cmdDown, 1}, {&BasicDVIReader::cmdDown, 2}, // 157-158
+ {&BasicDVIReader::cmdDown, 3}, {&BasicDVIReader::cmdDown, 4}, // 159-160
+ {&BasicDVIReader::cmdY0, 0}, // 161
+ {&BasicDVIReader::cmdY, 1}, {&BasicDVIReader::cmdY, 2}, // 162-163
+ {&BasicDVIReader::cmdY, 3}, {&BasicDVIReader::cmdY, 4}, // 164-165
+ {&BasicDVIReader::cmdZ0, 0}, // 166
+ {&BasicDVIReader::cmdZ, 1}, {&BasicDVIReader::cmdZ, 2}, // 167-168
+ {&BasicDVIReader::cmdZ, 3}, {&BasicDVIReader::cmdZ, 4}, // 169-170
+
+ {&BasicDVIReader::cmdFontNum, 1}, {&BasicDVIReader::cmdFontNum, 2}, // 235-236
+ {&BasicDVIReader::cmdFontNum, 3}, {&BasicDVIReader::cmdFontNum, 4}, // 237-238
+ {&BasicDVIReader::cmdXXX, 1}, {&BasicDVIReader::cmdXXX, 2}, // 239-240
+ {&BasicDVIReader::cmdXXX, 3}, {&BasicDVIReader::cmdXXX, 4}, // 241-242
+ {&BasicDVIReader::cmdFontDef, 1}, {&BasicDVIReader::cmdFontDef, 2}, // 243-244
+ {&BasicDVIReader::cmdFontDef, 3}, {&BasicDVIReader::cmdFontDef, 4}, // 245-246
+ {&BasicDVIReader::cmdPre, 0}, {&BasicDVIReader::cmdPost, 0}, // 247-248
+ {&BasicDVIReader::cmdPostPost, 0}, // 249
+ };
+
+ const int opcode = readByte();
+ if (!isStreamValid() || opcode < 0) // at end of file
+ throw InvalidDVIFileException("invalid DVI file");
+
+ int num_param_bytes = 0;
+ param = -1;
+ if (opcode >= OP_SETCHAR0 && opcode <= OP_SETCHAR127) {
+ handler = &BasicDVIReader::cmdSetChar0;
+ param = opcode;
+ }
+ else if (opcode >= OP_FNTNUM0 && opcode <= OP_FNTNUM63) {
+ handler = &BasicDVIReader::cmdFontNum0;
+ param = opcode-OP_FNTNUM0;
+ }
+ else if (evalXDVOpcode(opcode, handler))
+ num_param_bytes = 0;
+ else if (_dviVersion == DVI_PTEX && opcode == OP_DIR) { // direction command set by pTeX?
+ handler = &BasicDVIReader::cmdDir;
+ num_param_bytes = 1;
+ }
+ else if (opcode > OP_POSTPOST) {
+ ostringstream oss;
+ oss << "undefined DVI command (opcode " << opcode << ')';
+ throw DVIException(oss.str());
+ }
+ else {
+ const int offset = opcode < OP_FNTNUM0 ? OP_SET1 : (OP_FNTNUM63+1)-(OP_FNTNUM0-OP_SET1);
+ handler = commands[opcode-offset].handler;
+ num_param_bytes = commands[opcode-offset].length;
+ }
+ if (param < 0)
+ param = num_param_bytes;
+ return opcode;
+}
+
+
+/** Checks if a given opcode belongs to an XDV extension.
+ * @param[in] op the opcode to check
+ * @param[out] handler corresponding command handler if opcode is valid */
+bool BasicDVIReader::evalXDVOpcode (int op, CommandHandler &handler) const {
+ static const struct {
+ int min, max; // minimal and maximal opcode in XDV section
+ } xdvranges[] = {
+ {251, 254}, // XDV5
+ {252, 253}, // XDV6
+ {252, 254}, // XDV7
+ };
+ int index = _dviVersion-DVI_XDV5;
+ if (_dviVersion < DVI_XDV5 || _dviVersion > DVI_XDV7 || op < xdvranges[index].min || op > xdvranges[index].max)
+ return false;
+
+ static const CommandHandler handlers[] = {
+ &BasicDVIReader::cmdXPic, // 251 (XDV5 only)
+ &BasicDVIReader::cmdXFontDef, // 252
+ &BasicDVIReader::cmdXGlyphArray, // 253
+ &BasicDVIReader::cmdXTextAndGlyphs, // 254 (XDV7 only)
+ &BasicDVIReader::cmdXGlyphString // 254 (XDV5 only)
+ };
+ index = op-251;
+ if (_dviVersion == DVI_XDV5 && op == 254)
+ index++;
+ handler = handlers[index];
+ return true;
+}
+
+
+/** Reads a single DVI command from the current position of the input stream and calls the
+ * corresponding cmdFOO method.
+ * @return opcode of the executed command */
+int BasicDVIReader::executeCommand () {
+ CommandHandler handler;
+ int param; // parameter of handler
+ int opcode = evalCommand(handler, param);
+ (this->*handler)(param);
+ return opcode;
+}
+
+
+void BasicDVIReader::executePostPost () {
+ clearStream(); // reset all status bits
+ if (!isStreamValid())
+ throw DVIException("invalid DVI file");
+
+ seek(-1, ios::end); // stream pointer to last byte
+ int count=0;
+ while (peek() == DVI_FILL) { // count trailing fill bytes
+ seek(-1, ios::cur);
+ count++;
+ }
+ if (count < 4) // the standard requires at least 4 trailing fill bytes
+ throw DVIException("missing fill bytes at end of file");
+
+ setDVIVersion((DVIVersion)readUnsigned(1));
+}
+
+
+void BasicDVIReader::executeAllPages () {
+ if (_dviVersion == DVI_NONE)
+ executePostPost(); // get version ID from post_post
+ seek(0); // go to preamble
+ while (executeCommand() != OP_POST); // execute all commands until postamble is reached
+}
+
+
+void BasicDVIReader::setDVIVersion (DVIVersion version) {
+ _dviVersion = max(_dviVersion, version);
+ switch (_dviVersion) {
+ case DVI_STANDARD:
+ case DVI_PTEX:
+ case DVI_XDV5:
+ case DVI_XDV6:
+ case DVI_XDV7:
+ break;
+ default:
+ ostringstream oss;
+ oss << "DVI version " << _dviVersion << " not supported";
+ throw DVIException(oss.str());
+ }
+}
+
+/////////////////////////////////////
+
+/** Executes preamble command.
+ * Format: pre i[1] num[4] den[4] mag[4] k[1] x[k] */
+void BasicDVIReader::cmdPre (int) {
+ setDVIVersion((DVIVersion)readUnsigned(1)); // identification number
+ seek(12, ios::cur); // skip numerator, denominator, and mag factor
+ uint32_t k = readUnsigned(1); // length of following comment
+ seek(k, ios::cur); // skip comment
+}
+
+
+/** Executes postamble command.
+ * Format: post p[4] num[4] den[4] mag[4] l[4] u[4] s[2] t[2] */
+void BasicDVIReader::cmdPost (int) {
+ seek(28, ios::cur);
+}
+
+
+/** Executes postpost command.
+ * Format: postpost q[4] i[1] 223's[>= 4] */
+void BasicDVIReader::cmdPostPost (int) {
+ seek(4, ios::cur);
+ setDVIVersion((DVIVersion)readUnsigned(1)); // identification byte
+ while (readUnsigned(1) == DVI_FILL); // skip fill bytes (223), eof bit should be set now
+}
+
+
+/** Executes bop (begin of page) command.
+ * Format: bop c0[+4] ... c9[+4] p[+4] */
+void BasicDVIReader::cmdBop (int) {seek(44, ios::cur);}
+void BasicDVIReader::cmdEop (int) {}
+void BasicDVIReader::cmdPush (int) {}
+void BasicDVIReader::cmdPop (int) {}
+void BasicDVIReader::cmdSetChar0 (int) {}
+void BasicDVIReader::cmdSetChar (int len) {seek(len, ios::cur);}
+void BasicDVIReader::cmdPutChar (int len) {seek(len, ios::cur);}
+void BasicDVIReader::cmdSetRule (int) {seek(8, ios::cur);}
+void BasicDVIReader::cmdPutRule (int) {seek(8, ios::cur);}
+void BasicDVIReader::cmdRight (int len) {seek(len, ios::cur);}
+void BasicDVIReader::cmdDown (int len) {seek(len, ios::cur);}
+void BasicDVIReader::cmdX0 (int) {}
+void BasicDVIReader::cmdY0 (int) {}
+void BasicDVIReader::cmdW0 (int) {}
+void BasicDVIReader::cmdZ0 (int) {}
+void BasicDVIReader::cmdX (int len) {seek(len, ios::cur);}
+void BasicDVIReader::cmdY (int len) {seek(len, ios::cur);}
+void BasicDVIReader::cmdW (int len) {seek(len, ios::cur);}
+void BasicDVIReader::cmdZ (int len) {seek(len, ios::cur);}
+void BasicDVIReader::cmdNop (int) {}
+void BasicDVIReader::cmdDir (int) {seek(1, ios::cur);}
+void BasicDVIReader::cmdFontNum0 (int) {}
+void BasicDVIReader::cmdFontNum (int len) {seek(len, ios::cur);}
+void BasicDVIReader::cmdXXX (int len) {seek(readUnsigned(len), ios::cur);}
+
+
+/** Executes fontdef command.
+ * Format: fontdef k[len] c[4] s[4] d[4] a[1] l[1] n[a+l]
+ * @param[in] len size of font number variable (in bytes) */
+void BasicDVIReader::cmdFontDef (int len) {
+ seek(len+12, ios::cur); // skip font number
+ uint32_t pathlen = readUnsigned(1); // length of font path
+ uint32_t namelen = readUnsigned(1); // length of font name
+ seek(pathlen+namelen, ios::cur);
+}
+
+
+/** XDV extension: include image or pdf file.
+ * parameters: box[1] matrix[4][6] p[2] len[2] path[l] */
+void BasicDVIReader::cmdXPic (int) {
+ seek(1+24+2, ios::cur);
+ uint16_t len = readUnsigned(2);
+ seek(len, ios::cur);
+}
+
+
+void BasicDVIReader::cmdXFontDef (int) {
+ seek(4+4, ios::cur);
+ uint16_t flags = readUnsigned(2);
+ uint8_t len = readUnsigned(1);
+ if (_dviVersion == DVI_XDV5)
+ len += readUnsigned(1)+readUnsigned(1);
+ seek(len, ios::cur);
+ if (_dviVersion >= DVI_XDV6)
+ seek(4, ios::cur); // skip subfont index
+ if (flags & 0x0200) // colored?
+ seek(4, ios::cur);
+ if (flags & 0x1000) // extend?
+ seek(4, ios::cur);
+ if (flags & 0x2000) // slant?
+ seek(4, ios::cur);
+ if (flags & 0x4000) // embolden?
+ seek(4, ios::cur);
+ if ((flags & 0x0800) && (_dviVersion == DVI_XDV5)) { // variations?
+ uint16_t num_variations = readSigned(2);
+ seek(4*num_variations, ios::cur);
+ }
+}
+
+
+/** XDV extension: prints an array of characters where each character
+ * can take independent x and y coordinates.
+ * parameters: w[4] n[2] xy[(4+4)n] g[2n] */
+void BasicDVIReader::cmdXGlyphArray (int) {
+ seek(4, ios::cur);
+ uint16_t num_glyphs = readUnsigned(2);
+ seek(10*num_glyphs, ios::cur);
+}
+
+
+/** XDV extension: prints an array/string of characters where each character
+ * can take independent x coordinates whereas all share a single y coordinate.
+ * parameters: w[4] n[2] x[4n] y[4] g[2n] */
+void BasicDVIReader::cmdXGlyphString (int) {
+ seek(4, ios::cur);
+ uint16_t num_glyphs = readUnsigned(2);
+ seek(6*num_glyphs, ios::cur);
+}
+
+
+/** XDV extension: Same as cmdXGlyphArray plus a leading array of UTF-16 characters
+ * that specify the "actual text" represented by the glyphs to be printed. It usually
+ * contains the text with special characters (like ligatures) expanded so that it
+ * can be used for text search, plain text copy & paste etc. This XDV command was
+ * introduced with XeTeX 0.99995 and can be triggered by <tt>\\XeTeXgenerateactualtext1</tt>.
+ * parameters: l[2] t[2l] w[4] n[2] xy[8n] g[2n] */
+void BasicDVIReader::cmdXTextAndGlyphs (int) {
+ uint16_t l = readUnsigned(2);
+ seek(2*l, ios::cur);
+ cmdXGlyphArray(0);
+}
diff --git a/dviware/dvisvgm/src/BasicDVIReader.hpp b/dviware/dvisvgm/src/BasicDVIReader.hpp
new file mode 100644
index 0000000000..14a1fb9858
--- /dev/null
+++ b/dviware/dvisvgm/src/BasicDVIReader.hpp
@@ -0,0 +1,113 @@
+/*************************************************************************
+** BasicDVIReader.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef BASICDVIREADER_HPP
+#define BASICDVIREADER_HPP
+
+#include "Matrix.hpp"
+#include "MessageException.hpp"
+#include "StreamReader.hpp"
+
+struct DVIException : public MessageException {
+ explicit DVIException (const std::string &msg) : MessageException(msg) {}
+};
+
+
+struct InvalidDVIFileException : public DVIException {
+ explicit InvalidDVIFileException (const std::string &msg) : DVIException(msg) {}
+};
+
+
+/** This class provides the basic functionality to read a DVI file.
+ * It just skips all DVI commands and apply any semantic to it. The latter must
+ * be realized by deriving a separate class that implements the cmdXXX template
+ * methods. These are low-level functions that represent the DVI commands and
+ * require to read and evaluate the correct portion of data from the DVI stream.
+ * Since the DVI commands are almost skipped by advancing the file pointer,
+ * running through a DVI file is pretty fast. */
+class BasicDVIReader : public StreamReader {
+ protected:
+ using CommandHandler = void (BasicDVIReader::*)(int);
+ enum DVIVersion {DVI_NONE=0, DVI_STANDARD=2, DVI_PTEX=3, DVI_XDV5=5, DVI_XDV6=6, DVI_XDV7=7};
+ const uint8_t OP_SETCHAR0=0, OP_SETCHAR127=127, OP_SET1=128, OP_BOP=139, OP_EOP=140, OP_FNTNUM0=171,
+ OP_FNTNUM63=234, OP_PRE=247, OP_POST=248, OP_POSTPOST=249, OP_DIR=255, DVI_FILL=223;
+
+ public:
+ explicit BasicDVIReader (std::istream &is);
+ virtual void executeAllPages ();
+ virtual double getXPos () const {return 0;}
+ virtual double getYPos () const {return 0;}
+ virtual void finishLine () {}
+ virtual void translateToX (double x) {}
+ virtual void translateToY (double y) {}
+ virtual int stackDepth () const {return 0;}
+ virtual Matrix getPageTransformation () const {return Matrix(1);}
+ virtual unsigned currentPageNumber () const {return 0;}
+
+ protected:
+ void setDVIVersion (DVIVersion version);
+ DVIVersion getDVIVersion () const {return _dviVersion;}
+ virtual int evalCommand (CommandHandler &handler, int &param);
+ virtual int executeCommand ();
+ void executePostPost ();
+ bool evalXDVOpcode (int op, CommandHandler &handler) const;
+
+ // The following template methods represent the single DVI commands. They
+ // must read the correct chunk of data from the input stream in order to
+ // process the DVI file correctly.
+ virtual void cmdSetChar0 (int c);
+ virtual void cmdSetChar (int len);
+ virtual void cmdPutChar (int len);
+ virtual void cmdSetRule (int len);
+ virtual void cmdPutRule (int len);
+ virtual void cmdNop (int len);
+ virtual void cmdBop (int len);
+ virtual void cmdEop (int len);
+ virtual void cmdPush (int len);
+ virtual void cmdPop (int len);
+ virtual void cmdDir (int len);
+ virtual void cmdRight (int len);
+ virtual void cmdDown (int len);
+ virtual void cmdX0 (int len);
+ virtual void cmdY0 (int len);
+ virtual void cmdW0 (int len);
+ virtual void cmdZ0 (int len);
+ virtual void cmdX (int len);
+ virtual void cmdY (int len);
+ virtual void cmdW (int len);
+ virtual void cmdZ (int len);
+ virtual void cmdFontDef (int len);
+ virtual void cmdFontNum0 (int n);
+ virtual void cmdFontNum (int len);
+ virtual void cmdXXX (int len);
+ virtual void cmdPre (int len);
+ virtual void cmdPost (int len);
+ virtual void cmdPostPost (int len);
+ virtual void cmdXFontDef (int len); // XDV only
+ virtual void cmdXGlyphArray (int len); // XDV only
+ virtual void cmdXGlyphString (int len); // XDV version 5 only
+ virtual void cmdXPic (int len); // XDV version 5 only
+ virtual void cmdXTextAndGlyphs (int len); // XDV version 7 only
+
+ private:
+ DVIVersion _dviVersion; ///< DVI version of file being processed
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/Bezier.cpp b/dviware/dvisvgm/src/Bezier.cpp
new file mode 100644
index 0000000000..70656d14f9
--- /dev/null
+++ b/dviware/dvisvgm/src/Bezier.cpp
@@ -0,0 +1,256 @@
+/*************************************************************************
+** Bezier.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <utility>
+#include "Bezier.hpp"
+
+using namespace std;
+
+Bezier::Bezier () {
+ _points[0] = _points[1] = _points[2] = _points[3] = DPair(0);
+}
+
+
+/** Creates a quadratic Bézier curve. internally, it's represented as a cubic one. */
+Bezier::Bezier (const DPair &p0, const DPair &p1, const DPair &p2) {
+ setPoints(p0, p0+(p1-p0)*2.0/3.0, p2+(p1-p2)*2.0/3.0, p2);
+}
+
+
+Bezier::Bezier (const DPair &p0, const DPair &p1, const DPair &p2, const DPair &p3) {
+ setPoints(p0, p1, p2, p3);
+}
+
+
+/** Creates a subcurve of a given Bézier curve.
+ * @param[in] source original curve to be clipped
+ * @param[in] t0 'time' parameter \f$\in[0,1]\f$ of source curve where the subcurve starts
+ * @param[in] t1 'time' parameter \f$\in[0,1]\f$ of source curve where the subcurve ends */
+Bezier::Bezier (const Bezier &source, double t0, double t1) {
+ if (t0 == t1)
+ _points[0] = _points[1] = _points[2] = _points[3] = source.valueAt(t0);
+ else {
+ if (t0 > t1)
+ swap(t0, t1);
+ if (t0 == 0)
+ source.subdivide(t1, this, nullptr);
+ else if (t1 == 1)
+ source.subdivide(t0, nullptr, this);
+ else {
+ Bezier subcurve;
+ source.subdivide(t0, nullptr, &subcurve);
+ subcurve.subdivide((t1-t0)/(1-t0), this, nullptr);
+ }
+ }
+}
+
+
+void Bezier::setPoints(const DPair &p0, const DPair &p1, const DPair &p2, const DPair &p3) {
+ _points[0] = p0;
+ _points[1] = p1;
+ _points[2] = p2;
+ _points[3] = p3;
+}
+
+
+void Bezier::reverse() {
+ swap(_points[0], _points[3]);
+ swap(_points[1], _points[2]);
+}
+
+
+DPair Bezier::valueAt (double t) const {
+ const double s = 1-t;
+ return _points[0]*s*s*s + _points[1]*3.0*s*s*t + _points[2]*3.0*s*t*t + _points[3]*t*t*t;
+}
+
+
+/** Returns a value of the Bézier curve's blossom representation. */
+DPair Bezier::blossomValue (double u, double v, double w) const {
+ const double uv = u*v;
+ const double uw = u*w;
+ const double vw = v*w;
+ const double uvw = u*v*w;
+ return _points[0]*(1.0-u-v-w+uv+uw+vw-uvw)
+ +_points[1]*(u+v+w-2.0*(uv+uw+vw)+3.0*uvw)
+ +_points[2]*(uv+uw+vw-3.0*uvw)
+ +_points[3]*uvw;
+}
+
+
+/** Splits the curve at t into two sub-curves. */
+void Bezier::subdivide (double t, Bezier *bezier1, Bezier *bezier2) const {
+ const double s = 1-t;
+ DPair p01 = _points[0]*s + _points[1]*t;
+ DPair p12 = _points[1]*s + _points[2]*t;
+ DPair p23 = _points[2]*s + _points[3]*t;
+ DPair p012 = p01*s + p12*t;
+ DPair p123 = p12*s + p23*t;
+ DPair p0123 = p012*s + p123*t;
+ if (bezier1)
+ bezier1->setPoints(_points[0], p01, p012, p0123);
+ if (bezier2)
+ bezier2->setPoints(p0123, p123, p23, _points[3]);
+}
+
+
+/** Approximates the current Bézier curve by a sequence of line segments.
+ * This is done by subdividing the curve several times using De Casteljau's algorithm.
+ * If a sub-curve is almost flat, i.e. \f$\sum\limits_{k=0}^2 |p_{k+1}-p_k| - |p_3-p_0| < \delta\f$,
+ * the curve is not further subdivided.
+ * @param[in] delta threshold where to stop further subdivisions (see description above)
+ * @param[out] p the resulting sequence of points defining the start/end points of the line segments
+ * @param[out] t corresponding curve parameters of the approximated points p: \f$ b(t_i)=p_i \f$
+ * @return number of points in vector p */
+int Bezier::approximate (double delta, std::vector<DPair> &p, vector<double> *t) const {
+ p.push_back(_points[0]);
+ if (t)
+ t->push_back(0);
+ return approximate(delta, 0, 1, p, t);
+}
+
+
+int Bezier::approximate (double delta, double t0, double t1, vector<DPair> &p, vector<double> *t) const {
+ // compute distance of adjacent control points
+ const double l01 = (_points[1]-_points[0]).length();
+ const double l12 = (_points[2]-_points[1]).length();
+ const double l23 = (_points[3]-_points[2]).length();
+ const double l03 = (_points[3]-_points[0]).length();
+ if (l01+l12+l23-l03 < delta) { // is curve flat enough?
+ p.push_back(_points[3]); // => store endpoint
+ if (t)
+ t->push_back(t1);
+ }
+ else {
+ // subdivide curve at b(0.5) and approximate the resulting parts separately
+ Bezier b1, b2;
+ subdivide(0.5, &b1, &b2);
+ double tmid = (t0+t1)/2;
+ b1.approximate(delta, t0, tmid, p, t);
+ b2.approximate(delta, tmid, t1, p, t);
+ }
+ return p.size();
+}
+
+
+/** Returns the signed area of the triangle (p1, p2, p3). */
+static inline double signed_area (const DPair &p1, const DPair &p2, const DPair &p3) {
+ return ((p2.x()-p1.x())*(p3.y()-p1.y()) - (p3.x()-p1.x())*(p2.y()-p1.y()))/2.0;
+}
+
+
+static inline double dot_prod (const DPair &p1, const DPair &p2) {
+ return p1.x()*p2.x() + p1.y()*p2.y();
+}
+
+
+/** Returns true if p3 is located between p1 and p2, i.e. p3 lays almost on the line
+ * between p1 and p2. */
+static bool between (const DPair &p1, const DPair &p2, const DPair &p3, double delta) {
+ double sqr_dist = dot_prod(p2-p1, p2-p1);
+ double factor = sqr_dist == 0.0 ? 1.0 : sqr_dist;
+ double area2 = fabs(signed_area(p1, p2, p3));
+ return area2*area2/factor < delta // does p3 lay almost on the line through p1 and p2...
+ && min(p1.x(), p2.x()) <= p3.x() // ...and on or inside the rectangle spanned by p1 and p2?
+ && max(p1.x(), p2.x()) >= p3.x()
+ && min(p1.y(), p2.y()) <= p3.y()
+ && max(p1.y(), p2.y()) >= p3.y();
+}
+
+
+static inline bool near (const DPair &p1, const DPair &p2, double delta) {
+ DPair diff = p2-p1;
+ return fabs(diff.x()) < delta && fabs(diff.y()) < delta;
+}
+
+
+/** Tries to reduce the degree of the Bézier curve. This only works if the number of
+ * control points can be reduces without changing the shape of the curve significantly.
+ * @param[in] delta deviation tolerance
+ * @param[in] p control points of the reduced curve
+ * @return degree of the reduced curve */
+int Bezier::reduceDegree (double delta, vector<DPair> &p) const {
+ p.clear();
+ if (near(_points[0], _points[1], delta) && near(_points[0], _points[2], delta) && near(_points[0], _points[3], delta))
+ p.push_back(_points[0]);
+ else if (between(_points[0], _points[3], _points[1], delta) && between(_points[0], _points[3], _points[2], delta)) {
+ p.push_back(_points[0]);
+ p.push_back(_points[3]);
+ }
+ else if (near((_points[1]-_points[0])*1.5+_points[0], (_points[2]-_points[3])*1.5+_points[3], delta)) {
+ p.push_back(_points[0]);
+ p.push_back((_points[1]-_points[0])*1.5 + _points[0]);
+ p.push_back(_points[3]);
+ }
+ else {
+ p.resize(4);
+ for (int i=0; i < 4; i++)
+ p[i] = _points[i];
+ }
+ return p.size()-1;
+}
+
+
+/** Try to solve the quadratic equation ax^2 + bx + c = 0. */
+static bool solve_quadratic_equation (double a, double b, double c, double &x1, double &x2) {
+ if (a == 0) {
+ if (b == 0)
+ return false;
+ x1 = x2 = -c/b;
+ }
+ else {
+ double discr = b*b - 4*a*c;
+ if (discr < 0)
+ return false;
+ double p = -b/a/2;
+ double r = sqrt(discr)/a/2;
+ x1 = p+r;
+ x2 = p-r;
+ }
+ return true;
+}
+
+
+/** Returns a tight bounding box parallel to the x- and y-axis. */
+void Bezier::getBBox (BoundingBox &bbox) const {
+ bbox.invalidate();
+ // coefficients of the derivative
+ DPair pa = _points[3] - _points[2]*3.0 + _points[1]*3.0 - _points[0];
+ DPair pb = (_points[2]-_points[1]*2.0+_points[0])*2.0;
+ DPair pc = _points[1]-_points[0];
+
+ // compute extrema for t > 0 and t < 1
+ double t1, t2;
+ if (solve_quadratic_equation(pa.x(), pb.x(), pc.x(), t1, t2)) {
+ if (t1 > 0.001 && t1 < 0.999)
+ bbox.embed(valueAt(t1));
+ if (t1 != t2 && t2 > 0.001 && t2 < 0.999)
+ bbox.embed(valueAt(t2));
+ }
+ if (solve_quadratic_equation(pa.y(), pb.y(), pc.y(), t1, t2)) {
+ if (t1 > 0.001 && t1 < 0.999)
+ bbox.embed(valueAt(t1));
+ if (t1 != t2 && t2 > 0.001 && t2 < 0.999)
+ bbox.embed(valueAt(t2));
+ }
+ bbox.embed(_points[0]);
+ bbox.embed(_points[3]);
+}
diff --git a/dviware/dvisvgm/src/Bezier.hpp b/dviware/dvisvgm/src/Bezier.hpp
new file mode 100644
index 0000000000..f4f93dcf2f
--- /dev/null
+++ b/dviware/dvisvgm/src/Bezier.hpp
@@ -0,0 +1,52 @@
+/*************************************************************************
+** Bezier.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef BEZIER_HPP
+#define BEZIER_HPP
+
+#include <vector>
+#include "BoundingBox.hpp"
+#include "Pair.hpp"
+
+class Bezier
+{
+ public:
+ Bezier ();
+ Bezier (const DPair &p0, const DPair &p1, const DPair &p2);
+ Bezier (const DPair &p0, const DPair &p1, const DPair &p2, const DPair &p3);
+ Bezier (const Bezier &source, double t0, double t1);
+ void setPoints (const DPair &p0, const DPair &p1, const DPair &p2, const DPair &p3);
+ void reverse ();
+ DPair valueAt (double t) const;
+ DPair blossomValue (double u, double v, double w) const;
+ void subdivide (double t, Bezier *bezier1, Bezier *bezier2) const;
+ int approximate (double delta, std::vector<DPair> &p, std::vector<double> *t=nullptr) const;
+ const DPair& point (int i) const {return _points[i];}
+ int reduceDegree (double delta, std::vector<DPair> &p) const;
+ void getBBox (BoundingBox &bbox) const;
+
+ protected:
+ int approximate (double delta, double t0, double t1, std::vector<DPair> &p, std::vector<double> *t) const;
+
+ private:
+ DPair _points[4];
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/BgColorSpecialHandler.cpp b/dviware/dvisvgm/src/BgColorSpecialHandler.cpp
new file mode 100644
index 0000000000..10c05fca05
--- /dev/null
+++ b/dviware/dvisvgm/src/BgColorSpecialHandler.cpp
@@ -0,0 +1,68 @@
+/*************************************************************************
+** BgColorSpecialHandler.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include "BgColorSpecialHandler.hpp"
+#include "ColorSpecialHandler.hpp"
+#include "SpecialActions.hpp"
+
+using namespace std;
+
+
+/** Collect all background color changes while preprocessing the DVI file.
+ * We need them in order to apply the correct background colors even if
+ * not all but only selected DVI pages are converted. */
+void BgColorSpecialHandler::preprocess (const string&, std::istream &is, SpecialActions &actions) {
+ Color color = ColorSpecialHandler::readColor(is);
+ unsigned pageno = actions.getCurrentPageNumber();
+ if (_pageColors.empty() || _pageColors.back().second != color) {
+ if (!_pageColors.empty() && _pageColors.back().first == pageno)
+ _pageColors.back().second = color;
+ else
+ _pageColors.emplace_back(PageColor(pageno, color));
+ }
+}
+
+
+bool BgColorSpecialHandler::process (const string&, istream&, SpecialActions&) {
+ return true;
+}
+
+
+void BgColorSpecialHandler::dviBeginPage (unsigned pageno, SpecialActions &actions) {
+ // Ensure that the background color of the preceeding page is set as the
+ // default background color of the current page because this special affects
+ // the current and all subsequent pages until the next change.
+ // See the documentation of the color package, section 3.5.
+ if (_pageColors.empty())
+ return;
+ // find number of page with bg color change not lower than the current one
+ auto it = lower_bound(_pageColors.begin(), _pageColors.end(), PageColor(pageno, Color::BLACK));
+ if (it != _pageColors.end() && it->first == pageno)
+ actions.setBgColor(it->second);
+ else if (it != _pageColors.begin())
+ actions.setBgColor((--it)->second);
+}
+
+
+vector<const char*> BgColorSpecialHandler::prefixes() const {
+ vector<const char*> pfx {"background"};
+ return pfx;
+}
diff --git a/dviware/dvisvgm/src/BgColorSpecialHandler.hpp b/dviware/dvisvgm/src/BgColorSpecialHandler.hpp
new file mode 100644
index 0000000000..e1061efc8c
--- /dev/null
+++ b/dviware/dvisvgm/src/BgColorSpecialHandler.hpp
@@ -0,0 +1,45 @@
+/*************************************************************************
+** BgColorSpecialHandler.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef BGCOLORSPECIALHANDLER_HPP
+#define BGCOLORSPECIALHANDLER_HPP
+
+#include <vector>
+#include "Color.hpp"
+#include "SpecialHandler.hpp"
+
+
+class BgColorSpecialHandler : public SpecialHandler {
+ public:
+ void preprocess (const std::string &prefix, std::istream &is, SpecialActions &actions) override;
+ bool process (const std::string &prefix, std::istream &is, SpecialActions &actions) override;
+ const char* info () const override {return "background color special";}
+ const char* name () const override {return "bgcolor";}
+ std::vector<const char*> prefixes() const override;
+
+ protected:
+ void dviBeginPage (unsigned pageno, SpecialActions &actions) override;
+
+ private:
+ using PageColor = std::pair<unsigned,Color>; // page number and color
+ std::vector<PageColor> _pageColors;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/Bitmap.cpp b/dviware/dvisvgm/src/Bitmap.cpp
new file mode 100644
index 0000000000..e39ad79c47
--- /dev/null
+++ b/dviware/dvisvgm/src/Bitmap.cpp
@@ -0,0 +1,161 @@
+/*************************************************************************
+** Bitmap.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <cstdlib>
+#include <limits>
+#include "Bitmap.hpp"
+
+using namespace std;
+
+Bitmap::Bitmap () : _rows(0), _cols(0), _xshift(0), _yshift(0), _bpr(0), _bytes(0)
+{
+}
+
+
+/** Constructs a Bitmap */
+Bitmap::Bitmap (int minx, int maxx, int miny , int maxy) {
+ resize(minx, maxx, miny, maxy);
+}
+
+
+/** Resizes the bitmap and clears all pixels.
+ * @param[in] minx index of leftmost pixel column
+ * @param[in] maxx index of rightmost pixel column
+ * @param[in] miny index of bottom row
+ * @param[in] maxy index of top row */
+void Bitmap::resize (int minx, int maxx, int miny , int maxy) {
+ _rows = abs(maxy-miny)+1;
+ _cols = abs(maxx-minx)+1;
+ _xshift = minx;
+ _yshift = miny;
+ _bpr = _cols/8 + (_cols % 8 ? 1 : 0); // bytes per row
+ _bytes.resize(_rows*_bpr);
+ for (uint8_t &byte : _bytes)
+ byte = 0;
+}
+
+
+/** Sets n pixels of row r to 1 starting at pixel c.
+ * @param[in] row number of row
+ * @param[in] col number of column (pixel)
+ * @param[in] n number of bits to be set */
+void Bitmap::setBits (int row, int col, int n) {
+ row -= _yshift;
+ col -= _xshift;
+ uint8_t *byte = &_bytes[row*_bpr + col/8];
+ if (byte < &_bytes[0])
+ return;
+ const uint8_t *maxptr = &_bytes[0]+_bytes.size()-1;
+ while (n > 0 && byte <= maxptr) {
+ int b = 7 - col%8; // number of leftmost bit in current byte to be set
+ int m = min(n, b+1); // number of bits to be set in current byte
+ int bitseq = (1 << m)-1; // sequence of n set bits (bits 0..n-1 are set)
+ bitseq <<= b-m+1; // move bit sequence so that bit b is the leftmost set bit
+ *byte |= uint8_t(bitseq); // apply bit sequence to current byte
+ byte++;
+ n -= m;
+ col += m;
+ }
+}
+
+
+void Bitmap::forAllPixels (Callback &data) const {
+ for (int row=0; row < _rows ; row++) {
+ for (int col=0; col < _bpr; col++) {
+ uint8_t byte = _bytes[row*_bpr+col];
+ int x;
+ for (int b=7; (b >= 0) && ((x = 8*col+(7-b)) < _cols); b--)
+ data.pixel(x, row, bool(byte & (1 << b)), *this);
+ }
+ }
+ data.finish();
+}
+
+
+class BBoxCallback : public Bitmap::Callback
+{
+ public:
+ BBoxCallback () : _changed(false), _minx(numeric_limits<int>::max()), _miny(_minx), _maxx(0), _maxy(0) {}
+ int minx () const {return _minx;}
+ int miny () const {return _miny;}
+ int maxx () const {return _maxx;}
+ int maxy () const {return _maxy;}
+ bool empty () const {return !_changed;}
+
+ void pixel (int x, int y, bool set, const Bitmap&) override {
+ if (set) {
+ _minx = min(_minx, x);
+ _miny = min(_miny, y);
+ _maxx = max(_maxx, x);
+ _maxy = max(_maxy, y);
+ _changed = true;
+ }
+ }
+
+ void finish () override {
+ if (empty())
+ _minx = _miny = 0;
+ }
+
+ private:
+ bool _changed;
+ int _minx, _miny, _maxx, _maxy;
+};
+
+
+/** Computes the bounding box that spans all set pixels. */
+bool Bitmap::getBBox (int &minx, int &miny, int &maxx, int &maxy) const {
+ BBoxCallback bboxCallback;
+ forAllPixels(bboxCallback);
+ minx = bboxCallback.minx();
+ miny = bboxCallback.miny();
+ maxx = bboxCallback.maxx();
+ maxy = bboxCallback.maxy();
+ return !bboxCallback.empty();
+}
+
+
+/** Computes width and height of the bounding box that spans all set pixels. */
+void Bitmap::getExtent (int &w, int &h) const {
+ int minx, miny, maxx, maxy;
+ if (getBBox(minx, miny, maxx, maxy)) {
+ w = maxx-minx+1;
+ h = maxy-miny+1;
+ }
+ else
+ w = h = 0;
+}
+
+
+#if 0
+ostream& Bitmap::write (ostream &os) const {
+ for (int r=_rows-1; r >= 0 ; r--) {
+ for (int c=0; c < _bpr; c++) {
+ uint8_t byte = _bytes[r*_bpr+c];
+ for (int b=128; b; b>>=1)
+ os << (byte & b ? '*' : '-');
+ os << ' ';
+ }
+ os << endl;
+ }
+ return os;
+}
+#endif
diff --git a/dviware/dvisvgm/src/Bitmap.hpp b/dviware/dvisvgm/src/Bitmap.hpp
new file mode 100644
index 0000000000..fe9f31f053
--- /dev/null
+++ b/dviware/dvisvgm/src/Bitmap.hpp
@@ -0,0 +1,111 @@
+/*************************************************************************
+** Bitmap.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef BITMAP_HPP
+#define BITMAP_HPP
+
+#include <ostream>
+#include <vector>
+
+
+class Bitmap
+{
+ public:
+ struct Callback {
+ virtual ~Callback() =default;
+ virtual void pixel (int x, int y, bool set, Bitmap &bm) {}
+ virtual void pixel (int x, int y, bool set, const Bitmap &bm) {}
+ virtual void finish () {}
+ };
+
+ public:
+ Bitmap ();
+ Bitmap (int minx, int maxx, int miny , int maxy);
+ void resize (int minx, int maxx, int miny , int maxy);
+ void setBits(int row, int col, int n);
+ const uint8_t* rowPtr (int row) const {return &_bytes[row*_bpr];}
+ int height () const {return _rows;}
+ int width () const {return _cols;}
+ int xshift () const {return _xshift;}
+ int yshift () const {return _yshift;}
+ int bytesPerRow () const {return _bpr;}
+ bool empty () const {return (!_rows && !_cols) || _bytes.empty();}
+ bool getBBox (int &minx, int &miny, int &maxx, int &maxy) const;
+ void getExtent (int &w, int &h) const;
+ void forAllPixels (Callback &callback) const;
+
+ template <typename T>
+ int copy (std::vector<T> &target, bool vflip=false) const;
+
+// template <typename T>
+// void write (std::ostream &os, const std::vector<T> &v) const;
+
+// std::ostream& write (std::ostream &os) const;
+
+ private:
+ int _rows, _cols; ///< number of rows, columns
+ int _xshift, _yshift; ///< horizontal/vertical shift
+ int _bpr; ///< number of bytes per row
+ std::vector<uint8_t> _bytes;
+};
+
+
+/** Copies the bitmap to a new target area and reorganizes the bits.
+ * @tparam T component type of target vector
+ * @param[out] target points to first T of new bitmap (must be deleted after usage)
+ * @param[in] vflip true if the new bitmap should be flipped vertically
+ * @return number of Ts per row */
+template <typename T>
+int Bitmap::copy (std::vector<T> &target, bool vflip) const {
+ const int s = sizeof(T);
+ const int tpr = _bpr/s + (_bpr%s ? 1 : 0); // number of Ts per row
+ target.resize(_rows*tpr);
+ for (int r=0; r < _rows; r++) {
+ int targetrow = vflip ? _rows-r-1 : r;
+ for (int b=0; b < _bpr; b++) {
+ T &t = target[targetrow*tpr + b/s];
+ T chunk = (T)_bytes[r*_bpr+b] << (8*(s-1-b%s));
+ if (b % s == 0)
+ t = chunk;
+ else
+ t |= chunk;
+ }
+ }
+ return tpr;
+}
+
+
+/*
+template <typename T>
+void Bitmap::write (std::ostream &os, const std::vector<T> &v) const {
+ const int s = sizeof(T);
+ const int tpr = _bpr/s + (_bpr%s ? 1 : 0); // number of Ts per row
+ for (int r=_rows-1; r >= 0; r--) {
+ for (int t=0; t < tpr; t++) {
+ for (T b=(T)1<<(8*s-1); b; b>>=1)
+ os << ((v[r*tpr+t] & b) ? '*' : '-');
+ os << ' ';
+ }
+ os << std::endl;
+ }
+}*/
+
+
+#endif
diff --git a/dviware/dvisvgm/src/BoundingBox.cpp b/dviware/dvisvgm/src/BoundingBox.cpp
new file mode 100644
index 0000000000..a076b52363
--- /dev/null
+++ b/dviware/dvisvgm/src/BoundingBox.cpp
@@ -0,0 +1,274 @@
+/*************************************************************************
+** BoundingBox.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <sstream>
+#include "BoundingBox.hpp"
+#include "Matrix.hpp"
+#include "utility.hpp"
+#include "XMLNode.hpp"
+#include "XMLString.hpp"
+
+using namespace std;
+
+
+BoundingBox::BoundingBox ()
+ : _ulx(0), _uly(0), _lrx(0), _lry(0), _valid(false), _locked(false)
+{
+}
+
+
+BoundingBox::BoundingBox (double ulxx, double ulyy, double lrxx, double lryy)
+ : _ulx(min(ulxx,lrxx)), _uly(min(ulyy,lryy)),
+ _lrx(max(ulxx,lrxx)), _lry(max(ulyy,lryy)),
+ _valid(true), _locked(false)
+{
+}
+
+
+BoundingBox::BoundingBox (const DPair &p1, const DPair &p2)
+ : _ulx(min(p1.x(), p2.x())), _uly(min(p1.y(), p2.y())),
+ _lrx(max(p1.x(), p2.x())), _lry(max(p1.y(), p2.y())),
+ _valid(true), _locked(false)
+{
+}
+
+
+BoundingBox::BoundingBox (const Length &ulxx, const Length &ulyy, const Length &lrxx, const Length &lryy)
+ : _ulx(min(ulxx.bp(),lrxx.bp())), _uly(min(ulyy.bp(),lryy.bp())),
+ _lrx(max(ulxx.bp(),lrxx.bp())), _lry(max(ulyy.bp(),lryy.bp())),
+ _valid(true), _locked(false)
+{
+}
+
+
+BoundingBox::BoundingBox (const string &boxstr)
+ : _ulx(0), _uly(0), _lrx(0), _lry(0), _valid(false), _locked(false)
+{
+ set(boxstr);
+}
+
+
+/** Extracts a sequence of length values from a string like "5cm, 2.4in, 0pt".
+ * @param[in] boxstr whitespace and/or comma separated string of lengths.
+ * @param[out] the extracted lengths */
+vector<Length> BoundingBox::extractLengths (string boxstr) {
+ vector<Length> lengths;
+ boxstr = util::replace(boxstr, ",", " ");
+ boxstr = util::normalize_space(boxstr);
+ vector<string> lengthStrings = util::split(boxstr, " ");
+ for (const string &lenstr : lengthStrings) {
+ if (!lenstr.empty())
+ lengths.emplace_back(Length(lenstr));
+ }
+ return lengths;
+}
+
+
+/** Sets or modifies the bounding box. If 'boxstr' consists of 4 length values,
+ * they denote the absolute position of two diagonal corners of the box. In case
+ * of a single length value l the current box is enlarged by adding (-l,-l) the upper
+ * left and (l,l) to the lower right corner.
+ * @param[in] boxstr whitespace and/or comma separated string of lengths. */
+void BoundingBox::set (const string &boxstr) {
+ vector<Length> coord = extractLengths(boxstr);
+ set(coord);
+}
+
+
+void BoundingBox::set (const std::vector<Length> &coord) {
+ switch (coord.size()) {
+ case 1:
+ _ulx -= coord[0].bp();
+ _uly -= coord[0].bp();
+ _lrx += coord[0].bp();
+ _lry += coord[0].bp();
+ break;
+ case 2:
+ _ulx -= coord[0].bp();
+ _uly -= coord[1].bp();
+ _lrx += coord[0].bp();
+ _lry += coord[1].bp();
+ break;
+ case 4:
+ _ulx = min(coord[0].bp(), coord[2].bp());
+ _uly = min(coord[1].bp(), coord[3].bp());
+ _lrx = max(coord[0].bp(), coord[2].bp());
+ _lry = max(coord[1].bp(), coord[3].bp());
+ break;
+ default:
+ throw BoundingBoxException("1, 2 or 4 length parameters expected");
+ }
+ _valid = true;
+}
+
+
+/** Enlarges the box so that point (x,y) is enclosed. */
+void BoundingBox::embed (double x, double y) {
+ if (!_locked) {
+ if (_valid) {
+ if (x < _ulx)
+ _ulx = x;
+ else if (x > _lrx)
+ _lrx = x;
+ if (y < _uly)
+ _uly = y;
+ else if (y > _lry)
+ _lry = y;
+ }
+ else {
+ _ulx = _lrx = x;
+ _uly = _lry = y;
+ _valid = true;
+ }
+ }
+}
+
+
+/** Enlarges the box so that the given bounding box is enclosed. */
+void BoundingBox::embed (const BoundingBox &bbox) {
+ if (!_locked && bbox._valid) {
+ if (_valid) {
+ embed(bbox._ulx, bbox._uly);
+ embed(bbox._lrx, bbox._lry);
+ }
+ else {
+ _ulx = bbox._ulx;
+ _uly = bbox._uly;
+ _lrx = bbox._lrx;
+ _lry = bbox._lry;
+ _valid = true;
+ }
+ }
+}
+
+
+/** Embeds a virtual circle into the box and enlarges it accordingly.
+ * @param[in] c center of the circle
+ * @param[in] r radius of the circle */
+void BoundingBox::embed (const DPair &c, double r) {
+ embed(BoundingBox(c.x()-r, c.y()-r, c.x()+r, c.y()+r));
+}
+
+
+/** Expands the box in all four directions by a given value. */
+void BoundingBox::expand (double m) {
+ if (!_locked) {
+ _ulx -= m;
+ _uly -= m;
+ _lrx += m;
+ _lry += m;
+ }
+}
+
+
+/** Intersects the current box with bbox and applies the result to *this.
+ * If both boxes are disjoint, *this is not altered.
+ * @param[in] bbox box to intersect with
+ * @return false if *this is locked or both boxes are disjoint */
+bool BoundingBox::intersect (const BoundingBox &bbox) {
+ // check if the two boxes are disjoint
+ if (_locked || _lrx < bbox._ulx || _lry < bbox._uly || _ulx > bbox._lrx || _uly > bbox._lry)
+ return false;
+ // not disjoint: compute the intersection
+ _ulx = max(_ulx, bbox._ulx);
+ _uly = max(_uly, bbox._uly);
+ _lrx = min(_lrx, bbox._lrx);
+ _lry = min(_lry, bbox._lry);
+ return true;
+}
+
+
+void BoundingBox::operator += (const BoundingBox &bbox) {
+ if (!_locked) {
+ _ulx += bbox._ulx;
+ _uly += bbox._uly;
+ _lrx += bbox._lrx;
+ _lry += bbox._lry;
+ }
+}
+
+
+static inline bool almost_equal (double v1, double v2) {
+ return fabs(v1-v2) < 1e-10;
+}
+
+
+bool BoundingBox::operator == (const BoundingBox &bbox) const {
+ return _valid && bbox._valid
+ && almost_equal(_ulx, bbox._ulx)
+ && almost_equal(_uly, bbox._uly)
+ && almost_equal(_lrx, bbox._lrx)
+ && almost_equal(_lry, bbox._lry);
+}
+
+
+void BoundingBox::scale (double sx, double sy) {
+ if (!_locked) {
+ _ulx *= sx;
+ _lrx *= sx;
+ if (sx < 0) swap(_ulx, _lrx);
+ _uly *= sy;
+ _lry *= sy;
+ if (sy < 0) swap(_uly, _lry);
+ }
+}
+
+
+void BoundingBox::transform (const Matrix &tm) {
+ if (!_locked) {
+ DPair ul = tm * DPair(_ulx, _uly);
+ DPair lr = tm * DPair(_lrx, _lry);
+ DPair ll = tm * DPair(_ulx, _lry);
+ DPair ur = tm * DPair(_lrx, _uly);
+ _ulx = min(min(ul.x(), lr.x()), min(ur.x(), ll.x()));
+ _uly = min(min(ul.y(), lr.y()), min(ur.y(), ll.y()));
+ _lrx = max(max(ul.x(), lr.x()), max(ur.x(), ll.x()));
+ _lry = max(max(ul.y(), lr.y()), max(ur.y(), ll.y()));
+ }
+}
+
+
+string BoundingBox::toSVGViewBox () const {
+ ostringstream oss;
+ oss << XMLString(_ulx) << ' ' << XMLString(_uly) << ' ' << XMLString(width()) << ' ' << XMLString(height());
+ return oss.str();
+}
+
+
+ostream& BoundingBox::write (ostream &os) const {
+ os << '(' << _ulx << ", " << _uly << ", " << _lrx << ", " << _lry << ')';
+ if (!_valid)
+ os << " (invalid)";
+ else if (_locked)
+ os << " (locked)";
+ return os;
+}
+
+
+unique_ptr<XMLElement> BoundingBox::createSVGRect () const {
+ auto rect = util::make_unique<XMLElement>("rect");
+ rect->addAttribute("x", minX());
+ rect->addAttribute("y", minY());
+ rect->addAttribute("width", width());
+ rect->addAttribute("height", height());
+ rect->addAttribute("fill", "none");
+ return rect;
+}
diff --git a/dviware/dvisvgm/src/BoundingBox.hpp b/dviware/dvisvgm/src/BoundingBox.hpp
new file mode 100644
index 0000000000..8848f60f28
--- /dev/null
+++ b/dviware/dvisvgm/src/BoundingBox.hpp
@@ -0,0 +1,95 @@
+/*************************************************************************
+** BoundingBox.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef BOUNDINGBOX_HPP
+#define BOUNDINGBOX_HPP
+
+#include <memory>
+#include <ostream>
+#include <string>
+#include <vector>
+#include "Length.hpp"
+#include "MessageException.hpp"
+#include "Pair.hpp"
+
+
+class Matrix;
+class XMLElement;
+
+
+struct BoundingBoxException : MessageException {
+ explicit BoundingBoxException (const std::string &msg) : MessageException(msg) {}
+};
+
+
+class BoundingBox {
+ public:
+ BoundingBox ();
+ BoundingBox (double ulxx, double ulyy, double lrxx, double lryy);
+ BoundingBox (const DPair &p1, const DPair &p2);
+ BoundingBox (const Length &ulxx, const Length &ulyy, const Length &lrxx, const Length &lryy);
+ explicit BoundingBox (const std::string &boxstr);
+ void set (const std::string &boxstr);
+ void set (const std::vector<Length> &lengths);
+ void embed (double x, double y);
+ void embed (const BoundingBox &bb);
+ void embed (const DPair &p) {embed(p.x(), p.y());}
+ void embed (const DPair &c, double r);
+
+ static std::vector<Length> extractLengths (std::string boxstr);
+
+ template <typename T>
+ void embed (const Pair<T> &p) {embed(p.x(), p.y());}
+
+ void expand (double m);
+ bool intersect (const BoundingBox &bbox);
+ double minX () const {return _ulx;}
+ double minY () const {return _uly;}
+ double maxX () const {return _lrx;}
+ double maxY () const {return _lry;}
+ double width () const {return _lrx-_ulx;}
+ double height () const {return _lry-_uly;}
+ bool valid() const {return _valid;}
+ bool locked() const {return _locked;}
+ void lock () {_locked = true;}
+ void unlock () {_locked = false;}
+ void invalidate () {_valid = false;}
+ void operator += (const BoundingBox &bbox);
+ bool operator == (const BoundingBox &bbox) const;
+ bool operator != (const BoundingBox &bbox) const {return !(*this == bbox);}
+ void scale (double sx, double sy);
+ void transform (const Matrix &tm);
+ std::string toSVGViewBox () const;
+ std::ostream& write (std::ostream &os) const;
+ std::unique_ptr<XMLElement> createSVGRect () const;
+
+ private:
+ double _ulx, _uly; ///< coordinates of upper left vertex (in PS point units)
+ double _lrx, _lry; ///< coordinates of lower right vertex (in PS point units)
+ bool _valid : 1; ///< true if the box coordinates are properly set
+ bool _locked : 1; ///< if true, the box data is read-only
+};
+
+
+inline std::ostream& operator << (std::ostream &os, const BoundingBox &bbox) {
+ return bbox.write(os);
+}
+
+#endif
diff --git a/dviware/dvisvgm/src/CLCommandLine.cpp b/dviware/dvisvgm/src/CLCommandLine.cpp
new file mode 100644
index 0000000000..2b0c1b5359
--- /dev/null
+++ b/dviware/dvisvgm/src/CLCommandLine.cpp
@@ -0,0 +1,242 @@
+/*************************************************************************
+** CLCommandLine.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <cstring>
+#include <unordered_map>
+#include "CLCommandLine.hpp"
+#include "version.hpp"
+
+using namespace CL;
+using namespace std;
+
+
+CommandLine::CommandLine (const char *summary, const char *usage, const char *copyright)
+ : _summary(summary), _usage(usage), _copyright(copyright)
+{
+}
+
+
+void CommandLine::parse (int argc, char **argv) {
+ bool filesOnly = false;
+ for (int i=1; i < argc; i++) {
+ istringstream iss(argv[i]);
+ if (filesOnly || iss.peek() != '-')
+ _files.emplace_back(argv[i]);
+ else {
+ iss.get(); // skip dash
+ if (iss.peek() < 0)
+ _singleDashParsed = true;
+ else if (iss.peek() != '-')
+ parseShortOption(iss, argc, argv, i);
+ else {
+ iss.get(); // skip dash
+ if (iss.peek() == EOF) // "--" only, no following option name?
+ filesOnly = true; // treat all following options as filenames
+ else
+ parseLongOption(iss);
+ }
+ }
+ }
+}
+
+
+static void error (const Option &option, bool shortname, const string &msg) {
+ ostringstream oss;
+ oss << "option ";
+ if (shortname)
+ oss << '-' << option.shortName();
+ else
+ oss << "--" << option.longName();
+ oss << ": " << msg;
+ throw CommandLineException(oss.str());
+}
+
+
+static void type_error (const Option &option, bool shortname) {
+ ostringstream oss;
+ switch (option.argMode()) {
+ case Option::ArgMode::NONE:
+ error(option, shortname, "no argument expected");
+ break;
+ case Option::ArgMode::OPTIONAL:
+ oss << "optional argument ";
+ if (!option.argName().empty())
+ oss << "'" << option.argName() << "' ";
+ oss << "must be of "+option.typeString()+" type";
+ error(option, shortname, oss.str());
+ break;
+ default:
+ oss << option.typeString()+" argument ";
+ if (!option.argName().empty())
+ oss << "'" << option.argName() << "' ";
+ oss << "expected";
+ error(option, shortname, oss.str());
+ }
+}
+
+
+void CommandLine::parseShortOption (istringstream &iss, int argc, char **argv, int &argn) {
+ bool combined = false;
+ do {
+ char shortname = static_cast<char>(iss.get());
+ if (!isalnum(shortname))
+ throw CommandLineException(string("syntax error: -")+shortname);
+ if (Option *option = lookupOption(shortname)) {
+ if (!combined || option->argMode() == Option::ArgMode::NONE) {
+ if (option->argMode() == Option::ArgMode::REQUIRED && strlen(argv[argn]) == 2) { // required argument separated by whitespace?
+ if (argn+1 < argc && argv[argn+1][0] != '-') {
+ iss.clear(); // reset error flags
+ iss.str(argv[++argn]); // continue parsing with next command-line field
+ }
+ }
+ if (!option->parse(iss, false))
+ type_error(*option, true);
+ if (option->argMode() == Option::ArgMode::NONE)
+ combined = true;
+ }
+ else
+ throw CommandLineException(string("option -")+ shortname + " must be given separately");
+ }
+ else
+ throw CommandLineException(string("unknown option -")+shortname);
+ }
+ while (combined && !iss.eof());
+}
+
+
+void CommandLine::parseLongOption (istream &is) {
+ string optname;
+ while (isalnum(is.peek()) || (!optname.empty() && is.peek() == '-'))
+ optname += char(is.get());
+ if (optname.empty())
+ throw CommandLineException("unexpected character '" + string(1, is.peek()) + "' after '--'");
+ vector<Option*> options = lookupOption(optname);
+ if (options.empty())
+ throw CommandLineException("unknown option --"+optname);
+ else if (options.size() == 1) {
+ if (!options[0]->parse(is, true))
+ type_error(*options[0], false);
+ }
+ else { // is partially given option ambiguous?
+ sort(options.begin(), options.end(), [](const Option *opt1, const Option *opt2) {
+ return opt1->longName() < opt2->longName();
+ });
+ string msg = "option --" + optname + " is ambiguous (";
+ for (const Option *opt : options) {
+ if (opt != options[0])
+ msg += ", ";
+ msg += opt->longName();
+ }
+ msg += ')';
+ throw CommandLineException(msg);
+ }
+}
+
+
+/** Returns all options that match the given long name. */
+vector<Option*> CommandLine::lookupOption (const string &optname) const {
+ vector<Option*> matches;
+ int len = optname.length();
+ for (OptSectPair optsect : options()) {
+ if (optsect.first->longName() == optname) { // exact match?
+ matches.clear();
+ matches.push_back(optsect.first);
+ break;
+ }
+ if (optsect.first->longName().substr(0, len) == optname) // partial match?
+ matches.push_back(optsect.first);
+ }
+ return matches;
+}
+
+
+/** Returns the option that match the given short name. */
+Option* CommandLine::lookupOption (char optchar) const {
+ auto it = find_if(options().begin(), options().end(), [=](const OptSectPair &optsect) {
+ return optsect.first->shortName() == optchar;
+ });
+ return (it != options().end()) ? it->first : nullptr;
+}
+
+
+/** Prints help text including summary of options.
+ * @param[in] os output stream the help text is printed to
+ * @param[in] mode output mode (0=section, 1=sorted by short options, 2=sorted by long options) */
+void CommandLine::help (ostream &os, int mode) const {
+ os << PROGRAM_NAME << ' '<< PROGRAM_VERSION << "\n\n";
+ os << _summary << "\n\n";
+ // print usage info
+ string usage = _usage;
+ int count=0;
+ while (!usage.empty()) {
+ size_t pos = usage.find('\n');
+ os << (count++ == 0 ? "Usage: " : " ") << PROGRAM_NAME << ' ' << usage.substr(0, pos) << '\n';
+ if (pos != string::npos)
+ usage = usage.substr(pos+1);
+ else
+ usage.clear();
+ }
+ if (mode > 0)
+ os << '\n';
+
+ // compute width of first column of help output
+ unordered_map<Option*, pair<string,string>> linecols;
+ size_t col1width=0;
+ for (const OptSectPair &ospair : options()) {
+ size_t pos;
+ string line = ospair.first->helpline();
+ if ((pos = line.find('\t')) != string::npos) {
+ linecols.emplace(ospair.first, pair<string,string>(line.substr(0, pos), line.substr(pos+1)));
+ col1width = max(col1width, pos);
+ }
+ }
+ if (mode > 0) {
+ bool (*isless)(const OptSectPair&, const OptSectPair&) = [](const OptSectPair &p1, const OptSectPair &p2) {
+ return p1.first->longName() < p2.first->longName();
+ };
+ if (mode == 1) {
+ isless = [](const OptSectPair &p1, const OptSectPair &p2) {
+ char c1 = p1.first->shortName(), c2 = p2.first->shortName();
+ if (c1 == c2)
+ return p1.first->longName() < p2.first->longName();
+ if (tolower(c1) == tolower(c2))
+ return c1 > c2; // lower case before upper case letters
+ return tolower(c1) < tolower(c2);
+ };
+ }
+ sort(options().begin(), options().end(), isless);
+ }
+
+ // print summary of options
+ col1width += 2;
+ int sectno=-1;
+ for (const OptSectPair &ospair : options()) {
+ if (mode == 0 && ospair.second != sectno) { // first option in current section?
+ sectno = ospair.second;
+ if (section(sectno))
+ os << '\n' << section(sectno) << ":\n";
+ }
+ Option *opt = ospair.first;
+ os << " " << setw(col1width) << left << linecols[opt].first;
+ os << setw(0) << linecols[opt].second << '\n';
+ }
+ os << '\n' << _copyright << '\n';
+}
diff --git a/dviware/dvisvgm/src/CLCommandLine.hpp b/dviware/dvisvgm/src/CLCommandLine.hpp
new file mode 100644
index 0000000000..a80d9b87d7
--- /dev/null
+++ b/dviware/dvisvgm/src/CLCommandLine.hpp
@@ -0,0 +1,69 @@
+/*************************************************************************
+** CLCommandLine.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef CL_COMMANDLINE_HPP
+#define CL_COMMANDLINE_HPP
+
+#include <array>
+#include <ostream>
+#include <string>
+#include <sstream>
+#include <vector>
+#include "CLOption.hpp"
+#include "MessageException.hpp"
+
+namespace CL {
+
+class CommandLine {
+ public:
+ CommandLine (const char *summary, const char *usage, const char *copyright);
+ virtual ~CommandLine () =default;
+ void parse (int argc, char **argv);
+ void help (std::ostream &os, int mode=0) const;
+ void addFilename (const std::string &fname) {_files.emplace_back(fname);}
+ bool singleDashGiven () const {return _singleDashParsed;}
+ const std::vector<std::string>& filenames () const {return _files;}
+
+ protected:
+ using OptSectPair = std::pair<Option*,int>;
+ void parseShortOption (std::istringstream &is, int argc, char **argv, int &argn);
+ void parseLongOption (std::istream &is);
+ virtual std::vector<OptSectPair>& options () const =0;
+ virtual const char* section (size_t n) const {return nullptr;}
+ Option* lookupOption (char optchar) const;
+ std::vector<Option*> lookupOption (const std::string &optname) const;
+
+ private:
+ const char *_summary;
+ const char *_usage;
+ const char *_copyright;
+ bool _singleDashParsed=false; ///< true if a single '-' w/o a following char was parsed
+ std::vector<std::string> _files;
+};
+
+
+struct CommandLineException : public MessageException {
+ explicit CommandLineException (const std::string &msg) : MessageException(msg) {}
+};
+
+} // namespace CL
+
+#endif
+
diff --git a/dviware/dvisvgm/src/CLOption.hpp b/dviware/dvisvgm/src/CLOption.hpp
new file mode 100644
index 0000000000..54dff67025
--- /dev/null
+++ b/dviware/dvisvgm/src/CLOption.hpp
@@ -0,0 +1,198 @@
+/*************************************************************************
+** CLOption.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef CL_OPTION_HPP
+#define CL_OPTION_HPP
+
+#include <iomanip>
+#include <sstream>
+#include <string>
+
+namespace CL {
+
+class Option
+{
+ friend class CommandLine;
+ public:
+ enum class ArgMode {NONE, OPTIONAL, REQUIRED};
+
+ public:
+ Option (const char *longname, char shortname, const char *summary)
+ : _shortName(shortname), _longName(longname), _summary(summary), _given(false) {}
+
+ virtual ~Option () = default;
+ virtual std::string typeString () const {return "";}
+ virtual ArgMode argMode () const {return ArgMode::NONE;}
+ virtual bool given () const {return _given;}
+ virtual char shortName () const {return _shortName;}
+ virtual std::string longName () const {return _longName;}
+ virtual std::string summary () const {return _summary;}
+ virtual std::string argName () const {return "";}
+ virtual std::string valueString () const {return "";}
+
+ virtual std::string helpline () const {
+ std::string line;
+ if (_shortName != '\0')
+ line = std::string("-") + _shortName + ", ";
+ else
+ line = " ";
+ line += "--" + std::string(_longName);
+ if (!argName().empty()) {
+ if (argMode() == ArgMode::OPTIONAL)
+ line += '[';
+ line += '=';
+ line += argName();
+ if (argMode() == ArgMode::OPTIONAL)
+ line += ']';
+ }
+ if (_summary) {
+ line += '\t';
+ line += _summary;
+ }
+ if (argMode() != ArgMode::NONE && !valueString().empty())
+ line += std::string(" [") + valueString() + "]";
+ return line;
+ }
+
+ protected:
+ virtual bool parse (std::istream &is, bool longopt) {
+ if (is.eof())
+ return given(argMode() != ArgMode::REQUIRED);
+ if (argMode() == ArgMode::OPTIONAL && is.peek() == ' ')
+ return given(false);
+ if (longopt && is.get() != '=')
+ return given(false);
+ return given(parseValue(is));
+ }
+
+ virtual bool parseValue (std::istream &is) {
+ return is.peek() == EOF || argMode() == ArgMode::NONE;
+ }
+
+ bool given (bool val) {return _given = val;}
+
+ private:
+ char _shortName;
+ const char *_longName;
+ const char*_summary;
+ bool _given;
+};
+
+
+template <typename T>
+constexpr const char* typeString () {return "unknown";}
+
+template<> constexpr const char* typeString<bool> () {return "boolean";}
+template<> constexpr const char* typeString<int> () {return "integer";}
+template<> constexpr const char* typeString<unsigned> () {return "non-negative integer";}
+template<> constexpr const char* typeString<double> () {return "floating point";}
+template<> constexpr const char* typeString<std::string> () {return "string";}
+
+template <typename T>
+inline T parseValue (std::istream &is) {
+ T value;
+ is >> value;
+ return value;
+}
+
+template<>
+inline std::string parseValue (std::istream &is) {
+ is >> std::ws;
+ std::string str;
+ std::getline(is, str);
+ return str;
+}
+
+
+template <typename T, Option::ArgMode mode>
+class TypedOption : public Option
+{
+ public:
+ TypedOption (const char *longName, char shortName, const char *argName, T val, const char *summary)
+ : Option(longName, shortName, summary), _argName(argName), _value(val) {}
+
+ TypedOption (const char *longName, char shortName, const char *argName, const char *summary)
+ : Option(longName, shortName, summary), _argName(argName), _value() {}
+
+ T value () const {return _value;}
+ std::string typeString () const override {return CL::typeString<T>();}
+ std::string argName() const override {return _argName ? _argName : "";}
+
+ std::string valueString () const override {
+ std::ostringstream oss;
+ oss << _value;
+ return oss.str();
+ }
+
+ ArgMode argMode () const override {return mode;}
+
+ protected:
+ bool parseValue (std::istream &is) override {
+ T value = CL::parseValue<T>(is);
+ if (!is.fail())
+ _value = std::move(value);
+ return !is.fail() || (argMode() == ArgMode::OPTIONAL && is.eof());
+ }
+
+ private:
+ const char *_argName;
+ T _value;
+};
+
+
+template <Option::ArgMode mode>
+class TypedOption<bool, mode> : public Option
+{
+ public:
+ TypedOption (const char *longName, char shortName, const char *argName, bool val, const char *summary)
+ : Option(longName, shortName, summary), _argName(argName), _value(val) {}
+
+ TypedOption (const char *longName, char shortName, const char *argName, const char *summary)
+ : Option(longName, shortName, summary), _argName(argName), _value(false) {}
+
+ bool value () const {return _value;}
+ std::string valueString () const override {return _value ? "yes" : "no";}
+ ArgMode argMode () const override {return mode;}
+ std::string typeString () const override {return CL::typeString<bool>();}
+ std::string argName() const override {return _argName ? _argName : "";}
+
+ protected:
+ bool parseValue (std::istream &is) override {
+ std::string str;
+ is >> str;
+ if (is.fail())
+ return argMode() != ArgMode::REQUIRED;
+ if (str == "yes" || str == "y" || str == "true" || str == "1")
+ _value = true;
+ else if (str == "no" || str == "n" || str == "false" || str == "0")
+ _value = false;
+ else
+ return false;
+ return true;
+ }
+
+ private:
+ const char *_argName;
+ bool _value;
+};
+
+} // namespace CL
+
+#endif
diff --git a/dviware/dvisvgm/src/CMap.cpp b/dviware/dvisvgm/src/CMap.cpp
new file mode 100644
index 0000000000..fa92901eef
--- /dev/null
+++ b/dviware/dvisvgm/src/CMap.cpp
@@ -0,0 +1,71 @@
+/*************************************************************************
+** CMap.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <sstream>
+#include "CMap.hpp"
+#include "CMapManager.hpp"
+#include "FileFinder.hpp"
+
+using namespace std;
+
+
+const char* CMap::path () const {
+ return FileFinder::instance().lookup(name(), "cmap", false);
+}
+
+
+const FontEncoding* CMap::findCompatibleBaseFontMap (const PhysicalFont *font, CharMapID &charmapID) const {
+ return CMapManager::instance().findCompatibleBaseFontMap(font, this, charmapID);
+}
+
+//////////////////////////////////////////////////////////////////////
+
+/** Returns the RO (Registry-Ordering) string of the CMap. */
+string SegmentedCMap::getROString() const {
+ if (_registry.empty() || _ordering.empty())
+ return "";
+ return _registry + "-" + _ordering;
+}
+
+
+/** Returns the CID for a given character code. */
+uint32_t SegmentedCMap::cid (uint32_t c) const {
+ if (_cidranges.valueExists(c))
+ return _cidranges.valueAt(c);
+ if (_basemap)
+ return _basemap->cid(c);
+ return 0;
+}
+
+
+/** Returns the character code of a base font for a given CID. */
+uint32_t SegmentedCMap::bfcode (uint32_t cid) const {
+ if (_bfranges.valueExists(cid))
+ return _bfranges.valueAt(cid);
+ if (_basemap)
+ return _basemap->bfcode(cid);
+ return 0;
+}
+
+
+void SegmentedCMap::write (ostream &os) const {
+ _cidranges.write(os);
+}
diff --git a/dviware/dvisvgm/src/CMap.hpp b/dviware/dvisvgm/src/CMap.hpp
new file mode 100644
index 0000000000..0f0259e853
--- /dev/null
+++ b/dviware/dvisvgm/src/CMap.hpp
@@ -0,0 +1,108 @@
+/*************************************************************************
+** CMap.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef CMAP_HPP
+#define CMAP_HPP
+
+#include <algorithm>
+#include <ostream>
+#include <vector>
+#include "FontEncoding.hpp"
+#include "RangeMap.hpp"
+
+
+struct CMap : public NamedFontEncoding {
+ virtual bool vertical () const =0;
+ virtual bool mapsToCID () const =0;
+ virtual uint32_t cid (uint32_t c) const =0;
+ virtual uint32_t bfcode (uint32_t cid) const =0;
+ virtual std::string getROString () const =0;
+ const char* path () const override;
+ const FontEncoding* findCompatibleBaseFontMap (const PhysicalFont *font, CharMapID &charmapID) const override;
+ bool mapsToCharIndex () const override {return mapsToCID();}
+
+ Character decode (uint32_t c) const override {
+ if (mapsToCID())
+ return Character(Character::INDEX, cid(c));
+ return Character(Character::CHRCODE, bfcode(c));
+ }
+};
+
+
+struct IdentityCMap : public CMap {
+ uint32_t cid (uint32_t c) const override {return c;}
+ uint32_t bfcode (uint32_t cid) const override {return 0;}
+ std::string getROString () const override {return "Adobe-Identity";}
+ bool mapsToCID() const override {return true;}
+};
+
+
+struct IdentityHCMap : public IdentityCMap {
+ bool vertical () const override {return false;}
+ const char* name () const override {return "Identity-H";}
+};
+
+
+struct IdentityVCMap : public IdentityCMap {
+ bool vertical () const override {return true;}
+ const char* name () const override {return "Identity-V";}
+};
+
+
+struct UnicodeCMap : public CMap {
+ bool vertical () const override {return false;}
+ const char* name () const override {return "unicode";}
+ bool mapsToCID () const override {return false;}
+ const char* path () const override {return nullptr;}
+ uint32_t cid (uint32_t c) const override {return c;}
+ uint32_t bfcode (uint32_t cid) const override {return cid;}
+ std::string getROString () const override {return "";}
+};
+
+
+class SegmentedCMap : public CMap {
+ friend class CMapReader;
+
+ public:
+ explicit SegmentedCMap (std::string fname) : _filename(std::move(fname)) {}
+ const char* name () const override {return _filename.c_str();}
+ uint32_t cid (uint32_t c) const override;
+ uint32_t bfcode (uint32_t cid) const override;
+ void addCIDRange (uint32_t first, uint32_t last, uint32_t cid) {_cidranges.addRange(first, last, cid);}
+ void addBFRange (uint32_t first, uint32_t last, uint32_t chrcode) {_bfranges.addRange(first, last, chrcode);}
+ void write (std::ostream &os) const;
+ bool vertical () const override {return _vertical;}
+ bool mapsToCID () const override {return _mapsToCID;}
+ size_t numCIDRanges () const {return _cidranges.numRanges();}
+ size_t numBFRanges () const {return _bfranges.numRanges();}
+ std::string getROString () const override;
+
+ private:
+ std::string _filename;
+ std::string _registry;
+ std::string _ordering;
+ CMap *_basemap = nullptr;
+ bool _vertical = false;
+ bool _mapsToCID = true; // true: chrcode->CID, false: CID->charcode
+ RangeMap _cidranges;
+ RangeMap _bfranges;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/CMapManager.cpp b/dviware/dvisvgm/src/CMapManager.cpp
new file mode 100644
index 0000000000..24b5b69f24
--- /dev/null
+++ b/dviware/dvisvgm/src/CMapManager.cpp
@@ -0,0 +1,139 @@
+/*************************************************************************
+** CMapManager.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <array>
+#include <sstream>
+#include "CMap.hpp"
+#include "CMapManager.hpp"
+#include "CMapReader.hpp"
+#include "FileFinder.hpp"
+#include "Font.hpp"
+#include "Message.hpp"
+
+using namespace std;
+
+
+CMapManager& CMapManager::instance () {
+ static CMapManager cmm;
+ return cmm;
+}
+
+
+/** Loads a cmap and returns the corresponding object. */
+CMap* CMapManager::lookup (const string &name) {
+ auto it = _cmaps.find(name);
+ if (it != _cmaps.end())
+ return it->second.get();
+
+ if (_includedCMaps.find(name) != _includedCMaps.end()) {
+ _level = 0;
+ ostringstream oss;
+ oss << "circular reference of CMap " << name;
+ throw CMapReaderException(oss.str());
+ }
+
+ unique_ptr<CMap> cmap_ptr;
+ if (name == "Identity-H")
+ cmap_ptr = util::make_unique<IdentityHCMap>();
+ else if (name == "Identity-V")
+ cmap_ptr = util::make_unique<IdentityVCMap>();
+ else if (name == "unicode")
+ cmap_ptr = util::make_unique<UnicodeCMap>();
+ if (cmap_ptr) {
+ CMap *ret = cmap_ptr.get();
+ _cmaps[name] = std::move(cmap_ptr);
+ return ret;
+ }
+ // Load cmap data of file <name> and also process all cmaps referenced by operator "usecmap".
+ // This can lead to a sequence of further calls of lookup(). In order to prevent infinite loops
+ // due to (disallowed) circular cmap inclusions, we keep track of all cmaps processed during
+ // a sequence of inclusions.
+ _includedCMaps.insert(name); // save name of current cmap being processed
+ _level++; // increase nesting level
+ CMap *ret=nullptr;
+ try {
+ CMapReader reader;
+ if (!(cmap_ptr = reader.read(name))) {
+ _level = 1;
+ Message::wstream(true) << "CMap file '" << name << "' not found\n";
+ }
+ ret = cmap_ptr.get();
+ _cmaps[name] = std::move(cmap_ptr);
+ }
+ catch (const CMapReaderException &e) {
+ Message::estream(true) << "CMap file " << name << ": " << e.what() << "\n";
+ }
+ if (--_level == 0) // back again at initial nesting level?
+ _includedCMaps.clear(); // => names of included cmaps are no longer needed
+ return ret;
+}
+
+
+/** Looks for a base font CMap and a compatible encoding table in a given font. The CMap describe
+ * the mapping from CIDs to character codes where the latter are relative to the encoding table
+ * identified by charmapID.
+ * cmap:X->CID, bfmap:CID->Y, enctable:Y->CharCode
+ * @param[in] font look for available encoding tables in this font
+ * @param[in] cmap take the source registry-ordering pair from this CMap
+ * @param[out] charmapID ID of the compatible character map found in the given font
+ * @return base font CMap that maps from CIDs to character codes */
+const CMap* CMapManager::findCompatibleBaseFontMap (const PhysicalFont *font, const CMap *cmap, CharMapID &charmapID) {
+ if (!font || !cmap)
+ return nullptr;
+
+ struct CharMapIDToEncName {
+ CharMapID id;
+ const char *encname;
+ };
+ const array<CharMapIDToEncName, 10> encodings {{
+ {CharMapID::WIN_UCS4, "UCS4"},
+ {CharMapID::WIN_UCS2, "UCS2"},
+ {CharMapID::WIN_SHIFTJIS, "90ms-RKSJ"},
+ {CharMapID::WIN_PRC, "GBK-EUC"},
+ {CharMapID::WIN_BIG5, "ETen-B5"},
+ {CharMapID::WIN_WANSUNG, "KSCms-UHC"},
+ {CharMapID::MAC_JAPANESE, "90pv-RKSJ"},
+ {CharMapID::MAC_TRADCHINESE, "B5pc"},
+ {CharMapID::MAC_SIMPLCHINESE, "GBpc-EUC"},
+ {CharMapID::MAC_KOREAN, "KSCpc-EUC"}
+ }};
+
+ // get IDs of all available charmaps in font
+ vector<CharMapID> charmapIDs;
+ font->collectCharMapIDs(charmapIDs);
+
+
+ // try to find a compatible encoding CMap
+ const bool is_unicode_map = bool(dynamic_cast<const UnicodeCMap*>(cmap));
+ const string ro = cmap->getROString();
+ for (const CharMapIDToEncName &enc : encodings) {
+ for (const CharMapID &id : charmapIDs) {
+ if (enc.id == id) {
+ string cmapname = ro+"-"+enc.encname;
+ if (is_unicode_map || FileFinder::instance().lookup(cmapname, "cmap", false)) {
+ charmapID = enc.id;
+ return is_unicode_map ? cmap : lookup(cmapname);
+ }
+ }
+ }
+ }
+ return nullptr;
+}
+
diff --git a/dviware/dvisvgm/src/CMapManager.hpp b/dviware/dvisvgm/src/CMapManager.hpp
new file mode 100644
index 0000000000..0635f2ec3e
--- /dev/null
+++ b/dviware/dvisvgm/src/CMapManager.hpp
@@ -0,0 +1,51 @@
+/*************************************************************************
+** CMapManager.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef CMAPMANAGER_HPP
+#define CMAPMANAGER_HPP
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include "CharMapID.hpp"
+
+struct CMap;
+struct FontEncoding;
+class FontEncodingPair;
+class PhysicalFont;
+
+class CMapManager
+{
+ public:
+ CMap* lookup (const std::string &name);
+ const CMap* findCompatibleBaseFontMap (const PhysicalFont *font, const CMap *cmap, CharMapID &charmapID);
+ static CMapManager& instance ();
+
+ protected:
+ CMapManager () : _level(0) {}
+
+ private:
+ std::unordered_map<std::string, std::unique_ptr<CMap>> _cmaps; ///< loaded cmaps
+ std::unordered_set<std::string> _includedCMaps; ///< names of cmaps loaded by "usecmap"
+ int _level; ///< current inclusion depth; >0 if a cmap loaded by "usecmap" is being processed
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/CMapReader.cpp b/dviware/dvisvgm/src/CMapReader.cpp
new file mode 100644
index 0000000000..08450cf17f
--- /dev/null
+++ b/dviware/dvisvgm/src/CMapReader.cpp
@@ -0,0 +1,263 @@
+/*************************************************************************
+** CMapReader.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <array>
+#include <fstream>
+#include <sstream>
+#include "CMap.hpp"
+#include "CMapManager.hpp"
+#include "CMapReader.hpp"
+#include "FileFinder.hpp"
+#include "InputReader.hpp"
+#include "utility.hpp"
+
+using namespace std;
+
+
+CMapReader::CMapReader () : _inCMap(false) {
+}
+
+
+/** Reads a cmap file and returns the corresponding CMap object.
+ * @param fname[in] name/path of cmap file
+ * @return CMap object representing the read data, or 0 if file could not be read */
+unique_ptr<CMap> CMapReader::read (const string &fname) {
+ if (const char *path = FileFinder::instance().lookup(fname, "cmap", false)) {
+ ifstream ifs(path);
+ if (ifs)
+ return read(ifs, fname);
+ }
+ _tokens.clear();
+ return unique_ptr<CMap>();
+}
+
+
+/** Reads cmap data from a given stream and returns the corresponding CMap object.
+ * @param is[in] cmap data input stream
+ * @param is[in] name name of CMap to be read
+ * @return CMap object representing the read data, or 0 if file could not be read */
+unique_ptr<CMap> CMapReader::read (std::istream& is, const string &name) {
+ _tokens.clear();
+ _cmap = util::make_unique<SegmentedCMap>(name);
+ StreamInputReader ir(is);
+ try {
+ while (ir) {
+ Token token(ir);
+ if (token.type() == Token::Type::END)
+ break;
+ if (_inCMap) {
+ if (token.type() == Token::Type::OPERATOR)
+ executeOperator(token.strvalue(), ir);
+ else
+ _tokens.push_back(token);
+ }
+ else if (token.type() == Token::Type::OPERATOR && token.strvalue() == "begincmap")
+ _inCMap = true;
+ }
+ }
+ catch (CMapReaderException &e) {
+ _cmap.reset();
+ throw;
+ }
+ return std::move(_cmap);
+}
+
+
+/** Executes a PS operator from the CMap file.
+ * @param[in] opname name of operator to execute
+ * @param[in] ir reader object used to read the CMap stream */
+void CMapReader::executeOperator (const string &opname, InputReader &ir) {
+ struct Operator {
+ const char *name;
+ void (CMapReader::*handler)(InputReader&);
+ };
+ constexpr array<Operator, 6> operators {{
+ {"beginbfchar", &CMapReader::op_beginbfchar},
+ {"beginbfrange", &CMapReader::op_beginbfrange},
+ {"begincidrange", &CMapReader::op_begincidrange},
+ {"def", &CMapReader::op_def},
+ {"endcmap", &CMapReader::op_endcmap},
+ {"usecmap", &CMapReader::op_usecmap},
+ }};
+ auto it = find_if(operators.begin(), operators.end(), [&](const Operator &op) {
+ return op.name == opname;
+ });
+ if (it != operators.end())
+ (this->*it->handler)(ir);
+ _tokens.clear();
+}
+
+
+void CMapReader::op_def (InputReader&) {
+ size_t size = _tokens.size();
+ if (size >= 2) {
+ const string val = popToken().strvalue();
+ const string name = popToken().strvalue();
+ if (name == "WMode") {
+ if (val == "0" || val == "1")
+ _cmap->_vertical = (val == "1");
+ else
+ throw CMapReaderException("invalid WMode (0 or 1 expected)");
+ }
+ else if (name == "Registry")
+ _cmap->_registry = val;
+ else if (name == "Ordering")
+ _cmap->_ordering = val;
+ }
+}
+
+
+void CMapReader::op_endcmap (InputReader &) {
+ _inCMap = false;
+}
+
+
+void CMapReader::op_usecmap (InputReader &) {
+ if (_tokens.empty())
+ throw CMapReaderException("stack underflow while processing usecmap");
+ else {
+ const string name = popToken().strvalue();
+ if ((_cmap->_basemap = CMapManager::instance().lookup(name)) == nullptr)
+ throw CMapReaderException("CMap file '"+name+"' not found");
+ }
+}
+
+
+static uint32_t parse_hexentry (InputReader &ir) {
+ ir.skipSpace();
+ if (ir.get() != '<')
+ throw CMapReaderException("invalid range entry ('<' expected)");
+ unsigned val;
+ if (!ir.parseUInt(16, val))
+ throw CMapReaderException("invalid range entry (hexadecimal value expected)");
+ if (ir.get() != '>')
+ throw CMapReaderException("invalid range entry ('>' expected)");
+ return uint32_t(val);
+}
+
+
+void CMapReader::op_begincidrange (InputReader &ir) {
+ if (!_tokens.empty() && _tokens.back().type() == Token::Type::NUMBER) {
+ ir.skipSpace();
+ int num_entries = static_cast<int>(popToken().numvalue());
+ while (num_entries > 0 && ir.peek() == '<') {
+ uint32_t first = parse_hexentry(ir);
+ uint32_t last = parse_hexentry(ir);
+ uint32_t cid;
+ ir.skipSpace();
+ if (!ir.parseUInt(cid))
+ throw CMapReaderException("invalid range entry (decimal value expected)");
+ _cmap->addCIDRange(first, last, cid);
+ ir.skipSpace();
+ }
+ }
+}
+
+
+void CMapReader::op_beginbfrange (InputReader &ir) {
+ if (!_tokens.empty() && _tokens.back().type() == Token::Type::NUMBER) {
+ ir.skipSpace();
+ int num_entries = static_cast<int>(popToken().numvalue());
+ while (num_entries > 0 && ir.peek() == '<') {
+ uint32_t first = parse_hexentry(ir);
+ uint32_t last = parse_hexentry(ir);
+ uint32_t chrcode = parse_hexentry(ir);
+ _cmap->addBFRange(first, last, chrcode);
+ ir.skipSpace();
+ }
+ _cmap->_mapsToCID = false;
+ }
+}
+
+
+void CMapReader::op_beginbfchar (InputReader &ir) {
+ if (!_tokens.empty() && _tokens.back().type() == Token::Type::NUMBER) {
+ ir.skipSpace();
+ int num_entries = static_cast<int>(popToken().numvalue());
+ while (num_entries > 0 && ir.peek() == '<') {
+ uint32_t cid = parse_hexentry(ir);
+ ir.skipSpace();
+ if (ir.peek() == '/')
+ throw CMapReaderException("mapping of named characters is not supported");
+ uint32_t chrcode = parse_hexentry(ir);
+ _cmap->addBFRange(cid, cid, chrcode);
+ ir.skipSpace();
+ }
+ _cmap->_mapsToCID = false;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+
+CMapReader::Token::Token (InputReader &ir) {
+ scan(ir);
+}
+
+
+/** Reads the next characters from the input stream to create a token. */
+void CMapReader::Token::scan (InputReader &ir) {
+ ir.skipSpace();
+ while (ir.peek() == '%') { // comment?
+ while (ir.peek() != '\n') // => skip until end of line
+ ir.get();
+ ir.skipSpace();
+ }
+ ir.skipSpace();
+ if (ir.eof())
+ _type = Type::END;
+ else if (ir.peek() == '/') { // PS name?
+ ir.get();
+ while (!strchr("[]{}<>", ir.peek()) && !isspace(ir.peek()))
+ _value += ir.get();
+ _type = Type::NAME;
+ }
+ else if (ir.peek() == '(') { // string?
+ ir.get();
+ int level=0;
+ while (ir.peek() != ')' || level > 0) {
+ if (ir.peek() == '(')
+ level++;
+ else if (ir.peek() == ')' && level > 0)
+ level--;
+ _value += ir.get();
+ }
+ ir.get(); // skip ')'
+ _type = Type::STRING;
+ }
+ else if (strchr("[]{}<>", ir.peek())) { // PS delimiter
+ _value = ir.get();
+ _type = Type::DELIM;
+ }
+ else if (isdigit(ir.peek())) { // number?
+ double val;
+ if (ir.parseDouble(val)) {
+ ostringstream oss;
+ oss << val;
+ _value = oss.str();
+ _type = Type::NUMBER;
+ }
+ }
+ else {
+ while (!strchr("[]{}<>", ir.peek()) && !isspace(ir.peek()))
+ _value += ir.get();
+ _type = Type::OPERATOR;
+ }
+}
diff --git a/dviware/dvisvgm/src/CMapReader.hpp b/dviware/dvisvgm/src/CMapReader.hpp
new file mode 100644
index 0000000000..820882c04d
--- /dev/null
+++ b/dviware/dvisvgm/src/CMapReader.hpp
@@ -0,0 +1,79 @@
+/*************************************************************************
+** CMapReader.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef CMAPREADER_HPP
+#define CMAPREADER_HPP
+
+#include <cstdlib>
+#include <istream>
+#include <memory>
+#include <string>
+#include <vector>
+#include "MessageException.hpp"
+
+
+struct CMap;
+class InputReader;
+
+class CMapReader {
+ class Token {
+ public:
+ enum class Type {UNKNOWN, END, DELIM, NUMBER, STRING, NAME, OPERATOR};
+
+ public:
+ explicit Token (InputReader &ir);
+ void scan (InputReader &ir);
+ Type type () const {return _type;}
+ const std::string& strvalue () const {return _value;}
+ double numvalue () const {return std::atof(_value.c_str());}
+
+ private:
+ Type _type;
+ std::string _value;
+ };
+
+ public:
+ CMapReader ();
+ std::unique_ptr<CMap> read (const std::string &fname);
+ std::unique_ptr<CMap> read (std::istream &is, const std::string &name);
+
+ protected:
+ Token popToken () {Token t=_tokens.back(); _tokens.pop_back(); return t;}
+ void executeOperator (const std::string &op, InputReader &ir);
+ void op_beginbfchar (InputReader &ir);
+ void op_beginbfrange (InputReader &ir);
+ void op_begincidrange (InputReader &ir);
+ void op_def (InputReader &ir);
+ void op_endcmap (InputReader &ir);
+ void op_usecmap (InputReader &ir);
+
+ private:
+ std::unique_ptr<SegmentedCMap> _cmap; ///< pointer to CMap being read
+ std::vector<Token> _tokens; ///< stack of tokens to be processed
+ bool _inCMap; ///< operator begincmap has been executed
+};
+
+
+
+struct CMapReaderException : public MessageException {
+ explicit CMapReaderException (const std::string &msg) : MessageException(msg) {}
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/CRC32.cpp b/dviware/dvisvgm/src/CRC32.cpp
new file mode 100644
index 0000000000..583b019324
--- /dev/null
+++ b/dviware/dvisvgm/src/CRC32.cpp
@@ -0,0 +1,112 @@
+/*************************************************************************
+** CRC32.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <cstring>
+#include "CRC32.hpp"
+
+using namespace std;
+
+
+CRC32::CRC32 () : _crc32(0xFFFFFFFF)
+{
+ const uint32_t poly = 0xEDB88320;
+ for (int i = 0; i < 256; i++) {
+ uint32_t crc=i;
+ for (int j=8; j > 0; j--) {
+ if (crc & 1)
+ crc = (crc >> 1) ^ poly;
+ else
+ crc >>= 1;
+ }
+ _tab[i] = crc;
+ }
+}
+
+
+/** Resets CRC32 sum to 0. */
+void CRC32::reset () {
+ _crc32 = 0xFFFFFFFF;
+}
+
+
+/** Appends string bytes to the previous data and computes the resulting checksum.
+ * @param[in] data string to update the checksum with */
+void CRC32::update (const char *data) {
+ update((const uint8_t*)data, strlen(data));
+}
+
+
+/** Appends a single value to the previous data and computes the resulting checksum.
+ * @param[in] n value to update the checksum with
+ * @param[in] bytes number of bytes to consider (0-4) */
+void CRC32::update (uint32_t n, int bytes) {
+ for (int i=bytes-1; i >= 0; --i) {
+ uint8_t byte = uint8_t((n >> (8*i)) & 0xff);
+ update(&byte, 1);
+ }
+}
+
+
+/** Appends a sequence of bytes to the previous data and computes the resulting checksum.
+ * @param[in] bytes pointer to array of bytes
+ * @param[in] len number of bytes in array */
+void CRC32::update (const uint8_t *bytes, size_t len) {
+ for (size_t i=0; i < len; ++i)
+ _crc32 = ((_crc32 >> 8) & 0x00FFFFFF) ^ _tab[(_crc32 ^ *bytes++) & 0xFF];
+}
+
+
+void CRC32::update (istream &is) {
+ char buf [4096];
+ while (is) {
+ is.read(buf, 4096);
+ update((uint8_t*)buf, is.gcount());
+ }
+}
+
+
+/** Returns the checksum computed from values added with the update functions. */
+uint32_t CRC32::get () const {
+ return _crc32 ^ 0xFFFFFFFF;
+}
+
+
+/** Computes the CRC32 checksum of a sequence of bytes.
+ * @param[in] bytes pointer to array of bytes
+ * @param[in] len number of bytes in array
+ * @return CRC32 checksum */
+uint32_t CRC32::compute (const uint8_t *bytes, size_t len) {
+ CRC32 crc32;
+ crc32.update(bytes, len);
+ return crc32.get();
+}
+
+
+/** Computes the CRC32 checksum of a string. */
+uint32_t CRC32::compute (const char *str) {
+ return compute((const uint8_t*)str, strlen(str));
+}
+
+
+uint32_t CRC32::compute (istream &is) {
+ CRC32 crc32;
+ crc32.update(is);
+ return crc32.get();
+}
diff --git a/dviware/dvisvgm/src/CRC32.hpp b/dviware/dvisvgm/src/CRC32.hpp
new file mode 100644
index 0000000000..880f8b737e
--- /dev/null
+++ b/dviware/dvisvgm/src/CRC32.hpp
@@ -0,0 +1,47 @@
+/*************************************************************************
+** CRC32.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef CRC32_HPP
+#define CRC32_HPP
+
+#include <cstdlib>
+#include <istream>
+
+class CRC32 {
+ public:
+ CRC32 ();
+ CRC32 (const CRC32 &crc32) =delete;
+ void update (const uint8_t *bytes, size_t len);
+ void update (uint32_t n, int bytes=4);
+ void update (const char *str);
+ void update (std::istream &is);
+ uint32_t get () const;
+ void reset ();
+ static uint32_t compute (const uint8_t *bytes, size_t len);
+ static uint32_t compute (const char *str);
+ static uint32_t compute (std::istream &is);
+
+ private:
+ uint32_t _crc32;
+ uint32_t _tab[256];
+};
+
+
+#endif
diff --git a/dviware/dvisvgm/src/Calculator.cpp b/dviware/dvisvgm/src/Calculator.cpp
new file mode 100644
index 0000000000..3091c9cf35
--- /dev/null
+++ b/dviware/dvisvgm/src/Calculator.cpp
@@ -0,0 +1,185 @@
+/*************************************************************************
+** Calculator.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <cmath>
+#include <sstream>
+#include <stdexcept>
+#include "Calculator.hpp"
+
+using namespace std;
+
+// token types
+const char END = 0;
+const char NUMBER = 1;
+const char NAME = 2;
+
+
+/** Evaluates a given arithmetic expression and returns its value.
+ * The evaluator is implemented as a recursive descent parser.
+ * @param[in] is reads expression from this stream
+ * @return expression value */
+double Calculator::eval (istream &is) {
+ double ret = expr(is, false);
+ if (lookAhead(is) > 0)
+ throw CalculatorException("expression syntax error");
+ return ret;
+}
+
+
+/** Evaluates a given arithmetic expression and returns its value.
+ * @param[in] expr expression to evaluate
+ * @return expression value */
+double Calculator::eval (const string &expr) {
+ istringstream iss;
+ iss.str(expr);
+ return eval(iss);
+}
+
+
+/** Evaluates the root rule of the expression grammar. */
+double Calculator::expr (istream &is, bool skip) { // expr:
+ double left = term(is, skip);
+ for (;;) {
+ switch (lookAhead(is)) {
+ case '+': left += term(is, true); break; // term '+' term => $1 + $3
+ case '-': left -= term(is, true); break; // term '-' term => $1 - $3
+ default : return left; // term => $1
+ }
+ }
+}
+
+
+double Calculator::term (istream &is, bool skip) { // term:
+ double left = prim(is, skip);
+ for (;;)
+ switch (lookAhead(is)) {
+ case '*': left *= prim(is, true); break; // prim '*' prim => $1 * $3
+ case '/': { // prim '/' prim => $1 / $3
+ double denom = prim(is, true);
+ if (denom == 0)
+ throw CalculatorException("division by zero");
+ left /= denom;
+ break;
+ }
+ case '%': { // prim '%' prim => $1 mod $3
+ double denom = prim(is, true);
+ if (denom == 0)
+ throw CalculatorException("division by zero");
+ left -= denom*floor(left/denom);
+ break;
+ }
+ default: // prim => $1
+ return left;
+ }
+}
+
+
+double Calculator::prim (istream &is, bool skip) { // prim:
+ if (skip)
+ lex(is);
+ switch (lookAhead(is)) {
+ case NUMBER: { // NUMBER => $1
+ lex(is);
+ double ret = _numValue;
+ if (lookAhead(is) == NAME) { // NUMBER NAME => $1 * $2
+ lex(is);
+ ret *= getVariable(_strValue);
+ }
+ return ret;
+ }
+ case NAME: { // NAME => getVariable($1)
+ lex(is);
+ return getVariable(_strValue);
+ }
+ case '-': // '-' prim => -$2
+ return -prim(is, true);
+ case '(': { // '(' expr ')' => $2
+ double e = expr(is, true);
+ if (lookAhead(is) != ')')
+ throw CalculatorException("')' expected");
+ lex(is);
+ return e;
+ }
+ default:
+ throw CalculatorException("primary expression expected");
+ }
+}
+
+
+/** Determines type of next token without swallowing it. That means
+ * the same token will be read again next time. */
+char Calculator::lookAhead (istream &is) {
+ is >> ws;
+ int c = is.peek();
+ if (is.eof())
+ return END;
+ if (isdigit(c) || c == '.')
+ return NUMBER;
+ if (isalpha(c))
+ return NAME;
+ return char(c);
+}
+
+
+/** Reads next token and returns its type. The token value is either assigned
+ * to the object members numValue or strValue depending on the type. The token
+ * type is represented by a unique integer. In contrast to method 'lookAhead'
+ * lex consumes the read token.
+ * @param[in] is next token is read from this stream
+ * @return token type */
+char Calculator::lex (istream &is) {
+ int tokenType = lookAhead(is);
+ switch (tokenType) {
+ case NUMBER: {
+ string str;
+ while (isdigit(is.peek()) || is.peek() == '.')
+ str += char(is.get());
+ try {
+ _numValue = stod(str);
+ }
+ catch (const exception&) {
+ throw CalculatorException("invalid number: "+str);
+ }
+ break;
+ }
+ case NAME: {
+ _strValue.clear();
+ while (isalpha(is.peek()))
+ _strValue += char(is.get());
+ break;
+ }
+ default:
+ tokenType = is.get();
+ }
+ return char(tokenType);
+}
+
+
+/** Returns the value of a previously defined variable. If there
+ * is no variable of the given name, a CalculatorException is thrown.
+ * @param[in] name name of variable
+ * @return assigned value */
+double Calculator::getVariable (const string &name) const {
+ auto it = _variables.find(name);
+ if (it == _variables.end())
+ throw CalculatorException("undefined variable '" + name + "'");
+ return it->second;
+}
+
diff --git a/dviware/dvisvgm/src/Calculator.hpp b/dviware/dvisvgm/src/Calculator.hpp
new file mode 100644
index 0000000000..f498365e54
--- /dev/null
+++ b/dviware/dvisvgm/src/Calculator.hpp
@@ -0,0 +1,55 @@
+/*************************************************************************
+** Calculator.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef CALCULATOR_HPP
+#define CALCULATOR_HPP
+
+#include <istream>
+#include <map>
+#include <string>
+#include "MessageException.hpp"
+
+
+struct CalculatorException : public MessageException {
+ explicit CalculatorException (const std::string &msg) : MessageException(msg) {}
+};
+
+class Calculator {
+ public:
+ Calculator () : _numValue(0) {}
+ double eval (std::istream &is);
+ double eval (const std::string &expr);
+ void setVariable (const std::string &name, double value) {_variables[name] = value;}
+ double getVariable (const std::string &name) const;
+
+ protected:
+ double expr (std::istream &is, bool skip);
+ double term (std::istream &is, bool skip);
+ double prim (std::istream &is, bool skip);
+ char lex (std::istream &is);
+ char lookAhead (std::istream &is);
+
+ private:
+ std::map<std::string,double> _variables;
+ double _numValue;
+ std::string _strValue;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/CharMapID.cpp b/dviware/dvisvgm/src/CharMapID.cpp
new file mode 100644
index 0000000000..c04ae2ceb9
--- /dev/null
+++ b/dviware/dvisvgm/src/CharMapID.cpp
@@ -0,0 +1,36 @@
+/*************************************************************************
+** CharMapID.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include "CharMapID.hpp"
+
+const CharMapID CharMapID::NONE(0, 0);
+const CharMapID CharMapID::WIN_SYMBOL(3, 0);
+const CharMapID CharMapID::WIN_UCS2(3, 1);
+const CharMapID CharMapID::WIN_SHIFTJIS(3, 2);
+const CharMapID CharMapID::WIN_PRC(3, 3);
+const CharMapID CharMapID::WIN_BIG5(3, 4);
+const CharMapID CharMapID::WIN_WANSUNG(3, 5);
+const CharMapID CharMapID::WIN_JOHAB(3, 6);
+const CharMapID CharMapID::WIN_UCS4(3, 10);
+
+const CharMapID CharMapID::MAC_JAPANESE(1, 1);
+const CharMapID CharMapID::MAC_TRADCHINESE(1, 2);
+const CharMapID CharMapID::MAC_KOREAN(1, 3);
+const CharMapID CharMapID::MAC_SIMPLCHINESE(1, 25);
diff --git a/dviware/dvisvgm/src/CharMapID.hpp b/dviware/dvisvgm/src/CharMapID.hpp
new file mode 100644
index 0000000000..e79c64b24f
--- /dev/null
+++ b/dviware/dvisvgm/src/CharMapID.hpp
@@ -0,0 +1,59 @@
+/*************************************************************************
+** CharMapID.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef CHARMAPID_HPP
+#define CHARMAPID_HPP
+
+#include <cstdint>
+
+/** Represents a character map of a font. */
+struct CharMapID {
+ CharMapID () noexcept : platform_id(0), encoding_id(0) {}
+ CharMapID (uint8_t plf_id, uint8_t enc_id) noexcept : platform_id(plf_id), encoding_id(enc_id) {}
+
+ bool operator == (const CharMapID &ids) const {
+ return platform_id == ids.platform_id && encoding_id == ids.encoding_id;
+ }
+
+ bool operator != (const CharMapID &ids) const {
+ return platform_id != ids.platform_id || encoding_id != ids.encoding_id;
+ }
+
+ bool valid () const {return platform_id != 0 && encoding_id != 0;}
+
+ static const CharMapID NONE;
+ static const CharMapID WIN_SYMBOL;
+ static const CharMapID WIN_UCS2;
+ static const CharMapID WIN_SHIFTJIS;
+ static const CharMapID WIN_PRC;
+ static const CharMapID WIN_BIG5;
+ static const CharMapID WIN_WANSUNG;
+ static const CharMapID WIN_JOHAB;
+ static const CharMapID WIN_UCS4;
+ static const CharMapID MAC_JAPANESE;
+ static const CharMapID MAC_TRADCHINESE;
+ static const CharMapID MAC_SIMPLCHINESE;
+ static const CharMapID MAC_KOREAN;
+
+ uint8_t platform_id;
+ uint8_t encoding_id;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/Character.hpp b/dviware/dvisvgm/src/Character.hpp
new file mode 100644
index 0000000000..e232401fb8
--- /dev/null
+++ b/dviware/dvisvgm/src/Character.hpp
@@ -0,0 +1,44 @@
+/*************************************************************************
+** Character.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef CHARACTER_HPP
+#define CHARACTER_HPP
+
+
+class Character {
+ public:
+ enum Type {CHRCODE, INDEX, NAME};
+ explicit Character (const char *name) : _type(NAME), _name(name) {}
+ Character (Type type, uint32_t val) : _type(type), _number(val) {}
+ Character (Type type, const Character &c) : _type(type), _number(c.type() != NAME ? c._number : 0) {}
+ Type type () const {return _type;}
+ const char* name () const {return _name;}
+ uint32_t number () const {return _number;}
+
+ private:
+ Type _type;
+ union {
+ uint32_t _number;
+ const char *_name;
+ };
+};
+
+#endif
+
diff --git a/dviware/dvisvgm/src/Color.cpp b/dviware/dvisvgm/src/Color.cpp
new file mode 100644
index 0000000000..949fcba56c
--- /dev/null
+++ b/dviware/dvisvgm/src/Color.cpp
@@ -0,0 +1,681 @@
+/*************************************************************************
+** Color.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <array>
+#include <algorithm>
+#include <cctype>
+#include <cmath>
+#include <cstdlib>
+#include <cstring>
+#include <iomanip>
+#include <sstream>
+#include "Color.hpp"
+#include "utility.hpp"
+
+using namespace std;
+
+bool Color::SUPPRESS_COLOR_NAMES = true;
+
+const Color Color::BLACK(uint32_t(0));
+const Color Color::WHITE(uint8_t(255), uint8_t(255), uint8_t(255));
+const Color Color::TRANSPARENT(uint32_t(0xff000000));
+
+
+static inline uint8_t double_to_byte (double v) {
+ v = max(0.0, min(1.0, v));
+ return uint8_t(floor(255*v+0.5));
+}
+
+
+Color::Color (const string &psname) {
+ if (!setPSName(psname, false))
+ setGray(uint8_t(0));
+}
+
+
+void Color::setRGB (double r, double g, double b) {
+ setRGB(double_to_byte(r), double_to_byte(g), double_to_byte(b));
+}
+
+
+/** Expects a PostScript color name and sets the color accordingly.
+ * @param[in] name PS color name
+ * @param[in] case_sensitive if true, upper/lower case spelling is significant
+ * @return true if color name could be applied properly */
+bool Color::setPSName (string name, bool case_sensitive) {
+ if (name[0] == '#') {
+ char *p=nullptr;
+ _rgb = uint32_t(strtol(name.c_str()+1, &p, 16));
+ while (isspace(*p))
+ p++;
+ return (*p == 0 && _rgb <= 0xFFFFFF);
+ }
+
+ struct ColorConstant {
+ const char *name;
+ const uint32_t rgb;
+ };
+ // converted color constants from color.pro
+ static const array<ColorConstant, 68> constants {{
+ {"Apricot", 0xFFAD7A},
+ {"Aquamarine", 0x2DFFB2},
+ {"Bittersweet", 0xC10200},
+ {"Black", 0x000000},
+ {"Blue", 0x0000FF},
+ {"BlueGreen", 0x26FFAA},
+ {"BlueViolet", 0x190CF4},
+ {"BrickRed", 0xB70000},
+ {"Brown", 0x660000},
+ {"BurntOrange", 0xFF7C00},
+ {"CadetBlue", 0x606DC4},
+ {"CarnationPink", 0xFF5EFF},
+ {"Cerulean", 0x0FE2FF},
+ {"CornflowerBlue", 0x59DDFF},
+ {"Cyan", 0x00FFFF},
+ {"Dandelion", 0xFFB528},
+ {"DarkOrchid", 0x9932CC},
+ {"Emerald", 0x00FF7F},
+ {"ForestGreen", 0x00E000},
+ {"Fuchsia", 0x7202EA},
+ {"Goldenrod", 0xFFE528},
+ {"Gray", 0x7F7F7F},
+ {"Green", 0x00FF00},
+ {"GreenYellow", 0xD8FF4F},
+ {"JungleGreen", 0x02FF7A},
+ {"Lavender", 0xFF84FF},
+ {"LimeGreen", 0x7FFF00},
+ {"Magenta", 0xFF00FF},
+ {"Mahogany", 0xA50000},
+ {"Maroon", 0xAD0000},
+ {"Melon", 0xFF897F},
+ {"MidnightBlue", 0x007091},
+ {"Mulberry", 0xA314F9},
+ {"NavyBlue", 0x0F75FF},
+ {"OliveGreen", 0x009900},
+ {"Orange", 0xFF6321},
+ {"OrangeRed", 0xFF007F},
+ {"Orchid", 0xAD5BFF},
+ {"Peach", 0xFF7F4C},
+ {"Periwinkle", 0x6D72FF},
+ {"PineGreen", 0x00BF28},
+ {"Plum", 0x7F00FF},
+ {"ProcessBlue", 0x0AFFFF},
+ {"Purple", 0x8C23FF},
+ {"RawSienna", 0x8C0000},
+ {"Red", 0xFF0000},
+ {"RedOrange", 0xFF3A21},
+ {"RedViolet", 0x9600A8},
+ {"Rhodamine", 0xFF2DFF},
+ {"RoyalBlue", 0x007FFF},
+ {"RoyalPurple", 0x3F19FF},
+ {"RubineRed", 0xFF00DD},
+ {"Salmon", 0xFF779E},
+ {"SeaGreen", 0x4FFF7F},
+ {"Sepia", 0x4C0000},
+ {"SkyBlue", 0x60FFE0},
+ {"SpringGreen", 0xBCFF3D},
+ {"Tan", 0xDB9370},
+ {"TealBlue", 0x1EF9A3},
+ {"Thistle", 0xE068FF},
+ {"Turquoise", 0x26FFCC},
+ {"Violet", 0x351EFF},
+ {"VioletRed", 0xFF30FF},
+ {"White", 0xFFFFFF},
+ {"WildStrawberry", 0xFF0A9B},
+ {"Yellow", 0xFFFF00},
+ {"YellowGreen", 0x8EFF42},
+ {"YellowOrange", 0xFF9300},
+ }};
+ if (case_sensitive) {
+ const ColorConstant cmppair = {name.c_str(), 0};
+ auto it = lower_bound(constants.begin(), constants.end(), cmppair,
+ [](const ColorConstant &c1, const ColorConstant &c2) {
+ return strcmp(c1.name, c2.name) < 0;
+ }
+ );
+ if (it != constants.end() && it->name == name) {
+ _rgb = it->rgb;
+ return true;
+ }
+ }
+ else {
+ name = util::tolower(name);
+ auto it = find_if(constants.begin(), constants.end(), [&](const ColorConstant &cc) {
+ return name == util::tolower(cc.name);
+ });
+ if (it != constants.end()) {
+ _rgb = it->rgb;
+ return true;
+ }
+ }
+ return false;
+}
+
+
+void Color::setHSB (double h, double s, double b) {
+ valarray<double> hsb(3), rgb(3);
+ hsb[0] = h;
+ hsb[1] = s;
+ hsb[2] = b;
+ HSB2RGB(hsb, rgb);
+ setRGB(rgb);
+}
+
+
+void Color::setCMYK (double c, double m, double y, double k) {
+ valarray<double> cmyk(4), rgb(3);
+ cmyk[0] = c;
+ cmyk[1] = m;
+ cmyk[2] = y;
+ cmyk[3] = k;
+ CMYK2RGB(cmyk, rgb);
+ setRGB(rgb);
+}
+
+
+void Color::setCMYK (const std::valarray<double> &cmyk) {
+ valarray<double> rgb(3);
+ CMYK2RGB(cmyk, rgb);
+ setRGB(rgb);
+}
+
+
+void Color::set (ColorSpace colorSpace, VectorIterator<double> &it) {
+ switch (colorSpace) {
+ case ColorSpace::GRAY: setGray(*it++); break;
+ case ColorSpace::RGB : setRGB(*it, *(it+1), *(it+2)); it+=3; break;
+ case ColorSpace::LAB : setLab(*it, *(it+1), *(it+2)); it+=3; break;
+ case ColorSpace::CMYK: setCMYK(*it, *(it+1), *(it+2), *(it+3)); it+=4; break;
+ }
+}
+
+
+Color Color::operator *= (double c) {
+ if (abs(c) < 0.001)
+ _rgb &= 0xff000000;
+ else if (abs(c-trunc(c)) < 0.999) {
+ uint32_t rgb=0;
+ for (int i=0; i < 3; i++) {
+ rgb |= lround((_rgb & 0xff)*c) << (8*i);
+ _rgb >>= 8;
+ }
+ _rgb = rgb;
+ }
+ return *this;
+}
+
+
+/** Returns an RGB string representing the color. Depending on the
+ * color value, the string either consists of 3 or 6 hex digits
+ * plus a leading '#' character. */
+string Color::rgbString () const {
+ ostringstream oss;
+ oss << '#';
+ for (int i=2; i >= 0; i--) {
+ oss << setbase(16) << setfill('0') << setw(2)
+ << (((_rgb >> (8*i)) & 0xff));
+ }
+ // check if RGB string can be reduced to a three digit hex value
+ // #RRGGBB => #RGB, e.g. #112233 => #123
+ string hexstr = oss.str();
+ if (hexstr[1] == hexstr[2] && hexstr[3] == hexstr[4] && hexstr[5] == hexstr[6]) {
+ hexstr[2] = hexstr[3];
+ hexstr[3] = hexstr[5];
+ hexstr.resize(4);
+ }
+ return hexstr;
+}
+
+
+/** Returns an SVG color string representing the current color. The result can
+ * either be an SVG color name or an RGB string of the form #RRGGBB. The latter
+ * will be returned if parameter rgbonly is true or if the SVG standard doesn't
+ * define a name for the current color. */
+string Color::svgColorString (bool rgbonly) const {
+ if (!rgbonly) {
+ struct ColorName {
+ uint32_t rgb;
+ const char *name;
+ };
+ static const array<ColorName, 138> colornames {{
+ {0x000000, "black"},
+ {0x000080, "navy"},
+ {0x00008b, "darkblue"},
+ {0x0000cd, "mediumblue"},
+ {0x0000ff, "blue"},
+ {0x006400, "darkgreen"},
+ {0x008000, "green"},
+ {0x008080, "teal"},
+ {0x008b8b, "darkcyan"},
+ {0x00bfff, "deepskyblue"},
+ {0x00ced1, "darkturquoise"},
+ {0x00fa9a, "mediumspringgreen"},
+ {0x00ff00, "lime"},
+ {0x00ff7f, "springgreen"},
+ {0x00ffff, "cyan"},
+ {0x191970, "midnightblue"},
+ {0x1e90ff, "dodgerblue"},
+ {0x20b2aa, "lightseagreen"},
+ {0x228b22, "forestgreen"},
+ {0x2e8b57, "seagreen"},
+ {0x2f4f4f, "darkslategray"},
+ {0x32cd32, "limegreen"},
+ {0x3cb371, "mediumseagreen"},
+ {0x40e0d0, "turquoise"},
+ {0x4169e1, "royalblue"},
+ {0x4682b4, "steelblue"},
+ {0x483d8b, "darkslateblue"},
+ {0x48d1cc, "mediumturquoise"},
+ {0x4b0082, "indigo"},
+ {0x556b2f, "darkolivegreen"},
+ {0x5f9ea0, "cadetblue"},
+ {0x6495ed, "cornflowerblue"},
+ {0x66cdaa, "mediumaquamarine"},
+ {0x696969, "dimgray"},
+ {0x6a5acd, "slateblue"},
+ {0x6b8e23, "olivedrab"},
+ {0x708090, "slategray"},
+ {0x778899, "lightslategray"},
+ {0x7b68ee, "mediumslateblue"},
+ {0x7cfc00, "lawngreen"},
+ {0x7fff00, "chartreuse"},
+ {0x7fffd4, "aquamarine"},
+ {0x800000, "maroon"},
+ {0x800080, "purple"},
+ {0x808000, "olive"},
+ {0x808080, "gray"},
+ {0x87ceeb, "skyblue"},
+ {0x87cefa, "lightskyblue"},
+ {0x8a2be2, "blueviolet"},
+ {0x8b0000, "darkred"},
+ {0x8b008b, "darkmagenta"},
+ {0x8b4513, "saddlebrown"},
+ {0x8fbc8f, "darkseagreen"},
+ {0x90ee90, "lightgreen"},
+ {0x9370db, "mediumpurple"},
+ {0x9400d3, "darkviolet"},
+ {0x98fb98, "palegreen"},
+ {0x9932cc, "darkorchid"},
+ {0x9acd32, "yellowgreen"},
+ {0xa0522d, "sienna"},
+ {0xa52a2a, "brown"},
+ {0xa9a9a9, "darkgray"},
+ {0xadd8e6, "lightblue"},
+ {0xadff2f, "greenyellow"},
+ {0xafeeee, "paleturquoise"},
+ {0xb0c4de, "lightsteelblue"},
+ {0xb0e0e6, "powderblue"},
+ {0xb22222, "firebrick"},
+ {0xb8860b, "darkgoldenrod"},
+ {0xba55d3, "mediumorchid"},
+ {0xbc8f8f, "rosybrown"},
+ {0xbdb76b, "darkkhaki"},
+ {0xc0c0c0, "silver"},
+ {0xc71585, "mediumvioletred"},
+ {0xcd5c5c, "indianred"},
+ {0xcd853f, "peru"},
+ {0xd2691e, "chocolate"},
+ {0xd2b48c, "tan"},
+ {0xd3d3d3, "lightgray"},
+ {0xd8bfd8, "thistle"},
+ {0xda70d6, "orchid"},
+ {0xdaa520, "goldenrod"},
+ {0xdb7093, "palevioletred"},
+ {0xdc143c, "crimson"},
+ {0xdcdcdc, "gainsboro"},
+ {0xdda0dd, "plum"},
+ {0xdeb887, "burlywood"},
+ {0xe0ffff, "lightcyan"},
+ {0xe6e6fa, "lavender"},
+ {0xe9967a, "darksalmon"},
+ {0xee82ee, "violet"},
+ {0xeee8aa, "palegoldenrod"},
+ {0xf08080, "lightcoral"},
+ {0xf0e68c, "khaki"},
+ {0xf0f8ff, "aliceblue"},
+ {0xf0fff0, "honeydew"},
+ {0xf0ffff, "azure"},
+ {0xf4a460, "sandybrown"},
+ {0xf5deb3, "wheat"},
+ {0xf5f5dc, "beige"},
+ {0xf5f5f5, "whitesmoke"},
+ {0xf5fffa, "mintcream"},
+ {0xf8f8ff, "ghostwhite"},
+ {0xfa8072, "salmon"},
+ {0xfaebd7, "antiquewhite"},
+ {0xfaf0e6, "linen"},
+ {0xfafad2, "lightgoldenrodyellow"},
+ {0xfdf5e6, "oldlace"},
+ {0xff0000, "red"},
+ {0xff00ff, "magenta"},
+ {0xff1493, "deeppink"},
+ {0xff4500, "orangered"},
+ {0xff6347, "tomato"},
+ {0xff69b4, "hotpink"},
+ {0xff7f50, "coral"},
+ {0xff8c00, "darkorange"},
+ {0xffa07a, "lightsalmon"},
+ {0xffa500, "orange"},
+ {0xffb6c1, "lightpink"},
+ {0xffc0cb, "pink"},
+ {0xffd700, "gold"},
+ {0xffdab9, "peachpuff"},
+ {0xffdead, "navajowhite"},
+ {0xffe4b5, "moccasin"},
+ {0xffe4c4, "bisque"},
+ {0xffe4e1, "mistyrose"},
+ {0xffebcd, "blanchedalmond"},
+ {0xffefd5, "papayawhip"},
+ {0xfff0f5, "lavenderblush"},
+ {0xfff5ee, "seashell"},
+ {0xfff8dc, "cornsilk"},
+ {0xfffacd, "lemonchiffon"},
+ {0xfffaf0, "floralwhite"},
+ {0xfffafa, "snow"},
+ {0xffff00, "yellow"},
+ {0xffffe0, "lightyellow"},
+ {0xfffff0, "ivory"},
+ {0xffffff, "white"}
+ }};
+ ColorName cmppair = {_rgb, nullptr};
+ auto it = lower_bound(colornames.begin(), colornames.end(), cmppair, [](const ColorName &c1, const ColorName &c2) {
+ return c1.rgb < c2.rgb;
+ });
+ if (it != colornames.end() && it->rgb == _rgb)
+ return it->name;
+ }
+ return rgbString();
+}
+
+
+/** Approximates a CMYK color by an RGB triple. The component values
+ * are expected to be normalized, i.e. 0 <= cmyk[i],rgb[j] <= 1.
+ * @param[in] cmyk color in CMYK space
+ * @param[out] rgb RGB approximation */
+void Color::CMYK2RGB (const valarray<double> &cmyk, valarray<double> &rgb) {
+ double kc = 1.0-cmyk[3];
+ for (int i=0; i < 3; i++)
+ rgb[i] = min(1.0, max(0.0, (1.0-cmyk[i])*kc));
+}
+
+
+void Color::RGB2CMYK (const valarray<double> &rgb, valarray<double> &cmyk) {
+ double c = 1-rgb[0];
+ double m = 1-rgb[1];
+ double y = 1-rgb[2];
+ cmyk[3] = min(min(c, m), y);
+ cmyk[0] = c-cmyk[3];
+ cmyk[1] = m-cmyk[3];
+ cmyk[2] = y-cmyk[3];
+}
+
+
+/** Converts a color given in HSB coordinates to RGB.
+ * @param[in] hsb color in HSB space
+ * @param[out] rgb color in RGB space */
+void Color::HSB2RGB (const valarray<double> &hsb, valarray<double> &rgb) {
+ if (hsb[1] == 0)
+ rgb[0] = rgb[1] = rgb[2] = hsb[2];
+ else {
+ double h = hsb[0]-floor(hsb[0]);
+ int i = int(6*h);
+ double f = 6*h-i;
+ double p = hsb[2]*(1-hsb[1]);
+ double q = hsb[2]*(1-hsb[1]*f);
+ double t = hsb[2]*(1-hsb[1]*(1-f));
+ switch (i) {
+ case 0 : rgb[0]=hsb[2]; rgb[1]=t; rgb[2]=p; break;
+ case 1 : rgb[0]=q; rgb[1]=hsb[2]; rgb[2]=p; break;
+ case 2 : rgb[0]=p; rgb[1]=hsb[2]; rgb[2]=t; break;
+ case 3 : rgb[0]=p; rgb[1]=q; rgb[2]=hsb[2]; break;
+ case 4 : rgb[0]=t; rgb[1]=p; rgb[2]=hsb[2]; break;
+ case 5 : rgb[0]=hsb[2]; rgb[1]=p; rgb[2]=q; break;
+ default: ; // prevent compiler warning
+ }
+ }
+}
+
+
+double Color::getGray () const {
+ double r, g, b;
+ getRGB(r, g, b);
+ return r*0.3 + g*0.59 + b*0.11; // gray value according to NTSC video standard
+}
+
+
+void Color::getGray (valarray<double> &gray) const {
+ gray.resize(1);
+ gray[0] = getGray();
+}
+
+
+void Color::getRGB (double &r, double &g, double &b) const {
+ r = ((_rgb >> 16) & 255) / 255.0;
+ g = ((_rgb >> 8) & 255) / 255.0;
+ b = (_rgb & 255) / 255.0;
+}
+
+
+void Color::getRGB (valarray<double> &rgb) const {
+ rgb.resize(3);
+ double r, g, b;
+ getRGB(r, g, b);
+ rgb[0] = r;
+ rgb[1] = g;
+ rgb[2] = b;
+}
+
+
+void Color::getCMYK (double &c, double &m, double &y, double &k) const {
+ valarray<double> rgb(3), cmyk(4);
+ getRGB(rgb);
+ RGB2CMYK(rgb, cmyk);
+ c = cmyk[0];
+ m = cmyk[1];
+ y = cmyk[2];
+ k = cmyk[3];
+}
+
+
+void Color::getCMYK (std::valarray<double> &cmyk) const {
+ cmyk.resize(4);
+ valarray<double> rgb(3);
+ getRGB(rgb);
+ RGB2CMYK(rgb, cmyk);
+}
+
+
+void Color::getXYZ (double &x, double &y, double &z) const {
+ valarray<double> rgb(3), xyz(3);
+ getRGB(rgb);
+ RGB2XYZ(rgb, xyz);
+ x = xyz[0];
+ y = xyz[1];
+ z = xyz[2];
+}
+
+
+void Color::setXYZ (double x, double y, double z) {
+ valarray<double> xyz(3), rgb(3);
+ xyz[0] = x;
+ xyz[1] = y;
+ xyz[2] = z;
+ XYZ2RGB(xyz, rgb);
+ setRGB(rgb);
+}
+
+
+void Color::setXYZ (const valarray<double> &xyz) {
+ valarray<double> rgb(3);
+ XYZ2RGB(xyz, rgb);
+ setRGB(rgb);
+}
+
+
+void Color::setLab (double l, double a, double b) {
+ valarray<double> lab(3), xyz(3);
+ lab[0] = l;
+ lab[1] = a;
+ lab[2] = b;
+ Lab2XYZ(lab, xyz);
+ setXYZ(xyz);
+}
+
+
+void Color::setLab (const valarray<double> &lab) {
+ valarray<double> xyz(3);
+ Lab2XYZ(lab, xyz);
+ setXYZ(xyz);
+}
+
+
+/** Get the color in CIELAB color space using the sRGB working space and reference white D65. */
+void Color::getLab (double &l, double &a, double &b) const {
+ valarray<double> rgb(3), lab(3);
+ getRGB(rgb);
+ RGB2Lab(rgb, lab);
+ l = lab[0];
+ a = lab[1];
+ b = lab[2];
+}
+
+
+void Color::getLab (std::valarray<double> &lab) const {
+ lab.resize(3);
+ valarray<double> rgb(3);
+ getRGB(rgb);
+ RGB2Lab(rgb, lab);
+}
+
+
+// static inline double sqr (double x) {return x*x;}
+static inline double cube (double x) {return x*x*x;}
+
+void Color::Lab2XYZ (const valarray<double> &lab, valarray<double> &xyz) {
+ xyz.resize(3);
+ double wx=0.95047, wy=1.00, wz=1.08883; // reference white D65
+ double eps = 0.008856;
+ double kappa = 903.3;
+ double fy = (lab[0]+16)/116;
+ double fx = lab[1]/500 + fy;
+ double fz = fy - lab[2]/200;
+ double xr = (cube(fx) > eps ? cube(fx) : (116*fx-16)/kappa);
+ double yr = (lab[0] > kappa*eps ? cube((lab[0]+16)/116) : lab[0]/kappa);
+ double zr = (cube(fz) > eps ? cube(fz) : (116*fz-16)/kappa);
+ xyz[0] = xr*wx;
+ xyz[1] = yr*wy;
+ xyz[2] = zr*wz;
+}
+
+
+void Color::XYZ2RGB (const valarray<double> &xyz, valarray<double> &rgb) {
+ rgb.resize(3);
+ rgb[0] = 3.2404542*xyz[0] - 1.5371385*xyz[1] - 0.4985314*xyz[2];
+ rgb[1] = -0.9692660*xyz[0] + 1.8760108*xyz[1] + 0.0415560*xyz[2];
+ rgb[2] = 0.0556434*xyz[0] - 0.2040259*xyz[1] + 1.0572252*xyz[2];
+ for (int i=0; i < 3; i++)
+ rgb[i] = (rgb[i] <= 0.0031308 ? 12.92*rgb[i] : 1.055*pow(rgb[i], 1/2.4)-0.055);
+}
+
+
+void Color::RGB2XYZ (valarray<double> rgb, valarray<double> &xyz) {
+ xyz.resize(3);
+ for (int i=0; i < 3; i++)
+ rgb[i] = (rgb[i] <= 0.04045 ? rgb[i]/12.92 : pow((rgb[i]+0.055)/1.055, 2.4));
+ xyz[0] = 0.4124564*rgb[0] + 0.3575761*rgb[1] + 0.1804375*rgb[2];
+ xyz[1] = 0.2126729*rgb[0] + 0.7151522*rgb[1] + 0.0721750*rgb[2];
+ xyz[2] = 0.0193339*rgb[0] + 0.1191920*rgb[1] + 0.9503041*rgb[2];
+}
+
+
+void Color::RGB2Lab (const valarray<double> &rgb, valarray<double> &lab) {
+ double wx=0.95047, wy=1.00, wz=1.08883; // reference white D65
+ double eps = 0.008856;
+ double kappa = 903.3;
+ valarray<double> xyz(3);
+ RGB2XYZ(rgb, xyz);
+ xyz[0] /= wx;
+ xyz[1] /= wy;
+ xyz[2] /= wz;
+ double fx = (xyz[0] > eps ? pow(xyz[0], 1.0/3) : (kappa*xyz[0]+16)/116);
+ double fy = (xyz[1] > eps ? pow(xyz[1], 1.0/3) : (kappa*xyz[1]+16)/116);
+ double fz = (xyz[2] > eps ? pow(xyz[2], 1.0/3) : (kappa*xyz[2]+16)/116);
+ lab[0] = 116*fy-16;
+ lab[1] = 500*(fx-fy);
+ lab[2] = 200*(fy-fz);
+}
+
+
+#if 0
+/** Returns the Delta E difference (CIE 2000) between this and another color. */
+double Color::deltaE (const Color &c) const {
+ double l1, a1, b1;
+ double l2, a2, b2;
+ getLab(l1, a1, b1);
+ c.getLab(l2, a2, b2);
+ const double deltaL = l2-l1;
+ const double lBar = (l1+l2)/2;
+ const double c1 = sqrt(a1*a1 + b1*b1);
+ const double c2 = sqrt(a2*a2 + b2*b2);
+ const double cBar = (c1+c2)/2.0;
+ const double g = (1.0-sqrt(pow(cBar, 7.0)/(pow(cBar, 7.0)+6103515625.0)))/2.0;
+ const double aa1 = a1*(1.0+g);
+ const double aa2 = a2*(1.0+g);
+ const double cc1 = sqrt(aa1*aa1 + b1*b1);
+ const double cc2 = sqrt(aa2*aa2 + b2*b2);
+ const double ccBar = (cc1+cc2)/2.0;
+ const double deltaCC = cc2-cc1;
+ double hh1 = atan2(b1, aa1);
+ if (hh1 < 0) hh1 += deg2rad(360);
+ double hh2 = atan2(b2, aa2);
+ if (hh2 < 0) hh2 += deg2rad(360);
+ const double hhBar = (std::abs(hh1-hh2) > deg2rad(180) ? (hh1+hh2+deg2rad(360)) : (hh1+hh2))/2.0;
+ const double t = 1.0
+ - 0.17*cos(hhBar-deg2rad(30.0))
+ + 0.24*cos(2.0*hhBar)
+ + 0.32*cos(3.0*hhBar+deg2rad(6.0))
+ - 0.20*cos(4.0*hhBar-deg2rad(63.0));
+ double deltaHH = hh2-hh1;
+ if (deltaHH < deg2rad(-180))
+ deltaHH += deg2rad(360);
+ else if (deltaHH > deg2rad(180))
+ deltaHH -= deg2rad(360);
+ const double deltaHHH = 2.0*sqrt(cc1*cc2)*sin(deltaHH/2.0);
+ const double sl = 1.0 + 0.015*sqr(lBar-50.0)/sqrt(20.0+sqr(lBar-50.0));
+ const double sc = 1.0 + 0.045*ccBar;
+ const double sh = 1.0 + 0.015*ccBar*t;
+ const double deltaTheta = deg2rad(30)*exp(-sqr((hhBar-deg2rad(275))/deg2rad(25)));
+ const double rc = 2.0*sqrt(pow(ccBar, 7)/(pow(ccBar, 7)+6103515625.0));
+ const double rt = -rc*sin(2.0*deltaTheta);
+ const double deltaE = sqrt(sqr(deltaL/sl) + sqr(deltaCC/sc) + sqr(deltaHHH/sh) + rt*deltaCC/sc*deltaHHH/sh);
+ return deltaE;
+}
+#endif
+
+
+int Color::numComponents (ColorSpace colorSpace) {
+ switch (colorSpace) {
+ case ColorSpace::GRAY: return 1;
+ case ColorSpace::LAB:
+ case ColorSpace::RGB: return 3;
+ case ColorSpace::CMYK: return 4;
+ }
+ return 0;
+}
diff --git a/dviware/dvisvgm/src/Color.hpp b/dviware/dvisvgm/src/Color.hpp
new file mode 100644
index 0000000000..f522bded55
--- /dev/null
+++ b/dviware/dvisvgm/src/Color.hpp
@@ -0,0 +1,96 @@
+/*************************************************************************
+** Color.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef COLOR_HPP
+#define COLOR_HPP
+
+#include <string>
+#include <valarray>
+#include <vector>
+#include "VectorIterator.hpp"
+
+#ifdef TRANSPARENT
+#undef TRANSPARENT
+#endif
+
+class Color {
+ public:
+ static bool SUPPRESS_COLOR_NAMES;
+ static const Color BLACK;
+ static const Color WHITE;
+ static const Color TRANSPARENT;
+
+ enum class ColorSpace {GRAY, RGB, CMYK, LAB};
+
+ public:
+ Color () noexcept : _rgb(0) {}
+ explicit Color (uint32_t rgb) noexcept : _rgb(rgb) {}
+ Color (uint8_t r, uint8_t g, uint8_t b) noexcept {setRGB(r,g,b);}
+ Color (double r, double g, double b) noexcept {setRGB(r,g,b);}
+ explicit Color (const std::valarray<double> &rgb) noexcept {setRGB(rgb);}
+ explicit Color (const std::string &name);
+ explicit operator uint32_t () const {return _rgb;}
+ bool operator == (const Color &c) const {return _rgb == c._rgb;}
+ bool operator != (const Color &c) const {return _rgb != c._rgb;}
+ bool operator < (const Color &c) const {return _rgb < c._rgb;}
+ Color operator *= (double c);
+ Color operator * (double c) const {return Color(*this) *= c;}
+ void setRGB (uint8_t r, uint8_t g, uint8_t b) {_rgb = (r << 16) | (g << 8) | b;}
+ void setRGB (double r, double g, double b);
+ void setRGB (const std::valarray<double> &rgb) {setRGB(rgb[0], rgb[1], rgb[2]);}
+ bool setPSName (std::string name, bool case_sensitive=true);
+ void setGray (uint8_t g) {setRGB(g,g,g);}
+ void setGray (double g) {setRGB(g,g,g);}
+ void setGray (const std::valarray<double> &gray) {setRGB(gray[0], gray[0], gray[0]);}
+ void setHSB (double h, double s, double b);
+ void setCMYK (double c, double m, double y, double k);
+ void setCMYK (const std::valarray<double> &cmyk);
+ void setXYZ (double x, double y, double z);
+ void setXYZ (const std::valarray<double> &xyz);
+ void setLab (double l, double a, double b);
+ void setLab (const std::valarray<double> &lab);
+ void set (ColorSpace colorSpace, VectorIterator<double> &it);
+ double getGray () const;
+ void getGray (std::valarray<double> &gray) const;
+ void getRGB (double &r, double &g, double &b) const;
+ void getRGB (std::valarray<double> &rgb) const;
+ void getCMYK (double &c, double &m, double &y, double &k) const;
+ void getCMYK (std::valarray<double> &cmyk) const;
+ void getXYZ (double &x, double &y, double &z) const;
+ void getLab (double &l, double &a, double &b) const;
+ void getLab (std::valarray<double> &lab) const;
+ double deltaE (const Color &c) const;
+ std::string rgbString () const;
+ std::string svgColorString (bool rgbonly) const;
+ std::string svgColorString () const {return svgColorString(SUPPRESS_COLOR_NAMES);}
+ static void CMYK2RGB (const std::valarray<double> &cmyk, std::valarray<double> &rgb);
+ static void RGB2CMYK (const std::valarray<double> &rgb, std::valarray<double> &cmyk);
+ static void HSB2RGB (const std::valarray<double> &hsb, std::valarray<double> &rgb);
+ static void RGB2XYZ (std::valarray<double> rgb, std::valarray<double> &xyz);
+ static void XYZ2RGB (const std::valarray<double> &xyz, std::valarray<double> &rgb);
+ static void RGB2Lab (const std::valarray<double> &rgb, std::valarray<double> &lab);
+ static void Lab2XYZ (const std::valarray<double> &lab, std::valarray<double> &xyz);
+ static int numComponents (ColorSpace colorSpace);
+
+ private:
+ uint32_t _rgb;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/ColorSpecialHandler.cpp b/dviware/dvisvgm/src/ColorSpecialHandler.cpp
new file mode 100644
index 0000000000..4bfd494003
--- /dev/null
+++ b/dviware/dvisvgm/src/ColorSpecialHandler.cpp
@@ -0,0 +1,120 @@
+/*************************************************************************
+** ColorSpecialHandler.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <cstring>
+#include <sstream>
+#include <vector>
+#include "ColorSpecialHandler.hpp"
+#include "SpecialActions.hpp"
+
+using namespace std;
+
+
+static double read_double (istream &is) {
+ is.clear();
+ double v;
+ is >> v;
+ if (is.fail())
+ throw SpecialException("number expected");
+ return v;
+}
+
+
+/** Reads multiple double values from a given stream. The number of
+ * values read is determined by the size of the result vector.
+ * @param[in] is stream to be read from
+ * @param[out] vec the resulting values */
+static void read_doubles (istream &is, vector<double> &vec) {
+ for (double &val : vec)
+ val = read_double(is);
+}
+
+
+/** Reads a color statement from an input stream and converts it to a color object.
+ * A color statement has the following syntax:
+ * _color model_ _component values_
+ * Currently, the following color models are supported: rgb, cmyk, hsb and gray.
+ * Examples: rgb 1 0.5 0, gray 0.5
+ * @param[in] model the color model
+ * @param[in] is stream to be read from
+ * @return resulting Color object */
+Color ColorSpecialHandler::readColor (const string &model, istream &is) {
+ Color color;
+ if (model == "rgb") {
+ vector<double> rgb(3);
+ read_doubles(is, rgb);
+ color.setRGB(rgb[0], rgb[1], rgb[2]);
+ }
+ else if (model == "cmyk") {
+ vector<double> cmyk(4);
+ read_doubles(is, cmyk);
+ color.setCMYK(cmyk[0], cmyk[1], cmyk[2], cmyk[3]);
+ }
+ else if (model == "hsb") {
+ vector<double> hsb(3);
+ read_doubles(is, hsb);
+ color.setHSB(hsb[0], hsb[1], hsb[2]);
+ }
+ else if (model == "gray")
+ color.setGray(read_double(is));
+ else if (!color.setPSName(model, true))
+ throw SpecialException("unknown color statement");
+ return color;
+}
+
+
+/** Reads the color model (rgb, cmyk, hsb, or gray) and the corresponding color compontents
+ * from a given input stream.
+ * @param[in] is stream to be read from
+ * @return resulting Color object */
+Color ColorSpecialHandler::readColor (istream &is) {
+ string model;
+ is >> model;
+ return readColor(model, is);
+}
+
+
+bool ColorSpecialHandler::process (const string&, istream &is, SpecialActions &actions) {
+ string cmd;
+ is >> cmd;
+ if (cmd == "push") // color push <model> <params>
+ _colorStack.push(readColor(is));
+ else if (cmd == "pop") {
+ if (!_colorStack.empty()) // color pop
+ _colorStack.pop();
+ }
+ else { // color <model> <params>
+ while (!_colorStack.empty())
+ _colorStack.pop();
+ _colorStack.push(readColor(cmd, is));
+ }
+ if (_colorStack.empty())
+ actions.setColor(Color::BLACK);
+ else
+ actions.setColor(_colorStack.top());
+ return true;
+}
+
+
+vector<const char*> ColorSpecialHandler::prefixes() const {
+ vector<const char*> pfx {"color"};
+ return pfx;
+}
+
diff --git a/dviware/dvisvgm/src/ColorSpecialHandler.hpp b/dviware/dvisvgm/src/ColorSpecialHandler.hpp
new file mode 100644
index 0000000000..176253407e
--- /dev/null
+++ b/dviware/dvisvgm/src/ColorSpecialHandler.hpp
@@ -0,0 +1,44 @@
+/*************************************************************************
+** ColorSpecialHandler.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef COLORSPECIALHANDLER_HPP
+#define COLORSPECIALHANDLER_HPP
+
+#include <stack>
+#include <string>
+#include <vector>
+#include "Color.hpp"
+#include "SpecialHandler.hpp"
+
+
+class ColorSpecialHandler : public SpecialHandler {
+ public:
+ bool process (const std::string &prefix, std::istream &is, SpecialActions &actions) override;
+ static Color readColor (std::istream &is);
+ static Color readColor (const std::string &model, std::istream &is);
+ const char* name () const override {return "color";}
+ const char* info () const override {return "complete support of color specials";}
+ std::vector<const char*> prefixes() const override;
+
+ private:
+ std::stack<Color> _colorStack;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/CommandLine.hpp b/dviware/dvisvgm/src/CommandLine.hpp
new file mode 100644
index 0000000000..b1f2636040
--- /dev/null
+++ b/dviware/dvisvgm/src/CommandLine.hpp
@@ -0,0 +1,154 @@
+// This file was automatically generated by opt2cpp.
+// It is part of the dvisvgm package and published under the terms
+// of the GNU General Public License version 3, or (at your option) any later version.
+// See file COPYING for further details.
+// Copyright (C) 2016-2019 Martin Gieseking <martin.gieseking@uos.de>
+
+#ifndef COMMANDLINE_HPP
+#define COMMANDLINE_HPP
+
+#include <config.h>
+#include <array>
+#include <vector>
+#include "CLCommandLine.hpp"
+
+using CL::Option;
+using CL::TypedOption;
+
+class CommandLine : public CL::CommandLine {
+ public:
+ CommandLine () : CL::CommandLine(
+ "This program converts DVI files, as created by TeX/LaTeX, as well as\nEPS and PDF files to the XML-based scalable vector graphics format SVG.",
+ "[options] dvifile\n--eps [options] epsfile\n--pdf [options] pdffile",
+ "Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de>"
+ ) {}
+
+ CommandLine (int argc, char **argv) : CommandLine() {
+ parse(argc, argv);
+ }
+
+ // option variables
+ TypedOption<std::string, Option::ArgMode::REQUIRED> bboxOpt {"bbox", 'b', "size", "min", "set size of bounding box"};
+ TypedOption<std::string, Option::ArgMode::OPTIONAL> cacheOpt {"cache", 'C', "dir", "set/print path of cache directory"};
+ Option clipjoinOpt {"clipjoin", 'j', "compute intersection of clipping paths"};
+ Option colorOpt {"color", '\0', "colorize messages"};
+ Option colornamesOpt {"colornames", '\0', "prefer color names to RGB values if possible"};
+ Option commentsOpt {"comments", '\0', "add comments with additional information"};
+ Option epsOpt {"eps", 'E', "convert EPS file to SVG"};
+ Option exactBboxOpt {"exact-bbox", 'e', "compute exact glyph bounding boxes"};
+ TypedOption<std::string, Option::ArgMode::REQUIRED> fontFormatOpt {"font-format", 'f', "format", "svg", "select file format of embedded fonts"};
+ TypedOption<std::string, Option::ArgMode::REQUIRED> fontmapOpt {"fontmap", 'm', "filenames", "evaluate (additional) font map files"};
+ Option gradOverlapOpt {"grad-overlap", '\0', "create overlapping color gradient segments"};
+ TypedOption<int, Option::ArgMode::REQUIRED> gradSegmentsOpt {"grad-segments", '\0', "number", 20, "number of color gradient segments per row"};
+ TypedOption<double, Option::ArgMode::REQUIRED> gradSimplifyOpt {"grad-simplify", '\0', "delta", 0.05, "reduce level of detail for small segments"};
+ TypedOption<int, Option::ArgMode::OPTIONAL> helpOpt {"help", 'h', "mode", 0, "print this summary of options and exit"};
+ Option keepOpt {"keep", '\0', "keep temporary files"};
+ TypedOption<std::string, Option::ArgMode::REQUIRED> libgsOpt {"libgs", '\0', "filename", "set name of Ghostscript shared library"};
+ TypedOption<std::string, Option::ArgMode::REQUIRED> linkmarkOpt {"linkmark", 'L', "style", "box", "select how to mark hyperlinked areas"};
+ Option listSpecialsOpt {"list-specials", 'l', "print supported special sets and exit"};
+ TypedOption<double, Option::ArgMode::REQUIRED> magOpt {"mag", 'M', "factor", 4, "magnification of Metafont output"};
+ TypedOption<int, Option::ArgMode::OPTIONAL> noFontsOpt {"no-fonts", 'n', "variant", 0, "draw glyphs by using path elements"};
+ Option noMergeOpt {"no-merge", '\0', "don't merge adjacent text elements"};
+ Option noMktexmfOpt {"no-mktexmf", '\0', "don't try to create missing fonts"};
+ TypedOption<std::string, Option::ArgMode::OPTIONAL> noSpecialsOpt {"no-specials", 'S', "prefixes", "don't process [selected] specials"};
+ Option noStylesOpt {"no-styles", '\0', "don't use CSS styles to reference fonts"};
+ TypedOption<std::string, Option::ArgMode::OPTIONAL> optimizeOpt {"optimize", 'O', "modules", "all", "perform several SVG optimizations"};
+ TypedOption<std::string, Option::ArgMode::REQUIRED> outputOpt {"output", 'o', "pattern", "set name pattern of output files"};
+ TypedOption<std::string, Option::ArgMode::REQUIRED> pageOpt {"page", 'p', "ranges", "1", "choose page(s) to convert"};
+ TypedOption<std::string, Option::ArgMode::OPTIONAL> pageHashesOpt {"page-hashes", 'H', "params", "xxh64", "activate usage of page hashes"};
+ Option pdfOpt {"pdf", 'P', "convert PDF file to SVG"};
+ TypedOption<int, Option::ArgMode::REQUIRED> precisionOpt {"precision", 'd', "number", 0, "set number of decimal points (0-6)"};
+ TypedOption<double, Option::ArgMode::OPTIONAL> progressOpt {"progress", '\0', "delay", 0.5, "enable progress indicator"};
+ Option relativeOpt {"relative", 'R', "create relative path commands"};
+ TypedOption<double, Option::ArgMode::REQUIRED> rotateOpt {"rotate", 'r', "angle", "rotate page content clockwise"};
+ TypedOption<std::string, Option::ArgMode::REQUIRED> scaleOpt {"scale", 'c', "sx[,sy]", "scale page content"};
+ Option stdinOpt {"stdin", '\0', "read input file from stdin"};
+ Option stdoutOpt {"stdout", 's', "write SVG output to stdout"};
+ TypedOption<std::string, Option::ArgMode::OPTIONAL> tmpdirOpt {"tmpdir", '\0', "path", "set/print the directory for temporary files"};
+ TypedOption<bool, Option::ArgMode::OPTIONAL> traceAllOpt {"trace-all", 'a', "retrace", false, "trace all glyphs of bitmap fonts"};
+ TypedOption<std::string, Option::ArgMode::REQUIRED> transformOpt {"transform", 'T', "commands", "transform page content"};
+ TypedOption<std::string, Option::ArgMode::REQUIRED> translateOpt {"translate", 't', "tx[,ty]", "shift page content"};
+ TypedOption<unsigned, Option::ArgMode::REQUIRED> verbosityOpt {"verbosity", 'v', "level", 7, "set verbosity level (0-7)"};
+ TypedOption<bool, Option::ArgMode::OPTIONAL> versionOpt {"version", 'V', "extended", false, "print version and exit"};
+ TypedOption<int, Option::ArgMode::OPTIONAL> zipOpt {"zip", 'z', "level", 9, "create compressed .svgz file"};
+ TypedOption<double, Option::ArgMode::REQUIRED> zoomOpt {"zoom", 'Z', "factor", 1.0, "zoom page content"};
+
+ protected:
+ std::vector<OptSectPair>& options () const override {return _options;}
+ const char* section (size_t n) const override {return n < _sections.size() ? _sections[n] : nullptr;}
+
+ private:
+ std::array<const char*, 5> _sections {{
+ "Input options",
+ "SVG output options",
+ "SVG transformations",
+ "Processing options",
+ "Message options",
+ }};
+
+ mutable std::vector<OptSectPair> _options = {
+ {&pageOpt, 0},
+ {&fontmapOpt, 0},
+#if !defined(DISABLE_GS)
+ {&epsOpt, 0},
+#endif
+#if !defined(DISABLE_GS)
+ {&pdfOpt, 0},
+#endif
+ {&stdinOpt, 0},
+ {&bboxOpt, 1},
+#if !defined(DISABLE_GS)
+ {&clipjoinOpt, 1},
+#endif
+ {&colornamesOpt, 1},
+ {&commentsOpt, 1},
+#if !defined(DISABLE_WOFF)
+ {&fontFormatOpt, 1},
+#endif
+#if !defined(DISABLE_GS)
+ {&gradOverlapOpt, 1},
+#endif
+#if !defined(DISABLE_GS)
+ {&gradSegmentsOpt, 1},
+#endif
+#if !defined(DISABLE_GS)
+ {&gradSimplifyOpt, 1},
+#endif
+ {&linkmarkOpt, 1},
+ {&optimizeOpt, 1},
+ {&outputOpt, 1},
+ {&precisionOpt, 1},
+ {&relativeOpt, 1},
+ {&stdoutOpt, 1},
+ {&tmpdirOpt, 1},
+ {&noFontsOpt, 1},
+ {&noMergeOpt, 1},
+ {&noStylesOpt, 1},
+ {&zipOpt, 1},
+ {&rotateOpt, 2},
+ {&scaleOpt, 2},
+ {&translateOpt, 2},
+ {&transformOpt, 2},
+ {&zoomOpt, 2},
+ {&cacheOpt, 3},
+ {&exactBboxOpt, 3},
+ {&keepOpt, 3},
+#if !defined(HAVE_LIBGS) && !defined(DISABLE_GS)
+ {&libgsOpt, 3},
+#endif
+ {&magOpt, 3},
+ {&noMktexmfOpt, 3},
+ {&noSpecialsOpt, 3},
+ {&pageHashesOpt, 3},
+ {&traceAllOpt, 3},
+ {&colorOpt, 4},
+ {&helpOpt, 4},
+ {&listSpecialsOpt, 4},
+ {&progressOpt, 4},
+ {&verbosityOpt, 4},
+ {&versionOpt, 4},
+ };
+};
+
+#endif
+
diff --git a/dviware/dvisvgm/src/DLLoader.cpp b/dviware/dvisvgm/src/DLLoader.cpp
new file mode 100644
index 0000000000..b047203801
--- /dev/null
+++ b/dviware/dvisvgm/src/DLLoader.cpp
@@ -0,0 +1,59 @@
+/*************************************************************************
+** DLLoader.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include "DLLoader.hpp"
+
+using namespace std;
+
+
+/** Creates a new DLLoader object and tries to load the given dynamic/shared library.
+ * @param[in] dlname name of library to load */
+DLLoader::DLLoader (const string &dlname) : _handle(nullptr) {
+ loadLibrary(dlname);
+}
+
+
+/** Releases the currently assigned dynamic/shared library and loads another one.
+ * @param[in] dlname name of library to load */
+bool DLLoader::loadLibrary (const string &dlname) {
+ closeLibrary();
+ if (!dlname.empty()) {
+#ifdef _WIN32
+ _handle = LoadLibrary(dlname.c_str());
+#else
+ _handle = dlopen(dlname.c_str(), RTLD_LAZY);
+#endif
+ }
+ return _handle != nullptr;
+}
+
+
+/** Releases the library currently assigned to the DLLoader object. */
+void DLLoader::closeLibrary () {
+ if (_handle) {
+#ifdef _WIN32
+ FreeLibrary(_handle);
+#else
+ dlclose(_handle);
+#endif
+ _handle = nullptr;
+ }
+}
+
diff --git a/dviware/dvisvgm/src/DLLoader.hpp b/dviware/dvisvgm/src/DLLoader.hpp
new file mode 100644
index 0000000000..6bfff573e2
--- /dev/null
+++ b/dviware/dvisvgm/src/DLLoader.hpp
@@ -0,0 +1,72 @@
+/*************************************************************************
+** DLLoader.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef DLLOADER_HPP
+#define DLLOADER_HPP
+
+#include <string>
+
+#ifdef _WIN32
+ #include "windows.hpp"
+#else
+ #include <dlfcn.h>
+#endif
+
+
+class DLLoader {
+ public:
+ DLLoader () =delete;
+ explicit DLLoader (const std::string &dlname);
+ DLLoader (DLLoader &&loader) =default;
+ virtual ~DLLoader () {closeLibrary();}
+ bool loaded () const {return _handle != nullptr;}
+ bool loadLibrary (const std::string &dlname);
+
+ protected:
+ template <typename T> T loadSymbol (const char *name) const;
+ void closeLibrary ();
+
+ private:
+#ifdef _WIN32
+ HINSTANCE _handle;
+#else
+ void *_handle;
+#endif
+};
+
+
+/** Loads a function or variable from the dynamic/shared library.
+ * @param[in] name name of function/variable to load
+ * @return pointer to loaded symbol, or 0 if the symbol could not be loaded */
+template <typename T>
+T DLLoader::loadSymbol (const char *name) const {
+ if (_handle) {
+#ifdef _WIN32
+ return reinterpret_cast<T>(GetProcAddress(_handle, name));
+#else
+ return reinterpret_cast<T>(dlsym(_handle, name));
+#endif
+ }
+ return nullptr;
+}
+
+#define LOAD_SYMBOL(sym) loadSymbol<decltype(&sym)>(#sym)
+
+#endif
diff --git a/dviware/dvisvgm/src/DVIActions.hpp b/dviware/dvisvgm/src/DVIActions.hpp
new file mode 100644
index 0000000000..c42f446d9e
--- /dev/null
+++ b/dviware/dvisvgm/src/DVIActions.hpp
@@ -0,0 +1,46 @@
+/*************************************************************************
+** DVIActions.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef DVIACTIONS_HPP
+#define DVIACTIONS_HPP
+
+#include <string>
+#include <vector>
+
+class BoundingBox;
+class Font;
+
+struct DVIActions {
+ virtual ~DVIActions () =default;
+ virtual void reset () {}
+ virtual void setChar (double x, double y, unsigned c, bool vertical, const Font &f) {}
+ virtual void setRule (double x, double y, double height, double width) {}
+ virtual void setTextOrientation (bool vertical) {}
+ virtual void moveToX (double x, bool forceSVGMove) {}
+ virtual void moveToY (double y, bool forceSVGMove) {}
+ virtual void setFont (int num, const Font &font) {}
+ virtual void special (const std::string &s, double dvi2bp, bool preprocessing=false) {}
+ virtual void beginPage (unsigned pageno, const std::vector<int32_t> &c) {}
+ virtual void endPage (unsigned pageno) {}
+ virtual BoundingBox& bbox () =0;
+ virtual void progress (size_t current, size_t total, const char *id=nullptr) {}
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/DVIReader.cpp b/dviware/dvisvgm/src/DVIReader.cpp
new file mode 100644
index 0000000000..38862839a0
--- /dev/null
+++ b/dviware/dvisvgm/src/DVIReader.cpp
@@ -0,0 +1,706 @@
+/*************************************************************************
+** DVIReader.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <cstdarg>
+#include <fstream>
+#include <sstream>
+#include "Color.hpp"
+#include "DVIActions.hpp"
+#include "DVIReader.hpp"
+#include "Font.hpp"
+#include "FontManager.hpp"
+#include "HashFunction.hpp"
+#include "VectorStream.hpp"
+
+using namespace std;
+
+DVIReader::DVIReader (istream &is) : BasicDVIReader(is)
+{
+ _inPage = false;
+ _dvi2bp = 0.0;
+ _inPostamble = false;
+ _currFontNum = 0;
+ _currPageNum = 0;
+ _mag = 1;
+ executePreamble();
+ collectBopOffsets();
+ executePostamble();
+}
+
+
+int DVIReader::executeCommand () {
+ int opcode = BasicDVIReader::executeCommand();
+ return opcode;
+}
+
+
+/** Executes all DVI commands read from the input stream. */
+void DVIReader::executeAll () {
+ int opcode = 0;
+ while (!eof() && opcode >= 0) {
+ try {
+ opcode = executeCommand();
+ }
+ catch (const InvalidDVIFileException &e) {
+ // end of stream reached
+ opcode = -1;
+ }
+ }
+}
+
+
+/** Reads and executes the commands of a single page.
+ * This methods stops reading after the page's eop command has been executed.
+ * @param[in] n number of page to be executed (1-based)
+ * @returns true if page was read successfully */
+bool DVIReader::executePage (unsigned n) {
+ clearStream(); // reset all status bits
+ if (!isStreamValid())
+ throw DVIException("invalid DVI file");
+ if (n < 1 || n > numberOfPages())
+ return false;
+
+ seek(_bopOffsets[n-1]); // goto bop of n-th page
+ _inPostamble = false; // not in postamble
+ _currPageNum = n;
+ while (executeCommand() != OP_EOP);
+ return true;
+}
+
+
+void DVIReader::executePreamble () {
+ clearStream();
+ if (isStreamValid()) {
+ seek(0);
+ if (readByte() == OP_PRE) {
+ cmdPre(0);
+ return;
+ }
+ }
+ throw DVIException("invalid DVI file");
+}
+
+
+/** Moves stream pointer to begin of postamble */
+void DVIReader::goToPostamble () {
+ clearStream();
+ if (!isStreamValid())
+ throw DVIException("invalid DVI file");
+
+ seek(-1, ios::end); // stream pointer to last byte
+ int count=0;
+ while (peek() == DVI_FILL) { // skip fill bytes
+ seek(-1, ios::cur);
+ count++;
+ }
+ if (count < 4) // the standard requires at least 4 trailing fill bytes
+ throw DVIException("missing fill bytes at end of file");
+
+ seek(-4, ios::cur); // now at first byte of q (pointer to begin of postamble)
+ uint32_t q = readUnsigned(4); // pointer to begin of postamble
+ seek(q); // now at begin of postamble
+}
+
+
+/** Reads and executes the commands of the postamble. */
+void DVIReader::executePostamble () {
+ goToPostamble();
+ while (executeCommand() != OP_POSTPOST); // executes all commands until post_post (= 249) is reached
+}
+
+
+/** Collects and records the file offsets of all bop commands. */
+void DVIReader::collectBopOffsets () {
+ goToPostamble();
+ _bopOffsets.push_back(tell()); // also add offset of postamble
+ readByte(); // skip post command
+ uint32_t offset = readUnsigned(4); // offset of final bop
+ while (int32_t(offset) != -1) { // not yet on first bop?
+ _bopOffsets.push_back(offset); // record offset
+ seek(offset); // now on previous bop
+ if (readByte() != OP_BOP)
+ throw DVIException("bop offset at "+to_string(offset)+" doesn't point to bop command" );
+ seek(40, ios::cur); // skip the 10 \count values => now on offset of previous bop
+ uint32_t prevOffset = readUnsigned(4);
+ if ((prevOffset >= offset && int32_t(prevOffset) != -1))
+ throw DVIException("invalid bop offset at "+to_string(tell()-static_cast<streamoff>(4)));
+ offset = prevOffset;
+ }
+ reverse(_bopOffsets.begin(), _bopOffsets.end());
+}
+
+
+/** Computes a hash value for a given page. The hash algorithm is selected by
+ * a HashFunction object which will also contain the resulting hash value if
+ * this function returns true.
+ * @param[in] pageno number of page to process (1-based)
+ * @param[in,out] hashFunc hash function to use
+ * @return true on success, hashFunc contains the resulting hash value */
+bool DVIReader::computePageHash (size_t pageno, HashFunction &hashFunc) {
+ if (pageno == 0 || pageno > numberOfPages())
+ return false;
+
+ hashFunc.reset();
+ clearStream();
+ seek(_bopOffsets[pageno-1]+45); // now on first command after bop of selected page
+ const size_t BUFSIZE = 4096;
+ char buf[BUFSIZE];
+ size_t numBytes = numberOfPageBytes(pageno-1)-46; // number of bytes excluding bop and eop
+ while (numBytes > 0) {
+ getInputStream().read(buf, min(numBytes, BUFSIZE));
+ hashFunc.update(buf, getInputStream().gcount());
+ numBytes -= getInputStream().gcount();
+ }
+ return true;
+}
+
+
+/////////////////////////////////////
+
+/** Reads and executes DVI preamble command.
+ * Format: pre ver[1] num[4] den[4] mag[4] cmtlen[1] cmt[cmtlen] */
+void DVIReader::cmdPre (int) {
+ uint8_t id = readUnsigned(1);
+ setDVIVersion(DVIVersion(id)); // identification number
+ uint32_t numer = readUnsigned(4); // numerator units of measurement
+ uint32_t denom = readUnsigned(4); // denominator units of measurement
+ if (denom == 0)
+ throw DVIException("denominator of measurement unit is zero");
+ _mag = readUnsigned(4); // magnification
+ uint32_t k = readUnsigned(1); // length of following comment
+ string comment = readString(k);
+ // 1 dviunit * num/den == multiples of 0.0000001m
+ // 1 dviunit * _dvibp: length of 1 dviunit in PS points * _mag/1000
+ _dvi2bp = numer/254000.0*72.0/denom*_mag/1000.0;
+ dviPre(id, numer, denom, _mag, comment);
+}
+
+
+/** Reads and executes DVI postamble command.
+ * Format: post p[4] num[4] den[4] mag[4] ph[4] pw[4] sd[2] np[2] */
+void DVIReader::cmdPost (int) {
+ uint32_t prevBopOffset = readUnsigned(4);
+ uint32_t numer = readUnsigned(4);
+ uint32_t denom = readUnsigned(4);
+ if (denom == 0)
+ throw DVIException("denominator of measurement unit is zero");
+ _mag = readUnsigned(4);
+ uint32_t pageHeight = readUnsigned(4); // height of tallest page in dvi units
+ uint32_t pageWidth = readUnsigned(4); // width of widest page in dvi units
+ uint16_t stackDepth = readUnsigned(2); // max. stack depth required
+ uint16_t numPages = readUnsigned(2);
+ if (numPages != (numberOfPages() & 0xffff))
+ throw DVIException("page count in postamble doesn't match actual number of pages");
+
+ // 1 dviunit * num/den == multiples of 0.0000001m
+ // 1 dviunit * _dvi2bp: length of 1 dviunit in PS points * _mag/1000
+ _dvi2bp = numer/254000.0*72.0/denom*_mag/1000.0;
+ _inPostamble = true;
+ dviPost(stackDepth, numPages, pageWidth*_dvi2bp, pageHeight*_dvi2bp, _mag, numer, denom, prevBopOffset);
+}
+
+
+/** Reads and executes DVI post_post command.
+ * Format: post_post q[4] i[1] 223[>=4] */
+void DVIReader::cmdPostPost (int) {
+ _inPostamble = false;
+ uint32_t postOffset = readUnsigned(4); // pointer to begin of postamble
+ uint8_t id = readUnsigned(1);
+ setDVIVersion(DVIVersion(id)); // identification byte
+ while (readUnsigned(1) == DVI_FILL); // skip fill bytes (223), eof bit should be set now
+ dviPostPost(DVIVersion(id), postOffset);
+}
+
+
+/** Reads and executes Begin-Of-Page command.
+ * Format: bop c0[+4] ... c9[+4] p[+4] */
+void DVIReader::cmdBop (int) {
+ vector<int32_t> c(10);
+ for (int i=0; i < 10; i++)
+ c[i] = readSigned(4);
+ int32_t prevBopOffset = readSigned(4); // pointer to peceeding bop (-1 in case of first page)
+ _dviState.reset(); // set all DVI registers to 0
+ while (!_stateStack.empty())
+ _stateStack.pop();
+ _currFontNum = 0;
+ _inPage = true;
+ dviBop(c, prevBopOffset);
+}
+
+
+/** Reads and executes End-Of-Page command. */
+void DVIReader::cmdEop (int) {
+ if (!_stateStack.empty())
+ throw DVIException("stack not empty at end of page");
+ _inPage = false;
+ dviEop();
+}
+
+
+/** Reads and executes push command. */
+void DVIReader::cmdPush (int) {
+ _stateStack.push(_dviState);
+ dviPush();
+}
+
+
+/** Reads and executes pop command (restores pushed position information). */
+void DVIReader::cmdPop (int) {
+ if (_stateStack.empty())
+ throw DVIException("stack empty at pop command");
+ _dviState = _stateStack.top();
+ _stateStack.pop();
+ dviPop();
+}
+
+
+/** Helper function that handles charaters from virtual fonts (VF).
+ * It is called by the cmdSetChar and cmdPutChar methods.
+ * @param[in] font current font (corresponding to _currFontNum)
+ * @param[in] c character to typeset */
+void DVIReader::putVFChar (Font *font, uint32_t c) {
+ if (auto vf = dynamic_cast<VirtualFont*>(font)) { // is current font a virtual font?
+ if (const vector<uint8_t> *dvi = vf->getDVI(c)) { // try to get DVI snippet that represents character c
+ FontManager &fm = FontManager::instance();
+ DVIState savedState = _dviState; // save current cursor position
+ _dviState.x = _dviState.y = _dviState.w = _dviState.z = 0;
+ int savedFontNum = _currFontNum; // save current font number
+ fm.enterVF(vf); // enter VF font number context
+ setFont(fm.vfFirstFontNum(vf), SetFontMode::VF_ENTER);
+ double savedScale = _dvi2bp;
+ // DVI units in virtual fonts are multiples of 1^(-20) times the scaled size of the VF
+ _dvi2bp = vf->scaledSize()/(1 << 20);
+ VectorInputStream<uint8_t> vis(*dvi);
+ istream &is = replaceStream(vis);
+ try {
+ executeAll(); // execute DVI fragment
+ }
+ catch (const DVIException &e) {
+ // Message::estream(true) << "invalid dvi in vf: " << e.getMessage() << endl; // @@
+ }
+ replaceStream(is); // restore previous input stream
+ _dvi2bp = savedScale; // restore previous scale factor
+ fm.leaveVF(); // restore previous font number context
+ setFont(savedFontNum, SetFontMode::VF_LEAVE); // restore previous font number
+ _dviState = savedState; // restore previous cursor position
+ }
+ }
+}
+
+
+/** Reads and executes set_char_x command. Puts a character at the current
+ * position and advances the cursor.
+ * @param[in] c character to set
+ * @throw DVIException if method is called ouside a bop/eop pair */
+void DVIReader::cmdSetChar0 (int c) {
+ if (!_inPage)
+ throw DVIException("setchar outside of page");
+ Font *font = FontManager::instance().getFont(_currFontNum);
+ dviSetChar0(c, font); // template method that may trigger further actions
+ putVFChar(font, c); // further character processing if current font is a virtual font
+ moveRight(font->charWidth(c)*font->scaleFactor()*_mag/1000.0, MoveMode::SETCHAR);
+}
+
+
+/** Reads and executes setx command. Puts a character at the current
+ * position and advances the cursor.
+ * @param[in] len number of parameter bytes (possible values: 1-4)
+ * @throw DVIException if method is called ouside a bop/eop pair */
+void DVIReader::cmdSetChar (int len) {
+ if (!_inPage)
+ throw DVIException("setchar outside of page");
+ // According to the dvi specification all character codes are unsigned
+ // except len == 4. At the moment all char codes are treated as unsigned...
+ uint32_t c = readUnsigned(len); // if len == 4 c may be signed
+ Font *font = FontManager::instance().getFont(_currFontNum);
+ dviSetChar(c, font); // template method that may trigger further actions
+ putVFChar(font, c); // further character processing if current font is a virtual font
+ moveRight(font->charWidth(c)*font->scaleFactor()*_mag/1000.0, MoveMode::SETCHAR);
+}
+
+
+/** Reads and executes putx command. Puts a character at the current
+ * position but doesn't change the cursor position.
+ * @param[in] len number of parameter bytes (possible values: 1-4)
+ * @throw DVIException if method is called ouside a bop/eop pair */
+void DVIReader::cmdPutChar (int len) {
+ if (!_inPage)
+ throw DVIException("putchar outside of page");
+ // According to the dvi specification all character codes are unsigned
+ // except len == 4. At the moment all char codes are treated as unsigned...
+ int32_t c = readUnsigned(len);
+ Font *font = FontManager::instance().getFont(_currFontNum);
+ dviPutChar(c, font);
+ putVFChar(font, c);
+}
+
+
+/** Reads and executes set_rule command. Puts a solid rectangle at the current
+ * position and updates the cursor position.
+ * Format: set_rule h[+4] w[+4]
+ * @throw DVIException if method is called ouside a bop/eop pair */
+void DVIReader::cmdSetRule (int) {
+ if (!_inPage)
+ throw DVIException("set_rule outside of page");
+ double height = _dvi2bp*readSigned(4);
+ double width = _dvi2bp*readSigned(4);
+ dviSetRule(height, width);
+ moveRight(width, MoveMode::CHANGEPOS);
+}
+
+
+/** Reads and executes set_rule command. Puts a solid rectangle at the current
+ * position but leaves the cursor position unchanged.
+ * Format: put_rule h[+4] w[+4]
+ * @throw DVIException if method is called ouside a bop/eop pair */
+void DVIReader::cmdPutRule (int) {
+ if (!_inPage)
+ throw DVIException("put_rule outside of page");
+ double height = _dvi2bp*readSigned(4);
+ double width = _dvi2bp*readSigned(4);
+ dviPutRule(height, width);
+}
+
+
+void DVIReader::moveRight (double dx, MoveMode) {
+ switch (_dviState.d) {
+ case WritingMode::LR: _dviState.h += dx; break;
+ case WritingMode::TB: _dviState.v += dx; break;
+ case WritingMode::BT: _dviState.v -= dx; break;
+ }
+}
+
+
+void DVIReader::moveDown (double dy, MoveMode) {
+ switch (_dviState.d) {
+ case WritingMode::LR: _dviState.v += dy; break;
+ case WritingMode::TB: _dviState.h -= dy; break;
+ case WritingMode::BT: _dviState.h += dy; break;
+ }
+}
+
+
+void DVIReader::cmdRight (int len) {
+ double dx = _dvi2bp*readSigned(len);
+ moveRight(dx, MoveMode::CHANGEPOS);
+ dviRight(dx);
+}
+
+
+void DVIReader::cmdDown (int len) {
+ double dy = _dvi2bp*readSigned(len);
+ moveDown(dy, MoveMode::CHANGEPOS);
+ dviDown(dy);
+}
+
+
+void DVIReader::cmdNop (int) {dviNop();}
+void DVIReader::cmdX0 (int) {moveRight(_dviState.x, MoveMode::CHANGEPOS); dviX0();}
+void DVIReader::cmdY0 (int) {moveDown(_dviState.y, MoveMode::CHANGEPOS); dviY0();}
+void DVIReader::cmdW0 (int) {moveRight(_dviState.w, MoveMode::CHANGEPOS); dviW0();}
+void DVIReader::cmdZ0 (int) {moveDown(_dviState.z, MoveMode::CHANGEPOS); dviZ0();}
+
+
+void DVIReader::cmdX (int len) {
+ double dx = _dvi2bp*readSigned(len);
+ _dviState.x = dx;
+ moveRight(dx, MoveMode::CHANGEPOS);
+ dviX(dx);
+}
+
+
+void DVIReader::cmdY (int len) {
+ double dy = _dvi2bp*readSigned(len);
+ _dviState.y = dy;
+ moveDown(dy, MoveMode::CHANGEPOS);
+ dviY(dy);
+}
+
+
+void DVIReader::cmdW (int len) {
+ double dx = _dvi2bp*readSigned(len);
+ _dviState.w = dx;
+ moveRight(dx, MoveMode::CHANGEPOS);
+ dviW(dx);
+}
+
+
+void DVIReader::cmdZ (int len) {
+ double dy = _dvi2bp*readSigned(len);
+ _dviState.z = dy;
+ moveDown(dy, MoveMode::CHANGEPOS);
+ dviZ(dy);
+}
+
+
+/** Sets the text orientation (horizontal, vertical).
+ * This command is only available in DVI version 3 (created by pTeX) */
+void DVIReader::cmdDir (int) {
+ uint8_t wmode = readUnsigned(1);
+ if (wmode == 4) // yoko mode (4) equals default LR mode (0)
+ wmode = 0;
+ if (wmode == 2 || wmode > 3) {
+ ostringstream oss;
+ oss << "invalid writing mode value " << wmode << " (0, 1, 3, or 4 expected)";
+ throw DVIException(oss.str());
+ }
+ _dviState.d = (WritingMode)wmode;
+ dviDir(_dviState.d);
+}
+
+
+void DVIReader::cmdXXX (int len) {
+ if (!_inPage)
+ throw DVIException("special outside of page");
+ uint32_t numBytes = readUnsigned(len);
+ string str = readString(numBytes);
+ dviXXX(str);
+}
+
+
+/** Change the current font.
+ * @param[in] fontnum local number of font to select
+ * @param[in] mode info on command that triggered the font change
+ * @throw DVIException if font number is undefined */
+void DVIReader::setFont (int fontnum, SetFontMode mode) {
+ if (const Font *font = FontManager::instance().getFont(fontnum)) {
+ _currFontNum = fontnum;
+ dviFontNum(uint32_t(fontnum), mode, font);
+ }
+ else {
+ ostringstream oss;
+ oss << "undefined font number " << fontnum;
+ throw DVIException(oss.str());
+ }
+}
+
+
+/** Selects a previously defined font by its number.
+ * @param[in] fontnum font number
+ * @throw DVIException if font number is undefined */
+void DVIReader::cmdFontNum0 (int fontnum) {
+ setFont(fontnum, SetFontMode::SF_SHORT);
+}
+
+
+/** Selects a previously defined font.
+ * @param[in] len size of font number variable (in bytes)
+ * @throw DVIException if font number is undefined */
+void DVIReader::cmdFontNum (int len) {
+ uint32_t fontnum = readUnsigned(len);
+ setFont(fontnum, SetFontMode::SF_LONG);
+}
+
+
+/** Helper function to handle a font definition.
+ * @param[in] fontnum local font number
+ * @param[in] name font name
+ * @param[in] cs checksum to be compared with TFM checksum
+ * @param[in] ds design size in PS point units
+ * @param[in] ss scaled size in PS point units */
+const Font* DVIReader::defineFont (uint32_t fontnum, const string &name, uint32_t cs, double ds, double ss) {
+ FontManager &fm = FontManager::instance();
+ Font *font = fm.getFont(fontnum);
+ if (!font) {
+ int id = fm.registerFont(fontnum, name, cs, ds, ss);
+ font = fm.getFontById(id);
+ if (auto vf = dynamic_cast<VirtualFont*>(font)) {
+ // read vf file, register its font and character definitions
+ fm.enterVF(vf);
+ ifstream ifs(vf->path(), ios::binary);
+ VFReader vfReader(ifs);
+ vfReader.replaceActions(this);
+ vfReader.executeAll();
+ fm.leaveVF();
+ }
+ }
+ return font;
+}
+
+
+/** Defines a new font.
+ * @param[in] len size of font number variable (in bytes) */
+void DVIReader::cmdFontDef (int len) {
+ uint32_t fontnum = readUnsigned(len); // font number
+ uint32_t checksum = readUnsigned(4); // font checksum (to be compared with corresponding TFM checksum)
+ uint32_t ssize = readUnsigned(4); // scaled size of font in DVI units
+ uint32_t dsize = readUnsigned(4); // design size of font in DVI units
+ uint32_t pathlen = readUnsigned(1); // length of font path
+ uint32_t namelen = readUnsigned(1); // length of font name
+ string path = readString(pathlen); // path to font file
+ string fontname = readString(namelen);
+ const Font *font = defineFont(fontnum, fontname, checksum, dsize*_dvi2bp, ssize*_dvi2bp);
+ dviFontDef(fontnum, checksum, font);
+}
+
+
+/** This template method is called by the VFReader after reading a font definition from a VF file.
+ * @param[in] fontnum local font number
+ * @param[in] path path to font file
+ * @param[in] name font name
+ * @param[in] checksum checksum to be compared with TFM checksum
+ * @param[in] dsize design size in PS point units
+ * @param[in] ssize scaled size in PS point units */
+void DVIReader::defineVFFont (uint32_t fontnum, const string &path, const string &name, uint32_t checksum, double dsize, double ssize) {
+ if (const VirtualFont *vf = FontManager::instance().getVF())
+ defineFont(fontnum, name, checksum, dsize, ssize * vf->scaleFactor());
+}
+
+
+/** This template method is called by the VFReader after reading a character definition from a VF file.
+ * @param[in] c character number
+ * @param[in] dvi DVI fragment describing the character */
+void DVIReader::defineVFChar (uint32_t c, vector<uint8_t> &&dvi) {
+ FontManager::instance().assignVFChar(c, std::move(dvi));
+}
+
+
+/** XDV extension: includes image or pdf file.
+ * parameters: box[1] matrix[4][6] p[2] len[2] path[l] */
+void DVIReader::cmdXPic (int) {
+ uint8_t box = readUnsigned(1); // box
+ vector<int32_t> matrix(6);
+ for (int i=0; i < 6; i++) // matrix
+ matrix[i] = readSigned(4);
+ int16_t page = readSigned(2); // page number
+ uint16_t len = readUnsigned(2);
+ string path = readString(len); // path to image/pdf file
+ dviXPic(box, matrix, page, path);
+}
+
+
+/** XDV extension: defines a native font */
+void DVIReader::cmdXFontDef (int) {
+ int32_t fontnum = readSigned(4);
+ double ptsize = _dvi2bp*readUnsigned(4);
+ uint16_t flags = readUnsigned(2);
+ uint8_t psname_len = readUnsigned(1);
+ uint8_t fmname_len = getDVIVersion() == DVI_XDV5 ? readUnsigned(1) : 0; // length of family name
+ uint8_t stname_len = getDVIVersion() == DVI_XDV5 ? readUnsigned(1) : 0; // length of style name
+ string fontname = readString(psname_len);
+ uint32_t fontIndex=0;
+ if (getDVIVersion() == DVI_XDV5)
+ seek(fmname_len+stname_len, ios::cur);
+ else
+ fontIndex = readUnsigned(4);
+ FontStyle style;
+ Color color;
+ if (flags & 0x0100) { // vertical?
+ }
+ if (flags & 0x0200) { // colored?
+ // The font color must not interfere with color specials. If the font color is not black,
+ // all color specials should be ignored, i.e. glyphs of a non-black fonts have a fixed color
+ // that can't be changed by color specials.
+ uint32_t rgba = readUnsigned(4);
+ color.setRGB(uint8_t(rgba >> 24), uint8_t((rgba >> 16) & 0xff), uint8_t((rgba >> 8) & 0xff));
+ }
+ if (flags & 0x1000) // extend?
+ style.extend = _dvi2bp*readSigned(4);
+ if (flags & 0x2000) // slant?
+ style.slant = _dvi2bp*readSigned(4);
+ if (flags & 0x4000) // embolden?
+ style.bold = _dvi2bp*readSigned(4);
+ if ((flags & 0x0800) && (getDVIVersion() == DVI_XDV5)) { // variations?
+ uint16_t num_variations = readSigned(2);
+ for (int i=0; i < num_variations; i++)
+ readUnsigned(4);
+ }
+ const Font *font = FontManager::instance().getFont(fontnum);
+ if (!font) {
+ FontManager::instance().registerFont(fontnum, fontname, fontIndex, ptsize, style, color);
+ font = FontManager::instance().getFont(fontnum);
+ }
+ dviXFontDef(fontnum, dynamic_cast<const NativeFont*>(font));
+}
+
+
+/** XDV extension: prints an array of characters where each character
+ * can take independent x and y coordinates.
+ * parameters: w[4] n[2] (dx,dy)[(4+4)n] glyphs[2n] */
+void DVIReader::cmdXGlyphArray (int) {
+ vector<double> dx, dy;
+ vector<uint16_t> glyphs;
+ double width = putGlyphArray(false, dx, dy, glyphs);
+ if (Font *font = FontManager::instance().getFont(_currFontNum))
+ dviXGlyphArray(dx, dy, glyphs, *font);
+ else
+ throw DVIException("missing setfont prior to xglypharray");
+ moveRight(width, MoveMode::SETCHAR);
+}
+
+
+/** XDV extension: prints an array/string of characters where each character
+ * can take independent x coordinates whereas all share a single y coordinate.
+ * parameters: w[4] n[2] dx[4n] dy[4] glyphs[2n] */
+void DVIReader::cmdXGlyphString (int) {
+ vector<double> dx, dy;
+ vector<uint16_t> glyphs;
+ double width = putGlyphArray(true, dx, dy, glyphs);
+ if (Font *font = FontManager::instance().getFont(_currFontNum))
+ dviXGlyphString(dx, glyphs, *font);
+ else
+ throw DVIException("missing setfont prior to xglyphstring");
+ moveRight(width, MoveMode::SETCHAR);
+}
+
+
+/** XDV extension: Same as cmdXGlyphArray plus a leading array of UTF-16 characters
+ * that specify the "actual text" represented by the glyphs to be printed. It usually
+ * contains the text with special characters (like ligatures) expanded so that it
+ * can be used for text search, plain text copy & paste etc. This XDV command was
+ * introduced with XeTeX 0.99995 and can be triggered by <tt>\\XeTeXgenerateactualtext1</tt>.
+ * parameters: l[2] chars[2l] w[4] n[2] (dx,dy)[8n] glyphs[2n] */
+void DVIReader::cmdXTextAndGlyphs (int) {
+ uint16_t l = readUnsigned(2); // number of chars
+ vector<uint16_t> chars(l);
+ for (int i=0; i < l; i++)
+ chars[i] = readUnsigned(2);
+ vector<double> x, y;
+ vector<uint16_t> glyphs;
+ double width = putGlyphArray(false, x, y, glyphs);
+ if (Font *font = FontManager::instance().getFont(_currFontNum))
+ dviXTextAndGlyphs(x, y, chars, glyphs, *font);
+ else
+ throw DVIException("missing setfont prior to xtextandglyphs");
+ moveRight(width, MoveMode::SETCHAR);
+}
+
+
+/** Implements the common functionality of cmdXGlyphArray, cmdXGlyphString, and cmdXTextAndGlyphs.
+ * @param[in] xonly indicates if the characters share a single y coordinate (xonly==true)
+ * @param[out] dx relative horizontal positions of each glyph
+ * @param[out] dy relative vertical positions of each glyph
+ * @param[out] glyphs FreeType indices of the glyphs to typeset
+ * @return total width of the glyph array */
+double DVIReader::putGlyphArray (bool xonly, vector<double> &dx, vector<double> &dy, vector<uint16_t> &glyphs) {
+ double strwidth = _dvi2bp*readSigned(4);
+ uint16_t num_glyphs = readUnsigned(2);
+ dx.resize(num_glyphs);
+ dy.resize(num_glyphs);
+ glyphs.resize(num_glyphs);
+ for (int i=0; i < num_glyphs; i++) {
+ dx[i] = readSigned(4)*_dvi2bp;
+ dy[i] = xonly ? 0 : readSigned(4)*_dvi2bp;
+ }
+ for (int i=0; i < num_glyphs; i++)
+ glyphs[i] = readUnsigned(2);
+ return strwidth;
+}
diff --git a/dviware/dvisvgm/src/DVIReader.hpp b/dviware/dvisvgm/src/DVIReader.hpp
new file mode 100644
index 0000000000..972af2022a
--- /dev/null
+++ b/dviware/dvisvgm/src/DVIReader.hpp
@@ -0,0 +1,174 @@
+/*************************************************************************
+** DVIReader.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef DVIREADER_HPP
+#define DVIREADER_HPP
+
+#include <limits>
+#include <map>
+#include <stack>
+#include <string>
+#include "BasicDVIReader.hpp"
+#include "Color.hpp"
+#include "VFActions.hpp"
+
+class Font;
+class FontStyle;
+class HashFunction;
+class NativeFont;
+class VirtualFont;
+
+class DVIReader : public BasicDVIReader, public VFActions {
+ protected:
+ enum class WritingMode {LR=0, TB=1, BT=3};
+ enum class SetFontMode {SF_SHORT, SF_LONG, VF_ENTER, VF_LEAVE};
+ enum class MoveMode {SETCHAR, CHANGEPOS};
+
+ struct DVIState {
+ double h, v; ///< horizontal and vertical cursor position
+ double x, w, y, z; ///< additional registers to store horizontal (x, w) and vertical (y, z) positions
+ WritingMode d; ///< direction: 0: horizontal, 1: vertical(top->bottom), 3: vertical (bottom->top)
+ DVIState () {reset();}
+ void reset () {h = v = x = w = y = z = 0.0; d=WritingMode::LR;}
+ };
+
+ public:
+ explicit DVIReader (std::istream &is);
+ bool executeDocument ();
+ void executeAll ();
+ void executePreamble ();
+ void executePostamble ();
+ bool executePage (unsigned n);
+ bool inPostamble () const {return _inPostamble;}
+ double getXPos () const override {return _dviState.h;}
+ double getYPos () const override {return _dviState.v;}
+ int stackDepth () const override {return _stateStack.size();}
+ int currentFontNumber () const {return _currFontNum;}
+ unsigned currentPageNumber () const override {return _currPageNum;}
+ unsigned numberOfPages () const {return _bopOffsets.empty() ? 0 : _bopOffsets.size()-1;}
+
+ protected:
+ int executeCommand () override;
+ void collectBopOffsets ();
+ size_t numberOfPageBytes (int n) const {return _bopOffsets.size() > 1 ? _bopOffsets[n+1]-_bopOffsets[n] : 0;}
+ bool computePageHash (size_t pageno, HashFunction &hashFunc);
+ void goToPostamble ();
+ virtual void moveRight (double dx, MoveMode mode);
+ virtual void moveDown (double dy, MoveMode mode);
+ void putVFChar (Font *font, uint32_t c);
+ double putGlyphArray (bool xonly, std::vector<double> &dx, std::vector<double> &dy, std::vector<uint16_t> &glyphs);
+ const Font* defineFont (uint32_t fontnum, const std::string &name, uint32_t cs, double ds, double ss);
+ void setFont (int num, SetFontMode mode);
+ const DVIState& dviState() const {return _dviState;}
+ double dvi2bp () const {return _dvi2bp;}
+ bool inPage () const {return _inPage;}
+
+ // VFAction methods
+ void defineVFFont (uint32_t fontnum, const std::string &path, const std::string &name, uint32_t checksum, double dsize, double ssize) override;
+ void defineVFChar (uint32_t c, std::vector<uint8_t> &&dvi) override;
+
+ // The following template methods provide higher-level access to the DVI commands.
+ // In contrast to their cmdXXX pendants, they don't require any handling of the input stream.
+ // All command arguments are delivered as function parameters. These methods are called after
+ // the DVI registers have been updated, i.e. currState() represents the state after executing
+ // the command. If the previous register state is required, prevState() can be used.
+ virtual void dviPre (uint8_t id, uint32_t numer, uint32_t denom, uint32_t mag, const std::string &comment) {}
+ virtual void dviPost (uint16_t stdepth, uint16_t pages, uint32_t pw, uint32_t ph, uint32_t mag, uint32_t num, uint32_t den, uint32_t lbopofs) {}
+ virtual void dviPostPost (uint8_t id, uint32_t postOffset) {}
+ virtual void dviBop (const std::vector<int32_t> &c, int32_t prevBopOffset) {}
+ virtual void dviEop () {}
+ virtual void dviSetChar0 (uint32_t c, const Font *font) {}
+ virtual void dviSetChar (uint32_t c, const Font *font) {}
+ virtual void dviPutChar (uint32_t c, const Font *font) {}
+ virtual void dviSetRule (double h, double w) {}
+ virtual void dviPutRule (double h, double w) {}
+ virtual void dviNop () {}
+ virtual void dviPush () {}
+ virtual void dviPop () {}
+ virtual void dviRight (double dx) {}
+ virtual void dviDown (double dy) {}
+ virtual void dviX0 () {}
+ virtual void dviY0 () {}
+ virtual void dviW0 () {}
+ virtual void dviZ0 () {}
+ virtual void dviX (double x) {}
+ virtual void dviY (double y) {}
+ virtual void dviW (double w) {}
+ virtual void dviZ (double z) {}
+ virtual void dviFontDef (uint32_t fontnum, uint32_t checksum, const Font *font) {}
+ virtual void dviFontNum (uint32_t fontnum, SetFontMode mode, const Font *font) {}
+ virtual void dviDir (WritingMode dir) {}
+ virtual void dviXXX (const std::string &str) {}
+ virtual void dviXPic (uint8_t box, const std::vector<int32_t> &matrix, int16_t page, const std::string &path) {}
+ virtual void dviXFontDef (uint32_t fontnum, const NativeFont *font) {}
+ virtual void dviXGlyphArray (std::vector<double> &dx, std::vector<double> &dy, std::vector<uint16_t> &glyphs, const Font &font) {}
+ virtual void dviXGlyphString (std::vector<double> &dx, std::vector<uint16_t> &glyphs, const Font &font) {}
+ virtual void dviXTextAndGlyphs (std::vector<double> &dx, std::vector<double> &dy, std::vector<uint16_t> &chars, std::vector<uint16_t> &glyphs, const Font &font) {}
+
+ private:
+ // The following low-level methods evaluate the DVI commands read from
+ // the input stream and call the corresponding dviXXX methods.
+ void cmdSetChar0 (int c) override;
+ void cmdSetChar (int len) override;
+ void cmdPutChar (int len) override;
+ void cmdSetRule (int len) override;
+ void cmdPutRule (int len) override;
+ void cmdNop (int len) override;
+ void cmdBop (int len) override;
+ void cmdEop (int len) override;
+ void cmdPush (int len) override;
+ void cmdPop (int len) override;
+ void cmdDir (int len) override;
+ void cmdRight (int len) override;
+ void cmdDown (int len) override;
+ void cmdX0 (int len) override;
+ void cmdY0 (int len) override;
+ void cmdW0 (int len) override;
+ void cmdZ0 (int len) override;
+ void cmdX (int len) override;
+ void cmdY (int len) override;
+ void cmdW (int len) override;
+ void cmdZ (int len) override;
+ void cmdFontDef (int len) override;
+ void cmdFontNum0 (int n) override;
+ void cmdFontNum (int len) override;
+ void cmdXXX (int len) override;
+ void cmdPre (int len) override;
+ void cmdPost (int len) override;
+ void cmdPostPost (int len) override;
+ void cmdXPic (int len) override;
+ void cmdXFontDef (int len) override;
+ void cmdXGlyphArray (int len) override;
+ void cmdXGlyphString (int len) override;
+ void cmdXTextAndGlyphs (int len) override;
+
+ private:
+ bool _inPage; ///< true if stream pointer is between bop and eop
+ unsigned _currPageNum; ///< current page number (1 is first page)
+ int _currFontNum; ///< current font number
+ double _dvi2bp; ///< factor to convert dvi units to PS points
+ uint32_t _mag; ///< magnification factor * 1000
+ bool _inPostamble; ///< true if stream pointer is inside the postamble
+ DVIState _dviState; ///< current state of the DVI registers
+ std::stack<DVIState> _stateStack;
+ std::vector<uint32_t> _bopOffsets;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/DVIToSVG.cpp b/dviware/dvisvgm/src/DVIToSVG.cpp
new file mode 100644
index 0000000000..4911dfe321
--- /dev/null
+++ b/dviware/dvisvgm/src/DVIToSVG.cpp
@@ -0,0 +1,575 @@
+/*************************************************************************
+** DVIToSVG.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <config.h>
+#include <cstdlib>
+#include <ctime>
+#include <fstream>
+#include <iomanip>
+#include <set>
+#include <sstream>
+#include "Calculator.hpp"
+#include "DVIToSVG.hpp"
+#include "DVIToSVGActions.hpp"
+#include "FileSystem.hpp"
+#include "Font.hpp"
+#include "FontManager.hpp"
+#include "GlyphTracerMessages.hpp"
+#include "InputBuffer.hpp"
+#include "InputReader.hpp"
+#include "PageRanges.hpp"
+#include "PageSize.hpp"
+#include "PreScanDVIReader.hpp"
+#include "SignalHandler.hpp"
+#include "optimizer/SVGOptimizer.hpp"
+#include "SVGOutput.hpp"
+#include "utility.hpp"
+#include "version.hpp"
+#include "XXHashFunction.hpp"
+
+///////////////////////////////////
+// special handlers
+
+#include "BgColorSpecialHandler.hpp"
+#include "ColorSpecialHandler.hpp"
+#include "DvisvgmSpecialHandler.hpp"
+#include "EmSpecialHandler.hpp"
+#include "HtmlSpecialHandler.hpp"
+#include "PapersizeSpecialHandler.hpp"
+#include "PdfSpecialHandler.hpp"
+#include "TpicSpecialHandler.hpp"
+#ifndef HAVE_LIBGS
+ #include "NoPsSpecialHandler.hpp"
+#endif
+#ifndef DISABLE_GS
+ #include "PsSpecialHandler.hpp"
+#endif
+
+///////////////////////////////////
+
+using namespace std;
+
+/** 'a': trace all glyphs even if some of them are already cached
+ * 'm': trace missing glyphs, i.e. glyphs not yet cached
+ * 0 : only trace actually required glyphs */
+char DVIToSVG::TRACE_MODE = 0;
+bool DVIToSVG::COMPUTE_PROGRESS = false;
+DVIToSVG::HashSettings DVIToSVG::PAGE_HASH_SETTINGS;
+
+
+DVIToSVG::DVIToSVG (istream &is, SVGOutputBase &out) : DVIReader(is), _out(out)
+{
+ _pageHeight = _pageWidth = 0;
+ _tx = _ty = 0; // no cursor translation
+ _pageByte = 0;
+ _prevXPos = _prevYPos = numeric_limits<double>::min();
+ _prevWritingMode = WritingMode::LR;
+ _actions = util::make_unique<DVIToSVGActions>(*this, _svg);
+}
+
+
+/** Starts the conversion process.
+ * @param[in] first number of first page to convert
+ * @param[in] last number of last page to convert
+ * @param[in] hashFunc pointer to function to be used to compute page hashes */
+void DVIToSVG::convert (unsigned first, unsigned last, HashFunction *hashFunc) {
+ if (first > last)
+ swap(first, last);
+ if (first > numberOfPages()) {
+ ostringstream oss;
+ oss << "file contains only " << numberOfPages() << " page";
+ if (numberOfPages() > 1)
+ oss << 's';
+ throw DVIException(oss.str());
+ }
+ last = min(last, numberOfPages());
+ bool computeHashes = (hashFunc && !_out.ignoresHashes());
+ string shortenedOptHash = XXH32HashFunction(PAGE_HASH_SETTINGS.optionsHash()).digestString();
+ for (unsigned i=first; i <= last; ++i) {
+ string dviHash, combinedHash;
+ if (computeHashes) {
+ computePageHash(i, *hashFunc);
+ dviHash = hashFunc->digestString();
+ hashFunc->update(PAGE_HASH_SETTINGS.optionsHash());
+ combinedHash = hashFunc->digestString();
+ }
+ const SVGOutput::HashTriple hashTriple(dviHash, shortenedOptHash, combinedHash);
+ string fname = _out.filename(i, numberOfPages(), hashTriple);
+ if (!dviHash.empty() && !PAGE_HASH_SETTINGS.isSet(HashSettings::P_REPLACE) && FileSystem::exists(fname)) {
+ Message::mstream(false, Message::MC_PAGE_NUMBER) << "skipping page " << i;
+ Message::mstream().indent(1);
+ Message::mstream(false, Message::MC_PAGE_WRITTEN) << "\nfile " << fname << " exists\n";
+ Message::mstream().indent(0);
+ }
+ else {
+ executePage(i);
+ SVGOptimizer(_svg).execute();
+ embedFonts(_svg.rootNode());
+ bool success = _svg.write(_out.getPageStream(currentPageNumber(), numberOfPages(), hashTriple));
+ if (fname.empty())
+ fname = "<stdout>";
+ if (success)
+ Message::mstream(false, Message::MC_PAGE_WRITTEN) << "\noutput written to " << fname << '\n';
+ else
+ Message::wstream(true) << "failed to write output to " << fname << '\n';
+ _svg.reset();
+ _actions->reset();
+ }
+ }
+}
+
+
+/** Creates a HashFunction object for a given algorithm name.
+ * @param[in] algo name of hash algorithm
+ * @return pointer to hash function
+ * @throw MessageException if algorithm name is invalid or not supported */
+static unique_ptr<HashFunction> create_hash_function (const string &algo) {
+ if (auto hashFunc = HashFunction::create(algo))
+ return hashFunc;
+
+ string msg = "unknown hash algorithm '"+algo+"' (supported algorithms: ";
+ for (const string &name : HashFunction::supportedAlgorithms())
+ msg += name + ", ";
+ msg.pop_back();
+ msg.back() = ')';
+ throw MessageException(msg);
+}
+
+
+/** Convert DVI pages specified by a page range string.
+ * @param[in] rangestr string describing the pages to convert
+ * @param[out] pageinfo (number of converted pages, number of total pages) */
+void DVIToSVG::convert (const string &rangestr, pair<int,int> *pageinfo) {
+ PageRanges ranges;
+ if (!ranges.parse(rangestr, numberOfPages()))
+ throw MessageException("invalid page range format");
+
+ Message::mstream(false, Message::MC_PAGE_NUMBER) << "pre-processing DVI file (format version " << getDVIVersion() << ")\n";
+ if (auto actions = dynamic_cast<DVIToSVGActions*>(_actions.get())) {
+ PreScanDVIReader prescan(getInputStream(), actions);
+ actions->setDVIReader(prescan);
+ prescan.executeAllPages();
+ actions->setDVIReader(*this);
+ SpecialManager::instance().notifyPreprocessingFinished();
+ }
+
+ unique_ptr<HashFunction> hashFunc;
+ if (!PAGE_HASH_SETTINGS.algorithm().empty()) // name of hash algorithm present?
+ hashFunc = create_hash_function(PAGE_HASH_SETTINGS.algorithm());
+
+ for (const auto &range : ranges)
+ convert(range.first, range.second, hashFunc.get());
+ if (pageinfo) {
+ pageinfo->first = ranges.numberOfPages();
+ pageinfo->second = numberOfPages();
+ }
+}
+
+
+/** Writes the hash values of a selected set of pages to an output stream.
+ * @param[in] rangestr string describing the pages to convert
+ * @param[in,out] os stream the output is written to */
+void DVIToSVG::listHashes (const string &rangestr, std::ostream &os) {
+ PageRanges ranges;
+ if (!ranges.parse(rangestr, numberOfPages()))
+ throw MessageException("invalid page range format");
+
+ XXH32HashFunction xxh32;
+ auto hashFunc = create_hash_function(PAGE_HASH_SETTINGS.algorithm());
+ int width1 = util::ilog10(numberOfPages())+1;
+ int width2 = hashFunc->digestSize()*2;
+ int spaces1 = width1+2+(width2-3)/2;
+ int spaces2 = width1+2+width2+2-spaces1-3+(width2-7)/2;
+ string shortenedOptHash = XXH32HashFunction(PAGE_HASH_SETTINGS.optionsHash()).digestString();
+ os << string(spaces1, ' ') << "DVI"
+ << string(spaces2, ' ') << "DVI+opt\n";
+ for (const auto &range : ranges) {
+ for (int i=range.first; i <= range.second; i++) {
+ computePageHash(i, *hashFunc);
+ os << setw(width1) << i;
+ os << ": " << hashFunc->digestString();
+ hashFunc->update(PAGE_HASH_SETTINGS.optionsHash());
+ os << ", " << hashFunc->digestString() << '\n';
+ }
+ }
+ os << "hash algorithm: " << PAGE_HASH_SETTINGS.algorithm()
+ << ", options hash: " << shortenedOptHash << '\n';
+}
+
+
+int DVIToSVG::executeCommand () {
+ SignalHandler::instance().check();
+ const streampos cmdpos = tell();
+ int opcode = DVIReader::executeCommand();
+ if (dviState().v+_ty != _prevYPos) {
+ _tx = _ty = 0;
+ _prevYPos = dviState().v;
+ }
+ _prevXPos = dviState().h+_tx;
+ _prevWritingMode = dviState().d;
+ if (COMPUTE_PROGRESS && inPage() && _actions) {
+ size_t pagelen = numberOfPageBytes(currentPageNumber()-1);
+ _pageByte += tell()-cmdpos;
+ _actions->progress(_pageByte, pagelen);
+ }
+ return opcode;
+}
+
+
+/** This template method is called by parent class DVIReader before
+ * executing the BOP actions.
+ * @param[in] pageno physical page number (1 = first page)
+ * @param[in] c contains information about the page (page number etc.) */
+void DVIToSVG::enterBeginPage (unsigned pageno, const vector<int32_t> &c) {
+ if (dynamic_cast<DVIToSVGActions*>(_actions.get())) {
+ Message::mstream().indent(0);
+ Message::mstream(false, Message::MC_PAGE_NUMBER) << "processing page " << pageno;
+ if (pageno != (unsigned)c[0]) // Does page number shown on page differ from physical page number?
+ Message::mstream(false) << " [" << c[0] << ']';
+ Message::mstream().indent(1);
+ _svg.appendToDoc(util::make_unique<XMLComment>(" This file was generated by dvisvgm " + string(PROGRAM_VERSION) + " "));
+ }
+}
+
+
+/** This template method is called by DVIReader::cmdEop() after
+ * executing the EOP actions. */
+void DVIToSVG::leaveEndPage (unsigned) {
+ if (!dynamic_cast<DVIToSVGActions*>(_actions.get()))
+ return;
+
+ // set bounding box and apply page transformations
+ BoundingBox bbox = _actions->bbox(); // bounding box derived from the DVI commands executed
+ if (_bboxFormatString == "min" || _bboxFormatString == "preview" || _bboxFormatString == "papersize") {
+ bbox.unlock();
+ bbox.transform(getPageTransformation());
+ }
+ else if (_bboxFormatString == "dvi") {
+ // center page content
+ double dx = (_pageWidth-bbox.width())/2;
+ double dy = (_pageHeight-bbox.height())/2;
+ bbox += BoundingBox(-dx, -dy, dx, dy);
+ }
+ else if (_bboxFormatString != "none") {
+ istringstream iss(_bboxFormatString);
+ StreamInputReader ir(iss);
+ ir.skipSpace();
+ if (isalpha(ir.peek())) {
+ // set explicitly given page format
+ PageSize size(_bboxFormatString);
+ if (size.valid()) {
+ // convention: DVI position (0,0) equals (1in, 1in) relative
+ // to the upper left vertex of the page (see DVI specification)
+ const double border = -72;
+ bbox = BoundingBox(border, border, size.width().bp()+border, size.height().bp()+border);
+ }
+ }
+ else { // set/modify bounding box by explicitly given values
+ try {
+ vector<Length> lengths = BoundingBox::extractLengths(_bboxFormatString);
+ if (lengths.size() == 1 || lengths.size() == 2) { // relative box size?
+ // apply the page transformation and adjust the bbox afterwards
+ bbox.transform(getPageTransformation());
+ }
+ bbox.set(lengths);
+ }
+ catch (const MessageException &e) {
+ }
+ }
+ }
+ if (bbox.width() == 0)
+ Message::wstream(false) << "\npage is empty\n";
+ if (_bboxFormatString != "none") {
+ _svg.setBBox(bbox);
+ const double bp2pt = 72.27/72;
+ const double bp2mm = 25.4/72;
+ Message::mstream(false) << '\n';
+ Message::mstream(false, Message::MC_PAGE_SIZE) << "graphic size: " << XMLString(bbox.width()*bp2pt) << "pt"
+ " x " << XMLString(bbox.height()*bp2pt) << "pt"
+ " (" << XMLString(bbox.width()*bp2mm) << "mm"
+ " x " << XMLString(bbox.height()*bp2mm) << "mm)";
+ Message::mstream(false) << '\n';
+ }
+}
+
+
+Matrix DVIToSVG::getPageTransformation () const {
+ Matrix matrix(1); // unity matrix
+ if (!_transCmds.empty()) {
+ Calculator calc;
+ if (_actions) {
+ const double bp2pt = (1_bp).pt();
+ BoundingBox &bbox = _actions->bbox();
+ calc.setVariable("ux", bbox.minX()*bp2pt);
+ calc.setVariable("uy", bbox.minY()*bp2pt);
+ calc.setVariable("w", bbox.width()*bp2pt);
+ calc.setVariable("h", bbox.height()*bp2pt);
+ }
+ // add constants for length units to calculator
+ for (const auto &unit : Length::getUnits())
+ calc.setVariable(unit.first, Length(1, unit.second).pt());
+ matrix.set(_transCmds, calc);
+ }
+ return matrix;
+}
+
+
+static void collect_chars (unordered_map<const Font*, set<int>> &fontmap) {
+ for (const auto &entry : fontmap) {
+ if (entry.first->uniqueFont() != entry.first) {
+ for (int c : entry.second)
+ fontmap[entry.first->uniqueFont()].insert(c);
+ }
+ }
+}
+
+
+/** Adds the font information to the SVG tree.
+ * @param[in] svgElement the font nodes are added to this node */
+void DVIToSVG::embedFonts (XMLElement *svgElement) {
+ if (!svgElement || !_actions) // no dvi actions => no chars written => no fonts to embed
+ return;
+
+ const DVIToSVGActions *svgActions = static_cast<DVIToSVGActions*>(_actions.get());
+ auto &usedCharsMap = svgActions->getUsedChars();
+
+ collect_chars(usedCharsMap);
+
+ GlyphTracerMessages messages;
+ unordered_set<const Font*> tracedFonts; // collect unique fonts already traced
+ for (const auto &fontchar : usedCharsMap) {
+ const Font *font = fontchar.first;
+ if (auto ph_font = dynamic_cast<const PhysicalFont*>(font)) {
+ // Check if glyphs should be traced. Only trace the glyphs of unique fonts, i.e.
+ // avoid retracing the same glyphs again if they are referenced in various sizes.
+ if (TRACE_MODE != 0 && tracedFonts.find(ph_font->uniqueFont()) == tracedFonts.end()) {
+ ph_font->traceAllGlyphs(TRACE_MODE == 'a', &messages);
+ tracedFonts.insert(ph_font->uniqueFont());
+ }
+ if (font->path()) // does font file exist?
+ _svg.append(*ph_font, fontchar.second, &messages);
+ else
+ Message::wstream(true) << "can't embed font '" << font->name() << "'\n";
+ }
+ else
+ Message::wstream(true) << "can't embed font '" << font->name() << "'\n";
+ }
+ _svg.appendFontStyles(svgActions->getUsedFonts());
+}
+
+
+/** Enables or disables processing of specials. If ignorelist == 0, all
+ * supported special handlers are loaded. To disable selected sets of specials,
+ * the corresponding prefixes can be given separated by non alpha-numeric characters,
+ * e.g. "color, ps, em" or "color: ps em" etc.
+ * A single "*" in the ignore list disables all specials.
+ * @param[in] ignorelist list of special prefixes to ignore
+ * @param[in] pswarning if true, shows warning about disabled PS support
+ * @return the SpecialManager that handles special statements */
+void DVIToSVG::setProcessSpecials (const char *ignorelist, bool pswarning) {
+ if (ignorelist && strcmp(ignorelist, "*") == 0) // ignore all specials?
+ SpecialManager::instance().unregisterHandlers();
+ else {
+ // add special handlers
+ vector<unique_ptr<SpecialHandler>> handlers;
+ handlers.emplace_back(util::make_unique<BgColorSpecialHandler>()); // handles background color special
+ handlers.emplace_back(util::make_unique<ColorSpecialHandler>()); // handles color specials
+ handlers.emplace_back(util::make_unique<DvisvgmSpecialHandler>()); // handles raw SVG embeddings
+ handlers.emplace_back(util::make_unique<EmSpecialHandler>()); // handles emTeX specials
+ handlers.emplace_back(util::make_unique<HtmlSpecialHandler>()); // handles hyperref specials
+ handlers.emplace_back(util::make_unique<PapersizeSpecialHandler>()); // handles papersize special
+ handlers.emplace_back(util::make_unique<PdfSpecialHandler>()); // handles pdf specials
+ handlers.emplace_back(util::make_unique<TpicSpecialHandler>()); // handles tpic specials
+#ifndef DISABLE_GS
+ if (Ghostscript().available())
+ handlers.emplace_back(util::make_unique<PsSpecialHandler>()); // handles PostScript specials
+ else
+#endif
+ {
+#ifndef HAVE_LIBGS
+ // dummy PS special handler that only prints warning messages
+ handlers.emplace_back(util::make_unique<NoPsSpecialHandler>());
+ if (pswarning) {
+#ifdef DISABLE_GS
+ Message::wstream() << "processing of PostScript specials has been disabled permanently\n";
+#else
+ Message::wstream() << "processing of PostScript specials is disabled (Ghostscript not found)\n";
+#endif
+ }
+#endif
+ }
+ SpecialManager::instance().unregisterHandlers();
+ SpecialManager::instance().registerHandlers(handlers, ignorelist);
+ }
+}
+
+
+string DVIToSVG::getSVGFilename (unsigned pageno) const {
+ return _out.filename(pageno, numberOfPages());
+}
+
+
+void DVIToSVG::moveRight (double dx, MoveMode mode) {
+ DVIReader::moveRight(dx, mode);
+ if (_actions) {
+ if (dviState().d == WritingMode::LR)
+ _actions->moveToX(dviState().h+_tx, mode == MoveMode::CHANGEPOS);
+ else
+ _actions->moveToY(dviState().v+_ty, mode == MoveMode::CHANGEPOS);
+ }
+}
+
+
+void DVIToSVG::moveDown (double dy, MoveMode mode) {
+ DVIReader::moveDown(dy, mode);
+ if (_actions) {
+ if (dviState().d == WritingMode::LR)
+ _actions->moveToY(dviState().v+_ty, mode == MoveMode::CHANGEPOS);
+ else
+ _actions->moveToX(dviState().h+_tx, mode == MoveMode::CHANGEPOS);
+ }
+}
+
+
+void DVIToSVG::dviPost (uint16_t, uint16_t pages, uint32_t pw, uint32_t ph, uint32_t, uint32_t, uint32_t, uint32_t) {
+ _pageHeight = ph; // height of tallest page in dvi units
+ _pageWidth = pw; // width of widest page in dvi units
+}
+
+
+void DVIToSVG::dviBop (const std::vector<int32_t> &c, int32_t) {
+ _pageByte = 0;
+ enterBeginPage(currentPageNumber(), c);
+ if (_actions) {
+ _actions->progress(0, 1); // ensure that progress is called at 0%
+ _actions->beginPage(currentPageNumber(), c);
+ }
+}
+
+
+void DVIToSVG::dviEop () {
+ if (_actions) {
+ _actions->endPage(currentPageNumber());
+ _pageByte = numberOfPageBytes(currentPageNumber()-1);
+ _actions->progress(_pageByte, _pageByte); // ensure that progress is called at 100%
+ }
+ leaveEndPage(currentPageNumber());
+}
+
+
+void DVIToSVG::dviSetChar0 (uint32_t c, const Font *font) {
+ if (_actions && !dynamic_cast<const VirtualFont*>(font))
+ _actions->setChar(dviState().h+_tx, dviState().v+_ty, c, dviState().d != WritingMode::LR, *font);
+}
+
+
+void DVIToSVG::dviSetChar (uint32_t c, const Font *font) {
+ dviSetChar0(c, font);
+}
+
+
+void DVIToSVG::dviPutChar (uint32_t c, const Font *font) {
+ dviSetChar0(c, font);
+}
+
+
+void DVIToSVG::dviSetRule (double height, double width) {
+ if (_actions && height > 0 && width > 0)
+ _actions->setRule(dviState().h+_tx, dviState().v+_ty, height, width);
+}
+
+
+void DVIToSVG::dviPutRule (double height, double width) {
+ dviSetRule(height, width);
+}
+
+
+void DVIToSVG::dviPop () {
+ if (_actions) {
+ if (_prevXPos != dviState().h+_tx)
+ _actions->moveToX(dviState().h + _tx, true); // force setting the SVG position
+ if (_prevYPos != dviState().v+_ty)
+ _actions->moveToY(dviState().v + _ty, true); // force setting the SVG position
+ if (_prevWritingMode != dviState().d)
+ _actions->setTextOrientation(dviState().d != WritingMode::LR);
+ }
+}
+
+
+void DVIToSVG::dviFontNum (uint32_t fontnum, SetFontMode, const Font *font) {
+ if (_actions && font && !dynamic_cast<const VirtualFont*>(font))
+ _actions->setFont(FontManager::instance().fontID(fontnum), *font); // all fonts get a recomputed ID
+}
+
+
+void DVIToSVG::dviDir (WritingMode dir) {
+ if (_actions)
+ _actions->setTextOrientation(dir != WritingMode::LR);
+}
+
+
+void DVIToSVG::dviXXX (const std::string &str) {
+ if (_actions)
+ _actions->special(str, dvi2bp());
+}
+
+
+void DVIToSVG::dviXGlyphArray (std::vector<double> &dx, vector<double> &dy, vector<uint16_t> &glyphs, const Font &font) {
+ if (_actions) {
+ for (size_t i=0; i < glyphs.size(); i++)
+ _actions->setChar(dviState().h+dx[i]+_tx, dviState().v+dy[i]+_ty, glyphs[i], false, font);
+ }
+}
+
+
+void DVIToSVG::dviXGlyphString (vector<double> &dx, vector<uint16_t> &glyphs, const Font &font) {
+ if (_actions) {
+ for (size_t i=0; i < glyphs.size(); i++)
+ _actions->setChar(dviState().h+dx[i]+_tx, dviState().v+_ty, glyphs[i], false, font);
+ }
+}
+
+
+void DVIToSVG::dviXTextAndGlyphs (vector<double> &dx, vector<double> &dy, vector<uint16_t>&, vector<uint16_t> &glyphs, const Font &font) {
+ dviXGlyphArray(dx, dy, glyphs, font);
+}
+
+///////////////////////////////////////////////////////////////
+
+/** Parses a string consisting of comma-separated words, and assigns
+ * the values to the hash settings. */
+void DVIToSVG::HashSettings::setParameters (const string &paramstr) {
+ auto paramnames = util::split(paramstr, ",");
+ map<string, Parameter> paramMap = {
+ {"list", P_LIST},
+ {"replace", P_REPLACE}
+ };
+ for (string &name : paramnames) {
+ name = util::trim(name);
+ auto it = paramMap.find(name);
+ if (it != paramMap.end())
+ _params.insert(it->second);
+ else if (_algo.empty() && HashFunction::isSupportedAlgorithm(name))
+ _algo = name;
+ }
+ // set default hash algorithm if none is given
+ if (_algo.empty())
+ _algo = "xxh64";
+}
diff --git a/dviware/dvisvgm/src/DVIToSVG.hpp b/dviware/dvisvgm/src/DVIToSVG.hpp
new file mode 100644
index 0000000000..9463f78736
--- /dev/null
+++ b/dviware/dvisvgm/src/DVIToSVG.hpp
@@ -0,0 +1,112 @@
+/*************************************************************************
+** DVIToSVG.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef DVITOSVG_HPP
+#define DVITOSVG_HPP
+
+#include <set>
+#include <string>
+#include <utility>
+#include "DVIReader.hpp"
+#include "SVGTree.hpp"
+
+struct DVIActions;
+struct SVGOutputBase;
+class HashFunction;
+
+class DVIToSVG : public DVIReader {
+ public:
+ class HashSettings {
+ public:
+ enum Parameter {P_LIST, P_REPLACE};
+ void setParameters (const std::string &paramstr);
+ void setOptionHash (const std::string &optHash) {_optHash = optHash;}
+ std::string algorithm () const {return _algo;}
+ std::string optionsHash () const {return _optHash;}
+ bool isSet (Parameter param) {return _params.find(param) != _params.end();}
+
+ private:
+ std::string _algo;
+ std::string _optHash;
+ std::set<Parameter> _params;
+ };
+
+ public:
+ explicit DVIToSVG (std::istream &is, SVGOutputBase &out);
+ DVIToSVG (const DVIToSVG&) =delete;
+ void convert (const std::string &range, std::pair<int,int> *pageinfo=nullptr);
+ void setPageSize (const std::string &format) {_bboxFormatString = format;}
+ void setPageTransformation (const std::string &cmds) {_transCmds = cmds;}
+ Matrix getPageTransformation () const override;
+ void translateToX (double x) override {_tx = x-dviState().h-_tx;}
+ void translateToY (double y) override {_ty = y-dviState().v-_ty;}
+ double getXPos() const override {return dviState().h+_tx;}
+ double getYPos() const override {return dviState().v+_ty;}
+ void finishLine () override {_prevYPos = std::numeric_limits<double>::min();}
+ void listHashes (const std::string &rangestr, std::ostream &os);
+
+ std::string getSVGFilename (unsigned pageno) const;
+ std::string getUserBBoxString () const {return _bboxFormatString;}
+ static void setProcessSpecials (const char *ignorelist=nullptr, bool pswarning=false);
+
+ public:
+ static bool COMPUTE_PROGRESS; ///< if true, an action to handle the progress ratio of a page is triggered
+ static char TRACE_MODE;
+ static HashSettings PAGE_HASH_SETTINGS;
+
+ protected:
+ void convert (unsigned firstPage, unsigned lastPage, HashFunction *hashFunc);
+ int executeCommand () override;
+ void enterBeginPage (unsigned pageno, const std::vector<int32_t> &c);
+ void leaveEndPage (unsigned pageno);
+ void embedFonts (XMLElement *svgElement);
+ void moveRight (double dx, MoveMode mode) override;
+ void moveDown (double dy, MoveMode mode) override;
+
+ void dviPost (uint16_t stdepth, uint16_t pages, uint32_t pw, uint32_t ph, uint32_t mag, uint32_t num, uint32_t den, uint32_t lbopofs) override;
+ void dviBop (const std::vector<int32_t> &c, int32_t prevBopOffset) override;
+ void dviEop () override;
+ void dviSetChar0 (uint32_t c, const Font *font) override;
+ void dviSetChar (uint32_t c, const Font *font) override;
+ void dviPutChar (uint32_t c, const Font *font) override;
+ void dviSetRule (double h, double w) override;
+ void dviPutRule (double h, double w) override;
+ void dviPop () override;
+ void dviFontNum (uint32_t fontnum, SetFontMode mode, const Font *font) override;
+ void dviDir (WritingMode dir) override;
+ void dviXXX (const std::string &str) override;
+ void dviXGlyphArray (std::vector<double> &dx, std::vector<double> &dy, std::vector<uint16_t> &glyphs, const Font &font) override;
+ void dviXGlyphString (std::vector<double> &dx, std::vector<uint16_t> &glyphs, const Font &font) override;
+ void dviXTextAndGlyphs (std::vector<double> &dx, std::vector<double> &dy, std::vector<uint16_t> &chars, std::vector<uint16_t> &glyphs, const Font &font) override;
+
+ private:
+ SVGTree _svg;
+ SVGOutputBase &_out;
+ std::unique_ptr<DVIActions> _actions;
+ std::string _bboxFormatString; ///< bounding box size/format set by the user
+ std::string _transCmds; ///< page transformation commands set by the user
+ double _pageHeight, _pageWidth; ///< global page height and width stored in the postamble
+ double _tx, _ty; ///< translation of cursor position
+ double _prevXPos, _prevYPos; ///< previous cursor position
+ WritingMode _prevWritingMode; ///< previous writing mode
+ std::streampos _pageByte; ///< position of the stream pointer relative to the preceding bop (in bytes)
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/DVIToSVGActions.cpp b/dviware/dvisvgm/src/DVIToSVGActions.cpp
new file mode 100644
index 0000000000..6448fc2d42
--- /dev/null
+++ b/dviware/dvisvgm/src/DVIToSVGActions.cpp
@@ -0,0 +1,353 @@
+/*************************************************************************
+** DVIToSVGActions.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <cstring>
+#include <ctime>
+#include "BoundingBox.hpp"
+#include "DVIToSVG.hpp"
+#include "DVIToSVGActions.hpp"
+#include "Font.hpp"
+#include "FontManager.hpp"
+#include "GlyphTracerMessages.hpp"
+#include "System.hpp"
+#include "utility.hpp"
+
+using namespace std;
+
+
+DVIToSVGActions::DVIToSVGActions (DVIToSVG &dvisvg, SVGTree &svg)
+ : _svg(svg), _dvireader(&dvisvg), _bgcolor(Color::TRANSPARENT)
+{
+ _currentFontNum = -1;
+ _pageCount = 0;
+}
+
+
+void DVIToSVGActions::reset() {
+ _usedChars.clear();
+ _usedFonts.clear();
+ _bbox = BoundingBox();
+ _currentFontNum = -1;
+ _bgcolor = Color::TRANSPARENT;
+}
+
+
+/** Change current horizontal position.
+ * @param[in] x new horizontal position
+ * @param[in] forceSVGMove if true, creates an explicit position change in the SVG tree */
+void DVIToSVGActions::moveToX (double x, bool forceSVGMove) {
+ SpecialManager::instance().notifyPositionChange(getX(), getY(), *this);
+ if (forceSVGMove)
+ _svg.setX(x);
+}
+
+
+/** Change current vertical position.
+ * @param[in] y new vertical position
+ * @param[in] forceSVGMove if true, creates an explicit position change in the SVG tree */
+void DVIToSVGActions::moveToY (double y, bool forceSVGMove) {
+ SpecialManager::instance().notifyPositionChange(getX(), getY(), *this);
+ if (forceSVGMove)
+ _svg.setY(y);
+}
+
+
+string DVIToSVGActions::getSVGFilename (unsigned pageno) const {
+ if (auto dvi2svg = dynamic_cast<DVIToSVG*>(_dvireader))
+ return dvi2svg->getSVGFilename(pageno);
+ return "";
+}
+
+
+string DVIToSVGActions::getBBoxFormatString () const {
+ if (auto dvi2svg = dynamic_cast<DVIToSVG*>(_dvireader))
+ return dvi2svg->getUserBBoxString();
+ return "";
+}
+
+
+/** This method is called when a "set char" command was found in the DVI file.
+ * It draws a character of the current font.
+ * @param[in] x horizontal position of left bounding box edge
+ * @param[in] y vertical position of the character's baseline
+ * @param[in] c character code relative to the current font
+ * @param[in] vertical true if we're in vertical mode
+ * @param[in] font font to be used */
+void DVIToSVGActions::setChar (double x, double y, unsigned c, bool vertical, const Font &font) {
+ // If we use SVG fonts there is no need to record all font name/char/size combinations
+ // because the SVG font mechanism handles this automatically. It's sufficient to
+ // record font names and chars. The various font sizes can be ignored here.
+ // For a given font object, Font::uniqueFont() returns the same unique font object for
+ // all fonts with the same name.
+ _usedChars[SVGTree::USE_FONTS ? font.uniqueFont() : &font].insert(c);
+
+ // However, we record all required fonts
+ _usedFonts.insert(&font);
+ _svg.appendChar(c, x, y);
+
+ static string fontname;
+ GlyphTracerMessages callback(fontname != font.name(), false);
+ fontname = font.name();
+
+ GlyphMetrics metrics;
+ font.getGlyphMetrics(c, vertical, metrics);
+ auto pf = dynamic_cast<const PhysicalFont*>(&font);
+ if (PhysicalFont::EXACT_BBOX && pf) {
+ GlyphMetrics exact_metrics;
+ pf->getExactGlyphBox(c, exact_metrics, vertical, &callback);
+ if (vertical) {
+ // move top of bbox to upper margin of glyph (just an approximation yet)
+ y += (metrics.d-exact_metrics.h-exact_metrics.d)/2;
+ }
+ metrics = exact_metrics;
+ }
+ BoundingBox bbox(x-metrics.wl, y-metrics.h, x+metrics.wr, y+metrics.d);
+
+ // update bounding box
+ if (!getMatrix().isIdentity())
+ bbox.transform(getMatrix());
+ embed(bbox);
+#if 0
+ XMLElement *rect = new XMLElement("rect");
+ rect->addAttribute("x", x-metrics.wl);
+ rect->addAttribute("y", y-metrics.h);
+ rect->addAttribute("width", metrics.wl+metrics.wr);
+ rect->addAttribute("height", metrics.h+metrics.d);
+ rect->addAttribute("fill", "none");
+ rect->addAttribute("stroke", "red");
+ rect->addAttribute("stroke-width", "0.5");
+ _svg.appendToPage(rect);
+ if (metrics.d > 0) {
+ XMLElement *line = new XMLElement("line");
+ line->addAttribute("x1", x-metrics.wl);
+ line->addAttribute("y1", y);
+ line->addAttribute("x2", x+metrics.wr);
+ line->addAttribute("y2", y);
+ line->addAttribute("stroke", "blue");
+ line->addAttribute("stroke-width", "0.5");
+ _svg.appendToPage(line);
+ }
+ if (metrics.wl > 0) {
+ XMLElement *line = new XMLElement("line");
+ line->addAttribute("x1", x);
+ line->addAttribute("y1", y-metrics.h);
+ line->addAttribute("x2", x);
+ line->addAttribute("y2", y+metrics.d);
+ line->addAttribute("stroke", "blue");
+ line->addAttribute("stroke-width", "0.5");
+ _svg.appendToPage(line);
+ }
+#endif
+}
+
+
+/** This method is called when a "set rule" or "put rule" command was found in the
+ * DVI file. It draws a solid unrotated rectangle.
+ * @param[in] x horizontal position of left edge
+ * @param[in] y vertical position of bottom(!) edge
+ * @param[in] height length of the vertical edges
+ * @param[in] width length of the horizontal edges */
+void DVIToSVGActions::setRule (double x, double y, double height, double width) {
+ // (x,y) is the lower left corner of the rectangle
+ auto rect = util::make_unique<XMLElement>("rect");
+ rect->addAttribute("x", x);
+ rect->addAttribute("y", y-height);
+ rect->addAttribute("height", height);
+ rect->addAttribute("width", width);
+ if (!getMatrix().isIdentity())
+ rect->addAttribute("transform", getMatrix().toSVG());
+ if (getColor() != Color::BLACK)
+ rect->addAttribute("fill", _svg.getColor().svgColorString());
+ _svg.appendToPage(std::move(rect));
+
+ // update bounding box
+ BoundingBox bb(x, y-height, x+width, y);
+ if (!getMatrix().isIdentity())
+ bb.transform(getMatrix());
+ embed(bb);
+}
+
+
+/** This method is called when a "set font" command was found in the DVI file. The
+ * font must be previously defined.
+ * @param[in] num unique number of the font in the DVI file (not necessarily equal to the DVI font number)
+ * @param[in] font pointer to the font object (always represents a physical font and never a virtual font) */
+void DVIToSVGActions::setFont (int num, const Font &font) {
+ _currentFontNum = num;
+ _svg.setFont(num, font);
+}
+
+
+/** This method is called when a "special" command was found in the DVI file.
+ * @param[in] spc the special expression
+ * @param[in] dvi2bp factor to scale DVI units to PS points
+ * @param[in] preprocessing if true, the DVI file is being pre-processed */
+void DVIToSVGActions::special (const string &spc, double dvi2bp, bool preprocessing) {
+ try {
+ if (preprocessing)
+ SpecialManager::instance().preprocess(spc, *this);
+ else
+ SpecialManager::instance().process(spc, dvi2bp, *this);
+ // @@ output message in case of unsupported specials?
+ }
+ catch (const SpecialException &e) {
+ Message::estream(true) << "error in special: " << e.what() << '\n';
+ }
+}
+
+
+/** This method is called when a "begin of page (bop)" command was found in the DVI file.
+ * @param[in] pageno physical page number
+ * @param[in] c array with 10 components representing \\count0 ... \\count9. c[0] contains the
+ * current (printed) page number (may differ from page count) */
+void DVIToSVGActions::beginPage (unsigned pageno, const vector<int32_t>&) {
+ SpecialManager::instance().notifyBeginPage(pageno, *this);
+ _svg.newPage(++_pageCount);
+ _bbox = BoundingBox(); // clear bounding box
+ _boxes.clear();
+}
+
+
+/** This method is called when an "end of page (eop)" command was found in the DVI file. */
+void DVIToSVGActions::endPage (unsigned pageno) {
+ SpecialManager::instance().notifyEndPage(pageno, *this);
+ Matrix matrix = _dvireader->getPageTransformation();
+ _svg.transformPage(matrix);
+ if (_bgcolor != Color::TRANSPARENT) {
+ // create a rectangle filled with the background color
+ auto rect = util::make_unique<XMLElement>("rect");
+ rect->addAttribute("x", _bbox.minX());
+ rect->addAttribute("y", _bbox.minY());
+ rect->addAttribute("width", _bbox.width());
+ rect->addAttribute("height", _bbox.height());
+ rect->addAttribute("fill", _bgcolor.svgColorString());
+ _svg.prependToPage(std::move(rect));
+ }
+}
+
+
+void DVIToSVGActions::setBgColor (const Color &color) {
+ _bgcolor = color;
+}
+
+
+void DVIToSVGActions::embed(const BoundingBox &bbox) {
+ _bbox.embed(bbox);
+ for (auto &strboxpair : _boxes)
+ strboxpair.second.embed(bbox);
+}
+
+
+void DVIToSVGActions::embed(const DPair& p, double r) {
+ if (r == 0)
+ _bbox.embed(p);
+ else
+ _bbox.embed(p, r);
+ for (auto &strboxpair : _boxes)
+ strboxpair.second.embed(p, r);
+}
+
+
+BoundingBox& DVIToSVGActions::bbox(const string& name, bool reset) {
+ BoundingBox &box = _boxes[name];
+ if (reset)
+ box = BoundingBox();
+ return box;
+}
+
+
+/** This method is called by subprocesses like the PS handler when
+ * a computation step has finished. Since the total number of steps
+ * can't be determined in advance, we don't show a percent value but
+ * a rotating dash. */
+void DVIToSVGActions::progress (const char *id) {
+ if (PROGRESSBAR_DELAY < 1000) {
+ static double time=System::time();
+ // slow down updating the progress indicator to prevent flickering
+ if (System::time() - time > 0.1) {
+ progress(0, 0, id);
+ time = System::time();
+ }
+ }
+}
+
+
+/** Returns the number of digits of a given integer. */
+static int digits (int n) {
+ if (n == 0)
+ return 1;
+ if (n > 0)
+ return util::ilog10(n)+1;
+ return util::ilog10(-n)+2;
+}
+
+
+/** Draws a simple progress indicator.
+ * @param[in] current current iteration step (of 'total' steps)
+ * @param[in] total total number of iteration steps
+ * @param[in] id ID of the subprocess providing the information */
+void DVIToSVGActions::progress (size_t current, size_t total, const char *id) {
+ static double time=0;
+ static bool draw=false; // show progress indicator?
+ static const char *prev_id=nullptr;
+ if (current == 0 && total > 0) {
+ time = System::time();
+ draw = false;
+ Message::mstream() << '\n';
+ }
+ // don't show the progress indicator before the given time has elapsed
+ if (!draw && System::time()-time > PROGRESSBAR_DELAY) {
+ draw = true;
+ Terminal::cursor(false);
+ }
+ if (draw && (System::time() - time > 0.1 || (total > 0 && current == total) || prev_id != id)) {
+ static int step = -1; // >=0: rotating dash
+ static size_t prev_current=0, prev_total=1;
+ const char *tips = "-\\|/";
+ if (total == 0) {
+ current = prev_current;
+ total = prev_total;
+ step = (step+1) % 4;
+ }
+ else {
+ prev_current = current;
+ prev_total = total;
+ step = -1;
+ }
+ // adapt length of progress indicator to terminal width
+ int cols = Terminal::columns();
+ int width = (cols == 0 || cols > 60) ? 50 : 49-60+cols;
+ double factor = double(current)/double(total);
+ int length = int(width*factor);
+ Message::mstream(false, Message::MC_PROGRESS)
+ << '[' << string(length, '=')
+ << (factor < 1.0 ? (step < 0 ? ' ' : tips[step]) : '=')
+ << string(width-length, ' ')
+ << "] " << string(3-digits(int(100*factor)), ' ') << int(100*factor)
+ << "%\r";
+ // overprint indicator when finished
+ if (factor == 1.0) {
+ Message::estream().clearline();
+ Terminal::cursor(true);
+ }
+ time = System::time();
+ prev_id = id;
+ }
+}
diff --git a/dviware/dvisvgm/src/DVIToSVGActions.hpp b/dviware/dvisvgm/src/DVIToSVGActions.hpp
new file mode 100644
index 0000000000..e1292a5efb
--- /dev/null
+++ b/dviware/dvisvgm/src/DVIToSVGActions.hpp
@@ -0,0 +1,95 @@
+/*************************************************************************
+** DVIToSVGActions.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef DVITOSVGACTIONS_HPP
+#define DVITOSVGACTIONS_HPP
+
+#include <unordered_map>
+#include <unordered_set>
+#include "BoundingBox.hpp"
+#include "DVIActions.hpp"
+#include "Matrix.hpp"
+#include "SpecialActions.hpp"
+#include "SpecialManager.hpp"
+#include "SVGTree.hpp"
+
+
+class DVIToSVG;
+class FileFinder;
+class Font;
+class XMLNode;
+
+class DVIToSVGActions : public DVIActions, public SpecialActions {
+ using CharMap = std::unordered_map<const Font*, std::set<int>>;
+ using FontSet = std::unordered_set<const Font*>;
+ using BoxMap = std::unordered_map<std::string,BoundingBox>;
+
+ public:
+ DVIToSVGActions (DVIToSVG &dvisvg, SVGTree &svg);
+ void reset () override;
+ void setChar (double x, double y, unsigned c, bool vertical, const Font &f) override;
+ void setRule (double x, double y, double height, double width) override;
+ void setBgColor (const Color &color) override;
+ void setColor (const Color &color) override {_svg.setColor(color);}
+ void setMatrix (const Matrix &m) override {_svg.setMatrix(m);}
+ const Matrix& getMatrix () const override {return _svg.getMatrix();}
+ Matrix getPageTransformation () const override {return _dvireader->getPageTransformation();}
+ Color getColor () const override {return _svg.getColor();}
+ int getDVIStackDepth () const override {return _dvireader->stackDepth();}
+ unsigned getCurrentPageNumber () const override {return _dvireader->currentPageNumber();}
+ void setTextOrientation (bool vertical) override {_svg.setVertical(vertical);}
+ void moveToX (double x, bool forceSVGMove) override;
+ void moveToY (double y, bool forceSVGMove) override;
+ void setFont (int num, const Font &font) override;
+ void special (const std::string &spc, double dvi2bp, bool preprocessing=false) override;
+ void beginPage (unsigned pageno, const std::vector<int32_t> &c) override;
+ void endPage (unsigned pageno) override;
+ void progress (size_t current, size_t total, const char *id=nullptr) override;
+ void progress (const char *id) override;
+ double getX() const override {return _dvireader->getXPos();}
+ double getY() const override {return _dvireader->getYPos();}
+ void setX (double x) override {_dvireader->translateToX(x); _svg.setX(x);}
+ void setY (double y) override {_dvireader->translateToY(y); _svg.setY(y);}
+ void finishLine () override {_dvireader->finishLine();}
+ const SVGTree& svgTree () const override {return _svg;}
+ BoundingBox& bbox () override {return _bbox;}
+ BoundingBox& bbox (const std::string &name, bool reset=false) override;
+ void embed (const BoundingBox &bbox) override;
+ void embed (const DPair &p, double r=0) override;
+ std::string getSVGFilename (unsigned pageno) const override;
+ std::string getBBoxFormatString () const override;
+ CharMap& getUsedChars () const {return _usedChars;}
+ const FontSet& getUsedFonts () const {return _usedFonts;}
+ void setDVIReader (BasicDVIReader &r) {_dvireader = &r;}
+
+ private:
+ SVGTree &_svg;
+ BasicDVIReader *_dvireader;
+ BoundingBox _bbox;
+ int _pageCount;
+ int _currentFontNum;
+ mutable CharMap _usedChars;
+ FontSet _usedFonts;
+ Color _bgcolor;
+ BoxMap _boxes;
+};
+
+
+#endif
diff --git a/dviware/dvisvgm/src/Directory.cpp b/dviware/dvisvgm/src/Directory.cpp
new file mode 100644
index 0000000000..ccc05a2e92
--- /dev/null
+++ b/dviware/dvisvgm/src/Directory.cpp
@@ -0,0 +1,131 @@
+/*************************************************************************
+** Directory.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include "Directory.hpp"
+
+using namespace std;
+
+#ifdef _WIN32
+ #include "windows.hpp"
+#else
+ #include <cerrno>
+ #include <sys/stat.h>
+#endif
+
+
+Directory::Directory () {
+#if _WIN32
+ _handle = INVALID_HANDLE_VALUE;
+ _firstread = true;
+ memset(&_fileData, 0, sizeof(WIN32_FIND_DATA));
+#else
+ _dir = nullptr;
+ _dirent = nullptr;
+#endif
+}
+
+
+Directory::Directory (const string &dirname) {
+#if _WIN32
+ _handle = INVALID_HANDLE_VALUE;
+ _firstread = true;
+ memset(&_fileData, 0, sizeof(WIN32_FIND_DATA));
+#else
+ _dir = nullptr;
+ _dirent = nullptr;
+#endif
+ open(dirname);
+}
+
+
+Directory::~Directory () {
+ close();
+}
+
+
+bool Directory::open (string dirname) {
+ _dirname = dirname;
+#ifdef _WIN32
+ _firstread = true;
+ if (dirname[dirname.length()-1] == '/' || dirname[dirname.length()-1] == '\\')
+ dirname = dirname.substr(0, dirname.length()-1);
+ dirname += "\\*";
+ _handle = FindFirstFile(dirname.c_str(), &_fileData);
+ return _handle != INVALID_HANDLE_VALUE;
+#else
+ _dir = opendir(_dirname.c_str());
+ return bool(_dir);
+#endif
+}
+
+
+void Directory::close () {
+#ifdef _WIN32
+ FindClose(_handle);
+#else
+ if (_dir) {
+ closedir(_dir);
+ _dir = nullptr;
+ }
+#endif
+}
+
+
+/** Reads first/next directory entry.
+ * @param[in] type type of entry to return (a: file or dir, f: file, d: dir)
+ * @return name of entry */
+const char* Directory::read (EntryType type) {
+#ifdef _WIN32
+ if (_handle == INVALID_HANDLE_VALUE)
+ return nullptr;
+ while (_firstread || FindNextFile(_handle, &_fileData)) {
+ _firstread = false;
+ if (_fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+ if (type == ET_FILE_OR_DIR || type == ET_DIR)
+ return _fileData.cFileName;
+ }
+ else if (type == ET_FILE_OR_DIR || type == ET_FILE)
+ return _fileData.cFileName;
+ }
+ FindClose(_handle);
+ _handle = INVALID_HANDLE_VALUE;
+ return nullptr;
+#else
+ if (_dir) {
+ while ((_dirent = readdir(_dir))) {
+ string path = _dirname + "/" + _dirent->d_name;
+ struct stat stats;
+ if (stat(path.c_str(), &stats) == 0) {
+ if (S_ISDIR(stats.st_mode)) {
+ if (type == ET_FILE_OR_DIR || type == ET_DIR)
+ return _dirent->d_name;
+ }
+ else if (type == ET_FILE_OR_DIR || type == ET_FILE)
+ return _dirent->d_name;
+ }
+ }
+ closedir(_dir);
+ _dir = nullptr;
+ }
+ return nullptr;
+#endif
+}
+
+
diff --git a/dviware/dvisvgm/src/Directory.hpp b/dviware/dvisvgm/src/Directory.hpp
new file mode 100644
index 0000000000..a703c18977
--- /dev/null
+++ b/dviware/dvisvgm/src/Directory.hpp
@@ -0,0 +1,55 @@
+/*************************************************************************
+** Directory.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef DIRECTORY_HPP
+#define DIRECTORY_HPP
+
+#include <string>
+#ifdef _WIN32
+ #include "windows.hpp"
+#else
+ #include <dirent.h>
+#endif
+
+class Directory {
+ public:
+ enum EntryType {ET_FILE, ET_DIR, ET_FILE_OR_DIR};
+
+ public:
+ Directory ();
+ explicit Directory (const std::string &path);
+ ~Directory ();
+ bool open (std::string path);
+ void close ();
+ const char* read (EntryType type=ET_FILE_OR_DIR);
+
+ private:
+ std::string _dirname;
+#ifdef _WIN32
+ bool _firstread;
+ HANDLE _handle;
+ WIN32_FIND_DATA _fileData;
+#else
+ DIR *_dir;
+ struct dirent *_dirent;
+#endif
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/DvisvgmSpecialHandler.cpp b/dviware/dvisvgm/src/DvisvgmSpecialHandler.cpp
new file mode 100644
index 0000000000..dadf196f77
--- /dev/null
+++ b/dviware/dvisvgm/src/DvisvgmSpecialHandler.cpp
@@ -0,0 +1,539 @@
+/*************************************************************************
+** DvisvgmSpecialHandler.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <array>
+#include <cstring>
+#include <utility>
+#include "DvisvgmSpecialHandler.hpp"
+#include "InputBuffer.hpp"
+#include "InputReader.hpp"
+#include "Length.hpp"
+#include "SpecialActions.hpp"
+#include "SVGTree.hpp"
+#include "utility.hpp"
+#include "XMLNode.hpp"
+#include "XMLString.hpp"
+
+using namespace std;
+
+
+DvisvgmSpecialHandler::DvisvgmSpecialHandler () :
+ _currentMacro(_macros.end()),
+ _defsParser(&SVGTree::appendToDefs, &SVGTree::pushDefsContext, &SVGTree::popDefsContext),
+ _pageParser(&SVGTree::appendToPage, &SVGTree::pushPageContext, &SVGTree::popPageContext)
+{
+}
+
+
+void DvisvgmSpecialHandler::preprocess (const string&, istream &is, SpecialActions&) {
+ struct Command {
+ const char *name;
+ void (DvisvgmSpecialHandler::*handler)(InputReader&);
+ };
+ constexpr array<Command, 5> commands {{
+ {"raw", &DvisvgmSpecialHandler::preprocessRaw},
+ {"rawdef", &DvisvgmSpecialHandler::preprocessRawDef},
+ {"rawset", &DvisvgmSpecialHandler::preprocessRawSet},
+ {"endrawset", &DvisvgmSpecialHandler::preprocessEndRawSet},
+ {"rawput", &DvisvgmSpecialHandler::preprocessRawPut}
+ }};
+
+ StreamInputReader ir(is);
+ const string cmdstr = ir.getWord();
+ auto it = find_if(commands.begin(), commands.end(), [&](const Command &cmd) {
+ return cmd.name == cmdstr;
+ });
+ if (it != commands.end()) {
+ ir.skipSpace();
+ (this->*it->handler)(ir);
+ }
+}
+
+
+void DvisvgmSpecialHandler::preprocessRawSet (InputReader &ir) {
+ _nestingLevel++;
+ string id = ir.getString();
+ if (id.empty())
+ throw SpecialException("definition of unnamed SVG fragment");
+ if (_nestingLevel > 1)
+ throw SpecialException("nested definition of SVG fragment '" + id + "'");
+
+ _currentMacro = _macros.find(id);
+ if (_currentMacro != _macros.end()) {
+ _currentMacro = _macros.end();
+ throw SpecialException("redefinition of SVG fragment '" + id + "'");
+ }
+ pair<string, StringVector> entry(id, StringVector());
+ pair<MacroMap::iterator, bool> state = _macros.emplace(move(entry));
+ _currentMacro = state.first;
+}
+
+
+void DvisvgmSpecialHandler::preprocessEndRawSet (InputReader&) {
+ if (_nestingLevel > 0 && --_nestingLevel == 0)
+ _currentMacro = _macros.end();
+}
+
+
+void DvisvgmSpecialHandler::preprocessRaw (InputReader &ir) {
+ if (_currentMacro == _macros.end())
+ return;
+ string str = ir.getLine();
+ if (!str.empty())
+ _currentMacro->second.emplace_back("P"+str);
+}
+
+
+void DvisvgmSpecialHandler::preprocessRawDef (InputReader &ir) {
+ if (_currentMacro == _macros.end())
+ return;
+ string str = ir.getLine();
+ if (!str.empty())
+ _currentMacro->second.emplace_back("D"+str);
+}
+
+
+void DvisvgmSpecialHandler::preprocessRawPut (InputReader &ir) {
+ if (_currentMacro != _macros.end())
+ throw SpecialException("dvisvgm:rawput not allowed inside rawset/endrawset");
+}
+
+
+/** Evaluates and executes a dvisvgm special statement.
+ * @param[in] prefix special prefix read by the SpecialManager
+ * @param[in] is the special statement is read from this stream
+ * @param[in] actions object providing the actions that can be performed by the SpecialHandler */
+bool DvisvgmSpecialHandler::process (const string &prefix, istream &is, SpecialActions &actions) {
+ struct Command {
+ const char *name;
+ void (DvisvgmSpecialHandler::*handler)(InputReader&, SpecialActions&);
+ };
+ constexpr array<Command, 7> commands {{
+ {"raw", &DvisvgmSpecialHandler::processRaw},
+ {"rawdef", &DvisvgmSpecialHandler::processRawDef},
+ {"rawset", &DvisvgmSpecialHandler::processRawSet},
+ {"endrawset", &DvisvgmSpecialHandler::processEndRawSet},
+ {"rawput", &DvisvgmSpecialHandler::processRawPut},
+ {"bbox", &DvisvgmSpecialHandler::processBBox},
+ {"img", &DvisvgmSpecialHandler::processImg}
+ }};
+ StreamInputReader ir(is);
+ const string cmdstr = ir.getWord();
+ auto it = find_if(commands.begin(), commands.end(), [&](const Command &cmd) {
+ return cmd.name == cmdstr;
+ });
+ if (it != commands.end()) {
+ ir.skipSpace();
+ (this->*it->handler)(ir, actions);
+ }
+ return true;
+}
+
+
+/** Replaces constants of the form {?name} by their corresponding value.
+ * @param[in,out] str text to expand
+ * @param[in] actions interfcae to the world outside the special handler */
+static void expand_constants (string &str, SpecialActions &actions) {
+ bool repl_bbox = true;
+ while (repl_bbox) {
+ size_t pos = str.find("{?bbox ");
+ if (pos == string::npos)
+ repl_bbox = false;
+ else {
+ size_t endpos = pos+7;
+ while (endpos < str.length() && isalnum(str[endpos]))
+ ++endpos;
+ if (str[endpos] == '}') {
+ BoundingBox &box=actions.bbox(str.substr(pos+7, endpos-pos-7));
+ str.replace(pos, endpos-pos+1, box.toSVGViewBox());
+ }
+ else
+ repl_bbox = false;
+ }
+ }
+ struct Constant {
+ const char *name;
+ string val;
+ };
+ const array<Constant, 5> constants {{
+ {"x", XMLString(actions.getX())},
+ {"y", XMLString(actions.getY())},
+ {"color", actions.getColor().svgColorString()},
+ {"matrix", actions.getMatrix().toSVG()},
+ {"nl", "\n"},
+ }};
+ for (const Constant &constant : constants) {
+ const string pattern = string("{?")+constant.name+"}";
+ size_t pos = str.find(pattern);
+ while (pos != string::npos) {
+ str.replace(pos, strlen(constant.name)+3, constant.val);
+ pos = str.find(pattern, pos+constant.val.length()); // look for further matches
+ }
+ }
+}
+
+
+/** Processes raw SVG fragments from the input stream. The SVG data must represent
+ * a single or multiple syntactically complete XML parts, like opening/closing tags,
+ * comments, or CDATA blocks. These must not be split and distributed over several
+ * 'raw' statements. Elements can be split but element tags can't.
+ * Example: "<g transform=" is invalid, "<g transform='scale(2,3)'>" is ok. */
+void DvisvgmSpecialHandler::processRaw (InputReader &ir, SpecialActions &actions) {
+ if (_nestingLevel == 0) {
+ string xml = ir.getLine();
+ if (!xml.empty()) {
+ expand_constants(xml, actions);
+ _pageParser.parse(xml, actions);
+ }
+ }
+}
+
+
+void DvisvgmSpecialHandler::processRawDef (InputReader &ir, SpecialActions &actions) {
+ if (_nestingLevel == 0) {
+ string xml = ir.getLine();
+ if (!xml.empty()) {
+ expand_constants(xml, actions);
+ _defsParser.parse(xml, actions);
+ }
+ }
+}
+
+
+void DvisvgmSpecialHandler::processRawSet (InputReader&, SpecialActions&) {
+ _nestingLevel++;
+}
+
+
+void DvisvgmSpecialHandler::processEndRawSet (InputReader&, SpecialActions&) {
+ if (_nestingLevel > 0)
+ _nestingLevel--;
+}
+
+
+void DvisvgmSpecialHandler::processRawPut (InputReader &ir, SpecialActions &actions) {
+ if (_nestingLevel > 0)
+ return;
+ string id = ir.getString();
+ auto it = _macros.find(id);
+ if (it == _macros.end())
+ throw SpecialException("undefined SVG fragment '" + id + "' referenced");
+
+ StringVector &defvector = it->second;
+ for (string &defstr : defvector) {
+ char &type = defstr[0];
+ string def = defstr.substr(1);
+ if ((type == 'P' || type == 'D') && !def.empty()) {
+ expand_constants(def, actions);
+ if (type == 'P')
+ _pageParser.parse(def, actions);
+ else { // type == 'D'
+ _defsParser.parse(def, actions);
+ type = 'L'; // locked
+ }
+ }
+ }
+}
+
+
+/** Embeds the virtual rectangle (x, y ,w , h) into the current bounding box,
+ * where (x,y) is the lower left vertex composed of the current DVI position.
+ * @param[in] w width of the rectangle in PS point units
+ * @param[in] h height of the rectangle in PS point units
+ * @param[in] d depth of the rectangle in PS point units
+ * @param[in] transform if true, apply the current transformation matrix to the rectangle
+ * @param[in] actions object providing the actions that can be performed by the SpecialHandler */
+static void update_bbox (Length w, Length h, Length d, bool transform, SpecialActions &actions) {
+ double x = actions.getX();
+ double y = actions.getY();
+ BoundingBox bbox1(x, y, x+w.bp(), y-h.bp());
+ BoundingBox bbox2(x, y, x+w.bp(), y+d.bp());
+ if (transform) {
+ bbox1.transform(actions.getMatrix());
+ bbox2.transform(actions.getMatrix());
+ }
+ actions.embed(bbox1);
+ actions.embed(bbox2);
+}
+
+
+/** Reads a length value including a trailing unit specifier and returns it. */
+static Length read_length (InputReader &ir) {
+ Length length;
+ ir.skipSpace();
+ if (!isalpha(ir.peek())) {
+ double val = ir.getDouble();
+ string unit = isalpha(ir.peek()) ? ir.getString(2) : "pt";
+ length = Length(val, unit);
+ }
+ return length;
+}
+
+
+/** Evaluates the special dvisvgm:bbox.
+ * variant 1: dvisvgm:bbox [r[el]] <width> <height> [<depth>] [transform]
+ * variant 2: dvisvgm:bbox a[bs] <x1> <y1> <x2> <y2> [transform]
+ * variant 3: dvisvgm:bbox f[ix] <x1> <y1> <x2> <y2> [transform]
+ * variant 4: dvisvgm:bbox n[ew] <name>
+ * variant 5: dvisvgm:bbox lock | unlock */
+void DvisvgmSpecialHandler::processBBox (InputReader &ir, SpecialActions &actions) {
+ ir.skipSpace();
+ if (ir.check("lock"))
+ actions.bbox().lock();
+ else if (ir.check("unlock"))
+ actions.bbox().unlock();
+ else {
+ int c = ir.peek();
+ try {
+ if (!isalpha(c))
+ c = 'r'; // no mode specifier => relative box parameters
+ else {
+ while (!isspace(ir.peek())) // skip trailing characters
+ ir.get();
+ if (c == 'n') { // "new": create new local bounding box
+ ir.skipSpace();
+ string name;
+ while (isalnum(ir.peek()))
+ name += char(ir.get());
+ ir.skipSpace();
+ if (!name.empty() && ir.eof())
+ actions.bbox(name, true); // create new user box
+ }
+ else if (c == 'a' || c == 'f') { // "abs" or "fix"
+ Length lengths[4];
+ for (Length &len : lengths)
+ len = read_length(ir);
+ BoundingBox b(lengths[0], lengths[1], lengths[2], lengths[3]);
+ ir.skipSpace();
+ if (ir.check("transform"))
+ b.transform(actions.getMatrix());
+ if (c == 'a')
+ actions.embed(b);
+ else {
+ actions.bbox() = b;
+ actions.bbox().lock();
+ }
+ }
+ }
+ if (c == 'r') {
+ Length w = read_length(ir);
+ Length h = read_length(ir);
+ Length d = read_length(ir);
+ ir.skipSpace();
+ update_bbox(w, h, d, ir.check("transform"), actions);
+ }
+ }
+ catch (const UnitException &e) {
+ throw SpecialException(string("dvisvgm:bbox: ") + e.what());
+ }
+ }
+}
+
+
+void DvisvgmSpecialHandler::processImg (InputReader &ir, SpecialActions &actions) {
+ try {
+ Length w = read_length(ir);
+ Length h = read_length(ir);
+ string f = ir.getString();
+ update_bbox(w, h, Length(0), false, actions);
+ auto img = util::make_unique<XMLElement>("image");
+ img->addAttribute("x", actions.getX());
+ img->addAttribute("y", actions.getY());
+ img->addAttribute("width", w.bp());
+ img->addAttribute("height", h.bp());
+ img->addAttribute("xlink:href", f);
+ if (!actions.getMatrix().isIdentity())
+ img->addAttribute("transform", actions.getMatrix().toSVG());
+ actions.svgTree().appendToPage(std::move(img));
+ }
+ catch (const UnitException &e) {
+ throw SpecialException(string("dvisvgm:img: ") + e.what());
+ }
+}
+
+
+void DvisvgmSpecialHandler::dviPreprocessingFinished () {
+ string id;
+ if (_currentMacro != _macros.end())
+ id = _currentMacro->first;
+ // ensure all pattern definitions are closed after pre-processing the whole DVI file
+ _currentMacro = _macros.end();
+ _nestingLevel = 0;
+ if (!id.empty())
+ throw SpecialException("missing dvisvgm:endrawset for SVG fragment '" + id + "'");
+}
+
+
+void DvisvgmSpecialHandler::dviEndPage (unsigned, SpecialActions &actions) {
+ _defsParser.flush(actions);
+ _pageParser.flush(actions);
+ actions.bbox().unlock();
+ for (auto &strvecpair : _macros) {
+ StringVector &vec = strvecpair.second;
+ for (string &str : vec) {
+ // activate locked parts of a pattern again
+ if (str[0] == 'L')
+ str[0] = 'D';
+ }
+ }
+}
+
+
+vector<const char*> DvisvgmSpecialHandler::prefixes() const {
+ vector<const char*> pfx {"dvisvgm:"};
+ return pfx;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+/** Parses a fragment of XML code, creates corresponding XML nodes and adds them
+ * to the SVG tree. The code may be split and processed by several calls of this
+ * function. Incomplete chunks that can't be processed yet are stored and picked
+ * up again together with the next incoming XML fragment. If no further code should
+ * be appended, parameter 'finish' must be set.
+ * @param[in] xml XML fragment to parse
+ * @param[in] actions object providing the SVG tree functions
+ * @param[in] finish if true, no more XML is expected and parsing is finished */
+void DvisvgmSpecialHandler::XMLParser::parse (const string &xml, SpecialActions &actions, bool finish) {
+ // collect/extract an XML fragment that only contains complete tags
+ // incomplete tags are held back
+ _xmlbuf += xml;
+ size_t left=0, right;
+ while (left != string::npos) {
+ right = _xmlbuf.find('<', left);
+ if (left < right && left < _xmlbuf.length()) // plain text found?
+ (actions.svgTree().*_append)(util::make_unique<XMLText>(_xmlbuf.substr(left, right-left)));
+ if (right != string::npos) {
+ left = right;
+ if (_xmlbuf.compare(left, 9, "<![CDATA[") == 0) {
+ right = _xmlbuf.find("]]>", left+9);
+ if (right == string::npos) {
+ if (finish) throw SpecialException("expected ']]>' at end of CDATA block");
+ break;
+ }
+ (actions.svgTree().*_append)(util::make_unique<XMLCData>(_xmlbuf.substr(left+9, right-left-9)));
+ right += 2;
+ }
+ else if (_xmlbuf.compare(left, 4, "<!--") == 0) {
+ right = _xmlbuf.find("-->", left+4);
+ if (right == string::npos) {
+ if (finish) throw SpecialException("expected '-->' at end of comment");
+ break;
+ }
+ (actions.svgTree().*_append)(util::make_unique<XMLComment>(_xmlbuf.substr(left+4, right-left-4)));
+ right += 2;
+ }
+ else if (_xmlbuf.compare(left, 2, "<?") == 0) {
+ right = _xmlbuf.find("?>", left+2);
+ if (right == string::npos) {
+ if (finish) throw SpecialException("expected '?>' at end of processing instruction");
+ break;
+ }
+ (actions.svgTree().*_append)(util::make_unique<XMLText>(_xmlbuf.substr(left, right-left+2)));
+ right++;
+ }
+ else if (_xmlbuf.compare(left, 2, "</") == 0) {
+ right = _xmlbuf.find('>', left+2);
+ if (right == string::npos) {
+ if (finish) throw SpecialException("missing '>' at end of closing XML tag");
+ break;
+ }
+ closeElement(_xmlbuf.substr(left+2, right-left-2), actions);
+ }
+ else {
+ right = _xmlbuf.find('>', left+1);
+ if (right == string::npos) {
+ if (finish) throw SpecialException("missing '>' or '/>' at end of opening XML tag");
+ break;
+ }
+ openElement(_xmlbuf.substr(left+1, right-left-1), actions);
+ }
+ }
+ left = right;
+ if (right != string::npos)
+ left++;
+ }
+ if (left == string::npos)
+ _xmlbuf.clear();
+ else
+ _xmlbuf.erase(0, left);
+}
+
+
+/** Processes an opening element tag.
+ * @param[in] tag tag without leading and trailing angle brackets */
+void DvisvgmSpecialHandler::XMLParser::openElement (const string &tag, SpecialActions &actions) {
+ StringInputBuffer ib(tag);
+ BufferInputReader ir(ib);
+ string name = ir.getString("/ \t\n\r");
+ ir.skipSpace();
+ auto elemNode = util::make_unique<XMLElement>(name);
+ map<string, string> attribs;
+ if (ir.parseAttributes(attribs, true, "\"'")) {
+ for (const auto &attrpair : attribs)
+ elemNode->addAttribute(attrpair.first, attrpair.second);
+ }
+ ir.skipSpace();
+ if (ir.peek() == '/') // end of empty element tag
+ (actions.svgTree().*_append)(std::move(elemNode));
+ else if (ir.peek() < 0) { // end of opening tag
+ _nameStack.push_back(name);
+ (actions.svgTree().*_pushContext)(std::move(elemNode));
+ }
+ else
+ throw SpecialException("'>' or '/>' expected at end of opening tag <"+name);
+}
+
+
+/** Processes a closing element tag.
+ * @param[in] tag tag without leading and trailing angle brackets */
+void DvisvgmSpecialHandler::XMLParser::closeElement (const string &tag, SpecialActions &actions) {
+ StringInputBuffer ib(tag);
+ BufferInputReader ir(ib);
+ string name = ir.getString(" \t\n\r");
+ ir.skipSpace();
+ if (ir.peek() >= 0)
+ throw SpecialException("'>' expected at end of closing tag </"+name);
+ if (_nameStack.empty())
+ throw SpecialException("spurious closing tag </" + name + ">");
+ if (_nameStack.back() != name)
+ throw SpecialException("expected </" + name + "> but found </" + _nameStack.back() + ">");
+ (actions.svgTree().*_popContext)();
+ _nameStack.pop_back();
+}
+
+
+/** Processes any remaining XML fragments, checks for missing closing tags,
+ * and resets the parser state. */
+void DvisvgmSpecialHandler::XMLParser::flush (SpecialActions &actions) {
+ if (!_xmlbuf.empty()) {
+ parse("", actions, true);
+ _xmlbuf.clear();
+ }
+ string tags;
+ while (!_nameStack.empty()) {
+ tags += "</"+_nameStack.back()+">, ";
+ _nameStack.pop_back();
+ }
+ if (!tags.empty()) {
+ tags.resize(tags.length()-2);
+ throw SpecialException("missing closing tags: "+tags);
+ }
+} \ No newline at end of file
diff --git a/dviware/dvisvgm/src/DvisvgmSpecialHandler.hpp b/dviware/dvisvgm/src/DvisvgmSpecialHandler.hpp
new file mode 100644
index 0000000000..a8bde952e6
--- /dev/null
+++ b/dviware/dvisvgm/src/DvisvgmSpecialHandler.hpp
@@ -0,0 +1,105 @@
+/*************************************************************************
+** DvisvgmSpecialHandler.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef DVISVGMSPECIALHANDLER_HPP
+#define DVISVGMSPECIALHANDLER_HPP
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+#include "SpecialHandler.hpp"
+
+class InputReader;
+class SpecialActions;
+class SVGTree;
+class XMLElement;
+class XMLNode;
+
+#ifdef _MSC_VER
+// MSVC: Prevent aggressive optimization of pointers to member functions.
+// Instatiating class DvisvgmSpecialHandler without the following pragma
+// leads to memory corruption.
+// https://docs.microsoft.com/en-us/cpp/preprocessor/pointers-to-members
+#pragma pointers_to_members(full_generality, single_inheritance)
+#endif
+
+class DvisvgmSpecialHandler : public SpecialHandler {
+ class XMLParser {
+ using AppendFunc = void (SVGTree::*)(std::unique_ptr<XMLNode>);
+ using PushFunc = void (SVGTree::*)(std::unique_ptr<XMLElement>);
+ using PopFunc = void (SVGTree::*)();
+ using NameStack = std::vector<std::string>;
+
+ public:
+ XMLParser (AppendFunc append, PushFunc push, PopFunc pop)
+ : _append(append), _pushContext(push), _popContext(pop) {}
+
+ void parse (const std::string &xml, SpecialActions &actions, bool finish=false);
+ void flush (SpecialActions &actions);
+
+ protected:
+ void openElement (const std::string &tag, SpecialActions &actions);
+ void closeElement (const std::string &tag, SpecialActions &actions);
+
+ private:
+ AppendFunc _append;
+ PushFunc _pushContext;
+ PopFunc _popContext;
+ std::string _xmlbuf;
+ NameStack _nameStack; ///< names of nested elements still missing a closing tag
+ };
+
+ using StringVector = std::vector<std::string>;
+ using MacroMap = std::unordered_map<std::string, StringVector>;
+
+ public:
+ DvisvgmSpecialHandler ();
+ const char* name () const override {return "dvisvgm";}
+ const char* info () const override {return "special set for embedding raw SVG snippets";}
+ std::vector<const char*> prefixes() const override;
+ void preprocess (const std::string &prefix, std::istream &is, SpecialActions &actions) override;
+ bool process (const std::string &prefix, std::istream &is, SpecialActions &actions) override;
+
+ protected:
+ void preprocessRaw (InputReader &ir);
+ void preprocessRawDef (InputReader &ir);
+ void preprocessRawSet (InputReader &ir);
+ void preprocessEndRawSet (InputReader &ir);
+ void preprocessRawPut (InputReader &ir);
+ void processRaw (InputReader &ir, SpecialActions &actions);
+ void processRawDef (InputReader &ir, SpecialActions &actions);
+ void processRawSet (InputReader &ir, SpecialActions &actions);
+ void processEndRawSet (InputReader &ir, SpecialActions &actions);
+ void processRawPut (InputReader &ir, SpecialActions &actions);
+ void processBBox (InputReader &ir, SpecialActions &actions);
+ void processImg (InputReader &ir, SpecialActions &actions);
+ void dviPreprocessingFinished () override;
+ void dviEndPage (unsigned pageno, SpecialActions &actions) override;
+
+ private:
+ MacroMap _macros;
+ MacroMap::iterator _currentMacro;
+ int _nestingLevel=0; ///< nesting depth of rawset specials
+ XMLParser _defsParser; ///< parses XML added by 'rawdef' specials
+ XMLParser _pageParser; ///< parses XML added by 'raw' specials
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/EPSFile.cpp b/dviware/dvisvgm/src/EPSFile.cpp
new file mode 100644
index 0000000000..5a5fccc08f
--- /dev/null
+++ b/dviware/dvisvgm/src/EPSFile.cpp
@@ -0,0 +1,114 @@
+/*************************************************************************
+** EPSFile.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <array>
+#include <cstring>
+#include <istream>
+#include <limits>
+#include "EPSFile.hpp"
+#include "InputBuffer.hpp"
+#include "InputReader.hpp"
+
+using namespace std;
+
+
+/** Reads a little-endian 32-bit integer from the given input stream. */
+static uint32_t getUInt32 (istream &is) {
+ uint32_t value=0;
+ char buf[4];
+ is.read(buf, 4);
+ for (int i=0; i < 4; i++)
+ value |= ((buf[i] & 255) << (8*i));
+ return value;
+}
+
+
+static size_t getline (istream &is, char *line, size_t n) {
+ char buf[512];
+ is.get(buf, min(n, (size_t)512)-1);
+ n = is.gcount();
+ size_t linelen=0;
+ for (size_t i=0; i < n; i++)
+ if (isprint(buf[i]))
+ line[linelen++] = buf[i];
+ line[linelen] = 0;
+ if (is.peek() == '\n')
+ is.get();
+ else
+ is.ignore(numeric_limits<size_t>::max(), '\n');
+ return linelen;
+}
+
+
+EPSFile::EPSFile (const string &fname) : _ifs(fname, ios::binary) {
+ if (_ifs) {
+ if (getUInt32(_ifs) != 0xC6D3D0C5) // no binary header present?
+ _ifs.seekg(0); // go back to the first byte
+ else {
+ _offset = getUInt32(_ifs); // stream offset where PS part of the file begins
+ _pslength = getUInt32(_ifs); // length of PS section in bytes
+ _ifs.seekg(_offset); // continue reading at the beginning of the PS section
+ }
+ string str;
+ str += char(_ifs.get());
+ str += char(_ifs.get());
+ _headerValid = (str == "%!");
+ _ifs.seekg(0);
+ }
+}
+
+
+/** Returns an input stream for the EPS file. The stream pointer is automatically moved
+ * to the beginning of the ASCII (PostScript) part of the file. */
+istream& EPSFile::istream () const {
+ _ifs.clear();
+ _ifs.seekg(_offset);
+ return _ifs;
+}
+
+
+/** Extracts the bounding box information from the DSC header/footer (if present).
+ * @return the extracted bounding box */
+BoundingBox EPSFile::bbox () const {
+ BoundingBox box;
+ std::istream &is = EPSFile::istream();
+ if (is) {
+ char buf[64];
+ while (is) {
+ size_t linelen = getline(is, buf, 64);
+ if (strncmp(buf, "%%BoundingBox:", 14) == 0) {
+ CharInputBuffer ib(buf, linelen);
+ BufferInputReader ir(ib);
+ ir.skip(14);
+ ir.skipSpace();
+ if (!ir.check("(atend)", true)) {
+ array<int, 4> values;
+ for (int &v : values) {
+ ir.skipSpace();
+ ir.parseInt(v);
+ }
+ box = BoundingBox(values[0], values[1], values[2], values[3]);
+ break;
+ }
+ }
+ }
+ }
+ return box;
+}
diff --git a/dviware/dvisvgm/src/EPSFile.hpp b/dviware/dvisvgm/src/EPSFile.hpp
new file mode 100644
index 0000000000..4fdd03ee1b
--- /dev/null
+++ b/dviware/dvisvgm/src/EPSFile.hpp
@@ -0,0 +1,44 @@
+/*************************************************************************
+** EPSFile.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef EPSFILE_HPP
+#define EPSFILE_HPP
+
+#include <fstream>
+#include <istream>
+#include <string>
+#include "BoundingBox.hpp"
+
+class EPSFile {
+ public:
+ explicit EPSFile (const std::string &fname);
+ std::istream& istream () const;
+ bool hasValidHeader () const {return _headerValid;}
+ BoundingBox bbox () const;
+ uint32_t pslength () const {return _pslength;}
+
+ private:
+ mutable std::ifstream _ifs;
+ bool _headerValid=false; ///< true if file has a valid header
+ uint32_t _offset=0; ///< stream offset where ASCII part of the file begins
+ uint32_t _pslength=0; ///< length of PS section (in bytes)
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/EPSToSVG.hpp b/dviware/dvisvgm/src/EPSToSVG.hpp
new file mode 100644
index 0000000000..9b193dc9cb
--- /dev/null
+++ b/dviware/dvisvgm/src/EPSToSVG.hpp
@@ -0,0 +1,43 @@
+/*************************************************************************
+** EPSToSVG.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef EPSTOSVG_HPP
+#define EPSTOSVG_HPP
+
+#include "EPSFile.hpp"
+#include "ImageToSVG.hpp"
+
+class PsSpecialHandler;
+
+class EPSToSVG : public ImageToSVG {
+ public:
+ EPSToSVG (const std::string &fname, SVGOutputBase &out) : ImageToSVG(fname, out) {}
+ bool isSinglePageFormat() const override {return true;}
+ int totalPageCount() override {return 1;}
+
+ protected:
+ std::string imageFormat () const override {return "EPS";}
+ bool imageIsValid () const override {return EPSFile(filename()).hasValidHeader();}
+ BoundingBox imageBBox () const override {return EPSFile(filename()).bbox();}
+ std::string psSpecialCmd () const override {return "psfile=";}
+};
+
+#endif
+
diff --git a/dviware/dvisvgm/src/EmSpecialHandler.cpp b/dviware/dvisvgm/src/EmSpecialHandler.cpp
new file mode 100644
index 0000000000..49fdb11aa1
--- /dev/null
+++ b/dviware/dvisvgm/src/EmSpecialHandler.cpp
@@ -0,0 +1,265 @@
+/*************************************************************************
+** EmSpecialHandler.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <sstream>
+#include "EmSpecialHandler.hpp"
+#include "InputBuffer.hpp"
+#include "InputReader.hpp"
+#include "Length.hpp"
+#include "SpecialActions.hpp"
+#include "SVGTree.hpp"
+#include "XMLNode.hpp"
+#include "XMLString.hpp"
+
+using namespace std;
+
+
+EmSpecialHandler::EmSpecialHandler () : _linewidth(0.4*72/72.27) {
+}
+
+
+/** Computes the "cut vector" that is used to compute the line shape.
+ * Because each line has a width > 0 the actual shape of the line is a tetragon.
+ * The 4 vertices can be influenced by the cut parameter c that specifies
+ * a horizontal, vertical or orthogonal cut of a line end. Depending on c and the
+ * line's slope a cut vector v can be computed that, relatively to endpoint p, denotes
+ * the 2 vertices of that line end: v1=p+v and v2=p-v.
+ * @param[in] cuttype character identifying the cut direction ('h', 'v' or 'p')
+ * @param[in] linedir direction vector of line to be drawn
+ * @param[in] lw width of line to be drawn
+ * @return the "cut vector" */
+static DPair cut_vector (char cuttype, const DPair &linedir, double linewidth) {
+ DPair cut;
+ switch (cuttype) {
+ case 'v': // vertical
+ if (linedir.x() != 0) {
+ double slope = linedir.y()/linedir.x();
+ double h = sqrt(linewidth*linewidth*(1+slope*slope));
+ cut.y(h/2);
+ }
+ break;
+ case 'h': // horizontal
+ if (linedir.y() != 0) {
+ double slope = linedir.x()/linedir.y();
+ double h = sqrt(linewidth*linewidth*(1+slope*slope));
+ double sgn = slope < 0 ? 1.0 : -1.0;
+ cut.x(h*sgn/2);
+ }
+ break;
+ default: // c == 'p': perpendicular to the line vector
+ if (linedir.x() != 0 && linedir.y() != 0)
+ return linedir.ortho()/linedir.length() * (linewidth/2);
+ }
+ return cut;
+}
+
+
+/** Creates the SVG element that will a the line.
+ * @param[in] p1 first endpoint in PS point units
+ * @param[in] p2 second endpoint in PS point units
+ * @param[in] c1 cut method of first endpoint ('h', 'v' or 'p')
+ * @param[in] c2 cut method of second endpoint ('h', 'v' or 'p')
+ * @param[in] lw line width in PS point units
+ * @param[in] actions object providing the actions that can be performed by the SpecialHandler */
+static void create_line (const DPair &p1, const DPair &p2, char c1, char c2, double lw, SpecialActions &actions) {
+ unique_ptr<XMLElement> node;
+ DPair dir = p2-p1;
+ if (dir.x() == 0 || dir.y() == 0 || (c1 == 'p' && c2 == 'p')) {
+ // draw regular line
+ node = util::make_unique<XMLElement>("line");
+ node->addAttribute("x1", p1.x());
+ node->addAttribute("y1", p1.y());
+ node->addAttribute("x2", p2.x());
+ node->addAttribute("y2", p2.y());
+ node->addAttribute("stroke-width", lw);
+ node->addAttribute("stroke", actions.getColor().svgColorString());
+ // update bounding box
+ DPair cv = cut_vector('p', dir, lw);
+ actions.embed(p1+cv);
+ actions.embed(p1-cv);
+ actions.embed(p2+cv);
+ actions.embed(p2-cv);
+ }
+ else {
+ // draw polygon
+ DPair cv1 = cut_vector(c1, dir, lw);
+ DPair cv2 = cut_vector(c2, dir, lw);
+ DPair q11 = p1+cv1, q12 = p1-cv1;
+ DPair q21 = p2+cv2, q22 = p2-cv2;
+ ostringstream oss;
+ oss << XMLString(q11.x()) << ',' << XMLString(q11.y()) << ' '
+ << XMLString(q12.x()) << ',' << XMLString(q12.y()) << ' '
+ << XMLString(q22.x()) << ',' << XMLString(q22.y()) << ' '
+ << XMLString(q21.x()) << ',' << XMLString(q21.y());
+ node = util::make_unique<XMLElement>("polygon");
+ node->addAttribute("points", oss.str());
+ if (actions.getColor() != Color::BLACK)
+ node->addAttribute("fill", actions.getColor().svgColorString());
+ // update bounding box
+ actions.embed(q11);
+ actions.embed(q12);
+ actions.embed(q21);
+ actions.embed(q22);
+ }
+ actions.svgTree().appendToPage(std::move(node));
+}
+
+
+/** Reads a length (value + unit) and returns its value in PS points (bp).
+ * If no unit is specified, TeX points are assumed. */
+static double read_length (InputReader &in) {
+ double val = in.getDouble();
+ string unitstr;
+ if (isalpha(in.peek())) unitstr += char(in.get());
+ if (isalpha(in.peek())) unitstr += char(in.get());
+ Length::Unit unit = Length::Unit::PT;
+ try {
+ unit = Length::stringToUnit(unitstr);
+ }
+ catch (UnitException &e) {
+ }
+ return Length(val, unit).bp();
+}
+
+
+bool EmSpecialHandler::process (const string &prefix, istream &is, SpecialActions &actions) {
+ // em:moveto => move graphic cursor to dvi position
+ // em:lineto => draw line from graphic cursor to dvi cursor, then move graphic cursor to dvi position
+ // em:linewidth <w> => set line width to <w>
+ // em:point <n>[,<x>[,<y>]] => defines point <n> as (<x>,<y>); if <x> and/or <y> is missing,
+ // the corresponding dvi cursor coordinate is inserted
+ // <x> and <y> are lengths
+ // em:line <n>[h|v|p], <m>[h|v|p] [,<w>] => draws line of width <w> from point #<n> to point #<m>
+ // point number suffixes:
+ // h: cut line horizontally
+ // v: cut line vertically
+ // p: cut line orthogonally to line direction (default)
+ // if <w> is omitted, the global line width is used
+ //
+ // supported length units: pt, pc, in, bp, cm, mm, dd, cc, sp
+ // default line width: 0.4pt
+ struct Command {
+ const char *name;
+ void (EmSpecialHandler::*handler)(InputReader&, SpecialActions&);
+ } commands[] = {
+ {"point", &EmSpecialHandler::point},
+ {"line", &EmSpecialHandler::line},
+ {"moveto", &EmSpecialHandler::moveto},
+ {"lineto", &EmSpecialHandler::lineto},
+ {"linewidth", &EmSpecialHandler::linewidth},
+ {nullptr, nullptr}
+ };
+
+ StreamInputReader ir(is);
+ const string cmdstr = ir.getWord();
+ for (Command *cmd=commands; cmd->name; cmd++) {
+ if (cmdstr == cmd->name) {
+ (this->*cmd->handler)(ir, actions);
+ break;
+ }
+ }
+ return true;
+}
+
+
+/** Handles the "moveto" command that sets the drawing position to the current DVI position. */
+void EmSpecialHandler::moveto (InputReader&, SpecialActions &actions) {
+ _pos.x(actions.getX());
+ _pos.y(actions.getY());
+}
+
+
+/** Handles the "lineto" command that sraws a straight line from the current drawing position
+ * to the current DVI position, and sets the drawing position to the DVI position afterwards. */
+void EmSpecialHandler::lineto (InputReader&, SpecialActions &actions) {
+ DPair currpos(actions.getX(), actions.getY());
+ create_line(_pos, currpos, 'p', 'p', _linewidth, actions);
+ _pos = currpos;
+}
+
+
+/** Handles the "linewidth" command that changes the line width which affects all following
+ * drawing commands. */
+void EmSpecialHandler::linewidth (InputReader &ir, SpecialActions&) {
+ _linewidth = read_length(ir);
+}
+
+
+/** Handles the "point" command that adds a point to the point list. */
+void EmSpecialHandler::point (InputReader &ir, SpecialActions &actions) {
+ DPair pos(actions.getX(), actions.getY());
+ int n = ir.getInt();
+ if (ir.getPunct() == ',') {
+ pos.x(ir.getDouble());
+ if (ir.getPunct() == ',')
+ pos.y(ir.getDouble());
+ }
+ _points[n] = pos;
+}
+
+
+/** Handles the "line" command that draws a straight line between two points
+ * from the point list. */
+void EmSpecialHandler::line (InputReader &ir, SpecialActions& actions) {
+ int pointnum1 = ir.getInt();
+ int cut1 = 'p';
+ if (isalpha(ir.peek()))
+ cut1 = ir.get();
+ ir.getPunct();
+ int pointnum2 = ir.getInt();
+ int cut2 = 'p';
+ if (isalpha(ir.peek()))
+ cut2 = ir.get();
+ double linewidth = _linewidth;
+ if (ir.getPunct() == ',')
+ linewidth = read_length(ir);
+ auto it1=_points.find(pointnum1);
+ auto it2=_points.find(pointnum2);
+ if (it1 != _points.end() && it2 != _points.end())
+ create_line(it1->second, it2->second, char(cut1), char(cut2), linewidth, actions);
+ else {
+ // Line endpoints don't necessarily have to be defined before
+ // a line definition. If a point isn't defined yet, we put the line
+ // in a wait list and process the lines at the end of the page.
+ _lines.emplace_back(Line(pointnum1, pointnum2, char(cut1), char(cut2), linewidth));
+ }
+}
+
+
+/** This method is called at the end of a DVI page. Here we have to draw all pending
+ * lines that are still in the line list. All line endpoints must be defined until here. */
+void EmSpecialHandler::dviEndPage (unsigned pageno, SpecialActions &actions) {
+ for (const Line &line : _lines) {
+ auto pit1=_points.find(line.p1);
+ auto pit2=_points.find(line.p2);
+ if (pit1 != _points.end() && pit2 != _points.end())
+ create_line(pit1->second, pit2->second, line.c1, line.c2, line.width, actions);
+ // all lines with still undefined points are ignored
+ }
+ // line and point definitions are local to a page
+ _lines.clear();
+ _points.clear();
+}
+
+
+vector<const char*> EmSpecialHandler::prefixes() const {
+ vector<const char*> pfx {"em:"};
+ return pfx;
+}
diff --git a/dviware/dvisvgm/src/EmSpecialHandler.hpp b/dviware/dvisvgm/src/EmSpecialHandler.hpp
new file mode 100644
index 0000000000..546702c97c
--- /dev/null
+++ b/dviware/dvisvgm/src/EmSpecialHandler.hpp
@@ -0,0 +1,62 @@
+/*************************************************************************
+** EmSpecialHandler.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef EMSPECIALHANDLER_HPP
+#define EMSPECIALHANDLER_HPP
+
+#include <unordered_map>
+#include <vector>
+#include "Pair.hpp"
+#include "SpecialHandler.hpp"
+
+class InputReader;
+class SpecialActions;
+
+class EmSpecialHandler : public SpecialHandler {
+ struct Line {
+ Line (int pp1, int pp2, char cc1, char cc2, double w) : p1(pp1), p2(pp2), c1(cc1), c2(cc2), width(w) {}
+ int p1, p2; ///< point numbers of line ends
+ char c1, c2; ///< cut type of line ends (h, v or p)
+ double width; ///< line width
+ };
+
+ public:
+ EmSpecialHandler ();
+ const char* name () const override {return "em";}
+ const char* info () const override {return "line drawing statements of the emTeX special set";}
+ std::vector<const char*> prefixes() const override;
+ bool process (const std::string &prefix, std::istream &in, SpecialActions &actions) override;
+
+ protected:
+ void dviEndPage (unsigned pageno, SpecialActions &actions) override;
+ void linewidth (InputReader &ir, SpecialActions &actions);
+ void moveto (InputReader &ir, SpecialActions &actions);
+ void lineto (InputReader &ir, SpecialActions &actions);
+ void line (InputReader &ir, SpecialActions &actions);
+ void point (InputReader &ir, SpecialActions &actions);
+
+ private:
+ std::unordered_map<int, DPair> _points; ///< points defined by special em:point
+ std::vector<Line> _lines; ///< list of lines with undefined end points
+ double _linewidth; ///< global line width
+ DPair _pos; ///< current position of "graphic cursor"
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/EncFile.cpp b/dviware/dvisvgm/src/EncFile.cpp
new file mode 100644
index 0000000000..dba0a2544a
--- /dev/null
+++ b/dviware/dvisvgm/src/EncFile.cpp
@@ -0,0 +1,132 @@
+/*************************************************************************
+** EncFile.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <fstream>
+#include "Font.hpp"
+#include "EncFile.hpp"
+#include "InputBuffer.hpp"
+#include "InputReader.hpp"
+#include "FileFinder.hpp"
+#include "Message.hpp"
+
+using namespace std;
+
+static string read_entry (InputReader &in);
+static bool valid_name_char (int c);
+
+
+EncFile::EncFile (string encname) : _encname(std::move(encname))
+{
+ read();
+}
+
+
+const char* EncFile::path () const {
+ return FileFinder::instance().lookup(_encname+".enc", false);
+}
+
+
+/** Search for suitable enc-file and read its encoding information.
+ * The file contents must be a valid PostScript vector with 256 entries. */
+void EncFile::read () {
+ if (const char *p = path()) {
+ ifstream ifs(p);
+ read(ifs);
+ }
+ else
+ Message::wstream(true) << "encoding file '" << _encname << ".enc' not found\n";
+}
+
+
+/** Read encoding information from stream. */
+void EncFile::read (istream &is) {
+ StreamInputBuffer ib(is, 256);
+ BufferInputReader in(ib);
+ _table.resize(256);
+
+ // find beginning of vector
+ while (!in.eof()) {
+ in.skipSpace();
+ if (in.peek() == '%')
+ in.skipUntil("\n");
+ else
+ if (in.get() == '[')
+ break;
+ }
+
+ // read vector entries
+ int n=0;
+ while (!in.eof()) {
+ in.skipSpace();
+ if (in.peek() == '%')
+ in.skipUntil("\n");
+ else if (in.peek() == ']') {
+ in.get();
+ break;
+ }
+ else {
+ string entry = read_entry(in);
+ if (entry == ".notdef")
+ entry.clear();
+ if (n < 256)
+ _table[n++] = entry;
+ }
+ }
+ // remove trailing .notdef names
+ for (n--; n > 0 && _table[n].empty(); n--);
+ _table.resize(n+1);
+}
+
+
+static string read_entry (InputReader &in) {
+ string entry;
+ bool accept_slashes=true;
+ while (!in.eof() && ((in.peek() == '/' && accept_slashes) || valid_name_char(in.peek()))) {
+ if (in.peek() != '/')
+ accept_slashes = false;
+ entry += char(in.get());
+ }
+ if (entry.length() > 1) {
+ // strip leading slashes
+ // According to the PostScript specification, a single slash without further
+ // following characters is a valid name.
+ size_t n=0;
+ while (n < entry.length() && entry[n] == '/')
+ n++;
+ entry = entry.substr(n);
+ }
+ return entry;
+}
+
+
+static bool valid_name_char (int c) {
+ const char *delimiters = "<>(){}[]/~%";
+ return isprint(c) && !isspace(c) && !strchr(delimiters, c);
+}
+
+
+/** Returns an entry of the encoding table.
+ * @param[in] c character code
+ * @return character name assigned to character code c*/
+const char* EncFile::charName (uint32_t c) const {
+ if (c < _table.size())
+ return !_table[c].empty() ? _table[c].c_str() : nullptr;
+ return nullptr;
+}
diff --git a/dviware/dvisvgm/src/EncFile.hpp b/dviware/dvisvgm/src/EncFile.hpp
new file mode 100644
index 0000000000..fac26fd201
--- /dev/null
+++ b/dviware/dvisvgm/src/EncFile.hpp
@@ -0,0 +1,48 @@
+/*************************************************************************
+** EncFile.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef ENCFILE_HPP
+#define ENCFILE_HPP
+
+#include <istream>
+#include <map>
+#include <string>
+#include <vector>
+#include "FontEncoding.hpp"
+
+
+class EncFile : public NamedFontEncoding {
+ public:
+ explicit EncFile (std::string encname);
+ void read ();
+ void read (std::istream &is);
+ int size () const {return _table.size();}
+ const char* name () const override {return _encname.c_str();}
+ const char* charName (uint32_t c) const;
+ Character decode (uint32_t c) const override {return Character(charName(c));}
+ bool mapsToCharIndex () const override {return false;}
+ const char* path () const override;
+
+ private:
+ std::string _encname;
+ std::vector<std::string> _table;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/FileFinder.cpp b/dviware/dvisvgm/src/FileFinder.cpp
new file mode 100644
index 0000000000..87d22d73c9
--- /dev/null
+++ b/dviware/dvisvgm/src/FileFinder.cpp
@@ -0,0 +1,262 @@
+/*************************************************************************
+** FileFinder.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <config.h>
+#ifdef MIKTEX
+ #include "MiKTeXCom.hpp"
+ #include "utility.hpp"
+#else
+ #ifdef KPSE_CXX_UNSAFE
+ extern "C" {
+ #endif
+ #include <kpathsea/kpathsea.h>
+ #ifdef KPSE_CXX_UNSAFE
+ }
+ #endif
+#endif
+
+#include <cstdlib>
+#include <fstream>
+#include <map>
+#include <set>
+#include "FileFinder.hpp"
+#include "FilePath.hpp"
+#include "FileSystem.hpp"
+#include "FontMap.hpp"
+#include "Message.hpp"
+#include "MessageException.hpp"
+#include "Process.hpp"
+
+std::string FileFinder::_argv0;
+std::string FileFinder::_progname;
+bool FileFinder::_enableMktex = false;
+
+
+/** Constructs a new file finder.
+ * @param[in] argv0 argv[0] of main() function
+ * @param[in] progname name of application using the FileFinder
+ * @param[in] enable_mktexmf if true, tfm and mf file generation is activated */
+FileFinder::FileFinder () {
+ addLookupDir("."); // always lookup files in the current working directory
+#ifdef MIKTEX
+ _miktex = util::make_unique<MiKTeXCom>();
+#else
+ kpse_set_program_name(_argv0.c_str(), _progname.c_str());
+ // enable tfm and mf generation (actually invoked by calls of kpse_make_tex)
+ kpse_set_program_enabled(kpse_tfm_format, 1, kpse_src_env);
+ kpse_set_program_enabled(kpse_mf_format, 1, kpse_src_env);
+ kpse_make_tex_discard_errors = true; // suppress messages from mktexFOO tools
+#endif
+}
+
+
+void FileFinder::init (const std::string &argv0, const std::string &progname, bool enable_mktexmf) {
+ _argv0 = argv0;
+ _progname = progname;
+ _enableMktex = enable_mktexmf;
+}
+
+
+FileFinder& FileFinder::instance () {
+ static FileFinder instance;
+ return instance;
+}
+
+
+/** Returns the version string of the underlying file searching library (kpathsea, MiKTeX) */
+std::string FileFinder::version () const {
+#ifdef MIKTEX
+ return _miktex->getVersion();
+#else
+ if (const char *v = strrchr(KPSEVERSION, ' '))
+ return (std::string(KPSEVERSION).substr(0, 9) == "kpathsea ") ? v+1 : KPSEVERSION;
+ if (strlen(KPSEVERSION) > 0)
+ return KPSEVERSION;
+#endif
+ return "unknown";
+}
+
+
+void FileFinder::addLookupDir (const std::string &path) {
+ FilePath filepath(path);
+ _additionalDirs.insert(filepath.absolute());
+}
+
+
+/** Determines filetype by the filename extension and calls kpse_find_file
+ * to actually look up the file.
+ * @param[in] fname name of file to look up
+ * @param[in] ftype expected file format of file fname; if 0, it's derived from the filename suffix
+ * @return file path on success, 0 otherwise */
+const char* FileFinder::findFile (const std::string &fname, const char *ftype) const {
+ if (fname.empty())
+ return nullptr;
+
+ static std::string buf;
+ // try to lookup the file in the additionally specified directories
+ for (const std::string &dir : _additionalDirs) {
+ buf = dir + "/" + fname;
+ if (FileSystem::exists(buf))
+ return buf.c_str();
+ }
+ std::string ext;
+ if (ftype)
+ ext = ftype;
+ else {
+ size_t pos = fname.rfind('.');
+ if (pos == std::string::npos)
+ return nullptr; // no extension and no file type => no search
+ ext = fname.substr(pos+1);
+ }
+
+#ifdef MIKTEX
+ if (ext == "dll" || ext == "exe") {
+ // lookup dll and exe files in the MiKTeX bin directory first
+ buf = _miktex->getBinDir() + "/" + fname;
+ if (FileSystem::exists(buf))
+ return buf.c_str();
+ }
+ else if (ext == "cmap") {
+ // The MiKTeX SDK doesn't support the lookup of files without suffix (yet), thus
+ // it's not possible to find cmap files which usually don't have a suffix. In order
+ // to work around this, we try to lookup the files by calling kpsewhich.
+ Process process("kpsewhich", "-format=cmap "+fname);
+ process.run(&buf);
+ buf = util::trim(buf);
+ return buf.empty() ? nullptr : buf.c_str();
+ }
+ try {
+ return _miktex->findFile(fname.c_str());
+ }
+ catch (const MessageException &e) {
+ return nullptr;
+ }
+#else
+#ifdef TEXLIVEWIN32
+ if (ext == "exe") {
+ // lookup exe files in directory where dvisvgm is located
+ if (const char *path = kpse_var_value("SELFAUTOLOC")) {
+ buf = std::string(path) + "/" + fname;
+ return FileSystem::exists(buf) ? buf.c_str() : nullptr;
+ }
+ return nullptr;
+ }
+#endif
+ static std::map<std::string, kpse_file_format_type> types = {
+ {"tfm", kpse_tfm_format},
+ {"pfb", kpse_type1_format},
+ {"vf", kpse_vf_format},
+ {"mf", kpse_mf_format},
+ {"ttc", kpse_truetype_format},
+ {"ttf", kpse_truetype_format},
+ {"otf", kpse_opentype_format},
+ {"map", kpse_fontmap_format},
+ {"cmap", kpse_cmap_format},
+ {"sty", kpse_tex_format},
+ {"enc", kpse_enc_format},
+ {"pro", kpse_tex_ps_header_format},
+ {"sfd", kpse_sfd_format},
+ {"eps", kpse_pict_format},
+ {"pdf", kpse_tex_format},
+ };
+ auto it = types.find(ext);
+ if (it == types.end())
+ return nullptr;
+
+ if (char *path = kpse_find_file(fname.c_str(), it->second, 0)) {
+ // In the current version of libkpathsea, each call of kpse_find_file produces
+ // a memory leak since the path buffer is not freed. I don't think we can do
+ // anything against it here...
+ buf = path;
+ std::free(path);
+ return buf.c_str();
+ }
+ return nullptr;
+#endif
+}
+
+
+/** Checks whether the given file is mapped to a different name and if the
+ * file can be found under this name.
+ * @param[in] fname name of file to look up
+ * @return file path on success, 0 otherwise */
+const char* FileFinder::findMappedFile (std::string fname) const {
+ size_t pos = fname.rfind('.');
+ if (pos == std::string::npos)
+ return nullptr;
+ const std::string ext = fname.substr(pos+1); // file extension
+ const std::string base = fname.substr(0, pos);
+ if (const FontMap::Entry *entry = FontMap::instance().lookup(base)) {
+ const char *path=nullptr;
+ if (entry->fontname.find('.') != std::string::npos) // does the mapped filename has an extension?
+ path = findFile(entry->fontname, nullptr); // look for that file
+ else { // otherwise, use extension of unmapped file
+ fname = entry->fontname + "." + ext;
+ (path = findFile(fname, nullptr)) || (path = mktex(fname));
+ }
+ return path;
+ }
+ return nullptr;
+}
+
+
+/** Runs external mktexFOO tool to create missing tfm or mf file.
+ * @param[in] fname name of file to build
+ * @return file path on success, 0 otherwise */
+const char* FileFinder::mktex (const std::string &fname) const {
+ size_t pos = fname.rfind('.');
+ if (!_enableMktex || pos == std::string::npos)
+ return nullptr;
+
+ std::string ext = fname.substr(pos+1); // file extension
+ if (ext != "tfm" && ext != "mf")
+ return nullptr;
+
+ const char *path = nullptr;
+#ifdef MIKTEX
+ // maketfm and makemf are located in miktex/bin which is in the search PATH
+ std::string toolname = (ext == "tfm" ? "miktex-maketfm" : "miktex-makemf");
+ system((toolname+".exe "+fname).c_str());
+ path = findFile(fname, nullptr);
+#else
+ kpse_file_format_type type = (ext == "tfm" ? kpse_tfm_format : kpse_mf_format);
+ path = kpse_make_tex(type, fname.c_str());
+#endif
+ return path;
+}
+
+
+/** Searches a file in the TeX directory tree.
+ * If the file doesn't exist, maximal two further steps are applied
+ * (if "extended" is true):
+ * - checks whether the filename is mapped to a different name and returns
+ * the path to that name
+ * - in case of tfm or mf files: invokes the external mktextfm/mktexmf tool
+ * to create the missing file
+ * @param[in] fname name of file to look up
+ * @param[in] ftype type/format of file to look up
+ * @param[in] extended if true, use fontmap lookup and mktexFOO calls
+ * @return path to file on success, 0 otherwise */
+const char* FileFinder::lookup (const std::string &fname, const char *ftype, bool extended) const {
+ const char *path;
+ if ((path = findFile(fname, ftype)) || (extended && ((path = findMappedFile(fname)) || (path = mktex(fname)))))
+ return path;
+ return nullptr;
+}
diff --git a/dviware/dvisvgm/src/FileFinder.hpp b/dviware/dvisvgm/src/FileFinder.hpp
new file mode 100644
index 0000000000..b01c26b10c
--- /dev/null
+++ b/dviware/dvisvgm/src/FileFinder.hpp
@@ -0,0 +1,55 @@
+/*************************************************************************
+** FileFinder.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef FILEFINDER_HPP
+#define FILEFINDER_HPP
+
+#include <memory>
+#include <set>
+#include <string>
+
+class MiKTeXCom;
+
+class FileFinder {
+ public:
+ static void init (const std::string &argv0, const std::string &progname, bool enable_mktexmf);
+ static FileFinder& instance ();
+ std::string version () const;
+ void addLookupDir (const std::string &path);
+ const char* lookup (const std::string &fname, const char *ftype, bool extended=true) const;
+ const char* lookup (const std::string &fname, bool extended=true) const {return lookup(fname, nullptr, extended);}
+
+ protected:
+ FileFinder ();
+ const char* findFile (const std::string &fname, const char *ftype) const;
+ const char* findMappedFile (std::string fname) const;
+ const char* mktex (const std::string &fname) const;
+
+ private:
+ static std::string _argv0;
+ static std::string _progname;
+ static bool _enableMktex;
+ std::set<std::string> _additionalDirs;
+#ifdef MIKTEX
+ std::unique_ptr<MiKTeXCom> _miktex;
+#endif
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/FilePath.cpp b/dviware/dvisvgm/src/FilePath.cpp
new file mode 100644
index 0000000000..98449c101b
--- /dev/null
+++ b/dviware/dvisvgm/src/FilePath.cpp
@@ -0,0 +1,259 @@
+/*************************************************************************
+** FilePath.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <cctype>
+#include "FilePath.hpp"
+#include "FileSystem.hpp"
+#include "MessageException.hpp"
+#include "utility.hpp"
+
+using namespace std;
+
+
+/** Removes redundant slashes from a given path. */
+static string& single_slashes (string &str) {
+ size_t pos=0;
+ while ((pos = str.find("//", pos)) != string::npos)
+ str.replace(pos, 2, "/");
+ return str;
+}
+
+
+#ifdef _WIN32
+static char strip_drive_letter (string &path) {
+ char letter = 0;
+ if (path.length() >= 2 && path[1] == ':' && isalpha(path[0])) {
+ letter = path[0];
+ path.erase(0, 2);
+ }
+ return letter;
+}
+
+
+static char adapt_current_path (string &path, char target_drive) {
+ if (char current_drive = strip_drive_letter(path)) {
+ if (target_drive != current_drive) {
+ if (target_drive == 0)
+ target_drive = current_drive;
+ if (path.empty() || path[0] != '/') {
+ if (FileSystem::chdir(string(1, target_drive) + ":")) {
+ path.insert(0, FileSystem::getcwd()+"/");
+ strip_drive_letter(path);
+ }
+ else
+ throw MessageException("drive " + string(1, target_drive) + ": not accessible");
+ }
+ }
+ }
+ return target_drive;
+}
+
+#endif
+
+
+bool FilePath::Directory::operator == (const Directory &dir) const {
+#ifdef _WIN32
+ // letter case is not significant on Windows systems
+ return util::tolower(_dirstr) == util::tolower(dir._dirstr);
+#else
+ return _dirstr == dir._dirstr;
+#endif
+}
+
+
+/** Constructs a FilePath object.
+ * @param[in] path absolute or relative path to a file or directory
+ * @param[in] isfile true if 'path' references a file, false if a directory is referenced
+ * @param[in] current_dir if 'path' is a relative path expression it will be related to 'current_dir' */
+FilePath::FilePath (const string &path, bool isfile, const string &current_dir) {
+ init(path, isfile, current_dir);
+}
+
+
+/** Assigns a new path. Relative paths are relative to the current working directory.
+ * @param[in] path absolute or relative path to a file or directory */
+void FilePath::set(const string &path) {
+ init(path, !FileSystem::isDirectory(path), FileSystem::getcwd());
+}
+
+
+/** Initializes a FilePath object. This method should be called by the constructors only.
+ * @param[in] path absolute or relative path to a file or directory
+ * @param[in] isfile true if 'path' references a file, false if a directory is referenced
+ * @param[in] current_dir if 'path' is a relative path expression it will be related to 'current_dir' */
+void FilePath::init (string path, bool isfile, string current_dir) {
+ _dirs.clear();
+ _fname.clear();
+ single_slashes(path);
+ single_slashes(current_dir);
+#ifdef _WIN32
+ path = FileSystem::adaptPathSeperators(path);
+ _drive = strip_drive_letter(path);
+#endif
+ if (isfile) {
+ size_t pos = path.rfind('/');
+ _fname = path.substr((pos == string::npos) ? 0 : pos+1);
+ if (pos != string::npos)
+ path.erase(pos);
+ else
+ path.clear();
+ }
+ if (current_dir.empty())
+ current_dir = FileSystem::getcwd();
+#ifdef _WIN32
+ _drive = adapt_current_path(current_dir, _drive);
+#endif
+ if (!path.empty()) {
+ if (path[0] == '/')
+ current_dir.clear();
+ else if (current_dir[0] != '/')
+ current_dir = "/";
+ else {
+ FilePath curr(current_dir, false, "/");
+ current_dir = curr.absolute();
+#ifdef _WIN32
+ adapt_current_path(current_dir, _drive);
+#endif
+ }
+ }
+ path.insert(0, current_dir + "/");
+ string elem;
+ for (char c : path) {
+ if (c != '/')
+ elem += c;
+ else {
+ add(elem);
+ elem.clear();
+ }
+ }
+ add(elem);
+}
+
+
+/** Adds a location step to the current path. */
+void FilePath::add (const string &dir) {
+ if (dir == ".." && !_dirs.empty())
+ _dirs.pop_back();
+ else if (dir.length() > 0 && dir != ".")
+ _dirs.emplace_back(dir);
+}
+
+
+/** Returns the suffix of the filename. If FilePath represents the
+ * location of a directory (and not of a file) an empty string
+ * is returned. */
+string FilePath::suffix () const {
+ size_t start = 0;
+ // ignore leading dots
+ while (start < _fname.length() && _fname[start] == '.')
+ start++;
+ string sub = _fname.substr(start);
+ size_t pos = sub.rfind('.');
+ if (pos != string::npos && pos < sub.length()-1)
+ return sub.substr(pos+1);
+ return "";
+}
+
+
+/** Changes the suffix of the filename. If FilePath represents the
+ * location of a directory (and not of a file) nothing happens.
+ * @param[in] s new suffix */
+void FilePath::suffix (const string &s) {
+ if (!_fname.empty()) {
+ string current_suffix = suffix();
+ if (!current_suffix.empty())
+ _fname.erase(_fname.length()-current_suffix.length()-1);
+ _fname += "."+s;
+ }
+}
+
+
+/** Returns the filename without suffix.
+ * Example: FilePath("/a/b/c.def", true) == "c" */
+string FilePath::basename () const {
+ if (!_fname.empty()) {
+ size_t len = suffix().length();
+ if (len > 0)
+ len++; // strip dot too
+ return _fname.substr(0, _fname.length()-len);
+ }
+ return "";
+}
+
+
+/** Returns the absolute path string of this FilePath.
+ * @param[in] with_filename if false, the filename is omitted
+ * @return the absolute path string */
+string FilePath::absolute (bool with_filename) const {
+ string path;
+ for (const Directory &dir : _dirs)
+ path += "/" + string(dir);
+ if (path.empty())
+ path = "/";
+ if (with_filename && !_fname.empty())
+ path += "/"+_fname;
+#ifdef _WIN32
+ if (_drive)
+ path.insert(0, string(1, _drive) + ":");
+#endif
+ return single_slashes(path);
+}
+
+
+/** Returns a path string of this FilePath relative to reldir. If we wanted to
+ * navigate from /a/b/c/d to /a/b/e/f using the shell command "cd", we could do that
+ * with the relative path argument: "cd ../../e/f". This function returns such a relative
+ * path. Example: FilePath("/a/b/e/f").relative("/a/b/c/d") => "../../e/f".
+ * @param[in] reldir absolute path to a directory
+ * @param[in] with_filename if false, the filename is omitted
+ * @return the relative path string */
+string FilePath::relative (string reldir, bool with_filename) const {
+ if (reldir.empty())
+ reldir = FileSystem::getcwd();
+#ifdef _WIN32
+ adapt_current_path(reldir, _drive);
+#endif
+ if (reldir[0] != '/')
+ return absolute();
+ FilePath rel(reldir, false);
+ string path;
+#ifdef _WIN32
+ if (rel._drive && _drive && tolower(rel._drive) != tolower(_drive))
+ path += string(1, _drive) + ":";
+#endif
+ auto it1 = _dirs.begin();
+ auto it2 = rel._dirs.begin();
+ while (it1 != _dirs.end() && it2 != rel._dirs.end() && *it1 == *it2)
+ ++it1, ++it2;
+ for (; it2 != rel._dirs.end(); ++it2)
+ path += "../";
+ for (; it1 != _dirs.end(); ++it1)
+ path += string(*it1) + "/";
+ if (!path.empty())
+ path.erase(path.length()-1, 1); // remove trailing slash
+ if (with_filename && !_fname.empty()) {
+ if (!path.empty() && path != "/")
+ path += "/";
+ path += _fname;
+ }
+ if (path.empty())
+ path = ".";
+ return single_slashes(path);
+}
diff --git a/dviware/dvisvgm/src/FilePath.hpp b/dviware/dvisvgm/src/FilePath.hpp
new file mode 100644
index 0000000000..d152c19f20
--- /dev/null
+++ b/dviware/dvisvgm/src/FilePath.hpp
@@ -0,0 +1,69 @@
+/*************************************************************************
+** FilePath.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef FILEPATH_HPP
+#define FILEPATH_HPP
+
+#include <string>
+#include <utility>
+#include <vector>
+
+class FilePath {
+ class Directory {
+ public:
+ explicit Directory (std::string dir) : _dirstr(std::move(dir)) {}
+ explicit Directory (std::string &&dir) : _dirstr(std::move(dir)) {}
+ bool operator == (const Directory &dir) const;
+ bool operator != (const Directory &dir) const {return !(*this == dir);}
+ explicit operator std::string () const {return _dirstr;}
+
+ private:
+ std::string _dirstr;
+ };
+
+ public:
+ explicit FilePath (const std::string &path) {set(path);}
+ FilePath (const std::string &path, bool isfile) : FilePath(path, isfile, "") {}
+ FilePath (const std::string &path, bool isfile, const std::string &current_dir);
+ void set (const std::string &path);
+ std::string absolute (bool with_filename=true) const;
+ std::string relative (std::string reldir="", bool with_filename=true) const;
+ std::string basename () const;
+ std::string suffix () const;
+ void suffix (const std::string &s);
+ size_t depth () const {return _dirs.size();}
+ bool isFile () const {return !_fname.empty();}
+ bool empty () const {return _dirs.empty() && _fname.empty();}
+ const std::string& filename () const {return _fname;}
+ void filename (const std::string &fname) {_fname = fname;}
+
+ protected:
+ void init (std::string path, bool isfile, std::string current_dir);
+ void add (const std::string &elem);
+
+ private:
+ std::vector<Directory> _dirs;
+ std::string _fname;
+#ifdef _WIN32
+ char _drive;
+#endif
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/FileSystem.cpp b/dviware/dvisvgm/src/FileSystem.cpp
new file mode 100644
index 0000000000..35621465a5
--- /dev/null
+++ b/dviware/dvisvgm/src/FileSystem.cpp
@@ -0,0 +1,369 @@
+/*************************************************************************
+** FileSystem.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <config.h>
+#include <algorithm>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include "FileSystem.hpp"
+#include "utility.hpp"
+#include "version.hpp"
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+
+using namespace std;
+
+#ifdef _WIN32
+ #include <direct.h>
+ #include "windows.hpp"
+ const char *FileSystem::DEVNULL = "nul";
+ const char FileSystem::PATHSEP = '\\';
+ #define unlink _unlink
+#else
+ #include <dirent.h>
+ #include <pwd.h>
+ #include <sys/stat.h>
+ #include <sys/types.h>
+ const char *FileSystem::DEVNULL = "/dev/null";
+ const char FileSystem::PATHSEP = '/';
+#endif
+
+
+FileSystem FileSystem::_fs;
+string FileSystem::TMPDIR;
+const char *FileSystem::TMPSUBDIR = nullptr;
+
+
+/** Private wrapper function for mkdir: creates a single folder.
+ * @param[in] dir folder name
+ * @return true on success */
+static bool s_mkdir (const string &dirname) {
+ bool success = true;
+ if (!FileSystem::exists(dirname)) {
+#ifdef _WIN32
+ success = (_mkdir(dirname.c_str()) == 0);
+#else
+ success = (::mkdir(dirname.c_str(), 0775) == 0);
+#endif
+ }
+ return success;
+}
+
+
+static bool inline s_rmdir (const string &dirname) {
+#ifdef _WIN32
+ return (_rmdir(dirname.c_str()) == 0);
+#else
+ return (::rmdir(dirname.c_str()) == 0);
+#endif
+}
+
+
+FileSystem::~FileSystem () {
+ // remove the subdirectory from the system's temp folder (if empty)
+ if (TMPSUBDIR)
+ s_rmdir(tmpdir());
+}
+
+
+bool FileSystem::remove (const string &fname) {
+ return unlink(fname.c_str()) == 0;
+}
+
+
+/** Copies a file.
+ * @param[in] src path of file to copy
+ * @param[in] dest path of target file
+ * @param[in] remove_src remove file 'src' if true
+ * @return true on success */
+bool FileSystem::copy (const string &src, const string &dest, bool remove_src) {
+ ifstream ifs(src, ios::in|ios::binary);
+ ofstream ofs(dest, ios::out|ios::binary);
+ if (ifs && ofs) {
+ ofs << ifs.rdbuf();
+ if (!ifs.fail() && !ofs.fail() && remove_src) {
+ ofs.close();
+ ifs.close();
+ return remove(src);
+ }
+ else
+ return !remove_src;
+ }
+ return false;
+}
+
+
+bool FileSystem::rename (const string &oldname, const string &newname) {
+ return ::rename(oldname.c_str(), newname.c_str()) == 0;
+}
+
+
+uint64_t FileSystem::filesize (const string &fname) {
+#ifdef _WIN32
+ // unfortunately, stat doesn't work properly under Windows
+ // so we have to use this freaky code
+ WIN32_FILE_ATTRIBUTE_DATA attr;
+ GetFileAttributesExA(fname.c_str(), GetFileExInfoStandard, &attr);
+ return (static_cast<uint64_t>(attr.nFileSizeHigh) << (8*sizeof(attr.nFileSizeLow))) | attr.nFileSizeLow;
+#else
+ struct stat attr;
+ return (stat(fname.c_str(), &attr) == 0) ? attr.st_size : 0;
+#endif
+}
+
+
+string FileSystem::adaptPathSeperators (string path) {
+ std::replace(path.begin(), path.end(), PATHSEP, '/');
+ return path;
+}
+
+
+string FileSystem::getcwd () {
+ char buf[1024];
+#ifdef _WIN32
+ return adaptPathSeperators(_getcwd(buf, 1024));
+#else
+ return ::getcwd(buf, 1024);
+#endif
+}
+
+
+/** Changes the work directory.
+ * @param[in] dir path to new work directory
+ * @return true on success */
+bool FileSystem::chdir (const std::string &dirname) {
+ bool success = false;
+ if (const char *cdirname = dirname.c_str()) {
+#ifdef _WIN32
+ success = (_chdir(cdirname) == 0);
+#else
+ success = (::chdir(cdirname) == 0);
+#endif
+ }
+ return success;
+}
+
+
+/** Returns the user's home directory. */
+const char* FileSystem::userdir () {
+#ifdef _WIN32
+ const char *drive=getenv("HOMEDRIVE");
+ const char *path=getenv("HOMEPATH");
+ if (drive && path) {
+ static string ret = string(drive)+path;
+ if (!ret.empty())
+ return ret.c_str();
+ }
+ return nullptr;
+#else
+ const char *dir=getenv("HOME");
+ if (!dir) {
+ if (const char *user=getenv("USER")) {
+ if (struct passwd *pw=getpwnam(user))
+ dir = pw->pw_dir;
+ }
+ }
+ return dir;
+#endif
+}
+
+
+/** Returns the path of the temporary folder. */
+string FileSystem::tmpdir () {
+ string ret;
+ if (!TMPDIR.empty())
+ ret = TMPDIR;
+ else {
+#ifdef _WIN32
+ char buf[MAX_PATH];
+ if (GetTempPath(MAX_PATH, buf))
+ ret = adaptPathSeperators(buf);
+ else
+ ret = ".";
+#else
+ if (const char *path = getenv("TMPDIR"))
+ ret = path;
+ else
+ ret = "/tmp";
+#endif
+ if (ret.back() == '/')
+ ret.pop_back();
+ static bool initialized=false;
+ if (!initialized && ret != ".") {
+ TMPSUBDIR = PROGRAM_NAME;
+ s_mkdir(ret + "/" + TMPSUBDIR);
+ initialized = true;
+ }
+ if (TMPSUBDIR)
+ ret += string("/") + TMPSUBDIR;
+ }
+ if (!ret.empty() && ret.back() != '/')
+ ret += '/';
+ return ret;
+}
+
+
+/** Creates a new folder relative to the current work directory. If necessary,
+ * the parent folders are also created.
+ * @param[in] dir single folder name or path to folder
+ * @return true if folder(s) could be created */
+bool FileSystem::mkdir (const string &dirname) {
+ bool success = false;
+ if (const char *cdirname = dirname.c_str()) {
+ success = true;
+ const string dirstr = adaptPathSeperators(util::trim(cdirname));
+ for (size_t pos=1; success && (pos = dirstr.find('/', pos)) != string::npos; pos++)
+ success &= s_mkdir(dirstr.substr(0, pos));
+ success &= s_mkdir(dirstr);
+ }
+ return success;
+}
+
+
+/** Removes a directory and its contents.
+ * @param[in] dirname path to directory
+ * @return true on success */
+bool FileSystem::rmdir (const string &dirname) {
+ bool ok = false;
+ if (isDirectory(dirname)) {
+ ok = true;
+#ifdef _WIN32
+ string pattern = dirname + "/*";
+ WIN32_FIND_DATA data;
+ HANDLE h = FindFirstFile(pattern.c_str(), &data);
+ bool ready = (h == INVALID_HANDLE_VALUE);
+ while (!ready && ok) {
+ const char *fname = data.cFileName;
+ string path = dirname + "/" + fname;
+ if (isDirectory(path)) {
+ if (strcmp(fname, ".") != 0 && strcmp(fname, "..") != 0)
+ ok = rmdir(path) && s_rmdir(path);
+ }
+ else if (isFile(path))
+ ok = remove(path);
+ else
+ ok = false;
+ ready = !FindNextFile(h, &data);
+ }
+ FindClose(h);
+#else
+ if (DIR *dir = opendir(dirname.c_str())) {
+ struct dirent *ent;
+ while ((ent = readdir(dir)) && ok) {
+ const char *fname = ent->d_name;
+ string path = dirname + "/" + fname;
+ if (isDirectory(path)) {
+ if (strcmp(fname, ".") != 0 && strcmp(fname, "..") != 0)
+ ok = rmdir(path) && s_rmdir(path);
+ }
+ else if (isFile(path))
+ ok = remove(path);
+ else
+ ok = false;
+ }
+ closedir(dir);
+ }
+#endif
+ ok = s_rmdir(dirname);
+ }
+ return ok;
+}
+
+
+/** Checks if a file or directory exits. */
+bool FileSystem::exists (const string &fname) {
+ if (const char *cfname = fname.c_str()) {
+
+#ifdef _WIN32
+ return GetFileAttributes(cfname) != INVALID_FILE_ATTRIBUTES;
+#else
+ struct stat attr;
+ return stat(cfname, &attr) == 0;
+#endif
+ }
+ return false;
+}
+
+
+/** Returns true if 'fname' references a directory. */
+bool FileSystem::isDirectory (const string &fname) {
+ if (const char *cfname = fname.c_str()) {
+#ifdef _WIN32
+ auto attr = GetFileAttributes(cfname);
+ return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY);
+#else
+ struct stat attr;
+ return stat(cfname, &attr) == 0 && S_ISDIR(attr.st_mode);
+#endif
+ }
+ return false;
+}
+
+
+/** Returns true if 'fname' references a file. */
+bool FileSystem::isFile (const string &fname) {
+ if (const char *cfname = fname.c_str()) {
+#ifdef _WIN32
+ ifstream ifs(cfname);
+ return (bool)ifs;
+#else
+ struct stat attr;
+ return stat(cfname, &attr) == 0 && S_ISREG(attr.st_mode);
+#endif
+ }
+ return false;
+}
+
+
+int FileSystem::collect (const std::string &dirname, vector<string> &entries) {
+ entries.clear();
+#ifdef _WIN32
+ string pattern = dirname + "/*";
+ WIN32_FIND_DATA data;
+ HANDLE h = FindFirstFile(pattern.c_str(), &data);
+ bool ready = (h == INVALID_HANDLE_VALUE);
+ while (!ready) {
+ string fname = data.cFileName;
+ string path = dirname+"/"+fname;
+ string typechar = isFile(path) ? "f" : isDirectory(path) ? "d" : "?";
+ if (fname != "." && fname != "..")
+ entries.emplace_back(typechar+fname);
+ ready = !FindNextFile(h, &data);
+ }
+ FindClose(h);
+#else
+ if (DIR *dir = opendir(dirname.c_str())) {
+ struct dirent *ent;
+ while ((ent = readdir(dir))) {
+ string fname = ent->d_name;
+ string path = dirname+"/"+fname;
+ string typechar = isFile(path) ? "f" : isDirectory(path) ? "d" : "?";
+ if (fname != "." && fname != "..")
+ entries.emplace_back(typechar+fname);
+ }
+ closedir(dir);
+ }
+#endif
+ return entries.size();
+}
diff --git a/dviware/dvisvgm/src/FileSystem.hpp b/dviware/dvisvgm/src/FileSystem.hpp
new file mode 100644
index 0000000000..9a1b80067d
--- /dev/null
+++ b/dviware/dvisvgm/src/FileSystem.hpp
@@ -0,0 +1,59 @@
+/*************************************************************************
+** FileSystem.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef FILESYSTEM_HPP
+#define FILESYSTEM_HPP
+
+#include <string>
+#include <vector>
+
+class FileSystem
+{
+ public:
+ ~FileSystem ();
+ static bool remove (const std::string &fname);
+ static bool rename (const std::string &oldname, const std::string &newname);
+ static bool copy (const std::string &src, const std::string &dest, bool remove_src=false);
+ static uint64_t filesize (const std::string &fname);
+ static std::string adaptPathSeperators (std::string path);
+ static std::string getcwd ();
+ static std::string tmpdir ();
+ static bool chdir (const std::string &dir);
+ static bool exists (const std::string &fname);
+ static bool mkdir (const std::string &dirname);
+ static bool rmdir (const std::string &dirname);
+ static int collect (const std::string &dirname, std::vector<std::string> &entries);
+ static bool isDirectory (const std::string &fname);
+ static bool isFile (const std::string &fname);
+ static const char* userdir ();
+ static const char* DEVNULL; ///< name of NULL device
+ static const char PATHSEP; ///< character used to separate folders/files in a path
+ static std::string TMPDIR; ///< set path to temporary folder
+
+ protected:
+ FileSystem () =default;
+ bool system_tmpdir_available ();
+ static const char* TMPSUBDIR; ///< subdirectory of the system's temporary folder
+
+ private:
+ static FileSystem _fs;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/FixWord.hpp b/dviware/dvisvgm/src/FixWord.hpp
new file mode 100644
index 0000000000..41a09f1120
--- /dev/null
+++ b/dviware/dvisvgm/src/FixWord.hpp
@@ -0,0 +1,38 @@
+/*************************************************************************
+** FixWord.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef FIXWORD_HPP
+#define FIXWORD_HPP
+
+#include <cstdint>
+
+class FixWord {
+ public:
+ FixWord () : _value(0) {}
+ FixWord (int32_t fw) : _value(fw) {}
+ explicit operator double () const {return double(_value)/(1 << 20);}
+ bool operator < (FixWord fw) const {return _value < fw._value;}
+ bool operator == (FixWord fw) const {return _value == fw._value;}
+
+ private:
+ int32_t _value;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/Font.cpp b/dviware/dvisvgm/src/Font.cpp
new file mode 100644
index 0000000000..f5bdff6674
--- /dev/null
+++ b/dviware/dvisvgm/src/Font.cpp
@@ -0,0 +1,676 @@
+/*************************************************************************
+** Font.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <cstdlib>
+#include <fstream>
+#include <set>
+#include <sstream>
+#include "CMap.hpp"
+#include "FileFinder.hpp"
+#include "FileSystem.hpp"
+#include "Font.hpp"
+#include "FontEngine.hpp"
+#include "Message.hpp"
+#include "MetafontWrapper.hpp"
+#include "SignalHandler.hpp"
+#include "Subfont.hpp"
+#include "Unicode.hpp"
+#include "utility.hpp"
+
+
+using namespace std;
+
+
+uint32_t Font::unicode (uint32_t c) const {
+ return Unicode::charToCodepoint(c);
+}
+
+
+/** Returns the encoding object of this font which is asigned in a map file.
+ * If there's no encoding assigned, the function returns 0. */
+const FontEncoding* Font::encoding () const {
+ if (const FontMap::Entry *entry = fontMapEntry())
+ return FontEncoding::encoding(entry->encname);
+ return nullptr;
+}
+
+
+const FontMap::Entry* Font::fontMapEntry () const {
+ string fontname = name();
+ size_t pos = fontname.rfind('.');
+ if (pos != string::npos)
+ fontname = fontname.substr(0, pos); // strip extension
+ return FontMap::instance().lookup(fontname);
+}
+
+
+/** Compute the extents of a given glyph.
+ * @param[in] c character code of glyph
+ * @param[in] vertical true if is glyph is part of vertical aligned text
+ * @param[out] metrics the resulting extents */
+void Font::getGlyphMetrics (int c, bool vertical, GlyphMetrics &metrics) const {
+ double s = scaleFactor();
+ if (vertical) { // get metrics for vertical text flow?
+ if (verticalLayout()) { // is the font designed for vertical texts?
+ metrics.wl = s*charDepth(c);
+ metrics.wr = s*charHeight(c);
+ metrics.h = 0;
+ metrics.d = s*charWidth(c);
+ }
+ else { // rotate box by 90 degrees for alphabetic text
+ metrics.wl = s*charDepth(c);
+ metrics.wr = s*charHeight(c);
+ metrics.h = 0;
+ metrics.d = s*(charWidth(c)+italicCorr(c));
+ }
+ }
+ else {
+ metrics.wl = 0;
+ metrics.wr = s*(charWidth(c)+italicCorr(c));
+ metrics.h = s*charHeight(c);
+ metrics.d = s*charDepth(c);
+ }
+}
+
+
+const char* Font::filename () const {
+ const char *fname = strrchr(path(), '/');
+ if (fname)
+ return fname+1;
+ return path();
+}
+
+///////////////////////////////////////////////////////////////////////////////////////
+
+TFMFont::TFMFont (string name, uint32_t cs, double ds, double ss)
+ : _fontname(std::move(name)), _checksum(cs), _dsize(ds), _ssize(ss)
+{
+}
+
+
+/** Returns a font metrics object for the current font.
+ * @throw FontException if TFM file can't be found */
+const FontMetrics* TFMFont::getMetrics () const {
+ if (!_metrics) {
+ try {
+ _metrics = FontMetrics::read(_fontname);
+ if (!_metrics) {
+ _metrics = util::make_unique<NullFontMetric>();
+ Message::wstream(true) << "can't find "+_fontname+".tfm\n";
+ }
+ }
+ catch (FontMetricException &e) {
+ _metrics = util::make_unique<NullFontMetric>();
+ Message::wstream(true) << e.what() << " in " << _fontname << ".tfm\n";
+ }
+ }
+ return _metrics.get();
+}
+
+
+double TFMFont::charWidth (int c) const {
+ double w = getMetrics() ? getMetrics()->getCharWidth(c) : 0;
+ if (style())
+ w *= style()->extend;
+ return w;
+}
+
+
+double TFMFont::italicCorr (int c) const {
+ double w = getMetrics() ? getMetrics()->getItalicCorr(c) : 0;
+ if (style()) {
+ w += abs(style()->slant*charHeight(c)); // slant := tan(phi) = dx/height
+ w *= style()->extend;
+ }
+ return w;
+}
+
+
+double TFMFont::charDepth (int c) const {return getMetrics() ? getMetrics()->getCharDepth(c) : 0;}
+double TFMFont::charHeight (int c) const {return getMetrics() ? getMetrics()->getCharHeight(c) : 0;}
+
+
+/** Tests if the checksum of the font matches that of the corresponding TFM file. */
+bool TFMFont::verifyChecksums () const {
+ if (_checksum != 0 && getMetrics() && getMetrics()->getChecksum() != 0)
+ return _checksum == getMetrics()->getChecksum();
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+// static class variables
+bool PhysicalFont::EXACT_BBOX = false;
+bool PhysicalFont::KEEP_TEMP_FILES = false;
+const char *PhysicalFont::CACHE_PATH = nullptr;
+double PhysicalFont::METAFONT_MAG = 4;
+FontCache PhysicalFont::_cache;
+
+
+unique_ptr<Font> PhysicalFont::create (const string &name, uint32_t checksum, double dsize, double ssize, PhysicalFont::Type type) {
+ return unique_ptr<PhysicalFontImpl>(new PhysicalFontImpl(name, 0, checksum, dsize, ssize, type));
+}
+
+
+unique_ptr<Font> PhysicalFont::create (const string &name, int fontindex, uint32_t checksum, double dsize, double ssize) {
+ return unique_ptr<PhysicalFontImpl>(new PhysicalFontImpl(name, fontindex, checksum, dsize, ssize, PhysicalFont::Type::TTC));
+}
+
+
+const char* PhysicalFont::path () const {
+ const char *ext=nullptr;
+ switch (type()) {
+ case Type::OTF: ext = "otf"; break;
+ case Type::PFB: ext = "pfb"; break;
+ case Type::TTC: ext = "ttc"; break;
+ case Type::TTF: ext = "ttf"; break;
+ case Type::MF : ext = "mf"; break;
+ default : ext = nullptr;
+ }
+ if (ext)
+ return FileFinder::instance().lookup(name()+"."+ext);
+ return FileFinder::instance().lookup(name());
+}
+
+
+/** Returns true if this font is CID-based. */
+bool PhysicalFont::isCIDFont () const {
+ if (type() == Type::MF)
+ return false;
+ FontEngine::instance().setFont(*this);
+ return FontEngine::instance().isCIDFont();
+}
+
+
+/** Retrieve the IDs of all charachter maps available in the font file.
+ * @param[out] charMapIDs IDs of the found character maps
+ * @return number of found character maps */
+int PhysicalFont::collectCharMapIDs (std::vector<CharMapID> &charMapIDs) const {
+ if (type() == Type::MF)
+ return 0;
+ FontEngine::instance().setFont(*this);
+ return FontEngine::instance().getCharMapIDs(charMapIDs);
+}
+
+
+/** Decodes a character code used in the DVI file to the code required to
+ * address the correct character in the font.
+ * @param[in] c DVI character to decode
+ * @return target character code or name */
+Character PhysicalFont::decodeChar (uint32_t c) const {
+ if (const FontEncoding *enc = encoding())
+ return enc->decode(c);
+ return Character(Character::CHRCODE, c);
+}
+
+
+/** Returns the number of units per EM. The EM square is the virtual area a glyph is designed on.
+ * All coordinates used to specify portions of the glyph are relative to the origin (0,0) at the
+ * lower left corner of this square, while the upper right corner is located at (m,m), where m
+ * is an integer value defined with the font, and returned by this function. */
+int PhysicalFont::unitsPerEm() const {
+ if (type() == Type::MF)
+ return 1000;
+ FontEngine::instance().setFont(*this);
+ return FontEngine::instance().getUnitsPerEM();
+}
+
+
+int PhysicalFont::hAdvance () const {
+ if (type() == Type::MF)
+ return 0;
+ FontEngine::instance().setFont(*this);
+ return FontEngine::instance().getHAdvance();
+}
+
+
+double PhysicalFont::hAdvance (int c) const {
+ if (type() == Type::MF)
+ return unitsPerEm()*charWidth(c)/designSize();
+ FontEngine::instance().setFont(*this);
+ if (const FontMap::Entry *entry = fontMapEntry())
+ if (Subfont *sf = entry->subfont)
+ c = sf->decode(c);
+ return FontEngine::instance().getHAdvance(decodeChar(c));
+}
+
+
+double PhysicalFont::vAdvance (int c) const {
+ if (type() == Type::MF)
+ return unitsPerEm()*charWidth(c)/designSize();
+ FontEngine::instance().setFont(*this);
+ if (const FontMap::Entry *entry = fontMapEntry())
+ if (Subfont *sf = entry->subfont)
+ c = sf->decode(c);
+ return FontEngine::instance().getVAdvance(decodeChar(c));
+}
+
+
+string PhysicalFont::glyphName (int c) const {
+ if (type() == Type::MF)
+ return "";
+ FontEngine::instance().setFont(*this);
+ if (const FontMap::Entry *entry = fontMapEntry())
+ if (Subfont *sf = entry->subfont)
+ c = sf->decode(c);
+ return FontEngine::instance().getGlyphName(decodeChar(c));
+}
+
+
+double PhysicalFont::scaledAscent() const {
+ return ascent()*scaledSize()/unitsPerEm();
+}
+
+
+/** Returns the unscaled ascender of the font in design units. */
+int PhysicalFont::ascent () const {
+ if (type() == Type::MF)
+ return getMetrics() ? getMetrics()->getAscent()*unitsPerEm()/getMetrics()->getQuad() : 0;
+ FontEngine::instance().setFont(*this);
+ return FontEngine::instance().getAscender();
+}
+
+
+/** Returns the unscaled descender of the font in design units. */
+int PhysicalFont::descent () const {
+ if (type() == Type::MF)
+ return getMetrics() ? getMetrics()->getDescent()*unitsPerEm()/getMetrics()->getQuad() : 0;
+ FontEngine::instance().setFont(*this);
+ return FontEngine::instance().getDescender();
+}
+
+
+std::string PhysicalFont::familyName () const {
+ if (type() == Type::MF)
+ return "";
+ FontEngine::instance().setFont(*this);
+ const char *family = FontEngine::instance().getFamilyName();
+ return family ? family : "";
+}
+
+
+std::string PhysicalFont::styleName () const {
+ if (type() == Type::MF)
+ return "";
+ FontEngine::instance().setFont(*this);
+ const char *style = FontEngine::instance().getStyleName();
+ return style ? style : "";
+}
+
+
+
+/** Extracts the glyph outlines of a given character.
+ * @param[in] c character code of requested glyph
+ * @param[out] glyph path segments of the glyph outline
+ * @param[in] callback optional callback object for tracer class
+ * @return true if outline could be computed */
+bool PhysicalFont::getGlyph (int c, GraphicsPath<int32_t> &glyph, GFGlyphTracer::Callback *callback) const {
+ if (type() == Type::MF) {
+ const Glyph *cached_glyph=nullptr;
+ if (CACHE_PATH) {
+ _cache.write(CACHE_PATH);
+ _cache.read(name(), CACHE_PATH);
+ cached_glyph = _cache.getGlyph(c);
+ }
+ if (cached_glyph) {
+ glyph = *cached_glyph;
+ return true;
+ }
+ else {
+ string gfname;
+ if (createGF(gfname)) {
+ try {
+ double ds = getMetrics() ? getMetrics()->getDesignSize() : 1;
+ GFGlyphTracer tracer(gfname, unitsPerEm()/ds, callback);
+ tracer.setGlyph(glyph);
+ tracer.executeChar(c);
+ glyph.closeOpenSubPaths();
+ if (CACHE_PATH)
+ _cache.setGlyph(c, glyph);
+ return true;
+ }
+ catch (GFException &e) {
+ // @@ print error message
+ }
+ }
+ }
+ }
+ else { // vector fonts (OTF, PFB, TTF, TTC)
+ bool ok=true;
+ FontEngine::instance().setFont(*this);
+ if (const FontMap::Entry *entry = fontMapEntry())
+ if (Subfont *sf = entry->subfont)
+ c = sf->decode(c);
+ ok = FontEngine::instance().traceOutline(decodeChar(c), glyph, false);
+ glyph.closeOpenSubPaths();
+ return ok;
+ }
+ return false;
+}
+
+
+/** Creates a GF file for this font object.
+ * @param[out] gfname name of the generated GF font file
+ * @return true on success */
+bool PhysicalFont::createGF (string &gfname) const {
+ static set<string> failed_fonts;
+ if (failed_fonts.find(name()) == failed_fonts.end()) {
+ SignalHandler::instance().check();
+ gfname = FileSystem::tmpdir()+name()+".gf";
+ MetafontWrapper mf(name(), FileSystem::tmpdir());
+ bool ok = mf.make("ljfour", METAFONT_MAG); // call Metafont if necessary
+ if (ok && mf.success() && getMetrics())
+ return true;
+ // report failure only once
+ failed_fonts.insert(name());
+ Message::wstream(true) << "failed to create " << name() << ".gf\n";
+ }
+ return false;
+}
+
+
+/** Traces all glyphs of the current font and stores them in the cache. If caching is disabled, nothing happens.
+ * @param[in] includeCached if true, glyphs already cached are traced again
+ * @param[in] cb optional callback methods called by the tracer
+ * @return number of glyphs traced */
+int PhysicalFont::traceAllGlyphs (bool includeCached, GFGlyphTracer::Callback *cb) const {
+ int count = 0;
+ if (type() == Type::MF && CACHE_PATH) {
+ if (const FontMetrics *metrics = getMetrics()) {
+ int fchar = metrics->firstChar();
+ int lchar = metrics->lastChar();
+ string gfname;
+ Glyph glyph;
+ if (createGF(gfname)) {
+ _cache.read(name(), CACHE_PATH);
+ double ds = getMetrics() ? getMetrics()->getDesignSize() : 1;
+ GFGlyphTracer tracer(gfname, unitsPerEm()/ds, cb);
+ tracer.setGlyph(glyph);
+ for (int i=fchar; i <= lchar; i++) {
+ if (includeCached || !_cache.getGlyph(i)) {
+ glyph.clear();
+ tracer.executeChar(i);
+ glyph.closeOpenSubPaths();
+ _cache.setGlyph(i, glyph);
+ ++count;
+ }
+ }
+ _cache.write(CACHE_PATH);
+ }
+ }
+ }
+ return count;
+}
+
+
+/** Computes the exact bounding box of a glyph.
+ * @param[in] c character code of the glyph
+ * @param[out] bbox the computed bounding box
+ * @param[in] cb optional calback object forwarded to the tracer
+ * @return true if the box could be computed successfully */
+bool PhysicalFont::getExactGlyphBox(int c, BoundingBox& bbox, GFGlyphTracer::Callback* cb) const {
+ Glyph glyph;
+ if (getGlyph(c, glyph, cb)) {
+ glyph.computeBBox(bbox);
+ double s = scaledSize()/unitsPerEm();
+ bbox.scale(s, s);
+ return true;
+ }
+ return false;
+}
+
+
+bool PhysicalFont::getExactGlyphBox (int c, GlyphMetrics &metrics, bool vertical, GFGlyphTracer::Callback *cb) const {
+ BoundingBox charbox;
+ if (!getExactGlyphBox(c, charbox, cb))
+ return false;
+ if ((metrics.wl = -charbox.minX()) < 0) metrics.wl=0;
+ if ((metrics.wr = charbox.maxX()) < 0) metrics.wr=0;
+ if ((metrics.h = charbox.maxY()) < 0) metrics.h=0;
+ if ((metrics.d = -charbox.minY()) < 0) metrics.d=0;
+ if (vertical) { // vertical text orientation
+ if (verticalLayout()) { // font designed for vertical layout?
+ metrics.wl = metrics.wr = (metrics.wl+metrics.wr)/2;
+ metrics.d += metrics.h;
+ metrics.h = 0;
+ }
+ else {
+ double depth = metrics.d;
+ metrics.d = metrics.wr;
+ metrics.wr = metrics.h;
+ metrics.h = metrics.wl;
+ metrics.wl = depth;
+ }
+ }
+ return true;
+}
+
+
+unique_ptr<Font> VirtualFont::create (const string &name, uint32_t checksum, double dsize, double ssize) {
+ return unique_ptr<VirtualFontImpl>(new VirtualFontImpl(name, checksum, dsize, ssize));
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+
+
+PhysicalFontImpl::PhysicalFontImpl (const string &name, int fontindex, uint32_t cs, double ds, double ss, PhysicalFont::Type type)
+ : TFMFont(name, cs, ds, ss),
+ _filetype(type), _fontIndex(fontindex), _encodingPair(Font::encoding())
+{
+}
+
+
+PhysicalFontImpl::~PhysicalFontImpl () {
+ if (CACHE_PATH)
+ _cache.write(CACHE_PATH);
+ if (!KEEP_TEMP_FILES)
+ tidy();
+}
+
+
+const FontEncoding* PhysicalFontImpl::encoding () const {
+ if (!_encodingPair.enc1())
+ return nullptr;
+ return &_encodingPair;
+}
+
+
+bool PhysicalFontImpl::findAndAssignBaseFontMap () {
+ const FontEncoding *enc = encoding();
+ if (enc && enc->mapsToCharIndex()) {
+ // try to find a base font map that maps from character indexes to a suitable
+ // target encoding supported by the font file
+ if (const FontEncoding *bfmap = enc->findCompatibleBaseFontMap(this, _charmapID))
+ _encodingPair.assign(bfmap);
+ else
+ return false;
+ }
+ else if (type() != Type::MF) {
+ FontEngine::instance().setFont(*this);
+ _localCharMap = FontEngine::instance().createCustomToUnicodeMap();
+ if (_localCharMap)
+ _charmapID = FontEngine::instance().setCustomCharMap();
+ else
+ _charmapID = FontEngine::instance().setUnicodeCharMap();
+ }
+ return true;
+}
+
+
+/** Returns the Unicode point for a given DVI character. */
+uint32_t PhysicalFontImpl::unicode (uint32_t c) const {
+ if (type() == Type::MF)
+ return Font::unicode(c);
+ Character chr = decodeChar(c);
+ if (type() == Type::PFB) {
+ // try to get the Unicode point from the character name
+ string glyphname = glyphName(c);
+ uint32_t codepoint;
+ if (!glyphname.empty() && (codepoint = Unicode::aglNameToCodepoint(glyphname)) != 0)
+ return codepoint;
+ if (c <= 0x1900) // does character code c fit into Private Use Zone U+E000?
+ return 0xe000+c;
+// Message::wstream() << "can't properly map PS character '" << glyphname << "' (0x" << hex << c << ") to Unicode\n";
+ // If we get here, there is no easy mapping. As for now, we use the character code as Unicode point.
+ // Although quite unlikely, it might collide with properly mapped characters.
+ return Unicode::charToCodepoint(c);
+ }
+ if (chr.type() == Character::NAME || chr.number() == 0)
+ return Unicode::charToCodepoint(chr.number());
+
+ if (_localCharMap) {
+ if (uint32_t mapped_char = _localCharMap->valueAt(chr.number()))
+ return mapped_char;
+ }
+ // No Unicode equivalent found in the font file.
+ // Now we should look for a smart alternative but at the moment
+ // it's sufficient to simply choose a valid unused codepoint.
+ return Unicode::charToCodepoint(chr.number());
+}
+
+
+const FontStyle* PhysicalFontImpl::style () const {
+ if (auto entry = fontMapEntry())
+ return &entry->style;
+ return nullptr;
+}
+
+
+/** Delete all temporary font files created by Metafont. */
+void PhysicalFontImpl::tidy () const {
+ if (type() == Type::MF) {
+ string fname = FileSystem::tmpdir()+name();
+ for (const char *ext : {"gf", "tfm", "log"}) {
+ if (FileSystem::exists(fname+"."+ext))
+ FileSystem::remove(fname+"."+ext);
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+string NativeFont::uniqueName (const string &path, const FontStyle &style) {
+ static unordered_map<string, int> ids;
+ ostringstream oss;
+ oss << path << "b" << style.bold << "e" << style.extend << "s" << style.slant;
+ auto it = ids.find(oss.str());
+ int id = ids.size();
+ if (it == ids.end())
+ ids[oss.str()] = id;
+ else
+ id = it->second;
+ oss.str("");
+ oss << "nf" << id;
+ return oss.str();
+}
+
+
+string NativeFont::name () const {
+ return uniqueName(path(), _style);
+}
+
+
+PhysicalFont::Type NativeFont::type () const {
+ if (const char *filepath = path()) {
+ if (const char *p =strrchr(filepath, '.')) {
+ string ext = p+1;
+ if (ext == "otf")
+ return PhysicalFont::Type::OTF;
+ if (ext == "ttf")
+ return PhysicalFont::Type::TTF;
+ if (ext == "pfb")
+ return PhysicalFont::Type::PFB;
+ }
+ }
+ return PhysicalFont::Type::UNKNOWN;
+}
+
+
+double NativeFont::charWidth (int c) const {
+ FontEngine::instance().setFont(*this);
+ int upem = FontEngine::instance().getUnitsPerEM();
+ double w = upem ? (scaledSize()*FontEngine::instance().getAdvance(c)/upem*_style.extend) : 0;
+ w += fabs(_style.slant*charHeight(c));
+ return w;
+}
+
+
+double NativeFont::charHeight (int c) const {
+ FontEngine::instance().setFont(*this);
+ int upem = FontEngine::instance().getUnitsPerEM();
+ return upem ? (scaledSize()*FontEngine::instance().getAscender()/upem) : 0;
+}
+
+
+double NativeFont::charDepth (int c) const {
+ FontEngine::instance().setFont(*this);
+ int upem = FontEngine::instance().getUnitsPerEM();
+ return upem ? (scaledSize()*FontEngine::instance().getDescender()/upem) : 0;
+}
+
+
+bool NativeFontImpl::findAndAssignBaseFontMap () {
+ FontEngine &fe = FontEngine::instance();
+ fe.setFont(*this);
+ fe.setUnicodeCharMap();
+ fe.buildCharMap(_toUnicodeMap);
+ if (!_toUnicodeMap.addMissingMappings(fe.getNumGlyphs()))
+ Message::wstream(true) << "incomplete Unicode mapping for native font " << name() << " (" << filename() << ")\n";
+ return true;
+}
+
+
+Character NativeFontImpl::decodeChar (uint32_t c) const {
+ return Character(Character::INDEX, c);
+}
+
+
+uint32_t NativeFontImpl::unicode (uint32_t c) const {
+ uint32_t ucode = _toUnicodeMap.valueAt(c);
+ return Unicode::charToCodepoint(ucode);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+VirtualFontImpl::VirtualFontImpl (const string &name, uint32_t cs, double ds, double ss)
+ : TFMFont(name, cs, ds, ss)
+{
+}
+
+
+const char* VirtualFontImpl::path () const {
+ return FileFinder::instance().lookup(name()+".vf");
+}
+
+
+void VirtualFontImpl::assignChar (uint32_t c, DVIVector &&dvi) {
+ _charDefs.emplace(c, std::move(dvi));
+}
+
+
+/** Returns the DVI sippet that describes a given character of the virtual font.
+ * @param[in] c character code
+ * @return pointer to vector of DVI commands, or 0 if character doesn't exist */
+const vector<uint8_t>* VirtualFontImpl::getDVI (int c) const {
+ auto it = _charDefs.find(c);
+ return (it == _charDefs.end() ? nullptr : &it->second);
+}
+
diff --git a/dviware/dvisvgm/src/Font.hpp b/dviware/dvisvgm/src/Font.hpp
new file mode 100644
index 0000000000..bbc0d62b8a
--- /dev/null
+++ b/dviware/dvisvgm/src/Font.hpp
@@ -0,0 +1,397 @@
+/*************************************************************************
+** Font.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef FONT_HPP
+#define FONT_HPP
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+#include "Character.hpp"
+#include "CharMapID.hpp"
+#include "Color.hpp"
+#include "FontCache.hpp"
+#include "FontEncoding.hpp"
+#include "FontMap.hpp"
+#include "FontMetrics.hpp"
+#include "GFGlyphTracer.hpp"
+#include "Glyph.hpp"
+#include "GraphicsPath.hpp"
+#include "MessageException.hpp"
+#include "RangeMap.hpp"
+#include "ToUnicodeMap.hpp"
+#include "VFActions.hpp"
+#include "VFReader.hpp"
+#include "utility.hpp"
+
+struct FontStyle;
+
+struct GlyphMetrics {
+ GlyphMetrics () : wl(0), wr(0), h(0), d(0) {}
+ GlyphMetrics (double wwl, double wwr, double hh, double dd) : wl(wwl), wr(wwr), h(hh), d(dd) {}
+ double wl, wr, h, d;
+};
+
+
+/** Abstract base for all font classes. */
+class Font {
+ public:
+ virtual ~Font () =default;
+ virtual std::unique_ptr<Font> clone (double ds, double sc) const =0;
+ virtual const Font* uniqueFont () const =0;
+ virtual std::string name () const =0;
+ virtual double designSize () const =0;
+ virtual double scaledSize () const =0;
+ virtual double scaleFactor () const {return scaledSize()/designSize();}
+ virtual double charWidth (int c) const =0;
+ virtual double charDepth (int c) const =0;
+ virtual double charHeight (int c) const =0;
+ virtual double italicCorr (int c) const =0;
+ virtual const FontMetrics* getMetrics () const =0;
+ virtual const char* path () const =0;
+ virtual const char* filename () const;
+ virtual const FontEncoding* encoding () const;
+ virtual bool getGlyph (int c, Glyph &glyph, GFGlyphTracer::Callback *callback=nullptr) const =0;
+ virtual void getGlyphMetrics (int c, bool vertical, GlyphMetrics &metrics) const;
+ virtual uint32_t unicode (uint32_t c) const;
+ virtual void tidy () const {}
+ virtual bool findAndAssignBaseFontMap () {return true;}
+ virtual bool verticalLayout () const {return getMetrics() ? getMetrics()->verticalLayout() : false;}
+ virtual bool verifyChecksums () const {return true;}
+ virtual int fontIndex () const {return 0;}
+ virtual const FontStyle* style () const {return nullptr;}
+ virtual Color color () const {return Color::BLACK;}
+ virtual const FontMap::Entry* fontMapEntry () const;
+};
+
+
+/** Empty font without any glyphs. Instances of this class are used
+ * if no physical or virtual font file can be found.
+ * The metric values returned by the member functions are based on cmr10. */
+class EmptyFont : public Font {
+ public:
+ explicit EmptyFont (std::string name) : _fontname(std::move(name)) {}
+ std::unique_ptr<Font> clone (double ds, double sc) const override {return util::make_unique<EmptyFont>(*this);}
+ const Font* uniqueFont () const override {return this;}
+ std::string name () const override {return _fontname;}
+ double designSize () const override {return 10;} // cmr10 design size in pt
+ double scaledSize () const override {return 10;} // cmr10 scaled size in pt
+ double charWidth (int c) const override {return 9.164;} // width of cmr10's 'M' in pt
+ double charHeight (int c) const override {return 6.833;} // height of cmr10's 'M' in pt
+ double charDepth (int c) const override {return 0;}
+ double italicCorr (int c) const override {return 0;}
+ const FontMetrics* getMetrics () const override {return nullptr;}
+ const char* path () const override {return nullptr;}
+ bool getGlyph (int c, Glyph &glyph, GFGlyphTracer::Callback *cb=nullptr) const override {return false;}
+
+ private:
+ std::string _fontname;
+};
+
+
+/** Interface for all physical fonts. */
+class PhysicalFont : public virtual Font {
+ public:
+ enum class Type {MF, OTF, PFB, TTC, TTF, UNKNOWN};
+
+ static std::unique_ptr<Font> create (const std::string &name, uint32_t checksum, double dsize, double ssize, PhysicalFont::Type type);
+ static std::unique_ptr<Font> create (const std::string &name, int fontindex, uint32_t checksum, double dsize, double ssize);
+ virtual Type type () const =0;
+ bool getGlyph (int c, Glyph &glyph, GFGlyphTracer::Callback *cb=nullptr) const override;
+ virtual bool getExactGlyphBox (int c, BoundingBox &bbox, GFGlyphTracer::Callback *cb=nullptr) const;
+ virtual bool getExactGlyphBox (int c, GlyphMetrics &metrics, bool vertical, GFGlyphTracer::Callback *cb=nullptr) const;
+ virtual bool isCIDFont () const;
+ virtual int hAdvance () const;
+ virtual std::string familyName () const;
+ virtual std::string styleName () const;
+ virtual double hAdvance (int c) const;
+ virtual double vAdvance (int c) const;
+ std::string glyphName (int c) const;
+ virtual int unitsPerEm () const;
+ virtual double scaledAscent () const;
+ virtual int ascent () const;
+ virtual int descent () const;
+ virtual int traceAllGlyphs (bool includeCached, GFGlyphTracer::Callback *cb=nullptr) const;
+ virtual int collectCharMapIDs (std::vector<CharMapID> &charmapIDs) const;
+ virtual CharMapID getCharMapID () const =0;
+ virtual void setCharMapID (const CharMapID &id) {}
+ virtual Character decodeChar (uint32_t c) const;
+ const char* path () const override;
+
+ protected:
+ bool createGF (std::string &gfname) const;
+
+ public:
+ static bool EXACT_BBOX;
+ static bool KEEP_TEMP_FILES;
+ static const char *CACHE_PATH; ///< path to cache directory (0 if caching is disabled)
+ static double METAFONT_MAG; ///< magnification factor for Metafont calls
+
+ protected:
+ static FontCache _cache;
+};
+
+
+/** Interface for all virtual fonts. */
+class VirtualFont : public virtual Font {
+ friend class FontManager;
+ public:
+ using DVIVector = std::vector<uint8_t>;
+
+ public:
+ static std::unique_ptr<Font> create (const std::string &name, uint32_t checksum, double dsize, double ssize);
+ virtual const DVIVector* getDVI (int c) const =0;
+ bool getGlyph (int c, Glyph &glyph, GFGlyphTracer::Callback *cb=nullptr) const override {return false;}
+
+ protected:
+ virtual void assignChar (uint32_t c, DVIVector &&dvi) =0;
+};
+
+
+class TFMFont : public virtual Font {
+ public:
+ TFMFont (std::string name, uint32_t cs, double ds, double ss);
+ const FontMetrics* getMetrics () const override;
+ std::string name () const override {return _fontname;}
+ double designSize () const override {return _dsize;}
+ double scaledSize () const override {return _ssize;}
+ double charWidth (int c) const override;
+ double charDepth (int c) const override;
+ double charHeight (int c) const override;
+ double italicCorr (int c) const override;
+ bool verifyChecksums () const override;
+
+ private:
+ mutable std::unique_ptr<FontMetrics> _metrics;
+ std::string _fontname;
+ uint32_t _checksum; ///< cheksum to be compared with TFM checksum
+ double _dsize; ///< design size in PS point units
+ double _ssize; ///< scaled size in PS point units
+};
+
+
+class PhysicalFontProxy : public PhysicalFont {
+ friend class PhysicalFontImpl;
+ public:
+ std::unique_ptr<Font> clone (double ds, double sc) const override {
+ return std::unique_ptr<PhysicalFontProxy>(new PhysicalFontProxy(*this, ds, sc));
+ }
+
+ const Font* uniqueFont () const override {return _pf;}
+ std::string name () const override {return _pf->name();}
+ double designSize () const override {return _dsize;}
+ double scaledSize () const override {return _ssize;}
+ double charWidth (int c) const override {return _pf->charWidth(c);}
+ double charDepth (int c) const override {return _pf->charDepth(c);}
+ double charHeight (int c) const override {return _pf->charHeight(c);}
+ double italicCorr (int c) const override {return _pf->italicCorr(c);}
+ const FontMetrics* getMetrics () const override {return _pf->getMetrics();}
+ Type type () const override {return _pf->type();}
+ uint32_t unicode (uint32_t c) const override {return _pf->unicode(c);}
+ int fontIndex () const override {return _pf->fontIndex();}
+ const FontStyle* style () const override {return _pf->style();}
+ const FontMap::Entry* fontMapEntry () const override {return _pf->fontMapEntry();}
+ const FontEncoding* encoding () const override {return _pf->encoding();}
+ CharMapID getCharMapID () const override {return _pf->getCharMapID();}
+ int collectCharMapIDs (std::vector<CharMapID> &charmapIDs) const override {return _pf->collectCharMapIDs(charmapIDs);}
+
+ protected:
+ PhysicalFontProxy (const PhysicalFont *font, double ds, double ss) : _pf(font), _dsize(ds), _ssize(ss) {}
+ PhysicalFontProxy (const PhysicalFontProxy &proxy, double ds, double ss) : _pf(proxy._pf), _dsize(ds), _ssize(ss) {}
+
+ private:
+ const PhysicalFont *_pf;
+ double _dsize; ///< design size in PS point units
+ double _ssize; ///< scaled size in PS point units
+};
+
+
+class PhysicalFontImpl : public PhysicalFont, public TFMFont {
+ friend class PhysicalFont;
+ public:
+ ~PhysicalFontImpl () override;
+
+ std::unique_ptr<Font> clone (double ds, double ss) const override {
+ return std::unique_ptr<PhysicalFontProxy>(new PhysicalFontProxy(this, ds, ss));
+ }
+
+ const Font* uniqueFont () const override {return this;}
+ Type type () const override {return _filetype;}
+ int fontIndex() const override {return _fontIndex;}
+ const FontStyle* style () const override;
+ const FontEncoding* encoding () const override;
+ uint32_t unicode (uint32_t c) const override;
+ bool findAndAssignBaseFontMap () override;
+ void tidy () const override;
+ CharMapID getCharMapID () const override {return _charmapID;}
+
+ protected:
+ PhysicalFontImpl (const std::string &name, int fontindex, uint32_t checksum, double dsize, double ssize, PhysicalFont::Type type);
+
+ private:
+ Type _filetype;
+ int _fontIndex;
+ FontEncodingPair _encodingPair;
+ CharMapID _charmapID; ///< ID of the font's charmap to use
+ std::unique_ptr<const RangeMap> _localCharMap;
+};
+
+
+class NativeFont : public PhysicalFont {
+ public:
+ virtual std::unique_ptr<NativeFont> clone (double ptsize, const FontStyle &style, Color color) const =0;
+ std::unique_ptr<Font> clone (double ds, double sc) const override =0;
+ std::string name () const override;
+ Type type () const override;
+ double designSize () const override {return _ptsize;}
+ double scaledSize () const override {return _ptsize;}
+ double charWidth (int c) const override;
+ double charDepth (int c) const override;
+ double charHeight (int c) const override;
+ double italicCorr (int c) const override {return 0;}
+ const FontMetrics* getMetrics () const override {return nullptr;}
+ const FontStyle* style () const override {return &_style;}
+ Color color () const override {return _color;}
+ const FontMap::Entry* fontMapEntry () const override {return nullptr;}
+ static std::string uniqueName (const std::string &path, const FontStyle &style);
+
+ protected:
+ NativeFont (double ptsize, const FontStyle &style, Color color) : _ptsize(ptsize), _style(style), _color(color) {}
+
+ private:
+ double _ptsize; ///< font size in PS point units
+ FontStyle _style;
+ Color _color;
+};
+
+
+class NativeFontProxy : public NativeFont {
+ friend class NativeFontImpl;
+ public:
+ std::unique_ptr<NativeFont> clone (double ptsize, const FontStyle &style, Color color) const override {
+ return std::unique_ptr<NativeFontProxy>(new NativeFontProxy(this, ptsize, style, color));
+ }
+
+ std::unique_ptr<Font> clone (double ds, double sc) const override {
+ return std::unique_ptr<NativeFontProxy>(new NativeFontProxy(this , sc, *style(), color()));
+ }
+
+ const Font* uniqueFont () const override {return _nfont;}
+ const char* path () const override {return _nfont->path();}
+ int fontIndex () const override {return _nfont->fontIndex();}
+ Character decodeChar (uint32_t c) const override {return _nfont->decodeChar(c);}
+ uint32_t unicode (uint32_t c) const override {return _nfont->unicode(c);}
+ CharMapID getCharMapID () const override {return _nfont->getCharMapID();}
+
+ protected:
+ NativeFontProxy (const NativeFont *nfont, double ptsize, const FontStyle &style, Color color)
+ : NativeFont(ptsize, style, color), _nfont(nfont) {}
+
+ private:
+ const NativeFont *_nfont;
+};
+
+
+class NativeFontImpl : public NativeFont {
+ public:
+ NativeFontImpl (std::string fname, int fontIndex, double ptsize, const FontStyle &style, Color color)
+ : NativeFont(ptsize, style, color), _path(std::move(fname)), _fontIndex(fontIndex) {}
+
+ std::unique_ptr<NativeFont> clone (double ptsize, const FontStyle &style, Color color) const override {
+ return std::unique_ptr<NativeFontProxy>(new NativeFontProxy(this, ptsize, style, color));
+ }
+
+ std::unique_ptr<Font> clone (double ds, double sc) const override {
+ return std::unique_ptr<NativeFontProxy>(new NativeFontProxy(this , sc, *style(), color()));
+ }
+
+ const Font* uniqueFont () const override {return this;}
+ const char* path () const override {return _path.c_str();}
+ int fontIndex() const override {return _fontIndex;}
+ std::string fontFamily () const;
+ bool findAndAssignBaseFontMap () override;
+ CharMapID getCharMapID () const override {return CharMapID::NONE;}
+ Character decodeChar (uint32_t c) const override;
+ uint32_t unicode (uint32_t c) const override;
+
+ private:
+ std::string _path;
+ int _fontIndex;
+ ToUnicodeMap _toUnicodeMap; ///< maps from char indexes to unicode points
+};
+
+
+class VirtualFontProxy : public VirtualFont {
+ friend class VirtualFontImpl;
+ public:
+ std::unique_ptr<Font> clone (double ds, double ss) const override {
+ return std::unique_ptr<VirtualFontProxy>(new VirtualFontProxy(*this, ds, ss));
+ }
+
+ const Font* uniqueFont () const override {return _vf;}
+ std::string name () const override {return _vf->name();}
+ const DVIVector* getDVI (int c) const override {return _vf->getDVI(c);}
+ double designSize () const override {return _dsize;}
+ double scaledSize () const override {return _ssize;}
+ double charWidth (int c) const override {return _vf->charWidth(c);}
+ double charDepth (int c) const override {return _vf->charDepth(c);}
+ double charHeight (int c) const override {return _vf->charHeight(c);}
+ double italicCorr (int c) const override {return _vf->italicCorr(c);}
+ const FontMetrics* getMetrics () const override {return _vf->getMetrics();}
+ const char* path () const override {return _vf->path();}
+
+ protected:
+ VirtualFontProxy (const VirtualFont *font, double ds, double ss) : _vf(font), _dsize(ds), _ssize(ss) {}
+ VirtualFontProxy (const VirtualFontProxy &proxy, double ds, double ss) : _vf(proxy._vf), _dsize(ds), _ssize(ss) {}
+ void assignChar (uint32_t c, DVIVector &&dvi) override {}
+
+ private:
+ const VirtualFont *_vf;
+ double _dsize; ///< design size in PS point units
+ double _ssize; ///< scaled size in PS point units
+};
+
+
+class VirtualFontImpl : public VirtualFont, public TFMFont {
+ friend class VirtualFont;
+ public:
+ std::unique_ptr<Font> clone (double ds, double ss) const override {
+ return std::unique_ptr<VirtualFontProxy>(new VirtualFontProxy(this, ds, ss));
+ }
+
+ const Font* uniqueFont () const override {return this;}
+ const DVIVector* getDVI (int c) const override;
+ const char* path () const override;
+
+ protected:
+ VirtualFontImpl (const std::string &name, uint32_t checksum, double dsize, double ssize);
+ void assignChar (uint32_t c, DVIVector &&dvi) override;
+
+ private:
+ std::unordered_map<uint32_t, DVIVector> _charDefs; ///< dvi subroutines defining the characters
+};
+
+
+struct FontException : public MessageException {
+ explicit FontException (const std::string &msg) : MessageException(msg) {}
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/FontCache.cpp b/dviware/dvisvgm/src/FontCache.cpp
new file mode 100644
index 0000000000..5f61fc341c
--- /dev/null
+++ b/dviware/dvisvgm/src/FontCache.cpp
@@ -0,0 +1,377 @@
+/*************************************************************************
+** FontCache.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <cstring>
+#include <fstream>
+#include <iomanip>
+#include <sstream>
+#include "CRC32.hpp"
+#include "FileSystem.hpp"
+#include "FontCache.hpp"
+#include "Glyph.hpp"
+#include "Pair.hpp"
+#include "StreamReader.hpp"
+#include "StreamWriter.hpp"
+
+using namespace std;
+
+const uint8_t FontCache::FORMAT_VERSION = 5;
+
+
+static Pair32 read_pair (int bytes, StreamReader &sr) {
+ int32_t x = sr.readSigned(bytes);
+ int32_t y = sr.readSigned(bytes);
+ return Pair32(x, y);
+}
+
+
+/** Removes all data from the cache. This does not affect the cache files. */
+void FontCache::clear () {
+ _glyphs.clear();
+ _fontname.clear();
+}
+
+
+/** Assigns glyph data to a character and adds it to the cache.
+ * @param[in] c character code
+ * @param[in] glyph font glyph data */
+void FontCache::setGlyph (int c, const Glyph &glyph) {
+ _glyphs[c] = glyph;
+ _changed = true;
+}
+
+
+/** Returns the corresponding glyph data of a given character of the current font.
+ * @param[in] c character code
+ * @return font glyph data (0 if no matching data was found) */
+const Glyph* FontCache::getGlyph (int c) const {
+ auto it = _glyphs.find(c);
+ return (it != _glyphs.end()) ? &it->second : nullptr;
+}
+
+
+/** Writes the current cache data to a file (only if anything changed after
+ * the last call of read()).
+ * @param[in] fontname name of current font
+ * @param[in] dir directory where the cache file should go
+ * @return true if writing was successful */
+bool FontCache::write (const string &fontname, const string &dir) const {
+ if (!_changed)
+ return true;
+
+ if (!fontname.empty()) {
+ string dirstr = dir.empty() ? FileSystem::getcwd() : dir;
+ ostringstream oss;
+ oss << dirstr << '/' << fontname << ".fgd";
+ ofstream ofs(oss.str(), ios::binary);
+ return write(fontname, ofs);
+ }
+ return false;
+}
+
+
+bool FontCache::write (const string &dir) const {
+ return _fontname.empty() ? false : write(_fontname, dir);
+}
+
+
+/** Returns the minimal number of bytes needed to store the given value. */
+static int max_int_size (int32_t value) {
+ int32_t limit = 0x7f;
+ for (int i=1; i <= 4; i++) {
+ if ((value < 0 && -value <= limit+1) || (value >= 0 && value <= limit))
+ return i;
+ limit = (limit << 8) | 0xff;
+ }
+ return 4;
+}
+
+
+/** Returns the minimal number of bytes needed to store the biggest
+ * pair component of the given vector. */
+static int max_int_size (const Pair<int32_t> *pairs, size_t n) {
+ int ret=0;
+ for (size_t i=0; i < n; i++) {
+ ret = max(ret, max_int_size(pairs[i].x()));
+ ret = max(ret, max_int_size(pairs[i].y()));
+ }
+ return ret;
+}
+
+
+/** Writes the current cache data to a stream (only if anything changed after
+ * the last call of read()).
+ * @param[in] fontname name of current font
+ * @param[in] os output stream
+ * @return true if writing was successful */
+bool FontCache::write (const string &fontname, ostream &os) const {
+ if (!_changed)
+ return true;
+ if (!os)
+ return false;
+
+ StreamWriter sw(os);
+ CRC32 crc32;
+
+ struct WriteActions : Glyph::Actions {
+ WriteActions (StreamWriter &sw, CRC32 &crc32) : _sw(sw), _crc32(crc32) {}
+
+ void draw (char cmd, const Glyph::Point *points, int n) override {
+ int bytes = max_int_size(points, n);
+ int cmdchar = (bytes << 5) | (cmd - 'A');
+ _sw.writeUnsigned(cmdchar, 1, _crc32);
+ for (int i=0; i < n; i++) {
+ _sw.writeSigned(points[i].x(), bytes, _crc32);
+ _sw.writeSigned(points[i].y(), bytes, _crc32);
+ }
+ }
+ StreamWriter &_sw;
+ CRC32 &_crc32;
+ } actions(sw, crc32);
+
+ sw.writeUnsigned(FORMAT_VERSION, 1, crc32);
+ sw.writeUnsigned(0, 4); // space for checksum
+ sw.writeString(fontname, crc32, true);
+ sw.writeUnsigned(_glyphs.size(), 4, crc32);
+ for (const auto &charglyphpair : _glyphs) {
+ const Glyph &glyph = charglyphpair.second;
+ sw.writeUnsigned(charglyphpair.first, 4, crc32);
+ sw.writeUnsigned(glyph.size(), 2, crc32);
+ glyph.iterate(actions, false);
+ }
+ os.seekp(1);
+ sw.writeUnsigned(crc32.get(), 4); // insert CRC32 checksum
+ os.seekp(0, ios::end);
+ return true;
+}
+
+
+/** Reads font glyph information from a file.
+ * @param[in] fontname name of font data to read
+ * @param[in] dir directory where the cache files are located
+ * @return true if reading was successful */
+bool FontCache::read (const string &fontname, const string &dir) {
+ if (fontname.empty())
+ return false;
+ if (_fontname == fontname)
+ return true;
+ clear();
+ string dirstr = dir.empty() ? FileSystem::getcwd() : dir;
+ ostringstream oss;
+ oss << dirstr << '/' << fontname << ".fgd";
+ ifstream ifs(oss.str(), ios::binary);
+ return read(fontname, ifs);
+}
+
+
+/** Reads font glyph information from a stream.
+ * @param[in] fontname name of font data to read
+ * @param[in] is input stream to read the glyph data from
+ * @return true if reading was successful */
+bool FontCache::read (const string &fontname, istream &is) {
+ if (_fontname == fontname)
+ return true;
+ clear();
+ _fontname = fontname;
+ if (!is)
+ return false;
+
+ StreamReader sr(is);
+ CRC32 crc32;
+ if (sr.readUnsigned(1, crc32) != FORMAT_VERSION)
+ return false;
+
+ uint32_t crc32_cmp = sr.readUnsigned(4);
+ crc32.update(is);
+ if (crc32.get() != crc32_cmp)
+ return false;
+
+ is.clear();
+ is.seekg(5); // continue reading after checksum
+
+ string fname = sr.readString();
+ if (fname != fontname)
+ return false;
+
+ uint32_t num_glyphs = sr.readUnsigned(4);
+ while (num_glyphs-- > 0) {
+ uint32_t c = sr.readUnsigned(4); // character code
+ uint16_t s = sr.readUnsigned(2); // number of path commands
+ Glyph &glyph = _glyphs[c];
+ while (s-- > 0) {
+ uint8_t cmdval = sr.readUnsigned(1);
+ uint8_t cmdchar = (cmdval & 0x1f) + 'A';
+ int bytes = cmdval >> 5;
+ switch (cmdchar) {
+ case 'C': {
+ Pair32 p1 = read_pair(bytes, sr);
+ Pair32 p2 = read_pair(bytes, sr);
+ Pair32 p3 = read_pair(bytes, sr);
+ glyph.cubicto(p1, p2, p3);
+ break;
+ }
+ case 'L':
+ glyph.lineto(read_pair(bytes, sr));
+ break;
+ case 'M':
+ glyph.moveto(read_pair(bytes, sr));
+ break;
+ case 'Q': {
+ Pair32 p1 = read_pair(bytes, sr);
+ Pair32 p2 = read_pair(bytes, sr);
+ glyph.conicto(p1, p2);
+ break;
+ }
+ case 'Z':
+ glyph.closepath();
+ }
+ }
+ }
+ _changed = false;
+ return true;
+}
+
+
+/** Collects font cache information.
+ * @param[in] dirname path to font cache directory
+ * @param[out] infos the collected font information
+ * @param[out] invalid names of outdated/corrupted cache files
+ * @return true on success */
+bool FontCache::fontinfo (const string &dirname, vector<FontInfo> &infos, vector<string> &invalid) {
+ infos.clear();
+ invalid.clear();
+ if (!dirname.empty()) {
+ vector<string> fnames;
+ FileSystem::collect(dirname, fnames);
+ for (const string &fname : fnames) {
+ if (fname[0] == 'f' && fname.length() > 5 && fname.substr(fname.length()-4) == ".fgd") {
+ FontInfo info;
+ string path = dirname+"/"+(fname.substr(1));
+ ifstream ifs(path, ios::binary);
+ if (fontinfo(ifs, info))
+ infos.emplace_back(move(info));
+ else
+ invalid.emplace_back(fname.substr(1));
+ }
+ }
+ }
+ return !infos.empty();
+}
+
+
+/** Collects font cache information of a single font.
+ * @param[in] is input stream of the cache file
+ * @param[out] info the collected data
+ * @return true if data could be read, false if cache file is unavailable, outdated, or corrupted */
+bool FontCache::fontinfo (std::istream &is, FontInfo &info) {
+ info.name.clear();
+ info.numchars = info.numbytes = info.numcmds = 0;
+ if (is) {
+ is.clear();
+ is.seekg(0);
+ try {
+ StreamReader sr(is);
+ CRC32 crc32;
+ if ((info.version = sr.readUnsigned(1, crc32)) != FORMAT_VERSION)
+ return false;
+
+ info.checksum = sr.readUnsigned(4);
+ crc32.update(is);
+ if (crc32.get() != info.checksum)
+ return false;
+
+ is.clear();
+ is.seekg(5); // continue reading after checksum
+
+ info.name = sr.readString();
+ info.numchars = sr.readUnsigned(4);
+ for (uint32_t i=0; i < info.numchars; i++) {
+ sr.readUnsigned(4); // character code
+ uint16_t s = sr.readUnsigned(2); // number of path commands
+ while (s-- > 0) {
+ uint8_t cmdval = sr.readUnsigned(1);
+ uint8_t cmdchar = (cmdval & 0x1f) + 'A';
+ int bytes = cmdval >> 5;
+ int bc = 0;
+ switch (cmdchar) {
+ case 'C': bc = 6*bytes; break;
+ case 'H':
+ case 'L':
+ case 'M':
+ case 'T':
+ case 'V': bc = 2*bytes; break;
+ case 'Q':
+ case 'S': bc = 4*bytes; break;
+ case 'Z': break;
+ default : return false;
+ }
+ info.numbytes += bc+1; // command length + command
+ info.numcmds++;
+ is.seekg(bc, ios::cur);
+ }
+ info.numbytes += 6; // number of path commands + char code
+ }
+ info.numbytes += 6+info.name.length(); // version + 0-byte + fontname + number of chars
+ }
+ catch (StreamReaderException &e) {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+/** Collects font cache information and write it to a stream.
+ * @param[in] dirname path to font cache directory
+ * @param[in] os output is written to this stream
+ * @param[in] purge if true, outdated and corrupted cache files are removed */
+void FontCache::fontinfo (const string &dirname, ostream &os, bool purge) {
+ if (!dirname.empty()) {
+ ios::fmtflags osflags(os.flags());
+ vector<FontInfo> infos;
+ vector<string> invalid_files;
+ if (!fontinfo(dirname, infos, invalid_files))
+ os << "cache is empty\n";
+ else {
+ os << "cache format version " << infos[0].version << endl;
+ map<string, const FontInfo*> sortmap;
+ for (const FontInfo &info : infos)
+ sortmap[info.name] = &info;
+ for (const auto &strinfopair : sortmap) {
+ os << dec << setfill(' ') << left
+ << setw(10) << left << strinfopair.second->name
+ << setw(5) << right << strinfopair.second->numchars << " glyph" << (strinfopair.second->numchars == 1 ? ' ':'s')
+ << setw(10) << right << strinfopair.second->numcmds << " cmd" << (strinfopair.second->numcmds == 1 ? ' ':'s')
+ << setw(12) << right << strinfopair.second->numbytes << " byte" << (strinfopair.second->numbytes == 1 ? ' ':'s')
+ << setw(6) << "crc:" << setw(8) << hex << right << setfill('0') << strinfopair.second->checksum
+ << endl;
+ }
+ }
+ if (purge) {
+ for (const string &str : invalid_files) {
+ string path=dirname+"/"+str;
+ if (FileSystem::remove(path))
+ os << "invalid cache file " << str << " removed\n";
+ }
+ }
+ os.flags(osflags); // restore format flags
+ }
+}
diff --git a/dviware/dvisvgm/src/FontCache.hpp b/dviware/dvisvgm/src/FontCache.hpp
new file mode 100644
index 0000000000..d242e54ad3
--- /dev/null
+++ b/dviware/dvisvgm/src/FontCache.hpp
@@ -0,0 +1,64 @@
+/*************************************************************************
+** FontCache.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef FONTCACHE_HPP
+#define FONTCACHE_HPP
+
+#include <map>
+#include <string>
+#include <vector>
+#include "Glyph.hpp"
+
+
+class FontCache {
+ public:
+ struct FontInfo {
+ std::string name; // fontname
+ uint16_t version; // file format version
+ uint32_t checksum; // CRC32 checksum of file data
+ uint32_t numchars; // number of characters
+ uint32_t numbytes; // number of bytes
+ uint32_t numcmds; // number of path commands
+ };
+
+ public:
+ ~FontCache () {clear();}
+ bool read (const std::string &fontname, const std::string &dir);
+ bool read (const std::string &fontname, std::istream &is);
+ bool write (const std::string &dir) const;
+ bool write (const std::string &fontname, const std::string &dir) const;
+ bool write (const std::string &fontname, std::ostream &os) const;
+ const Glyph* getGlyph (int c) const;
+ void setGlyph (int c, const Glyph &glyph);
+ void clear ();
+ const std::string& fontname () const {return _fontname;}
+
+ static bool fontinfo (const std::string &dirname, std::vector<FontInfo> &infos, std::vector<std::string> &invalid);
+ static bool fontinfo (std::istream &is, FontInfo &info);
+ static void fontinfo (const std::string &dirname, std::ostream &os, bool purge=false);
+
+ private:
+ static const uint8_t FORMAT_VERSION;
+ std::string _fontname;
+ std::map<int, Glyph> _glyphs;
+ bool _changed=false;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/FontEncoding.cpp b/dviware/dvisvgm/src/FontEncoding.cpp
new file mode 100644
index 0000000000..3b038b63c6
--- /dev/null
+++ b/dviware/dvisvgm/src/FontEncoding.cpp
@@ -0,0 +1,89 @@
+/*************************************************************************
+** FontEncoding.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include "CMap.hpp"
+#include "CMapManager.hpp"
+#include "EncFile.hpp"
+#include "FileFinder.hpp"
+#include "FontEncoding.hpp"
+#include "utility.hpp"
+
+using namespace std;
+
+/** Returns the encoding object for a given encoding name.
+ * @param[in] encname name of the encoding to lookup
+ * @return pointer to encoding object, or 0 if there is no encoding defined */
+FontEncoding* FontEncoding::encoding (const string &encname) {
+ if (encname.empty())
+ return nullptr;
+
+ using EncodingMap = unordered_map<string, unique_ptr<EncFile>>;
+ static EncodingMap encmap;
+ // initially, try to find an .enc file with the given name
+ auto it = encmap.find(encname);
+ if (it != encmap.end())
+ return it->second.get();
+ if (FileFinder::instance().lookup(encname + ".enc", false)) {
+ auto state = encmap.emplace(encname, util::make_unique<EncFile>(encname));
+ return state.first->second.get();
+ }
+ // no .enc file found => try to find a CMap
+ if (CMap *cmap = CMapManager::instance().lookup(encname))
+ return cmap;
+ return nullptr;
+}
+
+/////////////////////////////////////////////////////////////////////////
+
+Character FontEncodingPair::decode (uint32_t c) const {
+ if (_enc1) {
+ Character chr = _enc1->decode(c);
+ if (_enc2 && chr.type() != Character::NAME)
+ chr = _enc2->decode(chr.number());
+ return chr;
+ }
+ return Character(Character::INDEX, 0);
+}
+
+
+bool FontEncodingPair::mapsToCharIndex () const {
+ if (_enc2)
+ return _enc2->mapsToCharIndex();
+ if (_enc1)
+ return _enc1->mapsToCharIndex();
+ return false;
+}
+
+
+const FontEncoding* FontEncodingPair::findCompatibleBaseFontMap (const PhysicalFont *font, CharMapID &charmapID) const {
+ if (_enc2)
+ return _enc2->findCompatibleBaseFontMap(font, charmapID);
+ if (_enc1)
+ return _enc1->findCompatibleBaseFontMap(font, charmapID);
+ return nullptr;
+}
+
+
+void FontEncodingPair::assign (const FontEncoding *enc) {
+ if (!_enc1)
+ _enc1 = enc;
+ else
+ _enc2 = enc;
+}
diff --git a/dviware/dvisvgm/src/FontEncoding.hpp b/dviware/dvisvgm/src/FontEncoding.hpp
new file mode 100644
index 0000000000..fffccd4224
--- /dev/null
+++ b/dviware/dvisvgm/src/FontEncoding.hpp
@@ -0,0 +1,61 @@
+/*************************************************************************
+** FontEncoding.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef FONTENCODING_HPP
+#define FONTENCODING_HPP
+
+#include <string>
+#include "Character.hpp"
+
+
+struct CharMapID;
+class PhysicalFont;
+
+struct FontEncoding {
+ virtual ~FontEncoding () =default;
+ virtual Character decode (uint32_t c) const =0;
+ virtual bool mapsToCharIndex () const =0;
+ virtual const FontEncoding* findCompatibleBaseFontMap (const PhysicalFont *font, CharMapID &charmapID) const {return nullptr;}
+ static FontEncoding* encoding (const std::string &encname);
+};
+
+
+struct NamedFontEncoding : public FontEncoding {
+ virtual const char* name () const =0;
+ virtual const char* path () const =0;
+};
+
+
+class FontEncodingPair : public FontEncoding {
+ public:
+ explicit FontEncodingPair (const FontEncoding *enc1) : _enc1(enc1), _enc2(nullptr) {}
+ FontEncodingPair (const FontEncoding *enc1, const FontEncoding *enc2) : _enc1(enc1), _enc2(enc2) {}
+ Character decode (uint32_t c) const override;
+ bool mapsToCharIndex () const override;
+ const FontEncoding* findCompatibleBaseFontMap (const PhysicalFont *font, CharMapID &charmapID) const override;
+ const FontEncoding* enc1 () const {return _enc1;}
+ const FontEncoding* enc2 () const {return _enc2;}
+ void assign (const FontEncoding *enc);
+
+ private:
+ const FontEncoding *_enc1, *_enc2;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/FontEngine.cpp b/dviware/dvisvgm/src/FontEngine.cpp
new file mode 100644
index 0000000000..ef02f37335
--- /dev/null
+++ b/dviware/dvisvgm/src/FontEngine.cpp
@@ -0,0 +1,413 @@
+/*************************************************************************
+** FontEngine.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <cmath>
+#include <sstream>
+#include <ft2build.h>
+#include FT_ADVANCES_H
+#include FT_GLYPH_H
+#include FT_OUTLINE_H
+#include FT_TRUETYPE_TABLES_H
+#include FT_TYPES_H
+#include "Font.hpp"
+#include "FontEngine.hpp"
+#include "FontStyle.hpp"
+#include "Message.hpp"
+#include "utility.hpp"
+
+using namespace std;
+
+
+/** Converts a floating point value to a 16.16 fixed point value. */
+static inline FT_Fixed to_16dot16 (double val) {
+ return static_cast<FT_Fixed>(lround(val*65536.0));
+}
+
+
+/** Converts an integer to a 16.16 fixed point value. */
+static inline FT_Fixed to_16dot16 (int val) {
+ return static_cast<FT_Fixed>(val) << 16;
+}
+
+
+///////////////////////////////////////////////////////////////////////////
+
+
+FontEngine::FontEngine () {
+ if (FT_Init_FreeType(&_library))
+ Message::estream(true) << "failed to initialize FreeType library\n";
+}
+
+
+FontEngine::~FontEngine () {
+ if (_currentFace && FT_Done_Face(_currentFace))
+ Message::estream(true) << "failed to release font\n";
+ if (FT_Done_FreeType(_library))
+ Message::estream(true) << "failed to release FreeType library\n";
+}
+
+
+/** Returns the singleton instance of this class. */
+FontEngine& FontEngine::instance () {
+ static FontEngine engine;
+ return engine;
+}
+
+
+string FontEngine::version () {
+ FT_Int major, minor, patch;
+ FT_Library &lib = instance()._library;
+ FT_Library_Version(lib, &major, &minor, &patch);
+ ostringstream oss;
+ oss << major << '.' << minor << '.' << patch;
+ return oss.str();
+}
+
+
+/** Sets the font to be used.
+ * @param[in] fname path to font file
+ * @param[in] fontindex index of font in font collection (multi-font files, like TTC)
+ * @return true on success */
+bool FontEngine::setFont (const string &fname, int fontindex, const CharMapID &charMapID) {
+ if (_currentFace && FT_Done_Face(_currentFace))
+ Message::estream(true) << "failed to release font\n";
+ if (FT_New_Face(_library, fname.c_str(), fontindex, &_currentFace)) {
+ Message::estream(true) << "can't read font file " << fname << '\n';
+ return false;
+ }
+ if (charMapID.valid())
+ setCharMap(charMapID);
+ return true;
+}
+
+
+bool FontEngine::setFont (const Font &font) {
+ if (_currentFont && _currentFont->name() == font.name())
+ return true;
+
+ if (const char *path=font.path()) {
+ auto pf = dynamic_cast<const PhysicalFont*>(&font);
+ if (setFont(path, font.fontIndex(), pf ? pf->getCharMapID() : CharMapID())) {
+ _currentFont = &font;
+ return true;
+ }
+ }
+ return false;
+}
+
+
+bool FontEngine::isCIDFont() const {
+ FT_Bool cid_keyed;
+ return FT_Get_CID_Is_Internally_CID_Keyed(_currentFace, &cid_keyed) == 0 && cid_keyed;
+}
+
+
+bool FontEngine::setCharMap (const CharMapID &charMapID) {
+ for (int i=0; i < _currentFace->num_charmaps; i++) {
+ FT_CharMap ft_cmap = _currentFace->charmaps[i];
+ if (ft_cmap->platform_id == charMapID.platform_id && ft_cmap->encoding_id == charMapID.encoding_id) {
+ FT_Set_Charmap(_currentFace, ft_cmap);
+ return true;
+ }
+ }
+ return false;
+}
+
+
+/** Returns a character map that maps from character indexes to character codes
+ * of the current encoding.
+ * @param[out] charmap the resulting charmap */
+void FontEngine::buildCharMap (RangeMap &charmap) {
+ charmap.clear();
+ FT_UInt glyph_index;
+ uint32_t charcode = FT_Get_First_Char(_currentFace, &glyph_index);
+ while (glyph_index) {
+ charmap.addRange(glyph_index, glyph_index, charcode);
+ charcode = FT_Get_Next_Char(_currentFace, charcode, &glyph_index);
+ }
+}
+
+
+/** Creates a charmap that maps from the custom character encoding to unicode.
+ * @return pointer to charmap if it could be created, 0 otherwise */
+unique_ptr<const RangeMap> FontEngine::createCustomToUnicodeMap () {
+ FT_CharMap ftcharmap = _currentFace->charmap;
+ if (FT_Select_Charmap(_currentFace, FT_ENCODING_ADOBE_CUSTOM) != 0)
+ return nullptr;
+ RangeMap index_to_source_chrcode;
+ buildCharMap(index_to_source_chrcode);
+ if (FT_Select_Charmap(_currentFace, FT_ENCODING_UNICODE) != 0)
+ return nullptr;
+ auto charmap = util::make_unique<RangeMap>();
+ FT_UInt glyph_index;
+ uint32_t unicode_point = FT_Get_First_Char(_currentFace, &glyph_index);
+ while (glyph_index) {
+ uint32_t custom_charcode = index_to_source_chrcode.valueAt(glyph_index);
+ charmap->addRange(custom_charcode, custom_charcode, unicode_point);
+ unicode_point = FT_Get_Next_Char(_currentFace, unicode_point, &glyph_index);
+ }
+ FT_Set_Charmap(_currentFace, ftcharmap);
+ return std::move(charmap);
+}
+
+
+const char* FontEngine::getFamilyName () const {
+ return _currentFace ? _currentFace->family_name : nullptr;
+}
+
+
+const char* FontEngine::getStyleName () const {
+ return _currentFace ? _currentFace->style_name : nullptr;
+}
+
+
+int FontEngine::getUnitsPerEM () const {
+ return _currentFace ? _currentFace->units_per_EM : 0;
+}
+
+
+/** Returns the ascender of the current font in font units.
+ * The (usually) positive value denotes the maximum height
+ * (extent above the baseline) of the font. */
+int FontEngine::getAscender () const {
+ return _currentFace ? _currentFace->ascender : 0;
+}
+
+
+/** Returns the descender of the current font in font units.
+ * The (usually) positive value denotes the maximum depth
+ * (extent below the baseline) of the font. */
+int FontEngine::getDescender () const {
+ return _currentFace ? -_currentFace->descender : 0;
+}
+
+
+int FontEngine::getAdvance (int c) const {
+ if (_currentFace) {
+ FT_Fixed adv=0;
+ FT_Get_Advance(_currentFace, c, FT_LOAD_NO_SCALE, &adv);
+ return adv;
+ }
+ return 0;
+}
+
+
+int FontEngine::getHAdvance () const {
+ if (_currentFace) {
+ auto table = static_cast<TT_OS2*>(FT_Get_Sfnt_Table(_currentFace, ft_sfnt_os2));
+ return table ? table->xAvgCharWidth : 0;
+ }
+ return 0;
+}
+
+
+int FontEngine::getHAdvance (const Character &c) const {
+ if (_currentFace) {
+ FT_Load_Glyph(_currentFace, charIndex(c), FT_LOAD_NO_SCALE);
+ return _currentFace->glyph->metrics.horiAdvance;
+ }
+ return 0;
+}
+
+
+int FontEngine::getVAdvance (const Character &c) const {
+ if (_currentFace) {
+ FT_Load_Glyph(_currentFace, charIndex(c), FT_LOAD_NO_SCALE);
+ if (FT_HAS_VERTICAL(_currentFace))
+ return _currentFace->glyph->metrics.vertAdvance;
+ return _currentFace->glyph->metrics.horiAdvance;
+ }
+ return 0;
+}
+
+
+int FontEngine::charIndex (const Character &c) const {
+ if (!_currentFace || !_currentFace->charmap)
+ return c.type() == Character::NAME ? 0 : c.number();
+ switch (c.type()) {
+ case Character::CHRCODE:
+ return FT_Get_Char_Index(_currentFace, (FT_ULong)c.number());
+ case Character::NAME:
+ return FT_Get_Name_Index(_currentFace, const_cast<FT_String*>(c.name()));
+ default:
+ return c.number();
+ }
+}
+
+
+/** Get first available character of the current font face. */
+int FontEngine::getFirstChar () const {
+ if (_currentFace)
+ return _currentChar = FT_Get_First_Char(_currentFace, &_currentGlyphIndex);
+ return 0;
+}
+
+
+/** Get the next available character of the current font face. */
+int FontEngine::getNextChar () const {
+ if (_currentFace && _currentGlyphIndex)
+ return _currentChar = FT_Get_Next_Char(_currentFace, _currentChar, &_currentGlyphIndex);
+ return getFirstChar();
+}
+
+
+/** Returns the number of glyphs present in the current font face. */
+int FontEngine::getNumGlyphs () const {
+ return _currentFace ? _currentFace->num_glyphs : 0;
+}
+
+
+/** Returns the glyph name for a given charater code.
+ * @param[in] c char code
+ * @return glyph name */
+string FontEngine::getGlyphName (const Character &c) const {
+ if (c.type() == Character::NAME)
+ return c.name();
+
+ if (_currentFace && FT_HAS_GLYPH_NAMES(_currentFace)) {
+ char buf[256];
+ FT_Get_Glyph_Name(_currentFace, charIndex(c), buf, 256);
+ return string(buf);
+ }
+ return "";
+}
+
+
+vector<int> FontEngine::getPanose () const {
+ vector<int> panose(10);
+ if (_currentFace) {
+ auto table = static_cast<TT_OS2*>(FT_Get_Sfnt_Table(_currentFace, ft_sfnt_os2));
+ if (table)
+ for (int i=0; i < 10; i++)
+ panose[i] = table->panose[i];
+ }
+ return panose;
+}
+
+
+int FontEngine::getCharMapIDs (vector<CharMapID> &charmapIDs) const {
+ charmapIDs.clear();
+ if (_currentFace) {
+ for (int i=0; i < _currentFace->num_charmaps; i++) {
+ FT_CharMap charmap = _currentFace->charmaps[i];
+ charmapIDs.emplace_back(CharMapID(charmap->platform_id, charmap->encoding_id));
+ }
+ }
+ return charmapIDs.size();
+}
+
+
+CharMapID FontEngine::setUnicodeCharMap () {
+ if (_currentFace && FT_Select_Charmap(_currentFace, FT_ENCODING_UNICODE) == 0)
+ return CharMapID(_currentFace->charmap->platform_id, _currentFace->charmap->encoding_id);
+ return CharMapID();
+}
+
+
+CharMapID FontEngine::setCustomCharMap () {
+ if (_currentFace && FT_Select_Charmap(_currentFace, FT_ENCODING_ADOBE_CUSTOM) == 0)
+ return CharMapID(_currentFace->charmap->platform_id, _currentFace->charmap->encoding_id);
+ return CharMapID();
+}
+
+
+// handle API change in freetype version 2.2.1
+#if FREETYPE_MAJOR > 2 || (FREETYPE_MAJOR == 2 && (FREETYPE_MINOR > 2 || (FREETYPE_MINOR == 2 && FREETYPE_PATCH >= 1)))
+ using FTVectorPtr = const FT_Vector*;
+#else
+ using FTVectorPtr = FT_Vector*;
+#endif
+
+
+// Callback functions used by trace_outline/FT_Outline_Decompose
+static int moveto (FTVectorPtr to, void *user) {
+ auto glyph = static_cast<Glyph*>(user);
+ glyph->moveto(to->x, to->y);
+ return 0;
+}
+
+
+static int lineto (FTVectorPtr to, void *user) {
+ auto glyph = static_cast<Glyph*>(user);
+ glyph->lineto(to->x, to->y);
+ return 0;
+}
+
+
+static int conicto (FTVectorPtr control, FTVectorPtr to, void *user) {
+ auto glyph = static_cast<Glyph*>(user);
+ glyph->conicto(control->x, control->y, to->x, to->y);
+ return 0;
+}
+
+
+static int cubicto (FTVectorPtr control1, FTVectorPtr control2, FTVectorPtr to, void *user) {
+ auto glyph = static_cast<Glyph*>(user);
+ glyph->cubicto(control1->x, control1->y, control2->x, control2->y, to->x, to->y);
+ return 0;
+}
+
+
+/** Traces the outline of a glyph by calling the corresponding "drawing" functions.
+ * Each glyph is composed of straight lines, quadratic (conic) or cubic B�zier curves.
+ * This function creates a Glyph object representing these graphics segments.
+ * @param[in] face FreeType object representing the font to scan
+ * @param[in] font corresponding Font object providing additional data
+ * @param[in] index index of the glyph to be traced
+ * @param[out] glyph resulting Glyph object containing the graphics segments
+ * @param[in] scale if true the current pt size will be considered otherwise the plain TrueType units are used.
+ * @return false on errors */
+static bool trace_outline (FT_Face face, const Font *font, int index, Glyph &glyph, bool scale) {
+ if (face) {
+ if (FT_Load_Glyph(face, index, scale ? FT_LOAD_DEFAULT : FT_LOAD_NO_SCALE)) {
+ Message::estream(true) << "can't load glyph " << int(index) << '\n';
+ return false;
+ }
+ if (face->glyph->format != FT_GLYPH_FORMAT_OUTLINE) {
+ Message::estream(true) << "no outlines found in glyph " << int(index) << '\n';
+ return false;
+ }
+ FT_Outline outline = face->glyph->outline;
+ // apply style parameters if set
+ if (const FontStyle *style = font->style()) {
+ FT_Matrix matrix = {to_16dot16(style->extend), to_16dot16(style->slant), 0, to_16dot16(1)};
+ FT_Outline_Transform(&outline, &matrix);
+ if (style->bold != 0)
+ FT_Outline_Embolden(&outline, style->bold/font->scaledSize()*face->units_per_EM);
+ }
+ const FT_Outline_Funcs funcs = {moveto, lineto, conicto, cubicto, 0, 0};
+ FT_Outline_Decompose(&outline, &funcs, &glyph);
+ return true;
+ }
+ Message::wstream(true) << "can't trace outline (no font selected)\n";
+ return false;
+}
+
+
+/** Traces the outline of a glyph by calling the corresponding "drawing" functions.
+ * Each glyph is composed of straight lines, quadratic (conic) or cubic B�zier curves.
+ * This function creates a Glyph object representing these graphics segments.
+ * @param[in] c the glyph of this character will be traced
+ * @param[out] glyph resulting Glyph object containing the graphics segments
+ * @param[in] scale if true the current pt size will be considered otherwise the plain TrueType units are used.
+ * @return false on errors */
+bool FontEngine::traceOutline (const Character &c, Glyph &glyph, bool scale) const {
+ return trace_outline(_currentFace, _currentFont, charIndex(c), glyph, scale);
+}
diff --git a/dviware/dvisvgm/src/FontEngine.hpp b/dviware/dvisvgm/src/FontEngine.hpp
new file mode 100644
index 0000000000..33c9b96da2
--- /dev/null
+++ b/dviware/dvisvgm/src/FontEngine.hpp
@@ -0,0 +1,82 @@
+/*************************************************************************
+** FontEngine.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef FONTENGINE_HPP
+#define FONTENGINE_HPP
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_CID_H
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+#include "Character.hpp"
+#include "CharMapID.hpp"
+#include "Glyph.hpp"
+#include "RangeMap.hpp"
+
+class Font;
+
+/** This class provides methods to handle font files and font data.
+ * It's a wrapper for the Freetype font library. */
+class FontEngine {
+ public:
+ ~FontEngine ();
+ static FontEngine& instance ();
+ static std::string version ();
+ bool setFont (const Font &font);
+ bool isCIDFont() const;
+ bool traceOutline (const Character &c, Glyph &glyph, bool scale=true) const;
+ const char* getFamilyName () const;
+ const char* getStyleName () const;
+ int getUnitsPerEM () const;
+ int getAscender () const;
+ int getDescender () const;
+ int getAdvance (int c) const;
+ int getHAdvance () const;
+ int getHAdvance (const Character &c) const;
+ int getVAdvance (const Character &c) const;
+ int getFirstChar () const;
+ int getNextChar () const;
+ int getCharMapIDs (std::vector<CharMapID> &charmapIDs) const;
+ int getNumGlyphs () const;
+ CharMapID setUnicodeCharMap ();
+ CharMapID setCustomCharMap ();
+ std::vector<int> getPanose () const;
+ std::string getGlyphName (const Character &c) const;
+ int getCharByGlyphName (const char *name) const;
+ bool setCharMap (const CharMapID &charMapID);
+ void buildCharMap (RangeMap &charmap);
+ std::unique_ptr<const RangeMap> createCustomToUnicodeMap ();
+
+ protected:
+ FontEngine ();
+ bool setFont (const std::string &fname, int fontindex, const CharMapID &charmapID);
+ int charIndex (const Character &c) const;
+
+ private:
+ mutable unsigned int _currentChar=0, _currentGlyphIndex=0;
+ FT_Face _currentFace = nullptr;
+ FT_Library _library;
+ const Font *_currentFont = nullptr;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/FontManager.cpp b/dviware/dvisvgm/src/FontManager.cpp
new file mode 100644
index 0000000000..4f236b3866
--- /dev/null
+++ b/dviware/dvisvgm/src/FontManager.cpp
@@ -0,0 +1,353 @@
+/*************************************************************************
+** FontManager.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <cstring>
+#include <cstdlib>
+#include <fstream>
+#include <set>
+#include "CMap.hpp"
+#include "Font.hpp"
+#include "FontManager.hpp"
+#include "FileFinder.hpp"
+#include "FileSystem.hpp"
+#include "Message.hpp"
+
+using namespace std;
+
+
+/** Returns the singleton instance */
+FontManager& FontManager::instance () {
+ static FontManager fm;
+ return fm;
+}
+
+
+/** Returns a unique ID that identifies the font.
+ * @param[in] n local font number, as used in DVI and VF files
+ * @return non-negative font ID if font was found, -1 otherwise */
+int FontManager::fontID (int n) const {
+ if (_vfStack.empty()) {
+ auto it = _num2id.find(n);
+ return (it == _num2id.end()) ? -1 : it->second;
+ }
+ auto vit = _vfnum2id.find(_vfStack.top());
+ if (vit == _vfnum2id.end())
+ return -1;
+ const Num2IdMap &num2id = vit->second;
+ auto it = num2id.find(n);
+ return (it == num2id.end()) ? -1 : it->second;
+}
+
+
+/** Returns a unique ID that identifies the font. Not the font object but the
+ * font pointer is looked up to get the ID. Thus, two different pointers
+ * referencing different objects of the same font are mapped to different IDs.
+ * @param[in] font pointer to font object to be identified
+ * @return non-negative font ID if font was found, -1 otherwise */
+int FontManager::fontID (const Font *font) const {
+ for (size_t i=0; i < _fonts.size(); i++)
+ if (_fonts[i].get() == font)
+ return i;
+ return -1;
+}
+
+
+/** Returns a unique ID that identifies the font.
+ * @param[in] name name of font to be identified, e.g. cmr10
+ * @return non-negative font ID if font was found, -1 otherwise */
+int FontManager::fontID (const string &name) const {
+ auto it = _name2id.find(name);
+ if (it == _name2id.end())
+ return -1;
+ return it->second;
+}
+
+
+int FontManager::fontnum (int id) const {
+ if (id < 0 || size_t(id) > _fonts.size())
+ return -1;
+ if (_vfStack.empty()) {
+ for (const auto &entry : _num2id)
+ if (entry.second == id)
+ return entry.first;
+ }
+ else {
+ auto it = _vfnum2id.find(_vfStack.top());
+ if (it == _vfnum2id.end())
+ return -1;
+ for (const auto &entry : it->second)
+ if (entry.second == id)
+ return entry.first;
+ }
+ return -1;
+}
+
+
+int FontManager::vfFirstFontNum (const VirtualFont *vf) const {
+ auto it = _vfFirstFontMap.find(vf);
+ return (it == _vfFirstFontMap.end()) ? -1 : (int) it->second;
+}
+
+
+/** Returns a previously registered font.
+ * @param[in] n local font number, as used in DVI and VF files
+ * @return pointer to font if font was found, 0 otherwise */
+Font* FontManager::getFont (int n) const {
+ int id = fontID(n);
+ return (id < 0) ? nullptr : _fonts[id].get();
+}
+
+
+Font* FontManager::getFont (const string &name) const {
+ int id = fontID(name);
+ if (id < 0)
+ return nullptr;
+ return _fonts[id].get();
+}
+
+
+Font* FontManager::getFontById (int id) const {
+ if (id < 0 || size_t(id) >= _fonts.size())
+ return nullptr;
+ return _fonts[id].get();
+}
+
+
+/** Returns the current active virtual font. */
+const VirtualFont* FontManager::getVF () const {
+ return _vfStack.empty() ? nullptr : _vfStack.top();
+}
+
+
+static unique_ptr<Font> create_font (const string &filename, const string &fontname, int fontindex, uint32_t checksum, double dsize, double ssize) {
+ string ext;
+ if (const char *dot = strrchr(filename.c_str(), '.'))
+ ext = dot+1;
+ if (!ext.empty() && FileFinder::instance().lookup(filename)) {
+ if (ext == "pfb")
+ return PhysicalFont::create(fontname, checksum, dsize, ssize, PhysicalFont::Type::PFB);
+ if (ext == "otf")
+ return PhysicalFont::create(fontname, checksum, dsize, ssize, PhysicalFont::Type::OTF);
+ if (ext == "ttf")
+ return PhysicalFont::create(fontname, checksum, dsize, ssize, PhysicalFont::Type::TTF);
+ if (ext == "ttc")
+ return PhysicalFont::create(fontname, fontindex, checksum, dsize, ssize);
+ if (ext == "vf")
+ return VirtualFont::create(fontname, checksum, dsize, ssize);
+ if (ext == "mf")
+ return PhysicalFont::create(fontname, checksum, dsize, ssize, PhysicalFont::Type::MF);
+ }
+ return nullptr;
+}
+
+
+/** Registers a new font to be managed by the FontManager. If there is
+ * already a registered font assigned to number n, nothing happens.
+ * @param[in] fontnum local font number, as used in DVI and VF files
+ * @param[in] name TFM fontname given in DVI file, e.g. cmr10
+ * @param[in] checksum checksum to be compared with TFM checksum
+ * @param[in] dsize design size in PS point units
+ * @param[in] ssize scaled size in PS point units
+ * @return id of registered font */
+int FontManager::registerFont (uint32_t fontnum, const string &name, uint32_t checksum, double dsize, double ssize) {
+ int id = fontID(fontnum);
+ if (id >= 0)
+ return id;
+
+ unique_ptr<Font> newfont;
+ const int newid = _fonts.size(); // the new font gets this ID
+ auto it = _name2id.find(name);
+ if (it != _name2id.end()) { // font with same name already registered?
+ const auto &font = _fonts[it->second];
+ newfont = font->clone(dsize, ssize);
+ }
+ else {
+ string filename = name;
+ int fontindex = 0;
+ const FontMap::Entry *map_entry = FontMap::instance().lookup(name);
+ if (map_entry) {
+ filename = map_entry->fontname;
+ fontindex = map_entry->fontindex;
+ }
+ // try to find font file with the exact given name
+ if (filename.rfind('.') != string::npos)
+ newfont = create_font(filename, name, fontindex, checksum, dsize, ssize);
+ else {
+ // try various font file formats if the given file has no extension
+ const char *exts[] = {"pfb", "otf", "ttc", "ttf", "vf", "mf", nullptr};
+ for (const char **p = exts; *p && !newfont; ++p)
+ newfont = create_font(filename+"."+*p, name, fontindex, checksum, dsize, ssize);
+ }
+ if (newfont) {
+ if (!newfont->findAndAssignBaseFontMap())
+ Message::wstream(true) << "no suitable encoding table found for font " << filename << "\n";
+ if (!newfont->verifyChecksums())
+ Message::wstream(true) << "checksum mismatch in font " << name << '\n';
+ }
+ else {
+ // create dummy font as a placeholder if the proper font is not available
+ newfont = util::make_unique<EmptyFont>(name);
+ if (filename.rfind('.') == string::npos)
+ filename += ".mf";
+ // print warning message about missing font file (only once for each filename)
+ static set<string> missing_fonts;
+ if (missing_fonts.find(filename) == missing_fonts.end()) {
+ Message::wstream(true) << "font file '" << filename << "' not found\n";
+ missing_fonts.insert(filename);
+ }
+ }
+ _name2id[name] = newid;
+ }
+ _fonts.emplace_back(std::move(newfont));
+ if (_vfStack.empty()) // register font referenced in dvi file?
+ _num2id[fontnum] = newid;
+ else { // register font referenced in vf file
+ const VirtualFont *vf = _vfStack.top();
+ _vfnum2id[vf][fontnum] = newid;
+ if (_vfFirstFontMap.find(vf) == _vfFirstFontMap.end()) // first fontdef of VF?
+ _vfFirstFontMap[vf] = fontnum;
+ }
+ return newid;
+}
+
+
+/** Registers a new native font to be managed by the FontManager. If there is
+ * already a registered font assigned to number n, nothing happens.
+ * @param[in] fontnum local font number used in DVI file
+ * @param[in] filename name/path of font file
+ * @param[in] ptsize font size in PS points
+ * @param[in] style font style parameters
+ * @param[in] color global font color
+ * @return global font id */
+int FontManager::registerFont (uint32_t fontnum, const string &filename, double ptsize, const FontStyle &style, Color color) {
+ return registerFont(fontnum, filename, 0, ptsize, style, color);
+}
+
+
+/** Registers a new native font to be managed by the FontManager. If there is
+ * already a registered font assigned to number n, nothing happens.
+ * @param[in] fontnum local font number used in DVI file
+ * @param[in] filename name/path of font file
+ * @param[in] fontIndex subfont index
+ * @param[in] ptsize font size in PS points
+ * @param[in] style font style parameters
+ * @param[in] color global font color
+ * @return global font id */
+int FontManager::registerFont (uint32_t fontnum, string filename, int fontIndex, double ptsize, const FontStyle &style, Color color) {
+ int id = fontID(fontnum);
+ if (id >= 0)
+ return id;
+
+ if (!filename.empty() && filename[0] == '[' && filename[filename.size()-1] == ']')
+ filename = filename.substr(1, filename.size()-2);
+ string fontname = NativeFont::uniqueName(filename, style);
+ const char *path = filename.c_str();
+ unique_ptr<Font> newfont;
+ const int newid = _fonts.size(); // the new font gets this ID
+ auto it = _name2id.find(fontname);
+ if (it != _name2id.end()) { // font with same name already registered?
+ if (auto font = dynamic_cast<NativeFont*>(_fonts[it->second].get()))
+ newfont = font->clone(ptsize, style, color);
+ }
+ else {
+ if (!FileSystem::exists(path))
+ path = FileFinder::instance().lookup(filename, false);
+ if (path) {
+ newfont.reset(new NativeFontImpl(path, fontIndex, ptsize, style, color));
+ newfont->findAndAssignBaseFontMap();
+ }
+ if (!newfont) {
+ // create dummy font as a placeholder if the proper font is not available
+ newfont = util::make_unique<EmptyFont>(filename);
+ // print warning message about missing font file (only once for each filename)
+ static set<string> missing_fonts;
+ if (missing_fonts.find(filename) == missing_fonts.end()) {
+ Message::wstream(true) << "font file '" << filename << "' not found\n";
+ missing_fonts.insert(filename);
+ }
+ }
+ _name2id[fontname] = newid;
+ }
+ _fonts.emplace_back(std::move(newfont));
+ _num2id[fontnum] = newid;
+ return newid;
+}
+
+
+/** Enters a new virtual font frame.
+ * This method must be called before processing a VF character.
+ * @param[in] vf virtual font */
+void FontManager::enterVF (VirtualFont *vf) {
+ if (vf)
+ _vfStack.push(vf);
+}
+
+
+/** Leaves a previously entered virtual font frame. */
+void FontManager::leaveVF () {
+ if (!_vfStack.empty())
+ _vfStack.pop();
+}
+
+
+/** Assigns a sequence of DVI commands to a char code.
+ * @param[in] c character code
+ * @param[in] dvi DVI commands that describe character c */
+void FontManager::assignVFChar (int c, vector<uint8_t> &&dvi) {
+ if (!_vfStack.empty())
+ _vfStack.top()->assignChar(c, std::move(dvi));
+}
+
+
+ostream& FontManager::write (ostream &os, Font *font, int level) {
+#if 0
+ if (font) {
+ int id = -1;
+ for (int i=0; i < _fonts.size() && id < 0; i++)
+ if (_fonts[i] == font)
+ id = i;
+
+ VirtualFont *vf = dynamic_cast<VirtualFont*>(font);
+ for (int j=0; j < level+1; j++)
+ os << " ";
+ os << "id " << id
+ << " fontnum " << fontnum(id) << " "
+ << (vf ? "VF" : "PF") << " "
+ << font->name()
+ << endl;
+
+ if (vf) {
+ enterVF(vf);
+ const Num2IdMap &num2id = _vfnum2id.find(vf)->second;
+ for (const auto &entry : num2id) {
+ Font *font = _fonts[entry.second];
+ write(os, font, level+1);
+ }
+ leaveVF();
+ }
+ }
+ else {
+ for (int i=0; i < _fonts.size(); i++)
+ write(os, _fonts[i], level);
+ os << endl;
+ }
+#endif
+ return os;
+}
diff --git a/dviware/dvisvgm/src/FontManager.hpp b/dviware/dvisvgm/src/FontManager.hpp
new file mode 100644
index 0000000000..4405f67ecd
--- /dev/null
+++ b/dviware/dvisvgm/src/FontManager.hpp
@@ -0,0 +1,83 @@
+/*************************************************************************
+** FontManager.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef FONTMANAGER_HPP
+#define FONTMANAGER_HPP
+
+#include <memory>
+#include <ostream>
+#include <set>
+#include <string>
+#include <stack>
+#include <unordered_map>
+#include <vector>
+#include "Color.hpp"
+#include "FontStyle.hpp"
+
+
+class FileFinder;
+class Font;
+class VirtualFont;
+
+/** This class provides methods for easy DVI font handling.
+ * DVI and VF files use local font numbers to reference fonts. For SVG output
+ * we need a single list with unique IDs of all physical fonts. Characters of
+ * virtual fonts are completely replaced by their DVI description so they don't
+ * appear anywhere in the output. */
+class FontManager
+{
+ using Num2IdMap = std::unordered_map<uint32_t,int>;
+ using Name2IdMap = std::unordered_map<std::string,int>;
+ using VfNum2IdMap = std::unordered_map<const VirtualFont*,Num2IdMap>;
+ using VfFirstFontMap = std::unordered_map<const VirtualFont*,uint32_t>;
+ using VfStack = std::stack<VirtualFont*>;
+
+ public:
+ static FontManager& instance ();
+ int registerFont (uint32_t fontnum, const std::string &fontname, uint32_t checksum, double dsize, double scale);
+ int registerFont (uint32_t fontnum, const std::string &fname, double ptsize, const FontStyle &style, Color color);
+ int registerFont (uint32_t fontnum, std::string fname, int fontIndex, double ptsize, const FontStyle &style, Color color);
+ Font* getFont (int n) const;
+ Font* getFont (const std::string &name) const;
+ Font* getFontById (int id) const;
+ const VirtualFont* getVF () const;
+ int fontID (int n) const;
+ int fontID (const Font *font) const;
+ int fontID (const std::string &name) const;
+ int fontnum (int id) const;
+ int vfFirstFontNum (const VirtualFont *vf) const;
+ void enterVF (VirtualFont *vf);
+ void leaveVF ();
+ void assignVFChar (int c, std::vector<uint8_t> &&dvi);
+ std::ostream& write (std::ostream &os, Font *font=nullptr, int level=0);
+
+ protected:
+ FontManager () =default;
+
+ private:
+ std::vector<std::unique_ptr<Font>> _fonts; ///< all registered Fonts
+ Num2IdMap _num2id; ///< DVI font number -> fontID
+ Name2IdMap _name2id; ///< fontname -> fontID
+ VfNum2IdMap _vfnum2id;
+ VfStack _vfStack; ///< stack of currently processed virtual fonts
+ VfFirstFontMap _vfFirstFontMap; ///< VF -> local font number of first font defined in VF
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/FontMap.cpp b/dviware/dvisvgm/src/FontMap.cpp
new file mode 100644
index 0000000000..cc48aef0e6
--- /dev/null
+++ b/dviware/dvisvgm/src/FontMap.cpp
@@ -0,0 +1,296 @@
+/*************************************************************************
+** FontMap.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <cstring>
+#include <fstream>
+#include <limits>
+#include <vector>
+#include "CMap.hpp"
+#include "Directory.hpp"
+#include "FileFinder.hpp"
+#include "FontManager.hpp"
+#include "FontMap.hpp"
+#include "MapLine.hpp"
+#include "Message.hpp"
+#include "Subfont.hpp"
+#include "utility.hpp"
+
+using namespace std;
+
+/** Returns the singleton instance. */
+FontMap& FontMap::instance() {
+ static FontMap fontmap;
+ return fontmap;
+}
+
+
+/** Reads and evaluates a single font map file.
+ * @param[in] fname name of map file to read
+ * @param[in] mode selects how to integrate the map file entries into the global map tree
+ * @return true if file could be opened */
+bool FontMap::read (const string &fname, FontMap::Mode mode) {
+ ifstream ifs(fname);
+ if (!ifs)
+ return false;
+
+ int line_number = 1;
+ while (ifs) {
+ int c = ifs.peek();
+ if (c < 0 || strchr("\n&#%;*", c)) // comment or empty line?
+ ifs.ignore(numeric_limits<int>::max(), '\n');
+ else {
+ try {
+ MapLine mapline(ifs);
+ apply(mapline, mode);
+ }
+ catch (const MapLineException &e) {
+ Message::wstream(true) << fname << ", line " << line_number << ": " << e.what() << '\n';
+ }
+ catch (const SubfontException &e) {
+ Message::wstream(true) << e.filename();
+ if (e.lineno() > 0)
+ Message::wstream(false) << ", line " << e.lineno();
+ Message::wstream(false) << e.what() << '\n';
+ }
+ }
+ line_number++;
+ }
+ return true;
+}
+
+
+bool FontMap::read (const string &fname, char modechar) {
+ Mode mode;
+ switch (modechar) {
+ case '=': mode = Mode::REPLACE; break;
+ case '-': mode = Mode::REMOVE; break;
+ default : mode = Mode::APPEND;
+ }
+ return read(fname, mode);
+}
+
+
+/** Applies a mapline according to the given mode (append, remove, replace).
+ * @param[in] mapline the mapline to be applied
+ * @param[in] mode mode to use
+ * @return true in case of success */
+bool FontMap::apply (const MapLine& mapline, FontMap::Mode mode) {
+ switch (mode) {
+ case Mode::APPEND:
+ return append(mapline);
+ case Mode::REMOVE:
+ return remove(mapline);
+ default:
+ return replace(mapline);
+ }
+}
+
+
+/** Applies a mapline according to the given mode (append, remove, replace).
+ * @param[in] mapline the mapline to be applied
+ * @param[in] modechar character that denotes the mode (+, -, or =)
+ * @return true in case of success */
+bool FontMap::apply (const MapLine& mapline, char modechar) {
+ Mode mode;
+ switch (modechar) {
+ case '=': mode = Mode::REPLACE; break;
+ case '-': mode = Mode::REMOVE; break;
+ default : mode = Mode::APPEND;
+ }
+ return apply(mapline, mode);
+}
+
+
+/** Reads and evaluates a sequence of map files. Each map file is looked up in the local
+ * directory and the TeX file tree.
+ * @param[in] fname_seq comma-separated list of map file names
+ * @return true if at least one of the given map files was found */
+bool FontMap::read (const string &fname_seq) {
+ bool found = false;
+ size_t left=0;
+ while (left < fname_seq.length()) {
+ const char modechar = fname_seq[left];
+ if (strchr("+-=", modechar))
+ left++;
+ string fname;
+ size_t right = fname_seq.find(',', left);
+ if (right != string::npos)
+ fname = fname_seq.substr(left, right-left);
+ else {
+ fname = fname_seq.substr(left);
+ right = fname_seq.length();
+ }
+ if (!fname.empty()) {
+ if (!read(fname, modechar)) {
+ if (const char *path = FileFinder::instance().lookup(fname, false))
+ found = found || read(path, modechar);
+ else
+ Message::wstream(true) << "map file " << fname << " not found\n";
+ }
+ }
+ left = right+1;
+ }
+ return found;
+}
+
+
+/** Appends given map line data to the font map if there is no entry for the corresponding
+ * font in the map yet.
+ * @param[in] mapline parsed font data
+ * @return true if data has been appended */
+bool FontMap::append (const MapLine &mapline) {
+ bool appended = false;
+ if (!mapline.texname().empty()) {
+ if (!mapline.fontfname().empty() || !mapline.encname().empty()) {
+ vector<Subfont*> subfonts;
+ if (mapline.sfd())
+ mapline.sfd()->subfonts(subfonts);
+ else
+ subfonts.push_back(nullptr);
+ for (Subfont *subfont : subfonts) {
+ string fontname = mapline.texname()+(subfont ? subfont->id() : "");
+ auto it = _entries.find(fontname);
+ if (it == _entries.end()) {
+ _entries.emplace(fontname, util::make_unique<Entry>(mapline, subfont));
+ appended = true;
+ }
+ }
+ }
+ }
+ return appended;
+}
+
+
+/** Replaces the map data of the given font.
+ * If the font is locked (because it's already in use) nothing happens.
+ * @param[in] mapline parsed font data
+ * @return true if data has been replaced */
+bool FontMap::replace (const MapLine &mapline) {
+ if (mapline.texname().empty())
+ return false;
+ if (mapline.fontfname().empty() && mapline.encname().empty())
+ return remove(mapline);
+
+ vector<Subfont*> subfonts;
+ if (mapline.sfd())
+ mapline.sfd()->subfonts(subfonts);
+ else
+ subfonts.push_back(nullptr);
+ for (Subfont *subfont : subfonts) {
+ string fontname = mapline.texname()+(subfont ? subfont->id() : "");
+ auto it = _entries.find(fontname);
+ if (it == _entries.end())
+ _entries.emplace(fontname, util::make_unique<Entry>(mapline, subfont));
+ else if (!it->second->locked)
+ *it->second = Entry(mapline, subfont);
+ }
+ return true;
+}
+
+
+/** Removes the map entry of the given font.
+ * If the font is locked (because it's already in use) nothing happens.
+ * @param[in] mapline parsed font data
+ * @return true if entry has been removed */
+bool FontMap::remove (const MapLine &mapline) {
+ bool removed = false;
+ if (!mapline.texname().empty()) {
+ vector<Subfont*> subfonts;
+ if (mapline.sfd())
+ mapline.sfd()->subfonts(subfonts);
+ else
+ subfonts.push_back(nullptr);
+ for (const Subfont *subfont : subfonts) {
+ string fontname = mapline.texname()+(subfont ? subfont->id() : "");
+ auto it = _entries.find(fontname);
+ if (it != _entries.end() && !it->second->locked) {
+ _entries.erase(it);
+ removed = true;
+ }
+ }
+ }
+ return removed;
+}
+
+
+ostream& FontMap::write (ostream &os) const {
+ for (const auto &entry : _entries)
+ os << entry.first << " -> " << entry.second->fontname << " [" << entry.second->encname << "]\n";
+ return os;
+}
+
+
+/** Reads and evaluates all map files in the given directory.
+ * @param[in] dirname path to directory containing the map files to be read */
+void FontMap::readdir (const string &dirname) {
+ Directory dir(dirname);
+ while (const char *fname = dir.read(Directory::ET_FILE)) {
+ if (strlen(fname) >= 4 && strcmp(fname+strlen(fname)-4, ".map") == 0) {
+ string path = dirname + "/" + fname;
+ read(path);
+ }
+ }
+}
+
+
+/** Returns name of font that is mapped to a given font.
+ * @param[in] fontname name of font whose mapped name is retrieved
+ * @returns name of mapped font */
+const FontMap::Entry* FontMap::lookup (const string &fontname) const {
+ auto it = _entries.find(fontname);
+ if (it == _entries.end())
+ return nullptr;
+ return it->second.get();
+}
+
+
+/** Sets the lock flag for the given font in order to avoid changing the map data of this font.
+ * @param[in] fontname name of font to be locked */
+void FontMap::lockFont (const string& fontname) {
+ auto it = _entries.find(fontname);
+ if (it != _entries.end())
+ it->second->locked = true;
+}
+
+
+/** Removes all (unlocked) entries from the font map.
+ * @param[in] unlocked_only if true, only unlocked entries are removed */
+void FontMap::clear (bool unlocked_only) {
+ if (!unlocked_only)
+ _entries.clear();
+ else {
+ auto it=_entries.begin();
+ while (it != _entries.end()) {
+ if (it->second->locked)
+ ++it;
+ else
+ it = _entries.erase(it);
+ }
+ }
+}
+
+/////////////////////////////////////////////////
+
+FontMap::Entry::Entry (const MapLine &mapline, Subfont *sf)
+ : fontname(mapline.fontfname()), encname(mapline.encname()), subfont(sf), fontindex(mapline.fontindex()),
+ locked(false), style(mapline.bold(), mapline.extend(), mapline.slant())
+{
+}
diff --git a/dviware/dvisvgm/src/FontMap.hpp b/dviware/dvisvgm/src/FontMap.hpp
new file mode 100644
index 0000000000..cd109c1809
--- /dev/null
+++ b/dviware/dvisvgm/src/FontMap.hpp
@@ -0,0 +1,74 @@
+/*************************************************************************
+** FontMap.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef FONTMAP_HPP
+#define FONTMAP_HPP
+
+#include <memory>
+#include <ostream>
+#include <string>
+#include <unordered_map>
+#include "FontStyle.hpp"
+
+struct FontEncoding;
+class MapLine;
+class Subfont;
+
+class FontMap {
+ public:
+ struct Entry {
+ explicit Entry (const MapLine &mapline, Subfont *subfont=nullptr);
+ Entry (const Entry &entry) =delete;
+ Entry (Entry &&entry) =default;
+ Entry& operator = (Entry &&entry) =default;
+ std::string fontname; ///< target font name
+ std::string encname; ///< name of font encoding
+ Subfont *subfont;
+ int fontindex; ///< index of font in multi-font file
+ bool locked;
+ FontStyle style;
+ };
+
+ public:
+ enum class Mode {APPEND, REMOVE, REPLACE};
+
+ static FontMap& instance ();
+ bool read (const std::string &fname, Mode mode);
+ bool read (const std::string &fname, char modechar);
+ bool read (const std::string &fname_seq);
+ void readdir (const std::string &dirname);
+ bool apply (const MapLine &mapline, Mode mode);
+ bool apply (const MapLine &mapline, char modechar);
+ bool append (const MapLine &mapline);
+ bool replace (const MapLine &mapline);
+ bool remove (const MapLine &mapline);
+ void lockFont (const std::string &fontname);
+ void clear (bool unlocked_only=false);
+ std::ostream& write (std::ostream &os) const;
+ const Entry* lookup (const std::string &fontname) const;
+
+ protected:
+ FontMap () =default;
+
+ private:
+ std::unordered_map<std::string,std::unique_ptr<Entry>> _entries;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/FontMetrics.cpp b/dviware/dvisvgm/src/FontMetrics.cpp
new file mode 100644
index 0000000000..003b80acad
--- /dev/null
+++ b/dviware/dvisvgm/src/FontMetrics.cpp
@@ -0,0 +1,41 @@
+/*************************************************************************
+** FontMetrics.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <fstream>
+#include <memory>
+#include "FileFinder.hpp"
+#include "FontMetrics.hpp"
+#include "JFM.hpp"
+#include "utility.hpp"
+
+using namespace std;
+
+
+unique_ptr<FontMetrics> FontMetrics::read (const string &fontname) {
+ const char *path = FileFinder::instance().lookup(fontname + ".tfm");
+ ifstream ifs(path, ios::binary);
+ if (!ifs)
+ return unique_ptr<FontMetrics>();
+ uint16_t id = 256*ifs.get();
+ id += ifs.get();
+ if (id == 9 || id == 11) // Japanese font metric file?
+ return util::make_unique<JFM>(ifs);
+ return util::make_unique<TFM>(ifs);
+}
diff --git a/dviware/dvisvgm/src/FontMetrics.hpp b/dviware/dvisvgm/src/FontMetrics.hpp
new file mode 100644
index 0000000000..ccba26b727
--- /dev/null
+++ b/dviware/dvisvgm/src/FontMetrics.hpp
@@ -0,0 +1,73 @@
+/*************************************************************************
+** FontMetrics.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef FONTMETRICS_HPP
+#define FONTMETRICS_HPP
+
+#include <istream>
+#include <memory>
+#include <string>
+#include "MessageException.hpp"
+
+struct FontMetrics {
+ virtual ~FontMetrics () =default;
+ virtual double getDesignSize () const =0;
+ virtual double getCharWidth (int c) const =0;
+ virtual double getCharHeight (int c) const =0;
+ virtual double getCharDepth (int c) const =0;
+ virtual double getItalicCorr (int c) const =0;
+ virtual double getSpace () const =0;
+ virtual double getSpaceStretch () const =0;
+ virtual double getSpaceShrink () const =0;
+ virtual double getQuad () const =0;
+ virtual double getAscent () const =0;
+ virtual double getDescent () const =0;
+ virtual bool verticalLayout () const =0;
+ virtual uint32_t getChecksum () const =0;
+ virtual uint16_t firstChar () const =0;
+ virtual uint16_t lastChar () const =0;
+ static std::unique_ptr<FontMetrics> read (const std::string &fontname);
+};
+
+
+struct NullFontMetric : public FontMetrics {
+ double getDesignSize () const override {return 1;}
+ double getCharWidth (int c) const override {return 0;}
+ double getCharHeight (int c) const override {return 0;}
+ double getCharDepth (int c) const override {return 0;}
+ double getItalicCorr (int c) const override {return 0;}
+ double getSpace () const override {return 0;}
+ double getSpaceStretch () const override {return 0;}
+ double getSpaceShrink () const override {return 0;}
+ double getQuad () const override {return 0;}
+ double getAscent () const override {return 0;}
+ double getDescent () const override {return 0;}
+ bool verticalLayout () const override {return false;}
+ uint32_t getChecksum () const override {return 0;}
+ uint16_t firstChar () const override {return 0;}
+ uint16_t lastChar () const override {return 0;}
+};
+
+
+struct FontMetricException : public MessageException {
+ explicit FontMetricException (const std::string &msg) : MessageException(msg) {}
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/FontStyle.hpp b/dviware/dvisvgm/src/FontStyle.hpp
new file mode 100644
index 0000000000..40c5d3e45e
--- /dev/null
+++ b/dviware/dvisvgm/src/FontStyle.hpp
@@ -0,0 +1,32 @@
+/*************************************************************************
+** FontStyle.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef FONTSTYLE
+#define FONTSTYLE
+
+struct FontStyle {
+ FontStyle () : bold(0), extend(1), slant(0) {}
+ FontStyle (float b, float e, float s) : bold(b), extend(e), slant(s) {}
+ double bold; ///< stroke width in pt used to draw the glyph outlines
+ double extend; ///< factor to strech/shrink the glyphs horizontally
+ double slant; ///< horizontal slanting/skewing value (= tan(phi))
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/FontWriter.cpp b/dviware/dvisvgm/src/FontWriter.cpp
new file mode 100644
index 0000000000..863b142c5c
--- /dev/null
+++ b/dviware/dvisvgm/src/FontWriter.cpp
@@ -0,0 +1,269 @@
+/*************************************************************************
+** FontWriter.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <array>
+#include "FontWriter.hpp"
+#include "Message.hpp"
+#include "utility.hpp"
+
+using namespace std;
+
+bool FontWriter::AUTOHINT_FONTS = false;
+
+const array<FontWriter::FontFormatInfo, 4> FontWriter::_formatInfos {{
+ {FontWriter::FontFormat::SVG, "image/svg+xml", "svg", "svg"},
+ {FontWriter::FontFormat::TTF, "application/x-font-ttf", "ttf", "truetype"},
+ {FontWriter::FontFormat::WOFF, "application/x-font-woff", "woff", "woff"},
+ {FontWriter::FontFormat::WOFF2, "application/x-font-woff2", "woff2", "woff2"},
+}};
+
+
+/** Returns the corresponding FontFormat for a given format name (e.g. "svg", "woff" etc.). */
+FontWriter::FontFormat FontWriter::toFontFormat (string formatstr) {
+ formatstr = util::tolower(formatstr);
+ auto it = find_if(_formatInfos.begin(), _formatInfos.end(), [&](const FontFormatInfo &info) {
+ return info.formatstr_short == formatstr;
+ });
+ return it != _formatInfos.end() ? it->format : FontFormat::UNKNOWN;
+}
+
+
+/** Returns the corresponding FontFormatInfo for a given FontFormat. */
+const FontWriter::FontFormatInfo* FontWriter::fontFormatInfo (FontFormat format) {
+ auto it = find_if(_formatInfos.begin(), _formatInfos.end(), [&](const FontFormatInfo &info) {
+ return info.format == format;
+ });
+ return it != _formatInfos.end() ? &(*it) : nullptr;
+}
+
+
+/** Returns the names of all supported font formats. */
+vector<string> FontWriter::supportedFormats () {
+ vector<string> formats;
+ for (const FontFormatInfo &info : _formatInfos)
+ formats.emplace_back(info.formatstr_short);
+ return formats;
+}
+
+
+#include <config.h>
+
+#ifdef DISABLE_WOFF
+// dummy functions used if WOFF support is disabled
+FontWriter::FontWriter (const PhysicalFont &font) : _font(font) {}
+std::string FontWriter::createFontFile (FontFormat format, const set<int> &charcodes, GFGlyphTracer::Callback *cb) const {return "";}
+bool FontWriter::writeCSSFontFace (FontFormat format, const set<int> &charcodes, ostream &os, GFGlyphTracer::Callback *cb) const {return false;}
+#else
+#include <cmath>
+#include <fstream>
+#include <iomanip>
+#include <sstream>
+#include <woff2/encode.h>
+#include "ffwrapper.h"
+#include "Bezier.hpp"
+#include "FileSystem.hpp"
+#include "Font.hpp"
+#include "Glyph.hpp"
+#include "TTFAutohint.hpp"
+#include "TrueTypeFont.hpp"
+
+
+FontWriter::FontWriter (const PhysicalFont &font) : _font(font) {
+ static bool initialized=false;
+ if (!initialized) {
+ ff_init();
+ initialized = true;
+ }
+}
+
+
+struct SFDActions : Glyph::Actions {
+ explicit SFDActions (ostream &os) : _os(os) {}
+ void draw (char cmd, const Glyph::Point *points, int n) override {
+ if (cmd == 'Q') {
+ // convert quadratic Bézier curve to cubic one
+ DPair p0(_currentPoint.x(), _currentPoint.y());
+ DPair p1(points[0].x(), points[0].y());
+ DPair p2(points[1].x(), points[1].y());
+ Bezier bezier(p0, p1, p2);
+ for (int i=1; i < 4; i++)
+ _os << lround(bezier.point(i).x()) << ' ' << lround(bezier.point(i).y()) << ' ';
+ _os << 'c';
+ }
+ else {
+ for (int i=0; i < n; i++)
+ _os << points[i].x() << ' ' << points[i].y() << ' ';
+ switch (cmd) {
+ case 'M': _os << 'm'; _startPoint = points[0]; break;
+ case 'L': _os << 'l'; break;
+ case 'C': _os << 'c'; break;
+ case 'Z': _os << _startPoint.x() << ' ' << _startPoint.y() << " l"; _currentPoint = _startPoint; break;
+ }
+ }
+ if (n > 0)
+ _currentPoint = points[n-1];
+ _os << " 0\n";
+ }
+ ostream &_os;
+ Glyph::Point _startPoint, _currentPoint;
+};
+
+
+/** Creates a Spline Font Database (SFD) file describing the font and its glyphs.
+ * https://fontforge.github.io/sfdformat.html */
+static void writeSFD (const string &sfdname, const PhysicalFont &font, const set<int> &charcodes, GFGlyphTracer::Callback *cb) {
+ ofstream sfd(sfdname);
+ if (!sfd)
+ throw FontWriterException("failed writing SFD file "+sfdname);
+
+ sfd <<
+ "SplineFontDB: 3.0\n"
+ "FontName: " << font.name() << '\n';
+
+ // ensure that the sum of the SFD's Ascent and Descent values equals the font's units per EM
+ double yext = font.ascent()+font.descent();
+ double scale = double(font.unitsPerEm())/(yext != 0 ? yext : fabs(font.ascent()));
+ sfd <<
+ "Ascent: " << font.ascent()*scale << "\n"
+ "Descent: " << font.descent()*scale << "\n"
+ "LayerCount: 2\n" // number of layers must be 2 at least
+ "Layer: 0 0 \"Back\" 1\n" // layer 0: background layer with cubic splines
+ "Layer: 1 0 \"Fore\" 0\n" // layer 1: foreground layer with cubic splines
+ "Encoding: UnicodeFull\n" // character codes can use the full Unicode range
+ "BeginChars: 1114112 " << charcodes.size() << '\n';
+
+ double extend = font.style() ? font.style()->extend : 1;
+ for (int c : charcodes) {
+ string name = font.glyphName(c);
+ if (name.empty()) {
+ // if the font doesn't provide glyph names, use AGL name uFOO
+ ostringstream oss;
+ oss << 'u' << hex << uppercase << setw(4) << setfill('0') << c;
+ name = oss.str();
+ }
+ uint32_t codepoint = font.unicode(c);
+ sfd <<
+ "StartChar: " << name << "\n"
+ "Encoding: " << codepoint << ' ' << codepoint << " 0\n"
+ "Width: " << font.hAdvance(c)*extend << "\n"
+ "VWidth: " << font.vAdvance(c) << "\n"
+ "Fore\n"
+ "SplineSet\n";
+ Glyph glyph;
+ if (font.getGlyph(c, glyph, cb)) {
+ SFDActions actions(sfd);
+ glyph.iterate(actions, false);
+ }
+ sfd <<
+ "EndSplineSet\n"
+ "EndChar\n";
+ }
+ sfd.flush();
+ sfd.close();
+ if (sfd.fail())
+ throw FontWriterException("failed writing SFD file "+sfdname);
+}
+
+
+bool FontWriter::createTTFFile (const string &sfdname, const string &ttfname) const {
+ TTFAutohint autohinter;
+ if (!autohinter.available())
+ return ff_sfd_to_ttf(sfdname.c_str(), ttfname.c_str(), AUTOHINT_FONTS);
+
+ bool ok = ff_sfd_to_ttf(sfdname.c_str(), ttfname.c_str(), false);
+ if (ok && AUTOHINT_FONTS) {
+ string tmpname = ttfname+"-ah";
+ int errnum = autohinter.autohint(ttfname, tmpname, true);
+ if (errnum) {
+ Message::wstream(true) << "failed to autohint font '" << _font.name() << "'";
+ string msg = autohinter.lastErrorMessage();
+ if (!msg.empty())
+ Message::wstream() << " (" << msg << ")";
+ // keep the unhinted TTF
+ FileSystem::remove(tmpname);
+ }
+ else {
+ FileSystem::remove(ttfname);
+ FileSystem::rename(tmpname, ttfname);
+ }
+ }
+ return ok;
+}
+
+
+/** Creates a font file containing a given set of glyphs mapped to their Unicode points.
+ * @param[in] format target font format
+ * @param[in] charcodes character codes of the glyphs to be considered
+ * @param[in] cb callback object that allows to react to events triggered by the glyph tracer
+ * @return name of the created font file */
+string FontWriter::createFontFile (FontFormat format, const set<int> &charcodes, GFGlyphTracer::Callback *cb) const {
+ string tmpdir = FileSystem::tmpdir();
+ string basename = tmpdir+_font.name()+"-tmp";
+ string sfdname = basename+".sfd";
+ writeSFD(sfdname, _font, charcodes, cb);
+ string ttfname = basename+".ttf";
+ string targetname = basename+"."+fontFormatInfo(format)->formatstr_short;
+ bool ok = createTTFFile(sfdname, ttfname);
+ if (ok) {
+ if (format == FontFormat::WOFF || format == FontFormat::WOFF2) {
+ TrueTypeFont ttf(ttfname);
+ if (format == FontFormat::WOFF)
+ ttf.writeWOFF(targetname);
+ else
+ ok = ttf.writeWOFF2(targetname);
+ if (!PhysicalFont::KEEP_TEMP_FILES)
+ FileSystem::remove(ttfname);
+ }
+ }
+ if (!PhysicalFont::KEEP_TEMP_FILES)
+ FileSystem::remove(sfdname);
+ if (!ok)
+ throw FontWriterException("failed writing "+string(fontFormatInfo(format)->formatstr_short)+ " file " + targetname);
+ return targetname;
+}
+
+
+/** Writes a CSS font-face rule to an output stream that references or contains the WOFF/TTF font data.
+ * @param[in] format target font format
+ * @param[in] charcodes character codes of the glyphs to be considered
+ * @param[in] os stream the CSS data is written to
+ * @param[in] cb callback object that allows to react to events triggered by the glyph tracer
+ * @return true on success */
+bool FontWriter::writeCSSFontFace (FontFormat format, const set<int> &charcodes, ostream &os, GFGlyphTracer::Callback *cb) const {
+ if (const FontFormatInfo *info = fontFormatInfo(format)) {
+ string filename = createFontFile(format, charcodes, cb);
+ ifstream ifs(filename, ios::binary);
+ if (ifs) {
+ os << "@font-face{"
+ << "font-family:" << _font.name() << ';'
+ << "src:url(data:" << info->mimetype << ";base64,";
+ util::base64_copy(istreambuf_iterator<char>(ifs), istreambuf_iterator<char>(), ostreambuf_iterator<char>(os));
+ os << ") format('" << info->formatstr_long << "');}\n";
+ ifs.close();
+ if (!PhysicalFont::KEEP_TEMP_FILES)
+ FileSystem::remove(filename);
+ return true;
+ }
+ }
+ return false;
+}
+
+#endif // !DISABLE_WOFF
diff --git a/dviware/dvisvgm/src/FontWriter.hpp b/dviware/dvisvgm/src/FontWriter.hpp
new file mode 100644
index 0000000000..458952cad9
--- /dev/null
+++ b/dviware/dvisvgm/src/FontWriter.hpp
@@ -0,0 +1,66 @@
+/*************************************************************************
+** FontWriter.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef FONTWRITER_HPP
+#define FONTWRITER_HPP
+
+#include <ostream>
+#include <set>
+#include <string>
+#include <vector>
+#include "GFGlyphTracer.hpp"
+#include "MessageException.hpp"
+
+class PhysicalFont;
+
+class FontWriter {
+ public:
+ enum class FontFormat {UNKNOWN, SVG, TTF, WOFF, WOFF2};
+ static bool AUTOHINT_FONTS;
+
+ public:
+ explicit FontWriter (const PhysicalFont &font);
+ std::string createFontFile (FontFormat format, const std::set<int> &charcodes, GFGlyphTracer::Callback *cb=nullptr) const;
+ bool writeCSSFontFace (FontFormat format, const std::set<int> &charcodes, std::ostream &os, GFGlyphTracer::Callback *cb=nullptr) const;
+ static FontFormat toFontFormat (std::string formatstr);
+ static std::vector<std::string> supportedFormats ();
+
+ protected:
+ struct FontFormatInfo {
+ FontWriter::FontFormat format;
+ const char *mimetype;
+ const char *formatstr_short;
+ const char *formatstr_long;
+ };
+ static const FontFormatInfo* fontFormatInfo (FontFormat format);
+ bool createTTFFile (const std::string &sfdname, const std::string &ttfname) const;
+
+ private:
+ const PhysicalFont &_font;
+ static const std::array<FontFormatInfo, 4> _formatInfos;
+};
+
+
+struct FontWriterException : MessageException {
+ explicit FontWriterException (const std::string &msg) : MessageException(msg) {}
+};
+
+#endif
+
diff --git a/dviware/dvisvgm/src/GFGlyphTracer.cpp b/dviware/dvisvgm/src/GFGlyphTracer.cpp
new file mode 100644
index 0000000000..85f7c2cab5
--- /dev/null
+++ b/dviware/dvisvgm/src/GFGlyphTracer.cpp
@@ -0,0 +1,91 @@
+/*************************************************************************
+** GFGlyphTracer.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include "GFGlyphTracer.hpp"
+
+using namespace std;
+
+GFGlyphTracer::GFGlyphTracer () : GFTracer(_ifs, 0)
+{
+}
+
+/** Constructs a new glyph tracer.
+ * @param[in] is GF input stream
+ * @param[in] upp target units per PS point */
+GFGlyphTracer::GFGlyphTracer (string &fname, double upp, Callback *cb)
+ : GFTracer(_ifs, upp), _callback(cb)
+{
+ if (_callback)
+ _callback->setFont(fname);
+ _ifs.open(fname, ios::binary);
+}
+
+
+void GFGlyphTracer::reset (string &fname, double upp) {
+ if (_callback)
+ _callback->setFont(fname);
+ if (_ifs.is_open())
+ _ifs.close();
+ unitsPerPoint(upp);
+ _ifs.open(fname, ios::binary);
+}
+
+
+bool GFGlyphTracer::executeChar (uint8_t c) {
+ if (!_glyph)
+ return false;
+
+ if (_callback)
+ _callback->beginChar(c);
+ bool ok = GFTracer::executeChar(c);
+ if (_callback) {
+ if (ok)
+ _callback->endChar(c);
+ else
+ _callback->emptyChar(c);
+ }
+ return ok;
+}
+
+
+void GFGlyphTracer::moveTo (double x, double y) {
+ _glyph->moveto(int(x), int(y));
+}
+
+
+void GFGlyphTracer::lineTo (double x, double y) {
+ _glyph->lineto(int(x), int(y));
+}
+
+
+void GFGlyphTracer::curveTo (double c1x, double c1y, double c2x, double c2y, double x, double y) {
+ _glyph->cubicto(int(c1x), int(c1y), int(c2x), int(c2y), int(x), int(y));
+}
+
+
+void GFGlyphTracer::closePath () {
+ _glyph->closepath();
+}
+
+
+void GFGlyphTracer::endChar (uint32_t c) {
+ _glyph->clear();
+ GFTracer::endChar(c);
+}
diff --git a/dviware/dvisvgm/src/GFGlyphTracer.hpp b/dviware/dvisvgm/src/GFGlyphTracer.hpp
new file mode 100644
index 0000000000..49f69b69ec
--- /dev/null
+++ b/dviware/dvisvgm/src/GFGlyphTracer.hpp
@@ -0,0 +1,60 @@
+/*************************************************************************
+** GFGlyphTracer.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef GFGLYPHTRACER_HPP
+#define GFGLYPHTRACER_HPP
+
+#include <fstream>
+#include <string>
+#include "GFTracer.hpp"
+#include "Glyph.hpp"
+
+class GFGlyphTracer : public GFTracer
+{
+ public:
+ struct Callback {
+ virtual ~Callback () =default;
+ virtual void setFont (const std::string &fontname) {}
+ virtual void beginChar (uint8_t c) {}
+ virtual void endChar (uint8_t c) {}
+ virtual void emptyChar (uint8_t c) {}
+ };
+
+ public:
+ GFGlyphTracer ();
+ GFGlyphTracer (std::string &fname, double upp, Callback *cb=nullptr);
+ void reset (std::string &fname, double upp);
+ void setCallback (Callback *cb) {_callback = cb;}
+ bool executeChar (uint8_t c) override;
+ void moveTo (double x, double y) override;
+ void lineTo (double x, double y) override;
+ void curveTo (double c1x, double c1y, double c2x, double c2y, double x, double y) override;
+ void closePath () override;
+ void endChar (uint32_t c) override;
+ void setGlyph (Glyph &glyph) {_glyph = &glyph;}
+ const Glyph& getGlyph () const {return *_glyph;}
+
+ private:
+ std::ifstream _ifs;
+ Glyph *_glyph = nullptr;
+ Callback *_callback = nullptr;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/GFReader.cpp b/dviware/dvisvgm/src/GFReader.cpp
new file mode 100644
index 0000000000..3cba8e5069
--- /dev/null
+++ b/dviware/dvisvgm/src/GFReader.cpp
@@ -0,0 +1,359 @@
+/*************************************************************************
+** GFReader.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <sstream>
+#include "FixWord.hpp"
+#include "GFReader.hpp"
+#include "Length.hpp"
+#include "SignalHandler.hpp"
+
+using namespace std;
+
+
+struct GFCommand {
+ void (GFReader::*method)(int);
+ int numBytes;
+};
+
+
+/** Converts a scaled value to double */
+static inline double scaled2double (int32_t scaled) {
+ return double(scaled)/(1 << 16);
+}
+
+
+GFReader::GFReader (istream &is) : _in(is), _insideCharDef(false), _penDown(false)
+{
+ _minX = _maxX = _minY = _maxY = _x = _y = 0;
+ _currentChar = 0;
+ _designSize = 0;
+ _hppp = _vppp = 0;
+ _checksum = 0;
+}
+
+
+uint32_t GFReader::readUnsigned (int bytes) {
+ uint32_t ret = 0;
+ for (int i=bytes-1; i >= 0 && !_in.eof(); i--) {
+ uint32_t b = _in.get();
+ ret |= b << (8*i);
+ }
+ return ret;
+}
+
+
+int32_t GFReader::readSigned (int bytes) {
+ uint32_t ret = _in.get();
+ if (ret & 128) // negative value?
+ ret |= 0xffffff00;
+ for (int i=bytes-2; i >= 0 && !_in.eof(); i--)
+ ret = (ret << 8) | _in.get();
+ return int32_t(ret);
+}
+
+
+string GFReader::readString (int bytes) {
+ bytes = max(0, bytes);
+ string str(bytes, '\0');
+ _in.read(&str[0], bytes);
+ return str;
+}
+
+
+/** Reads a single GF command from the current position of the input stream and calls the
+ * corresponding cmdFOO method.
+ * @return opcode of the executed command */
+int GFReader::executeCommand () {
+ SignalHandler::instance().check();
+ /* Each cmdFOO command reads the necessary number of bytes from the stream so executeCommand
+ doesn't need to know the exact GF command format. Some cmdFOO methods are used for multiple
+ GF commands because they only differ in the size of their parameters. */
+ static const GFCommand commands[] = {
+ {&GFReader::cmdPaint, 1}, {&GFReader::cmdPaint, 2}, {&GFReader::cmdPaint, 3}, // 64-66
+ {&GFReader::cmdBoc, 0}, {&GFReader::cmdBoc1, 0}, // 67-68
+ {&GFReader::cmdEoc, 0}, // 69
+ {&GFReader::cmdSkip, 0}, {&GFReader::cmdSkip, 1}, {&GFReader::cmdSkip, 2},{&GFReader::cmdSkip, 3}, // 70-73
+ {&GFReader::cmdXXX, 1}, {&GFReader::cmdXXX, 2}, {&GFReader::cmdXXX, 3}, {&GFReader::cmdXXX, 4}, // 239-242
+ {&GFReader::cmdYYY, 0}, // 243
+ {&GFReader::cmdNop, 0}, // 244
+ {&GFReader::cmdCharLoc, 0}, {&GFReader::cmdCharLoc0, 0}, // 245-246
+ {&GFReader::cmdPre, 0}, {&GFReader::cmdPost, 0}, {&GFReader::cmdPostPost, 0} // 247-249
+ };
+
+ int opcode = _in.get();
+ if (opcode < 0) // at end of file?
+ throw GFException("unexpected end of file");
+
+ if (opcode >= 0 && opcode <= 63)
+ cmdPaint0(opcode);
+ else if (opcode >= 74 && opcode <= 238)
+ cmdNewRow(opcode-74);
+ else if (opcode >= 250) {
+ ostringstream oss;
+ oss << "undefined GF command (opcode " << opcode << ")";
+ throw GFException(oss.str());
+ }
+ else {
+ int offset = opcode <= 73 ? 64 : 239-(73-64+1);
+ const GFCommand &cmd = commands[opcode-offset];
+ if (cmd.method)
+ (this->*cmd.method)(cmd.numBytes);
+ }
+ return opcode;
+}
+
+
+bool GFReader::executeChar (uint8_t c) {
+ _in.clear();
+ if (_charInfoMap.empty())
+ executePostamble(); // read character info
+ _in.clear();
+ auto it = _charInfoMap.find(c);
+ if (_in && it != _charInfoMap.end()) {
+ _in.seekg(it->second.location);
+ while (executeCommand() != 69); // execute all commands until eoc is reached
+ return true;
+ }
+ return false;
+}
+
+
+bool GFReader::executeAllChars () {
+ _in.clear();
+ if (_charInfoMap.empty())
+ executePostamble(); // read character info
+ _in.clear();
+ if (_in) {
+ _in.seekg(0);
+ while (executeCommand() != 248); // execute all commands until postamble is reached
+ return true;
+ }
+ return false;
+}
+
+
+bool GFReader::executePreamble () {
+ _in.clear();
+ if (!_in)
+ return false;
+ _in.seekg(0);
+ executeCommand();
+ return true;
+}
+
+
+bool GFReader::executePostamble () {
+ _in.clear();
+ if (!_in)
+ return false;
+ _in.seekg(-1, ios::end);
+ while (_in.peek() == 223) // skip fill bytes
+ _in.seekg(-1, ios::cur);
+ if (_in.peek() != 131)
+ throw GFException("invalid identification byte in postpost");
+ _in.seekg(-5, ios::cur); // now on postpost
+ if (_in.get() != 249)
+ throw GFException("invalid GF file");
+ uint32_t q = readUnsigned(4); // pointer to begin of postamble
+ _in.seekg(q); // now on begin of postamble
+ while (executeCommand() != 249); // execute all commands until postpost is reached
+ return true;
+}
+
+
+/** Returns the width of character c in PS point units */
+double GFReader::getCharWidth (uint32_t c) const {
+ auto it = _charInfoMap.find(c%256);
+ return it == _charInfoMap.end() ? 0 : it->second.width*getDesignSize()/(1<<24);
+}
+
+///////////////////////
+
+
+/** Reads the preamble. */
+void GFReader::cmdPre (int) {
+ uint32_t i = readUnsigned(1);
+ if (i != 131)
+ throw GFException("invalid identification byte in preamble");
+ else {
+ uint32_t k = readUnsigned(1);
+ string s = readString(k);
+ preamble(s);
+ }
+}
+
+
+/** Reads the postamble. */
+void GFReader::cmdPost (int) {
+ readUnsigned(4); // pointer to byte after final eoc
+ _designSize = double(FixWord(readUnsigned(4)))*Length::pt2bp; // design size of font in points
+ _checksum = readUnsigned(4); // checksum
+ _hppp = scaled2double(readUnsigned(4))/Length::pt2bp; // horizontal pixels per PS point
+ _vppp = scaled2double(readUnsigned(4))/Length::pt2bp; // vertical pixels per PS point
+ _in.seekg(16, ios::cur); // skip x and y bounds
+ postamble();
+}
+
+
+/** Reads trailing bytes at end of stream. */
+void GFReader::cmdPostPost (int) {
+ readUnsigned(4); // pointer to begin of postamble
+ uint32_t i = readUnsigned(1);
+ if (i != 131)
+ throw GFException("invalid identification byte in postpost");
+ while (readUnsigned(1) == 223); // skip fill bytes
+}
+
+
+/** Inverts "paint color" (black to white or vice versa) of n pixels
+ * and advances the cursor by n.
+ * @param[in] n number of pixels to be inverted */
+void GFReader::cmdPaint0 (int n) {
+ if (!_insideCharDef)
+ throw GFException("character-related command outside BOC and EOC");
+ if (_penDown) // set pixels?
+ _bitmap.setBits(_y, _x, n);
+ _x += n;
+ _penDown = !_penDown; // invert pen state
+}
+
+
+/** Inverts "paint color" (black to white or vice versa) of n pixels
+ * and advances the cursor by n. The number n of pixels is read from
+ * the input stream.
+ * @param[in] len size of n in bytes */
+void GFReader::cmdPaint (int len) {
+ uint32_t pixels = readUnsigned(len);
+ cmdPaint0(pixels);
+}
+
+
+/** Beginning of character (generic format). */
+void GFReader::cmdBoc (int) {
+ _currentChar = readSigned(4);
+ readSigned(4); // pointer to previous boc with same c mod 256
+ _minX = readSigned(4);
+ _maxX = readSigned(4);
+ _minY = readSigned(4);
+ _maxY = readSigned(4);
+ _x = _minX;
+ _y = _maxY;
+ _penDown = false;
+ _bitmap.resize(_minX, _maxX, _minY, _maxY);
+ _insideCharDef = true;
+ beginChar(_currentChar);
+}
+
+
+/** Beginning of character (compact format). */
+void GFReader::cmdBoc1 (int) {
+ _currentChar = readUnsigned(1);
+ uint32_t dx = readUnsigned(1);
+ _maxX = readUnsigned(1);
+ _minX = _maxX - dx;
+ uint32_t dy = readUnsigned(1);
+ _maxY = readUnsigned(1);
+ _minY = _maxY - dy;
+ _x = _minX;
+ _y = _maxY;
+ _penDown = false;
+ _bitmap.resize(_minX, _maxX, _minY, _maxY);
+ _insideCharDef = true;
+ beginChar(_currentChar);
+}
+
+
+/** End of character. */
+void GFReader::cmdEoc (int) {
+ if (!_insideCharDef)
+ throw GFException("misplaced EOC");
+ _insideCharDef = false;
+ endChar(_currentChar);
+}
+
+
+/** Moves cursor to the beginning of a following row and sets
+ * paint color to white.
+ * @param[in] len if 0: move to next row, otherwise: number of bytes to read.
+ * The read value denotes the number of rows to be skipped. */
+void GFReader::cmdSkip (int len) {
+ if (!_insideCharDef)
+ throw GFException("character-related command outside BOC and EOC");
+ if (len == 0)
+ _y--;
+ else
+ _y -= readUnsigned(len)+1;
+ _x = _minX;
+ _penDown = false;
+}
+
+
+/** Moves cursor to pixel number 'col' in the next row and sets
+ * the paint color to black.
+ * @param[in] col pixel/column number */
+void GFReader::cmdNewRow (int col) {
+ if (!_insideCharDef)
+ throw GFException("character-related command outside BOC and EOC");
+ _x = _minX + col ;
+ _y--;
+ _penDown = true;
+}
+
+
+void GFReader::cmdXXX (int len) {
+ uint32_t n = readUnsigned(len);
+ string str = readString(n);
+ special(str);
+}
+
+
+void GFReader::cmdYYY (int) {
+ int32_t y = readSigned(4);
+ numspecial(y);
+}
+
+
+/** Does nothing. */
+void GFReader::cmdNop (int) {
+}
+
+
+/** Reads character locator (part of postamble) */
+void GFReader::cmdCharLoc0 (int) {
+ uint8_t c = readUnsigned(1); // character code mod 256
+ uint8_t dm = readUnsigned(1); // = dx/65536
+ int32_t w = readSigned(4); // (1<<24)*characterWidth/designSize
+ int32_t p = readSigned(4); // pointer to begin of (last) character data
+ int32_t dx = 65536*dm; // horizontal escapement
+ int32_t dy = 0; // vertical escapement
+ _charInfoMap.emplace(c, CharInfo(dx, dy, w, p));
+}
+
+
+/** Reads character locator (part of postamble) */
+void GFReader::cmdCharLoc (int) {
+ uint8_t c = readUnsigned(1); // character code mod 256
+ int32_t dx = readSigned(4); // horizontal escapement (scaled pixel units)
+ int32_t dy = readSigned(4); // vertical escapement (scaled pixel units)
+ int32_t w = readSigned(4); // (1<<24)*characterWidth/designSize
+ int32_t p = readSigned(4); // pointer to begin of (last) character data
+ _charInfoMap.emplace(c, CharInfo(dx, dy, w, p));
+}
diff --git a/dviware/dvisvgm/src/GFReader.hpp b/dviware/dvisvgm/src/GFReader.hpp
new file mode 100644
index 0000000000..3dfceb98cd
--- /dev/null
+++ b/dviware/dvisvgm/src/GFReader.hpp
@@ -0,0 +1,106 @@
+/*************************************************************************
+** GFReader.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef GFREADER_HPP
+#define GFREADER_HPP
+
+#include <istream>
+#include <string>
+#include <unordered_map>
+#include "Bitmap.hpp"
+#include "MessageException.hpp"
+
+
+class CharInfo;
+
+
+struct GFException : public MessageException {
+ explicit GFException (const std::string &msg) : MessageException(msg) {}
+};
+
+
+class GFReader {
+ struct CharInfo {
+ CharInfo () : dx(0), dy(0), width(0), location(0) {}
+ CharInfo (int32_t dxx, int32_t dyy, int32_t w, uint32_t p) : dx(dxx), dy(dyy), width(w), location(p) {}
+
+ int32_t dx, dy;
+ int32_t width; // 2^24 * (true width)/(design size)
+ uint32_t location;
+ };
+
+ public:
+ explicit GFReader (std::istream &is);
+ virtual ~GFReader () =default;
+ virtual bool executeChar (uint8_t c);
+ virtual bool executeAllChars ();
+ bool executePreamble ();
+ bool executePostamble ();
+ virtual void preamble (const std::string &str) {}
+ virtual void postamble () {}
+ virtual void beginChar (uint32_t c) {}
+ virtual void endChar (uint32_t c) {}
+ virtual void special (const std::string &str) {}
+ virtual void numspecial (int32_t y) {}
+ const Bitmap& getBitmap () const {return _bitmap;}
+ double getDesignSize () const {return _designSize;}
+ double getHPixelsPerPoint () const {return _hppp;}
+ double getVPixelsPerPoint () const {return _vppp;};
+ double getCharWidth (uint32_t c) const;
+ uint32_t getChecksum () const {return _checksum;}
+
+ protected:
+ int32_t readSigned (int bytes);
+ uint32_t readUnsigned (int bytes);
+ std::string readString (int len);
+ int executeCommand ();
+ std::istream& getInputStream () const {return _in;}
+
+ void cmdPre (int);
+ void cmdPost (int);
+ void cmdPostPost (int);
+ void cmdPaint0 (int pixels);
+ void cmdPaint (int len);
+ void cmdBoc (int);
+ void cmdBoc1 (int);
+ void cmdEoc (int);
+ void cmdSkip (int len);
+ void cmdNewRow (int col);
+ void cmdXXX (int len);
+ void cmdYYY (int);
+ void cmdNop (int);
+ void cmdCharLoc0 (int);
+ void cmdCharLoc (int);
+
+ private:
+ std::istream &_in;
+ int32_t _minX, _maxX, _minY, _maxY;
+ int32_t _x, _y; ///< current pen location (pixel units)
+ int32_t _currentChar;
+ Bitmap _bitmap; ///< bitmap of current char
+ double _designSize; ///< designSize in PS points
+ double _hppp, _vppp; ///< horizontal and vertical pixel per point
+ uint32_t _checksum;
+ std::unordered_map<uint8_t,CharInfo> _charInfoMap;
+ bool _insideCharDef; ///< true if inside a character definition (between BOC and EOC)
+ bool _penDown;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/GFTracer.cpp b/dviware/dvisvgm/src/GFTracer.cpp
new file mode 100644
index 0000000000..61d297d982
--- /dev/null
+++ b/dviware/dvisvgm/src/GFTracer.cpp
@@ -0,0 +1,93 @@
+/*************************************************************************
+** GFTracer.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <fstream>
+#include <potracelib.h>
+#include "GFTracer.hpp"
+#include "Glyph.hpp"
+#include "Message.hpp"
+
+using namespace std;
+
+
+GFTracer::GFTracer (istream &is)
+ : GFReader(is), _unitsPerPoint(0.0)
+{
+}
+
+
+/** Constructs a new GFTracer.
+ * @param[in] is GF file is read from this stream
+ * @param[in] upp target units per PS point */
+GFTracer::GFTracer (istream &is, double upp)
+ : GFReader(is), _unitsPerPoint(upp)
+{
+}
+
+
+void GFTracer::beginChar (uint32_t c) {
+}
+
+
+void GFTracer::endChar (uint32_t c) {
+ const Bitmap &bitmap = getBitmap();
+ if (bitmap.empty())
+ return;
+
+ // prepare potrace's bitmap structure
+ vector<potrace_word> buffer;
+ potrace_bitmap_t pobitmap;
+ pobitmap.w = bitmap.width();
+ pobitmap.h = bitmap.height();
+ pobitmap.dy = bitmap.copy(buffer);
+ pobitmap.map = &buffer[0];
+ potrace_param_t *param = potrace_param_default();
+ potrace_state_t *state = potrace_trace(param, &pobitmap);
+ potrace_param_free(param);
+
+ if (!state || state->status == POTRACE_STATUS_INCOMPLETE)
+ Message::wstream(true) << "error while tracing character\n";
+ else {
+ double hsf=1.0, vsf=1.0; // horizontal a d vertical scale factor
+ if (_unitsPerPoint != 0.0) {
+ hsf = _unitsPerPoint/getHPixelsPerPoint(); // horizontal scale factor
+ vsf = _unitsPerPoint/getVPixelsPerPoint(); // vertical scale factor
+ }
+ for (potrace_path_t *path = state->plist; path; path = path->next) {
+ potrace_dpoint_t &p = path->curve.c[path->curve.n-1][2]; // start/end point
+ moveTo(hsf*(p.x+bitmap.xshift()), vsf*(p.y+bitmap.yshift()));
+ for (int i=0; i < path->curve.n; i++) {
+ if (path->curve.tag[i] == POTRACE_CURVETO) {
+ curveTo(hsf*(path->curve.c[i][0].x+bitmap.xshift()), vsf*(path->curve.c[i][0].y+bitmap.yshift()),
+ hsf*(path->curve.c[i][1].x+bitmap.xshift()), vsf*(path->curve.c[i][1].y+bitmap.yshift()),
+ hsf*(path->curve.c[i][2].x+bitmap.xshift()), vsf*(path->curve.c[i][2].y+bitmap.yshift()));
+ }
+ else {
+ lineTo(hsf*(path->curve.c[i][1].x+bitmap.xshift()), vsf*(path->curve.c[i][1].y+bitmap.yshift()));
+ if (i == path->curve.n-1)
+ closePath();
+ else
+ lineTo(hsf*(path->curve.c[i][2].x+bitmap.xshift()), vsf*(path->curve.c[i][2].y+bitmap.yshift()));
+ }
+ }
+ }
+ }
+ potrace_state_free(state);
+}
diff --git a/dviware/dvisvgm/src/GFTracer.hpp b/dviware/dvisvgm/src/GFTracer.hpp
new file mode 100644
index 0000000000..8fe746cc05
--- /dev/null
+++ b/dviware/dvisvgm/src/GFTracer.hpp
@@ -0,0 +1,45 @@
+/*************************************************************************
+** GFTracer.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef GFTRACER_HPP
+#define GFTRACER_HPP
+
+#include <istream>
+#include "GFReader.hpp"
+
+class GFTracer : public GFReader {
+ public:
+ explicit GFTracer (std::istream &is);
+ GFTracer (std::istream &is, double upp);
+ virtual void moveTo (double x, double y) =0;
+ virtual void lineTo (double x, double y) =0;
+ virtual void curveTo (double c1x, double c1y, double c2x, double c2y, double x, double y) =0;
+ virtual void closePath () =0;
+ void beginChar (uint32_t c) override;
+ void endChar (uint32_t c) override;
+
+ protected:
+ void unitsPerPoint(double upp) {_unitsPerPoint = upp;}
+
+ private:
+ double _unitsPerPoint; ///< target units per PS point
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/Ghostscript.cpp b/dviware/dvisvgm/src/Ghostscript.cpp
new file mode 100644
index 0000000000..c097ace667
--- /dev/null
+++ b/dviware/dvisvgm/src/Ghostscript.cpp
@@ -0,0 +1,382 @@
+/*************************************************************************
+** Ghostscript.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <config.h>
+#include "FilePath.hpp"
+#include "Ghostscript.hpp"
+#include "utility.hpp"
+#if !defined(DISABLE_GS)
+#include <cstring>
+#include <iomanip>
+#include <sstream>
+#if defined(HAVE_LIBGS)
+ #include <ghostscript/ierrors.h>
+#else
+ #include "ierrors.h"
+ #include "FileFinder.hpp"
+#endif
+
+using namespace std;
+
+// name of Ghostscript shared library set by the user
+string Ghostscript::LIBGS_NAME;
+
+#ifndef HAVE_LIBGS
+
+#ifdef _WIN32
+/** Looks up the path of the Ghostscript DLL in the Windows registry and returns it.
+ * If there is no proper registry entry, the returned string is empty. */
+static string get_path_from_registry () {
+#ifdef RRF_RT_REG_SZ // RegGetValueA and RRF_RT_REG_SZ may not be defined for some oldish MinGW
+ REGSAM mode = KEY_READ|KEY_QUERY_VALUE;
+#ifdef KEY_WOW64_64KEY
+#ifdef _WIN64
+ mode |= KEY_WOW64_64KEY;
+#else
+ mode |= KEY_WOW64_32KEY;
+#endif
+#endif
+ for (const char *gs_company : {"GPL", "GNU", "AFPL", "Aladdin"}) {
+ const string reg_path = string("SOFTWARE\\") + gs_company + " Ghostscript";
+ for (HKEY reg_root : {HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}) {
+ HKEY hkey;
+ if (RegOpenKeyExA(reg_root, reg_path.c_str(), 0, mode, &hkey) == ERROR_SUCCESS) {
+ char subkey[16];
+ for (int k=0; RegEnumKeyA(hkey, k, subkey, 16) == ERROR_SUCCESS; k++) {
+ istringstream iss(subkey);
+ int major_version;
+ iss >> major_version;
+ if (major_version >= 7) {
+ char dll_path[512]; // path to Ghostscript DLL stored in the registry
+ DWORD length = 512;
+ if (RegGetValueA(hkey, subkey, "GS_DLL", RRF_RT_REG_SZ, nullptr, dll_path, &length) == ERROR_SUCCESS) {
+ RegCloseKey(hkey);
+ return dll_path;
+ }
+ }
+ }
+ RegCloseKey(hkey);
+ }
+ }
+ }
+#endif // RRF_RT_REG_SZ
+ return "";
+}
+#endif // _WIN32
+
+#if defined(_WIN32) && !defined(_WIN64)
+static string get_gsdll32 () {
+ string pathstr;
+#if defined(TEXLIVEWIN32)
+ char exepath[256];
+ if (GetModuleFileNameA(NULL, exepath, 256)) {
+ FilePath path(exepath);
+ path.set(path.absolute(false)+"/../../tlpkg/tlgs");
+ pathstr = util::replace(path.absolute(false)+"/", "/", "\\");
+ string envvar = "GS_LIB=";
+ for (string dirs : {"lib", "fonts", "Resource\\Init", "Resource", "kanji"})
+ envvar += pathstr+dirs+";";
+ _putenv(envvar.c_str());
+ pathstr += "bin\\";
+ }
+#endif
+ return pathstr+"gsdll32.dll";
+}
+#endif // _WIN32 && !_WIN64
+
+
+/** Try to detect name of the Ghostscript shared library depending on the user settings.
+ * @param[in] fname path/filename given by the user
+ * @return name of Ghostscript shared library */
+static string get_libgs (const string &fname) {
+ if (!fname.empty())
+ return fname;
+#ifdef MIKTEX
+#if defined(_WIN64)
+ const char *gsdll = "mgsdll64.dll";
+#else
+ const char *gsdll = "mgsdll32.dll";
+#endif
+ // try to look up the Ghostscript DLL coming with MiKTeX
+ if (const char *gsdll_path = FileFinder::instance().lookup(gsdll))
+ return gsdll_path;
+#endif // MIKTEX
+#if defined(_WIN32)
+ // try to look up the path of the Ghostscript DLL in the Windows registry
+ string gsdll_path = get_path_from_registry();
+ if (!gsdll_path.empty())
+ return gsdll_path;
+#endif //_WIN32
+#if defined(_WIN64)
+ return "gsdll64.dll";
+#elif defined(_WIN32)
+ return get_gsdll32();
+#else
+ // try to find libgs.so.X on the user's system
+ const int abi_min=7, abi_max=9; // supported libgs ABI versions
+ for (int i=abi_max; i >= abi_min; i--) {
+#if defined(__CYGWIN__)
+ string dlname = "cyggs-" + to_string(i) + ".dll";
+#else
+ string dlname = "libgs.so." + to_string(i);
+#endif
+ DLLoader loader(dlname);
+ if (loader.loaded())
+ return dlname;
+#if defined(__APPLE__)
+ dlname = "libgs." + to_string(i) + ".dylib";
+ if (loader.loadLibrary(dlname))
+ return dlname;
+#endif
+ }
+#endif
+ // no appropriate library found
+ return "";
+}
+#endif // !HAVE_LIBGS
+
+
+/** Loads the Ghostscript library but does not create an instance. This
+ * constructor should only be used to call available() and revision(). */
+Ghostscript::Ghostscript ()
+#if !defined(HAVE_LIBGS)
+ : DLLoader(get_libgs(LIBGS_NAME))
+#endif
+{
+}
+
+
+/** Tries to load the shared library and to initialize Ghostscript.
+ * @param[in] argc number of parameters in array argv
+ * @param[in] argv parameters passed to Ghostscript
+ * @param[in] caller this parameter is passed to all callback functions */
+Ghostscript::Ghostscript (int argc, const char **argv, void *caller)
+#if !defined(HAVE_LIBGS)
+ : DLLoader(get_libgs(LIBGS_NAME))
+#endif
+{
+ init(argc, argv, caller);
+}
+
+
+/** Exits Ghostscript and unloads the dynamic library. */
+Ghostscript::~Ghostscript () {
+ if (_inst) {
+ this->exit();
+ delete_instance();
+ }
+}
+
+
+bool Ghostscript::init (int argc, const char **argv, void *caller) {
+ if (!_inst) {
+ int status = new_instance(&_inst, caller);
+ if (status < 0)
+ _inst = nullptr;
+ else {
+ init_with_args(argc, const_cast<char**>(argv));
+ }
+ }
+ return _inst != nullptr;
+}
+
+
+/** Returns true if Ghostscript library was found and can be loaded. */
+bool Ghostscript::available () {
+#if defined(HAVE_LIBGS)
+ return true;
+#else
+ gsapi_revision_t rev;
+ return loaded() && revision(&rev);
+#endif
+}
+
+
+/** Retrieves version information about Ghostscript.
+ * @param[out] r takes the revision information (see GS API documentation for further details)
+ * @return true on success */
+bool Ghostscript::revision (gsapi_revision_t *r) {
+#if defined(HAVE_LIBGS)
+ return (gsapi_revision(r, sizeof(gsapi_revision_t)) == 0);
+#else
+ if (auto fn = LOAD_SYMBOL(gsapi_revision))
+ return (fn(r, sizeof(gsapi_revision_t)) == 0);
+ return false;
+#endif
+}
+
+
+/** Returns the revision number of the GS library. */
+int Ghostscript::revision () {
+ gsapi_revision_t r;
+ if (revision(&r))
+ return static_cast<int>(r.revision);
+ return 0;
+}
+
+
+/** Returns the revision of the GS library as a string of the form "MAJOR.MINOR". */
+string Ghostscript::revisionstr () {
+ string revstr;
+ if (int rev = revision()) {
+ revstr = to_string(rev/100) + ".";
+ if (rev % 100 < 10)
+ revstr += "0";
+ revstr += to_string(rev%100);
+ }
+ return revstr;
+}
+
+
+/** Creates a new instance of Ghostscript. This method is called by the constructor and
+ * should not be used elsewhere.
+ * @param[out] psinst handle of newly created instance (or 0 on error)
+ * @param[in] caller pointer forwarded to callback functions */
+int Ghostscript::new_instance (void **psinst, void *caller) {
+#if defined(HAVE_LIBGS)
+ return gsapi_new_instance(psinst, caller);
+#else
+ if (auto fn = LOAD_SYMBOL(gsapi_new_instance))
+ return fn(psinst, caller);
+ *psinst = nullptr;
+ return 0;
+#endif
+}
+
+
+/** Destroys the current instance of Ghostscript. This method is called by the destructor
+ * and should not be used elsewhere. */
+void Ghostscript::delete_instance () {
+#if defined(HAVE_LIBGS)
+ gsapi_delete_instance(_inst);
+#else
+ if (auto fn = LOAD_SYMBOL(gsapi_delete_instance))
+ fn(_inst);
+#endif
+}
+
+
+/** Exits the interpreter. Must be called before destroying the GS instance. */
+int Ghostscript::exit () {
+#if defined(HAVE_LIBGS)
+ return gsapi_exit(_inst);
+#else
+ if (auto fn = LOAD_SYMBOL(gsapi_exit))
+ return fn(_inst);
+ return 0;
+#endif
+}
+
+
+/** Sets the I/O callback functions.
+ * @param[in] in pointer to stdin handler
+ * @param[in] out pointer to stdout handler
+ * @param[in] err pointer to stderr handler */
+int Ghostscript::set_stdio (Stdin in, Stdout out, Stderr err) {
+#if defined(HAVE_LIBGS)
+ return gsapi_set_stdio(_inst, in, out, err);
+#else
+ if (auto fn = LOAD_SYMBOL(gsapi_set_stdio))
+ return fn(_inst, in, out, err);
+ return 0;
+#endif
+}
+
+
+/** Initializes Ghostscript with a set of optional parameters. This
+ * method is called by the constructor and should not be used elsewhere.
+ * @param[in] argc number of paramters
+ * @param[in] argv parameters passed to Ghostscript */
+int Ghostscript::init_with_args (int argc, char **argv) {
+#if defined(HAVE_LIBGS)
+ return gsapi_init_with_args(_inst, argc, argv);
+#else
+ if (auto fn = LOAD_SYMBOL(gsapi_init_with_args))
+ return fn(_inst, argc, argv);
+ return 0;
+#endif
+}
+
+
+/** Tells Ghostscript that several calls of run_string_continue will follow. */
+int Ghostscript::run_string_begin (int user_errors, int *pexit_code) {
+#if defined(HAVE_LIBGS)
+ return gsapi_run_string_begin(_inst, user_errors, pexit_code);
+#else
+ if (auto fn = LOAD_SYMBOL(gsapi_run_string_begin))
+ return fn(_inst, user_errors, pexit_code);
+ *pexit_code = 0;
+ return 0;
+#endif
+}
+
+
+/** Executes a chunk of PostScript commands given by a buffer of characters. The size of
+ * this buffer must not exceed 64KB. Longer programs can be split into arbitrary smaller chunks
+ * and passed to Ghostscript by successive calls of run_string_continue.
+ * @param[in] str buffer containing the PostScript code
+ * @param[in] length number of characters in buffer
+ * @param[in] user_errors if non-negative, the default PS error values will be generated, otherwise this value is returned
+ * @param[out] pexit_code takes the PS error code */
+int Ghostscript::run_string_continue (const char *str, unsigned length, int user_errors, int *pexit_code) {
+#if defined(HAVE_LIBGS)
+ return gsapi_run_string_continue(_inst, str, length, user_errors, pexit_code);
+#else
+ if (auto fn = LOAD_SYMBOL(gsapi_run_string_continue))
+ return fn(_inst, str, length, user_errors, pexit_code);
+ *pexit_code = 0;
+ return 0;
+#endif
+}
+
+
+/** Terminates the successive code feeding. Must be called after the last call of run_string_continue. */
+int Ghostscript::run_string_end (int user_errors, int *pexit_code) {
+#if defined(HAVE_LIBGS)
+ return gsapi_run_string_end(_inst, user_errors, pexit_code);
+#else
+ if (auto fn = LOAD_SYMBOL(gsapi_run_string_end))
+ return fn(_inst, user_errors, pexit_code);
+ *pexit_code = 0;
+ return 0;
+#endif
+}
+
+
+const char* Ghostscript::error_name (int code) {
+ if (code < 0)
+ code = -code;
+ const char *error_names[] = { ERROR_NAMES };
+ if (code == 0 || (size_t)code > sizeof(error_names)/sizeof(error_names[0]))
+ return nullptr;
+#if defined(HAVE_LIBGS)
+ // use array defined in libgs to avoid linking the error strings into the binary
+ return gs_error_names[code-1];
+#elif defined(_WIN32)
+ // gs_error_names is private in the Ghostscript DLL so we can't access it here
+ return error_names[code-1];
+#else
+ if (auto error_names = loadSymbol<const char**>("gs_error_names"))
+ return error_names[code-1];
+ return nullptr;
+#endif
+}
+
+#endif // !DISABLE_GS
diff --git a/dviware/dvisvgm/src/Ghostscript.hpp b/dviware/dvisvgm/src/Ghostscript.hpp
new file mode 100644
index 0000000000..4ea62f775f
--- /dev/null
+++ b/dviware/dvisvgm/src/Ghostscript.hpp
@@ -0,0 +1,104 @@
+/*************************************************************************
+** Ghostscript.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef GHOSTSCRIPT_HPP
+#define GHOSTSCRIPT_HPP
+
+#include <config.h>
+#include <string>
+
+#if defined(DISABLE_GS)
+ #include "iapi.h"
+#elif defined(HAVE_LIBGS)
+ #include <ghostscript/iapi.h>
+#else
+ #include "DLLoader.hpp"
+ #include "iapi.h"
+#endif
+
+#if defined(_WIN32) && !defined(_Windows)
+ #define _Windows
+#endif
+
+#if defined(DISABLE_GS)
+struct Ghostscript {
+ using Stdin = int (GSDLLCALLPTR)(void *caller, char *buf, int len);
+ using Stdout = int (GSDLLCALLPTR)(void *caller, const char *str, int len);
+ using Stderr = int (GSDLLCALLPTR)(void *caller, const char *str, int len);
+
+ Ghostscript () {}
+ Ghostscript (int argc, const char **argv, void *caller=0) {}
+ bool init (int argc, const char **argv, void *caller=0) {return false;}
+ bool available () {return false;}
+ bool revision (gsapi_revision_t *r) {return false;}
+ int revision () {return 0;}
+ std::string revisionstr () {return "";}
+ int set_stdio (Stdin in, Stdout out, Stderr err) {return 0;}
+ int run_string_begin (int user_errors, int *pexit_code) {return 0;}
+ int run_string_continue (const char *str, unsigned int length, int user_errors, int *pexit_code) {return 0;}
+ int run_string_end (int user_errors, int *pexit_code) {return 0;}
+ int exit () {return 0;}
+ const char* error_name (int code) {return 0;}
+};
+
+#else
+
+/** Wrapper class of (a subset of) the Ghostscript API. */
+class Ghostscript
+#if !defined(HAVE_LIBGS)
+: public DLLoader
+#endif
+{
+ public:
+ using Stdin = int (GSDLLCALLPTR)(void *caller, char *buf, int len);
+ using Stdout = int (GSDLLCALLPTR)(void *caller, const char *str, int len);
+ using Stderr = int (GSDLLCALLPTR)(void *caller, const char *str, int len);
+
+ public:
+ Ghostscript ();
+ Ghostscript (int argc, const char **argv, void *caller=nullptr);
+ Ghostscript (const Ghostscript &gs) =delete;
+ ~Ghostscript ();
+ bool init (int argc, const char **argv, void *caller=nullptr);
+ bool available ();
+ bool revision (gsapi_revision_t *r);
+ int revision ();
+ std::string revisionstr ();
+ int set_stdio (Stdin in, Stdout out, Stderr err);
+ int run_string_begin (int user_errors, int *pexit_code);
+ int run_string_continue (const char *str, unsigned int length, int user_errors, int *pexit_code);
+ int run_string_end (int user_errors, int *pexit_code);
+ int exit ();
+ const char* error_name (int code);
+
+ static std::string LIBGS_NAME;
+
+ protected:
+ int init_with_args (int argc, char **argv);
+ int new_instance (void **psinst, void *caller);
+ void delete_instance ();
+
+ private:
+ void *_inst = nullptr; ///< Ghostscript handle needed to call the gsapi_foo functions
+};
+
+#endif // DISABLE_GS
+
+#endif
diff --git a/dviware/dvisvgm/src/Glyph.hpp b/dviware/dvisvgm/src/Glyph.hpp
new file mode 100644
index 0000000000..6f457a778f
--- /dev/null
+++ b/dviware/dvisvgm/src/Glyph.hpp
@@ -0,0 +1,28 @@
+/*************************************************************************
+** Glyph.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef GLYPH_HPP
+#define GLYPH_HPP
+
+#include "GraphicsPath.hpp"
+
+using Glyph = GraphicsPath<int32_t>;
+
+#endif
diff --git a/dviware/dvisvgm/src/GlyphTracerMessages.hpp b/dviware/dvisvgm/src/GlyphTracerMessages.hpp
new file mode 100644
index 0000000000..8a41b811d9
--- /dev/null
+++ b/dviware/dvisvgm/src/GlyphTracerMessages.hpp
@@ -0,0 +1,78 @@
+/*************************************************************************
+** GlyphTracerMessages.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef GLYPHTRACERMESSAGES_HPP
+#define GLYPHTRACERMESSAGES_HPP
+
+#include <sstream>
+#include "GFGlyphTracer.hpp"
+#include "Message.hpp"
+
+class GlyphTracerMessages : public GFGlyphTracer::Callback {
+ public:
+ explicit GlyphTracerMessages (bool sfmsg=true, bool autonl=true) : _sfmsg(sfmsg), _autonl(autonl), _traced(false) {}
+
+ ~GlyphTracerMessages () override {
+ if (_autonl)
+ Message::mstream() << '\n';
+ }
+
+ void beginChar (uint8_t c) override {
+ if (!_traced) {
+ if (!_fname.empty())
+ Message::mstream() << '\n';
+ // extract font name from file path
+ std::string fontname = _fname;
+ size_t pos;
+ if ((pos = fontname.rfind('/')) != std::string::npos)
+ fontname = fontname.substr(pos+1);
+ if ((pos = fontname.rfind('.')) != std::string::npos)
+ fontname = fontname.substr(0, pos);
+ Message::mstream(false, Message::MC_STATE)
+ << "tracing glyphs of " << fontname << '\n';
+ _traced = true;
+ }
+ }
+
+ void endChar (uint8_t c) override {
+ std::ostringstream oss;
+ oss << '[';
+ if (isprint(c))
+ oss << c;
+ else
+ oss << '#' << unsigned(c);
+ oss << ']';
+ Message::mstream(false, Message::MC_TRACING) << oss.str();
+ }
+
+ void setFont (const std::string &fname) override {
+ if (_sfmsg && fname != _fname) {
+ _fname = fname;
+ _traced = false;
+ }
+ }
+
+ private:
+ std::string _fname;
+ bool _sfmsg, _autonl;
+ bool _traced; ///< true if a glyph of the current font has already been traced?
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/GraphicsPath.hpp b/dviware/dvisvgm/src/GraphicsPath.hpp
new file mode 100644
index 0000000000..bf453d11d4
--- /dev/null
+++ b/dviware/dvisvgm/src/GraphicsPath.hpp
@@ -0,0 +1,467 @@
+/*************************************************************************
+** GraphicsPath.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef GRAPHICSPATH_HPP
+#define GRAPHICSPATH_HPP
+
+#include <cctype>
+#include <deque>
+#include <ostream>
+#include "BoundingBox.hpp"
+#include "Matrix.hpp"
+#include "Pair.hpp"
+#include "XMLString.hpp"
+
+
+template <typename T>
+class GraphicsPath {
+ friend class PathClipper;
+ public:
+ enum class WindingRule {EVEN_ODD, NON_ZERO};
+ using Point = Pair<T>;
+
+ struct Command {
+ enum class Type {MOVETO, LINETO, CONICTO, CUBICTO, CLOSEPATH};
+
+ explicit Command (Type t) : type(t) {}
+
+ Command (Type t, const Point &p) : type(t) {
+ params[0] = p;
+ }
+
+ Command (Type t, const Point &p1, const Point &p2) : type(t) {
+ params[0] = p1;
+ params[1] = p2;
+ }
+
+ Command (Type t, const Point &p1, const Point &p2, const Point &p3) : type(t) {
+ params[0] = p1;
+ params[1] = p2;
+ params[2] = p3;
+ }
+
+ int numParams () const {
+ switch (type) {
+ case Type::CLOSEPATH : return 0;
+ case Type::MOVETO :
+ case Type::LINETO : return 1;
+ case Type::CONICTO : return 2;
+ case Type::CUBICTO : return 3;
+ }
+ return 0;
+ }
+
+ void transform (const Matrix &matrix) {
+ for (int i=0; i < numParams(); i++)
+ params[i] = matrix * params[i];
+ }
+
+ bool operator == (const Command &cmd) const {
+ bool ret = (type == cmd.type);
+ for (int i=0; ret && i < numParams(); i++)
+ ret &= (params[i] == cmd.params[i]);
+ return ret;
+ }
+
+ bool operator != (const Command &cmd) const {
+ bool ret = (type != cmd.type);
+ for (int i=0; !ret && i < numParams(); i++)
+ ret |= (params[i] != cmd.params[i]);
+ return ret;
+ }
+
+ Type type;
+ Point params[3];
+ };
+
+ struct Actions {
+ virtual ~Actions () =default;
+ virtual void moveto (const Point &p) {}
+ virtual void lineto (const Point &p) {}
+ virtual void hlineto (const T &y) {}
+ virtual void vlineto (const T &x) {}
+ virtual void conicto (const Point &p) {}
+ virtual void conicto (const Point &p1, const Point &p2) {}
+ virtual void cubicto (const Point &p1, const Point &p2) {}
+ virtual void cubicto (const Point &p1, const Point &p2, const Point &p3) {}
+ virtual void closepath () {}
+ virtual void draw (char cmd, const Point *points, int n) {}
+ virtual bool quit () {return false;}
+ virtual void finished () {}
+ };
+
+ public:
+ explicit GraphicsPath (WindingRule wr=WindingRule::NON_ZERO) : _windingRule(wr) {}
+
+ void setWindingRule (WindingRule wr) {_windingRule = wr;}
+ WindingRule windingRule () const {return _windingRule;}
+
+ void clear () {
+ _commands.clear();
+ }
+
+ /// Returns true if the path is empty, i.e. there is nothing to draw
+ bool empty () const {
+ return _commands.empty();
+ }
+
+ /// Returns the number of path commands used to describe the path.
+ size_t size () const {
+ return _commands.size();
+ }
+
+ /// Insert another path at the beginning of this one.
+ void prepend (const GraphicsPath &path) {
+ _commands.insert(_commands.begin(), path._commands.begin(), path._commands.end());
+ }
+
+ void moveto (const T &x, const T &y) {
+ moveto(Point(x, y));
+ }
+
+ void moveto (const Point &p) {
+ // avoid sequences of several MOVETOs; always use latest
+ if (_commands.empty() || _commands.back().type != Command::Type::MOVETO)
+ _commands.emplace_back(Command(Command::Type::MOVETO, p));
+ else
+ _commands.back().params[0] = p;
+ }
+
+ void lineto (const T &x, const T &y) {
+ lineto(Point(x, y));
+ }
+
+ void lineto (const Point &p) {
+ _commands.emplace_back(Command(Command::Type::LINETO, p));
+ }
+
+ void conicto (const T &x1, const T &y1, const T &x2, const T &y2) {
+ conicto(Point(x1, y1), Point(x2, y2));
+ }
+
+ void conicto (const Point &p1, const Point &p2) {
+ _commands.emplace_back(Command(Command::Type::CONICTO, p1, p2));
+ }
+
+ void cubicto (const T &x1, const T &y1, const T &x2, const T &y2, const T &x3, const T &y3) {
+ cubicto(Point(x1, y1), Point(x2, y2), Point(x3, y3));
+ }
+
+ void cubicto (const Point &p1, const Point &p2, const Point &p3) {
+ _commands.emplace_back(Command(Command::Type::CUBICTO, p1, p2, p3));
+ }
+
+ void closepath () {
+ _commands.emplace_back(Command(Command::Type::CLOSEPATH));
+ }
+
+ const std::vector<Command>& commands () const {
+ return _commands;
+ }
+
+
+ /** Detects all open subpaths and closes them by adding a closePath command.
+ * Most font formats only support closed outline paths so there are no explicit closePath statements
+ * in the glyph's outline description. All open paths are automatically closed by the renderer.
+ * This method detects all open paths and adds the missing closePath statement. */
+ void closeOpenSubPaths () {
+ Command *prevCommand=0;
+ for (auto it=_commands.begin(); it != _commands.end(); ++it) {
+ if (it->type == Command::Type::MOVETO && prevCommand && prevCommand->type != Command::Type::CLOSEPATH) {
+ prevCommand = &(*it);
+ it = _commands.insert(it, Command(Command::Type::CLOSEPATH))+1;
+ }
+ else
+ prevCommand = &(*it);
+ }
+ if (!_commands.empty() && _commands.back().type != Command::Type::CLOSEPATH)
+ closepath();
+ }
+
+
+ /** Removes redundant path commands commands. Currently, it only removes movetos. */
+ void removeRedundantCommands () {
+ // remove trailing moveto commands
+ while (!_commands.empty() && _commands.back().type == Command::Type::MOVETO)
+ _commands.pop_back();
+ // resolve intermediate sequences of moveto commands
+ auto it=_commands.begin();
+ if (it == _commands.end())
+ return;
+ auto prev = it++;
+ while (it != _commands.end()) {
+ if (prev->type != Command::Type::MOVETO || it->type != Command::Type::MOVETO)
+ prev = it++;
+ else {
+ prev = _commands.erase(prev); // remove leading MOVETO and advance 'prev' to 'it'
+ ++it;
+ }
+ }
+ }
+
+
+ /** Writes the path data as SVG path drawing command to a given output stream.
+ * @param[in] os output stream used to write the SVG commands to
+ * @param[in] relative if true, create relative rather than absolute coordinate values
+ * @param[in] sx horizontal scale factor
+ * @param[in] sy vertical scale factor
+ * @param[in] dx horizontal translation in PS point units
+ * @param[in] dy vertical translation in PS point units */
+ void writeSVG (std::ostream &os, bool relative, double sx=1.0, double sy=1.0, double dx=0.0, double dy=0.0) const {
+ struct WriteActions : Actions {
+ WriteActions (std::ostream &os, bool relative, double sx, double sy, double dx, double dy)
+ : _os(os), _relative(relative), _sx(sx), _sy(sy), _dx(dx), _dy(dy) {}
+
+ void draw (char cmd, const Point *points, int n) override {
+ if (_relative)
+ cmd = tolower(cmd);
+ _os << cmd;
+ switch (cmd) {
+ case 'h': _os << XMLString(_sx*(points->x()-_currentPoint.x())+_dx); break;
+ case 'v': _os << XMLString(_sy*(points->y()-_currentPoint.y())+_dy); break;
+ case 'z': _currentPoint = _startPoint; break;
+ case 'H': _os << XMLString(_sx*points->x()+_dx); break;
+ case 'V': _os << XMLString(_sy*points->y()+_dy); break;
+ default : {
+ for (int i=0; i < n; i++) {
+ Point p = points[i];
+ if (_relative)
+ p -= _currentPoint;
+ double x = _sx*p.x() + _dx;
+ XMLString xstr(x);
+ if (i > 0 && (xstr[0] != '-')) // space required to separate numbers?
+ _os << ' ';
+ _os << xstr;
+ double y = _sy*p.y() + _dy;
+ XMLString ystr(y);
+ if (ystr[0] != '-') // space required to separate numbers?
+ _os << ' ';
+ _os << ystr;
+ }
+ }
+ }
+ if (cmd == 'm')
+ _startPoint = points[0];
+ if (islower(cmd) && n > 0)
+ _currentPoint = points[n-1];
+ }
+ std::ostream &_os;
+ bool _relative;
+ double _sx, _sy, _dx, _dy;
+ Point _startPoint, _currentPoint;
+ } actions(os, relative, sx, sy, dx, dy);
+ iterate(actions, true);
+ }
+
+#if 0
+ void writePS (std::ostream &os, double sx=1.0, double sy=1.0, double dx=0.0, double dy=0.0) const {
+ struct WriteActions : Actions {
+ WriteActions (std::ostream &os, double sx, double sy, double dx, double dy)
+ : _os(os), _sx(sx), _sy(sy), _dx(dx), _dy(dy) {}
+ void draw (char cmd, const Point *points, int n) {
+ for (int i=0; i < n; i++)
+ _os << _sx*points[i].x()+_dx << ' ' << _sy*points[i].y()+_dy << ' ';
+ switch (cmd) {
+ case 'M': _os << "moveto"; break;
+ case 'L': _os << "lineto"; break;
+ case 'C': _os << "curveto"; break;
+ case 'Z': _os << "closepath"; break;
+ default: ;
+ }
+ _os << '\n';
+ }
+ std::ostream &_os;
+ bool _relative;
+ double _sx, _sy, _dx, _dy;
+ } actions(os, sx, sy, dx, dy);
+ iterate(actions, false);
+ }
+#endif
+
+
+ /** Computes the bounding box of the current path.
+ * @param[out] bbox the computed bounding box */
+ void computeBBox (BoundingBox &bbox) const {
+ struct BBoxActions : Actions {
+ explicit BBoxActions (BoundingBox &bb) : bbox(bb) {}
+ void moveto (const Point &p) override {bbox.embed(p);}
+ void lineto (const Point &p) override {bbox.embed(p);}
+ void conicto (const Point &p1, const Point &p2) override {bbox.embed(p1); bbox.embed(p2);}
+ void cubicto (const Point &p1, const Point &p2, const Point &p3) override {bbox.embed(p1); bbox.embed(p2); bbox.embed(p3);}
+ BoundingBox &bbox;
+ } actions(bbox);
+ iterate(actions, false);
+ }
+
+
+ /** Checks whether the current path describes a dot/point only (with no extent).
+ * @param[out] p coordinates of the point if path describes a dot
+ * @return true if path is a dot/point */
+ bool isDot (Point &p) const {
+ struct DotActions : Actions {
+ DotActions () : differs(false) {}
+ void moveto (const Point &p) override {point = p;}
+ void lineto (const Point &p) override {differs = (p != point);}
+ void conicto (const Point &p1, const Point &p2) override {differs = (point != p1 || point != p2);}
+ void cubicto (const Point &p1, const Point &p2, const Point &p3) override {differs = (point != p1 || point != p2 || point != p3);}
+ bool quit () override {return differs;}
+ Point point;
+ bool differs;
+ } actions;
+ iterate(actions, false);
+ p = actions.point;
+ return !actions.differs;
+ }
+
+
+ /** Transforms the path according to a given Matrix.
+ * @param[in] matrix Matrix describing the affine transformation */
+ void transform (const Matrix &matrix) {
+ for (Command &command : _commands)
+ command.transform(matrix);
+ }
+
+
+ bool operator == (const GraphicsPath &path) const {
+ if (size() != path.size())
+ return false;
+ auto it = _commands.begin();
+ for (const Command &cmd : path._commands) {
+ if (*it++ != cmd)
+ return false;
+ }
+ return true;
+ }
+
+
+ bool operator != (const GraphicsPath &path) const {
+ if (size() != path.size())
+ return true;
+ auto it = _commands.begin();
+ for (const Command &cmd : path._commands) {
+ if (*it++ != cmd)
+ return true;
+ }
+ return false;
+ }
+
+
+ void iterate (Actions &actions, bool optimize) const;
+
+ private:
+ std::deque<Command> _commands;
+ WindingRule _windingRule;
+};
+
+
+/** Iterates over all commands defining this path and calls the corresponding template methods.
+ * In the case of successive bezier curve sequences, control points or tangent slopes are often
+ * identical so that the path description contains redundant information. SVG provides shorthand
+ * curve commands that require less parameters. If 'optimize' is true, this method detects such
+ * command sequences.
+ * @param[in] actions template methods called by each iteration step
+ * @param[in] optimize if true, shorthand drawing commands (hlineto, vlineto,...) are considered */
+template <typename T>
+void GraphicsPath<T>::iterate (Actions &actions, bool optimize) const {
+ auto prev = _commands.end(); // pointer to preceding command
+ Point fp; // first point of current path
+ Point cp; // current point
+ Point pstore[2];
+ const double eps = XMLString::DECIMAL_PLACES > 0 ? pow(10, -XMLString::DECIMAL_PLACES) : 1e-7;
+ for (auto it=_commands.begin(); it != _commands.end() && !actions.quit(); ++it) {
+ const Point *params = it->params;
+ switch (it->type) {
+ case Command::Type::MOVETO:
+ actions.moveto(params[0]);
+ actions.draw('M', params, 1);
+ fp = params[0];
+ break;
+ case Command::Type::LINETO: {
+ Point diff = abs(cp - params[0]);
+ if (diff.x() < eps && diff.y() < eps)
+ break;
+ if (optimize) {
+ if (diff.x() < eps) {
+ actions.vlineto(params[0].y());
+ actions.draw('V', params, 1);
+ }
+ else if (diff.y() < eps) {
+ actions.hlineto(params[0].x());
+ actions.draw('H', params, 1);
+ }
+ else {
+ actions.lineto(params[0]);
+ actions.draw('L', params, 1);
+ }
+ }
+ else {
+ actions.lineto(params[0]);
+ actions.draw('L', params, 1);
+ }
+ break;
+ }
+ case Command::Type::CONICTO: {
+ // check if first control point is the reflection of the preceding second control point?
+ Point diff = abs(params[0]-pstore[1]*T(2)+pstore[0]);
+ bool isReflection = diff.x() < eps && diff.y() < eps;
+ if (optimize && prev != _commands.end() && prev->type == Command::Type::CONICTO && isReflection) {
+ actions.conicto(params[1]);
+ actions.draw('T', params+1, 1);
+ }
+ else {
+ actions.conicto(params[0], params[1]);
+ actions.draw('Q', params, 2);
+ }
+ pstore[0] = params[0]; // store control point and
+ pstore[1] = params[1]; // curve endpoint
+ break;
+ }
+ case Command::Type::CUBICTO: {
+ // check if first control point is the reflection of the preceding second control point?
+ Point diff = abs(params[0]-pstore[1]*T(2)+pstore[0]);
+ bool isReflection = diff.x() < eps && diff.y() < eps;
+ // is first control point reflection of preceding second control point?
+ if (optimize && prev != _commands.end() && prev->type == Command::Type::CUBICTO && isReflection) {
+ actions.cubicto(params[1], params[2]);
+ actions.draw('S', params+1, 2);
+ }
+ else {
+ actions.cubicto(params[0], params[1], params[2]);
+ actions.draw('C', params, 3);
+ }
+ pstore[0] = params[1]; // store second control point and
+ pstore[1] = params[2]; // curve endpoint
+ break;
+ }
+ case Command::Type::CLOSEPATH:
+ actions.closepath();
+ actions.draw('Z', params, 0);
+ cp = fp;
+ }
+ // update current point
+ const int np = it->numParams();
+ if (np > 0)
+ cp = it->params[np-1];
+ prev = it;
+ }
+ actions.finished();
+}
+
+#endif
diff --git a/dviware/dvisvgm/src/HashFunction.cpp b/dviware/dvisvgm/src/HashFunction.cpp
new file mode 100644
index 0000000000..7479fca604
--- /dev/null
+++ b/dviware/dvisvgm/src/HashFunction.cpp
@@ -0,0 +1,84 @@
+/*************************************************************************
+** HashFunction.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <iomanip>
+#include <sstream>
+#include "HashFunction.hpp"
+#include "utility.hpp"
+#include "MD5HashFunction.hpp"
+#include "XXHashFunction.hpp"
+
+using namespace std;
+
+
+/** Returns a vector containing the names of the currently supported hash algorithms. */
+vector<string> HashFunction::supportedAlgorithms () {
+ return vector<string> {"md5", "xxh32", "xxh64"};
+}
+
+
+/** Returns true if 'algo' is the name of a supported hash algorithm. */
+bool HashFunction::isSupportedAlgorithm (const std::string &algo) {
+ auto algos = supportedAlgorithms();
+ return find(algos.begin(), algos.end(), algo) != algos.end();
+}
+
+
+/** Creates a hash function for a given algorithm name/identifier.
+ * The following names are currently supported: md5, xxh32, xxh64. */
+unique_ptr<HashFunction> HashFunction::create (const string &name) {
+ string lowerName = util::tolower(name);
+ if (lowerName == "md5")
+ return util::make_unique<MD5HashFunction>();
+ if (lowerName == "xxh32")
+ return util::make_unique<XXH32HashFunction>();
+ if (lowerName == "xxh64")
+ return util::make_unique<XXH64HashFunction>();
+ return nullptr;
+}
+
+
+std::unique_ptr<HashFunction> HashFunction::create (const string &name, const char *data, size_t length) {
+ auto hashfunc = create(name);
+ if (hashfunc)
+ hashfunc->update(data, length);
+ return hashfunc;
+}
+
+
+std::unique_ptr<HashFunction> HashFunction::create (const string &name, const string &data) {
+ return create(name, data.data(), data.length());
+}
+
+
+std::unique_ptr<HashFunction> HashFunction::create (const string &name, const vector<uint8_t> &data) {
+ return create(name, reinterpret_cast<const char*>(data.data()), data.size());
+}
+
+
+/** Returns the current digest as hexadecimal value. */
+string HashFunction::digestString () const {
+ ostringstream oss;
+ oss << hex << setfill('0');
+ for (int byte : digestValue())
+ oss << setw(2) << byte;
+ return oss.str();
+}
diff --git a/dviware/dvisvgm/src/HashFunction.hpp b/dviware/dvisvgm/src/HashFunction.hpp
new file mode 100644
index 0000000000..1b1259393f
--- /dev/null
+++ b/dviware/dvisvgm/src/HashFunction.hpp
@@ -0,0 +1,47 @@
+/*************************************************************************
+** HashFunction.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef HASHFUNCTION_HPP
+#define HASHFUNCTION_HPP
+
+#include <memory>
+#include <string>
+#include <vector>
+
+/** Common base class for all hash functions. */
+class HashFunction {
+ public:
+ virtual ~HashFunction () =default;
+ virtual int digestSize () const =0;
+ virtual void reset () =0;
+ virtual void update (const char *data, size_t length) =0;
+ virtual void update (const std::string &data) =0;
+ virtual void update (const std::vector<uint8_t> &data) =0;
+ virtual std::vector<uint8_t> digestValue () const =0;
+ std::string digestString () const;
+ static std::vector<std::string> supportedAlgorithms ();
+ static bool isSupportedAlgorithm (const std::string &algo);
+ static std::unique_ptr<HashFunction> create (const std::string &name);
+ static std::unique_ptr<HashFunction> create (const std::string &name, const char *data, size_t length);
+ static std::unique_ptr<HashFunction> create (const std::string &name, const std::string &data);
+ static std::unique_ptr<HashFunction> create (const std::string &name, const std::vector<uint8_t> &data);
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/HtmlSpecialHandler.cpp b/dviware/dvisvgm/src/HtmlSpecialHandler.cpp
new file mode 100644
index 0000000000..43d3dbc224
--- /dev/null
+++ b/dviware/dvisvgm/src/HtmlSpecialHandler.cpp
@@ -0,0 +1,86 @@
+/*************************************************************************
+** HtmlSpecialHandler.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include "HtmlSpecialHandler.hpp"
+#include "HyperlinkManager.hpp"
+#include "InputReader.hpp"
+#include "SpecialActions.hpp"
+
+using namespace std;
+
+
+void HtmlSpecialHandler::preprocess (const string&, istream &is, SpecialActions &actions) {
+ StreamInputReader ir(is);
+ ir.skipSpace();
+ // collect page number and ID of named anchors
+ map<string,string> attribs;
+ if (ir.check("<a ") && ir.parseAttributes(attribs, true, "\"") > 0) {
+ map<string,string>::iterator it;
+ if ((it = attribs.find("name")) != attribs.end())
+ HyperlinkManager::instance().addNameAchor(it->second, actions.getCurrentPageNumber());
+ else if ((it = attribs.find("href")) != attribs.end())
+ HyperlinkManager::instance().addHrefAnchor(it->second);
+ }
+}
+
+
+bool HtmlSpecialHandler::process (const string&, istream &is, SpecialActions &actions) {
+ _active = true;
+ StreamInputReader ir(is);
+ ir.skipSpace();
+ map<string,string> attribs;
+ map<string,string>::iterator it;
+ if (ir.check("<a ") && ir.parseAttributes(attribs, true, "\"") > 0) {
+ if ((it = attribs.find("href")) != attribs.end()) // <a href="URI">
+ HyperlinkManager::instance().createLink(it->second, actions);
+ else if ((it = attribs.find("name")) != attribs.end()) // <a name="ID">
+ HyperlinkManager::instance().setActiveNameAnchor(it->second, actions);
+ else
+ return false; // none or only invalid attributes
+ }
+ else if (ir.check("</a>"))
+ HyperlinkManager::instance().closeAnchor(actions);
+ else if (ir.check("<img src=")) {
+ }
+ else if (ir.check("<base ") && ir.parseAttributes(attribs, true, "\"") > 0 && (it = attribs.find("href")) != attribs.end())
+ HyperlinkManager::instance().setBaseUrl(it->second);
+ return true;
+}
+
+
+/** This method is called every time the DVI position changes. */
+void HtmlSpecialHandler::dviMovedTo (double x, double y, SpecialActions &actions) {
+ if (_active)
+ HyperlinkManager::instance().checkNewLine(actions);
+}
+
+
+void HtmlSpecialHandler::dviEndPage (unsigned pageno, SpecialActions &actions) {
+ if (_active) {
+ HyperlinkManager::instance().createViews(pageno, actions);
+ _active = false;
+ }
+}
+
+
+vector<const char*> HtmlSpecialHandler::prefixes() const {
+ vector<const char*> pfx {"html:"};
+ return pfx;
+}
diff --git a/dviware/dvisvgm/src/HtmlSpecialHandler.hpp b/dviware/dvisvgm/src/HtmlSpecialHandler.hpp
new file mode 100644
index 0000000000..0d4e3e4e11
--- /dev/null
+++ b/dviware/dvisvgm/src/HtmlSpecialHandler.hpp
@@ -0,0 +1,47 @@
+/*************************************************************************
+** HtmlSpecialHandler.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef HTMLSPECIALHANDLER_HPP
+#define HTMLSPECIALHANDLER_HPP
+
+#include <string>
+#include "Color.hpp"
+#include "SpecialHandler.hpp"
+
+class SpecialActions;
+
+class HtmlSpecialHandler : public SpecialHandler {
+ public:
+ HtmlSpecialHandler () : _active(false) {}
+ void preprocess (const std::string &prefix, std::istream &is, SpecialActions &actions) override;
+ bool process (const std::string &prefix, std::istream &is, SpecialActions &actions) override;
+ const char* name () const override {return "html";}
+ const char* info () const override {return "hyperref specials";}
+ std::vector<const char*> prefixes() const override;
+
+ protected:
+ void dviEndPage (unsigned pageno, SpecialActions &actions) override;
+ void dviMovedTo (double x, double y, SpecialActions &actions) override;
+
+ private:
+ bool _active;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/HyperlinkManager.cpp b/dviware/dvisvgm/src/HyperlinkManager.cpp
new file mode 100644
index 0000000000..ad5cabd93c
--- /dev/null
+++ b/dviware/dvisvgm/src/HyperlinkManager.cpp
@@ -0,0 +1,265 @@
+/*************************************************************************
+** HyperlinkManager.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <sstream>
+#include "HyperlinkManager.hpp"
+#include "Message.hpp"
+#include "SpecialActions.hpp"
+#include "SVGTree.hpp"
+#include "utility.hpp"
+#include "XMLNode.hpp"
+
+using namespace std;
+
+// variable to select the link marker variant (none, underlined, boxed, or colored background)
+HyperlinkManager::MarkerType HyperlinkManager::MARKER_TYPE = MarkerType::LINE;
+Color HyperlinkManager::LINK_BGCOLOR;
+Color HyperlinkManager::LINK_LINECOLOR;
+HyperlinkManager::ColorSource HyperlinkManager::COLORSOURCE = ColorSource::DEFAULT;
+
+
+HyperlinkManager& HyperlinkManager::instance () {
+ static HyperlinkManager instance;
+ return instance;
+}
+
+
+void HyperlinkManager::addHrefAnchor (const string &uri) {
+ if (uri.empty() || uri[0] != '#')
+ return;
+ string name = uri.substr(1);
+ auto it = _namedAnchors.find(name);
+ if (it != _namedAnchors.end()) // anchor already defined?
+ it->second.referenced = true;
+ else {
+ int id = static_cast<int>(_namedAnchors.size())+1;
+ _namedAnchors.emplace(name, NamedAnchor(0, -id, 0, true));
+ }
+}
+
+
+void HyperlinkManager::addNameAchor (const string &name, int pageno) {
+ if (name.empty())
+ return;
+ auto it = _namedAnchors.find(name);
+ if (it == _namedAnchors.end()) { // anchor completely undefined?
+ int id = static_cast<int>(_namedAnchors.size())+1;
+ _namedAnchors.emplace(name, NamedAnchor(pageno, id, 0));
+ }
+ else if (it->second.id >= 0) // anchor already defined?
+ Message::wstream(true) << "named hyperref anchor '" << name << "' redefined\n";
+ else {
+ // anchor referenced but not defined yet?
+ it->second.id *= -1;
+ it->second.pageno = pageno;
+ }
+}
+
+
+void HyperlinkManager::setActiveNameAnchor (const string &name, SpecialActions &actions) {
+ auto it = _namedAnchors.find(name);
+ if (it != _namedAnchors.end()) {
+ closeAnchor(actions);
+ it->second.pos = actions.getY();
+ _anchorType = AnchorType::NAME;
+ }
+}
+
+
+void HyperlinkManager::createLink (string uri, SpecialActions &actions) {
+ closeAnchor(actions);
+ string name;
+ if (uri[0] == '#') { // reference to named anchor?
+ name = uri.substr(1);
+ auto it = _namedAnchors.find(name);
+ if (it == _namedAnchors.end() || it->second.id < 0)
+ Message::wstream(true) << "reference to undefined anchor '" << name << "'\n";
+ else {
+ int id = it->second.id;
+ uri = "#loc"+XMLString(id);
+ if (actions.getCurrentPageNumber() != it->second.pageno) {
+ ostringstream oss;
+ oss << actions.getSVGFilename(it->second.pageno) << uri;
+ uri = oss.str();
+ }
+ }
+ }
+ if (!_base.empty() && uri.find("://") != string::npos) {
+ if (*_base.rbegin() != '/' && uri[0] != '/' && uri[0] != '#')
+ uri = "/" + uri;
+ uri = _base + uri;
+ }
+ auto anchorNode = util::make_unique<XMLElement>("a");
+ anchorNode->addAttribute("xlink:href", uri);
+ anchorNode->addAttribute("xlink:title", XMLString(name.empty() ? uri : name, false));
+ actions.svgTree().pushPageContext(std::move(anchorNode));
+ actions.bbox("{anchor}", true); // start computing the bounding box of the linked area
+ _depthThreshold = actions.getDVIStackDepth();
+ _anchorType = AnchorType::HREF;
+}
+
+
+void HyperlinkManager::closeAnchor (SpecialActions &actions) {
+ if (_anchorType == AnchorType::HREF) {
+ markLinkedBox(actions);
+ actions.svgTree().popPageContext();
+ _depthThreshold = 0;
+ }
+ _anchorType = AnchorType::NONE;
+}
+
+
+void HyperlinkManager::checkNewLine (SpecialActions &actions) {
+ if (_anchorType == AnchorType::NONE)
+ return;
+ // Start a new box if the current depth of the DVI stack underruns
+ // the initial threshold which indicates a line break.
+ if (actions.getDVIStackDepth() < _depthThreshold) {
+ markLinkedBox(actions);
+ _depthThreshold = actions.getDVIStackDepth();
+ actions.bbox("{anchor}", true); // start a new box on the new line
+ }
+}
+
+
+/** Marks a single rectangular area of the linked part of the document with a line or
+ * a box so that it's noticeable by the user. Additionally, an invisible rectangle is
+ * placed over this area in order to avoid flickering of the mouse cursor when moving
+ * it over the hyperlinked area. */
+void HyperlinkManager::markLinkedBox (SpecialActions &actions) {
+ const BoundingBox &bbox = actions.bbox("{anchor}");
+ if (bbox.width() > 0 && bbox.height() > 0) { // does the bounding box extend in both dimensions?
+ if (MARKER_TYPE != MarkerType::NONE) {
+ const double linewidth = _linewidth >= 0 ? _linewidth : min(0.5, bbox.height()/15);
+ auto rect = util::make_unique<XMLElement>("rect");
+ double x = bbox.minX();
+ double y = bbox.maxY()+linewidth;
+ double w = bbox.width();
+ double h = linewidth;
+ const Color linecolor = COLORSOURCE == ColorSource::DEFAULT ? actions.getColor() : LINK_LINECOLOR;
+ if (MARKER_TYPE == MarkerType::LINE)
+ rect->addAttribute("fill", linecolor.svgColorString());
+ else {
+ const double offset = _linewidth < 0 ? linewidth : 0 ;
+ x -= offset;
+ y = bbox.minY()-offset;
+ w += 2*offset;
+ h += bbox.height()+offset;
+ if (MARKER_TYPE == MarkerType::BGCOLOR) {
+ rect->addAttribute("fill", LINK_BGCOLOR.svgColorString());
+ if (COLORSOURCE != ColorSource::DEFAULT) {
+ rect->addAttribute("stroke", linecolor.svgColorString());
+ rect->addAttribute("stroke-width", linewidth);
+ }
+ }
+ else { // LM_BOX
+ rect->addAttribute("fill", "none");
+ rect->addAttribute("stroke", linecolor.svgColorString());
+ rect->addAttribute("stroke-width", linewidth);
+ }
+ }
+ rect->addAttribute("x", x);
+ rect->addAttribute("y", y);
+ rect->addAttribute("width", w);
+ rect->addAttribute("height", h);
+ actions.svgTree().prependToPage(std::move(rect));
+ if (MARKER_TYPE == MarkerType::BOX || MARKER_TYPE == MarkerType::BGCOLOR) {
+ // slightly enlarge the boxed area
+ x -= linewidth/2;
+ y -= linewidth/2;
+ w += linewidth;
+ h += linewidth;
+ }
+ actions.embed(BoundingBox(x, y, x+w, y+h));
+ }
+ // Create an invisible rectangle around the linked area so that it's easier to access.
+ // This is only necessary when using paths rather than real text elements together with fonts.
+ if (!SVGTree::USE_FONTS) {
+ auto rect = util::make_unique<XMLElement>("rect");
+ rect->addAttribute("x", bbox.minX());
+ rect->addAttribute("y", bbox.minY());
+ rect->addAttribute("width", bbox.width());
+ rect->addAttribute("height", bbox.height());
+ rect->addAttribute("fill", "white");
+ rect->addAttribute("fill-opacity", 0);
+ actions.svgTree().appendToPage(std::move(rect));
+ }
+ }
+}
+
+
+// Creates SVG views for all collected named anchors defined on a given page.
+void HyperlinkManager::createViews (unsigned pageno, SpecialActions &actions) {
+ const BoundingBox &pagebox = actions.bbox();
+ for (auto &stranchorpair : _namedAnchors) {
+ if (stranchorpair.second.pageno == pageno && stranchorpair.second.referenced) { // current anchor referenced?
+ ostringstream oss;
+ oss << pagebox.minX() << ' ' << stranchorpair.second.pos << ' '
+ << pagebox.width() << ' ' << pagebox.height();
+ auto view = util::make_unique<XMLElement>("view");
+ view->addAttribute("id", "loc"+XMLString(stranchorpair.second.id));
+ view->addAttribute("viewBox", oss.str());
+ actions.svgTree().appendToDefs(std::move(view));
+ }
+ }
+ closeAnchor(actions);
+}
+
+
+/** Sets the appearance of the link markers.
+ * @param[in] marker string specifying the marker (format: type[:linecolor])
+ * @return true on success */
+bool HyperlinkManager::setLinkMarker (const string &marker) {
+ string type; // "none", "box", "line", or a background color specifier
+ string color; // optional line color specifier
+ size_t seppos = marker.find(':');
+ if (seppos == string::npos)
+ type = marker;
+ else {
+ type = marker.substr(0, seppos);
+ color = marker.substr(seppos+1);
+ }
+ if (type.empty() || type == "none")
+ MARKER_TYPE = MarkerType::NONE;
+ else if (type == "line")
+ MARKER_TYPE = MarkerType::LINE;
+ else if (type == "box")
+ MARKER_TYPE = MarkerType::BOX;
+ else {
+ if (!LINK_BGCOLOR.setPSName(type, false))
+ return false;
+ MARKER_TYPE = MarkerType::BGCOLOR;
+ }
+ COLORSOURCE = ColorSource::DEFAULT;
+ if (MARKER_TYPE != MarkerType::NONE && !color.empty()) {
+ if (!LINK_LINECOLOR.setPSName(color, false))
+ return false;
+ COLORSOURCE = ColorSource::LINKMARKER;
+ }
+ return true;
+}
+
+
+void HyperlinkManager::setDefaultLinkColor (Color color) {
+ if (COLORSOURCE != ColorSource::LINKMARKER) {
+ COLORSOURCE = ColorSource::STATIC;
+ LINK_LINECOLOR = color;
+ }
+} \ No newline at end of file
diff --git a/dviware/dvisvgm/src/HyperlinkManager.hpp b/dviware/dvisvgm/src/HyperlinkManager.hpp
new file mode 100644
index 0000000000..7f5a460bdb
--- /dev/null
+++ b/dviware/dvisvgm/src/HyperlinkManager.hpp
@@ -0,0 +1,79 @@
+/*************************************************************************
+** HyperlinkManager.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef HYPERLINKMANAGER_HPP
+#define HYPERLINKMANAGER_HPP
+
+#include <string>
+#include <unordered_map>
+#include "Color.hpp"
+#include "SpecialActions.hpp"
+
+class SpecialActions;
+
+class HyperlinkManager {
+ struct NamedAnchor {
+ NamedAnchor () : pageno(0), id(0), pos(0), referenced(false) {}
+ NamedAnchor (unsigned pn, int i, double p, bool r=false) : pageno(pn), id(i), pos(p), referenced(r) {}
+ unsigned pageno; ///< page number where the anchor is located
+ int id; ///< unique numerical ID (< 0 if anchor is undefined yet)
+ double pos; ///< vertical position of named anchor (in PS point units)
+ bool referenced; ///< true if a reference to this anchor exists
+ };
+
+ enum class AnchorType {NONE, HREF, NAME};
+ enum class ColorSource {DEFAULT, LINKMARKER, STATIC};
+ using NamedAnchors = std::unordered_map<std::string, NamedAnchor>;
+
+ public:
+ HyperlinkManager (const HyperlinkManager&) =delete;
+ HyperlinkManager (HyperlinkManager&&) =delete;
+ void addHrefAnchor (const std::string &uri);
+ void addNameAchor (const std::string &name, int pageno);
+ void setActiveNameAnchor (const std::string &name, SpecialActions &actions);
+ void closeAnchor (SpecialActions &actions);
+ void checkNewLine (SpecialActions &actions);
+ void createLink (std::string uri, SpecialActions &actions);
+ void createViews (unsigned pageno, SpecialActions &actions);
+ void setBaseUrl (std::string &base) {_base = base;}
+ void setLineWidth (double w) {_linewidth = w;}
+ static HyperlinkManager& instance ();
+ static bool setLinkMarker (const std::string &marker);
+ static void setDefaultLinkColor (Color color);
+
+ protected:
+ HyperlinkManager () : _anchorType(AnchorType::NONE), _depthThreshold(0), _linewidth(-1) {}
+ void markLinkedBox (SpecialActions &actions);
+
+ enum class MarkerType {NONE, LINE, BOX, BGCOLOR};
+ static MarkerType MARKER_TYPE; ///< selects how to mark linked areas
+ static Color LINK_BGCOLOR; ///< background color if linkmark type == LT_BGCOLOR
+ static Color LINK_LINECOLOR; ///< line color if linkmark type is LM_LINE or LM_BOX
+ static ColorSource COLORSOURCE; ///< if true, LINK_LINECOLOR is applied
+
+ private:
+ AnchorType _anchorType; ///< type of active anchor
+ int _depthThreshold; ///< break anchor box if the DVI stack depth underruns this threshold
+ double _linewidth; ///< line width of link marker (-1 => compute individual value per link)
+ std::string _base; ///< base URL that is prepended to all relative targets
+ NamedAnchors _namedAnchors; ///< information about all named anchors processed
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/ImageToSVG.cpp b/dviware/dvisvgm/src/ImageToSVG.cpp
new file mode 100644
index 0000000000..92e5ece1dd
--- /dev/null
+++ b/dviware/dvisvgm/src/ImageToSVG.cpp
@@ -0,0 +1,199 @@
+/*************************************************************************
+** ImageToSVG.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <config.h>
+#include <fstream>
+#include <sstream>
+#include "Calculator.hpp"
+#include "ImageToSVG.hpp"
+#include "Message.hpp"
+#include "MessageException.hpp"
+#include "PageRanges.hpp"
+#include "PsSpecialHandler.hpp"
+#include "optimizer/SVGOptimizer.hpp"
+#include "SVGOutput.hpp"
+#include "System.hpp"
+#include "utility.hpp"
+#include "version.hpp"
+
+using namespace std;
+
+
+void ImageToSVG::checkGSAndFileFormat () {
+ if (!_haveGS) {
+#ifdef HAVE_LIBGS
+ _haveGS = true;
+#else
+ _haveGS = Ghostscript().available();
+#endif
+ if (!_haveGS)
+ throw MessageException("Ghostscript is required to process "+imageFormat()+" files");
+ if (!imageIsValid())
+ throw MessageException("invalid "+imageFormat()+" file");
+ }
+}
+
+
+void ImageToSVG::convert (int pageno) {
+ checkGSAndFileFormat();
+ BoundingBox bbox = imageBBox();
+ if (bbox.valid() && (bbox.width() == 0 || bbox.height() == 0))
+ Message::wstream(true) << "bounding box of " << imageFormat() << " file is empty\n";
+ Message::mstream().indent(0);
+ Message::mstream(false, Message::MC_PAGE_NUMBER) << "processing " << imageFormat() << " file\n";
+ Message::mstream().indent(1);
+ _svg.newPage(1);
+ // create a psfile special and forward it to the PsSpecialHandler
+ stringstream ss;
+ ss << "\"" << _fname << "\" "
+ "llx=" << bbox.minX() << " "
+ "lly=" << bbox.minY() << " "
+ "urx=" << bbox.maxX() << " "
+ "ury=" << bbox.maxY();
+ if (!isSinglePageFormat())
+ ss << " page=" << pageno;
+ try {
+ _psHandler.process(psSpecialCmd(), ss, *this);
+ }
+ catch (...) {
+ progress(nullptr); // remove progress message
+ throw;
+ }
+ progress(nullptr);
+ Matrix matrix = getUserMatrix(_bbox);
+ // output SVG file
+ SVGOptimizer(_svg).execute();
+ _svg.transformPage(matrix);
+ _bbox.transform(matrix);
+ _svg.setBBox(_bbox);
+ _svg.appendToDoc(util::make_unique<XMLComment>(" This file was generated by dvisvgm " + string(PROGRAM_VERSION) + " "));
+ bool success = _svg.write(_out.getPageStream(pageno, totalPageCount()));
+ string svgfname = _out.filename(pageno, totalPageCount());
+ if (svgfname.empty())
+ svgfname = "<stdout>";
+ if (!success)
+ Message::wstream() << "failed to write output to " << svgfname << '\n';
+ else {
+ const double bp2pt = 72.27/72;
+ const double bp2mm = 25.4/72;
+ Message::mstream(false, Message::MC_PAGE_SIZE) << "graphic size: " << XMLString(_bbox.width()*bp2pt) << "pt"
+ " x " << XMLString(_bbox.height()*bp2pt) << "pt"
+ " (" << XMLString(_bbox.width()*bp2mm) << "mm"
+ " x " << XMLString(_bbox.height()*bp2mm) << "mm)\n";
+ Message::mstream(false, Message::MC_PAGE_WRITTEN) << "output written to " << svgfname << '\n';
+ }
+ _svg.reset();
+}
+
+
+void ImageToSVG::convert (int firstPage, int lastPage, pair<int,int> *pageinfo) {
+ checkGSAndFileFormat();
+ int pageCount = 1; // number of pages converted
+ if (isSinglePageFormat())
+ convert(1);
+ else {
+ if (firstPage > lastPage)
+ swap(firstPage, lastPage);
+ firstPage = max(1, firstPage);
+ if (firstPage > totalPageCount())
+ pageCount = 0;
+ else {
+ lastPage = min(totalPageCount(), lastPage);
+ pageCount = lastPage-firstPage+1;
+ for (int i=firstPage; i <= lastPage; i++)
+ convert(i);
+ }
+ }
+ if (pageinfo) {
+ pageinfo->first = pageCount;
+ pageinfo->second = totalPageCount();
+ }
+}
+
+
+void ImageToSVG::convert (const std::string &rangestr, pair<int,int> *pageinfo) {
+ checkGSAndFileFormat();
+ PageRanges ranges;
+ if (!ranges.parse(rangestr, totalPageCount()))
+ throw MessageException("invalid page range format");
+
+ int pageCount=0; // number of pages converted
+ for (const auto &range : ranges) {
+ convert(range.first, range.second, pageinfo);
+ if (pageinfo)
+ pageCount += pageinfo->first;
+ }
+ if (pageinfo)
+ pageinfo->first = pageCount;
+}
+
+
+string ImageToSVG::getSVGFilename (unsigned pageno) const {
+ if (pageno == 1)
+ return _out.filename(1, 1);
+ return "";
+}
+
+
+void ImageToSVG::progress (const char *id) {
+ static double time=System::time();
+ static bool draw=false; // show progress indicator?
+ static size_t count=0;
+ count++;
+ // don't show the progress indicator before the given time has elapsed
+ if (!draw && System::time()-time > PROGRESSBAR_DELAY) {
+ draw = true;
+ Terminal::cursor(false);
+ Message::mstream(false) << "\n";
+ }
+ if (draw && ((System::time() - time > 0.05) || id == nullptr)) {
+ const size_t DIGITS=6;
+ Message::mstream(false, Message::MC_PROGRESS)
+ << string(DIGITS-min(DIGITS, static_cast<size_t>(log10(count))), ' ')
+ << count << " PostScript instructions processed\r";
+ // overprint indicator when finished
+ if (id == nullptr) {
+ Message::estream().clearline();
+ Terminal::cursor(true);
+ }
+ time = System::time();
+ }
+}
+
+
+/** Returns the matrix describing the graphics transformations
+ * given by the user in terms of transformation commands.
+ * @param[in] bbox bounding box of the graphics to transform */
+Matrix ImageToSVG::getUserMatrix (const BoundingBox &bbox) const {
+ Matrix matrix(1);
+ if (!_transCmds.empty()) {
+ const double bp2pt = (1_bp).pt();
+ Calculator calc;
+ calc.setVariable("ux", bbox.minX()*bp2pt);
+ calc.setVariable("uy", bbox.minY()*bp2pt);
+ calc.setVariable("w", bbox.width()*bp2pt);
+ calc.setVariable("h", bbox.height()*bp2pt);
+ // add constants for length units to calculator
+ for (const auto &unit : Length::getUnits())
+ calc.setVariable(unit.first, Length(1, unit.second).pt());
+ matrix.set(_transCmds, calc);
+ }
+ return matrix;
+}
diff --git a/dviware/dvisvgm/src/ImageToSVG.hpp b/dviware/dvisvgm/src/ImageToSVG.hpp
new file mode 100644
index 0000000000..88564fd736
--- /dev/null
+++ b/dviware/dvisvgm/src/ImageToSVG.hpp
@@ -0,0 +1,85 @@
+/*************************************************************************
+** ImageToSVG.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef IMAGETOSVG_HPP
+#define IMAGETOSVG_HPP
+
+#include <istream>
+#include <memory>
+#include <string>
+#include "PsSpecialHandler.hpp"
+#include "SpecialActions.hpp"
+#include "SVGTree.hpp"
+
+struct SVGOutputBase;
+
+class ImageToSVG : protected SpecialActions {
+ public:
+ ImageToSVG (std::string fname, SVGOutputBase &out) : _fname(std::move(fname)), _out(out) {}
+ void convert (int pageno);
+ void convert (int firstPage, int lastPage, std::pair<int,int> *pageinfo);
+ void convert (const std::string &rangestr, std::pair<int,int> *pageinfo);
+ void setPageTransformation (const std::string &transCmds) {_transCmds = transCmds;}
+// void setPageSize (const std::string &name);
+ std::string filename () const {return _fname;}
+ PSInterpreter& psInterpreter () {return _psHandler.psInterpreter();}
+ virtual bool isSinglePageFormat () const =0;
+ virtual int totalPageCount () =0;
+
+ protected:
+ void checkGSAndFileFormat ();
+ Matrix getUserMatrix (const BoundingBox &bbox) const;
+ virtual std::string imageFormat () const =0;
+ virtual bool imageIsValid () const =0;
+ virtual BoundingBox imageBBox () const =0;
+ virtual std::string psSpecialCmd () const =0;
+ // implement abstract base class SpecialActions
+ double getX () const override {return _x;}
+ double getY () const override {return _y;}
+ void setX (double x) override {_x = x; _svg.setX(x);}
+ void setY (double y) override {_y = y; _svg.setY(y);}
+ void finishLine () override {}
+ void setColor (const Color &color) override {_svg.setColor(color);}
+ Color getColor () const override {return _svg.getColor();}
+ void setMatrix (const Matrix &m) override {_svg.setMatrix(m);}
+ const Matrix& getMatrix () const override {return _svg.getMatrix();}
+ const SVGTree& svgTree () const override {return _svg;}
+ void setBgColor (const Color &color) override {}
+ void embed (const BoundingBox &bbox) override {_bbox.embed(bbox);}
+ void embed (const DPair &p, double r=0) override {if (r==0) _bbox.embed(p); else _bbox.embed(p, r);}
+ void progress (const char *id) override;
+ unsigned getCurrentPageNumber() const override {return 0;}
+ BoundingBox& bbox () override {return _bbox;}
+ BoundingBox& bbox (const std::string &name, bool reset=false) override {return _bbox;}
+ std::string getSVGFilename (unsigned pageno) const override;
+ std::string getBBoxFormatString () const override {return "";}
+
+ private:
+ std::string _fname; ///< name of image file
+ SVGTree _svg;
+ SVGOutputBase &_out;
+ double _x=0, _y=0;
+ BoundingBox _bbox;
+ PsSpecialHandler _psHandler;
+ bool _haveGS=false; ///< true if Ghostscript is available
+ std::string _transCmds; ///< transformation commands
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/InputBuffer.cpp b/dviware/dvisvgm/src/InputBuffer.cpp
new file mode 100644
index 0000000000..a952369afc
--- /dev/null
+++ b/dviware/dvisvgm/src/InputBuffer.cpp
@@ -0,0 +1,137 @@
+/*************************************************************************
+** InputBuffer.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <cmath>
+#include <cstring>
+#include "InputBuffer.hpp"
+
+using namespace std;
+
+
+StreamInputBuffer::StreamInputBuffer (istream &is, size_t bufsize)
+ : _is(is), _bufsize(bufsize), _buf1(new uint8_t[_bufsize]), _buf2(new uint8_t[_bufsize]), _bufptr(_buf1)
+{
+ _size1 = fillBuffer(_buf1);
+ _size2 = fillBuffer(_buf2);
+}
+
+
+StreamInputBuffer::~StreamInputBuffer () {
+ delete [] _buf1;
+ delete [] _buf2;
+}
+
+
+int StreamInputBuffer::get () {
+ if (pos() == _size1) {
+ if (_size2 == 0)
+ return -1;
+ swap(_buf1, _buf2);
+ _size1 = _size2;
+ _bufptr = _buf1;
+ _size2 = fillBuffer(_buf2);
+ }
+ uint8_t c = *_bufptr++;
+ return c;
+}
+
+
+/** Returns the next character to be read without skipping it.
+ * Same as peek(0). */
+int StreamInputBuffer::peek () const {
+ if (pos() < _size1)
+ return *_bufptr;
+ return _size2 > 0 ? *_buf2 : -1;
+}
+
+
+/** Returns the n-th next character without skipping it. */
+int StreamInputBuffer::peek (size_t n) const {
+ if (pos()+n < _size1)
+ return *(_bufptr+n);
+ if (pos()+n < _size1+_size2)
+ return *(_buf2 + pos()+n-_size1);
+ return -1;
+}
+
+
+/** Fills the buffer by reading a sequence of characters from the assigned
+ * input stream.
+ * @param[in] buf pointer to character buffer to be filled
+ * @return number of characters read */
+int StreamInputBuffer::fillBuffer (uint8_t *buf) {
+ if (_is && !_is.eof()) {
+ _is.read((char*)buf, _bufsize);
+ return _is.gcount();
+ }
+ return 0;
+}
+
+///////////////////////////////////////////////
+
+SplittedCharInputBuffer::SplittedCharInputBuffer (const char *buf1, size_t s1, const char *buf2, size_t s2) {
+ _buf[0] = buf1;
+ _buf[1] = buf2;
+ _size[0] = buf1 ? s1 : 0;
+ _size[1] = buf2 ? s2 : 0;
+ _index = _size[0] ? 0 : 1;
+}
+
+
+int SplittedCharInputBuffer::get () {
+ if (_size[_index] == 0)
+ return -1;
+ int ret = *_buf[_index]++;
+ _size[_index]--;
+ if (_index == 0 && _size[0] == 0)
+ _index++;
+ return ret;
+}
+
+
+int SplittedCharInputBuffer::peek () const {
+ return _size[_index] ? *_buf[_index] : -1;
+}
+
+
+int SplittedCharInputBuffer::peek (size_t n) const {
+ if (n < _size[_index])
+ return _buf[_index][n];
+ n -= _size[_index];
+ if (_index == 0 && n < _size[1])
+ return _buf[1][n];
+ return -1;
+}
+
+///////////////////////////////////////////////
+
+
+int TextStreamInputBuffer::get () {
+ int c = StreamInputBuffer::get();
+ if (c == '\n') {
+ _line++;
+ _col = 1;
+ }
+ else
+ _col++;
+ return c;
+}
+
+
diff --git a/dviware/dvisvgm/src/InputBuffer.hpp b/dviware/dvisvgm/src/InputBuffer.hpp
new file mode 100644
index 0000000000..8a62b6b011
--- /dev/null
+++ b/dviware/dvisvgm/src/InputBuffer.hpp
@@ -0,0 +1,139 @@
+/*************************************************************************
+** InputBuffer.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef INPUTBUFFER_HPP
+#define INPUTBUFFER_HPP
+
+#include <algorithm>
+#include <cstring>
+#include <istream>
+#include <string>
+#include <ostream>
+
+struct InputBuffer {
+ virtual ~InputBuffer () =default;
+ virtual int get () =0;
+ virtual int peek () const =0;
+ virtual int peek (size_t n) const =0;
+ virtual bool eof () const =0;
+ virtual void invalidate () =0;
+};
+
+
+class StreamInputBuffer : public InputBuffer {
+ public:
+ explicit StreamInputBuffer (std::istream &is, size_t bufsize=1024);
+ ~StreamInputBuffer () override;
+ int get () override;
+ int peek () const override;
+ int peek (size_t n) const override;
+ bool eof () const override {return pos() == _size1 && _size2 == 0;}
+ void invalidate () override {_bufptr = _buf1+_size1; _size2 = 0;}
+
+ protected:
+ int fillBuffer (uint8_t *buf);
+ size_t pos () const {return _bufptr-_buf1;}
+
+ private:
+ std::istream &_is;
+ const size_t _bufsize; ///< maximal number of bytes each buffer can hold
+ uint8_t *_buf1; ///< pointer to first buffer
+ uint8_t *_buf2; ///< pointer to second buffer
+ size_t _size1; ///< number of bytes in buffer 1
+ size_t _size2; ///< number of bytes in buffer 2
+ uint8_t *_bufptr; ///< pointer to next byte to read
+};
+
+
+class StringInputBuffer : public InputBuffer {
+ public:
+ explicit StringInputBuffer (const std::string &str) : _str(&str) {}
+ void assign (const std::string &str) {_str = &str; _pos=0;}
+ int get () override {return _pos < _str->length() ? _str->at(_pos++) : -1;}
+ int peek () const override {return _pos < _str->length() ? _str->at(_pos) : -1;}
+ int peek (size_t n) const override {return _pos+n < _str->length() ? _str->at(_pos+n) : -1;}
+ bool eof () const override {return _pos >= _str->length();}
+ void invalidate () override {_pos = _str->length();}
+
+ private:
+ const std::string *_str;
+ size_t _pos=0;
+};
+
+
+class CharInputBuffer : public InputBuffer {
+ public:
+ CharInputBuffer (const char *buf, size_t size) : _pos(buf), _size(buf ? size : 0) {}
+
+ int get () override {
+ if (_size == 0)
+ return -1;
+ else {
+ _size--;
+ return *_pos++;
+ }
+ }
+
+
+ void assign (const char *buf, size_t size) {
+ _pos = buf;
+ _size = size;
+ }
+
+ void assign (const char *buf) {assign(buf, std::strlen(buf));}
+ int peek () const override {return _size > 0 ? *_pos : -1;}
+ int peek (size_t n) const override {return _size >= n ? _pos[n] : -1;}
+ bool eof () const override {return _size == 0;}
+ void invalidate () override {_size = 0;}
+
+ private:
+ const char *_pos;
+ size_t _size;
+};
+
+
+class SplittedCharInputBuffer : public InputBuffer {
+ public:
+ SplittedCharInputBuffer (const char *buf1, size_t s1, const char *buf2, size_t s2);
+ int get () override;
+ int peek () const override;
+ int peek (size_t n) const override;
+ bool eof () const override {return _size[_index] == 0;}
+ void invalidate () override {_size[_index] = 0;}
+
+ private:
+ const char *_buf[2];
+ size_t _size[2];
+ int _index;
+};
+
+
+class TextStreamInputBuffer : public StreamInputBuffer {
+ public:
+ explicit TextStreamInputBuffer (std::istream &is) : StreamInputBuffer(is) {}
+ int get () override;
+ int line () const {return _line;}
+ int col () const {return _col;}
+
+ private:
+ int _line=1, _col=1;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/InputReader.cpp b/dviware/dvisvgm/src/InputReader.cpp
new file mode 100644
index 0000000000..5c9b9f5c62
--- /dev/null
+++ b/dviware/dvisvgm/src/InputReader.cpp
@@ -0,0 +1,413 @@
+/*************************************************************************
+** InputReader.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <cmath>
+#include <cstring>
+#include <functional>
+#include "InputReader.hpp"
+
+using namespace std;
+
+
+void StringMatcher::setPattern (const string &pattern) {
+ _pattern = pattern;
+ _charsRead = 0;
+ _borders.resize(pattern.length()+1);
+ // preprocessing to set up the border indexes
+ _borders[0] = -1;
+ int i=0, j=-1;
+ const int len = static_cast<int>(pattern.length());
+ while (i < len) {
+ while (j >= 0 && _pattern[i] != _pattern[j])
+ j = _borders[j];
+ _borders[++i] = ++j;
+ }
+}
+
+
+/** Reads characters from an input stream until the pattern string or EOF is reached.
+ * The matching string is read too if found. An empty pattern matches any character, i.e.
+ * reading stops after the first character.
+ * @return true if the pattern was found */
+bool StringMatcher::match (InputReader &ir) {
+ int c;
+ int i=0;
+ const int len = static_cast<int>(_pattern.length());
+ _charsRead = 0;
+ while ((c = ir.get()) >= 0) {
+ _charsRead++;
+ while (i >= 0 && c != _pattern[i])
+ i = _borders[i];
+ if (++i == len)
+ return true;
+ }
+ return false;
+}
+
+
+/** Reads characters from an input stream until the pattern string or EOF is reached
+ * and returns them as a string. The matching string is also appended to the returned string. */
+string StringMatcher::read (InputReader &ir) {
+ string ret;
+ int c;
+ int i=0;
+ const int len = static_cast<int>(_pattern.length());
+ while ((c = ir.get()) >= 0) {
+ ret += char(c);
+ while (i >= 0 && c != _pattern[i])
+ i = _borders[i];
+ if (++i == len)
+ break;
+ }
+ _charsRead = ret.length();
+ return ret;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+/** Skips n characters. */
+void InputReader::skip (size_t n) {
+ while (n-- > 0)
+ get();
+}
+
+
+/** Moves the buffer pointer to the next non-space character. A following call
+ * of get() returns this character. */
+void InputReader::skipSpace () {
+ while (isspace(peek()))
+ get();
+}
+
+
+/** Tries to find a given string and skips all characters preceding that string. If
+ * the string can't be found, all characters until EOF are skipped.
+ * @param[in] str string to look for (must not be longer than the maximal buffer size)
+ * @return true if str was found */
+bool InputReader::skipUntil (const char *str) {
+ StringMatcher matcher(str);
+ return matcher.match(*this);
+}
+
+
+/** Tries to find a given string and returns all characters including that string. If
+ * the string can't be found, all characters until EOF are read.
+ * @param[in] str string to look for (must not be longer than the maximal buffer size)
+ * @return the read characters */
+string InputReader::readUntil (const char *str) {
+ StringMatcher matcher(str);
+ return matcher.read(*this);
+}
+
+
+/** Looks for the first occurrence of a given character.
+ * @param[in] c character to lookup
+ * @return position of character relative to current location, -1 if character was not found */
+int InputReader::find (char c) const {
+ int pos = 0;
+ int cc;
+ while ((cc = peek(pos)) >= 0 && cc != c)
+ pos++;
+ return cc < 0 ? -1 : pos;
+}
+
+
+/** Checks if the next characters to be read match a given string.
+ * @param[in] s string to be matched
+ * @param[in] consume if true, the characters of the matched string are skipped
+ * @return true if s matches */
+bool InputReader::check (const char *s, bool consume) {
+ size_t count = 0;
+ for (const char *p=s; *p; p++) {
+ if (peek(count++) != *p)
+ return false;
+ }
+ if (consume)
+ skip(count);
+ return true;
+}
+
+
+/** Reads an integer from the buffer. All characters that are part of
+ * the read integer constant are skipped. If this function returns false,
+ * the buffer pointer points to the same position as before the function call.
+ * @param[out] val contains the read integer value on success
+ * @param[in] accept_sign if false, only positive integers (without sign) are accepted
+ * @return true if integer could be read */
+bool InputReader::parseInt (int &val, bool accept_sign) {
+ val = 0;
+ int fac=1;
+ int sign; // explicitly given sign
+ if (accept_sign && ((sign = peek()) == '+' || sign == '-')) {
+ if (isdigit(peek(1))) {
+ get(); // skip sign
+ if (sign == '-')
+ fac = -1;
+ }
+ else
+ return false;
+ }
+ else if (!isdigit(peek()))
+ return false;
+
+ while (isdigit(peek()))
+ val = val*10 + (get()-'0');
+ val *= fac;
+ return true;
+}
+
+
+bool InputReader::parseUInt (unsigned &val) {
+ val = 0;
+ if (!isdigit(peek()))
+ return false;
+ while (isdigit(peek()))
+ val = val*10 + (get()-'0');
+ return true;
+}
+
+
+bool InputReader::parseUInt (int base, unsigned &val) {
+ if (base < 2 || base > 32)
+ return false;
+
+ const char *digits = "0123456789abcdefghijklmnopqrstuvwxyz";
+ const char maxdigit = digits[base-1];
+ int c;
+ if (!isalnum(c = tolower(peek())) || c > maxdigit)
+ return false;
+
+ val = 0;
+ while (isalnum(c = tolower(peek())) && c <= maxdigit) {
+ get();
+ unsigned digit = c - (c <= '9' ? '0' : 'a'-10);
+ val = val*base + digit;
+ }
+ return true;
+}
+
+
+/** Reads a double from the buffer. All characters that are part of
+ * the read double constant are skipped. If this function returns false,
+ * the buffer pointer points to the same position as before the function call.
+ * @param[out] val contains the read double value on success
+ * @return number details: 0=no number, 'i'=integer, 'f'=floating point number */
+char InputReader::parseDouble (double &val) {
+ int fac=1;
+ int int_part=0;
+ bool is_float = false;
+ skipSpace();
+ int sign = peek();
+ if (parseInt(int_part)) { // match [+-]?[0-9]+\.?
+ if (peek() == '.') {
+ get();
+ is_float = true;
+ }
+ if (int_part < 0 || sign == '-') {
+ fac = -1;
+ int_part = -int_part;
+ }
+ }
+ else { // match [+-]?\.
+ if ((sign = peek()) == '+' || sign == '-') { // match [+-]?\.[0-9]
+ if (peek(1) != '.' || !isdigit(peek(2)))
+ return 0;
+ if (sign == '-')
+ fac = -1;
+ skip(2); // skip sign and dot
+ }
+ else if (peek() == '.' && isdigit(peek(1)))
+ get();
+ else
+ return 0;
+ is_float = true;
+ }
+ // parse fractional part
+ double frac_part=0.0;
+ for (double u=10; isdigit(peek()); u*=10)
+ frac_part += (get()-'0')/u;
+ val = (int_part + frac_part) * fac;
+ // parse exponent
+ int c;
+ if (tolower(peek()) == 'e' && (isdigit(c=peek(1)) || ((c == '+' || c == '-') && isdigit(peek(2))))) {
+ get(); // skip 'e'
+ int exp;
+ parseInt(exp);
+ val *= pow(10.0, exp);
+ is_float = true;
+ }
+ return is_float ? 'f' : 'i';
+}
+
+
+/** Reads an integer value from the buffer. If no valid integer constant
+ * could be found at the current position 0 is returned. */
+int InputReader::getInt () {
+ skipSpace();
+ int val;
+ return parseInt(val) ? val : 0;
+}
+
+
+/** Reads an double value from the buffer. If no valid double constant
+ * could be found at the current position 0 is returned. */
+double InputReader::getDouble () {
+ skipSpace();
+ double val;
+ return parseDouble(val) ? val : 0.0;
+}
+
+
+/** Reads a string that consists of alphabetic letters only. Reading stops as
+ * soon as a non-alphabetic character is found or EOF is reached. */
+string InputReader::getWord () {
+ string ret;
+ skipSpace();
+ while (isalpha(peek()))
+ ret += char(get());
+ return ret;
+}
+
+
+/** Reads a single punctuation character.
+ * @return the read character or 0 if there's no punctuation character at the current position */
+char InputReader::getPunct () {
+ skipSpace();
+ if (ispunct(peek()))
+ return char(get());
+ return 0;
+}
+
+
+/** Reads a string optionally enclosed by a given quotation character.
+ * Before reading the string, all leading whitespace is skipped. Then, the function checks
+ * for one of the the given quotation characters. If it is found, all characters until the
+ * second appearance of the same quotation char are appended to the result. Otherwise, an
+ * empty string is returned. If the quotation character is 0, the behavior of this function
+ * is identical to a call of getString().
+ * @param[in] quotechars recognized quotation characters bounding the string to be read
+ * @return the string read */
+string InputReader::getQuotedString (const char *quotechars) {
+ if (!quotechars)
+ return getString();
+
+ string ret;
+ skipSpace();
+ if (const char *quotechar = strchr(quotechars, peek())) {
+ get();
+ while (!eof() && peek() != *quotechar)
+ ret += char(get());
+ get();
+ }
+ return ret;
+}
+
+
+/** Reads a string delimited by whitespace and/or invisible characters.
+ * Before reading the string, all leading whitespace is skipped. Then, the function adds
+ * all printable characters to the result until a whitespace, an unprintable character, or
+ * EOF is found.
+ * @return the string read */
+string InputReader::getString () {
+ string ret;
+ skipSpace();
+ while (!eof() && !isspace(peek()) && isprint(peek()))
+ ret += char(get());
+ return ret;
+}
+
+
+/** Reads a given number of characters and returns the resulting string.
+ * @param n number of character to read
+ * @return the string read */
+string InputReader::getString (size_t n) {
+ string ret;
+ while (n-- > 0)
+ ret += char(get());
+ return ret;
+}
+
+
+/** Reads a string delimited by EOF or a single character from a given set of characters.
+ * @param[in] delim characters delimiting the string
+ * @return the read string */
+string InputReader::getString (const char *delim) {
+ if (!delim || !delim[0])
+ return getString();
+ string ret;
+ skipSpace();
+ while (!eof() && peek() > 0 && !strchr(delim, peek()))
+ ret += char(get());
+ return ret;
+}
+
+
+string InputReader::getLine () {
+ string ret;
+ skipSpace();
+ while (!eof() && peek() > 0 && peek() != '\n')
+ ret += char(get());
+ // trim trailing whitespace
+ ret.erase(std::find_if(ret.rbegin(), ret.rend(), not1(ptr_fun<int, int>(isspace))).base(), ret.end());
+ return ret;
+}
+
+
+/** Parses a sequence of key-value pairs of the form KEY=VALUE or KEY="VALUE".
+ * If parameter 'requireValues' is false, attributes may also consist of a key only.
+ * @param[out] attr the scanned atributes
+ * @param[in] requireValues true if all attributes require a value
+ * @param[in] quotechars recognized quote characters used to enclose the attribute values
+ * @return number of attributes scanned */
+int InputReader::parseAttributes (map<string,string> &attr, bool requireValues, const char *quotechars) {
+ while (!eof()) {
+ string key;
+ skipSpace();
+ if (!isalpha(peek())) // first character of attribute name must be a letter
+ break;
+ key += char(get());
+ while (isalnum(peek()) || strchr("-:._", peek()))
+ key += char(get());
+ skipSpace();
+ if (peek() == '=') {
+ get();
+ skipSpace();
+ string val = getQuotedString(quotechars);
+ attr.emplace(std::move(key), std::move(val));
+ }
+ else if (!requireValues)
+ attr.emplace(std::move(key), "");
+ }
+ return attr.size();
+}
+
+//////////////////////////////////////////
+
+
+int StreamInputReader::peek (size_t n) const {
+ if (n == 0)
+ return peek();
+ vector<char> chars(n);
+ _is.read(&chars[0], n);
+ int ret = peek();
+ for (int i=n-1; i >= 0; i--)
+ _is.putback(chars[i]);
+ return ret;
+}
diff --git a/dviware/dvisvgm/src/InputReader.hpp b/dviware/dvisvgm/src/InputReader.hpp
new file mode 100644
index 0000000000..0ca4f10772
--- /dev/null
+++ b/dviware/dvisvgm/src/InputReader.hpp
@@ -0,0 +1,107 @@
+/*************************************************************************
+** InputReader.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef INPUTREADER_HPP
+#define INPUTREADER_HPP
+
+#include <istream>
+#include <map>
+#include <string>
+#include <vector>
+#include "InputBuffer.hpp"
+
+class InputReader {
+ public:
+ virtual ~InputReader() =default;
+ virtual int get () =0;
+ virtual int peek () const =0;
+ virtual int peek (size_t n) const =0;
+ virtual bool eof () const =0;
+ virtual bool check (char c) const {return peek() == c;}
+ virtual bool check (const char *s, bool consume=true);
+ virtual void skip (size_t n);
+ virtual bool skipUntil (const char *str);
+ virtual std::string readUntil (const char *str);
+ virtual int find (char c) const;
+ virtual void skipSpace ();
+ virtual int getInt ();
+ virtual bool parseInt (int &val, bool accept_sign=true);
+ virtual bool parseUInt (int base, unsigned &val);
+ virtual bool parseUInt (unsigned &val);
+ virtual char parseDouble (double &val);
+ virtual double getDouble ();
+ virtual std::string getWord ();
+ virtual char getPunct ();
+ virtual std::string getQuotedString (const char *quotechars);
+ virtual std::string getString ();
+ virtual std::string getString (size_t n);
+ virtual std::string getString (const char *delim);
+ virtual std::string getLine ();
+ virtual int parseAttributes (std::map<std::string,std::string> &attr, bool requireValues, const char *quotechars=nullptr);
+ virtual operator bool () const {return !eof();}
+};
+
+
+class StreamInputReader : public InputReader {
+ public:
+ explicit StreamInputReader (std::istream &is) : _is(is) {}
+ int get () override {return _is.get();}
+ int peek () const override {return _is.peek();}
+ int peek (size_t n) const override;
+ bool eof () const override {return !_is || _is.eof();}
+ std::istream& getStream () {return _is;}
+
+ private:
+ std::istream &_is;
+};
+
+
+class BufferInputReader : public InputReader {
+ public:
+ explicit BufferInputReader (InputBuffer &ib) : _ib(&ib) {}
+ void assign (InputBuffer &ib) {_ib = &ib;}
+ int get () override {return _ib->get();}
+ int peek () const override {return _ib->peek();}
+ int peek (size_t n) const override {return _ib->peek(n);}
+ bool eof () const override {return _ib->eof();}
+
+ private:
+ InputBuffer *_ib;
+};
+
+
+/** Implementation of the Knuth-Morris-Pratt search algorithm.
+ * http://www.inf.fh-flensburg.de/lang/algorithmen/pattern/kmpen.htm */
+class StringMatcher {
+ public:
+ StringMatcher () : _charsRead(0) {}
+ explicit StringMatcher (const std::string &pattern) : _charsRead(0) {setPattern(pattern);}
+ void setPattern (const std::string &pattern);
+ bool match (InputReader &ir);
+ std::string read (InputReader &ir);
+ size_t charsRead () const {return _charsRead;}
+
+ private:
+ std::string _pattern;
+ std::vector<size_t> _borders;
+ size_t _charsRead;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/JFM.cpp b/dviware/dvisvgm/src/JFM.cpp
new file mode 100644
index 0000000000..a1835f6732
--- /dev/null
+++ b/dviware/dvisvgm/src/JFM.cpp
@@ -0,0 +1,103 @@
+/*************************************************************************
+** JFM.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <cstring>
+#include <fstream>
+#include <sstream>
+#include "JFM.hpp"
+#include "StreamReader.hpp"
+
+using namespace std;
+
+
+JFM::JFM (istream &is) {
+ is.seekg(0);
+ StreamReader reader(is);
+ uint16_t id = uint16_t(reader.readUnsigned(2)); // JFM ID (9 or 11)
+ if (id != 9 && id != 11) {
+ ostringstream oss;
+ oss << "invalid JFM identifier " << id << " (9 or 11 expected)";
+ throw FontMetricException(oss.str());
+ }
+ _vertical = (id == 9);
+ uint16_t nt = uint16_t(reader.readUnsigned(2)); // length of character type table
+ uint16_t lf = uint16_t(reader.readUnsigned(2)); // length of entire file in 4 byte words
+ uint16_t lh = uint16_t(reader.readUnsigned(2)); // length of header in 4 byte words
+ uint16_t bc = uint16_t(reader.readUnsigned(2)); // smallest character code in font
+ uint16_t ec = uint16_t(reader.readUnsigned(2)); // largest character code in font
+ uint16_t nw = uint16_t(reader.readUnsigned(2)); // number of words in width table
+ uint16_t nh = uint16_t(reader.readUnsigned(2)); // number of words in height table
+ uint16_t nd = uint16_t(reader.readUnsigned(2)); // number of words in depth table
+ uint16_t ni = uint16_t(reader.readUnsigned(2)); // number of words in italic corr. table
+ uint16_t nl = uint16_t(reader.readUnsigned(2)); // number of words in glue/kern table
+ uint16_t nk = uint16_t(reader.readUnsigned(2)); // number of words in kern table
+ uint16_t ng = uint16_t(reader.readUnsigned(2)); // number of words in glue table
+ uint16_t np = uint16_t(reader.readUnsigned(2)); // number of font parameter words
+
+ if (7+nt+lh+(ec-bc+1)+nw+nh+nd+ni+nl+nk+ng+np != lf)
+ throw FontMetricException("inconsistent length values");
+
+ setCharRange(bc, ec);
+ readHeader(reader);
+ is.seekg(28+lh*4);
+ readTables(reader, nt, nw, nh, nd, ni);
+ is.seekg(4*(lf-np), ios::beg);
+ readParameters(reader, np); // JFM files provide 9 parameters but we don't need all of them
+}
+
+
+void JFM::readTables (StreamReader &reader, int nt, int nw, int nh, int nd, int ni) {
+ // determine smallest charcode with chartype > 0
+ uint32_t minchar=0xFFFFFFFF, maxchar=0;
+ for (int i=0; i < nt; i++) {
+ // support new JFM spec by texjporg
+ uint32_t c = reader.readUnsigned(2);
+ c += 0x10000 * reader.readUnsigned(1);
+ uint8_t t = uint8_t(reader.readUnsigned(1));
+ if (t > 0) {
+ minchar = min(minchar, c);
+ maxchar = max(maxchar, c);
+ }
+ }
+ // build charcode to chartype map
+ if (minchar <= maxchar) {
+ _minchar = minchar;
+ _charTypeTable.resize(maxchar-minchar+1, 0);
+ reader.seek(-nt*4, ios::cur);
+ for (int i=0; i < nt; i++) {
+ // support new JFM spec by texjporg
+ uint32_t c = reader.readUnsigned(2);
+ c += 0x10000 * reader.readUnsigned(1);
+ uint8_t t = uint8_t(reader.readUnsigned(1));
+ if (c >= minchar)
+ _charTypeTable[c-minchar] = t;
+ }
+ }
+ TFM::readTables(reader, nw, nh, nd, ni);
+}
+
+
+int JFM::charIndex (int c) const {
+ uint8_t chartype = 0;
+ if (!_charTypeTable.empty() && uint32_t(c) >= _minchar && uint32_t(c) < _minchar+_charTypeTable.size())
+ chartype = _charTypeTable[c-_minchar];
+ return TFM::charIndex(chartype);
+}
diff --git a/dviware/dvisvgm/src/JFM.hpp b/dviware/dvisvgm/src/JFM.hpp
new file mode 100644
index 0000000000..c51f371a5e
--- /dev/null
+++ b/dviware/dvisvgm/src/JFM.hpp
@@ -0,0 +1,45 @@
+/*************************************************************************
+** JFM.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef JFM_HPP
+#define JFM_HPP
+
+#include <istream>
+#include "TFM.hpp"
+
+
+class JFM : public TFM {
+ public:
+ explicit JFM (std::istream &is);
+ bool verticalLayout () const override {return _vertical;}
+ uint32_t minChar () const {return _minchar;}
+ uint32_t maxChar () const {return static_cast<uint32_t>(_minchar+_charTypeTable.size()-1);}
+
+ protected:
+ void readTables (StreamReader &reader, int nt, int nw, int nh, int nd, int ni);
+ int charIndex (int c) const override;
+
+ private:
+ uint32_t _minchar; ///< character code of first entry in character type table
+ bool _vertical; ///< true if metrics refer to vertical text layout
+ std::vector<uint8_t> _charTypeTable;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/Length.cpp b/dviware/dvisvgm/src/Length.cpp
new file mode 100644
index 0000000000..ba7dd2a225
--- /dev/null
+++ b/dviware/dvisvgm/src/Length.cpp
@@ -0,0 +1,157 @@
+/*************************************************************************
+** Length.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <sstream>
+#include "InputReader.hpp"
+#include "Length.hpp"
+
+using namespace std;
+
+void Length::set (const string &lenstr) {
+ switch (lenstr.length()) {
+ case 0:
+ _pt = 0;
+ break;
+ case 1:
+ if (isdigit(lenstr[0]))
+ _pt = lenstr[0] - '0';
+ else
+ throw UnitException(string("invalid length: ")+lenstr);
+ break;
+ default:
+ istringstream iss(lenstr);
+ StreamInputReader ir(iss);
+ double val;
+ if (!ir.parseDouble(val))
+ throw UnitException(string("invalid length: ")+lenstr);
+ string unit = ir.getWord();
+ set(val, unit);
+ }
+}
+
+
+void Length::set (double val, Unit unit) {
+ switch (unit) {
+ case Unit::PT: _pt = val; break;
+ case Unit::BP: _pt = val/pt2bp; break;
+ case Unit::IN: _pt = val/pt2in; break;
+ case Unit::CM: _pt = val/pt2cm; break;
+ case Unit::MM: _pt = val/pt2mm; break;
+ case Unit::PC: _pt = val/pt2pc; break;
+ case Unit::DD: _pt = val/pt2dd; break;
+ case Unit::CC: _pt = val/pt2cc; break;
+ case Unit::SP: _pt = val/pt2sp; break;
+ default:
+ // this isn't supposed to happen
+ ostringstream oss;
+ oss << "invalid length unit: (" << static_cast<int>(unit) << ")";
+ throw UnitException(oss.str());
+ }
+}
+
+
+double Length::get (Unit unit) const {
+ switch (unit) {
+ case Unit::PT: return pt();
+ case Unit::BP: return bp();
+ case Unit::IN: return in();
+ case Unit::CM: return cm();
+ case Unit::MM: return mm();
+ case Unit::PC: return pc();
+ case Unit::DD: return dd();
+ case Unit::CC: return cc();
+ case Unit::SP: return sp();
+ }
+ // this isn't supposed to happen
+ ostringstream oss;
+ oss << "invalid length unit: (" << static_cast<int>(unit) << ")";
+ throw UnitException(oss.str());
+}
+
+
+string Length::toString (Unit unit) const {
+ ostringstream oss;
+ oss << get(unit) << unitToString(unit);
+ return oss.str();
+}
+
+
+static constexpr int unit_id (int c1, int c2) {
+ return (c1 << 8) | c2;
+}
+
+Length::Unit Length::stringToUnit (const std::string &unitstr) {
+ if (unitstr.length() == 2) {
+ switch (unit_id(unitstr[0], unitstr[1])) {
+ case unit_id('p','t'): return Unit::PT;
+ case unit_id('b','p'): return Unit::BP;
+ case unit_id('i','n'): return Unit::IN;
+ case unit_id('c','m'): return Unit::CM;
+ case unit_id('m','m'): return Unit::MM;
+ case unit_id('p','c'): return Unit::PC;
+ case unit_id('d','d'): return Unit::DD;
+ case unit_id('c','c'): return Unit::CC;
+ case unit_id('s','p'): return Unit::SP;
+ }
+ }
+ throw UnitException(string("invalid length unit: ")+unitstr);
+}
+
+
+string Length::unitToString (Unit unit) {
+ switch (unit) {
+ case Unit::PT: return "pt";
+ case Unit::BP: return "bp";
+ case Unit::IN: return "in";
+ case Unit::CM: return "cm";
+ case Unit::MM: return "mm";
+ case Unit::PC: return "pc";
+ case Unit::DD: return "dd";
+ case Unit::CC: return "cc";
+ case Unit::SP: return "sp";
+ }
+ // this isn't supposed to happen
+ return "??";
+}
+
+
+void Length::set (double val, string unitstr) {
+ if (unitstr.empty())
+ unitstr = "pt";
+ else if (unitstr.length() != 2)
+ throw UnitException(string("invalid length unit: ")+unitstr);
+ set(val, stringToUnit(unitstr));
+}
+
+
+map<string,Length::Unit> Length::getUnits () {
+ map<string,Length::Unit> units = {
+ {"pt", Unit::PT},
+ {"bp", Unit::BP},
+ {"in", Unit::IN},
+ {"cm", Unit::CM},
+ {"mm", Unit::MM},
+ {"pc", Unit::PC},
+ {"dd", Unit::DD},
+ {"cc", Unit::CC},
+ {"sp", Unit::SP},
+ };
+ return units;
+}
diff --git a/dviware/dvisvgm/src/Length.hpp b/dviware/dvisvgm/src/Length.hpp
new file mode 100644
index 0000000000..4454eec168
--- /dev/null
+++ b/dviware/dvisvgm/src/Length.hpp
@@ -0,0 +1,92 @@
+/*************************************************************************
+** Length.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef LENGTH_HPP
+#define LENGTH_HPP
+
+#include <map>
+#include <vector>
+#include "MessageException.hpp"
+
+#ifdef IN
+#undef IN
+#endif
+
+struct UnitException : MessageException {
+ explicit UnitException (const std::string &msg) : MessageException(msg) {}
+};
+
+
+class Length {
+ public:
+ enum class Unit {PT, BP, CM, MM, IN, PC, DD, CC, SP};
+
+ public:
+ constexpr Length () : _pt(0) {}
+ explicit constexpr Length (long double pt) : _pt(static_cast<double>(pt)) {}
+ Length (double val, Unit unit) {set(val, unit);}
+ Length (double val, const std::string &unitstr) {set(val, unitstr);}
+ explicit Length (const std::string &lenstr) {set(lenstr);}
+ void set (double val, Unit unit);
+ void set (double val, std::string unit);
+ void set (const std::string &lenstr);
+ double pt () const {return _pt;}
+ double in () const {return _pt*pt2in;}
+ double bp () const {return _pt*pt2bp;}
+ double cm () const {return _pt*pt2cm;}
+ double mm () const {return _pt*pt2mm;}
+ double pc () const {return _pt*pt2pc;}
+ double dd () const {return _pt*pt2dd;}
+ double cc () const {return _pt*pt2cc;}
+ double sp () const {return _pt*pt2sp;}
+ double get (Unit unit) const;
+ std::string toString (Unit unit) const;
+ bool operator == (Length len) const {return _pt == len._pt;}
+ bool operator != (Length len) const {return _pt != len._pt;}
+
+ static Unit stringToUnit (const std::string &unitstr);
+ static std::string unitToString (Unit unit);
+ static std::map<std::string,Unit> getUnits ();
+
+ public:
+ static constexpr double pt2in = 1.0/72.27;
+ static constexpr double pt2bp = pt2in*72;
+ static constexpr double pt2cm = pt2in*2.54;
+ static constexpr double pt2mm = pt2cm*10;
+ static constexpr double pt2pc = 1.0/12;
+ static constexpr double pt2dd = 1157.0/1238;
+ static constexpr double pt2cc = pt2dd/12;
+ static constexpr double pt2sp = 65536.0;
+
+ private:
+ double _pt; // length in TeX point units (72.27pt = 1in)
+};
+
+
+constexpr Length operator "" _pt (long double pt) {return Length(pt);}
+constexpr Length operator "" _bp (long double bp) {return Length(bp/Length::pt2bp);}
+constexpr Length operator "" _mm (long double mm) {return Length(mm/Length::pt2mm);}
+constexpr Length operator "" _in (long double in) {return Length(in/Length::pt2in);}
+constexpr Length operator "" _pt (unsigned long long pt) {return Length(static_cast<double>(pt));}
+constexpr Length operator "" _bp (unsigned long long bp) {return Length(static_cast<double>(bp)/Length::pt2bp);}
+constexpr Length operator "" _mm (unsigned long long mm) {return Length(static_cast<double>(mm)/Length::pt2mm);}
+constexpr Length operator "" _in (unsigned long long in) {return Length(static_cast<double>(in)/Length::pt2in);}
+
+#endif
diff --git a/dviware/dvisvgm/src/MD5HashFunction.hpp b/dviware/dvisvgm/src/MD5HashFunction.hpp
new file mode 100644
index 0000000000..4ff18f3f3e
--- /dev/null
+++ b/dviware/dvisvgm/src/MD5HashFunction.hpp
@@ -0,0 +1,58 @@
+/*************************************************************************
+** MD5HashFunction.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef MD5HASHFUNCTION_HPP
+#define MD5HASHFUNCTION_HPP
+
+#include <config.h>
+#ifdef HAVE_OPENSSL_MD5_H
+# include <openssl/md5.h>
+#else
+# include <md5.h>
+#endif
+
+#include "HashFunction.hpp"
+
+class MD5HashFunction : public HashFunction {
+ public:
+ explicit MD5HashFunction () {MD5_Init(&_context);}
+ MD5HashFunction (const char *data, size_t length) : MD5HashFunction() {update(data, length);}
+ explicit MD5HashFunction (const std::string &data) : MD5HashFunction() {update(data);}
+ explicit MD5HashFunction (const std::vector<uint8_t> &data) : MD5HashFunction() {update(data);}
+ int digestSize () const override {return 16;}
+ void reset () override {MD5_Init(&_context);}
+ void update (const char *data, size_t length) override {MD5_Update(&_context, data, length);}
+ void update (const std::string &data) override {update(data.data(), data.length());}
+ void update (const std::vector<uint8_t> &data) override {update(reinterpret_cast<const char*>(data.data()), data.size());}
+
+ std::vector<uint8_t> digestValue () const override {
+ std::vector<uint8_t> hash(16);
+ MD5_CTX savedContext = _context;
+ MD5_Final(hash.data(), &_context); // also erases the context structure
+ _context = savedContext;
+ return hash;
+ }
+
+ private:
+ mutable MD5_CTX _context;
+};
+
+#endif
+
diff --git a/dviware/dvisvgm/src/Makefile.am b/dviware/dvisvgm/src/Makefile.am
new file mode 100644
index 0000000000..6bdbea32e6
--- /dev/null
+++ b/dviware/dvisvgm/src/Makefile.am
@@ -0,0 +1,226 @@
+## This file is part of dvisvgm
+## Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de>
+##
+## Process this file with automake.
+
+bin_PROGRAMS = dvisvgm
+noinst_LTLIBRARIES = libdvisvgm.la
+SUBDIRS = optimizer
+
+dvisvgm_SOURCES = \
+ CommandLine.hpp \
+ dvisvgm.cpp
+
+include ../libs/defs.am
+
+dvisvgm_LDADD = \
+ $(noinst_LTLIBRARIES) \
+ ../libs/clipper/libclipper.a \
+ $(FREETYPE_LIBS) \
+ $(FONTFORGE_LIBS) \
+ $(POTRACE_LIBS) \
+ $(XXHASH_LIBS) \
+ $(ZLIB_LIBS)
+
+if ENABLE_WOFF
+dvisvgm_LDADD += \
+ ../libs/ff-woff/libfontforge.a \
+ $(WOFF2_LIBS) \
+ $(BROTLI_LIBS)
+endif
+
+if USE_BUNDLED_MD5
+dvisvgm_LDADD += ../libs/md5/libmd5.a
+else
+dvisvgm_LDADD += $(LIBCRYPTO_LIBS)
+endif
+
+dvisvgm_DEPENDENCIES = $(noinst_LTLIBRARIES)
+
+libdvisvgm_la_SOURCES = \
+ AGLTable.hpp \
+ BasicDVIReader.hpp BasicDVIReader.cpp \
+ Bezier.hpp Bezier.cpp \
+ BgColorSpecialHandler.hpp BgColorSpecialHandler.cpp \
+ Bitmap.hpp Bitmap.cpp \
+ BoundingBox.hpp BoundingBox.cpp \
+ Calculator.hpp Calculator.cpp \
+ Character.hpp \
+ CharMapID.hpp CharMapID.cpp \
+ CLCommandLine.hpp CLCommandLine.cpp \
+ CMap.hpp CMap.cpp \
+ CMapManager.hpp CMapManager.cpp \
+ CMapReader.hpp CMapReader.cpp \
+ CLOption.hpp \
+ Color.hpp Color.cpp \
+ ColorSpecialHandler.hpp ColorSpecialHandler.cpp \
+ CommandLine.hpp \
+ CRC32.hpp CRC32.cpp \
+ Directory.hpp Directory.cpp \
+ DVIActions.hpp \
+ DLLoader.hpp DLLoader.cpp \
+ DVIReader.hpp DVIReader.cpp \
+ DvisvgmSpecialHandler.hpp DvisvgmSpecialHandler.cpp \
+ DVIToSVG.hpp DVIToSVG.cpp \
+ DVIToSVGActions.hpp DVIToSVGActions.cpp \
+ EmSpecialHandler.hpp EmSpecialHandler.cpp \
+ EncFile.hpp EncFile.cpp \
+ EPSFile.hpp EPSFile.cpp \
+ EPSToSVG.hpp \
+ FileFinder.hpp FileFinder.cpp \
+ FilePath.hpp FilePath.cpp \
+ FileSystem.hpp FileSystem.cpp \
+ FixWord.hpp \
+ Font.hpp Font.cpp \
+ FontCache.hpp FontCache.cpp \
+ FontEncoding.hpp FontEncoding.cpp \
+ FontEngine.hpp FontEngine.cpp \
+ FontManager.hpp FontManager.cpp \
+ FontMap.hpp FontMap.cpp \
+ FontMetrics.hpp FontMetrics.cpp \
+ FontStyle.hpp \
+ FontWriter.hpp FontWriter.cpp \
+ GFGlyphTracer.hpp GFGlyphTracer.cpp \
+ GFReader.hpp GFReader.cpp \
+ GFTracer.hpp GFTracer.cpp \
+ Ghostscript.hpp Ghostscript.cpp \
+ Glyph.hpp \
+ GlyphTracerMessages.hpp \
+ GraphicsPath.hpp \
+ HashFunction.hpp HashFunction.cpp \
+ HtmlSpecialHandler.hpp HtmlSpecialHandler.cpp \
+ HyperlinkManager.hpp HyperlinkManager.cpp \
+ ImageToSVG.hpp ImageToSVG.cpp \
+ InputBuffer.hpp InputBuffer.cpp \
+ InputReader.hpp InputReader.cpp \
+ JFM.hpp JFM.cpp \
+ Length.hpp Length.cpp \
+ macros.hpp \
+ MapLine.hpp MapLine.cpp \
+ Matrix.hpp Matrix.cpp \
+ MD5HashFunction.hpp \
+ Message.hpp Message.cpp \
+ MessageException.hpp \
+ MetafontWrapper.hpp MetafontWrapper.cpp \
+ NoPsSpecialHandler.hpp NoPsSpecialHandler.cpp \
+ NumericRanges.hpp \
+ PageRanges.hpp PageRanges.cpp \
+ PageSize.hpp PageSize.cpp \
+ Pair.hpp \
+ PapersizeSpecialHandler.hpp PapersizeSpecialHandler.cpp \
+ PathClipper.hpp PathClipper.cpp \
+ PDFParser.hpp PDFParser.cpp \
+ PdfSpecialHandler.hpp PdfSpecialHandler.cpp \
+ PDFToSVG.hpp \
+ PreScanDVIReader.hpp PreScanDVIReader.cpp \
+ Process.hpp Process.cpp \
+ psdefs.cpp \
+ PSFilter.hpp \
+ PSInterpreter.hpp PSInterpreter.cpp \
+ PSPattern.hpp PSPattern.cpp \
+ PSPreviewFilter.hpp PSPreviewFilter.cpp \
+ PsSpecialHandler.hpp PsSpecialHandler.cpp \
+ RangeMap.hpp RangeMap.cpp \
+ ShadingPatch.hpp ShadingPatch.cpp \
+ SignalHandler.hpp SignalHandler.cpp \
+ SourceInput.hpp SourceInput.cpp \
+ SpecialActions.hpp \
+ SpecialHandler.hpp \
+ SpecialManager.hpp SpecialManager.cpp \
+ StreamReader.hpp StreamReader.cpp \
+ StreamWriter.hpp StreamWriter.cpp \
+ Subfont.hpp Subfont.cpp \
+ SVGCharHandler.hpp SVGCharHandler.cpp \
+ SVGCharHandlerFactory.hpp SVGCharHandlerFactory.cpp \
+ SVGCharPathHandler.hpp SVGCharPathHandler.cpp \
+ SVGCharTspanTextHandler.hpp SVGCharTspanTextHandler.cpp \
+ SVGOutput.hpp SVGOutput.cpp \
+ SVGSingleCharTextHandler.hpp SVGSingleCharTextHandler.cpp \
+ SVGTree.hpp SVGTree.cpp \
+ System.hpp System.cpp \
+ TensorProductPatch.hpp TensorProductPatch.cpp \
+ Terminal.hpp Terminal.cpp \
+ TFM.hpp TFM.cpp \
+ ToUnicodeMap.hpp ToUnicodeMap.cpp \
+ TpicSpecialHandler.hpp TpicSpecialHandler.cpp \
+ TriangularPatch.hpp TriangularPatch.cpp \
+ TrueTypeFont.hpp TrueTypeFont.cpp \
+ TTFAutohint.hpp TTFAutohint.cpp \
+ Unicode.hpp Unicode.cpp \
+ utility.hpp utility.cpp \
+ VectorIterator.hpp \
+ VectorStream.hpp \
+ VFActions.hpp \
+ VFReader.hpp VFReader.cpp \
+ windows.hpp \
+ XMLDocument.hpp XMLDocument.cpp \
+ XMLNode.hpp XMLNode.cpp \
+ XMLString.hpp XMLString.cpp \
+ XXHashFunction.hpp \
+ ZLibOutputStream.hpp
+
+if ENABLE_WOFF
+libdvisvgm_la_SOURCES += ffwrapper.c ffwrapper.h
+endif
+
+libdvisvgm_la_LIBADD = optimizer/liboptimizer.la
+
+EXTRA_DIST = options.xml options.dtd iapi.h ierrors.h MiKTeXCom.hpp MiKTeXCom.cpp
+
+AM_CFLAGS = -Wall \
+ $(ZLIB_CFLAGS) \
+ $(CODE_COVERAGE_CFLAGS)
+
+AM_CXXFLAGS = -Wall -Wnon-virtual-dtor \
+ -I$(dvisvgm_srcdir)/libs/clipper \
+ -I$(dvisvgm_srcdir)/libs/variant/include \
+ $(KPSE_CFLAGS) \
+ $(FREETYPE_CFLAGS) \
+ $(ZLIB_CFLAGS) \
+ $(CODE_COVERAGE_CFLAGS)
+
+AM_CXXFLAGS += \
+ $(POTRACE_CFLAGS) \
+ $(XXHASH_CFLAGS)
+
+AM_LDFLAGS = \
+ $(KPSE_LIBS) \
+ $(CODE_COVERAGE_LDFLAGS)
+
+if ENABLE_WOFF
+AM_CFLAGS += \
+ -I$(dvisvgm_srcdir)/libs/ff-woff/fontforge \
+ -I$(dvisvgm_srcdir)/libs/ff-woff/inc
+
+AM_CXXFLAGS += $(TTFAUTOHINT_CFLAGS)
+
+AM_CXXFLAGS += \
+ $(BROTLI_CFLAGS) \
+ $(WOFF2_CFLAGS)
+
+if USE_BUNDLED_MD5
+AM_CXXFLAGS += -I$(dvisvgm_srcdir)/libs/md5
+else
+AM_CXXFLAGS += $(LIBCRYPTO_CFLAGS)
+endif
+
+AM_LDFLAGS += $(TTFAUTOHINT_LIBS)
+endif
+
+
+# the command-line parser class is generated from options.xml by opt2cpp
+$(srcdir)/CommandLine.hpp: options.xml
+ rm -f $@
+ python $(srcdir)/opt2cpp.py $< >$@
+
+# Create a C string definition containing the PostScript routines psdefs.ps needed by class PSInterpreter
+$(srcdir)/psdefs.cpp: psdefs.ps
+ if test -f $<; then \
+ ps2c PSInterpreter::PSDEFS $< >$@; \
+ fi
+
+psdefs.ps: ;
+
+@CODE_COVERAGE_RULES@
+
+CLEANFILES = *.gcda *.gcno
diff --git a/dviware/dvisvgm/src/Makefile.in b/dviware/dvisvgm/src/Makefile.in
new file mode 100644
index 0000000000..6369ce9995
--- /dev/null
+++ b/dviware/dvisvgm/src/Makefile.in
@@ -0,0 +1,1424 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+target_triplet = @target@
+bin_PROGRAMS = dvisvgm$(EXEEXT)
+@HAVE_BROTLI_FALSE@am__append_1 = -I$(dvisvgm_srcdir)/libs/brotli/include
+@HAVE_BROTLI_FALSE@am__append_2 = ../libs/brotli/libbrotli.a
+@HAVE_WOFF2_FALSE@am__append_3 = -I$(dvisvgm_srcdir)/libs/woff2/include
+@HAVE_WOFF2_FALSE@am__append_4 = ../libs/woff2/libwoff2.a
+@ENABLE_WOFF_TRUE@am__append_5 = \
+@ENABLE_WOFF_TRUE@ ../libs/ff-woff/libfontforge.a \
+@ENABLE_WOFF_TRUE@ $(WOFF2_LIBS) \
+@ENABLE_WOFF_TRUE@ $(BROTLI_LIBS)
+
+@USE_BUNDLED_MD5_TRUE@am__append_6 = ../libs/md5/libmd5.a
+@USE_BUNDLED_MD5_FALSE@am__append_7 = $(LIBCRYPTO_LIBS)
+@ENABLE_WOFF_TRUE@am__append_8 = ffwrapper.c ffwrapper.h
+@ENABLE_WOFF_TRUE@am__append_9 = \
+@ENABLE_WOFF_TRUE@ -I$(dvisvgm_srcdir)/libs/ff-woff/fontforge \
+@ENABLE_WOFF_TRUE@ -I$(dvisvgm_srcdir)/libs/ff-woff/inc
+
+@ENABLE_WOFF_TRUE@am__append_10 = $(TTFAUTOHINT_CFLAGS) \
+@ENABLE_WOFF_TRUE@ $(BROTLI_CFLAGS) $(WOFF2_CFLAGS)
+@ENABLE_WOFF_TRUE@@USE_BUNDLED_MD5_TRUE@am__append_11 = -I$(dvisvgm_srcdir)/libs/md5
+@ENABLE_WOFF_TRUE@@USE_BUNDLED_MD5_FALSE@am__append_12 = $(LIBCRYPTO_CFLAGS)
+@ENABLE_WOFF_TRUE@am__append_13 = $(TTFAUTOHINT_LIBS)
+subdir = src
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4/ax_code_coverage.m4 \
+ $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES = version.hpp
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libdvisvgm_la_DEPENDENCIES = optimizer/liboptimizer.la
+am__libdvisvgm_la_SOURCES_DIST = AGLTable.hpp BasicDVIReader.hpp \
+ BasicDVIReader.cpp Bezier.hpp Bezier.cpp \
+ BgColorSpecialHandler.hpp BgColorSpecialHandler.cpp Bitmap.hpp \
+ Bitmap.cpp BoundingBox.hpp BoundingBox.cpp Calculator.hpp \
+ Calculator.cpp Character.hpp CharMapID.hpp CharMapID.cpp \
+ CLCommandLine.hpp CLCommandLine.cpp CMap.hpp CMap.cpp \
+ CMapManager.hpp CMapManager.cpp CMapReader.hpp CMapReader.cpp \
+ CLOption.hpp Color.hpp Color.cpp ColorSpecialHandler.hpp \
+ ColorSpecialHandler.cpp CommandLine.hpp CRC32.hpp CRC32.cpp \
+ Directory.hpp Directory.cpp DVIActions.hpp DLLoader.hpp \
+ DLLoader.cpp DVIReader.hpp DVIReader.cpp \
+ DvisvgmSpecialHandler.hpp DvisvgmSpecialHandler.cpp \
+ DVIToSVG.hpp DVIToSVG.cpp DVIToSVGActions.hpp \
+ DVIToSVGActions.cpp EmSpecialHandler.hpp EmSpecialHandler.cpp \
+ EncFile.hpp EncFile.cpp EPSFile.hpp EPSFile.cpp EPSToSVG.hpp \
+ FileFinder.hpp FileFinder.cpp FilePath.hpp FilePath.cpp \
+ FileSystem.hpp FileSystem.cpp FixWord.hpp Font.hpp Font.cpp \
+ FontCache.hpp FontCache.cpp FontEncoding.hpp FontEncoding.cpp \
+ FontEngine.hpp FontEngine.cpp FontManager.hpp FontManager.cpp \
+ FontMap.hpp FontMap.cpp FontMetrics.hpp FontMetrics.cpp \
+ FontStyle.hpp FontWriter.hpp FontWriter.cpp GFGlyphTracer.hpp \
+ GFGlyphTracer.cpp GFReader.hpp GFReader.cpp GFTracer.hpp \
+ GFTracer.cpp Ghostscript.hpp Ghostscript.cpp Glyph.hpp \
+ GlyphTracerMessages.hpp GraphicsPath.hpp HashFunction.hpp \
+ HashFunction.cpp HtmlSpecialHandler.hpp HtmlSpecialHandler.cpp \
+ HyperlinkManager.hpp HyperlinkManager.cpp ImageToSVG.hpp \
+ ImageToSVG.cpp InputBuffer.hpp InputBuffer.cpp InputReader.hpp \
+ InputReader.cpp JFM.hpp JFM.cpp Length.hpp Length.cpp \
+ macros.hpp MapLine.hpp MapLine.cpp Matrix.hpp Matrix.cpp \
+ MD5HashFunction.hpp Message.hpp Message.cpp \
+ MessageException.hpp MetafontWrapper.hpp MetafontWrapper.cpp \
+ NoPsSpecialHandler.hpp NoPsSpecialHandler.cpp \
+ NumericRanges.hpp PageRanges.hpp PageRanges.cpp PageSize.hpp \
+ PageSize.cpp Pair.hpp PapersizeSpecialHandler.hpp \
+ PapersizeSpecialHandler.cpp PathClipper.hpp PathClipper.cpp \
+ PDFParser.hpp PDFParser.cpp PdfSpecialHandler.hpp \
+ PdfSpecialHandler.cpp PDFToSVG.hpp PreScanDVIReader.hpp \
+ PreScanDVIReader.cpp Process.hpp Process.cpp psdefs.cpp \
+ PSFilter.hpp PSInterpreter.hpp PSInterpreter.cpp PSPattern.hpp \
+ PSPattern.cpp PSPreviewFilter.hpp PSPreviewFilter.cpp \
+ PsSpecialHandler.hpp PsSpecialHandler.cpp RangeMap.hpp \
+ RangeMap.cpp ShadingPatch.hpp ShadingPatch.cpp \
+ SignalHandler.hpp SignalHandler.cpp SourceInput.hpp \
+ SourceInput.cpp SpecialActions.hpp SpecialHandler.hpp \
+ SpecialManager.hpp SpecialManager.cpp StreamReader.hpp \
+ StreamReader.cpp StreamWriter.hpp StreamWriter.cpp Subfont.hpp \
+ Subfont.cpp SVGCharHandler.hpp SVGCharHandler.cpp \
+ SVGCharHandlerFactory.hpp SVGCharHandlerFactory.cpp \
+ SVGCharPathHandler.hpp SVGCharPathHandler.cpp \
+ SVGCharTspanTextHandler.hpp SVGCharTspanTextHandler.cpp \
+ SVGOutput.hpp SVGOutput.cpp SVGSingleCharTextHandler.hpp \
+ SVGSingleCharTextHandler.cpp SVGTree.hpp SVGTree.cpp \
+ System.hpp System.cpp TensorProductPatch.hpp \
+ TensorProductPatch.cpp Terminal.hpp Terminal.cpp TFM.hpp \
+ TFM.cpp ToUnicodeMap.hpp ToUnicodeMap.cpp \
+ TpicSpecialHandler.hpp TpicSpecialHandler.cpp \
+ TriangularPatch.hpp TriangularPatch.cpp TrueTypeFont.hpp \
+ TrueTypeFont.cpp TTFAutohint.hpp TTFAutohint.cpp Unicode.hpp \
+ Unicode.cpp utility.hpp utility.cpp VectorIterator.hpp \
+ VectorStream.hpp VFActions.hpp VFReader.hpp VFReader.cpp \
+ windows.hpp XMLDocument.hpp XMLDocument.cpp XMLNode.hpp \
+ XMLNode.cpp XMLString.hpp XMLString.cpp XXHashFunction.hpp \
+ ZLibOutputStream.hpp ffwrapper.c ffwrapper.h
+@ENABLE_WOFF_TRUE@am__objects_1 = ffwrapper.lo
+am_libdvisvgm_la_OBJECTS = BasicDVIReader.lo Bezier.lo \
+ BgColorSpecialHandler.lo Bitmap.lo BoundingBox.lo \
+ Calculator.lo CharMapID.lo CLCommandLine.lo CMap.lo \
+ CMapManager.lo CMapReader.lo Color.lo ColorSpecialHandler.lo \
+ CRC32.lo Directory.lo DLLoader.lo DVIReader.lo \
+ DvisvgmSpecialHandler.lo DVIToSVG.lo DVIToSVGActions.lo \
+ EmSpecialHandler.lo EncFile.lo EPSFile.lo FileFinder.lo \
+ FilePath.lo FileSystem.lo Font.lo FontCache.lo FontEncoding.lo \
+ FontEngine.lo FontManager.lo FontMap.lo FontMetrics.lo \
+ FontWriter.lo GFGlyphTracer.lo GFReader.lo GFTracer.lo \
+ Ghostscript.lo HashFunction.lo HtmlSpecialHandler.lo \
+ HyperlinkManager.lo ImageToSVG.lo InputBuffer.lo \
+ InputReader.lo JFM.lo Length.lo MapLine.lo Matrix.lo \
+ Message.lo MetafontWrapper.lo NoPsSpecialHandler.lo \
+ PageRanges.lo PageSize.lo PapersizeSpecialHandler.lo \
+ PathClipper.lo PDFParser.lo PdfSpecialHandler.lo \
+ PreScanDVIReader.lo Process.lo psdefs.lo PSInterpreter.lo \
+ PSPattern.lo PSPreviewFilter.lo PsSpecialHandler.lo \
+ RangeMap.lo ShadingPatch.lo SignalHandler.lo SourceInput.lo \
+ SpecialManager.lo StreamReader.lo StreamWriter.lo Subfont.lo \
+ SVGCharHandler.lo SVGCharHandlerFactory.lo \
+ SVGCharPathHandler.lo SVGCharTspanTextHandler.lo SVGOutput.lo \
+ SVGSingleCharTextHandler.lo SVGTree.lo System.lo \
+ TensorProductPatch.lo Terminal.lo TFM.lo ToUnicodeMap.lo \
+ TpicSpecialHandler.lo TriangularPatch.lo TrueTypeFont.lo \
+ TTFAutohint.lo Unicode.lo utility.lo VFReader.lo \
+ XMLDocument.lo XMLNode.lo XMLString.lo $(am__objects_1)
+libdvisvgm_la_OBJECTS = $(am_libdvisvgm_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+am_dvisvgm_OBJECTS = dvisvgm.$(OBJEXT)
+dvisvgm_OBJECTS = $(am_dvisvgm_OBJECTS)
+am__DEPENDENCIES_1 =
+am__DEPENDENCIES_2 = $(am__append_4)
+am__DEPENDENCIES_3 = $(am__append_2)
+@ENABLE_WOFF_TRUE@am__DEPENDENCIES_4 = ../libs/ff-woff/libfontforge.a \
+@ENABLE_WOFF_TRUE@ $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_3)
+@USE_BUNDLED_MD5_FALSE@am__DEPENDENCIES_5 = $(am__DEPENDENCIES_1)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/BasicDVIReader.Plo \
+ ./$(DEPDIR)/Bezier.Plo ./$(DEPDIR)/BgColorSpecialHandler.Plo \
+ ./$(DEPDIR)/Bitmap.Plo ./$(DEPDIR)/BoundingBox.Plo \
+ ./$(DEPDIR)/CLCommandLine.Plo ./$(DEPDIR)/CMap.Plo \
+ ./$(DEPDIR)/CMapManager.Plo ./$(DEPDIR)/CMapReader.Plo \
+ ./$(DEPDIR)/CRC32.Plo ./$(DEPDIR)/Calculator.Plo \
+ ./$(DEPDIR)/CharMapID.Plo ./$(DEPDIR)/Color.Plo \
+ ./$(DEPDIR)/ColorSpecialHandler.Plo ./$(DEPDIR)/DLLoader.Plo \
+ ./$(DEPDIR)/DVIReader.Plo ./$(DEPDIR)/DVIToSVG.Plo \
+ ./$(DEPDIR)/DVIToSVGActions.Plo ./$(DEPDIR)/Directory.Plo \
+ ./$(DEPDIR)/DvisvgmSpecialHandler.Plo ./$(DEPDIR)/EPSFile.Plo \
+ ./$(DEPDIR)/EmSpecialHandler.Plo ./$(DEPDIR)/EncFile.Plo \
+ ./$(DEPDIR)/FileFinder.Plo ./$(DEPDIR)/FilePath.Plo \
+ ./$(DEPDIR)/FileSystem.Plo ./$(DEPDIR)/Font.Plo \
+ ./$(DEPDIR)/FontCache.Plo ./$(DEPDIR)/FontEncoding.Plo \
+ ./$(DEPDIR)/FontEngine.Plo ./$(DEPDIR)/FontManager.Plo \
+ ./$(DEPDIR)/FontMap.Plo ./$(DEPDIR)/FontMetrics.Plo \
+ ./$(DEPDIR)/FontWriter.Plo ./$(DEPDIR)/GFGlyphTracer.Plo \
+ ./$(DEPDIR)/GFReader.Plo ./$(DEPDIR)/GFTracer.Plo \
+ ./$(DEPDIR)/Ghostscript.Plo ./$(DEPDIR)/HashFunction.Plo \
+ ./$(DEPDIR)/HtmlSpecialHandler.Plo \
+ ./$(DEPDIR)/HyperlinkManager.Plo ./$(DEPDIR)/ImageToSVG.Plo \
+ ./$(DEPDIR)/InputBuffer.Plo ./$(DEPDIR)/InputReader.Plo \
+ ./$(DEPDIR)/JFM.Plo ./$(DEPDIR)/Length.Plo \
+ ./$(DEPDIR)/MapLine.Plo ./$(DEPDIR)/Matrix.Plo \
+ ./$(DEPDIR)/Message.Plo ./$(DEPDIR)/MetafontWrapper.Plo \
+ ./$(DEPDIR)/NoPsSpecialHandler.Plo ./$(DEPDIR)/PDFParser.Plo \
+ ./$(DEPDIR)/PSInterpreter.Plo ./$(DEPDIR)/PSPattern.Plo \
+ ./$(DEPDIR)/PSPreviewFilter.Plo ./$(DEPDIR)/PageRanges.Plo \
+ ./$(DEPDIR)/PageSize.Plo \
+ ./$(DEPDIR)/PapersizeSpecialHandler.Plo \
+ ./$(DEPDIR)/PathClipper.Plo ./$(DEPDIR)/PdfSpecialHandler.Plo \
+ ./$(DEPDIR)/PreScanDVIReader.Plo ./$(DEPDIR)/Process.Plo \
+ ./$(DEPDIR)/PsSpecialHandler.Plo ./$(DEPDIR)/RangeMap.Plo \
+ ./$(DEPDIR)/SVGCharHandler.Plo \
+ ./$(DEPDIR)/SVGCharHandlerFactory.Plo \
+ ./$(DEPDIR)/SVGCharPathHandler.Plo \
+ ./$(DEPDIR)/SVGCharTspanTextHandler.Plo \
+ ./$(DEPDIR)/SVGOutput.Plo \
+ ./$(DEPDIR)/SVGSingleCharTextHandler.Plo \
+ ./$(DEPDIR)/SVGTree.Plo ./$(DEPDIR)/ShadingPatch.Plo \
+ ./$(DEPDIR)/SignalHandler.Plo ./$(DEPDIR)/SourceInput.Plo \
+ ./$(DEPDIR)/SpecialManager.Plo ./$(DEPDIR)/StreamReader.Plo \
+ ./$(DEPDIR)/StreamWriter.Plo ./$(DEPDIR)/Subfont.Plo \
+ ./$(DEPDIR)/System.Plo ./$(DEPDIR)/TFM.Plo \
+ ./$(DEPDIR)/TTFAutohint.Plo ./$(DEPDIR)/TensorProductPatch.Plo \
+ ./$(DEPDIR)/Terminal.Plo ./$(DEPDIR)/ToUnicodeMap.Plo \
+ ./$(DEPDIR)/TpicSpecialHandler.Plo \
+ ./$(DEPDIR)/TriangularPatch.Plo ./$(DEPDIR)/TrueTypeFont.Plo \
+ ./$(DEPDIR)/Unicode.Plo ./$(DEPDIR)/VFReader.Plo \
+ ./$(DEPDIR)/XMLDocument.Plo ./$(DEPDIR)/XMLNode.Plo \
+ ./$(DEPDIR)/XMLString.Plo ./$(DEPDIR)/dvisvgm.Po \
+ ./$(DEPDIR)/ffwrapper.Plo ./$(DEPDIR)/psdefs.Plo \
+ ./$(DEPDIR)/utility.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+SOURCES = $(libdvisvgm_la_SOURCES) $(dvisvgm_SOURCES)
+DIST_SOURCES = $(am__libdvisvgm_la_SOURCES_DIST) $(dvisvgm_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/../libs/defs.am $(srcdir)/Makefile.in \
+ $(srcdir)/version.hpp.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_CPPFLAGS = @AM_CPPFLAGS@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AM_LDFLAGS = $(KPSE_LIBS) $(CODE_COVERAGE_LDFLAGS) $(am__append_13)
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BROTLI_CFLAGS = @BROTLI_CFLAGS@ $(am__append_1)
+BROTLI_LIBS = @BROTLI_LIBS@ $(am__append_2)
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CODE_COVERAGE_CFLAGS = @CODE_COVERAGE_CFLAGS@
+CODE_COVERAGE_CPPFLAGS = @CODE_COVERAGE_CPPFLAGS@
+CODE_COVERAGE_CXXFLAGS = @CODE_COVERAGE_CXXFLAGS@
+CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@
+CODE_COVERAGE_LDFLAGS = @CODE_COVERAGE_LDFLAGS@
+CODE_COVERAGE_LIBS = @CODE_COVERAGE_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DATE = @DATE@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FREETYPE_CFLAGS = @FREETYPE_CFLAGS@
+FREETYPE_LIBS = @FREETYPE_LIBS@
+GCOV = @GCOV@
+GENHTML = @GENHTML@
+GREP = @GREP@
+HAVE_CXX11 = @HAVE_CXX11@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KPSE_CFLAGS = @KPSE_CFLAGS@
+KPSE_LIBS = @KPSE_LIBS@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBCRYPTO_CFLAGS = @LIBCRYPTO_CFLAGS@
+LIBCRYPTO_LIBS = @LIBCRYPTO_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+TTFAUTOHINT_CFLAGS = @TTFAUTOHINT_CFLAGS@
+TTFAUTOHINT_LIBS = @TTFAUTOHINT_LIBS@
+VERSION = @VERSION@
+WOFF2_CFLAGS = @WOFF2_CFLAGS@ $(am__append_3)
+WOFF2_LIBS = @WOFF2_LIBS@ $(am__append_4)
+XMLTO = @XMLTO@
+XSLTPROC = @XSLTPROC@
+ZLIB_CFLAGS = @ZLIB_CFLAGS@
+ZLIB_LIBS = @ZLIB_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+dvisvgm_srcdir = @dvisvgm_srcdir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target = @target@
+target_alias = @target_alias@
+target_cpu = @target_cpu@
+target_os = @target_os@
+target_vendor = @target_vendor@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libdvisvgm.la
+SUBDIRS = optimizer
+dvisvgm_SOURCES = \
+ CommandLine.hpp \
+ dvisvgm.cpp
+
+@HAVE_POTRACE_FALSE@POTRACE_CFLAGS = -I$(dvisvgm_srcdir)/libs/potrace
+@HAVE_POTRACE_FALSE@POTRACE_LIBS = ../libs/potrace/libpotrace.a
+@HAVE_XXHASH_FALSE@XXHASH_CFLAGS = -I$(dvisvgm_srcdir)/libs/xxHash
+@HAVE_XXHASH_FALSE@XXHASH_LIBS = ../libs/xxHash/libxxhash.a
+dvisvgm_LDADD = $(noinst_LTLIBRARIES) ../libs/clipper/libclipper.a \
+ $(FREETYPE_LIBS) $(FONTFORGE_LIBS) $(POTRACE_LIBS) \
+ $(XXHASH_LIBS) $(ZLIB_LIBS) $(am__append_5) $(am__append_6) \
+ $(am__append_7)
+dvisvgm_DEPENDENCIES = $(noinst_LTLIBRARIES)
+libdvisvgm_la_SOURCES = AGLTable.hpp BasicDVIReader.hpp \
+ BasicDVIReader.cpp Bezier.hpp Bezier.cpp \
+ BgColorSpecialHandler.hpp BgColorSpecialHandler.cpp Bitmap.hpp \
+ Bitmap.cpp BoundingBox.hpp BoundingBox.cpp Calculator.hpp \
+ Calculator.cpp Character.hpp CharMapID.hpp CharMapID.cpp \
+ CLCommandLine.hpp CLCommandLine.cpp CMap.hpp CMap.cpp \
+ CMapManager.hpp CMapManager.cpp CMapReader.hpp CMapReader.cpp \
+ CLOption.hpp Color.hpp Color.cpp ColorSpecialHandler.hpp \
+ ColorSpecialHandler.cpp CommandLine.hpp CRC32.hpp CRC32.cpp \
+ Directory.hpp Directory.cpp DVIActions.hpp DLLoader.hpp \
+ DLLoader.cpp DVIReader.hpp DVIReader.cpp \
+ DvisvgmSpecialHandler.hpp DvisvgmSpecialHandler.cpp \
+ DVIToSVG.hpp DVIToSVG.cpp DVIToSVGActions.hpp \
+ DVIToSVGActions.cpp EmSpecialHandler.hpp EmSpecialHandler.cpp \
+ EncFile.hpp EncFile.cpp EPSFile.hpp EPSFile.cpp EPSToSVG.hpp \
+ FileFinder.hpp FileFinder.cpp FilePath.hpp FilePath.cpp \
+ FileSystem.hpp FileSystem.cpp FixWord.hpp Font.hpp Font.cpp \
+ FontCache.hpp FontCache.cpp FontEncoding.hpp FontEncoding.cpp \
+ FontEngine.hpp FontEngine.cpp FontManager.hpp FontManager.cpp \
+ FontMap.hpp FontMap.cpp FontMetrics.hpp FontMetrics.cpp \
+ FontStyle.hpp FontWriter.hpp FontWriter.cpp GFGlyphTracer.hpp \
+ GFGlyphTracer.cpp GFReader.hpp GFReader.cpp GFTracer.hpp \
+ GFTracer.cpp Ghostscript.hpp Ghostscript.cpp Glyph.hpp \
+ GlyphTracerMessages.hpp GraphicsPath.hpp HashFunction.hpp \
+ HashFunction.cpp HtmlSpecialHandler.hpp HtmlSpecialHandler.cpp \
+ HyperlinkManager.hpp HyperlinkManager.cpp ImageToSVG.hpp \
+ ImageToSVG.cpp InputBuffer.hpp InputBuffer.cpp InputReader.hpp \
+ InputReader.cpp JFM.hpp JFM.cpp Length.hpp Length.cpp \
+ macros.hpp MapLine.hpp MapLine.cpp Matrix.hpp Matrix.cpp \
+ MD5HashFunction.hpp Message.hpp Message.cpp \
+ MessageException.hpp MetafontWrapper.hpp MetafontWrapper.cpp \
+ NoPsSpecialHandler.hpp NoPsSpecialHandler.cpp \
+ NumericRanges.hpp PageRanges.hpp PageRanges.cpp PageSize.hpp \
+ PageSize.cpp Pair.hpp PapersizeSpecialHandler.hpp \
+ PapersizeSpecialHandler.cpp PathClipper.hpp PathClipper.cpp \
+ PDFParser.hpp PDFParser.cpp PdfSpecialHandler.hpp \
+ PdfSpecialHandler.cpp PDFToSVG.hpp PreScanDVIReader.hpp \
+ PreScanDVIReader.cpp Process.hpp Process.cpp psdefs.cpp \
+ PSFilter.hpp PSInterpreter.hpp PSInterpreter.cpp PSPattern.hpp \
+ PSPattern.cpp PSPreviewFilter.hpp PSPreviewFilter.cpp \
+ PsSpecialHandler.hpp PsSpecialHandler.cpp RangeMap.hpp \
+ RangeMap.cpp ShadingPatch.hpp ShadingPatch.cpp \
+ SignalHandler.hpp SignalHandler.cpp SourceInput.hpp \
+ SourceInput.cpp SpecialActions.hpp SpecialHandler.hpp \
+ SpecialManager.hpp SpecialManager.cpp StreamReader.hpp \
+ StreamReader.cpp StreamWriter.hpp StreamWriter.cpp Subfont.hpp \
+ Subfont.cpp SVGCharHandler.hpp SVGCharHandler.cpp \
+ SVGCharHandlerFactory.hpp SVGCharHandlerFactory.cpp \
+ SVGCharPathHandler.hpp SVGCharPathHandler.cpp \
+ SVGCharTspanTextHandler.hpp SVGCharTspanTextHandler.cpp \
+ SVGOutput.hpp SVGOutput.cpp SVGSingleCharTextHandler.hpp \
+ SVGSingleCharTextHandler.cpp SVGTree.hpp SVGTree.cpp \
+ System.hpp System.cpp TensorProductPatch.hpp \
+ TensorProductPatch.cpp Terminal.hpp Terminal.cpp TFM.hpp \
+ TFM.cpp ToUnicodeMap.hpp ToUnicodeMap.cpp \
+ TpicSpecialHandler.hpp TpicSpecialHandler.cpp \
+ TriangularPatch.hpp TriangularPatch.cpp TrueTypeFont.hpp \
+ TrueTypeFont.cpp TTFAutohint.hpp TTFAutohint.cpp Unicode.hpp \
+ Unicode.cpp utility.hpp utility.cpp VectorIterator.hpp \
+ VectorStream.hpp VFActions.hpp VFReader.hpp VFReader.cpp \
+ windows.hpp XMLDocument.hpp XMLDocument.cpp XMLNode.hpp \
+ XMLNode.cpp XMLString.hpp XMLString.cpp XXHashFunction.hpp \
+ ZLibOutputStream.hpp $(am__append_8)
+libdvisvgm_la_LIBADD = optimizer/liboptimizer.la
+EXTRA_DIST = options.xml options.dtd iapi.h ierrors.h MiKTeXCom.hpp MiKTeXCom.cpp
+AM_CFLAGS = -Wall $(ZLIB_CFLAGS) $(CODE_COVERAGE_CFLAGS) \
+ $(am__append_9)
+AM_CXXFLAGS = -Wall -Wnon-virtual-dtor \
+ -I$(dvisvgm_srcdir)/libs/clipper \
+ -I$(dvisvgm_srcdir)/libs/variant/include $(KPSE_CFLAGS) \
+ $(FREETYPE_CFLAGS) $(ZLIB_CFLAGS) $(CODE_COVERAGE_CFLAGS) \
+ $(POTRACE_CFLAGS) $(XXHASH_CFLAGS) $(am__append_10) \
+ $(am__append_11) $(am__append_12)
+CLEANFILES = *.gcda *.gcno
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .c .cpp .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(srcdir)/../libs/defs.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+$(srcdir)/../libs/defs.am $(am__empty):
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+version.hpp: $(top_builddir)/config.status $(srcdir)/version.hpp.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libdvisvgm.la: $(libdvisvgm_la_OBJECTS) $(libdvisvgm_la_DEPENDENCIES) $(EXTRA_libdvisvgm_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(CXXLINK) $(libdvisvgm_la_OBJECTS) $(libdvisvgm_la_LIBADD) $(LIBS)
+
+dvisvgm$(EXEEXT): $(dvisvgm_OBJECTS) $(dvisvgm_DEPENDENCIES) $(EXTRA_dvisvgm_DEPENDENCIES)
+ @rm -f dvisvgm$(EXEEXT)
+ $(AM_V_CXXLD)$(CXXLINK) $(dvisvgm_OBJECTS) $(dvisvgm_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/BasicDVIReader.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Bezier.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/BgColorSpecialHandler.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Bitmap.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/BoundingBox.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/CLCommandLine.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/CMap.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/CMapManager.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/CMapReader.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/CRC32.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Calculator.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/CharMapID.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Color.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ColorSpecialHandler.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DLLoader.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DVIReader.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DVIToSVG.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DVIToSVGActions.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Directory.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DvisvgmSpecialHandler.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/EPSFile.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/EmSpecialHandler.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/EncFile.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FileFinder.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FilePath.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FileSystem.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Font.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FontCache.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FontEncoding.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FontEngine.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FontManager.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FontMap.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FontMetrics.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FontWriter.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/GFGlyphTracer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/GFReader.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/GFTracer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Ghostscript.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/HashFunction.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/HtmlSpecialHandler.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/HyperlinkManager.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ImageToSVG.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/InputBuffer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/InputReader.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/JFM.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Length.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/MapLine.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Matrix.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Message.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/MetafontWrapper.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NoPsSpecialHandler.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PDFParser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PSInterpreter.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PSPattern.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PSPreviewFilter.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PageRanges.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PageSize.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PapersizeSpecialHandler.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PathClipper.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PdfSpecialHandler.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PreScanDVIReader.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Process.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PsSpecialHandler.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RangeMap.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SVGCharHandler.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SVGCharHandlerFactory.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SVGCharPathHandler.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SVGCharTspanTextHandler.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SVGOutput.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SVGSingleCharTextHandler.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SVGTree.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ShadingPatch.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SignalHandler.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SourceInput.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SpecialManager.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/StreamReader.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/StreamWriter.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Subfont.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/System.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TFM.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TTFAutohint.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TensorProductPatch.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Terminal.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ToUnicodeMap.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TpicSpecialHandler.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TriangularPatch.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TrueTypeFont.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Unicode.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/VFReader.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/XMLDocument.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/XMLNode.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/XMLString.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dvisvgm.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ffwrapper.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/psdefs.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utility.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+.cpp.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cpp.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cpp.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCXX_TRUE@ $(LTCXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(bindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-binPROGRAMS clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/BasicDVIReader.Plo
+ -rm -f ./$(DEPDIR)/Bezier.Plo
+ -rm -f ./$(DEPDIR)/BgColorSpecialHandler.Plo
+ -rm -f ./$(DEPDIR)/Bitmap.Plo
+ -rm -f ./$(DEPDIR)/BoundingBox.Plo
+ -rm -f ./$(DEPDIR)/CLCommandLine.Plo
+ -rm -f ./$(DEPDIR)/CMap.Plo
+ -rm -f ./$(DEPDIR)/CMapManager.Plo
+ -rm -f ./$(DEPDIR)/CMapReader.Plo
+ -rm -f ./$(DEPDIR)/CRC32.Plo
+ -rm -f ./$(DEPDIR)/Calculator.Plo
+ -rm -f ./$(DEPDIR)/CharMapID.Plo
+ -rm -f ./$(DEPDIR)/Color.Plo
+ -rm -f ./$(DEPDIR)/ColorSpecialHandler.Plo
+ -rm -f ./$(DEPDIR)/DLLoader.Plo
+ -rm -f ./$(DEPDIR)/DVIReader.Plo
+ -rm -f ./$(DEPDIR)/DVIToSVG.Plo
+ -rm -f ./$(DEPDIR)/DVIToSVGActions.Plo
+ -rm -f ./$(DEPDIR)/Directory.Plo
+ -rm -f ./$(DEPDIR)/DvisvgmSpecialHandler.Plo
+ -rm -f ./$(DEPDIR)/EPSFile.Plo
+ -rm -f ./$(DEPDIR)/EmSpecialHandler.Plo
+ -rm -f ./$(DEPDIR)/EncFile.Plo
+ -rm -f ./$(DEPDIR)/FileFinder.Plo
+ -rm -f ./$(DEPDIR)/FilePath.Plo
+ -rm -f ./$(DEPDIR)/FileSystem.Plo
+ -rm -f ./$(DEPDIR)/Font.Plo
+ -rm -f ./$(DEPDIR)/FontCache.Plo
+ -rm -f ./$(DEPDIR)/FontEncoding.Plo
+ -rm -f ./$(DEPDIR)/FontEngine.Plo
+ -rm -f ./$(DEPDIR)/FontManager.Plo
+ -rm -f ./$(DEPDIR)/FontMap.Plo
+ -rm -f ./$(DEPDIR)/FontMetrics.Plo
+ -rm -f ./$(DEPDIR)/FontWriter.Plo
+ -rm -f ./$(DEPDIR)/GFGlyphTracer.Plo
+ -rm -f ./$(DEPDIR)/GFReader.Plo
+ -rm -f ./$(DEPDIR)/GFTracer.Plo
+ -rm -f ./$(DEPDIR)/Ghostscript.Plo
+ -rm -f ./$(DEPDIR)/HashFunction.Plo
+ -rm -f ./$(DEPDIR)/HtmlSpecialHandler.Plo
+ -rm -f ./$(DEPDIR)/HyperlinkManager.Plo
+ -rm -f ./$(DEPDIR)/ImageToSVG.Plo
+ -rm -f ./$(DEPDIR)/InputBuffer.Plo
+ -rm -f ./$(DEPDIR)/InputReader.Plo
+ -rm -f ./$(DEPDIR)/JFM.Plo
+ -rm -f ./$(DEPDIR)/Length.Plo
+ -rm -f ./$(DEPDIR)/MapLine.Plo
+ -rm -f ./$(DEPDIR)/Matrix.Plo
+ -rm -f ./$(DEPDIR)/Message.Plo
+ -rm -f ./$(DEPDIR)/MetafontWrapper.Plo
+ -rm -f ./$(DEPDIR)/NoPsSpecialHandler.Plo
+ -rm -f ./$(DEPDIR)/PDFParser.Plo
+ -rm -f ./$(DEPDIR)/PSInterpreter.Plo
+ -rm -f ./$(DEPDIR)/PSPattern.Plo
+ -rm -f ./$(DEPDIR)/PSPreviewFilter.Plo
+ -rm -f ./$(DEPDIR)/PageRanges.Plo
+ -rm -f ./$(DEPDIR)/PageSize.Plo
+ -rm -f ./$(DEPDIR)/PapersizeSpecialHandler.Plo
+ -rm -f ./$(DEPDIR)/PathClipper.Plo
+ -rm -f ./$(DEPDIR)/PdfSpecialHandler.Plo
+ -rm -f ./$(DEPDIR)/PreScanDVIReader.Plo
+ -rm -f ./$(DEPDIR)/Process.Plo
+ -rm -f ./$(DEPDIR)/PsSpecialHandler.Plo
+ -rm -f ./$(DEPDIR)/RangeMap.Plo
+ -rm -f ./$(DEPDIR)/SVGCharHandler.Plo
+ -rm -f ./$(DEPDIR)/SVGCharHandlerFactory.Plo
+ -rm -f ./$(DEPDIR)/SVGCharPathHandler.Plo
+ -rm -f ./$(DEPDIR)/SVGCharTspanTextHandler.Plo
+ -rm -f ./$(DEPDIR)/SVGOutput.Plo
+ -rm -f ./$(DEPDIR)/SVGSingleCharTextHandler.Plo
+ -rm -f ./$(DEPDIR)/SVGTree.Plo
+ -rm -f ./$(DEPDIR)/ShadingPatch.Plo
+ -rm -f ./$(DEPDIR)/SignalHandler.Plo
+ -rm -f ./$(DEPDIR)/SourceInput.Plo
+ -rm -f ./$(DEPDIR)/SpecialManager.Plo
+ -rm -f ./$(DEPDIR)/StreamReader.Plo
+ -rm -f ./$(DEPDIR)/StreamWriter.Plo
+ -rm -f ./$(DEPDIR)/Subfont.Plo
+ -rm -f ./$(DEPDIR)/System.Plo
+ -rm -f ./$(DEPDIR)/TFM.Plo
+ -rm -f ./$(DEPDIR)/TTFAutohint.Plo
+ -rm -f ./$(DEPDIR)/TensorProductPatch.Plo
+ -rm -f ./$(DEPDIR)/Terminal.Plo
+ -rm -f ./$(DEPDIR)/ToUnicodeMap.Plo
+ -rm -f ./$(DEPDIR)/TpicSpecialHandler.Plo
+ -rm -f ./$(DEPDIR)/TriangularPatch.Plo
+ -rm -f ./$(DEPDIR)/TrueTypeFont.Plo
+ -rm -f ./$(DEPDIR)/Unicode.Plo
+ -rm -f ./$(DEPDIR)/VFReader.Plo
+ -rm -f ./$(DEPDIR)/XMLDocument.Plo
+ -rm -f ./$(DEPDIR)/XMLNode.Plo
+ -rm -f ./$(DEPDIR)/XMLString.Plo
+ -rm -f ./$(DEPDIR)/dvisvgm.Po
+ -rm -f ./$(DEPDIR)/ffwrapper.Plo
+ -rm -f ./$(DEPDIR)/psdefs.Plo
+ -rm -f ./$(DEPDIR)/utility.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f ./$(DEPDIR)/BasicDVIReader.Plo
+ -rm -f ./$(DEPDIR)/Bezier.Plo
+ -rm -f ./$(DEPDIR)/BgColorSpecialHandler.Plo
+ -rm -f ./$(DEPDIR)/Bitmap.Plo
+ -rm -f ./$(DEPDIR)/BoundingBox.Plo
+ -rm -f ./$(DEPDIR)/CLCommandLine.Plo
+ -rm -f ./$(DEPDIR)/CMap.Plo
+ -rm -f ./$(DEPDIR)/CMapManager.Plo
+ -rm -f ./$(DEPDIR)/CMapReader.Plo
+ -rm -f ./$(DEPDIR)/CRC32.Plo
+ -rm -f ./$(DEPDIR)/Calculator.Plo
+ -rm -f ./$(DEPDIR)/CharMapID.Plo
+ -rm -f ./$(DEPDIR)/Color.Plo
+ -rm -f ./$(DEPDIR)/ColorSpecialHandler.Plo
+ -rm -f ./$(DEPDIR)/DLLoader.Plo
+ -rm -f ./$(DEPDIR)/DVIReader.Plo
+ -rm -f ./$(DEPDIR)/DVIToSVG.Plo
+ -rm -f ./$(DEPDIR)/DVIToSVGActions.Plo
+ -rm -f ./$(DEPDIR)/Directory.Plo
+ -rm -f ./$(DEPDIR)/DvisvgmSpecialHandler.Plo
+ -rm -f ./$(DEPDIR)/EPSFile.Plo
+ -rm -f ./$(DEPDIR)/EmSpecialHandler.Plo
+ -rm -f ./$(DEPDIR)/EncFile.Plo
+ -rm -f ./$(DEPDIR)/FileFinder.Plo
+ -rm -f ./$(DEPDIR)/FilePath.Plo
+ -rm -f ./$(DEPDIR)/FileSystem.Plo
+ -rm -f ./$(DEPDIR)/Font.Plo
+ -rm -f ./$(DEPDIR)/FontCache.Plo
+ -rm -f ./$(DEPDIR)/FontEncoding.Plo
+ -rm -f ./$(DEPDIR)/FontEngine.Plo
+ -rm -f ./$(DEPDIR)/FontManager.Plo
+ -rm -f ./$(DEPDIR)/FontMap.Plo
+ -rm -f ./$(DEPDIR)/FontMetrics.Plo
+ -rm -f ./$(DEPDIR)/FontWriter.Plo
+ -rm -f ./$(DEPDIR)/GFGlyphTracer.Plo
+ -rm -f ./$(DEPDIR)/GFReader.Plo
+ -rm -f ./$(DEPDIR)/GFTracer.Plo
+ -rm -f ./$(DEPDIR)/Ghostscript.Plo
+ -rm -f ./$(DEPDIR)/HashFunction.Plo
+ -rm -f ./$(DEPDIR)/HtmlSpecialHandler.Plo
+ -rm -f ./$(DEPDIR)/HyperlinkManager.Plo
+ -rm -f ./$(DEPDIR)/ImageToSVG.Plo
+ -rm -f ./$(DEPDIR)/InputBuffer.Plo
+ -rm -f ./$(DEPDIR)/InputReader.Plo
+ -rm -f ./$(DEPDIR)/JFM.Plo
+ -rm -f ./$(DEPDIR)/Length.Plo
+ -rm -f ./$(DEPDIR)/MapLine.Plo
+ -rm -f ./$(DEPDIR)/Matrix.Plo
+ -rm -f ./$(DEPDIR)/Message.Plo
+ -rm -f ./$(DEPDIR)/MetafontWrapper.Plo
+ -rm -f ./$(DEPDIR)/NoPsSpecialHandler.Plo
+ -rm -f ./$(DEPDIR)/PDFParser.Plo
+ -rm -f ./$(DEPDIR)/PSInterpreter.Plo
+ -rm -f ./$(DEPDIR)/PSPattern.Plo
+ -rm -f ./$(DEPDIR)/PSPreviewFilter.Plo
+ -rm -f ./$(DEPDIR)/PageRanges.Plo
+ -rm -f ./$(DEPDIR)/PageSize.Plo
+ -rm -f ./$(DEPDIR)/PapersizeSpecialHandler.Plo
+ -rm -f ./$(DEPDIR)/PathClipper.Plo
+ -rm -f ./$(DEPDIR)/PdfSpecialHandler.Plo
+ -rm -f ./$(DEPDIR)/PreScanDVIReader.Plo
+ -rm -f ./$(DEPDIR)/Process.Plo
+ -rm -f ./$(DEPDIR)/PsSpecialHandler.Plo
+ -rm -f ./$(DEPDIR)/RangeMap.Plo
+ -rm -f ./$(DEPDIR)/SVGCharHandler.Plo
+ -rm -f ./$(DEPDIR)/SVGCharHandlerFactory.Plo
+ -rm -f ./$(DEPDIR)/SVGCharPathHandler.Plo
+ -rm -f ./$(DEPDIR)/SVGCharTspanTextHandler.Plo
+ -rm -f ./$(DEPDIR)/SVGOutput.Plo
+ -rm -f ./$(DEPDIR)/SVGSingleCharTextHandler.Plo
+ -rm -f ./$(DEPDIR)/SVGTree.Plo
+ -rm -f ./$(DEPDIR)/ShadingPatch.Plo
+ -rm -f ./$(DEPDIR)/SignalHandler.Plo
+ -rm -f ./$(DEPDIR)/SourceInput.Plo
+ -rm -f ./$(DEPDIR)/SpecialManager.Plo
+ -rm -f ./$(DEPDIR)/StreamReader.Plo
+ -rm -f ./$(DEPDIR)/StreamWriter.Plo
+ -rm -f ./$(DEPDIR)/Subfont.Plo
+ -rm -f ./$(DEPDIR)/System.Plo
+ -rm -f ./$(DEPDIR)/TFM.Plo
+ -rm -f ./$(DEPDIR)/TTFAutohint.Plo
+ -rm -f ./$(DEPDIR)/TensorProductPatch.Plo
+ -rm -f ./$(DEPDIR)/Terminal.Plo
+ -rm -f ./$(DEPDIR)/ToUnicodeMap.Plo
+ -rm -f ./$(DEPDIR)/TpicSpecialHandler.Plo
+ -rm -f ./$(DEPDIR)/TriangularPatch.Plo
+ -rm -f ./$(DEPDIR)/TrueTypeFont.Plo
+ -rm -f ./$(DEPDIR)/Unicode.Plo
+ -rm -f ./$(DEPDIR)/VFReader.Plo
+ -rm -f ./$(DEPDIR)/XMLDocument.Plo
+ -rm -f ./$(DEPDIR)/XMLNode.Plo
+ -rm -f ./$(DEPDIR)/XMLString.Plo
+ -rm -f ./$(DEPDIR)/dvisvgm.Po
+ -rm -f ./$(DEPDIR)/ffwrapper.Plo
+ -rm -f ./$(DEPDIR)/psdefs.Plo
+ -rm -f ./$(DEPDIR)/utility.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-binPROGRAMS \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-binPROGRAMS install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ installdirs-am maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-binPROGRAMS
+
+.PRECIOUS: Makefile
+
+
+# the command-line parser class is generated from options.xml by opt2cpp
+$(srcdir)/CommandLine.hpp: options.xml
+ rm -f $@
+ python $(srcdir)/opt2cpp.py $< >$@
+
+# Create a C string definition containing the PostScript routines psdefs.ps needed by class PSInterpreter
+$(srcdir)/psdefs.cpp: psdefs.ps
+ if test -f $<; then \
+ ps2c PSInterpreter::PSDEFS $< >$@; \
+ fi
+
+psdefs.ps: ;
+
+@CODE_COVERAGE_RULES@
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/dviware/dvisvgm/src/MapLine.cpp b/dviware/dvisvgm/src/MapLine.cpp
new file mode 100644
index 0000000000..483fec4b71
--- /dev/null
+++ b/dviware/dvisvgm/src/MapLine.cpp
@@ -0,0 +1,283 @@
+/*************************************************************************
+** MapLine.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <cstring>
+#include <sstream>
+#include "InputBuffer.hpp"
+#include "InputReader.hpp"
+#include "MapLine.hpp"
+#include "Subfont.hpp"
+
+using namespace std;
+
+
+/** Constructs a MapLine object by parsing a single map line from the given stream. */
+MapLine::MapLine (istream &is) : MapLine() {
+ char buf[256];
+ is.getline(buf, 256);
+ parse(buf);
+}
+
+
+MapLine::MapLine (string str) : MapLine() {
+ size_t pos = str.rfind('\n');
+ if (pos != string::npos)
+ str = str.substr(0, pos);
+ parse(str.c_str());
+}
+
+
+// Some of the following functions have been derived from the dvipdfmx source file fontmap.c:
+// http://cvs.ktug.or.kr/viewcvs/dvipdfmx/src/fontmap.c?revision=1.43&view=markup
+
+
+/** Returns true if the given string is in dvips mapline format, and false if it's in dvipdfm format.
+ @param[in] line string to check */
+bool MapLine::isDVIPSFormat (const char *line) const {
+ if (strchr(line, '"') || strchr(line, '<')) // these chars are only present in dvips maps
+ return true;
+ char prevchar = ' ';
+ int entry_count=0;
+ for (const char *p=line; *p; ++p) {
+ if (isspace(prevchar)) {
+ if (*p == '-') // options starting with '-' are only present in dvipdfm map files
+ return false;
+ if (!isspace(*p))
+ entry_count++;
+ }
+ prevchar = *p;
+ }
+ // tfm_name and ps_name only => dvips map
+ return entry_count == 2;
+}
+
+
+/** Separates main font name and subfont definition name from a given combined name.
+ * Example: "basename@sfdname@10" => {"basename10", "sfdname"}
+ * @param[in,out] fontname complete fontname; after separation: main fontname only
+ * @param[out] sfdname name of subfont definition
+ * @return true on success */
+static bool split_fontname (string &fontname, string &sfdname) {
+ size_t pos1; // index of first '@'
+ if ((pos1 = fontname.find('@')) != string::npos && pos1 > 0) {
+ size_t pos2; // index of second '@'
+ if ((pos2 = fontname.find('@', pos1+1)) != string::npos && pos2 > pos1+1) {
+ sfdname = fontname.substr(pos1+1, pos2-pos1-1);
+ fontname = fontname.substr(0, pos1) + fontname.substr(pos2+1);
+ return true;
+ }
+ }
+ return false;
+}
+
+
+/** Parses a single mapline and stores the scanned data in member variables.
+ * The line may either be given in dvips or dvipdfmx mapfile format.
+ * @param[in] line the mapline */
+void MapLine::parse (const char *line) {
+ if (line) {
+ CharInputBuffer ib(line, strlen(line));
+ BufferInputReader ir(ib);
+ _texname = ir.getString();
+ string sfdname;
+ split_fontname(_texname, sfdname);
+ if (!sfdname.empty())
+ _sfd = SubfontDefinition::lookup(sfdname);
+ if (isDVIPSFormat(line))
+ parseDVIPSLine(ir);
+ else
+ parseDVIPDFMLine(ir);
+ }
+}
+
+
+/** Parses a single line in dvips mapfile format.
+ * @param[in] ir the input stream must be assigned to this reader */
+void MapLine::parseDVIPSLine (InputReader &ir) {
+ ir.skipSpace();
+ if (ir.peek() != '<' && ir.peek() != '"')
+ _psname = ir.getString();
+ ir.skipSpace();
+ while (ir.peek() == '<' || ir.peek() == '"') {
+ if (ir.peek() == '<') {
+ ir.get();
+ if (ir.peek() == '[' || ir.peek() == '<')
+ ir.get(); // skip second char of "<[" and "<<"
+ string name = ir.getString();
+ if (name.length() > 4 && name.substr(name.length()-4) == ".enc")
+ _encname = name.substr(0, name.length()-4);
+ else
+ _fontfname = name;
+ }
+ else { // ir.peek() == '"' => list of PS font operators
+ string options = ir.getQuotedString("\"");
+ StringInputBuffer sib(options);
+ BufferInputReader sir(sib);
+ while (!sir.eof()) {
+ double number;
+ if (sir.parseDouble(number)) {
+ // operator with preceding numeric parameter (value opstr)
+ string opstr = sir.getString();
+ if (opstr == "SlantFont")
+ _slant = number;
+ else if (opstr == "ExtendFont")
+ _extend = number;
+ }
+ else {
+ // operator without parameter => skip for now
+ sir.getString();
+ }
+ }
+ }
+ ir.skipSpace();
+ }
+}
+
+
+static void throw_number_expected (char opt, bool integer_only=false) {
+ ostringstream oss;
+ oss << "option -" << opt << ": " << (integer_only ? "integer" : "floating point") << " value expected";
+ throw MapLineException(oss.str());
+}
+
+
+/** Parses a single line in dvipdfmx mapfile format.
+ * @param[in] ir the input stream must be assigned to this reader */
+void MapLine::parseDVIPDFMLine (InputReader &ir) {
+ ir.skipSpace();
+ if (ir.peek() != '-') {
+ _encname = ir.getString();
+ if (_encname == "default" || _encname == "none")
+ _encname.clear();
+ }
+ ir.skipSpace();
+ if (ir.peek() != '-')
+ _fontfname = ir.getString();
+ if (!_fontfname.empty()) {
+ parseFilenameOptions(_fontfname);
+ }
+ ir.skipSpace();
+ while (ir.peek() == '-') {
+ ir.get();
+ int option = ir.get();
+ if (!isprint(option))
+ throw MapLineException("option character expected");
+ ir.skipSpace();
+ switch (option) {
+ case 's': // slant
+ if (!ir.parseDouble(_slant))
+ throw_number_expected('s');
+ break;
+ case 'e': // extend
+ if (!ir.parseDouble(_extend))
+ throw_number_expected('e');
+ break;
+ case 'b': // bold
+ if (!ir.parseDouble(_bold))
+ throw_number_expected('b');
+ break;
+ case 'r': //remap (deprecated)
+ break;
+ case 'i': // ttc index
+ if (!ir.parseInt(_fontindex, false))
+ throw_number_expected('i', true);
+ break;
+ case 'p': // UCS plane
+ int dummy;
+ if (!ir.parseInt(dummy, false))
+ throw_number_expected('p', true);
+ break;
+ case 'u': // to unicode
+ ir.getString();
+ break;
+ case 'v': // stemV
+ int stemv;
+ if (!ir.parseInt(stemv, true))
+ throw_number_expected('v', true);
+ break;
+ case 'm': // map single chars
+ ir.skipUntil("-");
+ break;
+ case 'w': // writing mode (horizontal=0, vertical=1)
+ int vertical;
+ if (!ir.parseInt(vertical, false))
+ throw_number_expected('w', true);
+ break;
+ default:
+ ostringstream oss;
+ oss << "invalid option: -" << option;
+ throw MapLineException(oss.str());
+ }
+ ir.skipSpace();
+ }
+}
+
+
+/** [:INDEX:][!]FONTNAME[/CSI][,VARIANT] */
+void MapLine::parseFilenameOptions (string fname) {
+ _fontfname = fname;
+ StringInputBuffer ib(fname);
+ BufferInputReader ir(ib);
+ if (ir.peek() == ':' && isdigit(ir.peek(1))) { // index given?
+ ir.get();
+ _fontindex = ir.getInt(); // font index of file with multiple fonts
+ if (ir.peek() == ':')
+ ir.get();
+ else
+ _fontindex = 0;
+ }
+ if (ir.peek() == '!') // no embedding
+ ir.get();
+
+ bool csi_given=false, style_given=false;
+ int pos;
+ if ((pos = ir.find('/')) >= 0) { // csi delimiter
+ csi_given = true;
+ _fontfname = ir.getString(pos);
+ }
+ else if ((pos = ir.find(',')) >= 0) {
+ style_given = true;
+ _fontfname = ir.getString(pos);
+ }
+ else
+ _fontfname = ir.getString();
+
+ if (csi_given) {
+ if ((pos = ir.find(',')) >= 0) {
+ style_given = true;
+ ir.getString(pos); // charcoll
+ }
+ else if (ir.eof())
+ throw MapLineException("CSI specifier expected");
+ else
+ ir.getString(); // charcoll
+ }
+ if (style_given) {
+ ir.get(); // skip ','
+ if (ir.check("BoldItalic")) {
+ }
+ else if (ir.check("Bold")) {
+ }
+ else if (ir.check("Italic")) {
+ }
+ if (!ir.eof())
+ throw MapLineException("invalid style given");
+ }
+}
diff --git a/dviware/dvisvgm/src/MapLine.hpp b/dviware/dvisvgm/src/MapLine.hpp
new file mode 100644
index 0000000000..ea41fae0d0
--- /dev/null
+++ b/dviware/dvisvgm/src/MapLine.hpp
@@ -0,0 +1,72 @@
+/*************************************************************************
+** MapLine.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef MAPLINE_HPP
+#define MAPLINE_HPP
+
+#include <istream>
+#include <sstream>
+#include <string>
+#include "MessageException.hpp"
+
+
+class InputReader;
+class SubfontDefinition;
+
+
+struct MapLineException : MessageException {
+ explicit MapLineException (const std::string &msg) : MessageException(msg) {}
+};
+
+
+class MapLine {
+ public:
+ explicit MapLine (std::istream &is);
+ explicit MapLine (std::string str);
+ const std::string& texname () const {return _texname;}
+ const std::string& psname () const {return _psname;}
+ const std::string& fontfname () const {return _fontfname;}
+ const std::string& encname () const {return _encname;}
+ int fontindex () const {return _fontindex;}
+ double bold () const {return _bold;}
+ double slant () const {return _slant;}
+ double extend () const {return _extend;}
+ SubfontDefinition* sfd () const {return _sfd;}
+
+ protected:
+ MapLine () =default;
+ bool isDVIPSFormat (const char *line) const;
+ void parse (const char *line);
+ void parseDVIPSLine (InputReader &ir);
+ void parseDVIPDFMLine (InputReader &ir);
+ void parseFilenameOptions (std::string opt);
+
+ private:
+ std::string _texname; ///< TeX font name
+ std::string _psname; ///< PS font name
+ std::string _fontfname; ///< name of fontfile
+ std::string _encname; ///< name of encoding (without file suffix ".enc")
+ SubfontDefinition *_sfd=nullptr; ///< subfont definition to be used
+ int _fontindex=0; ///< font index of file with multiple fonts (e.g. ttc files)
+ double _slant=0, _bold=0, _extend=1;
+};
+
+
+#endif
diff --git a/dviware/dvisvgm/src/Matrix.cpp b/dviware/dvisvgm/src/Matrix.cpp
new file mode 100644
index 0000000000..7b425596e9
--- /dev/null
+++ b/dviware/dvisvgm/src/Matrix.cpp
@@ -0,0 +1,609 @@
+/*************************************************************************
+** Matrix.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <limits>
+#include <sstream>
+#include "Calculator.hpp"
+#include "Matrix.hpp"
+#include "utility.hpp"
+#include "XMLString.hpp"
+
+using namespace std;
+using math::deg2rad;
+
+
+/** Computes the determinant of a given matrix */
+double det (const Matrix &m) {
+ double sum=0;
+ for (int i=0; i < 3; ++i) {
+ sum += m._values[0][i] * m._values[1][(i+1)%3] * m._values[2][(i+2)%3]
+ - m._values[0][2-i] * m._values[1][(4-i)%3] * m._values[2][(3-i)%3];
+ }
+ return sum;
+}
+
+
+/** Computes the determinant of the 2x2 submatrix of m where a given
+ * row and column were removed.
+ * @param[in] m base matrix
+ * @param[in] row row to remove
+ * @param[in] col column to remove */
+double det (const Matrix &m, int row, int col) {
+ int c1 = (col+1)%3, c2 = (col+2)%3;
+ int r1 = (row+1)%3, r2 = (row+2)%3;
+ if (c1 > c2)
+ swap(c1, c2);
+ if (r1 > r2)
+ swap(r1, r2);
+ return m._values[r1][c1] * m._values[r2][c2]
+ - m._values[r1][c2] * m._values[r2][c1];
+}
+
+
+/** Creates a diagonal matrix ((d,0,0),(0,d,0),(0,0,d)).
+ * @param[in] d value of diagonal elements */
+Matrix::Matrix (double d) {
+ set(d);
+}
+
+
+/** Creates the matrix ((v0,v1,v2),(v3,v4,v5),(v6,v7,v8)).
+ * Expects that array v consists of 'size' elements. If size is less than 9, the
+ * remaining matrix components will be set to those of the identity matrix.
+ * @param[in] v array containing the matrix components
+ * @param[in] size size of array v */
+Matrix::Matrix (const double *v, unsigned size) {
+ set(v, size);
+}
+
+
+/** Creates the matrix ((v0,v1,v2),(v3,v4,v5),(v6,v7,v8)).
+ * If vector v has less than 9 elements, the remaining matrix components will be set to
+ * those of the identity matrix.
+ * @param[in] v array containing the matrix components
+ * @param[in] start use vector components start,...,start+8 */
+Matrix::Matrix (const std::vector<double> &v, int start) {
+ set(v, start);
+}
+
+
+Matrix::Matrix (const string &cmds, Calculator &calc) {
+ *this = parse(cmds, calc);
+}
+
+
+Matrix::Matrix (initializer_list<double> initlist) {
+ int count=0;
+ for (auto it=initlist.begin(); it != initlist.end() && count < 9; ++it) {
+ _values[count/3][count%3] = *it;
+ count++;
+ }
+ for (; count < 9; count++)
+ _values[count/3][count%3] = (count%4 ? 0 : 1);
+}
+
+
+Matrix& Matrix::set (double d) {
+ for (int i=0; i < 3; i++)
+ for (int j=0; j < 3; j++)
+ _values[i][j] = (i==j ? d : 0);
+ return *this;
+}
+
+
+Matrix& Matrix::set (const double *v, unsigned size) {
+ size = min(size, 9u);
+ for (unsigned i=0; i < size; i++)
+ _values[i/3][i%3] = v[i];
+ for (unsigned i=size; i < 9; i++)
+ _values[i/3][i%3] = (i%4 ? 0 : 1);
+ return *this;
+}
+
+
+/** Set matrix values ((v0,v1,v2),(v3,v4,v5),(v6,v7,v8)).
+ * If vector v has less than 9 elements, the remaining matrix components will be set to
+ * those of the identity matrix.
+ * @param[in] v array containing the matrix components
+ * @param[in] start use vector components start,...,start+8 */
+Matrix& Matrix::set (const vector<double> &v, int start) {
+ unsigned size = min((unsigned)v.size()-start, 9u);
+ for (unsigned i=0; i < size; i++)
+ _values[i/3][i%3] = v[i+start];
+ for (unsigned i=size; i < 9; i++)
+ _values[i/3][i%3] = (i%4 ? 0 : 1);
+ return *this;
+}
+
+
+Matrix& Matrix::set (const string &cmds, Calculator &calc) {
+ *this = parse(cmds, calc);
+ return *this;
+}
+
+
+Matrix& Matrix::translate (double tx, double ty) {
+ if (tx != 0 || ty != 0) {
+ TranslationMatrix t(tx, ty);
+ lmultiply(t);
+ }
+ return *this;
+}
+
+
+Matrix& Matrix::scale (double sx, double sy) {
+ if (sx != 1 || sy != 1) {
+ ScalingMatrix s(sx, sy);
+ lmultiply(s);
+ }
+ return *this;
+}
+
+
+/** Multiplies this matrix by ((cos d, -sin d, 0), (sin d, cos d, 0), (0,0,1)) that
+ * describes an anti-clockwise rotation by d degrees.
+ * @param[in] deg rotation angle in degrees */
+Matrix& Matrix::rotate (double deg) {
+ RotationMatrix r(deg);
+ lmultiply(r);
+ return *this;
+}
+
+
+Matrix& Matrix::xskewByAngle (double deg) {
+ if (fmod(fabs(deg)-90, 180) != 0)
+ return xskewByRatio(tan(deg2rad(deg)));
+ return *this;
+}
+
+
+Matrix& Matrix::xskewByRatio (double xyratio) {
+ if (xyratio != 0) {
+ double v[] = {1, xyratio};
+ Matrix t(v, 2);
+ lmultiply(t);
+ }
+ return *this;
+}
+
+
+Matrix& Matrix::yskewByAngle (double deg) {
+ if (fmod(fabs(deg)-90, 180) != 0)
+ return yskewByRatio(tan(deg2rad(deg)));
+ return *this;
+}
+
+
+Matrix& Matrix::yskewByRatio (double xyratio) {
+ if (xyratio != 0) {
+ double v[] = {1, 0, 0, xyratio};
+ Matrix t(v, 4);
+ lmultiply(t);
+ }
+ return *this;
+}
+
+
+Matrix& Matrix::flip (bool haxis, double a) {
+ double s = 1;
+ if (haxis) // mirror at horizontal axis?
+ s = -1;
+ double v[] = {-s, 0, (haxis ? 0 : 2*a), 0, s, (haxis ? 2*a : 0), 0, 0, 1};
+ Matrix t(v);
+ lmultiply(t);
+ return *this;
+}
+
+
+/** Swaps rows and columns of the matrix. */
+Matrix& Matrix::transpose () {
+ for (int i=0; i < 3; i++)
+ for (int j=i+1; j < 3; j++)
+ swap(_values[i][j], _values[j][i]);
+ return *this;
+}
+
+
+/** Multiplies this matrix M with matrix tm (tm is the factor on the right side): M := M * tm */
+Matrix& Matrix::rmultiply (const Matrix &tm) {
+ Matrix ret;
+ for (int i=0; i < 3; i++)
+ for (int j=0; j < 3; j++)
+ for (int k=0; k < 3; k++)
+ ret._values[i][j] += _values[i][k] * tm._values[k][j];
+ return *this = ret;
+}
+
+
+/** Multiplies this matrix M with matrix tm (tm is the factor on the left side): M := tm * M */
+Matrix& Matrix::lmultiply (const Matrix &tm) {
+ Matrix ret;
+ for (int i=0; i < 3; i++)
+ for (int j=0; j < 3; j++)
+ for (int k=0; k < 3; k++)
+ ret._values[i][j] += tm._values[i][k] * _values[k][j];
+ return *this = ret;
+}
+
+
+Matrix& Matrix::invert () {
+ Matrix ret;
+ if (double denom = det(*this)) {
+ for (int i=0; i < 3; ++i) {
+ for (int j=0; j < 3; ++j) {
+ ret._values[i][j] = det(*this, i, j)/denom;
+ if ((i+j)%2 != 0)
+ ret._values[i][j] *= -1;
+ }
+ }
+ return *this = ret;
+ }
+ throw exception();
+}
+
+
+Matrix& Matrix::operator *= (double c) {
+ for (int i=0; i < 3; i++)
+ for (int j=0; j < 3; j++)
+ _values[i][j] *= c;
+ return *this;
+}
+
+
+DPair Matrix::operator * (const DPair &p) const {
+ double pp[] = {p.x(), p.y(), 1};
+ double ret[]= {0, 0};
+ for (int i=0; i < 2; i++)
+ for (int j=0; j < 3; j++)
+ ret[i] += _values[i][j] * pp[j];
+ return DPair(ret[0], ret[1]);
+}
+
+
+/** Returns true if this matrix equals. Checks equality by comparing the matrix components. */
+bool Matrix::operator == (const Matrix &m) const {
+ for (int i=0; i < 2; i++)
+ for (int j=0; j < 3; j++)
+ if (std::abs(_values[i][j]-m._values[i][j]) >= numeric_limits<double>::epsilon())
+ return false;
+ return true;
+}
+
+
+/** Returns true if this matrix doesn't equal m. Checks inequality by comparing the matrix components. */
+bool Matrix::operator != (const Matrix &m) const {
+ for (int i=0; i < 2; i++)
+ for (int j=0; j < 3; j++)
+ if (std::abs(_values[i][j]-m._values[i][j]) >= numeric_limits<double>::epsilon())
+ return true;
+ return false;
+}
+
+
+/** Returns true if this matrix is the identity matrix ((1,0,0),(0,1,0),(0,0,1)). */
+bool Matrix::isIdentity() const {
+ for (int i=0; i < 2; i++)
+ for (int j=0; j < 3; j++) {
+ const double &v = _values[i][j];
+ if ((i == j && v != 1) || (i != j && v != 0))
+ return false;
+ }
+ return true;
+}
+
+
+/** Checks whether this matrix describes a plain translation (without any other transformations).
+ * If so, the parameters tx and ty are filled with the translation components.
+ * @param[out] tx horizontal translation
+ * @param[out] ty vertical translation
+ * @return true if matrix describes a pure translation */
+bool Matrix::isTranslation (double &tx, double &ty) const {
+ tx = _values[0][2];
+ ty = _values[1][2];
+ for (int i=0; i < 3; i++)
+ for (int j=0; j < 2; j++) {
+ const double &v = _values[i][j];
+ if ((i == j && v != 1) || (i != j && v != 0))
+ return false;
+ }
+ return _values[2][2] == 1;
+}
+
+
+/** Gets a parameter for the transformation command.
+ * @param[in] is parameter chars are read from this stream
+ * @param[in] calc parameters can be arithmetic expressions, so we need a calculator to evaluate them
+ * @param[in] def default value if parameter is optional
+ * @param[in] optional true if parameter is optional
+ * @param[in] leadingComma true if first non-blank must be a comma
+ * @return value of argument */
+static double getArgument (istream &is, Calculator &calc, double def, bool optional, bool leadingComma) {
+ is >> ws;
+ if (!optional && leadingComma && is.peek() != ',')
+ throw ParserException("',' expected");
+ if (is.peek() == ',') {
+ is.get(); // skip comma
+ optional = false; // now we expect a parameter
+ }
+ string expr;
+ while (!isupper(is.peek()) && is.peek() != ',' && is)
+ expr += (char)is.get();
+ if (expr.length() == 0) {
+ if (optional)
+ return def;
+ else
+ throw ParserException("parameter expected");
+ }
+ return calc.eval(expr);
+}
+
+
+Matrix Matrix::parse (istream &is, Calculator &calc) {
+ Matrix ret(1);
+ while (is) {
+ is >> ws;
+ int cmd = is.get();
+ switch (cmd) {
+ case 'T': {
+ double tx = getArgument(is, calc, 0, false, false);
+ double ty = getArgument(is, calc, 0, true, true);
+ ret.translate(tx, ty);
+ break;
+ }
+ case 'S': {
+ double sx = getArgument(is, calc, 1, false, false);
+ double sy = getArgument(is, calc, sx, true, true );
+ ret.scale(sx, sy);
+ break;
+ }
+ case 'R': {
+ double a = getArgument(is, calc, 0, false, false);
+ double x = getArgument(is, calc, calc.getVariable("ux")+calc.getVariable("w")/2, true, true);
+ double y = getArgument(is, calc, calc.getVariable("uy")+calc.getVariable("h")/2, true, true);
+ ret.translate(-x, -y);
+ ret.rotate(a);
+ ret.translate(x, y);
+ break;
+ }
+ case 'F': {
+ int c = is.get();
+ if (c != 'H' && c != 'V')
+ throw ParserException("'H' or 'V' expected");
+ double a = getArgument(is, calc, 0, false, false);
+ ret.flip(c == 'H', a);
+ break;
+ }
+ case 'K': {
+ int c = is.get();
+ if (c != 'X' && c != 'Y')
+ throw ParserException("transformation command 'K' must be followed by 'X' or 'Y'");
+ double a = getArgument(is, calc, 0, false, false);
+ if (std::abs(cos(deg2rad(a))) < numeric_limits<double>::epsilon()) {
+ ostringstream oss;
+ oss << "illegal skewing angle: " << a << " degrees";
+ throw ParserException(oss.str());
+ }
+ if (c == 'X')
+ ret.xskewByAngle(a);
+ else
+ ret.yskewByAngle(a);
+ break;
+ }
+ case 'M': {
+ double v[9];
+ for (int i=0; i < 6; i++)
+ v[i] = getArgument(is, calc, i%4 ? 0 : 1, i!=0, i!=0);
+ // third row (0, 0, 1)
+ v[6] = v[7] = 0;
+ v[8] = 1;
+ Matrix tm(v);
+ ret.lmultiply(tm);
+ break;
+ }
+ default:
+ ostringstream oss;
+ oss << "transformation command expected (found '" << char(cmd) << "' instead)";
+ throw ParserException(oss.str());
+ }
+ }
+ return ret;
+}
+
+
+Matrix Matrix::parse (const string &cmds, Calculator &calc) {
+ istringstream iss;
+ iss.str(cmds);
+ return parse(iss, calc);
+}
+
+
+/** Returns an SVG matrix expression that can be used in transform attributes.
+ * ((a,b,c),(d,e,f),(0,0,1)) => matrix(a d b e c f) */
+string Matrix::toSVG () const {
+ ostringstream oss;
+ oss << "matrix(";
+ for (int i=0; i < 3; i++) {
+ for (int j=0; j < 2; j++)
+ oss << XMLString(_values[j][i]) << ' ';
+ }
+ oss.seekp(-1, ios::cur) << ')'; // overwrite trailing space character
+ return oss.str();
+}
+
+
+ostream& Matrix::write (ostream &os) const {
+ os << '(';
+ for (int i=0; i < 3; i++) {
+ os << '(' << _values[i][0];
+ for (int j=1; j < 3; j++)
+ os << ',' << _values[i][j];
+ os << ')';
+ if (i < 2)
+ os << ',';
+ }
+ os << ')';
+ return os;
+}
+
+
+static const char* ord_suffix (int n) {
+ static const char *suffixes[] = {"th", "st", "nd", "rd"};
+ if (abs(n) < 4)
+ return suffixes[n];
+ return suffixes[0];
+}
+
+
+static void skip_comma_wsp (istream &is) {
+ is >> ws;
+ if (is.peek() == ',') is.ignore(1);
+ is >> ws;
+}
+
+
+static size_t parse_transform_cmd (istream &is, string cmd, size_t minparams, size_t maxparams, vector<double> &params) {
+ for (int i=0; i < int(cmd.length()); i++) {
+ if (is.get() != cmd[i]) {
+ is.seekg(-i-1, ios::cur);
+ return 0;
+ }
+ }
+ params.clear();
+ is >> ws;
+ if (is.get() != '(')
+ throw ParserException("missing '(' after command '"+cmd+"'");
+ for (size_t i=1; i <= maxparams; i++) {
+ is >> ws;
+ double val;
+ if (is.fail())
+ throw ParserException(to_string(i)+ord_suffix(i)+" parameter of '"+cmd+"' must be a number");
+ is >> val;
+ params.push_back(val);
+ is >> ws;
+ if (i == minparams && is.peek() == ')') {
+ is.ignore(1);
+ return i;
+ }
+ if (i == maxparams) {
+ if (is.peek() != ')')
+ throw ParserException("missing ')' at end of command '"+cmd+"'");
+ is.ignore(1);
+ }
+ skip_comma_wsp(is);
+ }
+ return maxparams;
+}
+
+
+static bool ne (double x, double y) {return abs(x-y) >= 1e-6;}
+static bool ne_angle (double x, double y) {return abs(x-y) >= 1e-3;}
+
+
+Matrix Matrix::parseSVGTransform (const string &transform) {
+ istringstream iss(transform);
+ Matrix matrix(1);
+ iss >> ws;
+ while (iss) {
+ vector<double> params;
+ if (parse_transform_cmd(iss, "matrix", 6, 6, params)) {
+ if (ne(params[0], 1) || ne(params[1], 0) || ne(params[2], 0) || ne(params[3], 1) || ne(params[4], 0) || ne(params[5], 0))
+ matrix.rmultiply({params[0], params[2], params[4], params[1], params[3], params[5]});
+ }
+ else if (parse_transform_cmd(iss, "rotate", 1, 3, params)) {
+ if (params.size() == 1) {
+ params.push_back(0);
+ params.push_back(0);
+ }
+ if (ne_angle(fmod(params[0], 360), 0)) {
+ bool translate = ne(params[1], 0) || ne(params[2], 0);
+ if (translate)
+ matrix.rmultiply(TranslationMatrix(params[1], params[2]));
+ matrix.rmultiply(RotationMatrix(params[0]));
+ if (translate)
+ matrix.rmultiply(TranslationMatrix(-params[1], -params[2]));
+ }
+ }
+ else if (parse_transform_cmd(iss, "scale", 1, 2, params)) {
+ if (params.size() == 1)
+ params.push_back(1);
+ if (ne(params[0], 1) || ne(params[1], 1))
+ matrix.rmultiply(ScalingMatrix(params[0], params[1]));
+ }
+ else if (parse_transform_cmd(iss, "skewX", 1, 1, params)) {
+ if (ne_angle(fmod(abs(params[0])-90, 180), 0))
+ matrix.rmultiply(XSkewingMatrix(params[0]));
+ }
+ else if (parse_transform_cmd(iss, "skewY", 1, 1, params)) {
+ if (ne_angle(fmod(abs(params[0])-90, 180), 0))
+ matrix.rmultiply(YSkewingMatrix(params[0]));
+ }
+ else if (parse_transform_cmd(iss, "translate", 1, 2, params)) {
+ if (params.size() == 1)
+ params.push_back(0);
+ if (ne(params[0], 0) || ne(params[1], 0))
+ matrix.rmultiply(TranslationMatrix(params[0], params[1]));
+ }
+ else { // invalid command
+ string cmd;
+ while (isalpha(iss.peek()))
+ cmd += char(iss.get());
+ if (cmd.empty())
+ throw ParserException("unexpected character in transform attribute: "+to_string(char(iss.get())));
+ throw ParserException("invalid command in transform attribute: "+cmd);
+ }
+ skip_comma_wsp(iss);
+ }
+ return matrix;
+}
+
+//////////////////////////////////////////////////////////////////
+
+TranslationMatrix::TranslationMatrix (double tx, double ty) {
+ double v[] = {1, 0, tx, 0, 1, ty};
+ set(v, 6);
+}
+
+
+ScalingMatrix::ScalingMatrix (double sx, double sy) {
+ double v[] = {sx, 0, 0, 0, sy};
+ set(v, 5);
+}
+
+
+RotationMatrix::RotationMatrix (double deg) {
+ double rad = deg2rad(deg);
+ double c = cos(rad);
+ double s = sin(rad);
+ double v[] = {c, -s, 0, s, c};
+ set(v, 5);
+}
+
+
+XSkewingMatrix::XSkewingMatrix (double deg) {
+ double xyratio = tan(deg2rad(deg));
+ lmultiply(Matrix({1, xyratio}));
+}
+
+
+YSkewingMatrix::YSkewingMatrix (double deg) {
+ double xyratio = tan(deg2rad(deg));
+ lmultiply(Matrix({1, 0, 0, xyratio}));
+} \ No newline at end of file
diff --git a/dviware/dvisvgm/src/Matrix.hpp b/dviware/dvisvgm/src/Matrix.hpp
new file mode 100644
index 0000000000..733ff71fad
--- /dev/null
+++ b/dviware/dvisvgm/src/Matrix.hpp
@@ -0,0 +1,119 @@
+/*************************************************************************
+** Matrix.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef MATRIX_HPP
+#define MATRIX_HPP
+
+#include <initializer_list>
+#include <istream>
+#include <string>
+#include <vector>
+#include "MessageException.hpp"
+#include "Pair.hpp"
+
+
+struct ParserException : public MessageException {
+ explicit ParserException (const std::string &msg) : MessageException(msg) {}
+};
+
+class Calculator;
+
+class Matrix {
+ friend double det (const Matrix &m);
+ friend double det (const Matrix &m, int row, int col);
+
+ public:
+ Matrix (const std::string &cmds, Calculator &calc);
+ Matrix (double d=0);
+ explicit Matrix (const double *v, unsigned size=9);
+ explicit Matrix (const std::vector<double> &v, int start=0);
+ Matrix (std::initializer_list<double> initlist);
+ Matrix& set (double d);
+ Matrix& set (const double *v, unsigned size);
+ Matrix& set (const std::vector<double> &v, int start=0);
+ Matrix& set (const std::string &cmds, Calculator &calc);
+ double get (int row, int col) const {return _values[row][col];}
+ Matrix& transpose ();
+ Matrix& invert ();
+ Matrix& rmultiply (const Matrix &tm);
+ Matrix& lmultiply (const Matrix &tm);
+ Matrix& translate (double tx, double ty);
+ Matrix& translate (const DPair &p) {return translate(p.x(), p.y());}
+ Matrix& scale (double sx, double sy);
+ Matrix& scale (const DPair &p) {return scale(p.x(), p.y());}
+ Matrix& rotate (double deg);
+ Matrix& xskewByAngle (double deg);
+ Matrix& yskewByAngle (double deg);
+ Matrix& xskewByRatio (double xyratio);
+ Matrix& yskewByRatio (double xyratio);
+ Matrix& flip (bool h, double a);
+ Matrix& operator *= (double c);
+ DPair operator * (const DPair &p) const;
+ bool operator == (const Matrix &m) const;
+ bool operator != (const Matrix &m) const;
+ bool isIdentity() const;
+ bool isTranslation (double &tx, double &ty) const;
+ std::string toSVG () const;
+ std::ostream& write (std::ostream &os) const;
+
+ static Matrix parse (std::istream &is, Calculator &c);
+ static Matrix parse (const std::string &cmds, Calculator &c);
+ static Matrix parseSVGTransform (const std::string &transform);
+
+ private:
+ double _values[3][3]; // row x col
+};
+
+
+struct TranslationMatrix : public Matrix {
+ TranslationMatrix (double tx, double ty);
+};
+
+
+struct ScalingMatrix : public Matrix {
+ ScalingMatrix (double sx, double sy);
+};
+
+
+struct RotationMatrix : public Matrix {
+ explicit RotationMatrix (double deg);
+};
+
+
+struct XSkewingMatrix : public Matrix {
+ explicit XSkewingMatrix (double deg);
+};
+
+
+struct YSkewingMatrix : public Matrix {
+ explicit YSkewingMatrix (double deg);
+};
+
+
+inline std::ostream& operator << (std::ostream &os, const Matrix &m) {
+ return m.write(os);
+}
+
+
+double det (const Matrix &m);
+double det (const Matrix &m, int row, int col);
+
+
+#endif
diff --git a/dviware/dvisvgm/src/Message.cpp b/dviware/dvisvgm/src/Message.cpp
new file mode 100644
index 0000000000..85ed204cb4
--- /dev/null
+++ b/dviware/dvisvgm/src/Message.cpp
@@ -0,0 +1,270 @@
+/*************************************************************************
+** Message.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <cstdarg>
+#include <cstdlib>
+#include <cstring>
+#include <iostream>
+#include <unordered_map>
+#include "Message.hpp"
+#include "Terminal.hpp"
+
+using namespace std;
+
+MessageStream::MessageStream (std::ostream &os) noexcept
+ : _os(&os), _nl(true)
+{
+ Terminal::init(os);
+}
+
+
+MessageStream::~MessageStream () {
+ if (_os && Message::COLORIZE)
+ Terminal::finish(*_os);
+}
+
+
+void MessageStream::putChar (char c, ostream &os) {
+ switch (c) {
+ case '\r':
+ os << '\r';
+ _nl = true;
+ _col = 1;
+ return;
+ case '\n':
+ if (!_nl) {
+ _col = 1;
+ _nl = true;
+ os << '\n';
+ }
+ return;
+ default:
+ if (_nl) {
+ os << string(_indent, ' ');
+ _col += _indent;
+ }
+ else {
+ const int cols = Terminal::columns();
+ if (cols > 0 && _col >= cols) {
+#ifndef _WIN32
+ // move cursor to next line explicitly (not necessary in Windows/DOS terminal)
+ os << '\n';
+#endif
+ os << string(_indent, ' ');
+ _col = _indent+1;
+ }
+ else
+ _col++;
+ }
+ _nl = false;
+ if (!_nl || c != '\n')
+ os << c;
+ }
+}
+
+
+MessageStream& MessageStream::operator << (const char *str) {
+ if (_os && str) {
+ const char *first = str;
+ while (*first) {
+ const char *last = strchr(first, '\n');
+ if (!last)
+ last = first+strlen(first)-1;
+#ifndef _WIN32
+ // move cursor to next line explicitly (not necessary in Windows/DOS terminal)
+ const int cols = Terminal::columns();
+ int len = last-first+1;
+ if (cols > 0 && _col+len > cols && _indent+len <= cols)
+ putChar('\n', *_os);
+#endif
+ while (first <= last)
+ putChar(*first++, *_os);
+ first = last+1;
+ }
+ }
+ return *this;
+}
+
+
+MessageStream& MessageStream::operator << (const char &c) {
+ if (_os)
+ putChar(c, *_os);
+ return *this;
+}
+
+
+void MessageStream::indent (bool reset) {
+ if (reset)
+ _indent = 0;
+ _indent += 2;
+}
+
+
+void MessageStream::outdent (bool all) {
+ if (all)
+ _indent = 0;
+ else if (_indent > 0)
+ _indent -= 2;
+}
+
+
+void MessageStream::clearline () {
+ if (_os) {
+ int cols = Terminal::columns();
+ *_os << '\r' << string(cols ? cols-1 : 79, ' ') << '\r';
+ _nl = true;
+ _col = 1;
+ }
+}
+
+static MessageStream nullStream;
+static MessageStream messageStream(cerr);
+
+
+//////////////////////////////
+
+// maximal verbosity
+int Message::LEVEL = Message::MESSAGES | Message::WARNINGS | Message::ERRORS;
+bool Message::COLORIZE = false;
+bool Message::_initialized = false;
+Message::Color Message::_classColors[9];
+
+
+/** Returns the stream for usual messages. */
+MessageStream& Message::mstream (bool prefix, MessageClass mclass) {
+ init();
+ MessageStream *ms = (LEVEL & MESSAGES) ? &messageStream : &nullStream;
+ if (COLORIZE && ms && ms->os()) {
+ Terminal::fgcolor(_classColors[mclass].foreground, *ms->os());
+ Terminal::bgcolor(_classColors[mclass].background, *ms->os());
+ }
+ if (prefix)
+ *ms << "\nMESSAGE: ";
+ return *ms;
+}
+
+
+/** Returns the stream for warning messages. */
+MessageStream& Message::wstream (bool prefix) {
+ init();
+ MessageStream *ms = (LEVEL & WARNINGS) ? &messageStream : &nullStream;
+ if (COLORIZE && ms && ms->os()) {
+ Terminal::fgcolor(_classColors[MC_WARNING].foreground, *ms->os());
+ Terminal::bgcolor(_classColors[MC_WARNING].background, *ms->os());
+ }
+ if (prefix)
+ *ms << "\nWARNING: ";
+ return *ms;
+}
+
+
+/** Returns the stream for error messages. */
+MessageStream& Message::estream (bool prefix) {
+ init();
+ MessageStream *ms = (LEVEL & ERRORS) ? &messageStream : &nullStream;
+ if (COLORIZE && ms && ms->os()) {
+ Terminal::fgcolor(_classColors[MC_ERROR].foreground, *ms->os());
+ Terminal::bgcolor(_classColors[MC_ERROR].background, *ms->os());
+ }
+ if (prefix)
+ *ms << "\nERROR: ";
+ return *ms;
+}
+
+
+static bool colorchar2int (char colorchar, int *val) {
+ colorchar = tolower(colorchar);
+ if (colorchar >= '0' && colorchar <= '9')
+ *val = int(colorchar-'0');
+ else if (colorchar >= 'a' && colorchar <= 'f')
+ *val = int(colorchar-'a'+10);
+ else if (colorchar == '*')
+ *val = -1;
+ else
+ return false;
+ return true;
+}
+
+
+/** Initializes the Message class. Sets the colors for each message set.
+ * The colors can be changed via environment variable DVISVGM_COLORS. Its
+ * value must be a sequence of color entries of the form gg:BF where the
+ * two-letter ID gg specifies a message set, B the hex digit of the
+ * background, and F the hex digit of the foreground/text color.
+ * Color codes:
+ * - 1: red, 2: green, 4: blue
+ * - 0-7: dark colors
+ * - 8-F: light colors
+ * - *: default color
+ * Example: pn:01 sets page number messages to red on black background */
+void Message::init () {
+ if (_initialized || !Message::COLORIZE)
+ return;
+
+ // set default message colors
+ _classColors[MC_ERROR] = Color(Terminal::RED, true);
+ _classColors[MC_WARNING] = Color(Terminal::YELLOW);
+ _classColors[MC_PAGE_NUMBER] = Color(Terminal::BLUE, true);
+ _classColors[MC_PAGE_SIZE] = Color(Terminal::MAGENTA);
+ _classColors[MC_PAGE_WRITTEN] = Color(Terminal::GREEN);
+ _classColors[MC_STATE] = Color(Terminal::CYAN);
+ _classColors[MC_TRACING] = Color(Terminal::BLUE);
+ _classColors[MC_PROGRESS] = Color(Terminal::MAGENTA);
+
+ if (const char *color_str = getenv("DVISVGM_COLORS")) {
+ unordered_map<string, MessageClass> classes = {
+ {"er", MC_ERROR},
+ {"wn", MC_WARNING},
+ {"pn", MC_PAGE_NUMBER},
+ {"ps", MC_PAGE_SIZE},
+ {"fw", MC_PAGE_WRITTEN},
+ {"sm", MC_STATE},
+ {"tr", MC_TRACING},
+ {"pi", MC_PROGRESS},
+ };
+ const char *p=color_str;
+
+ // skip leading whitespace
+ while (isspace(*p))
+ ++p;
+
+ // iterate over color assignments
+ while (strlen(p) >= 5) {
+ auto it = classes.find(string(p, 2));
+ if (it != classes.end() && p[2] == '=') {
+ int bgcolor, fgcolor;
+ if (colorchar2int(p[3], &bgcolor) && colorchar2int(p[4], &fgcolor)) {
+ _classColors[it->second].background = bgcolor;
+ _classColors[it->second].foreground = fgcolor;
+ }
+ }
+ p += 5;
+
+ // skip trailing characters in a malformed entry
+ while (*p && !isspace(*p) && *p != ':' && *p != ';')
+ ++p;
+ // skip separation characters
+ while (isspace(*p) || *p == ':' || *p == ';')
+ ++p;
+ }
+ }
+ _initialized = true;
+}
+
diff --git a/dviware/dvisvgm/src/Message.hpp b/dviware/dvisvgm/src/Message.hpp
new file mode 100644
index 0000000000..a7e6034021
--- /dev/null
+++ b/dviware/dvisvgm/src/Message.hpp
@@ -0,0 +1,112 @@
+/*************************************************************************
+** Message.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef MESSAGE_HPP
+#define MESSAGE_HPP
+
+#include <algorithm>
+#include <cstdint>
+#include <string>
+#include <ostream>
+#include <sstream>
+#include "Terminal.hpp"
+
+
+class Message;
+
+class MessageStream {
+ friend class Message;
+
+ public:
+ MessageStream () =default;
+ explicit MessageStream (std::ostream &os) noexcept;
+ ~MessageStream ();
+
+ template <typename T>
+ MessageStream& operator << (const T &obj) {
+ std::ostringstream oss;
+ oss << obj;
+ (*this) << oss.str();
+ return *this;
+ }
+
+ MessageStream& operator << (const char *str);
+ MessageStream& operator << (const char &c);
+ MessageStream& operator << (const std::string &str) {return (*this) << str.c_str();}
+
+ void indent (int level) {_indent = std::max(0, level*2);}
+ void indent (bool reset=false);
+ void outdent (bool all=false);
+ void clearline ();
+
+ protected:
+ void putChar (char c, std::ostream &os);
+ std::ostream* os () {return _os;}
+
+ private:
+ std::ostream *_os=nullptr;
+ bool _nl=false; ///< true if previous character was a newline
+ int _col=1; ///< current terminal column
+ int _indent=0; ///< indentation width (number of columns/characters)
+};
+
+
+class Message {
+ struct Color {
+ Color () =default;
+ explicit Color (int8_t fgcolor) noexcept : foreground(fgcolor) {}
+ Color (int8_t fgcolor, bool light) noexcept: foreground(fgcolor + (light ? 8 : 0)) {}
+ Color (int8_t fgcolor, int8_t bgcolor) noexcept : foreground(fgcolor), background(bgcolor) {}
+ int8_t foreground = -1;
+ int8_t background = -1;
+ };
+
+ public:
+ enum MessageClass {
+ MC_ERROR,
+ MC_WARNING,
+ MC_MESSAGE,
+ MC_PAGE_NUMBER,
+ MC_PAGE_SIZE,
+ MC_PAGE_WRITTEN,
+ MC_STATE,
+ MC_TRACING,
+ MC_PROGRESS,
+ };
+
+ public:
+ static MessageStream& mstream (bool prefix=false, MessageClass mclass=MC_MESSAGE);
+ static MessageStream& estream (bool prefix=false);
+ static MessageStream& wstream (bool prefix=false);
+
+ enum {ERRORS=1, WARNINGS=2, MESSAGES=4};
+ static int LEVEL;
+ static bool COLORIZE;
+
+ protected:
+ static void init ();
+
+
+ private:
+ static Color _classColors[];
+ static bool _initialized;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/MessageException.hpp b/dviware/dvisvgm/src/MessageException.hpp
new file mode 100644
index 0000000000..c7aa342860
--- /dev/null
+++ b/dviware/dvisvgm/src/MessageException.hpp
@@ -0,0 +1,37 @@
+/*************************************************************************
+** MessageException.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef MESSAGEEXCEPTION_HPP
+#define MESSAGEEXCEPTION_HPP
+
+#include <exception>
+#include <string>
+
+
+class MessageException : public std::exception {
+ public:
+ explicit MessageException (std::string msg) : _message(std::move(msg)) {}
+ const char* what () const noexcept override {return _message.c_str();}
+
+ private:
+ std::string _message;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/MetafontWrapper.cpp b/dviware/dvisvgm/src/MetafontWrapper.cpp
new file mode 100644
index 0000000000..dfb7c9b465
--- /dev/null
+++ b/dviware/dvisvgm/src/MetafontWrapper.cpp
@@ -0,0 +1,122 @@
+/*************************************************************************
+** MetafontWrapper.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <cstdlib>
+#include <cctype>
+#include <fstream>
+#include <sstream>
+#include "FileSystem.hpp"
+#include "FileFinder.hpp"
+#include "Message.hpp"
+#include "MetafontWrapper.hpp"
+#include "Process.hpp"
+#include "XMLString.hpp"
+
+using namespace std;
+
+
+MetafontWrapper::MetafontWrapper (string fname, string dir)
+ : _fontname(std::move(fname)), _dir(std::move(dir))
+{
+ // ensure that folder paths ends with slash
+ if (_dir.empty())
+ _dir = "./";
+ else if (_dir != "/" && dir[dir.length()-1] != '/')
+ _dir += '/';
+}
+
+
+/** Calls Metafont and evaluates the logfile. If a gf file was successfully
+ * generated the dpi value is stripped from the filename
+ * (e.g. cmr10.600gf => cmr10.gf). This makes life easier...
+ * @param[in] mode Metafont mode, e.g. "ljfour"
+ * @param[in] mag magnification factor
+ * @return true on success */
+bool MetafontWrapper::call (const string &mode, double mag) {
+ if (!FileFinder::instance().lookup(_fontname+".mf"))
+ return false; // mf file not available => no need to call the "slow" Metafont
+ FileSystem::remove(_fontname+".gf");
+
+#ifdef _WIN32
+#ifdef TEXLIVEWIN32
+ const char *mfname = "mf-nowin.exe";
+#else
+ const char *mfname = "mf.exe";
+#endif
+ const char *cmd = FileFinder::instance().lookup(mfname, false);
+ if (!cmd) {
+ Message::estream(true) << "can't run Metafont (" << mfname << " not found)\n";
+ return false;
+ }
+#else
+ const char *cmd = "mf";
+#endif
+ ostringstream oss;
+ oss << "\"\\mode=" << mode << ";" // set MF mode, e.g. 'proof', 'ljfour' or 'localfont'
+ "mode_setup;" // initialize MF variables
+ "mag:=" << mag << ";" // set magnification factor
+ "show pixels_per_inch*mag;" // print character resolution to stdout
+ "batchmode;" // don't halt on errors and don't print informational messages
+ "input " << _fontname << "\""; // load font description
+ Message::mstream(false, Message::MC_STATE) << "\nrunning Metafont for " << _fontname << '\n';
+ Process mf_process(cmd, oss.str());
+ string mf_messages;
+ mf_process.run(_dir, &mf_messages);
+
+ // get resolution value from stdout created by above MF command
+ char buf[256];
+ istringstream iss(mf_messages);
+ int resolution = 0;
+ while (iss) {
+ iss.getline(buf, sizeof(buf));
+ string line = buf;
+ if (line.substr(0, 3) == ">> ") {
+ resolution = atoi(line.substr(3).c_str());
+ break;
+ }
+ }
+ // compose expected name of GF file (see Metafont Book, p. 324)
+ string gfname = _dir + _fontname + ".";
+ if (resolution > 0)
+ gfname += XMLString(resolution);
+ gfname += "gf";
+ FileSystem::rename(gfname, _dir+_fontname+".gf"); // remove resolution value from filename
+ return FileSystem::exists(_dir+_fontname+".gf");
+}
+
+
+/** Calls Metafont if output files (tfm and gf) don't already exist.
+ * @param[in] mode Metafont mode to be used (e.g. 'ljfour')
+ * @param[in] mag magnification factor
+ * @return true on success */
+bool MetafontWrapper::make (const string &mode, double mag) {
+ ifstream tfm(_dir+_fontname+".tfm");
+ ifstream gf(_dir+_fontname+".gf");
+ if (gf && tfm) // @@ distinguish between gf and tfm
+ return true;
+ return call(mode, mag);
+}
+
+
+bool MetafontWrapper::success () const {
+ ifstream tfm(_dir+_fontname+".tfm");
+ ifstream gf(_dir+_fontname+".gf");
+ return tfm && gf;
+}
diff --git a/dviware/dvisvgm/src/MetafontWrapper.hpp b/dviware/dvisvgm/src/MetafontWrapper.hpp
new file mode 100644
index 0000000000..031eb01ebd
--- /dev/null
+++ b/dviware/dvisvgm/src/MetafontWrapper.hpp
@@ -0,0 +1,42 @@
+/*************************************************************************
+** MetafontWrapper.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef METAFONTWRAPPER_HPP
+#define METAFONTWRAPPER_HPP
+
+#include <string>
+
+
+class FileFinder;
+
+class MetafontWrapper
+{
+ public:
+ MetafontWrapper (std::string fname, std::string dir);
+ bool call (const std::string &mode, double mag);
+ bool make (const std::string &mode, double mag);
+ bool success () const;
+
+ private:
+ std::string _fontname;
+ std::string _dir;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/MiKTeXCom.cpp b/dviware/dvisvgm/src/MiKTeXCom.cpp
new file mode 100644
index 0000000000..72c2af7f0e
--- /dev/null
+++ b/dviware/dvisvgm/src/MiKTeXCom.cpp
@@ -0,0 +1,111 @@
+/*************************************************************************
+** MiKTeXCom.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <comdef.h>
+#include <string>
+#include "MessageException.hpp"
+#include "MiKTeXCom.hpp"
+
+using namespace std;
+
+
+/** Constructs a COM object representing a MiKTeX session. */
+MiKTeXCom::MiKTeXCom () : _session() {
+ if (FAILED(CoInitialize(nullptr)))
+ throw MessageException("COM library could not be initialized\n");
+ // try to initialize the MiKTeX session object
+#ifdef _MSC_VER
+ HRESULT hres = _session.CreateInstance(L"MiKTeX.Session");
+#elif defined(_WIN64)
+ HRESULT hres = CoCreateInstance(CLSID_MiKTeXSession2_9, 0, CLSCTX_LOCAL_SERVER, IID_ISession2, (void**)&_session);
+#else
+ HRESULT hres = CoCreateInstance(CLSID_MiKTeXSession2_9, 0, CLSCTX_INPROC_SERVER, IID_ISession2, (void**)&_session);
+#endif
+ if (FAILED(hres)) {
+ CoUninitialize();
+ throw MessageException("MiKTeX session could not be initialized");
+ }
+}
+
+
+MiKTeXCom::~MiKTeXCom () {
+ if (_session) {
+#ifdef _MSC_VER
+ _session.Release();
+#else
+ _session->Release();
+#endif
+ _session = nullptr; // prevent automatic call of Release() after CoUninitialize()
+ }
+ CoUninitialize();
+}
+
+
+/** Returns the MiKTeX version number. */
+string MiKTeXCom::getVersion () {
+#ifdef _MSC_VER
+ MiKTeXSetupInfo info = _session->GetMiKTeXSetupInfo();
+#else
+ MiKTeXSetupInfo info;
+ _session->GetMiKTeXSetupInfo(&info);
+#endif
+ _bstr_t version = info.version;
+ return string(version);
+}
+
+
+/** Returns the path of the directory where the MiKTeX binaries are located. */
+string MiKTeXCom::getBinDir () {
+#ifdef _MSC_VER
+ MiKTeXSetupInfo info = _session->GetMiKTeXSetupInfo();
+#else
+ MiKTeXSetupInfo info;
+ _session->GetMiKTeXSetupInfo(&info);
+#endif
+ _bstr_t bindir = info.binDirectory;
+ return string(bindir);
+}
+
+
+/** Try to lookup a given file in the MiKTeX directory tree.
+ * @param[in] fname name of file to lookup
+ * @return path of the file or 0 if it can't be found */
+const char* MiKTeXCom::findFile (const char *fname) {
+ try {
+ _bstr_t path;
+ static string ret;
+#ifdef _MSC_VER
+ HRESULT hres = _session->FindFile(fname, path.GetAddress());
+ bool found = (hres != 0);
+#else
+ VARIANT_BOOL found_var;
+ _session->FindFile(_bstr_t(fname), path.GetAddress(), &found_var);
+ bool found = (found_var == VARIANT_TRUE);
+#endif
+ if (found) {
+ ret = _bstr_t(path);
+ return ret.c_str();
+ }
+ return nullptr;
+ }
+ catch (_com_error &e) {
+ throw MessageException((const char*)e.Description());
+ }
+}
diff --git a/dviware/dvisvgm/src/MiKTeXCom.hpp b/dviware/dvisvgm/src/MiKTeXCom.hpp
new file mode 100644
index 0000000000..c87d0e3d86
--- /dev/null
+++ b/dviware/dvisvgm/src/MiKTeXCom.hpp
@@ -0,0 +1,50 @@
+/*************************************************************************
+** MiKTeXCom.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef MIKTEXCOM_HPP
+#define MIKTEXCOM_HPP
+
+#include <string>
+#include "windows.hpp"
+
+#ifdef _MSC_VER
+#import <MiKTeX209-session.tlb>
+using namespace MiKTeXSession2_9;
+#else
+#include "miktex209-session.h"
+#endif
+
+class MiKTeXCom {
+ public:
+ MiKTeXCom ();
+ ~MiKTeXCom ();
+ std::string getBinDir ();
+ std::string getVersion ();
+ const char* findFile (const char* fname);
+
+ private:
+#ifdef _MSC_VER
+ ISession2Ptr _session;
+#else
+ ISession2 *_session;
+#endif
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/NoPsSpecialHandler.cpp b/dviware/dvisvgm/src/NoPsSpecialHandler.cpp
new file mode 100644
index 0000000000..533e5390be
--- /dev/null
+++ b/dviware/dvisvgm/src/NoPsSpecialHandler.cpp
@@ -0,0 +1,45 @@
+/*************************************************************************
+** NoPsSpecialHandler.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include "Message.hpp"
+#include "NoPsSpecialHandler.hpp"
+
+using namespace std;
+
+
+bool NoPsSpecialHandler::process (const string&, istream&, SpecialActions&) {
+ _count++;
+ return true;
+}
+
+
+void NoPsSpecialHandler::dviEndPage (unsigned pageno, SpecialActions &actions) {
+ if (_count > 0) {
+ string suffix = (_count > 1 ? "s" : "");
+ Message::wstream(true) << _count << " PostScript special" << suffix << " ignored. The resulting SVG might look wrong.\n";
+ _count = 0;
+ }
+}
+
+
+vector<const char*> NoPsSpecialHandler::prefixes() const {
+ vector<const char*> pfx {"header=", "psfile=", "PSfile=", "ps:", "ps::", "!", "\""};
+ return pfx;
+}
diff --git a/dviware/dvisvgm/src/NoPsSpecialHandler.hpp b/dviware/dvisvgm/src/NoPsSpecialHandler.hpp
new file mode 100644
index 0000000000..f4f0421885
--- /dev/null
+++ b/dviware/dvisvgm/src/NoPsSpecialHandler.hpp
@@ -0,0 +1,41 @@
+/*************************************************************************
+** NoPsSpecialHandler.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef NOPSSPECIALHANDLER_HPP
+#define NOPSSPECIALHANDLER_HPP
+
+#include "SpecialHandler.hpp"
+
+class NoPsSpecialHandler : public SpecialHandler {
+ public:
+ NoPsSpecialHandler () : _count(0) {}
+ bool process (const std::string &prefix, std::istream &is, SpecialActions &actions) override;
+ const char* name () const override {return nullptr;}
+ const char* info () const override {return nullptr;}
+ std::vector<const char*> prefixes() const override;
+
+ protected:
+ void dviEndPage (unsigned pageno, SpecialActions &actions) override;
+
+ private:
+ unsigned _count; // number of PS specials skipped
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/NumericRanges.hpp b/dviware/dvisvgm/src/NumericRanges.hpp
new file mode 100644
index 0000000000..c51f1eba73
--- /dev/null
+++ b/dviware/dvisvgm/src/NumericRanges.hpp
@@ -0,0 +1,109 @@
+/*************************************************************************
+** NumericRanges.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef NUMERICRANGES_HPP
+#define NUMERICRANGES_HPP
+
+#include <algorithm>
+#include <list>
+#include <utility>
+
+template <class T>
+class NumericRanges
+{
+ public:
+ using Range = std::pair<T,T>;
+ using Container = std::list<Range>;
+ using ConstIterator = typename Container::const_iterator;
+
+ public:
+ void addRange (T value) {addRange(value, value);}
+ void addRange (T first, T last);
+ bool valueExists (T value) const;
+ size_t size () const {return _ranges.size();}
+ ConstIterator begin () const {return _ranges.begin();}
+ ConstIterator end () const {return _ranges.end();}
+ const Container& ranges () const {return _ranges;}
+
+ private:
+ Container _ranges;
+};
+
+
+/** Adds a numeric range to the collection.
+ * @param[in] first left bound of new range
+ * @param[in] last right bound of new range */
+template <class T>
+void NumericRanges<T>::addRange (T first, T last) {
+ if (first > last)
+ std::swap(first, last);
+ typename Container::iterator it = _ranges.begin();
+ while (it != _ranges.end() && first > it->first+1 && first > it->second+1)
+ ++it;
+ if (it == _ranges.end() || last < it->first-1 || first > it->second+1)
+ it = _ranges.insert(it, Range(first, last));
+ else if ((first < it->first && last >= it->first-1) || (first <= it->second+1 && last > it->second)) {
+ it->first = std::min(it->first, first);
+ it->second = std::max(it->second, last);
+ }
+ // merge adjacent ranges
+ if (it != _ranges.end()) {
+ typename Container::iterator l = it;
+ typename Container::iterator r = it;
+ if (l == _ranges.begin())
+ l = _ranges.end();
+ else
+ --l;
+ ++r;
+ bool l_modified = false;
+ bool r_modified = false;
+ if (l != _ranges.end() && l->second >= it->first-1) {
+ l->first = std::min(l->first, it->first);
+ l->second = std::max(l->second, it->second);
+ l_modified = true;
+ }
+ if (r != _ranges.end() && r->first <= it->second+1) {
+ r->first = std::min(r->first, it->first);
+ r->second = std::max(r->second, it->second);
+ r_modified = true;
+ }
+ if (l_modified || r_modified) {
+ _ranges.erase(it);
+ if (l_modified && r_modified && l->second >= r->first-1) {
+ l->first = std::min(l->first, r->first);
+ l->second = std::max(l->second, r->second);
+ _ranges.erase(r);
+ }
+ }
+ }
+}
+
+
+template <class T>
+bool NumericRanges<T>::valueExists (T value) const {
+ auto it = std::lower_bound(_ranges.begin(), _ranges.end(), Range(value, 0),
+ [](const Range &r1, const Range &r2) {
+ return r1.first < r2.first;
+ });
+ return (it != _ranges.end() && it->first <= value && it->second >= value);
+}
+
+
+#endif
diff --git a/dviware/dvisvgm/src/PDFParser.cpp b/dviware/dvisvgm/src/PDFParser.cpp
new file mode 100644
index 0000000000..12637a6368
--- /dev/null
+++ b/dviware/dvisvgm/src/PDFParser.cpp
@@ -0,0 +1,469 @@
+/*************************************************************************
+** PDFParser.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <cctype>
+#include <istream>
+#include <ostream>
+#include <sstream>
+#include <stdexcept>
+#include "InputReader.hpp"
+#include "PDFParser.hpp"
+#include "utility.hpp"
+
+using namespace std;
+
+
+/** Parses PDF from an input stream and returns the corresponding object representation.
+ * @param[in] is input stream the PDF data is read from
+ * @param[in] opHandler handler used to treat PDF operators
+ * @return the parsed objects */
+vector<PDFObject> PDFParser::parse (std::istream &is, const PDFOperatorHandler &opHandler) {
+ StreamInputReader ir(is);
+ return parse(ir, opHandler);
+}
+
+
+/** Parses PDF from a string and returns the corresponding object representation.
+ * @param[in] str string that contains the PDF data
+ * @param[in] opHandler handler used to treat PDF operators
+ * @return the parsed objects */
+vector<PDFObject> PDFParser::parse (const std::string &str, const PDFOperatorHandler &opHandler) {
+ istringstream iss(str);
+ return parse(iss, opHandler);
+}
+
+
+/** Parses PDF from an InputReader object and returns the corresponding object representation.
+ * @param[in] ir InputReader the PDF data is read from
+ * @param[in] opHandler handler used to treat PDF operators
+ * @return the parsed objects */
+vector<PDFObject> PDFParser::parse (InputReader &ir, const PDFOperatorHandler &opHandler) {
+ vector<PDFObject> objects;
+ while (!ir.eof()) {
+ ir.skipSpace();
+ if (ir.peek() == '%') // comment?
+ while (ir.get() != '\n' && !ir.eof());
+ else if (!ir.eof())
+ parse(ir, objects, opHandler);
+ }
+ return objects;
+}
+
+
+/** Default handler for PDF operators. Just adds the operators to the
+ * object vector without evaluating them.
+ * @param[in] opname name of the operator
+ * @param[in,out] objects vector holding the parsed objects */
+static void append_operator (const string &opname, vector<PDFObject> &objects) {
+ objects.emplace_back(PDFOperator(opname));
+}
+
+
+/** Parses PDF from an input stream and returns the corresponding object representation.
+ * @param[in] is input stream the PDF data is read from
+ * @return the parsed objects */
+vector<PDFObject> PDFParser::parse (std::istream &is) {
+ return parse(is, append_operator);
+}
+
+
+/** Parses PDF from a string and returns the corresponding object representation.
+ * @param[in] str string that contains the PDF data
+ * @return the parsed objects */
+vector<PDFObject> PDFParser::parse (const std::string &str) {
+ return parse(str, append_operator);
+}
+
+/** Parses PDF from an InputReader object and returns the corresponding object representation.
+ * @param[in] ir InputReader the PDF data is read from
+ * @return the parsed objects */
+vector<PDFObject> PDFParser::parse (InputReader &ir) {
+ return parse(ir, append_operator);
+}
+
+
+/** Parses PDF from an InputReader object and appends the recognized objects to a vector.
+ * @param[in] ir InputReader the PDF data is read from
+ * @param[in,out] objects the parsed PDF objects are appended to this vector
+ * @return the parsed objects */
+void PDFParser::parse (InputReader &ir, vector<PDFObject> &objects) {
+ parse(ir, objects, append_operator);
+}
+
+
+inline bool isoctaldigit (int c) {return c >= '0' && c <= '7';}
+
+/** Parses a PDF escape sequence of the form \FOO, where FOO is a single
+ * character or a sequence of 1-3 octal digits
+ * @return pair (s,c), s=true if c contains a parsed character */
+static pair<bool,int> parse_escape_seq (InputReader &ir) {
+ // leading backslash has already been read
+ if (isoctaldigit(ir.peek())) {
+ string str;
+ for (int i=0; i < 3 && isoctaldigit(ir.peek()); i++)
+ str += static_cast<char>(ir.get());
+ return pair<bool,int>{true, stoi(str, nullptr, 8)};
+ }
+ char c = static_cast<char>(ir.get());
+ switch (c) {
+ case 'n': c = '\n'; break;
+ case 'r': c = '\r'; break;
+ case 't': c = '\t'; break;
+ case 'b': c = '\b'; break;
+ case 'f': c = '\f'; break;
+ case '\n':
+ case '\r':
+ if ((c == '\n' && ir.peek() == '\r') || (c == '\r' && ir.peek() == '\n'))
+ ir.get();
+ return pair<bool,int>{false, 0};
+ }
+ return pair<bool,int>{true, c};
+}
+
+
+/** Parses a literal PDF string of the form (FOO). */
+static string parse_literal_string (InputReader &ir) {
+ string str;
+ ir.get(); // skip initial '('
+ int open_parens=1;
+ while (ir.peek() >= 0 && open_parens > 0) {
+ if (ir.peek() == '\n' || ir.peek() == '\r')
+ break;
+ int c = ir.get();
+ switch (c) {
+ case '(': open_parens++; break;
+ case ')': open_parens--; break;
+ case '\\':
+ pair<bool,int> state = parse_escape_seq(ir);
+ c = state.first ? state.second : -1;
+ break;
+ }
+ if (open_parens > 0 && c >= 0)
+ str += static_cast<char>(c);
+ }
+ if (open_parens > 0)
+ throw PDFException("missing ')' at end of literal string");
+ return str;
+}
+
+
+/** Gets a single hex digit from the InputReader. */
+static char get_hex_digit (InputReader &ir) {
+ int c = ir.get();
+ if (isxdigit(c))
+ return static_cast<char>(c);
+ ostringstream oss;
+ oss << "invalid hexadecimal digit '" << static_cast<char>(c) << "'";
+ throw PDFException(oss.str());
+}
+
+
+/** Parses a PDF hex string of the form <FOO>, where FOO is a sequence of
+ * hex digits optionally separated by whitespace. */
+static string parse_hex_string (InputReader &ir) {
+ // initial '<' has already been read
+ string str;
+ ir.skipSpace();
+ while (ir.peek() > 0 && ir.peek() != '>') {
+ string hexpair;
+ hexpair += get_hex_digit(ir);
+ ir.skipSpace();
+ if (ir.peek() > 0 && ir.peek() != '>')
+ hexpair += get_hex_digit(ir);
+ else if (ir.peek() == '>')
+ hexpair += '0';
+ ir.skipSpace();
+ str += static_cast<char>(stoi(hexpair, nullptr, 16));
+ }
+ if (ir.peek() != '>')
+ throw PDFException("missing '>' at end of hexadecimal string");
+ ir.get(); // skip closing '>'
+ return str;
+}
+
+
+using NumberVariant = mpark::variant<int, double>;
+
+/** Parses a PDF number from a string. The number is either integer or real.
+ * @param[in] str string to parse
+ * @param[out] nv variant holding the numeric value
+ * @return true if entire string has been parsed succesfully */
+static bool parse_number (const string &str, NumberVariant &nv) {
+ if (str.empty())
+ return false;
+ try {
+ size_t dotpos = str.find('.');
+ if (dotpos == string::npos) { // not a real number?
+ size_t count;
+ nv = NumberVariant(stoi(str, &count, 10)); // then try to convert str to int
+ return count == str.length(); // successful only if all characters have been processed
+ }
+ string postdot = str.substr(dotpos+1);
+ // ensure signless integer after dot to exclude exponental notation
+ // which is not allowed in PDF real number constants
+ if (!postdot.empty() && isdigit(postdot[0])) {
+ size_t count;
+ stoi(postdot, &count, 10);
+ if (count != postdot.length())
+ return false;
+ }
+ size_t count;
+ nv = NumberVariant(stod(str, &count));
+ return count == str.length();
+ }
+ catch (invalid_argument &e) {
+ return false;
+ }
+}
+
+
+/** Parses a PDF array from the input stream and returns a corresponding object. */
+PDFArray PDFParser::parseArray (InputReader &ir, const PDFOperatorHandler &opHandler) {
+ ir.get(); // skip '['
+ vector<PDFObject> localObjects;
+ while (!ir.eof() && ir.peek() != ']')
+ parse(ir, localObjects, opHandler);
+ ir.skipSpace();
+ if (ir.peek() != ']')
+ throw PDFException("missing ']' at end of array");
+ ir.get();
+ PDFArray arr;
+ std::move(localObjects.begin(), localObjects.end(), back_inserter(arr));
+ return arr;
+}
+
+
+/** Parses a PDF dictionary from the input stream and returns a corresponding object.
+ * The function expects that the first opening angle bracket denoting the start of an
+ * dictionary has already been swallowed from the stream. */
+PDFDict PDFParser::parseDict (InputReader &ir, const PDFOperatorHandler &opHandler) {
+ ir.get(); // skip second "<"
+ vector<PDFObject> localObjects;
+ while (!ir.eof() && ir.peek() != '>')
+ parse(ir, localObjects, opHandler);
+ if (ir.getString(2) != ">>")
+ throw PDFException("missing '>>' at end of dictionary");
+ PDFDict dict;
+ for (auto it=localObjects.begin(); it != localObjects.end(); ++it) {
+ if (!it->get<PDFName>())
+ throw PDFException("name key expected in dictionary");
+ const PDFName &key = *it->get<PDFName>();
+ if (++it == localObjects.end())
+ throw PDFException(string("missing dictionary value for key '")+key.str+"'");
+ dict.emplace(key.str, std::move(*it));
+ }
+ return dict;
+}
+
+
+static PDFStream parse_stream (InputReader &ir, const char *delim) {
+ do
+ ir.skipUntil("endstream");
+ while (ir.peek() >= 0 && !strchr(delim, ir.peek())); // ensure delimiter after "endstream"
+ return PDFStream();
+}
+
+
+static PDFIndirectObject parse_indirect_object (InputReader &ir, const char *delim, vector<PDFObject> &objects) {
+ do
+ ir.skipUntil("endobj");
+ while (ir.peek() >= 0 && !strchr(delim, ir.peek())); // ensure delimiter after "endobj"
+ if (objects.size() >= 2) {
+ const int *genno = objects.back().get<int>();
+ objects.pop_back();
+ const int *objno = objects.back().get<int>();
+ objects.pop_back();
+ if (objno && genno)
+ return PDFIndirectObject(*objno, *genno);
+ }
+ throw PDFException("object and generation number expected before 'obj'");
+}
+
+
+static PDFObjectRef parse_object_ref (vector<PDFObject> &objects) {
+ if (objects.size() >= 2) {
+ const int *genno = objects.back().get<int>();
+ objects.pop_back();
+ const int *objno = objects.back().get<int>();
+ objects.pop_back();
+ if (objno && genno)
+ return PDFObjectRef(*objno, *genno);
+ }
+ throw PDFException("object and generation number expected before 'R'");
+}
+
+
+/** Replaces all occurences of "#XX" (XX are two hex digits) with the corresponding character. */
+static string& subst_numeric_chars (string &str) {
+ for (size_t pos=str.find('#'); pos != string::npos; pos=str.find('#', pos+1)) {
+ if (pos > str.length()-3)
+ throw PDFException("sign character # must be followed by two hexadecimal digits");
+ if (isxdigit(str[pos+1]) && isxdigit(str[pos+2])) {
+ int c = stoi(str.substr(pos+1, 2), nullptr, 16);
+ if (c == 0)
+ throw PDFException("null character not permitted in name");
+ str.replace(pos, 3, 1, static_cast<char>(c));
+ }
+ else
+ throw PDFException("sign character # must be followed by two hexadecimal digits");
+ }
+ return str;
+}
+
+
+/** Parses a single PDF object from an InputReader object.
+ * @param[in,out] ir reader object to read the PDF data from
+ * @param[out] objects the parsed object is appended to this vector
+ * @param[in] opHandler handler used to treat PDF operators
+ * @throws PDFException on failure */
+void PDFParser::parse (InputReader &ir, vector<PDFObject> &objects, const PDFOperatorHandler &opHandler) {
+ static const char *delim = "()<>[]{}/% \t\n\r\f";
+ ir.skipSpace();
+ if (ir.peek() < 0)
+ return;
+ switch (ir.peek()) {
+ case '(':
+ objects.emplace_back(parse_literal_string(ir)); break;
+ case '[':
+ objects.emplace_back(util::make_unique<PDFArray>(parseArray(ir, opHandler))); break;
+ case '<':
+ ir.get();
+ if (ir.peek() != '<')
+ objects.emplace_back(parse_hex_string(ir));
+ else
+ objects.emplace_back(util::make_unique<PDFDict>(parseDict(ir, opHandler)));
+ break;
+ case '/': {
+ ir.get();
+ string name = ir.getString(delim);
+ objects.emplace_back(PDFName(subst_numeric_chars(name)));
+ break;
+ }
+ default: {
+ string str = ir.getString(delim);
+ if (str.empty())
+ break;
+ if (str == "null")
+ objects.emplace_back(PDFNull());
+ else if (str == "true")
+ objects.emplace_back(true);
+ else if (str == "false")
+ objects.emplace_back(false);
+ else if (str == "stream")
+ objects.emplace_back(parse_stream(ir, delim));
+ else if (str == "obj")
+ objects.emplace_back(parse_indirect_object(ir, delim, objects));
+ else if (str == "R")
+ objects.emplace_back(parse_object_ref(objects));
+ else {
+ NumberVariant number;
+ if (!parse_number(str, number))
+ opHandler(str, objects);
+ else {
+ if (mpark::get_if<int>(&number))
+ objects.emplace_back(mpark::get<int>(number));
+ else
+ objects.emplace_back(mpark::get<double>(number));
+ }
+ }
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+struct ToDoubleVisitor {
+ template <typename V>
+ double operator () (const V &val) {return 0;}
+};
+
+template<> double ToDoubleVisitor::operator () (const int &val) {return static_cast<double>(val);}
+template<> double ToDoubleVisitor::operator () (const double &val) {return val;}
+template<> double ToDoubleVisitor::operator () (const string &val) {
+ try {
+ return stod(val);
+ }
+ catch (exception &e) {
+ return 0;
+ }
+}
+
+
+PDFObject::operator double () const {
+ return mpark::visit(ToDoubleVisitor(), _value);
+}
+
+
+PDFObject::operator std::string () const {
+ ostringstream oss;
+ oss << *this;
+ return oss.str();
+}
+
+
+static std::ostream& operator << (std::ostream &os, const PDFName &name) {return os << name.str;}
+static ostream& operator << (ostream &os, const PDFNull&) {return os << "null";}
+static ostream& operator << (ostream &os, const PDFStream&) {return os << "stream";}
+static ostream& operator << (ostream &os, const PDFOperator &op) {return os << op.opname;}
+
+static ostream& operator << (ostream &os, const PDFIndirectObject &obj) {
+ return os << "obj(" << obj.objnum << ", " << obj.gennum << ')';
+}
+
+
+static ostream& operator << (ostream &os, const PDFObjectRef &ref) {
+ return os << "obj(" << ref.objnum << ", " << ref.gennum << ')';
+}
+
+
+static ostream& operator << (ostream &os, const unique_ptr<vector<PDFObject>> &val) {
+ os << '[';
+ for (auto it=val->begin(); it != val->end(); ++it) {
+ if (it != val->begin())
+ os << ", ";
+ it->write(os);
+ }
+ os << ']';
+ return os;
+}
+
+
+static ostream& operator << (ostream &os, const unique_ptr<Dictionary<string,PDFObject>> &val) {
+ os << "<<";
+ for (auto it=val->begin(); it != val->end(); ++it) {
+ if (it != val->begin())
+ os << ", ";
+ os << it->first << ':' << it->second;
+ }
+ os << ">>";
+ return os;
+}
+
+
+struct WriteVisitor {
+ explicit WriteVisitor (ostream &os) : _os(os) {}
+ template <typename T> void operator () (const T &val) {_os << val;}
+ ostream &_os;
+};
+
+
+void PDFObject::write (ostream &os) const {
+ mpark::visit(WriteVisitor(os), _value);
+}
diff --git a/dviware/dvisvgm/src/PDFParser.hpp b/dviware/dvisvgm/src/PDFParser.hpp
new file mode 100644
index 0000000000..6471668307
--- /dev/null
+++ b/dviware/dvisvgm/src/PDFParser.hpp
@@ -0,0 +1,185 @@
+/*************************************************************************
+** PDFParser.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef PDFPARSER_HPP
+#define PDFPARSER_HPP
+
+#include <initializer_list>
+#include <istream>
+#include <functional>
+#include <map>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+#include <mpark/variant.hpp>
+#include "MessageException.hpp"
+
+template <typename K, typename V>
+class Dictionary {
+ using Map = std::map<K, V>;
+
+ public:
+ typename Map::const_iterator begin () const {return _map.begin();}
+ typename Map::const_iterator end () const {return _map.end();}
+ typename Map::iterator begin () {return _map.begin();}
+ typename Map::iterator end () {return _map.end();}
+ typename Map::const_iterator find (const K &key) const {return _map.find(key);}
+ bool empty () const {return _map.empty();}
+ bool exists (const K &key) const {return _map.find(key) != _map.end();}
+ size_t size () const {return _map.size();}
+
+ const V* get (const K &key) const {
+ auto it = _map.find(key);
+ if (it != _map.end())
+ return &it->second;
+ return nullptr;
+ }
+
+ std::pair<typename Map::iterator,bool> emplace (const K &key, V &&value) {
+ return _map.emplace(key, std::forward<V>(value));
+ }
+
+ private:
+ Map _map;
+};
+
+//////////////////////////////////////////////////////////////////////////
+// PDF object types
+
+struct PDFNull {};
+struct PDFStream {};
+
+struct PDFIndirectObject {
+ PDFIndirectObject (int n, int gen) : objnum(n), gennum(gen) {}
+ int objnum, gennum;
+};
+
+struct PDFObjectRef {
+ PDFObjectRef (int n, int gen) : objnum(n), gennum(gen) {}
+ int objnum, gennum;
+};
+
+struct PDFOperator {
+ explicit PDFOperator (std::string name) : opname(std::move(name)) {}
+ std::string opname;
+};
+
+struct PDFName {
+ explicit PDFName (std::string val) : str(std::move(val)) {}
+ bool operator == (const PDFName &name) const {return str == name.str;}
+ std::string str;
+};
+
+class PDFObject;
+
+using PDFArray = std::vector<PDFObject>;
+using PDFDict = Dictionary<std::string, PDFObject>;
+
+//////////////////////////////////////////////////////////////////////////
+
+/** This class represents a single variadic PDF object. */
+class PDFObject {
+ using Value = mpark::variant<
+ PDFNull,
+ bool,
+ int,
+ double,
+ PDFName,
+ PDFStream,
+ PDFIndirectObject,
+ PDFObjectRef,
+ PDFOperator,
+ std::string,
+ std::unique_ptr<PDFArray>,
+ std::unique_ptr<PDFDict>
+ >;
+
+ public:
+ PDFObject () : _value(0) {}
+
+ template <typename T>
+ explicit PDFObject (T &&value) : _value(std::forward<T>(value)) {}
+
+ explicit PDFObject (const char *value) : _value(std::string(value)) {}
+ explicit operator std::string () const;
+ explicit operator double () const;
+
+ template <typename T>
+ const T* get () const {return mpark::get_if<T>(&_value);}
+
+ void write (std::ostream &os) const;
+
+ private:
+ Value _value;
+};
+
+
+template<> inline const PDFArray* PDFObject::get() const {
+ if (auto p = mpark::get_if<std::unique_ptr<PDFArray>>(&_value))
+ return &(**p);
+ return nullptr;
+}
+
+
+template<> inline const PDFDict* PDFObject::get() const {
+ if (auto p = mpark::get_if<std::unique_ptr<PDFDict>>(&_value))
+ return &(**p);
+ return nullptr;
+}
+
+
+inline std::ostream& operator << (std::ostream &os, const PDFObject &obj) {
+ obj.write(os);
+ return os;
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+class InputReader;
+
+class PDFParser {
+ public:
+ using PDFOperatorHandler = std::function<void (const std::string&, std::vector<PDFObject>&)>;
+
+ public:
+ std::vector<PDFObject> parse (std::istream &is);
+ std::vector<PDFObject> parse (const std::string &str);
+ std::vector<PDFObject> parse (InputReader &ir);
+ void parse (InputReader &ir, std::vector<PDFObject> &objects);
+
+ std::vector<PDFObject> parse (std::istream &is, const PDFOperatorHandler &opHandler);
+ std::vector<PDFObject> parse (const std::string &str, const PDFOperatorHandler &opHandler);
+ std::vector<PDFObject> parse (InputReader &ir, const PDFOperatorHandler &opHandler);
+ void parse (InputReader &ir, std::vector<PDFObject> &objects, const PDFOperatorHandler &opHandler);
+
+ protected:
+ PDFArray parseArray (InputReader &ir, const PDFOperatorHandler &opHandler);
+ PDFDict parseDict (InputReader &ir, const PDFOperatorHandler &opHandler);
+};
+
+
+/** If errors occur while parsing a sequence of PDF objects, an instance of this exception is thrown. */
+struct PDFException : public MessageException {
+ explicit PDFException (const std::string &msg) : MessageException(msg) {}
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/PDFToSVG.hpp b/dviware/dvisvgm/src/PDFToSVG.hpp
new file mode 100644
index 0000000000..f999de88e8
--- /dev/null
+++ b/dviware/dvisvgm/src/PDFToSVG.hpp
@@ -0,0 +1,63 @@
+/*************************************************************************
+** PDFToSVG.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef PDFTOSVG_HPP
+#define PDFTOSVG_HPP
+
+#include <fstream>
+#include "ImageToSVG.hpp"
+
+class PsSpecialHandler;
+
+class PDFToSVG : public ImageToSVG {
+ public:
+ PDFToSVG (const std::string &fname, SVGOutputBase &out) : ImageToSVG(fname, out) {}
+ bool isSinglePageFormat() const override {return false;}
+
+ /** Returns the total number of pages in the PDF file. */
+ int totalPageCount() override {
+ if (_totalPageCount < 0) {
+ _totalPageCount = psInterpreter().pdfPageCount(filename());
+ if (_totalPageCount < 1)
+ throw MessageException("can't retrieve number of pages from file " + filename());
+ }
+ return _totalPageCount;
+ }
+
+ protected:
+ bool imageIsValid () const override {
+ std::ifstream ifs(filename());
+ if (ifs) {
+ char buf[16];
+ ifs.getline(buf, 16);
+ return std::strncmp(buf, "%PDF-1.", 7) == 0;
+ }
+ return false;
+ }
+ std::string imageFormat () const override {return "PDF";}
+ BoundingBox imageBBox () const override {return BoundingBox();}
+ std::string psSpecialCmd () const override {return "pdffile=";}
+
+ private:
+ mutable int _totalPageCount = -1;
+};
+
+#endif
+
diff --git a/dviware/dvisvgm/src/PSFilter.hpp b/dviware/dvisvgm/src/PSFilter.hpp
new file mode 100644
index 0000000000..2e1cac5c77
--- /dev/null
+++ b/dviware/dvisvgm/src/PSFilter.hpp
@@ -0,0 +1,41 @@
+/*************************************************************************
+** PSFilter.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef PSFILTER_HPP
+#define PSFILTER_HPP
+
+class PSInterpreter;
+
+class PSFilter
+{
+ public:
+ explicit PSFilter (PSInterpreter &psi) : _psi(psi) {}
+ virtual ~PSFilter () =default;
+ virtual void execute (const char *code, size_t len) =0;
+ virtual bool active () const =0;
+
+ protected:
+ PSInterpreter& psInterpreter () {return _psi;}
+
+ private:
+ PSInterpreter &_psi;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/PSInterpreter.cpp b/dviware/dvisvgm/src/PSInterpreter.cpp
new file mode 100644
index 0000000000..e9d33d7265
--- /dev/null
+++ b/dviware/dvisvgm/src/PSInterpreter.cpp
@@ -0,0 +1,371 @@
+/*************************************************************************
+** PSInterpreter.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <cstring>
+#include <fstream>
+#include <sstream>
+#include <unordered_map>
+#include "FileFinder.hpp"
+#include "InputReader.hpp"
+#include "Message.hpp"
+#include "PSFilter.hpp"
+#include "PSInterpreter.hpp"
+#include "SignalHandler.hpp"
+
+using namespace std;
+
+
+/** Constructs a new PSInterpreter object.
+ * @param[in] actions template methods to be executed after recognizing the corresponding PS operator. */
+PSInterpreter::PSInterpreter (PSActions *actions)
+ : _mode(PS_NONE), _actions(actions)
+{
+}
+
+
+void PSInterpreter::init () {
+ if (!_initialized) {
+ vector<const char*> gsargs {
+ "gs", // dummy name
+ "-q", // be quiet, suppress gs banner
+ "-dNODISPLAY", // we don't need a display device
+ "-dNOPAUSE", // keep going
+ "-dWRITESYSTEMDICT", // leave systemdict writable as some operators must be replaced
+ "-dNOPROMPT"
+ };
+ if (int gsrev = _gs.revision())
+ gsargs.emplace_back(gsrev == 922 ? "-dREALLYDELAYBIND" : "-dDELAYBIND");
+ _gs.init(gsargs.size(), gsargs.data(), this);
+ _gs.set_stdio(input, output, error);
+ _initialized = true;
+ // Before executing any random PS code redefine some operators and run
+ // initializing PS code. This cannot be done in the constructor because we
+ // need the completely initialized PSInterpreter object here.
+ execute(PSDEFS);
+ }
+}
+
+
+PSActions* PSInterpreter::setActions (PSActions *actions) {
+ PSActions *old_actions = _actions;
+ _actions = actions;
+ return old_actions;
+}
+
+
+/** Checks if the given status value returned by Ghostscript indicates an error.
+ * @param[in] status status value returned by Ghostscript after the execution of a PS snippet
+ * @throw PSException if the status value indicates a PostScript error */
+void PSInterpreter::checkStatus (int status) {
+ if (status < 0) {
+ _mode = PS_QUIT;
+ if (status < -100)
+ throw PSException("fatal error");
+ if (_errorMessage.empty())
+ throw PSException(_gs.error_name(status));
+ throw PSException(_errorMessage);
+ }
+}
+
+
+/** Executes a chunk of PostScript code.
+ * @param[in] str buffer containing the code
+ * @param[in] len number of characters in buffer
+ * @param[in] flush If true, a final 'flush' is sent which forces the output buffer to be written immediately.
+ * @return true if the assigned number of bytes have been read */
+bool PSInterpreter::execute (const char *str, size_t len, bool flush) {
+ init();
+ if (_mode == PS_QUIT)
+ return false;
+
+ int status=0;
+ if (_mode == PS_NONE) {
+ _gs.run_string_begin(0, &status);
+ _mode = PS_RUNNING;
+ }
+ checkStatus(status);
+
+ bool complete=false;
+ if (_bytesToRead > 0 && len >= _bytesToRead) {
+ len = _bytesToRead;
+ complete = true;
+ }
+
+ if (_filter && _filter->active()) {
+ PSFilter *filter = _filter;
+ _filter = nullptr; // prevent recursion when filter calls execute()
+ filter->execute(str, len);
+ if (filter->active()) // filter still active after execution?
+ _filter = filter;
+ return complete;
+ }
+
+ // feed Ghostscript with code chunks that are not larger than 64KB
+ // => see documentation of gsapi_run_string_foo()
+ const char *p=str;
+ while (PS_RUNNING && len > 0) {
+ SignalHandler::instance().check();
+ size_t chunksize = min(len, (size_t)0xffff);
+ _gs.run_string_continue(p, chunksize, 0, &status);
+ p += chunksize;
+ len -= chunksize;
+ if (_bytesToRead > 0)
+ _bytesToRead -= chunksize;
+ if (status == -101) // e_Quit
+ _mode = PS_QUIT;
+ else
+ checkStatus(status);
+ }
+ if (flush) {
+ // force writing contents of output buffer
+ _gs.run_string_continue("\nflush ", 7, 0, &status);
+ }
+ return complete;
+}
+
+
+/** Executes a chunk of PostScript code read from a stream. The method returns on EOF.
+ * @param[in] is the input stream
+ * @param[in] flush If true, a final 'flush' is sent which forces the output buffer to be written immediately.
+ * @return true if the assigned number of bytes have been read */
+bool PSInterpreter::execute (istream &is, bool flush) {
+ char buf[4096];
+ bool finished = false;
+ while (is && !is.eof() && !finished) {
+ is.read(buf, 4096);
+ finished = execute(buf, is.gcount(), false);
+ }
+ execute("\n", 1, flush);
+ return finished;
+}
+
+
+bool PSInterpreter::executeRaw (const string &str, int n) {
+ _rawData.clear();
+ ostringstream oss;
+ oss << str << ' ' << n << " (raw) prcmd\n";
+ execute(oss.str());
+ return !_rawData.empty();
+}
+
+
+/** This callback function handles input from stdin to Ghostscript. Currently not needed.
+ * @param[in] inst pointer to calling instance of PSInterpreter
+ * @param[in] buf takes the read characters
+ * @param[in] len size of buffer buf
+ * @return number of characters read */
+int GSDLLCALL PSInterpreter::input (void *inst, char *buf, int len) {
+ return 0;
+}
+
+
+/** This callback function handles output from Ghostscript to stdout. It looks for
+ * emitted commands staring with "dvi." and executes them by calling method callActions.
+ * Ghostscript sends the text in chunks by several calls of this function.
+ * Unfortunately, the PostScript specification wants error messages also to be sent to stdout
+ * instead of stderr. Thus, we must collect and concatenate the chunks until an evaluable text
+ * snippet is completely received. Furthermore, error messages have to be recognized and to be
+ * filtered out.
+ * @param[in] inst pointer to calling instance of PSInterpreter
+ * @param[in] buf contains the characters to be output
+ * @param[in] len number of characters in buf
+ * @return number of processed characters (equals 'len') */
+int GSDLLCALL PSInterpreter::output (void *inst, const char *buf, int len) {
+ auto self = static_cast<PSInterpreter*>(inst);
+ if (self && self->_actions) {
+ const size_t MAXLEN = 512; // maximal line length (longer lines are of no interest)
+ const char *end = buf+len-1; // last position of buf
+ for (const char *first=buf, *last=buf; first <= end; last++, first=last) {
+ // move first and last to begin and end of the next line, respectively
+ while (last < end && *last != '\n')
+ last++;
+ size_t linelength = last-first+1;
+ if (linelength > MAXLEN) // skip long lines since they don't contain any relevant information
+ continue;
+
+ vector<char> &linebuf = self->_linebuf; // just a shorter name...
+ if ((*last == '\n' || !self->active()) || self->_inError) {
+ if (linelength + linebuf.size() > 1) { // prefix "dvi." plus final newline
+ SplittedCharInputBuffer ib(linebuf.empty() ? nullptr : &linebuf[0], linebuf.size(), first, linelength);
+ BufferInputReader in(ib);
+ if (self->_inError)
+ self->_errorMessage += string(first, linelength);
+ else {
+ in.skipSpace();
+ if (in.check("Unrecoverable error: ")) {
+ self->_errorMessage.clear();
+ while (!in.eof())
+ self->_errorMessage += char(in.get());
+ self->_inError = true;
+ }
+ else if (in.check("dvi."))
+ self->callActions(in);
+ }
+ }
+ linebuf.clear();
+ }
+ else { // no line end found =>
+ // save remaining characters and prepend them to the next incoming chunk of characters
+ if (linebuf.size() + linelength > MAXLEN)
+ linebuf.clear(); // don't care for long lines
+ else {
+ size_t currsize = linebuf.size();
+ linebuf.resize(currsize+linelength);
+ memcpy(&linebuf[currsize], first, linelength);
+ }
+ }
+ }
+ }
+ return len;
+}
+
+
+/** Evaluates a command emitted by Ghostscript and invokes the corresponding
+ * method of interface class PSActions.
+ * @param[in] in reader pointing to the next command */
+void PSInterpreter::callActions (InputReader &in) {
+ struct Operator {
+ int pcount; // number of parameters (< 0 : variable number of parameters)
+ void (PSActions::*handler)(vector<double> &p); // operation handler
+ };
+ static const unordered_map<string, Operator> operators {
+ {"applyscalevals", { 3, &PSActions::applyscalevals}},
+ {"clip", { 0, &PSActions::clip}},
+ {"clippath", { 0, &PSActions::clippath}},
+ {"closepath", { 0, &PSActions::closepath}},
+ {"curveto", { 6, &PSActions::curveto}},
+ {"eoclip", { 0, &PSActions::eoclip}},
+ {"eofill", { 0, &PSActions::eofill}},
+ {"fill", { 0, &PSActions::fill}},
+ {"grestore", { 0, &PSActions::grestore}},
+ {"grestoreall", { 0, &PSActions::grestoreall}},
+ {"gsave", { 0, &PSActions::gsave}},
+ {"initclip", { 0, &PSActions::initclip}},
+ {"lineto", { 2, &PSActions::lineto}},
+ {"makepattern", {-1, &PSActions::makepattern}},
+ {"moveto", { 2, &PSActions::moveto}},
+ {"newpath", { 1, &PSActions::newpath}},
+ {"querypos", { 2, &PSActions::querypos}},
+ {"raw", {-1, nullptr}},
+ {"restore", { 1, &PSActions::restore}},
+ {"rotate", { 1, &PSActions::rotate}},
+ {"save", { 1, &PSActions::save}},
+ {"scale", { 2, &PSActions::scale}},
+ {"setblendmode", { 1, &PSActions::setblendmode}},
+ {"setcmykcolor", { 4, &PSActions::setcmykcolor}},
+ {"setdash", {-1, &PSActions::setdash}},
+ {"setgray", { 1, &PSActions::setgray}},
+ {"sethsbcolor", { 3, &PSActions::sethsbcolor}},
+ {"setlinecap", { 1, &PSActions::setlinecap}},
+ {"setlinejoin", { 1, &PSActions::setlinejoin}},
+ {"setlinewidth", { 1, &PSActions::setlinewidth}},
+ {"setmatrix", { 6, &PSActions::setmatrix}},
+ {"setmiterlimit", { 1, &PSActions::setmiterlimit}},
+ {"setopacityalpha",{ 1, &PSActions::setopacityalpha}},
+ {"setshapealpha", { 1, &PSActions::setshapealpha}},
+ {"setpagedevice", { 0, &PSActions::setpagedevice}},
+ {"setpattern", {-1, &PSActions::setpattern}},
+ {"setrgbcolor", { 3, &PSActions::setrgbcolor}},
+ {"shfill", {-1, &PSActions::shfill}},
+ {"stroke", { 0, &PSActions::stroke}},
+ {"translate", { 2, &PSActions::translate}},
+ };
+ if (_actions) {
+ in.skipSpace();
+ auto it = operators.find(in.getWord());
+ if (it != operators.end()) {
+ if (!it->second.handler) { // raw string data received?
+ _rawData.clear();
+ in.skipSpace();
+ while (!in.eof()) {
+ _rawData.emplace_back(in.getString());
+ in.skipSpace();
+ }
+ }
+ else {
+ // collect parameters
+ vector<string> params;
+ int pcount = it->second.pcount;
+ if (pcount < 0) { // variable number of parameters?
+ in.skipSpace();
+ while (!in.eof()) { // read all available parameters
+ params.emplace_back(in.getString());
+ in.skipSpace();
+ }
+ }
+ else { // fix number of parameters
+ for (int i=0; i < pcount; i++) {
+ in.skipSpace();
+ params.emplace_back(in.getString());
+ }
+ }
+ // convert parameter strings to doubles
+ vector<double> v(params.size());
+ transform(params.begin(), params.end(), v.begin(), [](const string &str) {
+ return stod(str);
+ });
+ // call operator handler
+ (_actions->*it->second.handler)(v);
+ _actions->executed();
+ }
+ }
+ }
+}
+
+
+/** This callback function handles output from Ghostscript to stderr.
+ * @param[in] inst pointer to calling instance of PSInterpreter
+ * @param[in] buf contains the characters to be output
+ * @param[in] len number of chars in buf
+ * @return number of processed characters */
+int GSDLLCALL PSInterpreter::error (void *inst, const char *buf, int len) {
+ return len;
+}
+
+
+/** Returns the total number of pages of a PDF file.
+ * @param[in] fname name/path of the PDF file */
+int PSInterpreter::pdfPageCount (const string &fname) {
+ executeRaw("\n("+fname+")@pdfpagecount ", 1);
+ if (!_rawData.empty()) {
+ size_t index;
+ int ret = stoi(_rawData[0], &index, 10);
+ if (index > 0)
+ return ret;
+ }
+ return 0;
+}
+
+
+/** Returns the bounding box of a PDF page. If the selected page doesn't exist,
+ * the "invalid" flag of the returned bounding box is set.
+ * @param[in] fname name/path of the PDF file
+ * @param[in] pageno page number
+ * @return the bounding box of the given page */
+BoundingBox PSInterpreter::pdfPageBox (const string &fname, int pageno) {
+ BoundingBox pagebox;
+ executeRaw("\n"+to_string(pageno)+"("+fname+")@pdfpagebox ", 4);
+ if (_rawData.size() < 4)
+ pagebox.invalidate();
+ else
+ pagebox = BoundingBox(stod(_rawData[0]), stod(_rawData[1]), stod(_rawData[2]), stod(_rawData[3]));
+ return pagebox;
+}
diff --git a/dviware/dvisvgm/src/PSInterpreter.hpp b/dviware/dvisvgm/src/PSInterpreter.hpp
new file mode 100644
index 0000000000..7d35261a59
--- /dev/null
+++ b/dviware/dvisvgm/src/PSInterpreter.hpp
@@ -0,0 +1,132 @@
+/*************************************************************************
+** PSInterpreter.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef PSINTERPRETER_HPP
+#define PSINTERPRETER_HPP
+
+#include <cstring>
+#include <istream>
+#include <string>
+#include <vector>
+#include "BoundingBox.hpp"
+#include "Ghostscript.hpp"
+#include "InputReader.hpp"
+#include "MessageException.hpp"
+
+
+struct PSException : public MessageException {
+ explicit PSException (const std::string &msg) : MessageException(msg) {}
+};
+
+
+/** This interface provides the template methods called by PSInterpreter when executing a PS snippet.
+ * Each method corresponds to a PostScript operator of the same name. */
+struct PSActions {
+ virtual ~PSActions () =default;
+ virtual void applyscalevals (std::vector<double> &p) =0;
+ virtual void clip (std::vector<double> &p) =0;
+ virtual void clippath (std::vector<double> &p) =0;
+ virtual void closepath (std::vector<double> &p) =0;
+ virtual void curveto (std::vector<double> &p) =0;
+ virtual void eoclip (std::vector<double> &p) =0;
+ virtual void eofill (std::vector<double> &p) =0;
+ virtual void fill (std::vector<double> &p) =0;
+ virtual void gsave (std::vector<double> &p) =0;
+ virtual void grestore (std::vector<double> &p) =0;
+ virtual void grestoreall (std::vector<double> &p) =0;
+ virtual void initclip (std::vector<double> &p) =0;
+ virtual void lineto (std::vector<double> &p) =0;
+ virtual void makepattern (std::vector<double> &p) =0;
+ virtual void moveto (std::vector<double> &p) =0;
+ virtual void newpath (std::vector<double> &p) =0;
+ virtual void querypos (std::vector<double> &p) =0;
+ virtual void restore (std::vector<double> &p) =0;
+ virtual void rotate (std::vector<double> &p) =0;
+ virtual void save (std::vector<double> &p) =0;
+ virtual void scale (std::vector<double> &p) =0;
+ virtual void setblendmode (std::vector<double> &p) =0;
+ virtual void setcmykcolor (std::vector<double> &cmyk) =0;
+ virtual void setdash (std::vector<double> &p) =0;
+ virtual void setgray (std::vector<double> &p) =0;
+ virtual void sethsbcolor (std::vector<double> &hsb) =0;
+ virtual void setlinecap (std::vector<double> &p) =0;
+ virtual void setlinejoin (std::vector<double> &p) =0;
+ virtual void setlinewidth (std::vector<double> &p) =0;
+ virtual void setmatrix (std::vector<double> &p) =0;
+ virtual void setmiterlimit (std::vector<double> &p) =0;
+ virtual void setopacityalpha (std::vector<double> &p) =0;
+ virtual void setshapealpha (std::vector<double> &p) =0;
+ virtual void setpagedevice (std::vector<double> &p) =0;
+ virtual void setpattern (std::vector<double> &p) =0;
+ virtual void setrgbcolor (std::vector<double> &rgb) =0;
+ virtual void shfill (std::vector<double> &p) =0;
+ virtual void stroke (std::vector<double> &p) =0;
+ virtual void translate (std::vector<double> &p) =0;
+ virtual void executed () {} // triggered if one of the above PS operators has been executed
+};
+
+class PSFilter;
+
+/** This class provides methods to execute chunks of PostScript code and calls
+ * several template methods on invocation of selected PS operators (see PSActions). */
+class PSInterpreter {
+ enum Mode {PS_NONE, PS_RUNNING, PS_QUIT};
+
+ public:
+ explicit PSInterpreter (PSActions *actions=nullptr);
+ PSInterpreter (const PSInterpreter &psi) =delete;
+ bool execute (const char *str, size_t len, bool flush=true);
+ bool execute (const char *str, bool flush=true) {return execute(str, std::strlen(str), flush);}
+ bool execute (const std::string &str, bool flush=true) {return execute(str.c_str(), flush);}
+ bool execute (std::istream &is, bool flush=true);
+ bool executeRaw (const std::string &str, int n);
+ bool active () const {return _mode != PS_QUIT;}
+ void limit (size_t max_bytes) {_bytesToRead = max_bytes;}
+ void setFilter (PSFilter *filter) {_filter = filter;}
+ PSActions* setActions (PSActions *actions);
+ int pdfPageCount (const std::string &fname);
+ BoundingBox pdfPageBox (const std::string &fname, int pageno);
+ const std::vector<std::string>& rawData () const {return _rawData;}
+
+ protected:
+ void init ();
+ // callback functions
+ static int GSDLLCALL input (void *inst, char *buf, int len);
+ static int GSDLLCALL output (void *inst, const char *buf, int len);
+ static int GSDLLCALL error (void *inst, const char *buf, int len);
+
+ void checkStatus (int status);
+ void callActions (InputReader &cib);
+
+ private:
+ Ghostscript _gs;
+ Mode _mode; ///< current execution mode
+ PSActions *_actions=nullptr; ///< actions to be performed
+ PSFilter *_filter=nullptr; ///< active filter used to process PS code
+ size_t _bytesToRead=0; ///< if > 0, maximal number of bytes to be processed by following calls of execute()
+ std::vector<char> _linebuf;
+ std::string _errorMessage; ///< text of error message
+ bool _inError=false; ///< true if scanning error message
+ bool _initialized=false; ///< true if PSInterpreter has been completely initialized
+ std::vector<std::string> _rawData; ///< raw data received
+ static const char *PSDEFS; ///< initial PostScript definitions
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/PSPattern.cpp b/dviware/dvisvgm/src/PSPattern.cpp
new file mode 100644
index 0000000000..90ff6a5685
--- /dev/null
+++ b/dviware/dvisvgm/src/PSPattern.cpp
@@ -0,0 +1,175 @@
+/*************************************************************************
+** PSPattern.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <array>
+#include <sstream>
+#include <vector>
+#include "BoundingBox.hpp"
+#include "PSPattern.hpp"
+#include "SpecialActions.hpp"
+#include "SVGTree.hpp"
+#include "utility.hpp"
+#include "XMLNode.hpp"
+
+using namespace std;
+
+
+string PSPattern::svgID () const {
+ return XMLString("pat")+XMLString(_id);
+}
+
+
+/** Appends the definition of this pattern to the "def" section of the SVG tree. */
+void PSPattern::apply (SpecialActions &actions) {
+ if (auto pattern = createPatternNode())
+ actions.svgTree().appendToDefs(std::move(pattern));
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+PSTilingPattern::PSTilingPattern (int id, BoundingBox &bbox, Matrix &matrix, double xstep, double ystep)
+ : PSPattern(id), _bbox(bbox), _matrix(matrix), _xstep(xstep), _ystep(ystep)
+{
+ _groupNode = PSTilingPattern::createGroupNode();
+ _groupNodePtr = _groupNode.get();
+}
+
+
+/** Assigns a new group element. */
+void PSTilingPattern::setGroupNode (unique_ptr<XMLElement> node) {
+ _groupNode = std::move(node);
+ _groupNodePtr = _groupNode.get();
+}
+
+
+/** Creates a new pattern element representing the pattern defined in the PS code. */
+unique_ptr<XMLElement> PSTilingPattern::createPatternNode () const {
+ if (!_groupNode)
+ return nullptr;
+ BoundingBox box(_bbox.minX(), _bbox.minY(), _bbox.minX()+_xstep, _bbox.minY()+_ystep);
+ auto pattern = util::make_unique<XMLElement>("pattern");
+ pattern->addAttribute("id", svgID());
+ pattern->addAttribute("x", box.minX());
+ pattern->addAttribute("y", box.minY());
+ pattern->addAttribute("width", box.width());
+ pattern->addAttribute("height", box.height());
+ pattern->addAttribute("viewBox", box.toSVGViewBox());
+ pattern->addAttribute("patternUnits", "userSpaceOnUse");
+ if (!_matrix.isIdentity())
+ pattern->addAttribute("patternTransform", _matrix.toSVG());
+ if (_xstep < _bbox.width() || _ystep < _bbox.height()) { // overlapping tiles?
+ // disable clipping at the tile borders => tiles become "transparent"
+ pattern->addAttribute("overflow", "visible");
+ }
+ if (auto clip = createClipNode())
+ pattern->append(std::move(clip));
+ pattern->append(std::move(_groupNode));
+ _groupNode.reset();
+ return pattern;
+}
+
+
+/** Creates a new clip element restricting the drawing area to the
+ * dimensions given in the definition of the pattern. */
+unique_ptr<XMLElement> PSTilingPattern::createClipNode() const {
+ auto clip = util::make_unique<XMLElement>("clipPath");
+ clip->addAttribute("id", "pc"+XMLString(psID()));
+ auto rect = util::make_unique<XMLElement>("rect");
+ rect->addAttribute("x", _bbox.minX());
+ rect->addAttribute("y", _bbox.minY());
+ rect->addAttribute("width", _bbox.width());
+ rect->addAttribute("height", _bbox.height());
+ clip->append(std::move(rect));
+ return clip;
+}
+
+
+/** Creates a new group element that contains all "drawing" elements that
+ * define the pattern graphic. */
+unique_ptr<XMLElement> PSTilingPattern::createGroupNode () const {
+ // add all succeeding path elements to this group
+ auto group = util::make_unique<XMLElement>("g");
+ group->addAttribute("clip-path", XMLString("url(#pc")+XMLString(psID())+")");
+ return group;
+}
+
+
+void PSTilingPattern::apply (SpecialActions &actions) {
+ PSPattern::apply(actions);
+ _groupNode.reset();
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+PSColoredTilingPattern::PSColoredTilingPattern (int id, BoundingBox &bbox, Matrix &matrix, double xstep, double ystep)
+ : PSTilingPattern(id, bbox, matrix, xstep, ystep)
+{
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+PSUncoloredTilingPattern::PSUncoloredTilingPattern (int id, BoundingBox &bbox, Matrix &matrix, double xstep, double ystep)
+ : PSTilingPattern(id, bbox, matrix, xstep, ystep), _applied()
+{
+}
+
+
+/** Returns an SVG id value that identifies this pattern with the current color applied. */
+string PSUncoloredTilingPattern::svgID () const {
+ ostringstream oss;
+ oss << PSPattern::svgID() << '-' << hex << uint32_t(_currentColor);
+ return oss.str();
+}
+
+
+/** Appends the definition of this pattern with the current color applied
+ * to the "def" section of the SVG tree. */
+void PSUncoloredTilingPattern::apply (SpecialActions &actions) {
+ auto it=_colors.find(_currentColor);
+ if (it == _colors.end()) {
+ if (_applied)
+ setGroupNode(util::static_unique_ptr_cast<XMLElement>(getGroupNode()->clone()));
+ // assign current color to the pattern graphic
+ vector<XMLElement*> colored_elems;
+ const array<const char*, 2> attribs = {{"fill", "stroke"}};
+ for (const char *attrib : attribs) {
+ getGroupNode()->getDescendants(nullptr, attrib, colored_elems);
+ for (XMLElement *elem : colored_elems) {
+ if (string(elem->getAttributeValue(attrib)) != "none")
+ elem->addAttribute(attrib, _currentColor.svgColorString());
+ }
+ colored_elems.clear();
+ }
+ PSPattern::apply(actions);
+ _colors.insert(_currentColor);
+ _applied = true;
+ }
+}
+
+
+unique_ptr<XMLElement> PSUncoloredTilingPattern::createClipNode() const {
+ // only the first instance of this patterns get a clip element
+ if (_colors.empty())
+ return PSTilingPattern::createClipNode();
+ return nullptr;
+}
diff --git a/dviware/dvisvgm/src/PSPattern.hpp b/dviware/dvisvgm/src/PSPattern.hpp
new file mode 100644
index 0000000000..7dde27827d
--- /dev/null
+++ b/dviware/dvisvgm/src/PSPattern.hpp
@@ -0,0 +1,97 @@
+/*************************************************************************
+** PSPattern.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef PSPATTERN_HPP
+#define PSPATTERN_HPP
+
+#include <memory>
+#include <set>
+#include <string>
+#include "BoundingBox.hpp"
+#include "Color.hpp"
+#include "Matrix.hpp"
+#include "XMLNode.hpp"
+
+
+class SpecialActions;
+class SVGTree;
+class XMLElement;
+
+class PSPattern {
+ public:
+ virtual ~PSPattern () =default;
+ virtual int psID () const {return _id;}
+ virtual std::string svgID () const;
+ virtual void apply (SpecialActions &actions);
+
+ protected:
+ explicit PSPattern (int id) : _id(id) {}
+ virtual std::unique_ptr<XMLElement> createPatternNode () const =0;
+
+ private:
+ int _id; ///< PostSCript ID of this pattern
+};
+
+
+class PSTilingPattern : public PSPattern {
+ public:
+ virtual XMLElement* getContainerNode () {return _groupNode.get();}
+ void apply (SpecialActions &actions) override;
+
+ protected:
+ PSTilingPattern (int id, BoundingBox &bbox, Matrix &matrix, double xstep, double ystep);
+ std::unique_ptr<XMLElement> createPatternNode () const override;
+ virtual std::unique_ptr<XMLElement> createClipNode () const;
+ virtual std::unique_ptr<XMLElement> createGroupNode () const;
+ virtual XMLElement* getGroupNode () const {return _groupNodePtr;}
+ virtual void setGroupNode (std::unique_ptr<XMLElement> node);
+
+ private:
+ BoundingBox _bbox; ///< bounding box of the tile graphics
+ Matrix _matrix; ///< tile transformation
+ double _xstep, _ystep; ///< horizontal and vertical distance between neighboured tiles
+ mutable std::unique_ptr<XMLElement> _groupNode; ///< group containing the drawing elements
+ XMLElement *_groupNodePtr; ///< keeps a pointer to the group node even after moving _groupNode to the SVGTree
+};
+
+
+class PSColoredTilingPattern final : public PSTilingPattern {
+ public:
+ PSColoredTilingPattern (int id, BoundingBox &bbox, Matrix &matrix, double xstep, double ystep);
+};
+
+
+class PSUncoloredTilingPattern final : public PSTilingPattern {
+ public:
+ PSUncoloredTilingPattern (int id, BoundingBox &bbox, Matrix &matrix, double xstep, double ystep);
+ std::string svgID () const override;
+ void setColor (Color color) {_currentColor = color;}
+ void apply (SpecialActions &actions) override;
+
+ protected:
+ std::unique_ptr<XMLElement> createClipNode () const override;
+
+ private:
+ std::set<Color> _colors; ///< colors this pattern has already been drawn with
+ Color _currentColor; ///< current color to be applied
+ bool _applied; ///< has pattern with current group node already been applied to the SVG tree?
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/PSPreviewFilter.cpp b/dviware/dvisvgm/src/PSPreviewFilter.cpp
new file mode 100644
index 0000000000..36f0303e0f
--- /dev/null
+++ b/dviware/dvisvgm/src/PSPreviewFilter.cpp
@@ -0,0 +1,110 @@
+/*************************************************************************
+** PSPreviewFilter.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <vector>
+#include "InputBuffer.hpp"
+#include "InputReader.hpp"
+#include "PSInterpreter.hpp"
+#include "PSPreviewFilter.hpp"
+#include "SpecialActions.hpp"
+
+
+using namespace std;
+
+PSPreviewFilter::PSPreviewFilter (PSInterpreter &psi)
+ : PSFilter(psi), _active(false), _tightpage(false), _dvi2bp(1.0/65536.0)
+{
+}
+
+
+/** Activates this filter so that the PS code will be redirected through it if
+ * it's hooked into the PSInterpreter. */
+void PSPreviewFilter::activate () {
+ if (_tightpage) // reactivate filter?
+ _active = true;
+ else { // first activation?
+ _tightpage = _active = false;
+ // try to retrieve version string of preview package set in the PS header section
+ if (psInterpreter().executeRaw("SDict begin currentdict/preview@version known{preview@version}{0}ifelse end", 1))
+ _version = psInterpreter().rawData()[0];
+ // check if tightpage option was set
+ if (_version != "0" && psInterpreter().executeRaw("SDict begin preview@tightpage end", 1)) {
+ _tightpage = (psInterpreter().rawData()[0] == "true");
+ _active = true;
+ }
+ }
+ _boxExtents.clear();
+}
+
+
+/** Tries to extract the bounding box information from a chunk of PostScript code.
+ * @param[in] code pointer to buffer with PS code to filter
+ * @param[in] len number of bytes in buffer */
+void PSPreviewFilter::execute (const char *code, size_t len) {
+ // If the "tightpage" option was set in the TeX file, 7 integers representing the
+ // extent of the bounding box are present at the begin of each page.
+ if (!_tightpage)
+ psInterpreter().execute(code, len);
+ else {
+ // Read bounding box information pushed on the operand stack by the preview package.
+ // It consists of 7 values in DVI units:
+ // adj_left, adj_bottom, adj_right, adj_top, height, depth, width,
+ // where the first 4 values are set by \PreviewBorder or \PreviewBbAdjust. They denote
+ // the border adjustments to create additional space around the graphics.
+ // The baseline of the tight box extends from (0,0) to (tight_width, 0).
+ CharInputBuffer ib(code, len);
+ BufferInputReader ir(ib);
+ ir.skipSpace();
+ int val;
+ while (ir.parseInt(val) && _boxExtents.size() <= 7) {
+ _boxExtents.push_back(val);
+ ir.skipSpace();
+ }
+ }
+ _active = false; // no further processing required
+}
+
+
+/** Returns the bounding box defined by the preview package. */
+bool PSPreviewFilter::getBoundingBox (BoundingBox &bbox) const {
+ if (_boxExtents.size() < 7)
+ return false;
+ const double leftX = _boxExtents[0]*_dvi2bp;
+ bbox = BoundingBox(leftX, -height(), width()+leftX, depth());
+ return true;
+}
+
+
+/** Returns the box height in PS points, or -1 if no data was found or read yet. */
+double PSPreviewFilter::height () const {
+ return _boxExtents.size() > 4 ? (_boxExtents[4]+_boxExtents[3])*_dvi2bp : -1;
+}
+
+
+/** Returns the box depth in PS points, or -1 if no data was found or read yet. */
+double PSPreviewFilter::depth () const {
+ return _boxExtents.size() > 5 ? (_boxExtents[5]-_boxExtents[1])*_dvi2bp : -1;
+}
+
+
+/** Returns the box width in PS points, or -1 if no data was found or read yet. */
+double PSPreviewFilter::width () const {
+ return _boxExtents.size() > 6 ? (_boxExtents[6]+_boxExtents[2]-_boxExtents[0])*_dvi2bp : -1;
+}
diff --git a/dviware/dvisvgm/src/PSPreviewFilter.hpp b/dviware/dvisvgm/src/PSPreviewFilter.hpp
new file mode 100644
index 0000000000..7de5a2e2a8
--- /dev/null
+++ b/dviware/dvisvgm/src/PSPreviewFilter.hpp
@@ -0,0 +1,54 @@
+/*************************************************************************
+** PSPreviewFilter.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef PSPREVIEWFILTER_HPP
+#define PSPREVIEWFILTER_HPP
+
+#include <string>
+#include <vector>
+#include "BoundingBox.hpp"
+#include "PSFilter.hpp"
+
+class SpecialActions;
+
+class PSPreviewFilter : public PSFilter {
+ public:
+ explicit PSPreviewFilter (PSInterpreter &psi);
+ void activate ();
+ void execute (const char *code, size_t len) override;
+ bool active () const override {return _active;}
+ std::string version () const {return _version;}
+ bool tightpage () const {return _tightpage;}
+ void setDviScaleFactor (double dvi2bp) {_dvi2bp = dvi2bp;}
+ void assignBorders (BoundingBox &bbox) const;
+ bool getBoundingBox (BoundingBox &bbox) const;
+ double height () const;
+ double depth () const;
+ double width () const;
+
+ private:
+ std::string _version; ///< version string of preview package
+ bool _active; ///< true if filter is active
+ bool _tightpage; ///< true if tightpage option was given
+ double _dvi2bp; ///< factor to convert dvi units to PS points
+ std::vector<int> _boxExtents; ///< bounding box data set by the preview package (in DVI units)
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/PageRanges.cpp b/dviware/dvisvgm/src/PageRanges.cpp
new file mode 100644
index 0000000000..e6c1a8287b
--- /dev/null
+++ b/dviware/dvisvgm/src/PageRanges.cpp
@@ -0,0 +1,84 @@
+/*************************************************************************
+** PageRanges.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <sstream>
+#include "InputBuffer.hpp"
+#include "InputReader.hpp"
+#include "PageRanges.hpp"
+
+using namespace std;
+
+
+/** Analyzes a string describing a range sequence.
+ * Syntax: ([0-9]+(-[0-9]*)?)|(-[0-9]+)(,([0-9]+(-[0-9]*)?)|(-[0-9]+))*
+ * @param[in] str string to parse
+ * @param[in] max_page greatest allowed value
+ * @return true on success; false denotes a syntax error */
+bool PageRanges::parse (const string &str, int max_page) {
+ StringInputBuffer ib(str);
+ BufferInputReader ir(ib);
+ while (ir) {
+ int first=1;
+ int last=max_page;
+ ir.skipSpace();
+ if (!isdigit(ir.peek()) && ir.peek() != '-')
+ return false;
+
+ if (isdigit(ir.peek()))
+ first = ir.getInt();
+ ir.skipSpace();
+ if (ir.peek() == '-') {
+ while (ir.peek() == '-')
+ ir.get();
+ ir.skipSpace();
+ if (isdigit(ir.peek()))
+ last = ir.getInt();
+ }
+ else
+ last = first;
+ ir.skipSpace();
+ if (ir.peek() == ',') {
+ ir.get();
+ if (ir.eof())
+ return false;
+ }
+ else if (!ir.eof())
+ return false;
+ if (first > last)
+ swap(first, last);
+ first = max(1, first);
+ last = max(first, last);
+ if (max_page > 0) {
+ first = min(first, max_page);
+ last = min(last, max_page);
+ }
+ addRange(first, last);
+ }
+ return true;
+}
+
+
+/** Returns the number of pages. */
+size_t PageRanges::numberOfPages () const {
+ size_t sum=0;
+ for (const auto &entry : *this)
+ sum += entry.second - entry.first + 1;
+ return sum;
+}
diff --git a/dviware/dvisvgm/src/PageRanges.hpp b/dviware/dvisvgm/src/PageRanges.hpp
new file mode 100644
index 0000000000..5ecc7a998c
--- /dev/null
+++ b/dviware/dvisvgm/src/PageRanges.hpp
@@ -0,0 +1,36 @@
+/*************************************************************************
+** PageRanges.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef PAGERANGES_HPP
+#define PAGERANGES_HPP
+
+#include <list>
+#include <string>
+#include <utility>
+#include "NumericRanges.hpp"
+
+class PageRanges : public NumericRanges<int>
+{
+ public:
+ bool parse (const std::string &str, int max_page=0);
+ size_t numberOfPages () const;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/PageSize.cpp b/dviware/dvisvgm/src/PageSize.cpp
new file mode 100644
index 0000000000..2f0a48aeb9
--- /dev/null
+++ b/dviware/dvisvgm/src/PageSize.cpp
@@ -0,0 +1,146 @@
+/*************************************************************************
+** PageSize.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <cctype>
+#include <cmath>
+#include <sstream>
+#include "PageSize.hpp"
+#include "utility.hpp"
+
+using namespace std;
+
+
+/** Computes width and height of ISO/DIN An.
+ * @param[in] n the A level (e.g. n=4 => DIN A4)
+ * @param[out] width contains the page width when function returns
+ * @param[out] height contains the page height when function returns */
+static void computeASize (int n, Length &width, Length &height) {
+ height.set(floor(1189.0/pow(math::SQRT2, n)+0.5), Length::Unit::MM);
+ width.set(floor(height.mm()/math::SQRT2+0.5), Length::Unit::MM);
+}
+
+
+/** Computes width and height of ISO/DIN Bn.
+ * @param[in] n the B level (e.g. n=4 => DIN B4)
+ * @param[out] width contains the page width when function returns
+ * @param[out] height contains the page height when function returns */
+static void computeBSize (int n, Length &width, Length &height) {
+ Length w, h;
+ computeASize(n, width, height);
+ computeASize(n-1, w, h);
+ width.set(floor(sqrt(width.mm() * w.mm())+0.5), Length::Unit::MM);
+ height.set(floor(sqrt(height.mm() * h.mm())+0.5), Length::Unit::MM);
+}
+
+
+/** Computes width and height of ISO/DIN Cn.
+ * @param[in] n the C level (e.g. n=4 => DIN C4)
+ * @param[out] width contains the page width when function returns
+ * @param[out] height contains the page height when function returns */
+static void computeCSize (int n, Length &width, Length &height) {
+ Length w, h;
+ computeASize(n, width, height);
+ computeBSize(n, w, h);
+ width.set(floor(sqrt(width.mm() * w.mm())+0.5), Length::Unit::MM);
+ height.set(floor(sqrt(height.mm() * h.mm())+0.5), Length::Unit::MM);
+}
+
+
+/** Computes width and height of ISO/DIN Dn in millimeters.
+ * @param[in] n the D level (e.g. n=4 => DIN D4)
+ * @param[out] width contains the page width when function returns
+ * @param[out] height contains the page height when function returns */
+static void computeDSize (int n, Length &width, Length &height) {
+ Length w, h;
+ computeASize(n, width, height);
+ computeBSize(n+1, w, h);
+ width.set(floor(sqrt(width.mm() * w.mm())+0.5), Length::Unit::MM);
+ height.set(floor(sqrt(height.mm() * h.mm())+0.5), Length::Unit::MM);
+}
+
+
+/** Constructs a PageSize object of given size.
+ * @param[in] name specifies the page size, e.g. "A4" or "letter" */
+PageSize::PageSize (const string &name) : _width(0), _height(0) {
+ resize(name);
+}
+
+
+void PageSize::resize (Length w, Length h) {
+ _width = w;
+ _height = h;
+}
+
+
+void PageSize::resize (string name) {
+ if (name.length() < 2)
+ throw PageSizeException("unknown page format: "+name);
+
+ name = util::tolower(name);
+ // extract optional suffix
+ size_t pos = name.rfind('-');
+ bool landscape = false;
+ if (pos != string::npos) {
+ string suffix = name.substr(pos);
+ name = name.substr(0, pos);
+ if (suffix == "-l" || suffix == "-landscape")
+ landscape = true;
+ else if (suffix != "-p" && suffix != "-portrait")
+ throw PageSizeException("invalid page format suffix: " + suffix);
+ }
+
+ if (name == "invoice") {
+ _width = 140_mm;
+ _height = 216_mm;
+ }
+ else if (name == "executive") {
+ _width = 184_mm;
+ _height = 267_mm;
+ }
+ else if (name == "legal") {
+ _width = 216_mm;
+ _height = 356_mm;
+ }
+ else if (name == "letter") {
+ _width = 216_mm;
+ _height = 279_mm;
+ }
+ else if (name == "ledger") {
+ _width = 279_mm;
+ _height = 432_mm;
+ }
+ else if (isdigit(name[1]) && name.length() < 5) { // limit length of number to prevent arithmetic errors
+ istringstream iss(name.substr(1));
+ int n;
+ iss >> n;
+ switch (name[0]) {
+ case 'a' : computeASize(n, _width, _height); break;
+ case 'b' : computeBSize(n, _width, _height); break;
+ case 'c' : computeCSize(n, _width, _height); break;
+ case 'd' : computeDSize(n, _width, _height); break;
+ default : throw PageSizeException("invalid page format: "+name);
+ }
+ }
+ if (_width.pt() == 0 || _height.pt() == 0)
+ throw PageSizeException("unknown page format: "+name);
+ if (landscape)
+ swap(_width, _height);
+}
diff --git a/dviware/dvisvgm/src/PageSize.hpp b/dviware/dvisvgm/src/PageSize.hpp
new file mode 100644
index 0000000000..c785a89564
--- /dev/null
+++ b/dviware/dvisvgm/src/PageSize.hpp
@@ -0,0 +1,46 @@
+/*************************************************************************
+** PageSize.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef PAGESIZE_HPP
+#define PAGESIZE_HPP
+
+#include "Length.hpp"
+#include "MessageException.hpp"
+
+struct PageSizeException : public MessageException {
+ explicit PageSizeException (const std::string &msg) : MessageException(msg) {}
+};
+
+class PageSize {
+ public:
+ PageSize () =default;
+ PageSize (Length w, Length h) : _width(w), _height(h) {}
+ explicit PageSize (const std::string &name);
+ void resize (std:: string name);
+ void resize (Length w, Length h);
+ Length width () const {return _width;}
+ Length height () const {return _height;}
+ bool valid () const {return _width.pt() > 0 && _height.pt() > 0;}
+
+ private:
+ Length _width, _height;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/Pair.hpp b/dviware/dvisvgm/src/Pair.hpp
new file mode 100644
index 0000000000..5804a2b467
--- /dev/null
+++ b/dviware/dvisvgm/src/Pair.hpp
@@ -0,0 +1,81 @@
+/*************************************************************************
+** Pair.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef PAIR_HPP
+#define PAIR_HPP
+
+#include <cmath>
+#include <ostream>
+#include "macros.hpp"
+
+template <typename T>
+class Pair
+{
+ public:
+ explicit Pair (T x=0, T y=0) : _x(x), _y(y) {}
+ Pair operator += (const Pair &p) {_x += p._x; _y += p._y; return *this;}
+ Pair operator -= (const Pair &p) {_x -= p._x; _y -= p._y; return *this;}
+ Pair operator *= (T c) {_x *= c; _y *= c; return *this;}
+ Pair operator /= (T c) {_x /= c; _y /= c; return *this;}
+ Pair operator - () const {return Pair(-_x, -_y);}
+ Pair ortho () const {return Pair(-_y, _x);}
+ double length () const {return std::sqrt(_x*_x + _y*_y);}
+ bool operator == (const Pair &p) const {return _x == p._x && _y == p._y;}
+ bool operator != (const Pair &p) const {return _x != p._x || _y != p._y;}
+ T x () const {return _x;}
+ T y () const {return _y;}
+ void x (const T &xx) {_x = xx;}
+ void y (const T &yy) {_y = yy;}
+ std::ostream& write (std::ostream &os) const {return os << '(' << _x << ',' << _y << ')';}
+
+ private:
+ T _x, _y;
+};
+
+template <typename T>
+inline Pair<T> abs (const Pair<T> &p) {
+ return Pair<T>(std::abs(p.x()), std::abs(p.y()));
+}
+
+struct Pair32 : public Pair<int32_t> {
+ explicit Pair32 (int32_t x=0, int32_t y=0) : Pair<int32_t>(x, y) {}
+ explicit Pair32 (double x, double y) : Pair<int32_t>(lround(x), lround(y)) {}
+ Pair32 (const Pair<int32_t> &p) : Pair<int32_t>(p) {}
+};
+
+typedef Pair<double> DPair;
+
+template <typename T>
+IMPLEMENT_ARITHMETIC_OPERATOR(Pair<T>, +)
+
+template <typename T>
+IMPLEMENT_ARITHMETIC_OPERATOR(Pair<T>, -)
+
+template <typename T>
+IMPLEMENT_ARITHMETIC_OPERATOR2(Pair<T>, T, *)
+
+template <typename T>
+IMPLEMENT_ARITHMETIC_OPERATOR2(Pair<T>, T, /)
+
+template <typename T>
+IMPLEMENT_OUTPUT_OPERATOR(Pair<T>)
+
+IMPLEMENT_ARITHMETIC_OPERATOR2(Pair32, int32_t, *)
+#endif
diff --git a/dviware/dvisvgm/src/PapersizeSpecialHandler.cpp b/dviware/dvisvgm/src/PapersizeSpecialHandler.cpp
new file mode 100644
index 0000000000..0907afc8f0
--- /dev/null
+++ b/dviware/dvisvgm/src/PapersizeSpecialHandler.cpp
@@ -0,0 +1,98 @@
+/*************************************************************************
+** PapersizeSpecialHandler.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include "Message.hpp"
+#include "PapersizeSpecialHandler.hpp"
+#include "SpecialActions.hpp"
+
+using namespace std;
+
+void PapersizeSpecialHandler::preprocess (const string&, std::istream &is, SpecialActions &actions) {
+ string params;
+ is >> params;
+ Length w, h;
+ const size_t splitpos = params.find(',');
+ try {
+ if (splitpos == string::npos) {
+ w.set(params);
+ h.set(params);
+ }
+ else {
+ w.set(params.substr(0, splitpos));
+ h.set(params.substr(splitpos+1));
+ }
+ storePaperSize(actions.getCurrentPageNumber(), w, h);
+ }
+ catch (UnitException &e) { // ignore invalid length units for now
+ }
+}
+
+
+bool PapersizeSpecialHandler::process (const string&, std::istream&, SpecialActions&) {
+ return true;
+}
+
+
+/** Records a paper size for a given page number for later processing. This function doesn't
+ * assign them to the page. */
+void PapersizeSpecialHandler::storePaperSize (unsigned pageno, Length width, Length height) {
+ DoublePair whpair(width.bp(), height.bp());
+ if (_pageSizes.empty() || _pageSizes.back().second != whpair) {
+ if (!_pageSizes.empty() && _pageSizes.back().first == pageno)
+ _pageSizes.back().second = whpair;
+ else
+ _pageSizes.emplace_back(PageSize(pageno, whpair));
+ }
+}
+
+
+/** Applies the previously recorded size to a given page. */
+void PapersizeSpecialHandler::applyPaperSize (unsigned pageno, SpecialActions &actions) {
+ // find page n >= pageno that contains a papersize special
+ auto lb_it = lower_bound(_pageSizes.begin(), _pageSizes.end(), PageSize(pageno, DoublePair()),
+ [](const PageSize &ps1, const PageSize &ps2) {
+ // order PageSize objects by page number
+ return ps1.first < ps2.first;
+ });
+ auto it = _pageSizes.end();
+ if (lb_it != _pageSizes.end() && lb_it->first == pageno)
+ it = lb_it; // if current page contains a papersize special, use it
+ else if (lb_it != _pageSizes.begin()) // no papersize special on current page?
+ it = lb_it-1; // => use the one on the nearest preceding page
+ if (it == _pageSizes.end())
+ Message::wstream(true) << "no valid papersize special found\n";
+ else {
+ DoublePair size = it->second;
+ const double border = -72; // DVI standard: coordinates of upper left paper corner are (-72bp, -72bp)
+ actions.bbox() = BoundingBox(border, border, size.first+border, size.second+border);
+ }
+}
+
+
+void PapersizeSpecialHandler::dviEndPage (unsigned pageno, SpecialActions &actions) {
+ if (actions.getBBoxFormatString() == "papersize")
+ applyPaperSize(pageno, actions);
+}
+
+
+vector<const char*> PapersizeSpecialHandler::prefixes() const {
+ vector<const char*> pfx {"papersize="};
+ return pfx;
+}
diff --git a/dviware/dvisvgm/src/PapersizeSpecialHandler.hpp b/dviware/dvisvgm/src/PapersizeSpecialHandler.hpp
new file mode 100644
index 0000000000..c7f8f26d60
--- /dev/null
+++ b/dviware/dvisvgm/src/PapersizeSpecialHandler.hpp
@@ -0,0 +1,50 @@
+/*************************************************************************
+** PapersizeSpecialHandler.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef PAPERSIZESPECIALHANDLER_HPP
+#define PAPERSIZESPECIALHANDLER_HPP
+
+#include <utility>
+#include <vector>
+#include "Length.hpp"
+#include "SpecialHandler.hpp"
+
+class PapersizeSpecialHandler : public SpecialHandler {
+ using DoublePair = std::pair<double,double>; // (width, height)
+ using PageSize = std::pair<unsigned,DoublePair>; // page number -> (width, height)
+
+ public:
+ void preprocess (const std::string &prefix, std::istream &is, SpecialActions &actions) override;
+ bool process (const std::string &prefix, std::istream &is, SpecialActions &actions) override;
+ const char* info () const override {return "special to set the page size";}
+ const char* name () const override {return "papersize";}
+ std::vector<const char*> prefixes() const override;
+ void storePaperSize (unsigned pageno, Length width, Length height);
+ void reset () {_pageSizes.clear();}
+
+ protected:
+ void dviEndPage (unsigned pageno, SpecialActions &actions) override;
+ void applyPaperSize (unsigned pageno, SpecialActions &actions);
+
+ private:
+ std::vector<PageSize> _pageSizes;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/PathClipper.cpp b/dviware/dvisvgm/src/PathClipper.cpp
new file mode 100644
index 0000000000..1663e7ae36
--- /dev/null
+++ b/dviware/dvisvgm/src/PathClipper.cpp
@@ -0,0 +1,331 @@
+/*************************************************************************
+** PathClipper.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <cmath>
+#include "Bezier.hpp"
+#include "PathClipper.hpp"
+
+using namespace std;
+using namespace ClipperLib;
+
+using Polygon = ClipperLib::Path;
+using Polygons = ClipperLib::Paths;
+using CurvedPath = PathClipper::CurvedPath;
+
+const int SCALE_FACTOR = 1000;
+
+inline cInt to_cInt (double x) {
+ return static_cast<cInt>(lround(x*SCALE_FACTOR));
+}
+
+
+inline double to_double (cInt x) {
+ return static_cast<double>(x)/SCALE_FACTOR;
+}
+
+
+inline DPair to_DPair (const IntPoint &p) {
+ return DPair(to_double(p.X), to_double(p.Y));
+}
+
+
+/** In order to flatten a curved path, all path segements are processed sequentially.
+ * Depending on the type of the segment, one of the methods provided by this class
+ * is called. */
+class FlattenActions : public CurvedPath::Actions {
+ public:
+ FlattenActions (vector<Bezier> &curves, Polygons &polygons, int &numLines)
+ : _polygons(polygons), _curves(curves), _numLines(numLines) {}
+
+ void moveto (const CurvedPath::Point &p) override {
+ if (p == _currentPoint && !_currentPoly.empty())
+ return;
+ closepath();
+ _currentPoly.emplace_back(IntPoint(to_cInt(p.x()), to_cInt(p.y()), 0));
+ _currentPoint = _startPoint = p;
+ }
+
+ void lineto (const CurvedPath::Point &p) override {
+ if (p == _currentPoint && !_currentPoly.empty())
+ return;
+ if (_currentPoly.empty()) // this shouldn't happen but in case it does...
+ _currentPoly.emplace_back(IntPoint(0, 0, 0)); // ...add a start point first
+ _numLines--;
+ _currentPoly.back().Z.label2 = _numLines;
+ _currentPoly.emplace_back(IntPoint(to_cInt(p.x()), to_cInt(p.y()), ZType(_numLines, 0)));
+ _currentPoint = p;
+ }
+
+ void conicto (const CurvedPath::Point &p1, const CurvedPath::Point &p2) override {
+ Bezier bezier(_currentPoint, p1, p2);
+ addCurvePoints(bezier);
+ }
+
+ void cubicto (const CurvedPath::Point &p1, const CurvedPath::Point &p2, const CurvedPath::Point &p3) override {
+ Bezier bezier(_currentPoint, p1, p2, p3);
+ addCurvePoints(bezier);
+ }
+
+ void closepath () override {
+ if (_currentPoly.empty())
+ return;
+ _numLines--;
+ _currentPoly.back().Z.label2 = ZLabel(_numLines, 0);
+ _currentPoly.front().Z.label1 = ZLabel(_numLines, 0);
+ _polygons.push_back(_currentPoly);
+ _currentPoly.clear();
+ }
+
+ void finished () override {
+ closepath();
+ }
+
+ protected:
+ void addCurvePoints (const Bezier &bezier) {
+ if (_currentPoly.empty()) // this shouldn't happen but in case it does, ...
+ _currentPoly.emplace_back(IntPoint(0, 0, 0)); // ...add a start point first
+ vector<DPair> points; // points of flattened curve
+ vector<double> t; // corresponding 'time' parameters
+ bezier.approximate(0.01, points, &t);
+ if (points.size() < 2)
+ return;
+ _curves.push_back(bezier);
+ for (size_t i=1; i < points.size(); i++) {
+ const DPair &p = points[i];
+ if (p == _currentPoint)
+ continue;
+ _currentPoly.back().Z.label2 = ZLabel(_curves.size(), t[i-1]);
+ ZLabel label(_curves.size(), t[i]);
+ _currentPoly.emplace_back(IntPoint(to_cInt(p.x()), to_cInt(p.y()), ZType(label, label)));
+ _currentPoint = p;
+ }
+ }
+
+ private:
+ CurvedPath::Point _startPoint, _currentPoint;
+ Polygon _currentPoly; ///< polygon being created
+ Polygons &_polygons; ///< all polygons created
+ vector<Bezier> &_curves;
+ int &_numLines;
+};
+
+
+/** Removes adjacent polygon vertices that equal their predecessor. */
+static void remove_redundant_vertices (Polygon &polygon) {
+ auto it1=polygon.begin();
+ while (it1 != polygon.end()) {
+ auto it2 = it1+1;
+ if (it2 == polygon.end())
+ it2 = polygon.begin();
+ if (it1 == it2)
+ return;
+
+ if (*it1 != *it2)
+ ++it1;
+ else {
+ it1->Z.label2 = it2->Z.label2;
+ polygon.erase(it2);
+ }
+ }
+}
+
+
+/** Approximates a curved path by a set of polygons and stores information
+ * to reconstruct the curved segments later. The z component of each
+ * polygon vertex holds two integers representing information about the two
+ * adjacent edges the vertex belongs to. This is required to identify the
+ * affected edges and thus the former (curve/line) segment of the path during
+ * the intersection process.
+ * @param[in] curvedPath curved path to be flattened
+ * @param[out] polygons the flattened path (set of polygons) */
+void PathClipper::flatten (const CurvedPath &curvedPath, Polygons &polygons) {
+ FlattenActions flattenActions(_curves, polygons, _numLines);
+ curvedPath.iterate(flattenActions, false);
+ for (Polygon &poly : polygons)
+ remove_redundant_vertices(poly);
+}
+
+
+/** Returns the ID of the path segment the polygon edge defined by its start
+ * and end point belongs to. The z component of a polygon vertex holds a pair
+ * of labels that allows to identify the original path segments the point belongs to.
+ * Since always two adjacent segments share a point, each point gets two values assigned.
+ * Negative numbers denote line segments, positive ones Bézier curves.
+ * There are only these two segment types, so we don't need further flags in
+ * order to distinguish them. By comparing the labels of two adjacent polygon
+ * vertexes it's possible to identify the original path segment the corresponding
+ * edge belongs to.
+ * @param[in] p1 first of two adjacent vertices
+ * @param[in] p2 second of two adjacent vertices
+ * @param[out] t1 time parameter of p1
+ * @param[out] t2 time parameter of p2
+ * @return id of edge between p1 and p2, or 0 if it's not possible to identify the segment */
+static int32_t segment_id (const IntPoint &p1, const IntPoint &p2, double &t1, double &t2) {
+ const ZType &z1=p1.Z, &z2=p2.Z;
+ if (z1 == z2 && z1.minLabel().id < 0) return z1.minLabel().id;
+ if (z1.label1 == z2.label2) {t1=z1.label1.t; t2=z2.label2.t; return z1.label1.id;}
+ if (z1.label2 == z2.label1) {t1=z1.label2.t; t2=z2.label1.t; return z1.label2.id;}
+ if (z1.label1 == z2.label1) {t1=z1.label1.t; t2=z2.label1.t; return z1.label1.id;}
+ if (z1.label2 == z2.label2) {t1=z1.label2.t; t2=z2.label2.t; return z1.label2.id;}
+ // if we get here, it's not possible to identify the segment
+ // => the edge is going to be handled as line segment
+ return 0;
+}
+
+
+/** This function expects 3 colinear points p1, p2, and q, where q lies between p1 and p2,
+ * i.e. q divides the line \f$ \overline{p_1 p_2} \f$ somewhere. The function returns
+ * the corresponding division ratio. */
+static double division_ratio (const IntPoint &p1, const IntPoint &p2, const IntPoint &q) {
+ if (p1 == p2 || q == p1)
+ return 0;
+ if (q == p2)
+ return 1;
+ if (p1.X == p2.X)
+ return double(q.Y-p1.Y)/(p2.Y-p1.Y);
+ return double(q.X-p1.X)/(p2.X-p1.X);
+}
+
+
+/** Returns the label of point q that lies on the line between points p1 and p2. */
+inline ZLabel division_label (const IntPoint &p1, const IntPoint &p2, const IntPoint &q) {
+ double t1, t2;
+ double s=0;
+ int32_t id = segment_id(p1, p2, t1, t2);
+ if (id > 0)
+ s = t1+(t2-t1)*division_ratio(p1, p2, q);
+ return ZLabel(id, s);
+}
+
+
+/** This method is called if the clipper library finds an intersection between two polygon edges.
+ * It populates the z coordinate of the intersection point with the idexes of the two edges.
+ * @param[in] e1bot first endpoint of edge 1
+ * @param[in] e1top second endpoint of edge 1
+ * @param[in] e2bot first endpoint of edge 2
+ * @param[in] e2top second endpoint of edge 2
+ * @param[in] ip intersection point of edge 1 and 2 */
+void PathClipper::callback (IntPoint &e1bot, IntPoint &e1top, IntPoint &e2bot, IntPoint &e2top, IntPoint &ip) {
+ ZLabel label1 = division_label(e1bot, e1top, ip);
+ ZLabel label2 = division_label(e2bot, e2top, ip);
+ ip.Z = ZType(label1, label2);
+}
+
+
+/** Iterates along the polygon edges until the endpoint of the current
+ * path segment is found and returns its vector index afterwards.
+ * @param[in] polygon the polygon to be processed
+ * @param[in] start index of the vertex where the iteration starts
+ * @param[out] label if not 0, retrieves the label of the endpoint
+ * @param[in] startLabel if true, the found endpoint is treated as start point and
+ * parameter 'label' gets the corresponding value */
+static size_t find_segment_endpoint (const Polygon &polygon, size_t start, ZLabel *label=nullptr, bool startLabel=false) {
+ if (polygon.empty())
+ return 0;
+
+ const size_t num_points = polygon.size();
+ int i = start%num_points;
+ double t1=0, t2=0; // time parameters of start and endpoint of current edge
+ int32_t id1 = segment_id(polygon[i], polygon[(i+1)%num_points], t1, t2);
+ int32_t id2 = id1;
+ double t = t2; // time parameter of resulting endpoint
+ for (size_t j=1; id1 == id2 && j < num_points; j++) {
+ t = t2;
+ i = (i+1)%num_points;
+ if (id1 == 0)
+ break;
+ id2 = segment_id(polygon[i], polygon[(i+1)%num_points], t1, t2);
+ }
+ if (label) {
+ *label = ZLabel(id1, id1 < 0 ? 0 : t);
+ if (startLabel && id1 != 0)
+ *label = polygon[i].Z.otherLabel(*label);
+ }
+ return i;
+}
+
+
+/** Reconstructs a curved path from the set of polygons.
+ * @param[in] polygons set of polygons to reconstruct
+ * @param[out] path the reconstructed curved path */
+void PathClipper::reconstruct (const Polygons &polygons, CurvedPath &path) {
+ for (const Polygon &polygon : polygons)
+ reconstruct(polygon, path);
+}
+
+
+/** Reconstructs a curved path from a single polygon.
+ * @param[in] polygon polygon to reconstruct
+ * @param[out] path the reconstructed curved path */
+void PathClipper::reconstruct (const Polygon &polygon, CurvedPath &path) {
+ size_t num_points = polygon.size();
+ if (num_points < 2)
+ return;
+
+ ZLabel label1, label2; // labels of the current segment's start and endpoint
+ int index1 = find_segment_endpoint(polygon, 0, &label1, true);
+ int index2 = find_segment_endpoint(polygon, index1, &label2);
+ int diff = (num_points+index2-index1)%num_points;
+ path.moveto(to_DPair(polygon[index1]));
+ for (size_t count = diff; count <= num_points; count += diff) {
+ if (diff == 1 || label1.id <= 0) // line segment?
+ path.lineto(to_DPair(polygon[index2]));
+ else { // Bézier curve segment
+ Bezier bezier(_curves[label1.id-1], label1.t, label2.t);
+ if (label1.t > label2.t)
+ bezier.reverse();
+ path.cubicto(bezier.point(1), bezier.point(2), bezier.point(3));
+ }
+ if (label1.id == 0)
+ find_segment_endpoint(polygon, index2, &label1, true);
+ else
+ label1 = polygon[index2].Z.otherLabel(label2);
+ index1 = index2;
+ index2 = find_segment_endpoint(polygon, index1, &label2);
+ diff = (num_points+index2-index1)%num_points;
+ }
+ path.closepath();
+}
+
+
+inline PolyFillType polyFillType (CurvedPath::WindingRule wr) {
+ return (wr == CurvedPath::WindingRule::NON_ZERO) ? pftNonZero : pftEvenOdd;
+}
+
+
+/** Computes the intersection of to curved paths.
+ * @param[in] p1 first curved path
+ * @param[in] p2 second curved path
+ * @param[out] result intersection of p1 and p2 */
+void PathClipper::intersect (const CurvedPath &p1, const CurvedPath &p2, CurvedPath &result) {
+ if (p1.size() < 2 || p2.size() < 2)
+ return;
+ Clipper clipper;
+ Polygons polygons;
+ flatten(p1, polygons);
+ clipper.AddPaths(polygons, ptSubject, true);
+ polygons.clear();
+ flatten(p2, polygons);
+ clipper.AddPaths(polygons, ptClip, true);
+ clipper.ZFillFunction(callback);
+ Polygons flattenedPath;
+ clipper.Execute(ctIntersection, flattenedPath, polyFillType(p1.windingRule()), polyFillType(p2.windingRule()));
+ reconstruct(flattenedPath, result);
+}
diff --git a/dviware/dvisvgm/src/PathClipper.hpp b/dviware/dvisvgm/src/PathClipper.hpp
new file mode 100644
index 0000000000..09c904bfd4
--- /dev/null
+++ b/dviware/dvisvgm/src/PathClipper.hpp
@@ -0,0 +1,55 @@
+/*************************************************************************
+** PathClipper.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef PATHCLIPPER_HPP
+#define PATHCLIPPER_HPP
+
+#include <clipper.hpp>
+#include <string>
+#include <vector>
+#include "Bezier.hpp"
+#include "GraphicsPath.hpp"
+#include "MessageException.hpp"
+
+
+using ClipperLib::IntPoint;
+
+class PathClipper
+{
+ public:
+ using CurvedPath = GraphicsPath<double>;
+
+ public:
+ PathClipper () : _numLines(0) {}
+ void intersect (const CurvedPath &p1, const CurvedPath &p2, CurvedPath &result);
+
+ protected:
+ void flatten (const CurvedPath &gp, ClipperLib::Paths &polygons);
+// void divide (IntPoint &p1, IntPoint &p2, IntPoint &ip);
+ void reconstruct (const ClipperLib::Path &polygon, CurvedPath &path);
+ void reconstruct (const ClipperLib::Paths &polygons, CurvedPath &path);
+ static void callback (IntPoint &e1bot, IntPoint &e1top, IntPoint &e2bot, IntPoint &e2top, IntPoint &ip);
+
+ private:
+ std::vector<Bezier> _curves;
+ int _numLines; ///< negative number of straight line segments in path been processed
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/PdfSpecialHandler.cpp b/dviware/dvisvgm/src/PdfSpecialHandler.cpp
new file mode 100644
index 0000000000..70174b0fe1
--- /dev/null
+++ b/dviware/dvisvgm/src/PdfSpecialHandler.cpp
@@ -0,0 +1,289 @@
+/*************************************************************************
+** PdfSpecialHandler.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <array>
+#include <cstring>
+#include <unordered_map>
+#include <utility>
+#include "Color.hpp"
+#include "HyperlinkManager.hpp"
+#include "InputReader.hpp"
+#include "MapLine.hpp"
+#include "PdfSpecialHandler.hpp"
+#include "FontMap.hpp"
+#include "Message.hpp"
+#include "PapersizeSpecialHandler.hpp"
+#include "PDFParser.hpp"
+#include "SpecialActions.hpp"
+#include "SpecialManager.hpp"
+
+using namespace std;
+
+using CmdHandler = void (PdfSpecialHandler::*)(StreamInputReader&, SpecialActions&);
+
+
+void PdfSpecialHandler::preprocess (const string&, istream &is, SpecialActions &actions) {
+ StreamInputReader ir(is);
+ ir.skipSpace();
+ const string cmdstr = ir.getWord();
+ static unordered_map<string, CmdHandler> commands = {
+ {"bann", &PdfSpecialHandler::preprocessBeginAnn},
+ {"bannot", &PdfSpecialHandler::preprocessBeginAnn},
+ {"beginann", &PdfSpecialHandler::preprocessBeginAnn},
+ {"dest", &PdfSpecialHandler::preprocessDest},
+ {"pagesize", &PdfSpecialHandler::preprocessPagesize}
+ };
+ auto it = commands.find(cmdstr);
+ if (it != commands.end())
+ (this->*it->second)(ir, actions);
+}
+
+
+bool PdfSpecialHandler::process (const string&, istream &is, SpecialActions &actions) {
+ _active = true;
+ StreamInputReader ir(is);
+ ir.skipSpace();
+ const string cmdstr = ir.getWord();
+ ir.skipSpace();
+ // dvipdfm(x) specials currently supported
+ static unordered_map<string, CmdHandler> commands = {
+ {"bann", &PdfSpecialHandler::processBeginAnn},
+ {"bannot", &PdfSpecialHandler::processBeginAnn},
+ {"beginann", &PdfSpecialHandler::processBeginAnn},
+ {"eann", &PdfSpecialHandler::processEndAnn},
+ {"eannot", &PdfSpecialHandler::processEndAnn},
+ {"endann", &PdfSpecialHandler::processEndAnn},
+ {"dest", &PdfSpecialHandler::processDest},
+ {"mapfile", &PdfSpecialHandler::processMapfile},
+ {"mapline", &PdfSpecialHandler::processMapline}
+ };
+ auto it = commands.find(cmdstr);
+ if (it != commands.end())
+ (this->*it->second)(ir, actions);
+ return true;
+}
+
+
+static char prepare_mode (InputReader &ir) {
+ // read mode selector ('+', '-', or '=')
+ char modechar = '+'; // default mode (append if new, do not replace existing mapping)
+ if (strchr("=+-", ir.peek())) // leading modifier given?
+ modechar = static_cast<char>(ir.get());
+ return modechar;
+}
+
+
+/** Sets the page size. This command is similar to the papersize special.
+ * Syntax pdf:pagesize (<dimname> <dimlenhth>)+ */
+void PdfSpecialHandler::preprocessPagesize (StreamInputReader &ir, SpecialActions &actions) {
+ // add page sizes to collection of paper sizes in order to handle them equally
+ SpecialHandler *handler = SpecialManager::instance().findHandlerByName("papersize");
+ if (auto papersizeHandler = dynamic_cast<PapersizeSpecialHandler*>(handler)) {
+ try {
+ Length width, height;
+ // parse parameter sequence of the form (name length)+
+ while (!ir.eof()) {
+ string dimname = ir.getWord();
+ string lenstr = ir.getString(" \t");
+ // only consider width and height settings
+ if (dimname == "width" && !lenstr.empty())
+ width.set(lenstr);
+ else if (dimname == "height" && !lenstr.empty())
+ height.set(lenstr);
+ }
+ papersizeHandler->storePaperSize(actions.getCurrentPageNumber(), width, height);
+ }
+ catch (UnitException &e) { // ignore invalid length units for now
+ }
+ }
+}
+
+
+void PdfSpecialHandler::processMapfile (StreamInputReader &ir, SpecialActions&) {
+ char modechar = prepare_mode(ir);
+ string fname = ir.getString();
+ if (!FontMap::instance().read(fname, modechar))
+ Message::wstream(true) << "can't open map file " << fname << '\n';
+}
+
+
+void PdfSpecialHandler::processMapline (StreamInputReader &ir, SpecialActions&) {
+ char modechar = prepare_mode(ir);
+ try {
+ MapLine mapline(ir.getStream());
+ FontMap::instance().apply(mapline, modechar);
+ }
+ catch (const MapLineException &ex) {
+ Message::wstream(true) << "pdf:mapline: " << ex.what() << '\n';
+ }
+}
+
+
+/** Defines a named destination, e.g. a link target.
+ * Syntax: dest PDFString PDFDest */
+void PdfSpecialHandler::preprocessDest (StreamInputReader &ir, SpecialActions &actions) {
+ PDFParser parser;
+ vector<PDFObject> objects = parser.parse(ir, [&](const string &opname, vector<PDFObject> &objects) {
+ if (!opname.empty() && opname[0] == '@') {
+ if (opname == "@thispage")
+ objects.emplace_back(static_cast<int>(actions.getCurrentPageNumber()));
+ else if (opname == "@xpos")
+ objects.emplace_back(actions.getX());
+ else if (opname == "@ypos")
+ objects.emplace_back(actions.getY());
+ }
+ });
+ if (objects.size() < 2)
+ return;
+ auto name = objects[0].get<string>();
+ auto dest = objects[1].get<PDFArray>();
+ // get target info from array [pageno /XYZ xpos ypos zpos]
+ if (name && dest && dest->size() >= 4 && dest->at(0).get<int>()) {
+ int pageno = *dest->at(0).get<int>();
+ HyperlinkManager::instance().addNameAchor(*name, pageno);
+ }
+}
+
+
+/** Extracts the URI from a PDF annotation dictionary. "GoTo" targets (named anchors)
+ * are prefixed with a '#'.
+ * @param[in] annotDict annotation dictionary containing the target URI
+ * @return the URI if one was found, "" otherwise */
+static string get_uri (const PDFDict &annotDict) {
+ // At the moment, we only support link annotations
+ const PDFObject *type = annotDict.get("Type");
+ if (type && string(*type) == "Annot") {
+ const PDFObject *subtype = annotDict.get("Subtype");
+ if (subtype && string(*subtype) == "Link") {
+ const PDFObject *dict = annotDict.get("A");
+ if (const PDFDict *actionDict = dict->get<PDFDict>()) {
+ if (const PDFObject *s = actionDict->get("S")) {
+ if (string(*s) == "GoTo") {
+ if (const PDFObject *dest = actionDict->get("D"))
+ return "#" + string(*dest);
+ }
+ else if (string(*s) == "URI") {
+ if (const PDFObject *uri = actionDict->get("URI"))
+ return string(*uri);
+ }
+ }
+ }
+ }
+ }
+ return "";
+}
+
+
+void PdfSpecialHandler::preprocessBeginAnn (StreamInputReader &ir, SpecialActions&) {
+ PDFParser parser;
+ vector<PDFObject> pdfobjs = parser.parse(ir);
+ if (pdfobjs.empty() || !pdfobjs[0].get<PDFDict>())
+ return;
+ const PDFDict &annotDict = *pdfobjs[0].get<PDFDict>();
+ string uri = get_uri(annotDict);
+ if (!uri.empty())
+ HyperlinkManager::instance().addHrefAnchor(uri);
+}
+
+
+/** Converts a PDFObject to a Color, where a single number denotes a gray value.
+ * Number arrays are treated as gray, RGB, or CMYK colors depending on the
+ * number of components. */
+static Color to_color (const PDFObject &obj) {
+ Color color;
+ if (obj.get<int>() || obj.get<double>())
+ color.setGray(double(obj));
+ else if (auto colorArray = obj.get<PDFArray>()) {
+ size_t size = min(size_t(4), colorArray->size());
+ valarray<double> colorComps(size);
+ for (size_t i=0; i < size; i++)
+ colorComps[i] = double(colorArray->at(i));
+ switch (size) {
+ case 1: color.setGray(colorComps); break;
+ case 3: color.setRGB(colorComps); break;
+ case 4: color.setCMYK(colorComps); break;
+ }
+ }
+ return color;
+}
+
+
+/** Begins a breakable annotation, e.g. a hyperlink.
+ * Syntax: beginann PDFDict */
+void PdfSpecialHandler::processBeginAnn (StreamInputReader &ir, SpecialActions &actions) {
+ PDFParser parser;
+ vector<PDFObject> pdfobjs = parser.parse(ir);
+ if (pdfobjs.empty() || !pdfobjs[0].get<PDFDict>())
+ return;
+ const PDFDict &annotDict = *pdfobjs[0].get<PDFDict>();
+ string uri = get_uri(annotDict);
+ if (uri.empty())
+ return;
+
+ // check presence of entry /Border [hr vr bw] defining the horizontal/vertical
+ // corner radius and the border width
+ auto it = annotDict.find("Border");
+ if (it != annotDict.end() && it->second.get<PDFArray>() && it->second.get<PDFArray>()->size() > 2)
+ HyperlinkManager::instance().setLineWidth(double(it->second.get<PDFArray>()->at(2)));
+
+ // check presence of entry /C defining the border color
+ it = annotDict.find("C");
+ if (it != annotDict.end())
+ HyperlinkManager::setDefaultLinkColor(to_color(it->second));
+ HyperlinkManager::instance().createLink(uri, actions);
+}
+
+
+/** Terminates the preceding breakable annotation.
+ * Syntax: endann */
+void PdfSpecialHandler::processEndAnn (StreamInputReader&, SpecialActions &actions) {
+ HyperlinkManager::instance().closeAnchor(actions);
+}
+
+
+void PdfSpecialHandler::processDest (StreamInputReader &ir, SpecialActions &actions) {
+ PDFParser parser;
+ vector<PDFObject> objects = parser.parse(ir);
+ if (!objects.empty()) {
+ if (auto name = objects[0].get<string>())
+ HyperlinkManager::instance().setActiveNameAnchor(*name, actions);
+ }
+}
+
+
+/** This method is called every time the DVI position changes. */
+void PdfSpecialHandler::dviMovedTo (double x, double y, SpecialActions &actions) {
+ if (_active)
+ HyperlinkManager::instance().checkNewLine(actions);
+}
+
+
+void PdfSpecialHandler::dviEndPage (unsigned pageno, SpecialActions &actions) {
+ if (_active) {
+ HyperlinkManager::instance().createViews(pageno, actions);
+ _active = false;
+ }
+}
+
+
+vector<const char*> PdfSpecialHandler::prefixes() const {
+ vector<const char*> pfx {"pdf:"};
+ return pfx;
+}
diff --git a/dviware/dvisvgm/src/PdfSpecialHandler.hpp b/dviware/dvisvgm/src/PdfSpecialHandler.hpp
new file mode 100644
index 0000000000..389ee96b86
--- /dev/null
+++ b/dviware/dvisvgm/src/PdfSpecialHandler.hpp
@@ -0,0 +1,54 @@
+/*************************************************************************
+** PdfSpecialHandler.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef PDFSPECIALHANDLER_HPP
+#define PDFSPECIALHANDLER_HPP
+
+#include "SpecialHandler.hpp"
+
+class StreamInputReader;
+
+class PdfSpecialHandler : public SpecialHandler {
+ public:
+ const char* info () const override {return "PDF hyperlink, font map, and pagesize specials";}
+ const char* name () const override {return "pdf";}
+ std::vector<const char*> prefixes() const override;
+ void preprocess (const std::string &prefix, std::istream &is, SpecialActions &actions) override;
+ bool process (const std::string &prefix, std::istream &is, SpecialActions &actions) override;
+
+ protected:
+ // handlers for corresponding PDF specials
+ void preprocessBeginAnn (StreamInputReader &ir, SpecialActions &actions);
+ void preprocessDest (StreamInputReader &ir, SpecialActions &actions);
+ void preprocessPagesize (StreamInputReader &ir, SpecialActions &actions);
+ void processBeginAnn (StreamInputReader &ir, SpecialActions &actions);
+ void processEndAnn (StreamInputReader &ir, SpecialActions &actions);
+ void processDest (StreamInputReader &ir, SpecialActions &actions);
+ void processMapfile (StreamInputReader &ir, SpecialActions &actions);
+ void processMapline (StreamInputReader &ir, SpecialActions &actions);
+
+ void dviMovedTo (double x, double y, SpecialActions &actions) override;
+ void dviEndPage (unsigned pageno, SpecialActions &actions) override;
+
+ private:
+ bool _active=false;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/PreScanDVIReader.cpp b/dviware/dvisvgm/src/PreScanDVIReader.cpp
new file mode 100644
index 0000000000..415eadbb73
--- /dev/null
+++ b/dviware/dvisvgm/src/PreScanDVIReader.cpp
@@ -0,0 +1,44 @@
+/*************************************************************************
+** PreScanDVIReader.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include "DVIActions.hpp"
+#include "PreScanDVIReader.hpp"
+
+using namespace std;
+
+
+PreScanDVIReader::PreScanDVIReader (std::istream &is, DVIActions *actions)
+ : BasicDVIReader(is), _actions(actions), _currentPageNumber(0)
+{
+}
+
+
+void PreScanDVIReader::cmdBop (int) {
+ _currentPageNumber++;
+ BasicDVIReader::cmdBop(0);
+}
+
+
+void PreScanDVIReader::cmdXXX (int len) {
+ uint32_t numBytes = readUnsigned(len);
+ string s = readString(numBytes);
+ if (_actions)
+ _actions->special(s, 0, true); // pre-process special
+}
diff --git a/dviware/dvisvgm/src/PreScanDVIReader.hpp b/dviware/dvisvgm/src/PreScanDVIReader.hpp
new file mode 100644
index 0000000000..614beac66e
--- /dev/null
+++ b/dviware/dvisvgm/src/PreScanDVIReader.hpp
@@ -0,0 +1,43 @@
+/*************************************************************************
+** PreScanDVIReader.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef PRESCANDVIREADER_HPP
+#define PRESCANDVIREADER_HPP
+
+#include "BasicDVIReader.hpp"
+
+struct DVIActions;
+
+class PreScanDVIReader : public BasicDVIReader
+{
+ public:
+ PreScanDVIReader (std::istream &is, DVIActions *actions);
+ unsigned currentPageNumber () const override {return _currentPageNumber;}
+
+ protected:
+ void cmdBop (int) override;
+ void cmdXXX (int len) override;
+
+ private:
+ DVIActions *_actions;
+ unsigned _currentPageNumber;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/Process.cpp b/dviware/dvisvgm/src/Process.cpp
new file mode 100644
index 0000000000..ff73cb5336
--- /dev/null
+++ b/dviware/dvisvgm/src/Process.cpp
@@ -0,0 +1,325 @@
+/*************************************************************************
+** Process.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifdef _WIN32
+ #include "windows.hpp"
+#else
+ #include <csignal>
+ #include <fcntl.h>
+ #include <sys/wait.h>
+ #include <unistd.h>
+#endif
+
+#include <cstdlib>
+#include "FileSystem.hpp"
+#include "Process.hpp"
+#include "SignalHandler.hpp"
+
+using namespace std;
+
+
+/** Helper class that encapsulates the system-specific parts of
+ * running a subprocess and retrieving its terminal output. */
+class Subprocess {
+ public:
+ enum class State {RUNNING, FINISHED, FAILED};
+
+ public:
+ Subprocess ();
+ Subprocess (const Subprocess&) =delete;
+ Subprocess (Subprocess&&) =delete;
+ ~Subprocess ();
+ bool run (const string &cmd, string params);
+ bool readFromPipe (string &out);
+ State state ();
+
+ private:
+#ifdef _WIN32
+ HANDLE _pipeReadHandle; ///< handle of read end of pipe
+ HANDLE _childProcHandle; ///< handle of child process
+#else
+ int _readfd; ///< file descriptor of read end of pipe
+ pid_t _pid; ///< PID of the subprocess
+#endif
+};
+
+
+Process::Process (string cmd, string paramstr)
+ : _cmd(std::move(cmd)), _paramstr(std::move(paramstr))
+{
+}
+
+
+/** Runs the process and waits until it's finished.
+ * @param[out] out takes the output written to stdout by the executed subprocess
+ * @return true if process terminated properly
+ * @throw SignalException if CTRL-C was pressed during execution */
+bool Process::run (string *out) {
+ Subprocess subprocess;
+ if (!subprocess.run(_cmd, _paramstr))
+ return false;
+ for (;;) {
+ if (out) {
+ out->clear();
+ subprocess.readFromPipe(*out);
+ }
+ Subprocess::State state = subprocess.state();
+ if (state != Subprocess::State::RUNNING)
+ return state == Subprocess::State::FINISHED;
+ SignalHandler::instance().check();
+ }
+}
+
+
+/** Runs the process in the given working directory and waits until it's finished.
+ * @param[in] dir working directory
+ * @param[out] out takes the output written to stdout by the executed process
+ * @return true if process terminated properly
+ * @throw SignalException if CTRL-C was pressed during execution */
+bool Process::run (const string &dir, string *out) {
+ bool ret = false;
+ string cwd = FileSystem::getcwd();
+ if (FileSystem::chdir(dir)) {
+ ret = run(out);
+ ret &= FileSystem::chdir(cwd);
+ }
+ return ret;
+}
+
+// system-specific stuff
+
+#ifdef _WIN32
+
+static inline void close_and_zero_handle (HANDLE &handle) {
+ CloseHandle(handle);
+ handle = NULL;
+}
+
+
+Subprocess::Subprocess() : _pipeReadHandle(NULL), _childProcHandle(NULL) {
+}
+
+
+Subprocess::~Subprocess () {
+ if (_pipeReadHandle != NULL)
+ CloseHandle(_pipeReadHandle);
+ if (_childProcHandle != NULL) {
+ TerminateProcess(_childProcHandle, 1);
+ CloseHandle(_childProcHandle);
+ }
+}
+
+
+/** Retrieves output generated by child process.
+ * @param[out] out read output is appended to this string
+ * @returns false on errors */
+bool Subprocess::readFromPipe (string &out) {
+ if (!_pipeReadHandle)
+ return false;
+
+ bool success=false;
+ bool processExited=false;
+ DWORD len=0;
+ while (PeekNamedPipe(_pipeReadHandle, NULL, 0, NULL, &len, NULL)) { // prevent blocking
+ if (len == 0) {
+ if (processExited)
+ break;
+ // process still busy
+ processExited = (!_childProcHandle || WaitForSingleObject(_childProcHandle, 100) != WAIT_TIMEOUT);
+ }
+ else {
+ char buf[4096];
+ success = ReadFile(_pipeReadHandle, buf, sizeof(buf), &len, NULL);
+ if (!success || len == 0)
+ break;
+ out.append(buf, len);
+ }
+ }
+ return success;
+}
+
+
+/** Starts a child process.
+ * @param[in] cmd name of command to execute
+ * @param[in] paramstr parameters required by command
+ * @returns true if child process started properly */
+bool Subprocess::run (const string &cmd, string paramstr) {
+ SECURITY_ATTRIBUTES securityAttribs;
+ ZeroMemory(&securityAttribs, sizeof(SECURITY_ATTRIBUTES));
+ securityAttribs.nLength = sizeof(SECURITY_ATTRIBUTES);
+ securityAttribs.bInheritHandle = true;
+
+ HANDLE pipeWriteHandle; // write end of pipe
+ if (CreatePipe(&_pipeReadHandle, &pipeWriteHandle, &securityAttribs, 0) == ERROR_INVALID_HANDLE)
+ return false;
+
+ SetHandleInformation(_pipeReadHandle, HANDLE_FLAG_INHERIT, 0);
+ HANDLE nullFile = CreateFile("NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &securityAttribs, OPEN_EXISTING, 0, NULL);
+ bool success = false;
+ if (nullFile != INVALID_HANDLE_VALUE) {
+ STARTUPINFO startupInfo;
+ ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
+ startupInfo.cb = sizeof(STARTUPINFO);
+ startupInfo.dwFlags = STARTF_USESTDHANDLES;
+ startupInfo.hStdInput = nullFile;
+ startupInfo.hStdOutput = pipeWriteHandle;
+ startupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
+
+ PROCESS_INFORMATION processInfo;
+ ZeroMemory(&processInfo, sizeof(PROCESS_INFORMATION));
+
+ string cmdline = cmd + " " + paramstr;
+ // put subprocess in separate process group to prevent its termination in case of CTRL-C
+ success = CreateProcess(NULL, (LPSTR)cmdline.c_str(), NULL, NULL, true, CREATE_NEW_PROCESS_GROUP, NULL, NULL, &startupInfo, &processInfo);
+ if (success) {
+ _childProcHandle = processInfo.hProcess;
+ CloseHandle(processInfo.hThread);
+ }
+ CloseHandle(nullFile);
+ }
+ CloseHandle(pipeWriteHandle); // must be closed before reading from pipe to prevent blocking
+ if (!success)
+ close_and_zero_handle(_pipeReadHandle);
+ return success;
+}
+
+
+/** Returns the current state of the child process. */
+Subprocess::State Subprocess::state () {
+ DWORD status;
+ if (!GetExitCodeProcess(_childProcHandle, &status))
+ return State::FAILED;
+ if (status == STILL_ACTIVE)
+ return State::RUNNING;
+ close_and_zero_handle(_childProcHandle);
+ return status == 0 ? State::FINISHED : State::FAILED;
+}
+
+#else // !_WIN32
+
+Subprocess::Subprocess () : _readfd(-1), _pid(-1) {
+}
+
+
+Subprocess::~Subprocess () {
+ if (_readfd >= 0)
+ close(_readfd);
+ if (_pid > 0)
+ kill(_pid, SIGKILL);
+}
+
+
+/** Retrieves output generated by child process.
+ * @param[out] out read output is appended to this string
+ * @returns false on errors */
+bool Subprocess::readFromPipe (string &out) {
+ if (_readfd < 0 || _pid < 0)
+ return false;
+
+ char buf[1024];
+ ssize_t len;
+ while ((len = read(_readfd, buf, sizeof(buf))) > 0)
+ out.append(buf, len);
+ if (len < 0) {
+ close(_readfd);
+ _readfd = -1;
+ }
+ return len >= 0;
+}
+
+
+/** Extracts whitespace-separated parameters from a string.
+ * @param[in,out] paramstr the parameter string
+ * @param[out] params vector holding pointers to the extracted parameters */
+static void split_paramstr (string &paramstr, vector<const char*> &params) {
+ size_t left=0, right=0; // index of first and last character of current parameter
+ char quote=0; // current quote character, 0=none
+ const size_t len = paramstr.length();
+ while (left <= right && right < len) {
+ while (left < len && isspace(paramstr[left]))
+ ++left;
+ if (left < len && (paramstr[left] == '"' || paramstr[left] == '\''))
+ quote = paramstr[left++];
+ right = left;
+ while (right < len && (quote || !isspace(paramstr[right]))) {
+ if (quote && paramstr[right] == quote) {
+ quote=0;
+ break;
+ }
+ else
+ ++right;
+ }
+ if (right < len)
+ paramstr[right]=0;
+ if (left < len)
+ params.push_back(&paramstr[left]);
+ left = ++right;
+ }
+}
+
+
+/** Starts a child process.
+ * @param[in] cmd name of command to execute
+ * @param[in] paramstr parameters required by command
+ * @returns true if child process started properly */
+bool Subprocess::run (const string &cmd, string paramstr) {
+ int pipefd[2];
+ if (pipe(pipefd) < 0)
+ return false;
+
+ _pid = fork();
+ if (_pid < 0) {
+ close(pipefd[0]);
+ close(pipefd[1]);
+ return false;
+ }
+ if (_pid == 0) { // child process
+ dup2(pipefd[1], STDOUT_FILENO); // redirect stdout to the pipe
+ dup2(pipefd[1], STDERR_FILENO); // redirect stderr to the pipe
+ close(pipefd[0]);
+ close(pipefd[1]);
+
+ vector<const char*> params;
+ params.push_back(cmd.c_str());
+ split_paramstr(paramstr, params);
+ params.push_back(nullptr); // trailing null pointer marks end of parameter list
+ signal(SIGINT, SIG_IGN); // child process is supposed to ignore ctrl-c events
+ execvp(cmd.c_str(), const_cast<char* const*>(&params[0]));
+ exit(1);
+ }
+ _readfd = pipefd[0];
+ close(pipefd[1]); // close write end of pipe
+ return true;
+}
+
+
+/** Returns the current state of the child process. */
+Subprocess::State Subprocess::state () {
+ int status;
+ pid_t wpid = waitpid(_pid, &status, WNOHANG);
+ if (wpid == 0)
+ return State::RUNNING; // still running
+ _pid = -1;
+ if (wpid > 0 && WIFEXITED(status) && WEXITSTATUS(status) == 0)
+ return State::FINISHED;
+ return State::FAILED;
+}
+
+#endif // !_WIN32
diff --git a/dviware/dvisvgm/src/Process.hpp b/dviware/dvisvgm/src/Process.hpp
new file mode 100644
index 0000000000..70eeafb9d8
--- /dev/null
+++ b/dviware/dvisvgm/src/Process.hpp
@@ -0,0 +1,40 @@
+/*************************************************************************
+** Process.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef PROCESS_HPP
+#define PROCESS_HPP
+
+#include <string>
+
+class Process {
+ public:
+ Process (std::string cmd, std::string paramstr);
+ Process (const Process &orig) =delete;
+ Process (Process &&orig) =delete;
+ bool run (std::string *out=nullptr);
+ bool run (const std::string &dir, std::string *out=nullptr);
+
+ private:
+ std::string _cmd;
+ const std::string _paramstr;
+};
+
+#endif
+
diff --git a/dviware/dvisvgm/src/PsSpecialHandler.cpp b/dviware/dvisvgm/src/PsSpecialHandler.cpp
new file mode 100644
index 0000000000..6d5c906a8c
--- /dev/null
+++ b/dviware/dvisvgm/src/PsSpecialHandler.cpp
@@ -0,0 +1,1297 @@
+/*************************************************************************
+** PsSpecialHandler.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <array>
+#include <cmath>
+#include <fstream>
+#include <memory>
+#include <sstream>
+#include "EPSFile.hpp"
+#include "FileFinder.hpp"
+#include "FileSystem.hpp"
+#include "Message.hpp"
+#include "PathClipper.hpp"
+#include "PSPattern.hpp"
+#include "PSPreviewFilter.hpp"
+#include "PsSpecialHandler.hpp"
+#include "SpecialActions.hpp"
+#include "SVGTree.hpp"
+#include "TensorProductPatch.hpp"
+#include "TriangularPatch.hpp"
+
+using namespace std;
+
+
+bool PsSpecialHandler::COMPUTE_CLIPPATHS_INTERSECTIONS = false;
+bool PsSpecialHandler::SHADING_SEGMENT_OVERLAP = false;
+int PsSpecialHandler::SHADING_SEGMENT_SIZE = 20;
+double PsSpecialHandler::SHADING_SIMPLIFY_DELTA = 0.01;
+
+
+PsSpecialHandler::PsSpecialHandler () : _psi(this), _actions(), _previewFilter(_psi), _xmlnode(), _savenode()
+{
+}
+
+
+PsSpecialHandler::~PsSpecialHandler () {
+ _psi.setActions(nullptr); // ensure no further PS actions are performed
+}
+
+
+/** Initializes the PostScript handler. It's called by the first use of process(). The
+ * deferred initialization speeds up the conversion of DVI files that doesn't contain
+ * PS specials. */
+void PsSpecialHandler::initialize () {
+ if (_psSection == PS_NONE) {
+ initgraphics();
+ // execute dvips prologue/header files
+ for (const char *fname : {"tex.pro", "texps.pro", "special.pro", "color.pro"})
+ processHeaderFile(fname);
+ // disable bop/eop operators to prevent side-effects by
+ // unexpected bops/eops present in PS specials
+ _psi.execute("\nTeXDict begin /bop{pop pop}def /eop{}def end ");
+ _psSection = PS_HEADERS; // allow to process header specials now
+ }
+}
+
+
+/** Set initial values of PS graphics state (see PS language reference (Red Book)), p. 612). */
+void PsSpecialHandler::initgraphics () {
+ _linewidth = 1;
+ _linecap = _linejoin = 0; // butt end caps and miter joins
+ _miterlimit = 4;
+ _xmlnode = _savenode = nullptr;
+ _opacityalpha = _shapealpha = 1; // fully opaque
+ _blendmode = 0; // "normal" mode (no blending)
+ _sx = _sy = _cos = 1.0;
+ _pattern = nullptr;
+ _currentcolor = Color::BLACK;
+ _dashoffset = 0;
+ _dashpattern.clear();
+ _path.clear();
+ _clipStack.clear();
+}
+
+
+void PsSpecialHandler::processHeaderFile (const char *name) {
+ if (const char *path = FileFinder::instance().lookup(name, false)) {
+ ifstream ifs(path);
+ _psi.execute(string("%%BeginProcSet: ")+name+" 0 0\n", false);
+ _psi.execute(ifs, false);
+ _psi.execute("%%EndProcSet\n", false);
+ }
+ else
+ Message::wstream(true) << "PostScript header file " << name << " not found\n";
+}
+
+
+void PsSpecialHandler::enterBodySection () {
+ if (_psSection == PS_HEADERS) {
+ _psSection = PS_BODY; // don't process any PS header code
+ ostringstream oss;
+ // process collected header code
+ if (!_headerCode.empty()) {
+ oss << "\nTeXDict begin @defspecial " << _headerCode << "\n@fedspecial end";
+ _headerCode.clear();
+ }
+ // push dictionary "TeXDict" with dvips definitions on dictionary stack
+ // and initialize basic dvips PostScript variables
+ oss << "\nTeXDict begin 0 0 1000 72 72 () @start 0 0 moveto ";
+ _psi.execute(oss.str(), false);
+ // Check for information generated by preview.sty. If the tightpage options
+ // was set, don't execute the bop-hook but allow the PS filter to read
+ // the bbox data present at the beginning of the page.
+ _psi.setFilter(&_previewFilter);
+ _previewFilter.activate();
+ if (!_previewFilter.tightpage())
+ _psi.execute("userdict/bop-hook known{bop-hook}if\n", false);
+ }
+}
+
+
+/** Move PS graphic position to current DVI location. */
+void PsSpecialHandler::moveToDVIPos () {
+ if (_actions) {
+ const double x = _actions->getX();
+ const double y = _actions->getY();
+ ostringstream oss;
+ oss << '\n' << x << ' ' << y << " moveto ";
+ _psi.execute(oss.str());
+ _currentpoint = DPair(x, y);
+ }
+}
+
+
+/** Executes a PS snippet and optionally synchronizes the DVI cursor position
+ * with the current PS point.
+ * @param[in] is stream to read the PS code from
+ * @param[in] updatePos if true, move the DVI drawing position to the current PS point */
+void PsSpecialHandler::executeAndSync (istream &is, bool updatePos) {
+ if (_actions && _actions->getColor() != _currentcolor) {
+ // update the PS graphics state if the color has been changed by a color special
+ double r, g, b;
+ _actions->getColor().getRGB(r, g, b);
+ ostringstream oss;
+ oss << '\n' << r << ' ' << g << ' ' << b << " setrgbcolor ";
+ _psi.execute(oss.str(), false);
+ }
+ _psi.execute(is);
+ if (updatePos) {
+ // retrieve current PS position (stored in _currentpoint)
+ _psi.execute("\nquerypos ");
+ if (_actions) {
+ _actions->setX(_currentpoint.x());
+ _actions->setY(_currentpoint.y());
+ }
+ }
+}
+
+
+void PsSpecialHandler::preprocess (const string &prefix, istream &is, SpecialActions &actions) {
+ initialize();
+ if (_psSection != PS_HEADERS)
+ return;
+
+ _actions = &actions;
+ if (prefix == "!") {
+ _headerCode += "\n";
+ _headerCode += string(istreambuf_iterator<char>(is), istreambuf_iterator<char>());
+ }
+ else if (prefix == "header=") {
+ // read and execute PS header file
+ string fname;
+ is >> fname;
+ processHeaderFile(fname.c_str());
+ }
+}
+
+
+bool PsSpecialHandler::process (const string &prefix, istream &is, SpecialActions &actions) {
+ // process PS headers only once (in prescan)
+ if (prefix == "!" || prefix == "header=")
+ return true;
+
+ _actions = &actions;
+ initialize();
+ if (_psSection != PS_BODY)
+ enterBodySection();
+
+ if (prefix == "\"" || prefix == "pst:") {
+ // read and execute literal PostScript code (isolated by a wrapping save/restore pair)
+ moveToDVIPos();
+ _psi.execute("\n@beginspecial @setspecial ");
+ executeAndSync(is, false);
+ _psi.execute("\n@endspecial ");
+ }
+ else if (prefix == "psfile=" || prefix == "PSfile=" || prefix == "pdffile=") {
+ if (_actions) {
+ StreamInputReader in(is);
+ const string fname = in.getQuotedString(in.peek() == '"' ? "\"" : nullptr);
+ map<string,string> attr;
+ in.parseAttributes(attr, false);
+ imgfile(prefix == "pdffile=" ? FileType::PDF : FileType::EPS, fname, attr);
+ }
+ }
+ else if (prefix == "ps::") {
+ if (_actions)
+ _actions->finishLine(); // reset DVI position on next DVI command
+ if (is.peek() == '[') {
+ // collect characters inside the brackets
+ string code;
+ for (int i=0; i < 9 && is.peek() != ']' && !is.eof(); ++i)
+ code += char(is.get());
+ if (is.peek() == ']')
+ code += char(is.get());
+
+ if (code == "[begin]" || code == "[nobreak]") {
+ moveToDVIPos();
+ executeAndSync(is, true);
+ }
+ else {
+ // no move to DVI position here
+ if (code != "[end]") // PS array?
+ _psi.execute(code);
+ executeAndSync(is, true);
+ }
+ }
+ else { // ps::<code> behaves like ps::[end]<code>
+ // no move to DVI position here
+ executeAndSync(is, true);
+ }
+ }
+ else { // ps: ... or PST: ...
+ if (_actions)
+ _actions->finishLine();
+ moveToDVIPos();
+ StreamInputReader in(is);
+ if (in.check(" plotfile ")) { // ps: plotfile fname
+ string fname = in.getString();
+ ifstream ifs(fname);
+ if (ifs)
+ _psi.execute(ifs);
+ else
+ Message::wstream(true) << "file '" << fname << "' not found in ps: plotfile\n";
+ }
+ else {
+ // ps:<code> is almost identical to ps::[begin]<code> but does
+ // a final repositioning to the current DVI location
+ executeAndSync(is, true);
+ moveToDVIPos();
+ }
+ }
+ return true;
+}
+
+
+/** Handles a psfile/pdffile special which places an external EPS/PDF graphic
+ * at the current DVI position. The lower left corner (llx,lly) of the
+ * given bounding box is placed at the DVI position.
+ * @param[in] filetype type of file to process (EPS or PDF)
+ * @param[in] fname EPS/PDF file to be included
+ * @param[in] attr attributes given with psfile/pdffile special */
+void PsSpecialHandler::imgfile (FileType filetype, const string &fname, const map<string,string> &attr) {
+ // prevent warning about missing image file "/dev/null" which is
+ // added by option "psfixbb" of the preview package
+ if (fname == "/dev/null")
+ return;
+
+ string filepath;
+ if (const char *path = FileFinder::instance().lookup(fname, false))
+ filepath = FileSystem::adaptPathSeperators(path);
+ if ((filepath.empty() || !FileSystem::exists(filepath)) && FileSystem::exists(fname))
+ filepath = fname;
+ if (filepath.empty()) {
+ Message::wstream(true) << "file '" << fname << "' not found\n";
+ return;
+ }
+ map<string,string>::const_iterator it;
+
+ // bounding box of EPS figure in PS point units (lower left and upper right corner)
+ double llx = (it = attr.find("llx")) != attr.end() ? stod(it->second) : 0;
+ double lly = (it = attr.find("lly")) != attr.end() ? stod(it->second) : 0;
+ double urx = (it = attr.find("urx")) != attr.end() ? stod(it->second) : 0;
+ double ury = (it = attr.find("ury")) != attr.end() ? stod(it->second) : 0;
+ int pageno = (it = attr.find("page")) != attr.end() ? stoi(it->second, nullptr, 10) : 1;
+
+ if (filetype == FileType::PDF && llx == 0 && lly == 0 && urx == 0 && ury == 0) {
+ BoundingBox pagebox = _psi.pdfPageBox(fname, pageno);
+ if (pagebox.valid()) {
+ llx = pagebox.minX();
+ lly = pagebox.minY();
+ urx = pagebox.maxX();
+ ury = pagebox.maxY();
+ }
+ }
+
+ // desired width and height of the untransformed figure in PS point units
+ double rwi = (it = attr.find("rwi")) != attr.end() ? stod(it->second)/10.0 : -1;
+ double rhi = (it = attr.find("rhi")) != attr.end() ? stod(it->second)/10.0 : -1;
+ if (rwi == 0 || rhi == 0 || urx-llx == 0 || ury-lly == 0)
+ return;
+
+ // user transformations (default values chosen according to dvips manual)
+ // order of transformations: rotate, scale, translate/offset
+ double hoffset = (it = attr.find("hoffset")) != attr.end() ? stod(it->second) : 0;
+ double voffset = (it = attr.find("voffset")) != attr.end() ? stod(it->second) : 0;
+// double hsize = (it = attr.find("hsize")) != attr.end() ? stod(it->second) : 612;
+// double vsize = (it = attr.find("vsize")) != attr.end() ? stod(it->second) : 792;
+ double hscale = (it = attr.find("hscale")) != attr.end() ? stod(it->second) : 100;
+ double vscale = (it = attr.find("vscale")) != attr.end() ? stod(it->second) : 100;
+ double angle = (it = attr.find("angle")) != attr.end() ? stod(it->second) : 0;
+
+ bool clipToBbox = (attr.find("clip") != attr.end());
+
+ // compute factors to scale the bounding box to width rwi and height rhi
+ double sx = rwi/abs(llx-urx);
+ double sy = rhi/abs(lly-ury);
+ if (sx == 0 || sy == 0)
+ return;
+
+ if (sx < 0) sx = sy; // rwi attribute not set
+ if (sy < 0) sy = sx; // rhi attribute not set
+ if (sx < 0) sx = sy = 1.0; // neither rwi nor rhi set
+
+ // save current DVI position
+ double x = _actions->getX();
+ double y = _actions->getY();
+
+ // all following drawings are relative to (0,0)
+ _actions->setX(0);
+ _actions->setY(0);
+ moveToDVIPos();
+
+ // clip image to its bounding box if flag 'clip' is given
+ string rectclip;
+ if (clipToBbox)
+ rectclip = to_string(llx)+" "+to_string(lly)+" "+to_string(urx-llx)+" "+to_string(ury-lly)+" rectclip";
+
+ auto groupNode = util::make_unique<XMLElement>("g"); // put SVG nodes created from the EPS/PDF file in this group
+ _xmlnode = groupNode.get();
+ _psi.execute(
+ "\n@beginspecial @setspecial" // enter special environment
+ "/setpagedevice{@setpagedevice}def " // activate processing of operator "setpagedevice"
+ "matrix setmatrix" // don't apply outer PS transformations
+ "/FirstPage "+to_string(pageno)+" def" // set number of fisrt page to convert (PDF only)
+ "/LastPage "+to_string(pageno)+" def " // set number of last page to convert (PDF only)
+ +rectclip+ // clip to bounding box (if requexted by attribute 'clip')
+ "("+ filepath + ")run " // execute file content
+ "@endspecial " // leave special environment
+ );
+ if (!groupNode->empty()) { // has anything been drawn?
+ Matrix matrix(1);
+ matrix.scale(sx, -sy).rotate(-angle).scale(hscale/100, vscale/100); // apply transformation attributes
+ matrix.translate(x+hoffset, y-voffset); // move image to current DVI position
+ matrix.lmultiply(_actions->getMatrix());
+
+ // update bounding box
+ BoundingBox bbox(0, 0, urx-llx, ury-lly);
+ bbox.transform(matrix);
+ _actions->embed(bbox);
+
+ // insert group containing SVG nodes created from image
+ matrix.rmultiply(TranslationMatrix(-llx, -lly)); // move lower left corner of image to origin
+ if (!matrix.isIdentity())
+ groupNode->addAttribute("transform", matrix.toSVG());
+ _actions->svgTree().appendToPage(std::move(groupNode));
+ }
+ _xmlnode = nullptr; // append following elements to page group again
+
+ // restore DVI position
+ _actions->setX(x);
+ _actions->setY(y);
+ moveToDVIPos();
+}
+
+
+/** Apply transformation to width, height, and depth set by preview package.
+ * @param[in] matrix transformation matrix to apply
+ * @param[out] w width
+ * @param[out] h height
+ * @param[out] d depth
+ * @return true if the baseline is still horizontal after the transformation */
+static bool transform_box_extents (const Matrix &matrix, double &w, double &h, double &d) {
+ DPair shift = matrix*DPair(0,0); // the translation component of the matrix
+ DPair ex = matrix*DPair(1,0)-shift;
+ DPair ey = matrix*DPair(0,1)-shift;
+ if (ex.y() != 0 && ey.x() != 0) // rotation != mod 90 degrees?
+ return false; // => non-horizontal baseline, can't compute meaningful extents
+
+ if (ex.y() == 0) // horizontal scaling or skewing?
+ w *= fabs(ex.x());
+ if (ey.x()==0 || ex.y()==0) { // vertical scaling?
+ if (ey.y() < 0) swap(h, d);
+ if (double sy = fabs(ey.y())/ey.length()) {
+ h *= fabs(ey.y()/sy);
+ d *= fabs(ey.y()/sy);
+ }
+ else
+ h = d = 0;
+ }
+ return true;
+}
+
+
+void PsSpecialHandler::dviEndPage (unsigned, SpecialActions &actions) {
+ BoundingBox bbox;
+ if (_previewFilter.getBoundingBox(bbox)) { // is there any data written by preview package?
+ double w=0, h=0, d=0;
+ if (actions.getBBoxFormatString() == "preview" || actions.getBBoxFormatString() == "min") {
+ if (actions.getBBoxFormatString() == "preview") {
+ w = max(0.0, _previewFilter.width());
+ h = max(0.0, _previewFilter.height());
+ d = max(0.0, _previewFilter.depth());
+ actions.bbox() = bbox;
+ Message::mstream() << "\napplying bounding box set by";
+ }
+ else {
+ w = actions.bbox().width();
+ h = max(0.0, -actions.bbox().minY());
+ d = max(0.0, actions.bbox().maxY());
+ Message::mstream() << "\ncomputing extents based on data set by";
+ }
+ Message::mstream() << " preview package (version " << _previewFilter.version() << ")\n";
+
+ // apply page transformations to box extents
+ Matrix pagetrans = actions.getPageTransformation();
+ bool isBaselineHorizontal = transform_box_extents(pagetrans, w, h, d);
+ actions.bbox().lock();
+
+ if (!isBaselineHorizontal)
+ Message::mstream() << "can't determine height, width, and depth due to non-horizontal baseline\n";
+ else {
+ const double bp2pt = 72.27/72.0;
+ Message::mstream() <<
+ "width=" << XMLString(w*bp2pt) << "pt, "
+ "height=" << XMLString(h*bp2pt) << "pt, "
+ "depth=" << XMLString(d*bp2pt) << "pt\n";
+ }
+#if 0
+ auto rect = util::make_unique<XMLElement>("rect");
+ rect->addAttribute("x", actions.bbox().minX());
+ rect->addAttribute("y", actions.bbox().minY());
+ rect->addAttribute("width", w);
+ rect->addAttribute("height", h+d);
+ rect->addAttribute("fill", "none");
+ rect->addAttribute("stroke", "red");
+ rect->addAttribute("stroke-width", "0.1");
+ actions.appendToPage(std::move(rect));
+ if (d > 0) {
+ auto line = util::make_unique<XMLElement>("line");
+ line->addAttribute("x1", actions.bbox().minX());
+ line->addAttribute("y1", actions.bbox().minY()+h);
+ line->addAttribute("x2", actions.bbox().maxX());
+ line->addAttribute("y2", actions.bbox().minY()+h);
+ line->addAttribute("stroke", "blue");
+ line->addAttribute("stroke-width", "0.1");
+ actions.appendToPage(std::move(line));
+ }
+#endif
+ }
+ }
+ // close dictionary TeXDict and execute end-hook if defined
+ if (_psSection == PS_BODY) {
+ _psi.execute("\nend userdict/end-hook known{end-hook}if initgraphics ");
+ initgraphics(); // reset graphics state to default values
+ _psSection = PS_HEADERS;
+ }
+}
+
+///////////////////////////////////////////////////////
+
+void PsSpecialHandler::setpagedevice (std::vector<double> &p) {
+ _linewidth = 1;
+ _linecap = _linejoin = 0; // butt end caps and miter joins
+ _miterlimit = 4;
+ _opacityalpha = _shapealpha = 1; // fully opaque
+ _blendmode = 0; // "normal" mode (no blending)
+ _sx = _sy = _cos = 1.0;
+ _pattern = nullptr;
+ _currentcolor = Color::BLACK;
+ _dashoffset = 0;
+ _dashpattern.clear();
+ _path.clear();
+}
+
+
+void PsSpecialHandler::gsave (vector<double>&) {
+ _clipStack.dup();
+}
+
+
+void PsSpecialHandler::grestore (vector<double>&) {
+ _clipStack.pop();
+}
+
+
+void PsSpecialHandler::grestoreall (vector<double>&) {
+ _clipStack.pop(-1, true);
+}
+
+
+void PsSpecialHandler::save (vector<double> &p) {
+ _clipStack.dup(static_cast<int>(p[0]));
+}
+
+
+void PsSpecialHandler::restore (vector<double> &p) {
+ _clipStack.pop(static_cast<int>(p[0]));
+}
+
+
+void PsSpecialHandler::moveto (vector<double> &p) {
+ _path.moveto(p[0], p[1]);
+}
+
+
+void PsSpecialHandler::lineto (vector<double> &p) {
+ _path.lineto(p[0], p[1]);
+}
+
+
+void PsSpecialHandler::curveto (vector<double> &p) {
+ _path.cubicto(p[0], p[1], p[2], p[3], p[4], p[5]);
+}
+
+
+void PsSpecialHandler::closepath (vector<double>&) {
+ _path.closepath();
+}
+
+
+static string css_blendmode_name (int mode) {
+ static const array<const char*,16> modenames = {{
+ "normal", "multiply", "screen", "overlay", "soft-light", "hard-light", "color-dodge", "color-burn",
+ "darken", "lighten", "difference", "exclusion", "hue", "saturation", "color", "luminosity"
+ }};
+ if (mode < 0 || mode > 15)
+ return "";
+ return modenames[mode];
+}
+
+
+/** Draws the current path recorded by previously executed path commands (moveto, lineto,...).
+ * @param[in] p not used */
+void PsSpecialHandler::stroke (vector<double> &p) {
+ _path.removeRedundantCommands();
+ if ((_path.empty() && !_clipStack.prependedPath()) || !_actions)
+ return;
+
+ BoundingBox bbox;
+ if (!_actions->getMatrix().isIdentity()) {
+ _path.transform(_actions->getMatrix());
+ if (!_xmlnode)
+ bbox.transform(_actions->getMatrix());
+ }
+ if (_clipStack.prependedPath())
+ _path.prepend(*_clipStack.prependedPath());
+ unique_ptr<XMLElement> path;
+ Pair<double> point;
+ if (_path.isDot(point)) { // zero-length path?
+ if (_linecap == 1) { // round line ends? => draw dot
+ double x = point.x();
+ double y = point.y();
+ double r = _linewidth/2.0;
+ path = util::make_unique<XMLElement>("circle");
+ path->addAttribute("cx", x);
+ path->addAttribute("cy", y);
+ path->addAttribute("r", r);
+ path->addAttribute("fill", _actions->getColor().svgColorString());
+ bbox = BoundingBox(x-r, y-r, x+r, y+r);
+ }
+ }
+ else {
+ // compute bounding box
+ _path.computeBBox(bbox);
+ bbox.expand(_linewidth/2);
+
+ ostringstream oss;
+ _path.writeSVG(oss, SVGTree::RELATIVE_PATH_CMDS);
+ path = util::make_unique<XMLElement>("path");
+ path->addAttribute("d", oss.str());
+ path->addAttribute("stroke", _actions->getColor().svgColorString());
+ path->addAttribute("fill", "none");
+ if (_linewidth != 1)
+ path->addAttribute("stroke-width", _linewidth);
+ if (_miterlimit != 4)
+ path->addAttribute("stroke-miterlimit", _miterlimit);
+ if (_linecap > 0) // default value is "butt", no need to set it explicitly
+ path->addAttribute("stroke-linecap", _linecap == 1 ? "round" : "square");
+ if (_linejoin > 0) // default value is "miter", no need to set it explicitly
+ path->addAttribute("stroke-linejoin", _linecap == 1 ? "round" : "bevel");
+ if (_opacityalpha < 1 || _shapealpha < 1)
+ path->addAttribute("stroke-opacity", _opacityalpha*_shapealpha);
+ if (_blendmode > 0 && _blendmode < 16)
+ path->addAttribute("style", "mix-blend-mode:"+css_blendmode_name(_blendmode));
+ if (!_dashpattern.empty()) {
+ ostringstream oss;
+ for (size_t i=0; i < _dashpattern.size(); i++) {
+ if (i > 0)
+ oss << ',';
+ oss << XMLString(_dashpattern[i]);
+ }
+ path->addAttribute("stroke-dasharray", oss.str());
+ if (_dashoffset != 0)
+ path->addAttribute("stroke-dashoffset", _dashoffset);
+ }
+ }
+ if (path && _clipStack.path()) {
+ // assign clipping path and clip bounding box
+ path->addAttribute("clip-path", XMLString("url(#clip")+XMLString(_clipStack.topID())+")");
+ BoundingBox clipbox;
+ _clipStack.path()->computeBBox(clipbox);
+ bbox.intersect(clipbox);
+ _clipStack.removePrependedPath();
+ }
+ if (_xmlnode)
+ _xmlnode->append(std::move(path));
+ else {
+ _actions->svgTree().appendToPage(std::move(path));
+ _actions->embed(bbox);
+ }
+ _path.clear();
+}
+
+
+/** Draws a closed path filled with the current color.
+ * @param[in] p not used
+ * @param[in] evenodd true: use even-odd fill algorithm, false: use nonzero fill algorithm */
+void PsSpecialHandler::fill (vector<double> &p, bool evenodd) {
+ _path.removeRedundantCommands();
+ if ((_path.empty() && !_clipStack.prependedPath()) || !_actions)
+ return;
+
+ // compute bounding box
+ BoundingBox bbox;
+ _path.computeBBox(bbox);
+ if (!_actions->getMatrix().isIdentity()) {
+ _path.transform(_actions->getMatrix());
+ if (!_xmlnode)
+ bbox.transform(_actions->getMatrix());
+ }
+ if (_clipStack.prependedPath())
+ _path.prepend(*_clipStack.prependedPath());
+
+ ostringstream oss;
+ _path.writeSVG(oss, SVGTree::RELATIVE_PATH_CMDS);
+ unique_ptr<XMLElement> path = util::make_unique<XMLElement>("path");
+ path->addAttribute("d", oss.str());
+ if (_pattern)
+ path->addAttribute("fill", XMLString("url(#")+_pattern->svgID()+")");
+ else if (_actions->getColor() != Color::BLACK || _savenode)
+ path->addAttribute("fill", _actions->getColor().svgColorString());
+ if (_clipStack.path()) {
+ // assign clipping path and clip bounding box
+ path->addAttribute("clip-path", XMLString("url(#clip")+XMLString(_clipStack.topID())+")");
+ BoundingBox clipbox;
+ _clipStack.path()->computeBBox(clipbox);
+ bbox.intersect(clipbox);
+ _clipStack.removePrependedPath();
+ }
+ if (evenodd) // SVG default fill rule is "nonzero" algorithm
+ path->addAttribute("fill-rule", "evenodd");
+ if (_opacityalpha < 1 || _shapealpha < 1)
+ path->addAttribute("fill-opacity", _opacityalpha*_shapealpha);
+ if (_blendmode > 0 && _blendmode < 16)
+ path->addAttribute("style", "mix-blend-mode:"+css_blendmode_name(_blendmode));
+ if (_xmlnode)
+ _xmlnode->append(std::move(path));
+ else {
+ _actions->svgTree().appendToPage(std::move(path));
+ _actions->embed(bbox);
+ }
+ _path.clear();
+}
+
+
+/** Creates a Matrix object from a given sequence of 6 double values.
+ * The given values must be arranged in PostScript matrix order.
+ * @param[in] v vector containing the matrix values
+ * @param[in] startindex vector index of first component
+ * @param[out] matrix the generated matrix */
+static void create_matrix (vector<double> &v, int startindex, Matrix &matrix) {
+ // Ensure vector p has 6 elements. If necessary, add missing ones
+ // using corresponding values of the identity matrix.
+ if (v.size()-startindex < 6) {
+ v.resize(6+startindex);
+ for (int i=v.size()-startindex; i < 6; ++i)
+ v[i+startindex] = (i%3 ? 0 : 1);
+ }
+ // PS matrix [a b c d e f] equals ((a,b,0),(c,d,0),(e,f,1)).
+ // Since PS uses left multiplications, we must transpose and reorder
+ // the matrix to ((a,c,e),(b,d,f),(0,0,1)). This is done by the
+ // following swaps.
+ swap(v[startindex+1], v[startindex+2]); // => (a, c, b, d, e, f)
+ swap(v[startindex+2], v[startindex+4]); // => (a, c, e, d, b, f)
+ swap(v[startindex+3], v[startindex+4]); // => (a, c, e, b, d, f)
+ matrix.set(v, startindex);
+}
+
+
+/** Starts the definition of a new fill pattern. This operator
+ * expects 9 parameters for tiling patterns (see PS reference 4.9.2):
+ * @param[in] p the 9 values defining a tiling pattern (see PS reference 4.9.2):
+ * 0: pattern type (0:none, 1:tiling, 2:shading)
+ * 1: pattern ID
+ * 2-5: lower left and upper right coordinates of pattern box
+ * 6: horizontal distance of adjacent tiles
+ * 7: vertical distance of adjacent tiles
+ * 8: paint type (1: colored pattern, 2: uncolored pattern)
+ * 9-14: pattern matrix */
+void PsSpecialHandler::makepattern (vector<double> &p) {
+ int pattern_type = static_cast<int>(p[0]);
+ int id = static_cast<int>(p[1]);
+ switch (pattern_type) {
+ case 0:
+ // pattern definition completed
+ if (_savenode) {
+ _xmlnode = _savenode;
+ _savenode = nullptr;
+ }
+ break;
+ case 1: { // tiling pattern
+ BoundingBox bbox(p[2], p[3], p[4], p[5]);
+ const double &xstep=p[6], &ystep=p[7]; // horizontal and vertical distance of adjacent tiles
+ int paint_type = static_cast<int>(p[8]);
+
+ Matrix matrix; // transformation matrix given together with pattern definition
+ create_matrix(p, 9, matrix);
+ matrix.lmultiply(_actions->getMatrix());
+
+ unique_ptr<PSTilingPattern> pattern;
+ if (paint_type == 1)
+ pattern = util::make_unique<PSColoredTilingPattern>(id, bbox, matrix, xstep, ystep);
+ else
+ pattern = util::make_unique<PSUncoloredTilingPattern>(id, bbox, matrix, xstep, ystep);
+ _savenode = _xmlnode;
+ _xmlnode = pattern->getContainerNode(); // insert the following SVG elements into this node
+ _patterns[id] = std::move(pattern);
+ break;
+ }
+ case 2: {
+ // define a shading pattern
+ }
+ }
+}
+
+
+/** Selects a previously defined fill pattern.
+ * 0: pattern ID
+ * 1-3: (optional) RGB values for uncolored tiling patterns
+ * further parameters depend on the pattern type */
+void PsSpecialHandler::setpattern (vector<double> &p) {
+ int patternID = static_cast<int>(p[0]);
+ Color color;
+ if (p.size() == 4)
+ color.setRGB(p[1], p[2], p[3]);
+ auto it = _patterns.find(patternID);
+ if (it == _patterns.end())
+ _pattern = nullptr;
+ else {
+ if (auto pattern = dynamic_cast<PSUncoloredTilingPattern*>(it->second.get()))
+ pattern->setColor(color);
+ it->second->apply(*_actions);
+ if (auto pattern = dynamic_cast<PSTilingPattern*>(it->second.get()))
+ _pattern = pattern;
+ else
+ _pattern = nullptr;
+ }
+}
+
+
+/** Clears the current clipping path.
+ * @param[in] p not used */
+void PsSpecialHandler::initclip (vector<double> &) {
+ _clipStack.pushEmptyPath();
+}
+
+
+/** Assigns the current clipping path to the graphics path. */
+void PsSpecialHandler::clippath (std::vector<double>&) {
+ if (!_clipStack.empty())
+ _clipStack.setPrependedPath();
+}
+
+
+/** Assigns a new clipping path to the graphics state using the current path.
+ * If the graphics state already contains a clipping path, the new one is
+ * computed by intersecting the current clipping path with the current graphics
+ * path (see PS language reference, 3rd edition, pp. 193, 542)
+ * @param[in] p not used
+ * @param[in] evenodd true: use even-odd fill algorithm, false: use nonzero fill algorithm */
+void PsSpecialHandler::clip (vector<double>&, bool evenodd) {
+ clip(_path, evenodd);
+}
+
+
+/** Assigns a new clipping path to the graphics state using the current path.
+ * If the graphics state already contains a clipping path, the new one is
+ * computed by intersecting the current one with the given path.
+ * @param[in] path path used to restrict the clipping region
+ * @param[in] evenodd true: use even-odd fill algorithm, false: use nonzero fill algorithm */
+void PsSpecialHandler::clip (Path path, bool evenodd) {
+ // when this method is called, _path contains the clipping path
+ if (path.empty() || !_actions)
+ return;
+
+ Path::WindingRule windingRule = evenodd ? Path::WindingRule::EVEN_ODD : Path::WindingRule::NON_ZERO;
+ path.setWindingRule(windingRule);
+
+ if (!_actions->getMatrix().isIdentity())
+ path.transform(_actions->getMatrix());
+ if (_clipStack.prependedPath())
+ path.prepend(*_clipStack.prependedPath());
+
+ int oldID = _clipStack.topID();
+
+ ostringstream oss;
+ bool pathReplaced;
+ if (!COMPUTE_CLIPPATHS_INTERSECTIONS || oldID < 1) {
+ pathReplaced = _clipStack.replace(path);
+ path.writeSVG(oss, SVGTree::RELATIVE_PATH_CMDS);
+ }
+ else {
+ // compute the intersection of the current clipping path with the current graphics path
+ const Path *oldPath = _clipStack.path();
+ Path intersectedPath(windingRule);
+ PathClipper clipper;
+ clipper.intersect(*oldPath, path, intersectedPath);
+ pathReplaced = _clipStack.replace(intersectedPath);
+ intersectedPath.writeSVG(oss, SVGTree::RELATIVE_PATH_CMDS);
+ }
+ if (pathReplaced) {
+ auto pathElem = util::make_unique<XMLElement>("path");
+ pathElem->addAttribute("d", oss.str());
+ if (evenodd)
+ pathElem->addAttribute("clip-rule", "evenodd");
+
+ int newID = _clipStack.topID();
+ auto clipElem = util::make_unique<XMLElement>("clipPath");
+ clipElem->addAttribute("id", XMLString("clip")+XMLString(newID));
+ if (!COMPUTE_CLIPPATHS_INTERSECTIONS && oldID)
+ clipElem->addAttribute("clip-path", XMLString("url(#clip")+XMLString(oldID)+")");
+
+ clipElem->append(std::move(pathElem));
+ _actions->svgTree().appendToDefs(std::move(clipElem));
+ }
+}
+
+
+/** Applies a gradient fill to the current graphics path. Vector p contains the shading parameters
+ * in the following order:
+ * - shading type (6=Coons, 7=tensor product)
+ * - color space (1=gray, 3=rgb, 4=cmyk)
+ * - 1.0 followed by the background color components based on the declared color space, or 0.0
+ * - 1.0 followed by the bounding box coordinates, or 0.0
+ * - geometry and color parameters depending on the shading type */
+void PsSpecialHandler::shfill (vector<double> &params) {
+ if (params.size() < 9)
+ return;
+
+ // collect common data relevant for all shading types
+ int shadingTypeID = static_cast<int>(params[0]);
+ ColorSpace colorSpace = Color::ColorSpace::RGB;
+ switch (static_cast<int>(params[1])) {
+ case 1: colorSpace = Color::ColorSpace::GRAY; break;
+ case 3: colorSpace = Color::ColorSpace::RGB; break;
+ case 4: colorSpace = Color::ColorSpace::CMYK; break;
+ }
+ VectorIterator<double> it(params);
+ it += 2; // skip shading type and color space
+ // Get color to fill the whole mesh area before drawing the gradient colors on top of that background.
+ // This is an optional parameter to shfill.
+ bool bgcolorGiven = static_cast<bool>(*it++);
+ Color bgcolor;
+ if (bgcolorGiven)
+ bgcolor.set(colorSpace, it);
+ // Get clipping rectangle to limit the drawing area of the gradient mesh.
+ // This is an optional parameter to shfill too.
+ bool bboxGiven = static_cast<bool>(*it++);
+ if (bboxGiven) { // bounding box given
+ Path bboxPath;
+ const double &x1 = *it++;
+ const double &y1 = *it++;
+ const double &x2 = *it++;
+ const double &y2 = *it++;
+ bboxPath.moveto(x1, y1);
+ bboxPath.lineto(x2, y1);
+ bboxPath.lineto(x2, y2);
+ bboxPath.lineto(x1, y2);
+ bboxPath.closepath();
+ clip(bboxPath, false);
+ }
+ try {
+ if (shadingTypeID == 5)
+ processLatticeTriangularPatchMesh(colorSpace, it);
+ else
+ processSequentialPatchMesh(shadingTypeID, colorSpace, it);
+ }
+ catch (ShadingException &e) {
+ Message::estream(false) << "PostScript error: " << e.what() << '\n';
+ it.invalidate(); // stop processing the remaining patch data
+ }
+ catch (IteratorException &e) {
+ Message::estream(false) << "PostScript error: incomplete shading data\n";
+ }
+ if (bboxGiven)
+ _clipStack.pop();
+}
+
+
+/** Reads position and color data of a single shading patch from the data vector.
+ * @param[in] shadingTypeID PS shading type ID identifying the format of the subsequent patch data
+ * @param[in] edgeflag edge flag specifying how to connect the current patch to the preceding one
+ * @param[in] cspace color space used to compute the color gradient
+ * @param[in,out] it iterator used to sequentially access the patch data
+ * @param[out] points the points defining the geometry of the patch
+ * @param[out] colors the colors assigned to the vertices of the patch */
+static void read_patch_data (ShadingPatch &patch, int edgeflag,
+ VectorIterator<double> &it, vector<DPair> &points, vector<Color> &colors)
+{
+ // number of control points and colors required to define a single patch
+ int numPoints = patch.numPoints(edgeflag);
+ int numColors = patch.numColors(edgeflag);
+ points.resize(numPoints);
+ colors.resize(numColors);
+ if (patch.psShadingType() == 4) {
+ // format of a free-form triangular patch definition, where eN denotes
+ // the edge of the corresponding vertex:
+ // edge flag = 0, x1, y1, {color1}, e2, x2, y2, {color2}, e3, x3, y3, {color3}
+ // edge flag > 0, x1, y1, {color1}
+ for (int i=0; i < numPoints; i++) {
+ if (i > 0) ++it; // skip redundant edge flag from free-form triangular patch
+ double x = *it++;
+ double y = *it++;
+ points[i] = DPair(x, y);
+ colors[i].set(patch.colorSpace(), it);
+ }
+ }
+ else if (patch.psShadingType() == 6 || patch.psShadingType() == 7) {
+ // format of each Coons/tensor product patch definition:
+ // edge flag = 0, x1, y1, ... , xn, yn, {color1}, {color2}, {color3}, {color4}
+ // edge flag > 0, x5, y5, ... , xn, yn, {color3}, {color4}
+ for (int i=0; i < numPoints; i++) {
+ double x = *it++;
+ double y = *it++;
+ points[i] = DPair(x, y);
+ }
+ for (int i=0; i < numColors; i++)
+ colors[i].set(patch.colorSpace(), it);
+ }
+}
+
+
+class ShadingCallback : public ShadingPatch::Callback {
+ public:
+ ShadingCallback (SpecialActions &actions, XMLElement *parent, int clippathID)
+ : _actions(actions)
+ {
+ auto group = util::make_unique<XMLElement>("g");
+ _group = group.get();
+ if (parent)
+ parent->append(std::move(group));
+ else
+ actions.svgTree().appendToPage(std::move(group));
+ if (clippathID > 0)
+ _group->addAttribute("clip-path", XMLString("url(#clip")+XMLString(clippathID)+")");
+ }
+
+ void patchSegment (GraphicsPath<double> &path, const Color &color) override {
+ if (!_actions.getMatrix().isIdentity())
+ path.transform(_actions.getMatrix());
+
+ // draw a single patch segment
+ ostringstream oss;
+ path.writeSVG(oss, SVGTree::RELATIVE_PATH_CMDS);
+ auto pathElem = util::make_unique<XMLElement>("path");
+ pathElem->addAttribute("d", oss.str());
+ pathElem->addAttribute("fill", color.svgColorString());
+ _group->append(std::move(pathElem));
+ }
+
+ private:
+ SpecialActions &_actions;
+ XMLElement *_group;
+};
+
+
+/** Handle all patch meshes whose patches and their connections can be processed sequentially.
+ * This comprises free-form triangular, Coons, and tensor-product patch meshes. */
+void PsSpecialHandler::processSequentialPatchMesh (int shadingTypeID, ColorSpace colorSpace, VectorIterator<double> &it) {
+ unique_ptr<ShadingPatch> previousPatch;
+ while (it.valid()) {
+ int edgeflag = static_cast<int>(*it++);
+ vector<DPair> points;
+ vector<Color> colors;
+ unique_ptr<ShadingPatch> patch = ShadingPatch::create(shadingTypeID, colorSpace);
+ read_patch_data(*patch, edgeflag, it, points, colors);
+ patch->setPoints(points, edgeflag, previousPatch.get());
+ patch->setColors(colors, edgeflag, previousPatch.get());
+ ShadingCallback callback(*_actions, _xmlnode, _clipStack.topID());
+#if 0
+ if (bgcolorGiven) {
+ // fill whole patch area with given background color
+ GraphicsPath<double> outline;
+ patch->getBoundaryPath(outline);
+ callback.patchSegment(outline, bgcolor);
+ }
+#endif
+ patch->approximate(SHADING_SEGMENT_SIZE, SHADING_SEGMENT_OVERLAP, SHADING_SIMPLIFY_DELTA, callback);
+ if (!_xmlnode) {
+ // update bounding box
+ BoundingBox bbox;
+ patch->getBBox(bbox);
+ bbox.transform(_actions->getMatrix());
+ _actions->embed(bbox);
+ }
+ previousPatch = std::move(patch);
+ }
+}
+
+
+void PsSpecialHandler::processLatticeTriangularPatchMesh (ColorSpace colorSpace, VectorIterator<double> &it) {
+ int verticesPerRow = static_cast<int>(*it++);
+ if (verticesPerRow < 2)
+ return;
+
+ struct PatchVertex {
+ DPair point;
+ Color color;
+ };
+
+ // hold two adjacent rows of vertices and colors
+ vector<PatchVertex> row1(verticesPerRow);
+ vector<PatchVertex> row2(verticesPerRow);
+ vector<PatchVertex> *rowptr1 = &row1;
+ vector<PatchVertex> *rowptr2 = &row2;
+ // read data of first row
+ for (int i=0; i < verticesPerRow; i++) {
+ PatchVertex &vertex = (*rowptr1)[i];
+ vertex.point.x(*it++);
+ vertex.point.y(*it++);
+ vertex.color.set(colorSpace, it);
+ }
+ LatticeTriangularPatch patch(colorSpace);
+ ShadingCallback callback(*_actions, _xmlnode, _clipStack.topID());
+ while (it.valid()) {
+ // read next row
+ for (int i=0; i < verticesPerRow; i++) {
+ PatchVertex &vertex = (*rowptr2)[i];
+ vertex.point.x(*it++);
+ vertex.point.y(*it++);
+ vertex.color.set(colorSpace, it);
+ }
+ // create triangular patches for the vertices of the two rows
+ for (int i=0; i < verticesPerRow-1; i++) {
+ const PatchVertex &v1 = (*rowptr1)[i], &v2 = (*rowptr1)[i+1];
+ const PatchVertex &v3 = (*rowptr2)[i], &v4 = (*rowptr2)[i+1];
+ patch.setPoints(v1.point, v2.point, v3.point);
+ patch.setColors(v1.color, v2.color, v3.color);
+ patch.approximate(SHADING_SEGMENT_SIZE, SHADING_SEGMENT_OVERLAP, SHADING_SIMPLIFY_DELTA, callback);
+
+ patch.setPoints(v2.point, v3.point, v4.point);
+ patch.setColors(v2.color, v3.color, v4.color);
+ patch.approximate(SHADING_SEGMENT_SIZE, SHADING_SEGMENT_OVERLAP, SHADING_SIMPLIFY_DELTA, callback);
+ }
+ swap(rowptr1, rowptr2);
+ }
+}
+
+
+/** Clears current path. */
+void PsSpecialHandler::newpath (vector<double> &p) {
+ bool calledByNewpathOp = (p[0] > 0);
+ if (calledByNewpathOp) // function triggered by PS operator 'newpath'?
+ _clipStack.removePrependedPath();
+ _path.clear();
+}
+
+
+void PsSpecialHandler::setmatrix (vector<double> &p) {
+ if (_actions) {
+ Matrix m;
+ create_matrix(p, 0, m);
+ _actions->setMatrix(m);
+ }
+}
+
+
+// In contrast to SVG, PostScript transformations are applied in
+// reverse order (M' = T*M). Thus, the transformation matrices must be
+// left-multiplied in the following methods scale(), translate() and rotate().
+
+
+void PsSpecialHandler::scale (vector<double> &p) {
+ if (_actions) {
+ Matrix m = _actions->getMatrix();
+ ScalingMatrix s(p[0], p[1]);
+ m.rmultiply(s);
+ _actions->setMatrix(m);
+ }
+}
+
+
+void PsSpecialHandler::translate (vector<double> &p) {
+ if (_actions) {
+ Matrix m = _actions->getMatrix();
+ TranslationMatrix t(p[0], p[1]);
+ m.rmultiply(t);
+ _actions->setMatrix(m);
+ }
+}
+
+
+void PsSpecialHandler::rotate (vector<double> &p) {
+ if (_actions) {
+ Matrix m = _actions->getMatrix();
+ RotationMatrix r(p[0]);
+ m.rmultiply(r);
+ _actions->setMatrix(m);
+ }
+}
+
+
+void PsSpecialHandler::setgray (vector<double> &p) {
+ _pattern = nullptr;
+ _currentcolor.setGray(p[0]);
+ if (_actions)
+ _actions->setColor(_currentcolor);
+}
+
+
+void PsSpecialHandler::setrgbcolor (vector<double> &p) {
+ _pattern= nullptr;
+ _currentcolor.setRGB(p[0], p[1], p[2]);
+ if (_actions)
+ _actions->setColor(_currentcolor);
+}
+
+
+void PsSpecialHandler::setcmykcolor (vector<double> &p) {
+ _pattern = nullptr;
+ _currentcolor.setCMYK(p[0], p[1], p[2], p[3]);
+ if (_actions)
+ _actions->setColor(_currentcolor);
+}
+
+
+void PsSpecialHandler::sethsbcolor (vector<double> &p) {
+ _pattern = nullptr;
+ _currentcolor.setHSB(p[0], p[1], p[2]);
+ if (_actions)
+ _actions->setColor(_currentcolor);
+}
+
+
+/** Sets the dash parameters used for stroking.
+ * @param[in] p dash pattern array m1,...,mn plus trailing dash offset */
+void PsSpecialHandler::setdash (vector<double> &p) {
+ _dashpattern.clear();
+ for (size_t i=0; i < p.size()-1; i++)
+ _dashpattern.push_back(scale(p[i]));
+ _dashoffset = scale(p.back());
+}
+
+
+/** This method is called by the PSInterpreter if an PS operator has been executed. */
+void PsSpecialHandler::executed () {
+ if (_actions)
+ _actions->progress("ps");
+}
+
+////////////////////////////////////////////
+
+void PsSpecialHandler::ClippingStack::pushEmptyPath () {
+ if (!_stack.empty())
+ _stack.emplace(Entry());
+}
+
+
+void PsSpecialHandler::ClippingStack::push (const Path &path, int saveID) {
+ shared_ptr<Path> prependedPath;
+ if (!_stack.empty())
+ prependedPath = _stack.top().prependedPath;
+ if (path.empty())
+ _stack.emplace(Entry(saveID));
+ else
+ _stack.emplace(Entry(path, ++_maxID, saveID));
+ _stack.top().prependedPath = prependedPath;
+}
+
+
+/** Pops a single or several elements from the clipping stack.
+ * The method distinguishes between the following cases:
+ * 1) saveID < 0 and grestoreall == false:
+ * pop top element if it was pushed by gsave (its saveID is < 0 as well)
+ * 2) saveID < 0 and grestoreall == true
+ * repeat popping until stack is empty or the top element was pushed
+ * by save (its saveID is >= 0)
+ * 3) saveID >= 0:
+ * pop all elements until the saveID of the top element equals parameter saveID */
+void PsSpecialHandler::ClippingStack::pop (int saveID, bool grestoreall) {
+ if (_stack.empty())
+ return;
+
+ if (saveID < 0) { // grestore?
+ if (_stack.top().saveID < 0) // pushed by 'gsave'?
+ _stack.pop();
+ // pop all further elements pushed by 'gsave' if grestoreall == true
+ while (grestoreall && !_stack.empty() && _stack.top().saveID < 0)
+ _stack.pop();
+ }
+ else {
+ // pop elements pushed by 'gsave'
+ while (!_stack.empty() && _stack.top().saveID != saveID)
+ _stack.pop();
+ // pop element pushed by 'save'
+ if (!_stack.empty())
+ _stack.pop();
+ }
+}
+
+
+/** Returns a pointer to the path on top of the stack, or 0 if the stack is empty. */
+const PsSpecialHandler::Path* PsSpecialHandler::ClippingStack::path () const {
+ return _stack.empty() ? nullptr : _stack.top().path.get();
+}
+
+
+/** Returns a pointer to the path on top of the stack, or 0 if the stack is empty. */
+const PsSpecialHandler::Path* PsSpecialHandler::ClippingStack::prependedPath () const {
+ return _stack.empty() ? nullptr : _stack.top().prependedPath.get();
+}
+
+
+void PsSpecialHandler::ClippingStack::removePrependedPath () {
+ if (!_stack.empty())
+ _stack.top().prependedPath = nullptr;
+}
+
+
+/** Pops all elements from the stack. */
+void PsSpecialHandler::ClippingStack::clear() {
+ while (!_stack.empty())
+ _stack.pop();
+}
+
+
+/** Replaces the top path by a new one.
+ * @param[in] path new path to put on the stack
+ * @return true if the new path differs from the previous one */
+bool PsSpecialHandler::ClippingStack::replace (const Path &path) {
+ if (_stack.empty())
+ push(path, -1);
+ else if (_stack.top().path && path == *_stack.top().path)
+ return false;
+ else {
+ _stack.top().path = make_shared<Path>(path);
+ _stack.top().pathID = ++_maxID;
+ }
+ return true;
+}
+
+
+/** Duplicates the top element, i.e. the top element is pushed again. */
+void PsSpecialHandler::ClippingStack::dup (int saveID) {
+ _stack.emplace(_stack.empty() ? Entry() : _stack.top());
+ _stack.top().saveID = saveID;
+}
+
+
+void PsSpecialHandler::ClippingStack::setPrependedPath () {
+ if (!_stack.empty())
+ _stack.top().prependedPath = _stack.top().path;
+}
+
+
+vector<const char*> PsSpecialHandler::prefixes() const {
+ vector<const char*> pfx {
+ "header=", // read and execute PS header file prior to the following PS statements
+ "pdffile=", // process PDF file
+ "psfile=", // read and execute PS file
+ "PSfile=", // dito
+ "ps:", // execute literal PS code wrapped by @beginspecial and @endspecial
+ "ps::", // execute literal PS code without additional adaption of the drawing position
+ "!", // execute literal PS header code following this prefix
+ "\"", // execute literal PS code following this prefix
+ "pst:", // dito
+ "PST:", // same as "ps:"
+ };
+ return pfx;
+}
diff --git a/dviware/dvisvgm/src/PsSpecialHandler.hpp b/dviware/dvisvgm/src/PsSpecialHandler.hpp
new file mode 100644
index 0000000000..4c06e6a1b6
--- /dev/null
+++ b/dviware/dvisvgm/src/PsSpecialHandler.hpp
@@ -0,0 +1,185 @@
+/*************************************************************************
+** PsSpecialHandler.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef PSSPECIALHANDLER_HPP
+#define PSSPECIALHANDLER_HPP
+
+#include <memory>
+#include <set>
+#include <stack>
+#include <string>
+#include <vector>
+#include "GraphicsPath.hpp"
+#include "PSInterpreter.hpp"
+#include "SpecialHandler.hpp"
+#include "PSPattern.hpp"
+#include "PSPreviewFilter.hpp"
+
+
+class PSPattern;
+class XMLElement;
+
+class PsSpecialHandler : public SpecialHandler, protected PSActions {
+ using Path = GraphicsPath<double>;
+ using ColorSpace = Color::ColorSpace;
+
+ /** Helper class storing the clipping paths currently present on the graphics context stack.
+ * Since PS operator 'clippath' only delivers a linearly approximated version of the paths
+ * that are sometimes too imprecise for direct usage in SVG, we keep the possibly curved
+ * clipping paths and compute their intersections locally if necessary. */
+ class ClippingStack {
+ public:
+ ClippingStack () : _maxID(0) {}
+ void pushEmptyPath ();
+ void push (const Path &path, int saveID=-1);
+ bool replace (const Path &path);
+ void dup (int saveID=-1);
+ void pop (int saveID=-1, bool grestore=false);
+ void clear ();
+ bool empty () const {return _stack.empty();}
+ void setPrependedPath ();
+ const Path* path () const;
+ const Path* prependedPath () const;
+ void removePrependedPath ();
+ int topID () const {return _stack.empty() ? 0 : _stack.top().pathID;}
+
+ private:
+ struct Entry {
+ std::shared_ptr<Path> path; ///< pointer to current clipping path
+ std::shared_ptr<Path> prependedPath=nullptr;
+ int pathID; ///< ID of current clipping path
+ int saveID; ///< if >=0, path was pushed by 'save', and saveID holds the ID of the PS memory object
+ Entry () : Entry(-1) {}
+ Entry (const Path &p, int pid, int sid) : path(std::make_shared<Path>(p)), pathID(pid), saveID(sid) {}
+ explicit Entry (int sid) : path(nullptr), pathID(0), saveID(sid) {}
+ Entry (const Entry &entry) =default;
+ Entry (Entry &&entry) =default;
+ };
+ size_t _maxID;
+ std::stack<Entry> _stack; ///< stack holding the clipping information of the current graphics context
+ };
+
+ enum PsSection {PS_NONE, PS_HEADERS, PS_BODY};
+ enum class FileType {EPS, PDF};
+
+ public:
+ PsSpecialHandler ();
+ ~PsSpecialHandler () override;
+ const char* name () const override {return "ps";}
+ const char* info () const override {return "dvips PostScript specials";}
+ std::vector<const char*> prefixes() const override;
+ void preprocess (const std::string &prefix, std::istream &is, SpecialActions &actions) override;
+ bool process (const std::string &prefix, std::istream &is, SpecialActions &actions) override;
+ void setDviScaleFactor (double dvi2bp) override {_previewFilter.setDviScaleFactor(dvi2bp);}
+ void enterBodySection ();
+ PSInterpreter& psInterpreter () {return _psi;}
+
+ public:
+ static bool COMPUTE_CLIPPATHS_INTERSECTIONS;
+ static bool SHADING_SEGMENT_OVERLAP;
+ static int SHADING_SEGMENT_SIZE;
+ static double SHADING_SIMPLIFY_DELTA;
+
+ protected:
+ void initialize ();
+ void initgraphics ();
+ void moveToDVIPos ();
+ void executeAndSync (std::istream &is, bool updatePos);
+ void processHeaderFile (const char *fname);
+ void imgfile (FileType type, const std::string &fname, const std::map<std::string,std::string> &attr);
+ void dviEndPage (unsigned pageno, SpecialActions &actions) override;
+ void clip (Path path, bool evenodd);
+ void processSequentialPatchMesh (int shadingTypeID, ColorSpace cspace, VectorIterator<double> &it);
+ void processLatticeTriangularPatchMesh (ColorSpace colorSpace, VectorIterator<double> &it);
+
+ /// scale given value by current PS scale factors
+ double scale (double v) const {return v*(_sx*(1-_cos*_cos) + _sy*_cos*_cos);}
+
+ void applyscalevals (std::vector<double> &p) override {_sx = p[0]; _sy = p[1]; _cos = p[2];}
+ void clip (std::vector<double> &p) override {clip(p, false);}
+ void clip (std::vector<double> &p, bool evenodd);
+ void clippath (std::vector<double> &p) override;
+ void closepath (std::vector<double> &p) override;
+ void curveto (std::vector<double> &p) override;
+ void eoclip (std::vector<double> &p) override {clip(p, true);}
+ void eofill (std::vector<double> &p) override {fill(p, true);}
+ void fill (std::vector<double> &p, bool evenodd);
+ void fill (std::vector<double> &p) override {fill(p, false);}
+ void grestore (std::vector<double> &p) override;
+ void grestoreall (std::vector<double> &p) override;
+ void gsave (std::vector<double> &p) override;
+ void initclip (std::vector<double> &p) override;
+ void lineto (std::vector<double> &p) override;
+ void makepattern (std::vector<double> &p) override;
+ void moveto (std::vector<double> &p) override;
+ void newpath (std::vector<double> &p) override;
+ void querypos (std::vector<double> &p) override {_currentpoint = DPair(p[0], p[1]);}
+ void restore (std::vector<double> &p) override;
+ void rotate (std::vector<double> &p) override;
+ void save (std::vector<double> &p) override;
+ void scale (std::vector<double> &p) override;
+ void setblendmode (std::vector<double> &p) override {_blendmode = int(p[0]);}
+ void setcmykcolor (std::vector<double> &cmyk) override;
+ void setdash (std::vector<double> &p) override;
+ void setgray (std::vector<double> &p) override;
+ void sethsbcolor (std::vector<double> &hsb) override;
+ void setlinecap (std::vector<double> &p) override {_linecap = uint8_t(p[0]);}
+ void setlinejoin (std::vector<double> &p) override {_linejoin = uint8_t(p[0]);}
+ void setlinewidth (std::vector<double> &p) override {_linewidth = scale(p[0] ? p[0] : 0.5);}
+ void setmatrix (std::vector<double> &p) override;
+ void setmiterlimit (std::vector<double> &p) override {_miterlimit = p[0];}
+ void setopacityalpha (std::vector<double> &p) override {_opacityalpha = p[0];}
+ void setshapealpha (std::vector<double> &p) override {_shapealpha = p[0];}
+ void setpagedevice (std::vector<double> &p) override;
+ void setpattern (std::vector<double> &p) override;
+ void setrgbcolor (std::vector<double> &rgb) override;
+ void shfill (std::vector<double> &p) override;
+ void stroke (std::vector<double> &p) override;
+ void translate (std::vector<double> &p) override;
+ void executed () override;
+
+ private:
+ PSInterpreter _psi;
+ SpecialActions *_actions;
+ PSPreviewFilter _previewFilter; ///< filter to extract information generated by the preview package
+ PsSection _psSection=PS_NONE; ///< current section processed (nothing yet, headers, or body specials)
+ XMLElement *_xmlnode; ///< if != 0, created SVG elements are appended to this node
+ XMLElement *_savenode; ///< pointer to temporaryly store _xmlnode
+ std::string _headerCode; ///< collected literal PS header code
+ Path _path;
+ DPair _currentpoint; ///< current PS position in bp units
+ Color _currentcolor; ///< current stroke/fill color
+ double _sx, _sy; ///< horizontal and vertical scale factors retrieved by operator "applyscalevals"
+ double _cos; ///< cosine of angle between (1,0) and transform(1,0)
+ double _linewidth; ///< current line width in bp units
+ double _miterlimit; ///< current miter limit in bp units
+ double _opacityalpha; ///< opacity level (0=fully transparent, ..., 1=opaque)
+ double _shapealpha; ///< shape opacity level (0=fully transparent, ..., 1=opaque)
+ int _blendmode; ///< blend mode used when overlaying colored areas
+ uint8_t _linecap : 2; ///< current line cap (0=butt, 1=round, 2=projecting square)
+ uint8_t _linejoin : 2; ///< current line join (0=miter, 1=round, 2=bevel)
+ double _dashoffset; ///< current dash offset
+ std::vector<double> _dashpattern;
+ ClippingStack _clipStack;
+ std::map<int, std::unique_ptr<PSPattern>> _patterns;
+ PSTilingPattern *_pattern; ///< current pattern
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/RangeMap.cpp b/dviware/dvisvgm/src/RangeMap.cpp
new file mode 100644
index 0000000000..5fa2bb10ab
--- /dev/null
+++ b/dviware/dvisvgm/src/RangeMap.cpp
@@ -0,0 +1,197 @@
+/*************************************************************************
+** RangeMap.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <numeric>
+#include "RangeMap.hpp"
+
+using namespace std;
+
+
+/** Tries to merge range r into this one. This is only possible if the ranges
+ * touch or overlap and if the assigned values match at the junction points.
+ * @param[in] r range to join
+ * @return true if join was successful */
+bool RangeMap::Range::join (const Range &r) {
+ // check most common cases first
+ if (_max+1 < r._min || _min-1 > r._max) // disjoint ranges?
+ return false;
+ if (r._min-1 == _max) { // does r touch *this on the right?
+ if (valueAt(r._min) == r._minval) {
+ _max = r._max;
+ return true;
+ }
+ return false;
+ }
+ if (r._max+1 == _min) { // does r touch *this on the left
+ if (r.valueAt(_min) == _minval) {
+ setMinAndAdaptValue(r._min);
+ return true;
+ }
+ return false;
+ }
+ // the following cases should be pretty rare
+ if (r._min <= _min && r._max >= _max) { // does r overlap *this on both sides?
+ *this = r;
+ return true;
+ }
+ if (r._min < _min) { // left overlap only?
+ if (r.valueAt(_min) == _minval) {
+ _min = r._min;
+ _minval = r._minval;
+ return true;
+ }
+ return false;
+ }
+ if (r._max > _max) { // right overlap only?
+ if (valueAt(r._min) == r._minval) {
+ _max = r._max;
+ return true;
+ }
+ return false;
+ }
+ // r completely inside *this
+ return valueAt(r._min) == r._minval;
+}
+
+
+/** Adds a new number range. The range describes a mapping from c to v(c), where
+ * \f$c \in [cmin,cmax]\f$ and \f$v(cmin):=vmin, v(c):=vmin+c-cmin\f$.
+ * @param[in] cmin smallest number in the range
+ * @param[in] cmax largest number in the range
+ * @param[in] vmin map value of cmin */
+void RangeMap::addRange (uint32_t cmin, uint32_t cmax, uint32_t vmin) {
+ if (cmin > cmax)
+ swap(cmin, cmax);
+
+ Range range(cmin, cmax, vmin);
+ if (_ranges.empty())
+ _ranges.emplace_back(std::move(range));
+ else {
+ // check for simple cases that can be handled pretty fast
+ Range &lrange = *_ranges.begin();
+ Range &rrange = *_ranges.rbegin();
+ if (cmin > rrange.max()) { // non-overlapping range at end of vector?
+ if (!rrange.join(range))
+ _ranges.emplace_back(std::move(range));
+ }
+ else if (cmax < lrange.min()) { // non-overlapping range at begin of vector?
+ if (!lrange.join(range))
+ _ranges.emplace(_ranges.begin(), std::move(range));
+ }
+ else {
+ // ranges overlap and/or must be inserted somewhere inside the vector
+ auto it = lower_bound(_ranges.begin(), _ranges.end(), range);
+ const bool at_end = (it == _ranges.end());
+ if (at_end)
+ --it;
+ if (!it->join(range) && (it == _ranges.begin() || !(it-1)->join(range))) {
+ if (it->min() < cmin && it->max() > cmax) { // new range completely inside an existing range?
+ //split existing range
+ uint32_t itmax = it->max();
+ it->max(cmin-1);
+ it = _ranges.emplace(it+1, Range(cmax+1, itmax, it->valueAt(cmax+1)));
+ }
+ else if (at_end) // does new range overlap right side of last range in vector?
+ it = _ranges.end(); // => append new range at end of vector
+ it = _ranges.emplace(it, std::move(range));
+ }
+ adaptNeighbors(it); // resolve overlaps
+ }
+ }
+}
+
+
+/** Adapts the left and right neighbor elements of a newly inserted range.
+ * The new range could overlap ranges in the neighborhood so that those must be
+ * adapted or removed. All ranges in the range vector are ordered ascendingly, i.e.
+ * [min_1, max_1],...,[min_n, max_n] where min_i < min_j for all i < j.
+ * @param[in] it pointer to the newly inserted range */
+void RangeMap::adaptNeighbors (Ranges::iterator it) {
+ if (it != _ranges.end()) {
+ // adapt left neighbor
+ if (it != _ranges.begin() && it->min() <= (it-1)->max()) {
+ auto lit = it-1; // points to left neighbor
+ bool left_neighbor_valid = (it->min() > 0 && it->min()-1 >= lit->min());
+ if (left_neighbor_valid) // is adapted left neighbor valid?
+ lit->max(it->min()-1); // => assign new max value
+ if (!left_neighbor_valid || it->join(*lit))
+ it = _ranges.erase(lit);
+ }
+ // remove right neighbors completely overlapped by *it
+ auto rit = it+1; // points to right neighbor
+ while (rit != _ranges.end() && it->max() >= rit->max()) { // complete overlap?
+ _ranges.erase(rit);
+ rit = it+1;
+ }
+ // adapt rightmost range partially overlapped by *it
+ if (rit != _ranges.end()) {
+ if (it->max() >= rit->min())
+ rit->setMinAndAdaptValue(it->max()+1);
+ // try to merge right neighbor into *this
+ if (it->join(*rit))
+ _ranges.erase(rit); // remove merged neighbor
+ }
+ }
+}
+
+
+/** Finds the index of the range that contains a given value c.
+ * @param[in] c find range that contains this value
+ * @return index of the range found, or -1 if range was not found */
+int RangeMap::lookup (uint32_t c) const {
+ // simple binary search
+ int left=0, right=_ranges.size()-1;
+ while (left <= right) {
+ int mid = (left+right)/2;
+ if (c < _ranges[mid].min())
+ right = mid-1;
+ else if (c > _ranges[mid].max())
+ left = mid+1;
+ else
+ return mid;
+ }
+ return -1;
+}
+
+
+uint32_t RangeMap::valueAt (uint32_t c) const {
+ int pos = lookup(c);
+ return pos < 0 ? 0 : _ranges[pos].valueAt(c);
+}
+
+
+/** Returns the number of values mapped. */
+size_t RangeMap::numValues () const {
+ return std::accumulate(_ranges.begin(), _ranges.end(), 0, [](size_t sum, const Range &range) {
+ return sum+range.max()-range.min()+1;
+ });
+}
+
+
+ostream& RangeMap::Range::write (ostream& os) const {
+ return os << '[' << _min << ',' << _max << "] => " << _minval;
+}
+
+
+ostream& RangeMap::write (ostream& os) const {
+ for (const Range &range : _ranges)
+ range.write(os) << '\n';
+ return os;
+}
diff --git a/dviware/dvisvgm/src/RangeMap.hpp b/dviware/dvisvgm/src/RangeMap.hpp
new file mode 100644
index 0000000000..128d37c038
--- /dev/null
+++ b/dviware/dvisvgm/src/RangeMap.hpp
@@ -0,0 +1,142 @@
+/*************************************************************************
+** RangeMap.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef RANGEMAP_HPP
+#define RANGEMAP_HPP
+
+#include <algorithm>
+#include <ostream>
+#include <vector>
+
+
+class RangeMap {
+ class Range {
+ friend class RangeMap;
+ public:
+ Range () : _min(0), _max(0), _minval(0) {}
+
+ Range (uint32_t min, uint32_t max, uint32_t minval) : _min(min), _max(max), _minval(minval) {
+ if (_min > _max)
+ std::swap(_min, _max);
+ }
+
+ uint32_t min () const {return _min;}
+ uint32_t max () const {return _max;}
+ uint32_t minval () const {return _minval;}
+ uint32_t maxval () const {return valueAt(_max);}
+ uint32_t valueAt (uint32_t c) const {return c-_min+_minval;}
+ uint32_t numKeys () const {return _max-_min+1;}
+ bool operator < (const Range &r) const {return _min < r._min;}
+ std::ostream& write (std::ostream &os) const;
+
+ protected:
+ void min (uint32_t m) {_min = m;}
+ void max (uint32_t m) {_max = m;}
+ void setMinAndAdaptValue (uint32_t c) {_minval = valueAt(c); _min = c;}
+ bool join (const Range &r);
+
+ private:
+ uint32_t _min, _max;
+ uint32_t _minval;
+ };
+
+ using Ranges = std::vector<Range>;
+
+ class Iterator {
+ friend class RangeMap;
+ public:
+ void operator ++ () {
+ if (_currentKey < _rangeIterator->max())
+ ++_currentKey;
+ else {
+ if (++_rangeIterator != _rangeMap._ranges.end())
+ _currentKey = _rangeIterator->min();
+ }
+ }
+
+ void operator -- () {
+ if (_currentKey > _rangeIterator->min())
+ --_currentKey;
+ else {
+ if (_rangeIterator != _rangeMap._ranges.begin())
+ _currentKey = (--_rangeIterator)->max();
+ }
+ }
+
+ std::pair<uint32_t,uint32_t> operator * () const {
+ return std::pair<uint32_t,uint32_t>(_currentKey, _rangeMap.valueAt(_currentKey));
+ }
+
+ bool operator == (const Iterator &it) const {
+ bool ret = (_rangeIterator == it._rangeIterator);
+ if (ret && _rangeIterator != _rangeMap._ranges.end())
+ ret = (_currentKey == it._currentKey);
+ return ret;
+ }
+
+ bool operator != (const Iterator &it) const {
+ bool ret = (_rangeIterator != it._rangeIterator);
+ if (!ret && _rangeIterator != _rangeMap._ranges.end())
+ ret = _currentKey != it._currentKey;
+ return ret;
+ }
+
+ protected:
+ Iterator (const RangeMap &rangeMap, Ranges::const_iterator it) : _rangeMap(rangeMap), _rangeIterator(it), _currentKey() {
+ if (_rangeIterator != _rangeMap._ranges.end())
+ _currentKey = _rangeIterator->min();
+ }
+
+ private:
+ const RangeMap &_rangeMap;
+ Ranges::const_iterator _rangeIterator;
+ uint32_t _currentKey;
+ };
+
+ public:
+ void addRange (uint32_t first, uint32_t last, uint32_t cid);
+ bool valueExists (uint32_t c) const {return lookup(c) >= 0;}
+ uint32_t valueAt (uint32_t c) const;
+ uint32_t minKey () const {return _ranges.empty() ? 0 : _ranges.front().min();}
+ uint32_t maxKey () const {return _ranges.empty() ? 0 : _ranges.back().max();}
+ size_t numRanges () const {return _ranges.size();}
+ size_t numValues () const;
+ bool empty () const {return _ranges.empty();}
+ void clear () {_ranges.clear();}
+ const Range& getRange (size_t n) const {return _ranges[n];}
+ Iterator begin () const {return Iterator(*this, _ranges.begin());}
+ Iterator end () const {return Iterator(*this, _ranges.end());}
+ std::ostream& write (std::ostream &os) const;
+
+ protected:
+ void adaptNeighbors (Ranges::iterator it);
+ int lookup (uint32_t c) const;
+ const Range& rangeAt (size_t n) const {return _ranges[n];}
+
+ private:
+ Ranges _ranges;
+};
+
+
+inline std::ostream& operator << (std::ostream& os, const RangeMap &rangemap) {
+ return rangemap.write(os);
+}
+
+#endif
diff --git a/dviware/dvisvgm/src/SVGCharHandler.cpp b/dviware/dvisvgm/src/SVGCharHandler.cpp
new file mode 100644
index 0000000000..3733053e4d
--- /dev/null
+++ b/dviware/dvisvgm/src/SVGCharHandler.cpp
@@ -0,0 +1,92 @@
+/*************************************************************************
+** SVGCharHandler.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include "SVGCharHandler.hpp"
+#include "utility.hpp"
+#include "XMLNode.hpp"
+
+using namespace std;
+
+
+void SVGCharHandler::setInitialContextNode (XMLElement *node) {
+ resetContextNode();
+ _initialContextNode = node;
+}
+
+
+/** Changes the context element. All following nodes will be appended to this node.
+ * @param[in] node the new context node
+ * @return bare pointer to the new context node or 0 if context hasn't changed */
+XMLElement* SVGCharHandler::pushContextNode (unique_ptr<XMLElement> node) {
+ if (node && (_contextNodeStack.empty() || node.get() != _contextNodeStack.top())) {
+ XMLElement *nodeptr = node.get();
+ contextNode()->append(std::move(node));
+ _contextNodeStack.push(nodeptr);
+ return nodeptr;
+ }
+ return nullptr;
+}
+
+
+void SVGCharHandler::popContextNode () {
+ if (!_contextNodeStack.empty())
+ _contextNodeStack.pop();
+}
+
+
+/** Resets the context node to the initial element node. */
+void SVGCharHandler::resetContextNode () {
+ while (!_contextNodeStack.empty())
+ _contextNodeStack.pop();
+}
+
+
+/** Creates and returns a new SVG text element.
+ * @param[in] x current x coordinate
+ * @param[in] y current y coordinate */
+unique_ptr<XMLElement> SVGCharTextHandler::createTextNode (double x, double y) const {
+ const Font *font = _font.get();
+ if (!font)
+ return nullptr;
+ auto textNode = util::make_unique<XMLElement>("text");
+ if (_selectFontByClass)
+ textNode->addAttribute("class", string("f")+XMLString(_fontnum));
+ else {
+ textNode->addAttribute("font-family", font->name());
+ textNode->addAttribute("font-size", XMLString(font->scaledSize()));
+ if (font->color() != Color::BLACK)
+ textNode->addAttribute("fill", font->color().svgColorString());
+ }
+ if (_vertical) {
+ textNode->addAttribute("writing-mode", "tb");
+ // align glyphs designed for horizontal layout properly
+ if (auto pf = dynamic_cast<const PhysicalFont*>(font)) {
+ if (!pf->getMetrics()->verticalLayout()) { // alphabetic text designed for horizontal layout?
+ x += pf->scaledAscent()/2.5; // move vertical baseline to the right by strikethrough offset
+ textNode->addAttribute("glyph-orientation-vertical", 90); // ensure rotation
+ }
+ }
+ }
+ textNode->addAttribute("x", x);
+ textNode->addAttribute("y", y);
+ if (!_matrix.get().isIdentity())
+ textNode->addAttribute("transform", _matrix.get().toSVG());
+ return textNode;
+}
diff --git a/dviware/dvisvgm/src/SVGCharHandler.hpp b/dviware/dvisvgm/src/SVGCharHandler.hpp
new file mode 100644
index 0000000000..48d24496fa
--- /dev/null
+++ b/dviware/dvisvgm/src/SVGCharHandler.hpp
@@ -0,0 +1,108 @@
+/*************************************************************************
+** SVGCharHandler.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef SVGCHARHANDLER_HPP
+#define SVGCHARHANDLER_HPP
+
+#include <memory>
+#include <stack>
+#include "Color.hpp"
+#include "Font.hpp"
+#include "Matrix.hpp"
+
+
+template <typename T>
+class CharProperty {
+ public:
+ CharProperty (const T &v) : _value(v) {}
+
+ void set (const T &v) {
+ if (v != _value) {
+ _value = v;
+ _changed = true;
+ }
+ }
+
+ const T& get () const {return _value;}
+ operator const T& () const {return _value;}
+ bool changed () const {return _changed;}
+ void changed (bool c) {_changed = c;}
+
+ private:
+ T _value;
+ bool _changed=false;
+};
+
+
+class XMLElement;
+
+
+/** Base class for all character handlers. These handlers create SVG representations
+ * for the added characters and append them to the SVG tree. */
+class SVGCharHandler {
+ public:
+ SVGCharHandler () : _matrix(1) {}
+ virtual ~SVGCharHandler() =default;
+ virtual void setInitialContextNode (XMLElement *node);
+ virtual void appendChar (uint32_t c, double x, double y) =0;
+ virtual void notifyXAdjusted () {}
+ virtual void notifyYAdjusted () {}
+ void setColor (const Color &color) {_color.set(color);}
+ void setFont (const Font &font, int id) {_font.set(&font); _fontnum = id;}
+ void setMatrix (const Matrix &matrix) {_matrix.set(matrix);}
+ void setVertical (bool vertical) {_vertical.set(vertical);}
+ Color getColor () const {return _color.get();}
+ const Font* getFont () const {return _font.get();}
+ const Matrix& getMatrix () const {return _matrix.get();}
+
+ protected:
+ virtual void resetContextNode ();
+ XMLElement* pushContextNode (std::unique_ptr<XMLElement> node);
+ void popContextNode ();
+
+ XMLElement* contextNode () const {
+ return _contextNodeStack.empty() ? _initialContextNode : _contextNodeStack.top();
+ }
+
+ CharProperty<Color> _color=Color::BLACK; ///< current color
+ CharProperty<const Font*> _font=0; ///< current font
+ int _fontnum=0; ///< current font ID
+ CharProperty<Matrix> _matrix; ///< current transformation
+ CharProperty<bool> _vertical=false; ///< current writing mode
+
+ private:
+ XMLElement *_initialContextNode= nullptr; ///< SVG element the generated character nodes are attached to
+ std::stack<XMLElement*> _contextNodeStack;
+};
+
+
+/** Base class for all character handlers that create SVG <text> elements. */
+class SVGCharTextHandler : public SVGCharHandler {
+ public:
+ explicit SVGCharTextHandler (bool selectFontByClass) : _selectFontByClass(selectFontByClass) {}
+
+ protected:
+ std::unique_ptr<XMLElement> createTextNode (double x, double y) const;
+
+ private:
+ bool _selectFontByClass;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/SVGCharHandlerFactory.cpp b/dviware/dvisvgm/src/SVGCharHandlerFactory.cpp
new file mode 100644
index 0000000000..613f7e7d8b
--- /dev/null
+++ b/dviware/dvisvgm/src/SVGCharHandlerFactory.cpp
@@ -0,0 +1,36 @@
+/*************************************************************************
+** SVGCharHandlerFactory.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include "SVGCharHandlerFactory.hpp"
+#include "SVGCharPathHandler.hpp"
+#include "SVGSingleCharTextHandler.hpp"
+#include "SVGCharTspanTextHandler.hpp"
+#include "SVGTree.hpp"
+#include "utility.hpp"
+
+using namespace std;
+
+unique_ptr<SVGCharHandler> SVGCharHandlerFactory::createHandler () {
+ if (!SVGTree::USE_FONTS)
+ return util::make_unique<SVGCharPathHandler>(SVGTree::CREATE_USE_ELEMENTS, SVGTree::RELATIVE_PATH_CMDS);
+ if (SVGTree::MERGE_CHARS)
+ return util::make_unique<SVGCharTspanTextHandler>(SVGTree::CREATE_CSS);
+ return util::make_unique<SVGSingleCharTextHandler>(SVGTree::CREATE_CSS);
+}
diff --git a/dviware/dvisvgm/src/SVGCharHandlerFactory.hpp b/dviware/dvisvgm/src/SVGCharHandlerFactory.hpp
new file mode 100644
index 0000000000..92a08dc196
--- /dev/null
+++ b/dviware/dvisvgm/src/SVGCharHandlerFactory.hpp
@@ -0,0 +1,32 @@
+/*************************************************************************
+** SVGCharHandlerFactory.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef SVGCHARHANDLERFACTORY_HPP
+#define SVGCHARHANDLERFACTORY_HPP
+
+#include <memory>
+#include "SVGCharHandler.hpp"
+
+struct SVGCharHandlerFactory {
+ static std::unique_ptr<SVGCharHandler> createHandler ();
+};
+
+#endif
+
diff --git a/dviware/dvisvgm/src/SVGCharPathHandler.cpp b/dviware/dvisvgm/src/SVGCharPathHandler.cpp
new file mode 100644
index 0000000000..3962168ae2
--- /dev/null
+++ b/dviware/dvisvgm/src/SVGCharPathHandler.cpp
@@ -0,0 +1,136 @@
+/*************************************************************************
+** SVGCharPathHandler.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <sstream>
+#include "Font.hpp"
+#include "FontManager.hpp"
+#include "SVGCharPathHandler.hpp"
+#include "utility.hpp"
+#include "XMLNode.hpp"
+
+using namespace std;
+
+/** Constructs a new path builder.
+ * @param[in] createUseElements determines whether to create "use" elements to reference previous paths or not
+ * @param[in] relativePathCommands determines whether to create relative or absolute SVG path commands */
+SVGCharPathHandler::SVGCharPathHandler (bool createUseElements, bool relativePathCommands)
+ : _relativePathCommands(relativePathCommands)
+{
+ if (createUseElements)
+ _appendChar = &SVGCharPathHandler::appendUseElement;
+ else
+ _appendChar = &SVGCharPathHandler::appendPathElement;
+}
+
+
+void SVGCharPathHandler::resetContextNode () {
+ SVGCharHandler::resetContextNode();
+ _groupNode = nullptr;
+}
+
+
+/** Appends the path representation of a single character to the context element.
+ * @param[in] c code of the character to be appended
+ * @param[in] x horizontal position of the character (in bp units)
+ * @param[in] y vertical position of the character (in bp units) */
+void SVGCharPathHandler::appendChar (uint32_t c, double x, double y) {
+ if (_font.changed()) {
+ _fontColor.set(_font.get()->color());
+ if (_fontColor.changed() && _fontColor.get() != Color::BLACK)
+ _color.changed(true); // ensure application of text color when resetting the font color to black
+ _font.changed(false);
+ }
+ // Apply text color changes only if the color of the entire font is black.
+ // Glyphs of non-black fonts (e.g. defined in a XeTeX document) can't change their color.
+ CharProperty<Color> &color = (_fontColor.get() != Color::BLACK) ? _fontColor : _color;
+ bool applyColor = color.get() != Color::BLACK;
+ bool applyMatrix = !_matrix.get().isIdentity();
+ if (!_groupNode) {
+ color.changed(applyColor);
+ _matrix.changed(applyMatrix);
+ }
+ if (color.changed() || _matrix.changed()) {
+ resetContextNode();
+ if (applyColor || applyMatrix) {
+ _groupNode = pushContextNode(util::make_unique<XMLElement>("g"));
+ if (applyColor)
+ contextNode()->addAttribute("fill", color.get().svgColorString());
+ if (applyMatrix)
+ contextNode()->addAttribute("transform", _matrix.get().toSVG());
+ }
+ color.changed(false);
+ _matrix.changed(false);
+ }
+ const Font *font = _font.get();
+ if (font->verticalLayout()) {
+ // move glyph graphics so that its origin is located at the top center position
+ GlyphMetrics metrics;
+ font->getGlyphMetrics(c, _vertical, metrics);
+ x -= metrics.wl;
+ if (auto pf = dynamic_cast<const PhysicalFont*>(font)) {
+ // Center glyph between top and bottom border of the TFM box.
+ // This is just an approximation used until I find a way to compute
+ // the exact location in vertical mode.
+ GlyphMetrics exact_metrics;
+ pf->getExactGlyphBox(c, exact_metrics, false);
+ y += exact_metrics.h+(metrics.d-exact_metrics.h-exact_metrics.d)/2;
+ }
+ else
+ y += metrics.d;
+ }
+ Matrix rotation(1);
+ if (_vertical && !font->verticalLayout()) {
+ // alphabetic text designed for horizontal mode
+ // must be rotated by 90 degrees if in vertical mode
+ rotation.translate(-x, -y);
+ rotation.rotate(90);
+ rotation.translate(x, y);
+ }
+ (this->*_appendChar)(c, x, y, rotation);
+}
+
+
+void SVGCharPathHandler::appendUseElement (uint32_t c, double x, double y, const Matrix &matrix) {
+ string id = "#g" + to_string(FontManager::instance().fontID(_font)) + "-" + to_string(c);
+ auto useNode = util::make_unique<XMLElement>("use");
+ useNode->addAttribute("x", XMLString(x));
+ useNode->addAttribute("y", XMLString(y));
+ useNode->addAttribute("xlink:href", id);
+ if (!matrix.isIdentity())
+ useNode->addAttribute("transform", matrix.toSVG());
+ contextNode()->append(std::move(useNode));
+}
+
+
+void SVGCharPathHandler::appendPathElement (uint32_t c, double x, double y, const Matrix &matrix) {
+ Glyph glyph;
+ auto pf = dynamic_cast<const PhysicalFont*>(_font.get());
+ if (pf && pf->getGlyph(c, glyph)) {
+ double sx = pf->scaledSize()/pf->unitsPerEm();
+ double sy = -sx;
+ ostringstream oss;
+ glyph.writeSVG(oss, _relativePathCommands, sx, sy, x, y);
+ auto glyphNode = util::make_unique<XMLElement>("path");
+ glyphNode->addAttribute("d", oss.str());
+ if (!matrix.isIdentity())
+ glyphNode->addAttribute("transform", matrix.toSVG());
+ contextNode()->append(std::move(glyphNode));
+ }
+}
diff --git a/dviware/dvisvgm/src/SVGCharPathHandler.hpp b/dviware/dvisvgm/src/SVGCharPathHandler.hpp
new file mode 100644
index 0000000000..ba172de340
--- /dev/null
+++ b/dviware/dvisvgm/src/SVGCharPathHandler.hpp
@@ -0,0 +1,46 @@
+/*************************************************************************
+** SVGCharPathHandler.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef SVGCHARPATHHANDLER_HPP
+#define SVGCHARPATHHANDLER_HPP
+
+#include "SVGCharHandler.hpp"
+
+/** Creates SVG path elements for all characters. */
+class SVGCharPathHandler : public SVGCharHandler {
+ using AppendMethod = void (SVGCharPathHandler::*)(uint32_t c, double x, double y, const Matrix &matrix);
+
+ public:
+ SVGCharPathHandler (bool createUseElements, bool relativePathsCommands);
+ void appendChar (uint32_t c, double x, double y) override;
+
+ protected:
+ void appendUseElement (uint32_t c, double x, double y, const Matrix &matrix);
+ void appendPathElement (uint32_t c, double x, double y, const Matrix &matrix);
+ void resetContextNode () override;
+
+ private:
+ AppendMethod _appendChar; ///< method called to append a single character
+ bool _relativePathCommands; ///< if true, create relative rather than absolute SVG path commands
+ XMLElement *_groupNode=nullptr; ///< current group node taking the path elements
+ CharProperty<Color> _fontColor=Color::BLACK; ///< color of current font
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/SVGCharTspanTextHandler.cpp b/dviware/dvisvgm/src/SVGCharTspanTextHandler.cpp
new file mode 100644
index 0000000000..a48f5f9a93
--- /dev/null
+++ b/dviware/dvisvgm/src/SVGCharTspanTextHandler.cpp
@@ -0,0 +1,87 @@
+/*************************************************************************
+** SVGCharTspanTextHandler.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include "SVGCharTspanTextHandler.hpp"
+#include "utility.hpp"
+#include "XMLNode.hpp"
+
+using namespace std;
+
+SVGCharTspanTextHandler::SVGCharTspanTextHandler (bool selectFontByClass)
+ : SVGCharTextHandler(selectFontByClass)
+{
+ resetContextNode();
+}
+
+
+void SVGCharTspanTextHandler::appendChar (uint32_t c, double x, double y) {
+ // changes of fonts and transformations require a new text element
+ if (!_textNode || _font.changed() || _matrix.changed() || _vertical.changed()) {
+ resetContextNode();
+ _textNode = pushContextNode(createTextNode(x, y));
+ _color.changed(true); // force creating tspan with color attribute if current color differs from font color
+ }
+ if (_tspanNode && (_xchanged || _ychanged || _color.changed())) {
+ // if drawing position or color was explicitly changed, finish current tspan element
+ popContextNode();
+ _tspanNode = nullptr;
+ }
+ // Apply text color changes only if the color of the entire font is black.
+ // Glyphs of non-black fonts (e.g. defined in a XeTeX document) can't change their color.
+ bool applyColor = _color.get() != Color::BLACK && _font.get()->color() == Color::BLACK;
+ if (_xchanged || _ychanged || (_color.changed() && applyColor)) {
+ _tspanNode = pushContextNode(util::make_unique<XMLElement>("tspan"));
+ if (applyColor)
+ _tspanNode->addAttribute("fill", _color.get().svgColorString());
+ _color.changed(false);
+ if (_xchanged) {
+ if (_vertical) {
+ // align glyphs designed for horizontal layout properly
+ if (auto pf = dynamic_cast<const PhysicalFont*>(_font.get()))
+ if (!pf->getMetrics()->verticalLayout())
+ x += pf->scaledAscent()/2.5; // move vertical baseline to the right by strikethrough offset
+ }
+ _tspanNode->addAttribute("x", x);
+ _xchanged = false;
+ }
+ if (_ychanged) {
+ _tspanNode->addAttribute("y", y);
+ _ychanged = false;
+ }
+ }
+ contextNode()->append(XMLString(_font.get()->unicode(c), false));
+}
+
+
+void SVGCharTspanTextHandler::setInitialContextNode (XMLElement *node) {
+ SVGCharHandler::setInitialContextNode(node);
+ _textNode = _tspanNode = nullptr;
+ _xchanged = _ychanged = false;
+}
+
+
+void SVGCharTspanTextHandler::resetContextNode () {
+ SVGCharHandler::resetContextNode();
+ _textNode = _tspanNode = nullptr;
+ _xchanged = _ychanged = false;
+ _font.changed(false);
+ _matrix.changed(false);
+ _vertical.changed(false);
+}
diff --git a/dviware/dvisvgm/src/SVGCharTspanTextHandler.hpp b/dviware/dvisvgm/src/SVGCharTspanTextHandler.hpp
new file mode 100644
index 0000000000..01567997b6
--- /dev/null
+++ b/dviware/dvisvgm/src/SVGCharTspanTextHandler.hpp
@@ -0,0 +1,43 @@
+/*************************************************************************
+** SVGCharTspanTextHandler.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef SVGCHARTSPANTEXTHANDLER_HPP
+#define SVGCHARTSPANTEXTHANDLER_HPP
+
+#include "SVGCharHandler.hpp"
+
+class SVGCharTspanTextHandler : public SVGCharTextHandler {
+ public:
+ explicit SVGCharTspanTextHandler (bool selectFontByClass);
+ void notifyXAdjusted () override {_xchanged = true;}
+ void notifyYAdjusted() override {_ychanged = true;}
+ void appendChar (uint32_t c, double x, double y) override;
+ void setInitialContextNode (XMLElement *node) override;
+
+ protected:
+ void resetContextNode () override;
+
+ private:
+ bool _xchanged, _ychanged;
+ XMLElement *_textNode;
+ XMLElement *_tspanNode;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/SVGOutput.cpp b/dviware/dvisvgm/src/SVGOutput.cpp
new file mode 100644
index 0000000000..469e5f5af2
--- /dev/null
+++ b/dviware/dvisvgm/src/SVGOutput.cpp
@@ -0,0 +1,197 @@
+/*************************************************************************
+** SVGOutput.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <cmath>
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+#include "Calculator.hpp"
+#include "FileSystem.hpp"
+#include "Message.hpp"
+#include "SVGOutput.hpp"
+#include "utility.hpp"
+#include "ZLibOutputStream.hpp"
+
+using namespace std;
+
+
+SVGOutput::SVGOutput (const string &base, string pattern, int zipLevel)
+ : _path(base), _pattern(std::move(pattern)), _stdout(base.empty()), _zipLevel(zipLevel), _page(-1)
+{
+}
+
+
+/** Returns an output stream for the given page.
+ * @param[in] page number of current page
+ * @param[in] numPages total number of pages in the DVI file
+ * @param[in] hash hash value of the current page
+ * @return output stream for the given page */
+ostream& SVGOutput::getPageStream (int page, int numPages, const HashTriple &hashes) const {
+ string fname = filename(page, numPages, hashes);
+ if (fname.empty()) {
+ if (_zipLevel == 0) {
+ _osptr.reset();
+ return cout;
+ }
+#ifdef _WIN32
+ if (_setmode(_fileno(stdout), _O_BINARY) == -1)
+ throw MessageException("can't open stdout in binary mode");
+#endif
+ return *(_osptr = util::make_unique<ZLibOutputStream>(cout, ZLIB_GZIP, _zipLevel));
+ }
+ if (page == _page)
+ return *_osptr;
+
+ _page = page;
+ if (_zipLevel > 0)
+ _osptr = util::make_unique<ZLibOutputFileStream>(fname, ZLIB_GZIP, _zipLevel);
+ else
+ _osptr = util::make_unique<ofstream>(fname);
+ if (!_osptr)
+ throw MessageException("can't open file "+fname+" for writing");
+ return *_osptr;
+}
+
+
+/** Returns the name of the SVG file containing the given page.
+ * @param[in] page number of current page
+ * @param[in] numPages total number of pages
+ * @param[in] hash hash value of current page */
+string SVGOutput::filename (int page, int numPages, const HashTriple &hashes) const {
+ if (_stdout)
+ return "";
+
+ string expanded_pattern = util::trim(expandFormatString(_pattern, page, numPages, hashes));
+ // set and expand default pattern if necessary
+ if (expanded_pattern.empty()) {
+ string pattern = hashes.empty() ? (numPages > 1 ? "%f-%p" : "%f") : "%f-%hd";
+ expanded_pattern = expandFormatString(pattern, page, numPages, hashes);
+ }
+ // append suffix if necessary
+ FilePath outpath(expanded_pattern, true);
+ if (outpath.suffix().empty())
+ outpath.suffix(_zipLevel > 0 ? "svgz" : "svg");
+ string abspath = outpath.absolute();
+ string relpath = outpath.relative();
+ return abspath.length() < relpath.length() ? abspath : relpath;
+}
+
+
+#if 0
+string SVGOutput::outpath (int page, int numPages) const {
+ string path = filename(page, numPages);
+ if (path.empty())
+ return "";
+ size_t pos = path.rfind('/');
+ if (pos == string::npos)
+ return ".";
+ if (pos == 0)
+ return "/";
+ return path.substr(0, pos);
+}
+#endif
+
+
+/** Replaces expressions in a given string by the corresponding values and returns the result.
+ * Supported constructs:
+ * %f: basename of the current file (filename without suffix)
+ * %h: hash value of current page
+ * %[0-9]?p: current page number
+ * %[0-9]?P: number of pages in DVI file
+ * %[0-9]?(expr): arithmetic expression
+ * @param[in] str string to expand
+ * @param[in] page number of current page
+ * @param[in] numPages total number of pages
+ * @param[in] hash hash value of current page (skipped if empty) */
+string SVGOutput::expandFormatString (string str, int page, int numPages, const HashTriple &hashes) const {
+ string result;
+ while (!str.empty()) {
+ size_t pos = str.find('%');
+ if (pos == string::npos) {
+ result += str;
+ str.clear();
+ }
+ else {
+ result += str.substr(0, pos);
+ str = str.substr(pos);
+ pos = 1;
+ ostringstream oss;
+ if (!isdigit(str[pos]))
+ oss << setw(util::ilog10(numPages)+1) << setfill('0');
+ else {
+ oss << setw(str[pos]-'0') << setfill('0');
+ pos++;
+ }
+ switch (str[pos]) {
+ case 'f':
+ result += _path.basename();
+ break;
+ case 'h': {
+ char variant = pos+1 < str.length() ? str[++pos] : '\0';
+ switch (variant) {
+ case 'd': result += hashes.dviHash(); break;
+ case 'c': result += hashes.cmbHash(); break;
+ case 'o': result += hashes.optHash(); break;
+ default:
+ throw MessageException("hash type 'd', 'c', or 'o' expected after '%h' in filename pattern");
+ }
+ break;
+ }
+ case 'p':
+ case 'P':
+ oss << (str[pos] == 'p' ? page : numPages);
+ result += oss.str();
+ break;
+ case '(': {
+ size_t endpos = str.find(')', pos);
+ if (endpos == string::npos)
+ throw MessageException("missing ')' in filename pattern");
+ else if (endpos-pos-1 > 1) {
+ try {
+ Calculator calculator;
+ calculator.setVariable("p", page);
+ calculator.setVariable("P", numPages);
+ oss << floor(calculator.eval(str.substr(pos, endpos-pos+1)));
+ result += oss.str();
+ }
+ catch (CalculatorException &e) {
+ oss.str("");
+ oss << "error in filename pattern (" << e.what() << ")";
+ throw MessageException(oss.str());
+ }
+ pos = endpos;
+ }
+ break;
+ }
+ }
+ str = str.substr(pos+1);
+ }
+ }
+ return result;
+}
+
+
+/** Returns true if methods 'filename' and 'getPageStream' ignore the hash
+ * parameter because it's not requested in the filename pattern. */
+bool SVGOutput::ignoresHashes () const {
+ return _stdout || (!_pattern.empty() && _pattern.find("%h") == string::npos);
+}
diff --git a/dviware/dvisvgm/src/SVGOutput.hpp b/dviware/dvisvgm/src/SVGOutput.hpp
new file mode 100644
index 0000000000..38193dbdc8
--- /dev/null
+++ b/dviware/dvisvgm/src/SVGOutput.hpp
@@ -0,0 +1,76 @@
+/*************************************************************************
+** SVGOutput.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef SVGOUTPUT_HPP
+#define SVGOUTPUT_HPP
+
+#include <memory>
+#include <ostream>
+#include <string>
+#include "FilePath.hpp"
+
+
+struct SVGOutputBase {
+ class HashTriple {
+ public:
+ HashTriple () =default;
+ HashTriple (std::string dviHash, std::string optHash, std::string cmbHash)
+ : _dviHash(std::move(dviHash)), _optHash(std::move(optHash)), _cmbHash(std::move(cmbHash)) {}
+ std::string dviHash () const {return _dviHash;}
+ std::string optHash () const {return _optHash;}
+ std::string cmbHash () const {return _cmbHash;}
+ bool empty () const {return _dviHash.empty();}
+
+ private:
+ std::string _dviHash; ///< hash of plain DVI data
+ std::string _optHash; ///< hash of options affecting the SVG document
+ std::string _cmbHash; ///< combined hash of DVI data and options
+ };
+
+ virtual ~SVGOutputBase () =default;
+ virtual std::ostream& getPageStream (int page, int numPages, const HashTriple &hashes=HashTriple()) const =0;
+ virtual std::string filename (int page, int numPages, const HashTriple &hashes=HashTriple()) const =0;
+ virtual bool ignoresHashes () const {return true;}
+};
+
+
+class SVGOutput : public SVGOutputBase {
+ public:
+ SVGOutput () : SVGOutput("", "", 0) {}
+ explicit SVGOutput (const std::string &base) : SVGOutput(base, "", 0) {}
+ SVGOutput (const std::string &base, const std::string &pattern) : SVGOutput(base, pattern, 0) {}
+ SVGOutput (const std::string &base, std::string pattern, int zipLevel);
+ std::ostream& getPageStream (int page, int numPages, const HashTriple &hash=HashTriple()) const override;
+ std::string filename (int page, int numPages, const HashTriple &hash=HashTriple()) const override;
+ bool ignoresHashes () const override;
+
+ protected:
+ std::string expandFormatString (std::string str, int page, int numPages, const HashTriple &hashes) const;
+
+ private:
+ FilePath _path;
+ std::string _pattern;
+ bool _stdout; ///< write to STDOUT?
+ int _zipLevel; ///< compression level
+ mutable int _page; ///< number of current page being written
+ mutable std::unique_ptr<std::ostream> _osptr;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/SVGSingleCharTextHandler.cpp b/dviware/dvisvgm/src/SVGSingleCharTextHandler.cpp
new file mode 100644
index 0000000000..0d36b5a8b8
--- /dev/null
+++ b/dviware/dvisvgm/src/SVGSingleCharTextHandler.cpp
@@ -0,0 +1,37 @@
+/*************************************************************************
+** SVGSingleCharTextHandler.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include "SVGSingleCharTextHandler.hpp"
+#include "XMLNode.hpp"
+
+using namespace std;
+
+void SVGSingleCharTextHandler::appendChar (uint32_t c, double x, double y) {
+ const Font *font = _font.get();
+ auto textNode = createTextNode(x, y);
+ textNode->append(XMLString(font->unicode(c), false));
+ // Apply color changes only if the color differs from black and if the font color itself is black.
+ // Glyphs from non-black fonts (e.g. defined in a XeTeX document) can't change their color.
+ if (_color.get() != Color::BLACK && font->color() == Color::BLACK) {
+ textNode->addAttribute("fill", _color.get().svgColorString());
+ _color.changed(false);
+ }
+ contextNode()->append(std::move(textNode));
+}
diff --git a/dviware/dvisvgm/src/SVGSingleCharTextHandler.hpp b/dviware/dvisvgm/src/SVGSingleCharTextHandler.hpp
new file mode 100644
index 0000000000..7fd2174f12
--- /dev/null
+++ b/dviware/dvisvgm/src/SVGSingleCharTextHandler.hpp
@@ -0,0 +1,34 @@
+/*************************************************************************
+** SVGSingleCharTextHandler.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef SVGSINGLECHARTEXTHANDLER_HPP
+#define SVGSINGLECHARTEXTHANDLER_HPP
+
+#include "SVGCharHandler.hpp"
+
+
+class SVGSingleCharTextHandler : public SVGCharTextHandler {
+ public:
+ explicit SVGSingleCharTextHandler (bool createStyleElements) : SVGCharTextHandler(createStyleElements) {}
+ void appendChar (uint32_t c, double x, double y) override;
+};
+
+#endif
+
diff --git a/dviware/dvisvgm/src/SVGTree.cpp b/dviware/dvisvgm/src/SVGTree.cpp
new file mode 100644
index 0000000000..57b5fd5a4f
--- /dev/null
+++ b/dviware/dvisvgm/src/SVGTree.cpp
@@ -0,0 +1,346 @@
+/*************************************************************************
+** SVGTree.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <array>
+#include <cstring>
+#include <sstream>
+#include "BoundingBox.hpp"
+#include "DVIToSVG.hpp"
+#include "FileSystem.hpp"
+#include "Font.hpp"
+#include "FontManager.hpp"
+#include "FontWriter.hpp"
+#include "SVGCharHandlerFactory.hpp"
+#include "SVGTree.hpp"
+#include "XMLDocument.hpp"
+#include "XMLNode.hpp"
+#include "XMLString.hpp"
+
+using namespace std;
+
+// static class variables
+bool SVGTree::CREATE_CSS=true;
+bool SVGTree::USE_FONTS=true;
+FontWriter::FontFormat SVGTree::FONT_FORMAT = FontWriter::FontFormat::SVG;
+bool SVGTree::CREATE_USE_ELEMENTS=false;
+bool SVGTree::RELATIVE_PATH_CMDS=false;
+bool SVGTree::MERGE_CHARS=true;
+bool SVGTree::ADD_COMMENTS=false;
+double SVGTree::ZOOM_FACTOR=1.0;
+
+
+SVGTree::SVGTree () : _charHandler(SVGCharHandlerFactory::createHandler()) {
+ reset();
+}
+
+
+/** Clears the SVG tree and initializes the root element. */
+void SVGTree::reset () {
+ _doc.clear();
+ auto rootNode = util::make_unique<XMLElement>("svg");
+ rootNode->addAttribute("version", "1.1");
+ rootNode->addAttribute("xmlns", "http://www.w3.org/2000/svg");
+ rootNode->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
+ _root = rootNode.get();
+ _doc.setRootNode(std::move(rootNode));
+ _page = _defs = nullptr;
+ _styleCDataNode = nullptr;
+}
+
+
+/** Sets the bounding box of the document.
+ * @param[in] bbox bounding box in PS point units */
+void SVGTree::setBBox (const BoundingBox &bbox) {
+ if (ZOOM_FACTOR >= 0) {
+ _root->addAttribute("width", XMLString(bbox.width()*ZOOM_FACTOR)+"pt");
+ _root->addAttribute("height", XMLString(bbox.height()*ZOOM_FACTOR)+"pt");
+ }
+ _root->addAttribute("viewBox", bbox.toSVGViewBox());
+}
+
+
+void SVGTree::setColor (const Color &c) {
+ const Font *font = _charHandler->getFont();
+ if (!font || font->color() == Color::BLACK)
+ _charHandler->setColor(c);
+}
+
+
+void SVGTree::setFont (int num, const Font &font) {
+ _charHandler->setFont(font, num);
+ // set default color assigned to the font
+ if (font.color() != Color::BLACK && getColor() != font.color())
+ setColor(font.color());
+}
+
+
+bool SVGTree::setFontFormat (string formatstr) {
+ size_t pos = formatstr.find(',');
+ string opt;
+ if (pos != string::npos) {
+ opt = formatstr.substr(pos+1);
+ formatstr = formatstr.substr(0, pos);
+ }
+ FontWriter::FontFormat format = FontWriter::toFontFormat(formatstr);
+ if (format == FontWriter::FontFormat::UNKNOWN)
+ return false;
+ FONT_FORMAT = format;
+ FontWriter::AUTOHINT_FONTS = (opt == "autohint" || opt == "ah");
+ return true;
+}
+
+
+/** Starts a new page.
+ * @param[in] pageno number of new page */
+void SVGTree::newPage (int pageno) {
+ auto pageNode = util::make_unique<XMLElement>("g");
+ if (pageno >= 0)
+ pageNode->addAttribute("id", string("page")+XMLString(pageno));
+ _charHandler->setInitialContextNode(pageNode.get());
+ _page = pageNode.get();
+ _root->append(std::move(pageNode));
+ _defsContextStack = stack<XMLElement*>();
+ _pageContextStack = stack<XMLElement*>();
+}
+
+
+void SVGTree::appendToDefs (unique_ptr<XMLNode> node) {
+ if (!_defs) {
+ auto defsNode = util::make_unique<XMLElement>("defs");
+ _defs = defsNode.get();
+ _root->prepend(std::move(defsNode));
+ }
+ XMLElement *parent = _defsContextStack.empty() ? _defs : _defsContextStack.top();
+ parent->append(std::move(node));
+}
+
+
+void SVGTree::appendToPage (unique_ptr<XMLNode> node) {
+ XMLElement *parent = _pageContextStack.empty() ? _page : _pageContextStack.top();
+ parent->append(std::move(node));
+ _charHandler->setInitialContextNode(parent);
+}
+
+
+void SVGTree::prependToPage (unique_ptr<XMLNode> node) {
+ if (_pageContextStack.empty())
+ _page->prepend(std::move(node));
+ else
+ _pageContextStack.top()->prepend(std::move(node));
+}
+
+
+void SVGTree::transformPage (const Matrix &usermatrix) {
+ if (!usermatrix.isIdentity())
+ _page->addAttribute("transform", usermatrix.toSVG());
+}
+
+
+/** Creates an SVG element for a single glyph.
+ * @param[in] c character number
+ * @param[in] font font to extract the glyph from
+ * @param[in] cb pointer to callback object for sending feedback to the glyph tracer (may be 0)
+ * @return pointer to element node if glyph exists, 0 otherwise */
+static unique_ptr<XMLElement> createGlyphNode (int c, const PhysicalFont &font, GFGlyphTracer::Callback *cb) {
+ Glyph glyph;
+ if (!font.getGlyph(c, glyph, cb) || (!SVGTree::USE_FONTS && !SVGTree::CREATE_USE_ELEMENTS))
+ return nullptr;
+
+ double sx=1.0, sy=1.0;
+ double upem = font.unitsPerEm();
+ unique_ptr<XMLElement> glyphNode;
+ if (SVGTree::USE_FONTS) {
+ double extend = font.style() ? font.style()->extend : 1;
+ glyphNode = util::make_unique<XMLElement>("glyph");
+ glyphNode->addAttribute("unicode", XMLString(font.unicode(c), false));
+ glyphNode->addAttribute("horiz-adv-x", XMLString(font.hAdvance(c)*extend));
+ glyphNode->addAttribute("vert-adv-y", XMLString(font.vAdvance(c)));
+ string name = font.glyphName(c);
+ if (!name.empty())
+ glyphNode->addAttribute("glyph-name", name);
+ }
+ else {
+ glyphNode = util::make_unique<XMLElement>("path");
+ glyphNode->addAttribute("id", "g"+to_string(FontManager::instance().fontID(&font))+"-"+to_string(c));
+ sx = font.scaledSize()/upem;
+ sy = -sx;
+ }
+ ostringstream oss;
+ glyph.writeSVG(oss, SVGTree::RELATIVE_PATH_CMDS, sx, sy);
+ glyphNode->addAttribute("d", oss.str());
+ return glyphNode;
+}
+
+
+static string font_info (const Font &font) {
+ ostringstream oss;
+ if (auto nf = dynamic_cast<const NativeFont*>(&font)) {
+ oss << nf->familyName() << ' ' << nf->styleName() << "; " << nf->filename();
+ if (nf->style()) {
+ if (nf->style()->bold != 0)
+ oss << ", bold:" << XMLString(nf->style()->bold) << "pt";
+ if (nf->style()->extend != 1)
+ oss << ", extent:" << XMLString(nf->style()->extend);
+ if (nf->style()->slant != 0)
+ oss << ", slant:" << XMLString(nf->style()->slant);
+ }
+ }
+ return oss.str();
+}
+
+
+void SVGTree::appendFontStyles (const unordered_set<const Font*> &fonts) {
+ if (CREATE_CSS && USE_FONTS && !fonts.empty() && _page) {
+ map<int, const Font*> sortmap;
+ for (const Font *font : fonts)
+ if (!dynamic_cast<const VirtualFont*>(font)) // skip virtual fonts
+ sortmap[FontManager::instance().fontID(font)] = font;
+ ostringstream style;
+ // add font style definitions in ascending order
+ for (auto &idfontpair : sortmap) {
+ if (CREATE_CSS) {
+ style << "text.f" << idfontpair.first << ' '
+ << "{font-family:" << idfontpair.second->name()
+ << ";font-size:" << XMLString(idfontpair.second->scaledSize()) << "px";
+ if (idfontpair.second->color() != Color::BLACK)
+ style << ";fill:" << idfontpair.second->color().svgColorString();
+ style << '}';
+ if (ADD_COMMENTS) {
+ string info = font_info(*idfontpair.second);
+ if (!info.empty())
+ style << " /* " << info << " */";
+ }
+ style << '\n';
+ }
+ }
+ styleCDataNode()->append(style.str());
+ }
+}
+
+
+/** Appends glyph definitions of a given font to the defs section of the SVG tree.
+ * @param[in] font font to be appended
+ * @param[in] chars codes of the characters whose glyph outlines should be appended
+ * @param[in] callback pointer to callback object for sending feedback to the glyph tracer (may be 0) */
+void SVGTree::append (const PhysicalFont &font, const set<int> &chars, GFGlyphTracer::Callback *callback) {
+ if (chars.empty())
+ return;
+
+ if (USE_FONTS) {
+ if (FONT_FORMAT != FontWriter::FontFormat::SVG) {
+ ostringstream style;
+ FontWriter fontWriter(font);
+ if (fontWriter.writeCSSFontFace(FONT_FORMAT, chars, style, callback))
+ styleCDataNode()->append(style.str());
+ }
+ else {
+ if (ADD_COMMENTS) {
+ string info = font_info(font);
+ if (!info.empty())
+ appendToDefs(util::make_unique<XMLComment>(string(" font: ")+info+" "));
+ }
+ auto fontNode = util::make_unique<XMLElement>("font");
+ string fontname = font.name();
+ fontNode->addAttribute("id", fontname);
+ fontNode->addAttribute("horiz-adv-x", XMLString(font.hAdvance()));
+
+ auto faceNode = util::make_unique<XMLElement>("font-face");
+ faceNode->addAttribute("font-family", fontname);
+ faceNode->addAttribute("units-per-em", XMLString(font.unitsPerEm()));
+ if (!font.verticalLayout()) {
+ faceNode->addAttribute("ascent", XMLString(font.ascent()));
+ faceNode->addAttribute("descent", XMLString(font.descent()));
+ }
+ fontNode->append(std::move(faceNode));
+ for (int c : chars)
+ fontNode->append(createGlyphNode(c, font, callback));
+ appendToDefs(std::move(fontNode));
+ }
+ }
+ else if (CREATE_USE_ELEMENTS && &font != font.uniqueFont()) {
+ // If the same character is used in various sizes, we don't want to embed the complete (lengthy) path
+ // descriptions multiple times. Because they would only differ by a scale factor, it's better to
+ // reference the already embedded path together with a transformation attribute and let the SVG renderer
+ // scale the glyphs properly. This is only necessary if we don't want to use font but path elements.
+ for (int c : chars) {
+ auto useNode = util::make_unique<XMLElement>("use");
+ useNode->addAttribute("id", "g"+to_string(FontManager::instance().fontID(&font))+"-"+to_string(c));
+ useNode->addAttribute("xlink:href", "#g"+to_string(FontManager::instance().fontID(font.uniqueFont()))+"-"+to_string(c));
+ double scale = font.scaledSize()/font.uniqueFont()->scaledSize();
+ if (scale != 1.0)
+ useNode->addAttribute("transform", "scale("+XMLString(scale)+")");
+ appendToDefs(std::move(useNode));
+ }
+ }
+ else {
+ for (int c : chars)
+ appendToDefs(createGlyphNode(c, font, callback));
+ }
+}
+
+
+void SVGTree::pushDefsContext (unique_ptr<XMLElement> node) {
+ XMLElement *nodePtr = node.get();
+ if (_defsContextStack.empty())
+ appendToDefs(std::move(node));
+ else
+ _defsContextStack.top()->append(std::move(node));
+ _defsContextStack.push(nodePtr);
+}
+
+
+void SVGTree::popDefsContext () {
+ if (!_defsContextStack.empty())
+ _defsContextStack.pop();
+}
+
+
+/** Pushes a new context element that will take all following nodes added to the page. */
+void SVGTree::pushPageContext (unique_ptr<XMLElement> node) {
+ XMLElement *nodePtr = node.get();
+ if (_pageContextStack.empty())
+ _page->append(std::move(node));
+ else
+ _pageContextStack.top()->append(std::move(node));
+ _pageContextStack.push(nodePtr);
+ _charHandler->setInitialContextNode(nodePtr);
+}
+
+
+/** Pops the current context element and restored the previous one. */
+void SVGTree::popPageContext () {
+ if (!_pageContextStack.empty())
+ _pageContextStack.pop();
+ _charHandler->setInitialContextNode(_page);
+}
+
+
+XMLCData* SVGTree::styleCDataNode () {
+ if (!_styleCDataNode) {
+ auto styleNode = util::make_unique<XMLElement>("style");
+ styleNode->addAttribute("type", "text/css");
+ auto cdataNode = util::make_unique<XMLCData>();
+ _styleCDataNode = cdataNode.get();
+ styleNode->append(std::move(cdataNode));
+ _root->insertBefore(std::move(styleNode), _page);
+ }
+ return _styleCDataNode;
+}
diff --git a/dviware/dvisvgm/src/SVGTree.hpp b/dviware/dvisvgm/src/SVGTree.hpp
new file mode 100644
index 0000000000..d351ec9799
--- /dev/null
+++ b/dviware/dvisvgm/src/SVGTree.hpp
@@ -0,0 +1,98 @@
+/*************************************************************************
+** SVGTree.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef SVGTREE_HPP
+#define SVGTREE_HPP
+
+#include <map>
+#include <memory>
+#include <set>
+#include <stack>
+#include <unordered_set>
+#include "Color.hpp"
+#include "FontWriter.hpp"
+#include "GFGlyphTracer.hpp"
+#include "Matrix.hpp"
+#include "SVGCharHandler.hpp"
+#include "XMLDocument.hpp"
+#include "XMLNode.hpp"
+
+class BoundingBox;
+class Color;
+class Font;
+class Matrix;
+class PhysicalFont;
+
+class SVGTree {
+ public:
+ SVGTree ();
+ void reset ();
+ bool write (std::ostream &os) const {return bool(_doc.write(os));}
+ void newPage (int pageno);
+ void appendToDefs (std::unique_ptr<XMLNode> node);
+ void appendToPage (std::unique_ptr<XMLNode> node);
+ void prependToPage (std::unique_ptr<XMLNode> node);
+ void appendToDoc (std::unique_ptr<XMLNode> node) {_doc.append(std::move(node));}
+ void appendToRoot (std::unique_ptr<XMLNode> node) {_root->append(std::move(node));}
+ void appendChar (int c, double x, double y) {_charHandler->appendChar(c, x, y);}
+ void appendFontStyles (const std::unordered_set<const Font*> &fonts);
+ void append (const PhysicalFont &font, const std::set<int> &chars, GFGlyphTracer::Callback *callback=nullptr);
+ void pushDefsContext (std::unique_ptr<XMLElement> node);
+ void popDefsContext ();
+ void pushPageContext (std::unique_ptr<XMLElement> node);
+ void popPageContext ();
+ void setBBox (const BoundingBox &bbox);
+ void setFont (int id, const Font &font);
+ static bool setFontFormat (std::string formatstr);
+ void setX (double x) {_charHandler->notifyXAdjusted();}
+ void setY (double y) {_charHandler->notifyYAdjusted();}
+ void setMatrix (const Matrix &m) {_charHandler->setMatrix(m);}
+ void setColor (const Color &c);
+ void setVertical (bool state) {_charHandler->setVertical(state);}
+ void transformPage (const Matrix &m);
+ Color getColor () const {return _charHandler->getColor();}
+ const Matrix& getMatrix () const {return _charHandler->getMatrix();}
+ XMLElement* rootNode () const {return _root;}
+ XMLElement* defsNode () const {return _defs;}
+ XMLElement* pageNode () const {return _page;}
+
+ protected:
+ XMLCData* styleCDataNode ();
+
+ public:
+ static bool USE_FONTS; ///< if true, create font references and don't draw paths directly
+ static bool CREATE_CSS; ///< define and use CSS classes to reference fonts?
+ static bool CREATE_USE_ELEMENTS; ///< allow generation of <use/> elements?
+ static FontWriter::FontFormat FONT_FORMAT; ///< format of fonts to be embedded
+ static bool RELATIVE_PATH_CMDS; ///< relative path commands rather than absolute ones?
+ static bool MERGE_CHARS; ///< whether to merge chars with common properties into the same <text> tag
+ static bool ADD_COMMENTS; ///< add comments with additional information
+ static double ZOOM_FACTOR; ///< factor applied to width/height attribute
+
+ private:
+ XMLDocument _doc;
+ XMLElement *_root, *_page, *_defs;
+ XMLCData *_styleCDataNode;
+ std::unique_ptr<SVGCharHandler> _charHandler;
+ std::stack<XMLElement*> _defsContextStack;
+ std::stack<XMLElement*> _pageContextStack;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/ShadingPatch.cpp b/dviware/dvisvgm/src/ShadingPatch.cpp
new file mode 100644
index 0000000000..1ad0011daf
--- /dev/null
+++ b/dviware/dvisvgm/src/ShadingPatch.cpp
@@ -0,0 +1,65 @@
+/*************************************************************************
+** ShadingPatch.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <sstream>
+#include "ShadingPatch.hpp"
+#include "TensorProductPatch.hpp"
+#include "TriangularPatch.hpp"
+#include "utility.hpp"
+
+using namespace std;
+
+/** Get functions to get/set the current color depending on the assigned color space. */
+void ShadingPatch::colorQueryFuncs (ColorGetter &getter, ColorSetter &setter) const {
+ switch (_colorspace) {
+ case Color::ColorSpace::CMYK:
+ getter = &Color::getCMYK;
+ setter = &Color::setCMYK;
+ break;
+ case Color::ColorSpace::LAB:
+ getter = &Color::getLab;
+ setter = &Color::setLab;
+ break;
+ case Color::ColorSpace::RGB:
+ getter = &Color::getRGB;
+ setter = &Color::setRGB;
+ break;
+ case Color::ColorSpace::GRAY:
+ getter = &Color::getGray;
+ setter = &Color::setGray;
+ }
+}
+
+
+/** Factory method: Creates a shading patch object depending on the given PostScript shading type. */
+unique_ptr<ShadingPatch> ShadingPatch::create (int psShadingType, Color::ColorSpace cspace) {
+ switch (psShadingType) {
+ case 4: return util::make_unique<TriangularPatch>(cspace);
+ case 5: return util::make_unique<LatticeTriangularPatch>(cspace);
+ case 6: return util::make_unique<CoonsPatch>(cspace);
+ case 7: return util::make_unique<TensorProductPatch>(cspace);
+ }
+ ostringstream oss;
+ if (psShadingType > 0 && psShadingType < 4)
+ oss << "shading type " << psShadingType << " not supported";
+ else
+ oss << "invalid shading type " << psShadingType;
+ throw ShadingException(oss.str());
+}
diff --git a/dviware/dvisvgm/src/ShadingPatch.hpp b/dviware/dvisvgm/src/ShadingPatch.hpp
new file mode 100644
index 0000000000..a39edc5100
--- /dev/null
+++ b/dviware/dvisvgm/src/ShadingPatch.hpp
@@ -0,0 +1,70 @@
+/*************************************************************************
+** ShadingPatch.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef SHADINGPATCH_HPP
+#define SHADINGPATCH_HPP
+
+#include <memory>
+#include "Color.hpp"
+#include "GraphicsPath.hpp"
+#include "MessageException.hpp"
+
+
+class ShadingPatch {
+ public:
+ struct Callback {
+ virtual ~Callback () =default;
+ virtual void patchSegment (GraphicsPath<double> &path, const Color &color) =0;
+ };
+
+ using PointVec = std::vector<DPair>;
+ using ColorVec = std::vector<Color>;
+
+ public:
+ explicit ShadingPatch (Color::ColorSpace colorSpace) : _colorspace(colorSpace) {}
+ virtual ~ShadingPatch () =default;
+ virtual int psShadingType () const =0;
+ virtual void approximate (int gridsize, bool overlap, double delta, Callback &callback) const =0;
+ virtual void getBBox (BoundingBox &bbox) const =0;
+ virtual void getBoundaryPath (GraphicsPath<double> &path) const =0;
+ virtual void setPoints (const PointVec &points, int edgeflag, ShadingPatch *patch) =0;
+ virtual void setColors (const ColorVec &colors, int edgeflag, ShadingPatch *patch) =0;
+ virtual int numPoints (int edgeflag) const =0;
+ virtual int numColors (int edgeflag) const =0;
+ virtual Color averageColor() const =0;
+ Color::ColorSpace colorSpace () const {return _colorspace;}
+ static std::unique_ptr<ShadingPatch> create (int psShadingType, Color::ColorSpace cspace);
+
+ protected:
+ using ColorGetter = void (Color::*)(std::valarray<double> &va) const;
+ using ColorSetter = void (Color::*)(const std::valarray<double> &va);
+ void colorQueryFuncs (ColorGetter &getter, ColorSetter &setter) const;
+
+ private:
+ Color::ColorSpace _colorspace; ///< color space used to compute the shading values
+};
+
+
+struct ShadingException : public MessageException {
+ explicit ShadingException (const std::string &msg) : MessageException(msg) {}
+};
+
+#endif
+
diff --git a/dviware/dvisvgm/src/SignalHandler.cpp b/dviware/dvisvgm/src/SignalHandler.cpp
new file mode 100644
index 0000000000..886c9281bb
--- /dev/null
+++ b/dviware/dvisvgm/src/SignalHandler.cpp
@@ -0,0 +1,153 @@
+/*************************************************************************
+** SignalHandler.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <config.h>
+#include <cerrno>
+#include <csignal>
+#include <cstdlib>
+#include "SignalHandler.hpp"
+#include "utility.hpp"
+
+using namespace std;
+
+volatile bool SignalHandler::_break = false;
+
+/** Helper class that encapsulates the system-specific parts of
+ * registering and handling CTRL-C (SIGINT) events. */
+class SignalHandler::Impl {
+ using HandlerFunc = void (*)(int);
+ public:
+ Impl ();
+ bool setSigintHandler (HandlerFunc handler);
+ bool restoreSigintHandler ();
+
+ private:
+#ifdef HAVE_SIGACTION
+ struct sigaction _currentSigaction;
+ struct sigaction _origSigaction;
+#else
+ HandlerFunc _origHandlerFunc;
+#endif
+};
+
+
+SignalHandler::SignalHandler ()
+ : _active(false), _impl(util::make_unique<SignalHandler::Impl>())
+{
+}
+
+
+SignalHandler::~SignalHandler () {
+ stop();
+}
+
+
+/** Returns the singleton handler object. */
+SignalHandler& SignalHandler::instance() {
+ static SignalHandler handler;
+ return handler;
+}
+
+
+/** Starts listening to CTRL-C signals.
+ * @return true if handler was activated. */
+bool SignalHandler::start () {
+ if (!_active) {
+ _break = false;
+ if (_impl->setSigintHandler(callback))
+ _active = true;
+ }
+ return _active;
+}
+
+
+/** Stops listening for CTRL-C signals. */
+void SignalHandler::stop () {
+ if (_active) {
+ _impl->restoreSigintHandler();
+ _active = false;
+ }
+}
+
+
+/** Checks for incoming signals and throws an exception if CTRL-C was caught.
+ * @throw SignalException */
+void SignalHandler::check() {
+ if (_break)
+ throw SignalException();
+}
+
+
+void SignalHandler::trigger (bool notify) {
+ _break = true;
+ if (notify)
+ check();
+}
+
+
+/** This function is called on CTRL-C signals. */
+void SignalHandler::callback (int) {
+ _break = true;
+}
+
+
+// Prefer sigaction() to signal() due to its more reliable interface and behavior.
+// Use signal() as fallback on systems that don't provide sigaction().
+
+#ifdef HAVE_SIGACTION
+
+SignalHandler::Impl::Impl () {
+ _origSigaction.sa_handler = nullptr;
+}
+
+
+bool SignalHandler::Impl::setSigintHandler (HandlerFunc handler) {
+ sigemptyset(&_currentSigaction.sa_mask);
+ _currentSigaction.sa_handler = handler;
+ _currentSigaction.sa_flags = SA_SIGINFO;
+ return (sigaction(SIGINT, &_currentSigaction, &_origSigaction) == 0);
+}
+
+
+bool SignalHandler::Impl::restoreSigintHandler () {
+ if (_origSigaction.sa_handler == nullptr)
+ return false;
+ return (sigaction(SIGINT, &_origSigaction, nullptr) == 0);
+}
+
+#else // !HAVE_SIGACTION
+
+SignalHandler::Impl::Impl () : _origHandlerFunc(SIG_ERR) {
+}
+
+
+bool SignalHandler::Impl::setSigintHandler (HandlerFunc handler) {
+ _origHandlerFunc = signal(SIGINT, handler);
+ return _origHandlerFunc != SIG_ERR;
+}
+
+
+bool SignalHandler::Impl::restoreSigintHandler () {
+ if (_origHandlerFunc == SIG_ERR)
+ return false;
+ return (signal(SIGINT, _origHandlerFunc) != SIG_ERR);
+}
+
+#endif
diff --git a/dviware/dvisvgm/src/SignalHandler.hpp b/dviware/dvisvgm/src/SignalHandler.hpp
new file mode 100644
index 0000000000..827f9e0962
--- /dev/null
+++ b/dviware/dvisvgm/src/SignalHandler.hpp
@@ -0,0 +1,54 @@
+/*************************************************************************
+** SignalHandler.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef SIGNALHANDLER_HPP
+#define SIGNALHANDLER_HPP
+
+#include <exception>
+#include <memory>
+
+struct SignalException : public std::exception {
+};
+
+
+class SignalHandler {
+ public:
+ SignalHandler (const SignalHandler&) =delete;
+ SignalHandler (SignalHandler&&) =delete;
+ ~SignalHandler ();
+ static SignalHandler& instance ();
+ bool start ();
+ void stop ();
+ void check ();
+ void trigger (bool notify);
+ bool active () const {return _active;}
+
+ protected:
+ SignalHandler ();
+ static void callback (int signal);
+
+ private:
+ bool _active; ///< true if listening for signals
+ static volatile bool _break; ///< true if signal has been caught
+ class Impl;
+ std::unique_ptr<Impl> _impl; ///< system-specific data/functions
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/SourceInput.cpp b/dviware/dvisvgm/src/SourceInput.cpp
new file mode 100644
index 0000000000..35cdd24af9
--- /dev/null
+++ b/dviware/dvisvgm/src/SourceInput.cpp
@@ -0,0 +1,150 @@
+/*************************************************************************
+** SourceInput.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <fstream>
+#include <iostream>
+#include "FileSystem.hpp"
+#include "Message.hpp"
+#include "MessageException.hpp"
+#include "SourceInput.hpp"
+#include "utility.hpp"
+
+#ifdef _WIN32
+# include <fcntl.h>
+# include <sys/stat.h>
+# include <windows.h>
+#else
+# include <config.h>
+# ifdef HAVE_UMASK
+# include <sys/stat.h>
+# endif
+#endif
+
+#ifdef _MSC_VER
+# include <io.h>
+#else
+# include <cstdlib>
+# include <unistd.h>
+#endif
+
+using namespace std;
+
+#ifdef _WIN32
+static int fdwrite (int fd, const char *buf, size_t len) {return _write(fd, buf, len);}
+static int fdclose (int fd) {return _close(fd);}
+#else
+static int fdwrite (int fd, const char *buf, size_t len) {return write(fd, buf, len);}
+static int fdclose (int fd) {return close(fd);}
+#endif
+
+
+/** Creates a new temporary file in the configured tmp folder.
+ * If the object already holds an opened temporary file, it's closed
+ * and removed before creating the new one.
+ * @return true on success */
+bool TemporaryFile::create () {
+ if (opened())
+ close();
+ _path = FileSystem::tmpdir();
+#ifndef _WIN32
+ _path += "stdinXXXXXX";
+#ifdef HAVE_UMASK
+ mode_t mode_mask = umask(S_IXUSR | S_IRWXG | S_IRWXO); // set file permissions to 0600
+#endif
+ _fd = mkstemp(&_path[0]);
+#ifdef HAVE_UMASK
+ umask(mode_mask);
+#endif
+#else // !_WIN32
+ char fname[MAX_PATH];
+ std::replace(_path.begin(), _path.end(), '/', '\\');
+ if (GetTempFileName(_path.c_str(), "stdin", 0, fname)) {
+ _fd = _open(fname, _O_CREAT | _O_WRONLY | _O_BINARY, _S_IWRITE);
+ _path = fname;
+ }
+#endif
+ return opened();
+}
+
+
+/** Writes a sequence of characters to the file.
+ * @param[in] buf buffer containing the characters to write
+ * @param[in] len number of characters to write
+ * @return true on success */
+bool TemporaryFile::write (const char *buf, size_t len) {
+ return opened() && fdwrite(_fd, buf, len) >= 0;
+}
+
+
+/** Closes and removes the temporary file.
+ * @return true on success */
+bool TemporaryFile::close () {
+ bool ok = true;
+ if (opened()) {
+ ok = (fdclose(_fd) >= 0);
+ FileSystem::remove(_path);
+ _fd = -1;
+ _path.clear();
+ }
+ return ok;
+}
+
+////////////////////////////////////////////////////////////////
+
+istream& SourceInput::getInputStream (bool showMessages) {
+ if (!_ifs.is_open()) {
+ if (!_fname.empty())
+ _ifs.open(_fname, ios::binary);
+ else {
+#ifdef _WIN32
+ if (_setmode(_fileno(stdin), _O_BINARY) == -1)
+ throw MessageException("can't open stdin in binary mode");
+#endif
+ if (!_tmpfile.create())
+ throw MessageException("can't create temporary file for writing");
+ if (showMessages)
+ Message::mstream() << "reading from " << getMessageFileName() << '\n';
+ char buf[1024];
+ while (cin) {
+ cin.read(buf, 1024);
+ size_t count = cin.gcount();
+ if (!_tmpfile.write(buf, count))
+ throw MessageException("failed to write data to temporary file");
+ }
+ _ifs.open(_tmpfile.path(), ios::binary);
+ }
+ }
+ return _ifs;
+}
+
+
+string SourceInput::getFileName () const {
+ return _fname.empty() ? "stdin" : _fname;
+}
+
+
+string SourceInput::getMessageFileName () const {
+ return _fname.empty() ? "<stdin>" : _fname;
+}
+
+
+string SourceInput::getFilePath () const {
+ return _tmpfile.path().empty() ? _fname : _tmpfile.path();
+}
diff --git a/dviware/dvisvgm/src/SourceInput.hpp b/dviware/dvisvgm/src/SourceInput.hpp
new file mode 100644
index 0000000000..3b0f7b3c62
--- /dev/null
+++ b/dviware/dvisvgm/src/SourceInput.hpp
@@ -0,0 +1,58 @@
+/*************************************************************************
+** SourceInput.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef SOURCEINPUT_HPP
+#define SOURCEINPUT_HPP
+
+#include <fstream>
+#include <string>
+
+
+/** Helper class to handle temporary files. */
+class TemporaryFile {
+ public:
+ ~TemporaryFile () {close();}
+ bool create ();
+ bool opened () const {return _fd >= 0;}
+ bool write (const char *buf, size_t len);
+ bool close ();
+ const std::string& path () const {return _path;}
+
+ private:
+ int _fd = -1; ///< file descriptor assigned to the temporary file
+ std::string _path; ///< path to temporary file
+};
+
+
+class SourceInput {
+ public:
+ explicit SourceInput (const std::string &fname) : _fname(fname) {}
+ std::istream& getInputStream (bool showMessages=false);
+ std::string getFileName () const;
+ std::string getMessageFileName () const;
+ std::string getFilePath () const;
+
+ private:
+ const std::string &_fname; ///< name of file to read from
+ TemporaryFile _tmpfile; ///< temporary file used when reading from stdin
+ std::ifstream _ifs;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/SpecialActions.hpp b/dviware/dvisvgm/src/SpecialActions.hpp
new file mode 100644
index 0000000000..0f7a43074d
--- /dev/null
+++ b/dviware/dvisvgm/src/SpecialActions.hpp
@@ -0,0 +1,92 @@
+/*************************************************************************
+** SpecialActions.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef SPECIALACTIONS_HPP
+#define SPECIALACTIONS_HPP
+
+#include <string>
+#include <memory>
+#include "BoundingBox.hpp"
+#include "Color.hpp"
+#include "Matrix.hpp"
+#include "SVGTree.hpp"
+
+class XMLElement;
+class XMLNode;
+
+class SpecialActions {
+ public:
+ virtual ~SpecialActions () =default;
+ virtual double getX () const =0;
+ virtual double getY () const =0;
+ virtual void setX (double x) =0;
+ virtual void setY (double y) =0;
+ virtual void finishLine () =0;
+ virtual void setColor (const Color &color) =0;
+ virtual Color getColor () const =0;
+ virtual void setMatrix (const Matrix &m) =0;
+ virtual const Matrix& getMatrix () const =0;
+ virtual Matrix getPageTransformation () const {return Matrix(1);}
+ virtual void setBgColor (const Color &color) =0;
+ virtual const SVGTree& svgTree () const =0;
+ SVGTree& svgTree () {return const_cast<SVGTree&>(const_cast<const SpecialActions*>(this)->svgTree());}
+ virtual BoundingBox& bbox () =0;
+ virtual BoundingBox& bbox (const std::string &name, bool reset=false) =0;
+ virtual void embed (const BoundingBox &bbox) =0;
+ virtual void embed (const DPair &p, double r=0) =0;
+ virtual unsigned getCurrentPageNumber () const =0;
+ virtual std::string getSVGFilename (unsigned pageno) const =0;
+ virtual std::string getBBoxFormatString () const =0;
+ virtual void progress (const char *id) {}
+ virtual int getDVIStackDepth () const {return 0;}
+
+ static double PROGRESSBAR_DELAY; ///< progress bar doesn't appear before this time has elapsed (in sec)
+};
+
+
+class EmptySpecialActions : public SpecialActions {
+ public:
+ EmptySpecialActions () : _matrix(1) {_svg.newPage(1);}
+ double getX () const override {return 0;}
+ double getY () const override {return 0;}
+ void setX (double x) override {}
+ void setY (double y) override {}
+ void finishLine () override {}
+ void setColor (const Color &color) override {}
+ void setBgColor (const Color &color) override {}
+ Color getColor () const override {return Color::BLACK;}
+ void setMatrix (const Matrix &m) override {}
+ const Matrix& getMatrix () const override {return _matrix;}
+ const SVGTree& svgTree () const override {return _svg;}
+ BoundingBox& bbox () override {return _bbox;}
+ BoundingBox& bbox (const std::string &name, bool reset=false) override {return _bbox;}
+ void embed (const BoundingBox &bbox) override {}
+ void embed (const DPair &p, double r=0) override {}
+ unsigned getCurrentPageNumber() const override {return 0;}
+ std::string getSVGFilename (unsigned pageno) const override {return "";}
+ std::string getBBoxFormatString () const override {return "";}
+
+ private:
+ BoundingBox _bbox;
+ Matrix _matrix;
+ SVGTree _svg;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/SpecialHandler.hpp b/dviware/dvisvgm/src/SpecialHandler.hpp
new file mode 100644
index 0000000000..4a3439bb24
--- /dev/null
+++ b/dviware/dvisvgm/src/SpecialHandler.hpp
@@ -0,0 +1,55 @@
+/*************************************************************************
+** SpecialHandler.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef SPECIALHANDLER_HPP
+#define SPECIALHANDLER_HPP
+
+#include <istream>
+#include <list>
+#include <vector>
+#include "MessageException.hpp"
+
+
+class SpecialActions;
+class SpecialManager;
+
+
+struct SpecialException : public MessageException {
+ explicit SpecialException (const std::string &msg) : MessageException(msg) {}
+};
+
+
+class SpecialHandler {
+ friend class SpecialManager;
+ public:
+ virtual ~SpecialHandler () =default;
+ virtual const char* info () const=0;
+ virtual const char* name () const=0;
+ virtual std::vector<const char*> prefixes() const =0;
+ virtual void setDviScaleFactor (double dvi2bp) {}
+ virtual void preprocess (const std::string &prefix, std::istream &is, SpecialActions &actions) {}
+ virtual bool process (const std::string &prefix, std::istream &is, SpecialActions &actions)=0;
+ virtual void dviPreprocessingFinished () {}
+ virtual void dviBeginPage (unsigned pageno, SpecialActions &actions) {}
+ virtual void dviEndPage (unsigned pageno, SpecialActions &actions) {}
+ virtual void dviMovedTo (double x, double y, SpecialActions &actions) {}
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/SpecialManager.cpp b/dviware/dvisvgm/src/SpecialManager.cpp
new file mode 100644
index 0000000000..4838cbdf47
--- /dev/null
+++ b/dviware/dvisvgm/src/SpecialManager.cpp
@@ -0,0 +1,179 @@
+/*************************************************************************
+** SpecialManager.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <iomanip>
+#include <map>
+#include <sstream>
+#include "SpecialActions.hpp"
+#include "SpecialHandler.hpp"
+#include "SpecialManager.hpp"
+
+using namespace std;
+
+double SpecialActions::PROGRESSBAR_DELAY=1000; // initial delay in seconds (values >= 1000 disable the progressbar)
+
+
+SpecialManager& SpecialManager::instance() {
+ static SpecialManager sm;
+ return sm;
+}
+
+
+/** Remove all registered handlers. */
+void SpecialManager::unregisterHandlers () {
+ _handlerPool.clear();
+ _handlersByPrefix.clear();
+}
+
+
+/** Registers a single special handler. This method doesn't check if a
+ * handler of the same class is already registered.
+ * @param[in] handler pointer to handler to be registered */
+void SpecialManager::registerHandler (unique_ptr<SpecialHandler> handler) {
+ if (handler) {
+ // get array of prefixes this handler is responsible for
+ for (const char *prefix : handler->prefixes())
+ _handlersByPrefix[prefix] = handler.get();
+ _handlerPool.emplace_back(std::move(handler));
+ }
+}
+
+
+/** Registers several special handlers at once.
+ * If ignorelist == 0, all given handlers are registered. To exclude selected sets of
+ * specials, the corresponding names can be given separated by non alpha-numeric characters,
+ * e.g. "color, ps, em" or "color: ps em" etc.
+ * @param[in] handlers pointer to zero-terminated array of handlers to be registered
+ * @param[in] ignorelist list of special names to be ignored */
+void SpecialManager::registerHandlers (vector<unique_ptr<SpecialHandler>> &handlers, const char *ignorelist) {
+ if (handlers.empty())
+ return;
+ string ignorestr = ignorelist ? ignorelist : "";
+ for (char &c : ignorestr)
+ if (!isalnum(c))
+ c = '%';
+ ignorestr = "%"+ignorestr+"%";
+
+ for (auto &handler : handlers)
+ if (!handler->name() || ignorestr.find("%"+string(handler->name())+"%") == string::npos)
+ registerHandler(std::move(handler));
+}
+
+
+/** Looks for a handler responsible for a given special prefix.
+ * @param[in] prefix the special prefix, e.g. "color" or "em"
+ * @return in case of success: pointer to handler, 0 otherwise */
+SpecialHandler* SpecialManager::findHandlerByPrefix (const string &prefix) const {
+ auto it = _handlersByPrefix.find(prefix);
+ if (it != _handlersByPrefix.end())
+ return it->second;
+ return nullptr;
+}
+
+
+/** Looks for a handler with a given name.
+ * @param[in] name name of handler to look for, e.g. "papersize"
+ * @return in case of success: pointer to handler, 0 otherwise */
+SpecialHandler* SpecialManager::findHandlerByName (const string &name) const {
+ for (auto &handler : _handlerPool) {
+ if (handler->name() == name)
+ return handler.get();
+ }
+ return nullptr;
+}
+
+
+static string extract_prefix (istream &is) {
+ int c;
+ string prefix;
+ while (isalnum(c=is.get()))
+ prefix += char(c);
+ if (ispunct(c)) // also add separation character to identifying prefix
+ prefix += char(c);
+ if (prefix == "ps:" && is.peek() == ':')
+ prefix += char(is.get());
+ return prefix;
+}
+
+
+void SpecialManager::preprocess (const string &special, SpecialActions &actions) const {
+ istringstream iss(special);
+ const string prefix = extract_prefix(iss);
+ if (SpecialHandler *handler = findHandlerByPrefix(prefix))
+ handler->preprocess(prefix, iss, actions);
+}
+
+
+/** Executes a special command.
+ * @param[in] special the special expression
+ * @param[in] dvi2bp factor to convert DVI units to PS points
+ * @param[in] actions actions the special handlers can perform
+ * @return true if the special could be processed successfully
+ * @throw SpecialException in case of errors during special processing */
+bool SpecialManager::process (const string &special, double dvi2bp, SpecialActions &actions) const {
+ istringstream iss(special);
+ const string prefix = extract_prefix(iss);
+ bool success=false;
+ if (SpecialHandler *handler = findHandlerByPrefix(prefix)) {
+ handler->setDviScaleFactor(dvi2bp);
+ success = handler->process(prefix, iss, actions);
+ }
+ return success;
+}
+
+
+void SpecialManager::notifyPreprocessingFinished () const {
+ for (auto &handler : _handlerPool)
+ handler->dviPreprocessingFinished();
+}
+
+
+void SpecialManager::notifyBeginPage (unsigned pageno, SpecialActions &actions) const {
+ for (auto &handler : _handlerPool)
+ handler->dviBeginPage(pageno, actions);
+}
+
+
+void SpecialManager::notifyEndPage (unsigned pageno, SpecialActions &actions) const {
+ for (auto &handler : _handlerPool)
+ handler->dviEndPage(pageno, actions);
+}
+
+
+void SpecialManager::notifyPositionChange (double x, double y, SpecialActions &actions) const {
+ for (auto &handler : _handlerPool)
+ handler->dviMovedTo(x, y, actions);
+}
+
+
+void SpecialManager::writeHandlerInfo (ostream &os) const {
+ ios::fmtflags osflags(os.flags());
+ map<string,SpecialHandler*> sortmap;
+ for (const auto &handler : _handlerPool)
+ if (handler->name())
+ sortmap[handler->name()] = handler.get();
+ for (const auto &strhandlerpair : sortmap) {
+ os << setw(10) << left << strhandlerpair.second->name() << ' ';
+ if (strhandlerpair.second->info())
+ os << strhandlerpair.second->info();
+ os << endl;
+ }
+ os.flags(osflags); // restore format flags
+}
diff --git a/dviware/dvisvgm/src/SpecialManager.hpp b/dviware/dvisvgm/src/SpecialManager.hpp
new file mode 100644
index 0000000000..fe88eaf58e
--- /dev/null
+++ b/dviware/dvisvgm/src/SpecialManager.hpp
@@ -0,0 +1,62 @@
+/*************************************************************************
+** SpecialManager.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef SPECIALMANAGER_HPP
+#define SPECIALMANAGER_HPP
+
+#include <memory>
+#include <ostream>
+#include <string>
+#include <unordered_map>
+#include <vector>
+#include "SpecialHandler.hpp"
+
+class SpecialActions;
+
+class SpecialManager {
+ private:
+ using HandlerPool = std::vector<std::unique_ptr<SpecialHandler>>;
+ using HandlerMap = std::unordered_map<std::string,SpecialHandler*>;
+
+ public:
+ SpecialManager (const SpecialManager &) =delete;
+ static SpecialManager& instance ();
+ void registerHandler (std::unique_ptr<SpecialHandler> handler);
+ void registerHandlers (std::vector<std::unique_ptr<SpecialHandler>> &handlers, const char *ignorelist);
+ void unregisterHandlers ();
+ void preprocess (const std::string &special, SpecialActions &actions) const;
+ bool process (const std::string &special, double dvi2bp, SpecialActions &actions) const;
+ void notifyPreprocessingFinished () const;
+ void notifyBeginPage (unsigned pageno, SpecialActions &actions) const;
+ void notifyEndPage (unsigned pageno, SpecialActions &actions) const;
+ void notifyPositionChange (double x, double y, SpecialActions &actions) const;
+ void writeHandlerInfo (std::ostream &os) const;
+ SpecialHandler* findHandlerByName (const std::string &name) const;
+
+ protected:
+ SpecialManager () =default;
+ SpecialHandler* findHandlerByPrefix (const std::string &prefix) const;
+
+ private:
+ HandlerPool _handlerPool; ///< stores pointers to all handlers
+ HandlerMap _handlersByPrefix; ///< pointers to handlers for corresponding prefixes
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/StreamReader.cpp b/dviware/dvisvgm/src/StreamReader.cpp
new file mode 100644
index 0000000000..f06dcd42dd
--- /dev/null
+++ b/dviware/dvisvgm/src/StreamReader.cpp
@@ -0,0 +1,154 @@
+/*************************************************************************
+** StreamReader.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <vector>
+#include "CRC32.hpp"
+#include "StreamReader.hpp"
+
+using namespace std;
+
+
+istream& StreamReader::replaceStream (istream &in) {
+ istream &ret = *_is;
+ _is = &in;
+ return ret;
+}
+
+
+/** Reads an unsigned integer from assigned input stream.
+ * @param[in] bytes number of bytes to read (max. 4)
+ * @return read integer */
+uint32_t StreamReader::readUnsigned (int bytes) {
+ uint32_t ret = 0;
+ for (bytes--; bytes >= 0 && !_is->eof(); bytes--) {
+ uint32_t b = uint32_t(_is->get());
+ ret |= b << (8*bytes);
+ }
+ return ret;
+}
+
+
+/** Reads an unsigned integer from assigned input stream and updates the CRC32 checksum.
+ * @param[in] bytes number of bytes to read (max. 4)
+ * @param[in,out] crc32 checksum to be updated
+ * @return read integer */
+uint32_t StreamReader::readUnsigned (int bytes, CRC32 &crc32) {
+ uint32_t ret = readUnsigned(bytes);
+ crc32.update(ret, bytes);
+ return ret;
+}
+
+
+/** Reads an signed integer from assigned input stream.
+ * @param[in] bytes number of bytes to read (max. 4)
+ * @return read integer */
+int32_t StreamReader::readSigned (int bytes) {
+ uint32_t ret = uint32_t(_is->get());
+ if (ret & 128) // negative value?
+ ret |= 0xffffff00;
+ for (bytes-=2; bytes >= 0 && !_is->eof(); bytes--)
+ ret = (ret << 8) | _is->get();
+ return int32_t(ret);
+}
+
+
+/** Reads an signed integer from assigned input stream and updates the CRC32 checksum.
+ * @param[in] bytes number of bytes to read (max. 4)
+ * @param[in,out] crc32 checksum to be updated
+ * @return read integer */
+int32_t StreamReader::readSigned (int bytes, CRC32 &crc32) {
+ int32_t ret = readSigned(bytes);
+ crc32.update(uint32_t(ret), bytes);
+ return ret;
+}
+
+
+/** Reads a string terminated by a 0-byte. */
+string StreamReader::readString () {
+ if (!_is)
+ throw StreamReaderException("no stream assigned");
+ string ret;
+ while (!_is->eof() && _is->peek() > 0)
+ ret += char(_is->get());
+ _is->get(); // skip 0-byte
+ return ret;
+}
+
+
+/** Reads a string terminated by a 0-byte and updates the CRC32 checksum.
+ * @param[in,out] crc32 checksum to be updated
+ * @param[in] finalZero consider final 0-byte in checksum
+ * @return the string read */
+string StreamReader::readString (CRC32 &crc32, bool finalZero) {
+ string ret = readString();
+ crc32.update(reinterpret_cast<const uint8_t*>(ret.data()), ret.length());
+ if (finalZero)
+ crc32.update(0, 1);
+ return ret;
+}
+
+
+/** Reads a string of a given length.
+ * @param[in] length number of characters to read
+ * @return the string read */
+string StreamReader::readString (int length) {
+ if (!_is)
+ throw StreamReaderException("no stream assigned");
+ length = max(0, length);
+ string str(length, '\0');
+ _is->read(&str[0], length); // read 'length' bytes and append '\0'
+ return str;
+}
+
+
+/** Reads a string of a given length and updates the CRC32 checksum.
+ * @param[in] length number of characters to read
+ * @param[in,out] crc32 checksum to be updated
+ * @return the string read */
+string StreamReader::readString (int length, CRC32 &crc32) {
+ string ret = readString(length);
+ crc32.update(reinterpret_cast<const uint8_t*>(ret.data()), length);
+ return ret;
+}
+
+
+vector<uint8_t>& StreamReader::readBytes (int n, vector<uint8_t> &bytes) {
+ if (n > 0)
+ _is->read((char*)&bytes[0], n);
+ return bytes;
+}
+
+
+vector<uint8_t>& StreamReader::readBytes (int n, vector<uint8_t> &bytes, CRC32 &crc32) {
+ readBytes(n, bytes);
+ crc32.update(&bytes[0], bytes.size());
+ return bytes;
+}
+
+
+int StreamReader::readByte (CRC32 &crc32) {
+ int ret = readByte();
+ if (ret >= 0) {
+ const uint8_t c = uint8_t(ret & 0xff);
+ crc32.update(&c, 1);
+ }
+ return ret;
+}
diff --git a/dviware/dvisvgm/src/StreamReader.hpp b/dviware/dvisvgm/src/StreamReader.hpp
new file mode 100644
index 0000000000..2b95f653ca
--- /dev/null
+++ b/dviware/dvisvgm/src/StreamReader.hpp
@@ -0,0 +1,68 @@
+/*************************************************************************
+** StreamReader.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef STREAMREADER_HPP
+#define STREAMREADER_HPP
+
+#include <istream>
+#include <string>
+#include <vector>
+#include "MessageException.hpp"
+
+class CRC32;
+
+class StreamReader {
+ public:
+ explicit StreamReader (std::istream &is) : _is(&is) {}
+ virtual ~StreamReader () =default;
+ bool isStreamValid () const {return _is;}
+ bool eof () const {return _is->eof();}
+ void clearStream () {_is->clear();}
+ std::istream& replaceStream (std::istream &s);
+ uint32_t readUnsigned (int n);
+ uint32_t readUnsigned (int n, CRC32 &crc32);
+ int32_t readSigned (int n);
+ int32_t readSigned (int n, CRC32 &crc32);
+ std::string readString ();
+ std::string readString (CRC32 &crc32, bool finalZero=false);
+ std::string readString (int length);
+ std::string readString (int length, CRC32 &crc32);
+ std::vector<uint8_t>& readBytes (int n, std::vector<uint8_t> &bytes);
+ std::vector<uint8_t>& readBytes (int n, std::vector<uint8_t> &bytes, CRC32 &crc32);
+ int readByte () {return _is->get();}
+ int readByte (CRC32 &crc32);
+ void seek (std::streampos pos, std::ios::seekdir dir) {_is->seekg(pos, dir);}
+ void seek (std::streampos pos) {_is->seekg(pos);}
+ std::streampos tell () const {return _is->tellg();}
+ int peek () const {return _is->peek();}
+
+ protected:
+ std::istream& getInputStream () {return *_is;}
+
+ private:
+ std::istream *_is;
+};
+
+
+struct StreamReaderException : public MessageException {
+ explicit StreamReaderException (const std::string &msg) : MessageException(msg) {}
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/StreamWriter.cpp b/dviware/dvisvgm/src/StreamWriter.cpp
new file mode 100644
index 0000000000..c5630f56ba
--- /dev/null
+++ b/dviware/dvisvgm/src/StreamWriter.cpp
@@ -0,0 +1,97 @@
+/*************************************************************************
+** StreamWriter.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include "CRC32.hpp"
+#include "StreamWriter.hpp"
+
+using namespace std;
+
+
+/** Writes an unsigned integer to the output stream.
+ * @param[in] val the value to write
+ * @param[in] n number of bytes to be considered */
+void StreamWriter::writeUnsigned (uint32_t val, int n) {
+ for (n--; n >= 0; n--)
+ _os.put(char((val >> (8*n)) & 0xff));
+}
+
+
+/** Writes a signed integer to the output stream.
+ * @param[in] val the value to write
+ * @param[in] n number of bytes to be considered */
+void StreamWriter::writeSigned (int32_t val, int n) {
+ writeUnsigned((uint32_t)val, n);
+}
+
+
+void StreamWriter::writeBytes (const std::vector<uint8_t> &bytes) {
+ _os.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
+}
+
+
+void StreamWriter::writeBytes (const uint8_t *buf, size_t bufsize) {
+ _os.write(reinterpret_cast<const char*>(buf), bufsize);
+}
+
+
+void StreamWriter::writeBytes (int byte, size_t count) {
+ while (count-- > 0)
+ _os.put(byte);
+}
+
+
+/** Writes a string to the output stream.
+ * @param[in] str the string to write
+ * @param[in] finalZero if true, a final 0-byte is appended */
+void StreamWriter::writeString (const string &str, bool finalZero) {
+ for (char c : str)
+ _os.put(c);
+ if (finalZero)
+ _os.put(0);
+}
+
+
+/** Writes an unsigned integer to the output stream.
+ * @param[in] val the value to write
+ * @param[in] n number of bytes to be considered
+ * @param[in,out] crc32 checksum to be updated */
+void StreamWriter::writeUnsigned (uint32_t val, int n, CRC32 &crc32) {
+ writeUnsigned(val, n);
+ crc32.update(val, n);
+}
+
+
+/** Writes a signed integer to the output stream and updates the CRC32 checksum.
+ * @param[in] val the value to write
+ * @param[in] n number of bytes to be considered
+ * @param[in,out] crc32 checksum to be updated */
+void StreamWriter::writeSigned (int32_t val, int n, CRC32 &crc32) {
+ writeUnsigned((uint32_t)val, n, crc32);
+}
+
+
+/** Writes a string to the output stream and updates the CRC32 checksum.
+ * @param[in] str the string to write
+ * @param[in,out] crc32 checksum to be updated
+ * @param[in] finalZero if true, a final 0-byte is appended */
+void StreamWriter::writeString (const std::string &str, CRC32 &crc32, bool finalZero) {
+ writeString(str, finalZero);
+ crc32.update((const uint8_t*)str.c_str(), str.length() + (finalZero ? 1 : 0));
+}
diff --git a/dviware/dvisvgm/src/StreamWriter.hpp b/dviware/dvisvgm/src/StreamWriter.hpp
new file mode 100644
index 0000000000..49b3401589
--- /dev/null
+++ b/dviware/dvisvgm/src/StreamWriter.hpp
@@ -0,0 +1,46 @@
+/*************************************************************************
+** StreamWriter.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef STREAMWRITER_HPP
+#define STREAMWRITER_HPP
+
+#include <ostream>
+#include <vector>
+
+class CRC32;
+
+class StreamWriter {
+ public:
+ explicit StreamWriter (std::ostream &os) : _os(os) {}
+ void writeUnsigned (uint32_t val, int n);
+ void writeSigned (int32_t val, int n);
+ void writeBytes (const std::vector<uint8_t> &bytes);
+ void writeBytes (const uint8_t *buf, size_t bufsize);
+ void writeBytes (int byte, size_t count);
+ void writeString (const std::string &str, bool finalZero=false);
+ void writeUnsigned (uint32_t val, int n, CRC32 &crc32);
+ void writeSigned (int32_t val, int n, CRC32 &crc32);
+ void writeString (const std::string &str, CRC32 &crc32, bool finalZero=false);
+
+ private:
+ std::ostream &_os;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/Subfont.cpp b/dviware/dvisvgm/src/Subfont.cpp
new file mode 100644
index 0000000000..e0b2570d8c
--- /dev/null
+++ b/dviware/dvisvgm/src/Subfont.cpp
@@ -0,0 +1,255 @@
+/*************************************************************************
+** Subfont.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <limits>
+#include <memory>
+#include "FileFinder.hpp"
+#include "Message.hpp"
+#include "Subfont.hpp"
+#include "utility.hpp"
+
+
+using namespace std;
+
+// helper functions
+
+static int skip_mapping_data (istream &is);
+static bool scan_line (const char *line, int lineno, vector<uint16_t> &mapping, const string &fname, long &offset);
+
+
+/** Constructs a new SubfontDefinition object.
+ * @param[in] name name of subfont definition
+ * @param[in] fpath path to corresponding .sfd file*/
+SubfontDefinition::SubfontDefinition (string name, const char *fpath) : _sfname(std::move(name)) {
+ // read all subfont IDs from the .sfd file but skip the mapping data
+ ifstream is(fpath);
+ while (is) {
+ if (is.peek() == '#') // comment line?
+ is.ignore(numeric_limits<int>::max(), '\n'); // => skip it
+ else if (isspace(is.peek()))
+ is.get();
+ else {
+ string id;
+ while (is && !isspace(is.peek()))
+ id += char(is.get());
+ if (!id.empty()) {
+ auto state = _subfonts.emplace(pair<string,unique_ptr<Subfont>>(id, unique_ptr<Subfont>()));
+ if (state.second) // id was not present in map already
+ state.first->second = unique_ptr<Subfont>(new Subfont(*this, state.first->first));
+ skip_mapping_data(is);
+ }
+ }
+ }
+}
+
+
+/** Looks for a subfont definition of the given name and returns the corresponding object.
+ * All objects are only created once for a given name and stored in an internal cache.
+ * @param[in] name name of subfont definition to lookup
+ * @return pointer to subfont definition object or 0 if it doesn't exist */
+SubfontDefinition* SubfontDefinition::lookup (const std::string &name) {
+ static map<string,unique_ptr<SubfontDefinition>> sfdMap;
+ auto it = sfdMap.find(name);
+ if (it != sfdMap.end())
+ return it->second.get();
+ SubfontDefinition *sfd=nullptr;
+ if (const char *path = FileFinder::instance().lookup(name+".sfd", false)) {
+ sfd = new SubfontDefinition(name, path);
+ sfdMap[name] = unique_ptr<SubfontDefinition>(sfd);
+ }
+ return sfd;
+}
+
+
+/** Returns the full path to the corresponding .sfd file or 0 if it can't be found. */
+const char* SubfontDefinition::path () const {
+ return FileFinder::instance().lookup(filename(), false);
+}
+
+
+/** Returns the subfont with the given ID, or 0 if it doesn't exist. */
+Subfont* SubfontDefinition::subfont (const string &id) const {
+ auto it = _subfonts.find(id);
+ if (it != _subfonts.end())
+ return it->second.get();
+ return nullptr;
+}
+
+
+/** Returns all subfonts defined in this SFD. */
+int SubfontDefinition::subfonts (vector<Subfont*> &sfs) const {
+ for (const auto &strsfpair : _subfonts)
+ sfs.push_back(strsfpair.second.get());
+ return int(sfs.size());
+}
+
+//////////////////////////////////////////////////////////////////////
+
+
+/** Reads the character mappings for a given subfont ID.
+ * Format of subfont definition (sfd) files:
+ * sfd ::= (ID entries | '#' <string> '\n')*
+ * ID ::= <string without whitespace>
+ * entries ::= (integer | integer ':' | integer '_' integer)*
+ * The mapping data for a subfont is given as a sequence of 256 16-bit values where
+ * value v at position c defines the (global) character code that is assigned to the
+ * local subfont character c. The sequence v,v+1,v+2,...,v+n can be abbreviated with
+ * v '_' v+n, e.g. 10_55. In order to continue the sequence at a different position,
+ * the syntax number ':' can be used. Example: 10: 5 6 7 assigns the values v=5, 6, 7
+ * to c=10, 11 and 12, respectively.
+ * @return true if the data has been read successfully */
+bool Subfont::read () {
+ if (!_mapping.empty()) // if there's already a mapping assigned, we're finished here
+ return true;
+ if (const char *p = _sfd.path()) {
+ ifstream is(p);
+ if (!is)
+ return false;
+ int lineno=1;
+ while (is) {
+ if (is.peek() == '#' || is.peek() == '\n') {
+ is.ignore(numeric_limits<int>::max(), '\n'); // skip comment and empty line
+ lineno++;
+ }
+ else if (isspace(is.peek()))
+ is.get();
+ else {
+ string id;
+ while (is && !isspace(is.peek()))
+ id += char(is.get());
+ if (id != _id)
+ lineno += skip_mapping_data(is);
+ else {
+ // build mapping array
+ _mapping.resize(256, 0);
+ long pos=0;
+ char buf[1024];
+ bool complete=false;
+ while (!complete) {
+ is.getline(buf, 1024);
+ complete = scan_line(buf, lineno, _mapping, _sfd.filename(), pos);
+ }
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+
+/** Returns the global character code of the target font for a
+ * (local) character code of the subfont.
+ * @param[in] c local character code relative to the subfont
+ * @return character code of the target font */
+uint16_t Subfont::decode (unsigned char c) {
+ if (_mapping.empty() && !read())
+ return 0;
+ return _mapping[c];
+}
+
+
+//////////////////////////////////////////////////////////////////////
+
+
+/** Skips the mapping data of a subfont entry.
+ * @param[in] is stream to read from
+ * @return number of lines skipped */
+static int skip_mapping_data (istream &is) {
+ char buf[1024];
+ bool complete=false;
+ int lines=0;
+ while (is && !complete) {
+ is.getline(buf, 1024);
+ if (is.gcount() > 1)
+ lines++;
+ const char *p = buf+is.gcount()-2;
+ while (p >= buf && isspace(*p))
+ p--;
+ complete = (p < buf || *p != '\\'); // line doesn't end with backslash
+ }
+ return lines;
+}
+
+
+/** Scans a single line of mapping data and stores the values in the given array.
+ * @param[in] line the line of text to be scanned
+ * @param[in] lineno line number used in exception messages
+ * @param[in,out] mapping the mapping data
+ * @param[in] fname name of the mapfile being scanned
+ * @param[in,out] offset position/index of next mapping value
+ * @return true if the line is the last one the current mapping sequence, i.e. the line doesn't end with a backslash */
+static bool scan_line (const char *line, int lineno, vector<uint16_t> &mapping, const string &fname, long &offset) {
+ const char *p=line;
+ char *q;
+ for (; *p && isspace(*p); p++);
+ while (*p) {
+ if (*p == '\\') {
+ while (*++p)
+ if (!isspace(*p))
+ throw SubfontException("unexpected backslash in mapping table", fname, lineno);
+ }
+ else {
+ long val1 = strtol(p, &q, 0); // first value of range
+ long val2 = -1; // last value of range
+ switch (*q) {
+ case ':':
+ if (val1 < 0 || val1 > 255)
+ throw SubfontException("offset value "+to_string(val1)+" out of range (0-255)", fname, lineno);
+ offset = val1;
+ val1 = -1;
+ q++;
+ break;
+ case '_':
+ p = q+1;
+ val2 = strtol(p, &q, 0);
+ if (val1 < 0 || val1 > 0xffffL)
+ throw SubfontException("table value "+to_string(val1)+" out of range", fname, lineno);
+ if (val2 < 0 || val2 > 0xffffL)
+ throw SubfontException("table value "+to_string(val2)+" out of range", fname, lineno);
+ if (p == q || (!isspace(*q) && *q != '\\' && *q))
+ throw SubfontException("unexpected character '"+to_string(*q)+"'", fname, lineno);
+ break;
+ default:
+ if (p == q || (!isspace(*q) && *q != '\\' && *q))
+ throw SubfontException("unexpected character '"+to_string(*q)+"'", fname, lineno);
+ if (val1 < 0 || val1 > 0xffffL)
+ throw SubfontException("invalid character code", fname, lineno);
+ val2 = val1;
+ }
+ if (val1 >= 0) {
+ if (val1 > val2 || offset+val2-val1 > 255)
+ throw SubfontException("invalid range in mapping table: "+util::tohex(val1)+"_"+util::tohex(val2), fname, lineno);
+ for (long v=val1; v <= val2; v++) {
+ if (mapping[offset])
+ throw SubfontException("mapping of character "+to_string(offset)+" already defined", fname, lineno);
+ mapping[offset++] = static_cast<uint16_t>(v);
+ }
+ }
+ for (p=q; *p && isspace(*p); p++);
+ }
+ }
+ for (p--; p >= line && isspace(*p); p--);
+ return p < line || *p != '\\';
+}
+
diff --git a/dviware/dvisvgm/src/Subfont.hpp b/dviware/dvisvgm/src/Subfont.hpp
new file mode 100644
index 0000000000..ca88520ea2
--- /dev/null
+++ b/dviware/dvisvgm/src/Subfont.hpp
@@ -0,0 +1,90 @@
+/*************************************************************************
+** Subfont.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef SUBFONT_HPP
+#define SUBFONT_HPP
+
+#include <istream>
+#include <map>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+#include "MessageException.hpp"
+
+class Subfont;
+
+
+/** Represents a collection of subfont mappings as defined in a .sfd file, and
+ * encapsulates the evaluation of these files. */
+class SubfontDefinition {
+ using Subfonts = std::map<std::string, std::unique_ptr<Subfont>>;
+ public:
+ SubfontDefinition (const SubfontDefinition &sfd) =delete;
+ static SubfontDefinition* lookup (const std::string &name);
+// int getIDs (std::vector<std::string> &ids) const;
+ const std::string& name() const {return _sfname;}
+ std::string filename() const {return _sfname+".sfd";}
+ Subfont* subfont (const std::string &id) const;
+ int subfonts (std::vector<Subfont*> &sfs) const;
+ const char* path () const;
+
+ protected:
+ SubfontDefinition (std::string name, const char *fpath);
+
+ private:
+ std::string _sfname; ///< name of subfont
+ Subfonts _subfonts; ///< all subfonts defined in the corresponding .sfd file
+};
+
+
+/** Represents a single subfont mapping defined in a SubfontDefinition (.sfd file). */
+class Subfont {
+ friend class SubfontDefinition;
+ public:
+ Subfont (const Subfont &sf) =delete;
+ const std::string& id () const {return _id;}
+ uint16_t decode (unsigned char c);
+
+ protected:
+ Subfont (SubfontDefinition &sfd, const std::string &id) : _sfd(sfd), _id(id), _mapping(0) {}
+ bool read ();
+
+ private:
+ SubfontDefinition &_sfd; ///< SubfontDefinition where this Subfont belongs to
+ const std::string &_id; ///< id of this subfont as specified in the .sfd file
+ std::vector<uint16_t> _mapping; ///< the character mapping table with 256 entries
+};
+
+
+class SubfontException : public MessageException {
+ public:
+ SubfontException (const std::string &msg, std::string fname, int lineno=0)
+ : MessageException(msg), _fname(std::move(fname)), _lineno(lineno) {}
+
+ const char* filename () const {return _fname.c_str();}
+ int lineno () const {return _lineno;}
+
+ private:
+ std::string _fname;
+ int _lineno;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/System.cpp b/dviware/dvisvgm/src/System.cpp
new file mode 100644
index 0000000000..b2724bdcab
--- /dev/null
+++ b/dviware/dvisvgm/src/System.cpp
@@ -0,0 +1,49 @@
+/*************************************************************************
+** System.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <config.h>
+#include <ctime>
+#include "System.hpp"
+
+#if defined (HAVE_SYS_TIME_H)
+#include <sys/time.h>
+#elif defined (HAVE_SYS_TIMEB_H)
+#include <sys/timeb.h>
+#endif
+
+
+using namespace std;
+
+
+/** Returns timestamp (wall time) in seconds. */
+double System::time () {
+#if defined (HAVE_SYS_TIME_H)
+ struct timeval tv;
+ gettimeofday(&tv, nullptr);
+ return tv.tv_sec + tv.tv_usec/1000000.0;
+#elif defined (HAVE_SYS_TIMEB_H)
+ struct timeb tb;
+ ftime(&tb);
+ return tb.time + tb.millitm/1000.0;
+#else
+ clock_t myclock = clock();
+ return double(myclock)/CLOCKS_PER_SEC;
+#endif
+}
diff --git a/dviware/dvisvgm/src/System.hpp b/dviware/dvisvgm/src/System.hpp
new file mode 100644
index 0000000000..fa29325252
--- /dev/null
+++ b/dviware/dvisvgm/src/System.hpp
@@ -0,0 +1,29 @@
+/*************************************************************************
+** System.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef SYSTEM_HPP
+#define SYSTEM_HPP
+
+namespace System
+{
+ double time ();
+}
+
+#endif
diff --git a/dviware/dvisvgm/src/TFM.cpp b/dviware/dvisvgm/src/TFM.cpp
new file mode 100644
index 0000000000..005081533e
--- /dev/null
+++ b/dviware/dvisvgm/src/TFM.cpp
@@ -0,0 +1,189 @@
+/*************************************************************************
+** TFM.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <fstream>
+#include <vector>
+#include "FileFinder.hpp"
+#include "Length.hpp"
+#include "Message.hpp"
+#include "StreamReader.hpp"
+#include "TFM.hpp"
+
+using namespace std;
+
+
+/** Reads a sequence of n TFM words (4 Bytes each).
+ * @param[in] reader the TFM data is read from this object
+ * @param[out] v the read words
+ * @param[in] n number of words to be read */
+template <typename T>
+static void read_words (StreamReader &reader, vector<T> &v, unsigned n) {
+ v.clear();
+ v.resize(n);
+ for (unsigned i=0; i < n; i++)
+ v[i] = reader.readUnsigned(4);
+}
+
+
+TFM::TFM (istream &is) : _checksum(0), _firstChar(0), _lastChar(0), _designSize(0), _ascent(0), _descent(0) {
+ if (!is)
+ return;
+ is.seekg(0);
+ StreamReader reader(is);
+ uint16_t lf = uint16_t(reader.readUnsigned(2)); // length of entire file in 4 byte words
+ uint16_t lh = uint16_t(reader.readUnsigned(2)); // length of header in 4 byte words
+ _firstChar= uint16_t(reader.readUnsigned(2)); // smallest character code in font
+ _lastChar = uint16_t(reader.readUnsigned(2)); // largest character code in font
+ uint16_t nw = uint16_t(reader.readUnsigned(2)); // number of words in width table
+ uint16_t nh = uint16_t(reader.readUnsigned(2)); // number of words in height table
+ uint16_t nd = uint16_t(reader.readUnsigned(2)); // number of words in depth table
+ uint16_t ni = uint16_t(reader.readUnsigned(2)); // number of words in italic corr. table
+ uint16_t nl = uint16_t(reader.readUnsigned(2)); // number of words in lig/kern table
+ uint16_t nk = uint16_t(reader.readUnsigned(2)); // number of words in kern table
+ uint16_t ne = uint16_t(reader.readUnsigned(2)); // number of words in ext. char table
+ uint16_t np = uint16_t(reader.readUnsigned(2)); // number of font parameter words
+
+ if (6+lh+(_lastChar-_firstChar+1)+nw+nh+nd+ni+nl+nk+ne+np != lf)
+ throw FontMetricException("inconsistent length values");
+ if (_firstChar >= _lastChar || _lastChar > 255 || ne > 256)
+ throw FontMetricException("character codes out of range");
+
+ readHeader(reader);
+ is.seekg(24+lh*4); // move to char info table
+ readTables(reader, nw, nh, nd, ni);
+ is.seekg(4*(lf-np), ios::beg); // move to param section
+ readParameters(reader, np);
+}
+
+
+void TFM::readHeader (StreamReader &reader) {
+ _checksum = reader.readUnsigned(4);
+ _designSize = double(FixWord(reader.readUnsigned(4)))*Length::pt2bp;
+}
+
+
+void TFM::readTables (StreamReader &reader, int nw, int nh, int nd, int ni) {
+ read_words(reader, _charInfoTable, _lastChar-_firstChar+1);
+ read_words(reader, _widthTable, nw);
+ read_words(reader, _heightTable, nh);
+ read_words(reader, _depthTable, nd);
+ read_words(reader, _italicTable, ni);
+ for (FixWord h : _heightTable)
+ _ascent = max(_ascent, h);
+ for (FixWord d : _depthTable)
+ _descent = max(_descent, d);
+}
+
+
+/** Read the values from the param section of the TFM file.
+ * @param[in] reader read from this stream
+ * @param[in] np number of paramaters to read */
+void TFM::readParameters (StreamReader &reader, int np) {
+ _params.resize(7);
+ np = min(np, 7);
+ for (int i=0; i < np; i++)
+ _params[i] = reader.readUnsigned(4);
+ for (int i=np; i < 7; i++)
+ _params[i] = 0;
+}
+
+
+/** Returns the optimal space width between words (in PS point units). */
+double TFM::getSpace () const {
+ return _params.empty() ? 0 : double(_params[1])*_designSize;
+}
+
+
+/** Returns the amount of glue stretching between words (in PS point units). */
+double TFM::getSpaceStretch () const {
+ return _params.empty() ? 0 : double(_params[2])*_designSize;
+}
+
+
+/** Returns the amount of glue shrinking between words (in PS point units). */
+double TFM::getSpaceShrink () const {
+ return _params.empty() ? 0 : double(_params[3])*_designSize;
+}
+
+
+/** Returns the size of one EM unit (in PS point units). */
+double TFM::getQuad () const {
+ if (_params.empty() || _params[5] == 0)
+ return _designSize;
+ return double(_params[5])*_designSize;
+}
+
+
+/** Returns the index to the entry of the character info table that describes the metric of a given character.
+ * @param[in] c character whose index is retrieved
+ * @return table index for character c, or -1 if there's no entry */
+int TFM::charIndex (int c) const {
+ if (c < _firstChar || c > _lastChar || size_t(c-_firstChar) >= _charInfoTable.size())
+ return -1;
+ return c-_firstChar;
+}
+
+
+// the char info word for each character consists of 4 bytes holding the following information:
+// width index w, height index (h), depth index (d), italic correction index (it),
+// tag (tg) and a remainder:
+//
+// byte 1 | byte 2 | byte 3 | byte 4
+// xxxxxxxx | xxxx xxxx | xxxxxx xx | xxxxxxxx
+// w | h d | it tg | remainder
+
+/** Returns the width of char c in PS point units. */
+double TFM::getCharWidth (int c) const {
+ int index = charIndex(c);
+ if (index < 0)
+ return 0;
+ index = (_charInfoTable[index] >> 24) & 0xFF;
+ return double(_widthTable[index]) * _designSize;
+}
+
+
+/** Returns the height of char c in PS point units. */
+double TFM::getCharHeight (int c) const {
+ int index = charIndex(c);
+ if (index < 0)
+ return 0;
+ index = (_charInfoTable[index] >> 20) & 0x0F;
+ return double(_heightTable[index]) * _designSize;
+}
+
+
+/** Returns the depth of char c in PS point units. */
+double TFM::getCharDepth (int c) const {
+ int index = charIndex(c);
+ if (index < 0)
+ return 0;
+ index = (_charInfoTable[index] >> 16) & 0x0F;
+ return double(_depthTable[index]) * _designSize;
+}
+
+
+/** Returns the italic correction of char c in PS point units. */
+double TFM::getItalicCorr (int c) const {
+ int index = charIndex(c);
+ if (index < 0)
+ return 0;
+ index = (_charInfoTable[index] >> 10) & 0x3F;
+ return double(_italicTable[index]) * _designSize;
+}
diff --git a/dviware/dvisvgm/src/TFM.hpp b/dviware/dvisvgm/src/TFM.hpp
new file mode 100644
index 0000000000..668ff39083
--- /dev/null
+++ b/dviware/dvisvgm/src/TFM.hpp
@@ -0,0 +1,74 @@
+/*************************************************************************
+** TFM.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef TFM_HPP
+#define TFM_HPP
+
+#include <istream>
+#include <string>
+#include <vector>
+#include "FixWord.hpp"
+#include "FontMetrics.hpp"
+#include "StreamReader.hpp"
+
+class StreamReader;
+
+class TFM : public FontMetrics
+{
+ public:
+ explicit TFM (std::istream &is);
+ double getCharWidth (int c) const override;
+ double getCharHeight (int c) const override;
+ double getCharDepth (int c) const override;
+ double getItalicCorr (int c) const override;
+ double getDesignSize () const override {return _designSize;}
+ double getSpace () const override;
+ double getSpaceStretch () const override;
+ double getSpaceShrink () const override;
+ double getQuad () const override;
+ double getAscent () const override {return double(_ascent)*_designSize;}
+ double getDescent () const override {return double(_descent)*_designSize;}
+ bool verticalLayout () const override {return false;}
+ uint32_t getChecksum () const override {return _checksum;}
+ uint16_t firstChar () const override {return _firstChar;}
+ uint16_t lastChar () const override {return _lastChar;}
+
+ protected:
+ TFM () : _checksum(0), _firstChar(0), _lastChar(0), _designSize(0), _params(7) {}
+ void readHeader (StreamReader &reader);
+ void readTables (StreamReader &reader, int nw, int nh, int nd, int ni);
+ void readParameters (StreamReader &reader, int np);
+ virtual int charIndex (int c) const;
+ void setCharRange (int firstchar, int lastchar) {_firstChar=firstchar; _lastChar=lastchar;}
+
+ private:
+ uint32_t _checksum;
+ uint16_t _firstChar, _lastChar;
+ double _designSize; ///< design size of the font in PS points (72bp = 1in)
+ std::vector<uint32_t> _charInfoTable;
+ std::vector<FixWord> _widthTable; ///< character widths in design size units
+ std::vector<FixWord> _heightTable; ///< character height in design size units
+ std::vector<FixWord> _depthTable; ///< character depth in design size units
+ std::vector<FixWord> _italicTable; ///< italic corrections in design size units
+ std::vector<FixWord> _params; ///< values of the TFM's param section
+ FixWord _ascent, _descent; ///< max. height and depth
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/TTFAutohint.cpp b/dviware/dvisvgm/src/TTFAutohint.cpp
new file mode 100644
index 0000000000..a7aef2a5ec
--- /dev/null
+++ b/dviware/dvisvgm/src/TTFAutohint.cpp
@@ -0,0 +1,139 @@
+/*************************************************************************
+** TTFAutohint.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include "TTFAutohint.hpp"
+
+using namespace std;
+
+#ifndef HAVE_TTFAUTOHINT_H
+TTFAutohint::TTFAutohint () {}
+
+bool TTFAutohint::available () const {
+ return false;
+}
+
+int TTFAutohint::autohint (const string&, const string&, bool) {
+ return 0;
+}
+
+string TTFAutohint::lastErrorMessage () const {
+ return "";
+}
+
+string TTFAutohint::version () const {
+ return "";
+}
+
+#else // HAVE_TTFAUTOHINT_H
+
+#include <ttfautohint.h>
+
+#ifndef HAVE_LIBTTFAUTOHINT
+static string get_libttfautohint () {
+#ifdef _WIN32
+ return "ttfautohint.dll";
+#else
+ string dlname = "libttfautohint.so.1";
+#ifdef __APPLE__
+ DLLoader loader(dlname);
+ if (!loader.loaded())
+ dlname = "libttfautohint.1.dylib";
+#endif
+ return dlname;
+#endif // _WIN32
+}
+#endif // HAVE_LIBTTFAUTOHINT
+
+
+TTFAutohint::TTFAutohint () :
+#ifndef HAVE_LIBTTFAUTOHINT
+ DLLoader(get_libttfautohint()),
+#endif
+ _lastErrorMessage()
+{
+}
+
+
+/** Returns true if the ttfautohint library is present. */
+bool TTFAutohint::available () const {
+#ifdef HAVE_LIBTTFAUTOHINT
+ return true;
+#else
+ return loaded();
+#endif
+}
+
+
+/** Runs ttfautohint on a given font file.
+ * @param[in] source name of TTF input file
+ * @param[in] target name of TTF output file
+ * @param[in] rehintIfSymbolFont if true, ttfautohint is run again with option "symbol" in case regular hinting is rejected.
+ * @return ttfautohint error code (0 = success) */
+int TTFAutohint::autohint (const string &source, const string &target, bool rehintIfSymbolFont) {
+#ifdef HAVE_LIBTTFAUTOHINT
+ auto fn = &TTF_autohint;
+#else
+ auto fn = LOAD_SYMBOL(TTF_autohint);
+#endif
+ int ret=0;
+ if (fn) {
+ FILE *ttf_in = fopen(source.c_str(), "rb");
+ FILE *ttf_out = fopen(target.c_str(), "wb");
+ ret = fn("in-file, out-file, default-script, error-string", ttf_in, ttf_out, "latn", &_lastErrorMessage);
+ if (ret == TA_Err_Missing_Glyph && rehintIfSymbolFont) {
+ fseek(ttf_in, 0, SEEK_SET);
+ fseek(ttf_out, 0, SEEK_SET);
+ ret = fn("in-file, out-file, symbol, error-string", ttf_in, ttf_out, true, &_lastErrorMessage);
+ }
+ fclose(ttf_out);
+ fclose(ttf_in);
+ }
+ return ret;
+}
+
+
+/** Returns the error message of the last autohint call. */
+string TTFAutohint::lastErrorMessage () const {
+ string message;
+ if (_lastErrorMessage)
+ message = reinterpret_cast<const char*>(_lastErrorMessage);
+ return message;
+}
+
+
+/** Returns the version number of ttfautohint. */
+string TTFAutohint::version () const {
+#ifdef HAVE_LIBTTFAUTOHINT
+ auto fn = &TTF_autohint_version;
+#else
+ auto fn = LOAD_SYMBOL(TTF_autohint_version);
+#endif
+ string ret;
+ if (fn) {
+ int major, minor, revision;
+ fn(&major, &minor, &revision);
+ ret = to_string(major)+"."+to_string(minor);
+ if (revision)
+ ret += "."+to_string(revision);
+ }
+ return ret;
+}
+
+#endif // HAVE_TTFAUTOHINT_H
diff --git a/dviware/dvisvgm/src/TTFAutohint.hpp b/dviware/dvisvgm/src/TTFAutohint.hpp
new file mode 100644
index 0000000000..417661d3bb
--- /dev/null
+++ b/dviware/dvisvgm/src/TTFAutohint.hpp
@@ -0,0 +1,51 @@
+/*************************************************************************
+** TTFAutohint.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef TTFAUTOHINT_HPP
+#define TTFAUTOHINT_HPP
+
+#include <config.h>
+#include <string>
+
+#if defined(HAVE_TTFAUTOHINT_H) && !defined(HAVE_LIBTTFAUTOHINT)
+#include "DLLoader.hpp"
+#endif
+
+class TTFAutohint
+#if defined(HAVE_TTFAUTOHINT_H) && !defined(HAVE_LIBTTFAUTOHINT)
+ : public DLLoader
+#endif
+{
+ public:
+ TTFAutohint ();
+ TTFAutohint (const TTFAutohint &ta) =delete;
+ bool available () const;
+ int autohint (const std::string &source, const std::string &target, bool rehintIfSymbolFont);
+ std::string lastErrorMessage () const;
+ std::string version () const;
+
+#ifdef HAVE_TTFAUTOHINT_H
+ private:
+ const unsigned char *_lastErrorMessage;
+#endif
+};
+
+#endif
+
diff --git a/dviware/dvisvgm/src/TensorProductPatch.cpp b/dviware/dvisvgm/src/TensorProductPatch.cpp
new file mode 100644
index 0000000000..13650d71ee
--- /dev/null
+++ b/dviware/dvisvgm/src/TensorProductPatch.cpp
@@ -0,0 +1,548 @@
+/*************************************************************************
+** TensorProductPatch.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <valarray>
+#include "TensorProductPatch.hpp"
+
+using namespace std;
+
+
+TensorProductPatch::TensorProductPatch (const PointVec &points, const ColorVec &colors, Color::ColorSpace cspace, int edgeflag, TensorProductPatch *patch)
+ : ShadingPatch(cspace)
+{
+ setPoints(points, edgeflag, patch);
+ setColors(colors, edgeflag, patch);
+}
+
+
+void TensorProductPatch::setFirstMatrixColumn (const DPair source[4], bool reverse) {
+ for (int i=0; i < 4; i++)
+ _points[i][0] = source[reverse ? 3-i : i];
+}
+
+
+void TensorProductPatch::setFirstMatrixColumn (DPair source[4][4], int col, bool reverse) {
+ for (int i=0; i < 4; i++)
+ _points[i][0] = source[reverse ? 3-i : i][col];
+}
+
+
+/*void TensorProductPatch::setPoints (const DPair points[4][4]) {
+ for (int i=0; i < 4; i++)
+ for (int j=0; j < 4; j++)
+ _points[i][j] = points[i][j];
+}*/
+
+
+/** Sets the control points defining the structure of the patch. If the edge flag is 0,
+ * the point vector must contain all 16 control points of the 4x4 matrix in "spiral" order:
+ * 0 11 10 9
+ * 1 12 15 8
+ * 2 13 14 7
+ * 3 4 5 6
+ * If the edge flag is 1,2, or 3, the points of the first matrix collumn
+ * are omitted, and taken from a reference patch instead.
+ * @param[in] points the control points in "spiral" order as described in the PS reference, p. 286
+ * @param[in] edgeflag defines how to connect this patch with another one
+ * @param[in] patch reference patch required if edgeflag > 0 */
+void TensorProductPatch::setPoints (const PointVec &points, int edgeflag, ShadingPatch *patch) {
+ auto tpPatch = dynamic_cast<TensorProductPatch*>(patch);
+ if (edgeflag > 0 && !tpPatch)
+ throw ShadingException("missing preceding data in definition of tensor-product patch");
+ if ((edgeflag == 0 && points.size() != 16) || (edgeflag > 0 && points.size() != 12))
+ throw ShadingException("invalid number of control points in tensor-product patch definition");
+
+ // assign the 12 control points that are invariant for all edge flag values
+ int i = (edgeflag == 0 ? 4 : 0);
+ _points[3][1] = points[i++];
+ _points[3][2] = points[i++];
+ _points[3][3] = points[i++];
+ _points[2][3] = points[i++];
+ _points[1][3] = points[i++];
+ _points[0][3] = points[i++];
+ _points[0][2] = points[i++];
+ _points[0][1] = points[i++];
+ _points[1][1] = points[i++];
+ _points[2][1] = points[i++];
+ _points[2][2] = points[i++];
+ _points[1][2] = points[i];
+ // populate the first column of the control point matrix
+ switch (edgeflag) {
+ case 0: setFirstMatrixColumn(&points[0], false); break;
+ case 1: setFirstMatrixColumn(tpPatch->_points[3], false); break;
+ case 2: setFirstMatrixColumn(tpPatch->_points, 3, true); break;
+ case 3: setFirstMatrixColumn(tpPatch->_points[0], true); break;
+ }
+}
+
+
+/** Sets the vertex colors of the patch. If the edge flag is 0,
+ * the color vector must contain all 4 colors in the following order:
+ * c00, c30, c33, c03, where cXY belongs to the vertex pXY of the control
+ * point matrix.
+ * c00 ---- c03
+ * | |
+ * | |
+ * c30 ---- c33
+ * If the edge flag is 1,2, or 3, the colors c00 and c30 are omitted,
+ * and taken from a reference patch instead.
+ * @param[in] points the color values in the order c00, c30, c33, c03
+ * @param[in] edgeflag defines how to connect this patch with another one
+ * @param[in] patch reference patch required if edgeflag > 0 */
+void TensorProductPatch::setColors(const ColorVec &colors, int edgeflag, ShadingPatch* patch) {
+ auto tpPatch = dynamic_cast<TensorProductPatch*>(patch);
+ if (edgeflag > 0 && !tpPatch)
+ throw ShadingException("missing preceding data in definition of tensor-product patch");
+ if ((edgeflag == 0 && colors.size() != 4) || (edgeflag > 0 && colors.size() != 2))
+ throw ShadingException("invalid number of colors in tensor-product patch definition");
+
+ int i = (edgeflag == 0 ? 2 : 0);
+ _colors[3] = colors[i];
+ _colors[1] = colors[i+1];
+ switch (edgeflag) {
+ case 0: _colors[0] = colors[0]; _colors[2] = colors[1]; break;
+ case 1: _colors[0] = tpPatch->_colors[2]; _colors[2] = tpPatch->_colors[3]; break;
+ case 2: _colors[0] = tpPatch->_colors[3]; _colors[2] = tpPatch->_colors[1]; break;
+ case 3: _colors[0] = tpPatch->_colors[1]; _colors[2] = tpPatch->_colors[0]; break;
+ }
+}
+
+
+/** Returns the point P(u,v) of the patch. */
+DPair TensorProductPatch::valueAt (double u, double v) const {
+ // check if we can return one of the vertices
+ if (u == 0) {
+ if (v == 0)
+ return _points[0][0];
+ else if (v == 1)
+ return _points[3][0];
+ }
+ else if (u == 1) {
+ if (v == 0)
+ return _points[0][3];
+ else if (v == 1)
+ return _points[3][3];
+ }
+ // compute tensor product
+ DPair p[4];
+ for (int i=0; i < 4; i++) {
+ Bezier bezier(_points[i][0], _points[i][1], _points[i][2], _points[i][3]);
+ p[i] = bezier.valueAt(u);
+ }
+ Bezier bezier(p[0], p[1], p[2], p[3]);
+ return bezier.valueAt(v);
+}
+
+
+/** Returns the color at point P(u,v) which is bilinearly interpolated from
+ * the colors assigned to vertices of the patch. */
+Color TensorProductPatch::colorAt (double u, double v) const {
+ // check if we can return one of the vertex colors
+ if (u == 0) {
+ if (v == 0)
+ return _colors[0];
+ else if (v == 1)
+ return _colors[2];
+ }
+ else if (u == 1) {
+ if (v == 0)
+ return _colors[1];
+ else if (v == 1)
+ return _colors[3];
+ }
+ // interpolate color
+ ColorGetter getComponents;
+ ColorSetter setComponents;
+ colorQueryFuncs(getComponents, setComponents);
+ valarray<double> comp[4];
+ for (int i=0; i < 4; i++)
+ (_colors[i].*getComponents)(comp[i]);
+ Color color;
+ (color.*setComponents)((1-u)*(1-v)*comp[0] + u*(1-v)*comp[1] + (1-u)*v*comp[2] + u*v*comp[3]);
+ return color;
+}
+
+
+Color TensorProductPatch::averageColor () const {
+ return averageColor(_colors[0], _colors[1], _colors[2], _colors[3]);
+}
+
+
+/** Compute the average of four given colors depending on the assigned color space. */
+Color TensorProductPatch::averageColor (const Color &c1, const Color &c2, const Color &c3, const Color &c4) const {
+ ColorGetter getComponents;
+ ColorSetter setComponents;
+ colorQueryFuncs(getComponents, setComponents);
+ valarray<double> va1, va2, va3, va4;
+ (c1.*getComponents)(va1);
+ (c2.*getComponents)(va2);
+ (c3.*getComponents)(va3);
+ (c4.*getComponents)(va4);
+ Color averageColor;
+ (averageColor.*setComponents)((va1+va2+va3+va4)/4.0);
+ return averageColor;
+}
+
+
+void TensorProductPatch::getBoundaryPath (GraphicsPath<double> &path) const {
+ // Simple approach: Use the outer curves as boundary path. This doesn't always lead
+ // to correct results since, depending on the control points, P(u,v) might exceed
+ // the simple boundary.
+ path.moveto(_points[0][0]);
+ path.cubicto(_points[0][1], _points[0][2], _points[0][3]);
+ path.cubicto(_points[1][3], _points[2][3], _points[3][3]);
+ path.cubicto(_points[3][2], _points[3][1], _points[3][0]);
+ path.cubicto(_points[2][0], _points[1][0], _points[0][0]);
+ path.closepath();
+}
+
+
+/** Computes the bicubically interpolated isoparametric Bézier curve P(u,t) that
+ * runs "vertically" from P(u,0) to P(u,1) through the patch P.
+ * @param[in] u "horizontal" parameter in the range from 0 to 1
+ * @param[out] bezier the resulting Bézier curve */
+void TensorProductPatch::verticalCurve (double u, Bezier &bezier) const {
+ // check for simple cases (boundary curves) first
+ if (u == 0)
+ bezier.setPoints(_points[0][0], _points[1][0], _points[2][0], _points[3][0]);
+ else if (u == 1)
+ bezier.setPoints(_points[0][3], _points[1][3], _points[2][3], _points[3][3]);
+ else {
+ // compute "inner" curve
+ DPair p[4];
+ for (int i=0; i < 4; i++) {
+ Bezier bezier(_points[i][0], _points[i][1], _points[i][2], _points[i][3]);
+ p[i] = bezier.valueAt(u);
+ }
+ bezier.setPoints(p[0], p[1], p[2], p[3]);
+ }
+}
+
+
+/** Computes the bicubically interpolated isoparametric Bézier curve P(t,v) that
+ * runs "horizontally" from P(0,v) to P(1,v) through the patch P.
+ * @param[in] v "vertical" parameter in the range from 0 to 1
+ * @param[out] bezier the resulting Bézier curve */
+void TensorProductPatch::horizontalCurve (double v, Bezier &bezier) const {
+ // check for simple cases (boundary curves) first
+ if (v == 0)
+ bezier.setPoints(_points[0][0], _points[0][1], _points[0][2], _points[0][3]);
+ else if (v == 1)
+ bezier.setPoints(_points[3][0], _points[3][1], _points[3][2], _points[3][3]);
+ else {
+ // compute "inner" curve
+ DPair p[4];
+ for (int i=0; i < 4; i++) {
+ Bezier bezier(_points[0][i], _points[1][i], _points[2][i], _points[3][i]);
+ p[i] = bezier.valueAt(v);
+ }
+ bezier.setPoints(p[0], p[1], p[2], p[3]);
+ }
+}
+
+
+/** Computes the sub-patch that maps the unit square [0,1]x[0,1] to
+ * the area P([u1,u2],[v1,v2]) of patch P. The control points of the sub-patch
+ * can easily be calculated using the tensor product blossom of patch P.
+ * See G. Farin: Curves and Surfaces for CAGD, p. 259 for example. */
+void TensorProductPatch::subpatch (double u1, double u2, double v1, double v2, TensorProductPatch &patch) const {
+ if (u1 > u2) swap(u1, u2);
+ if (v1 > v2) swap(v1, v2);
+ // compute control points
+ double u[] = {u1, u1, u1, 0}; // blossom parameters of the "horizontal" domain (plus dummy value 0)
+ for (int i=0; i < 4; i++) {
+ u[3-i] = u2;
+ double v[] = {v1, v1, v1, 0}; // blossom parameters of the "vertical" domain (plus dummy value 0)
+ for (int j=0; j < 4; j++) {
+ v[3-j] = v2;
+ patch._points[i][j] = blossomValue(u, v);
+ }
+ }
+ // assign color values
+ patch._colors[0] = colorAt(u1, v1);
+ patch._colors[1] = colorAt(u2, v1);
+ patch._colors[2] = colorAt(u1, v2);
+ patch._colors[3] = colorAt(u2, v2);
+}
+
+
+/** Computes the value b(u1,u2,u3;v1,v2,v3) where b is tensor product blossom of the patch. */
+DPair TensorProductPatch::blossomValue (double u1, double u2, double u3, double v1, double v2, double v3) const {
+ DPair p[4];
+ for (int i=0; i < 4; i++) {
+ Bezier bezier(_points[i][0], _points[i][1], _points[i][2], _points[i][3]);
+ p[i] = bezier.blossomValue(u1, u2, u3);
+ }
+ Bezier bezier(p[0], p[1], p[2], p[3]);
+ return bezier.blossomValue(v1, v2, v3);
+}
+
+
+/** Snaps value x to the interval [0,1]. Values lesser than or near 0 are mapped to 0, values
+ * greater than or near 1 are mapped to 1. */
+static inline double snap (double x) {
+ if (fabs(x) < 0.001)
+ return 0;
+ if (fabs(1-x) < 0.001)
+ return 1;
+ return x;
+}
+
+
+/** Computes a single row of segments approximating the patch region between v1 and v1+inc. */
+void TensorProductPatch::approximateRow (double v1, double inc, bool overlap, double delta, const vector<Bezier> &vbeziers, Callback &callback) const {
+ double v2 = snap(v1+inc);
+ double ov2 = (overlap && v2 < 1) ? snap(v2+inc) : v2;
+ Bezier hbezier1, hbezier2;
+ horizontalCurve(v1, hbezier1);
+ horizontalCurve(ov2, hbezier2);
+ double u1 = 0;
+ for (size_t i=1; i < vbeziers.size(); i++) {
+ double u2 = snap(u1+inc);
+ double ou2 = (overlap && u2 < 1) ? snap(u2+inc) : u2;
+ // compute segment boundaries
+ Bezier b1(hbezier1, u1, ou2);
+ Bezier b2(vbeziers[i + (overlap && i < vbeziers.size()-1 ? 1 : 0)], v1, ov2);
+ Bezier b3(hbezier2, u1, ou2);
+ Bezier b4(vbeziers[i-1], v1, ov2);
+ GraphicsPath<double> path;
+ path.moveto(b1.point(0));
+ if (inc > delta) {
+ path.cubicto(b1.point(1), b1.point(2), b1.point(3));
+ path.cubicto(b2.point(1), b2.point(2), b2.point(3));
+ path.cubicto(b3.point(2), b3.point(1), b3.point(0));
+ path.cubicto(b4.point(2), b4.point(1), b4.point(0));
+ }
+ else {
+ path.lineto(b1.point(3));
+ path.lineto(b2.point(3));
+ path.lineto(b3.point(0));
+ }
+ path.closepath();
+ callback.patchSegment(path, averageColor(colorAt(u1, v1), colorAt(u2, v1), colorAt(u1, v2), colorAt(u2, v2)));
+ u1 = u2;
+ }
+}
+
+
+/** Approximate the patch by dividing it into a grid of segments that are filled with the
+ * average color of the corresponding region. The boundary of each segment consists of
+ * four Bézier curves, too. In order to prevent visual gaps between neighbored segments due
+ * to anti-aliasing, the flag 'overlap' can be set. It enlarges the segments so that they overlap
+ * with their right and bottom neighbors (which are drawn on top of the overlapping regions).
+ * @param[in] gridsize number of segments per row/column
+ * @param[in] overlap if true, enlarge each segment to overlap with its right and bottom neighbors
+ * @param[in] delta reduce level of detail if the segment size is smaller than the given value
+ * @param[in] callback object notified */
+void TensorProductPatch::approximate (int gridsize, bool overlap, double delta, Callback &callback) const {
+ if (_colors[0] == _colors[1] && _colors[1] == _colors[2] && _colors[2] == _colors[3]) {
+ // simple case: monochromatic patch
+ GraphicsPath<double> path;
+ getBoundaryPath(path);
+ callback.patchSegment(path, _colors[0]);
+ }
+ else {
+ const double inc = 1.0/gridsize;
+ // collect curves dividing the patch into several columns (curved vertical stripes)
+ vector<Bezier> vbeziers(gridsize+1);
+ double u=0;
+ for (int i=0; i <= gridsize; i++) {
+ verticalCurve(u, vbeziers[i]);
+ u = snap(u+inc);
+ }
+ // compute the segments row by row
+ double v=0;
+ for (int i=0; i < gridsize; i++) {
+ approximateRow(v, inc, overlap, delta, vbeziers, callback);
+ v = snap(v+inc);
+ }
+ }
+}
+
+
+void TensorProductPatch::getBBox (BoundingBox &bbox) const {
+ Bezier bezier;
+ BoundingBox bezierBox;
+ for (int i=0; i <= 1; i++) {
+ horizontalCurve(i, bezier);
+ bezier.getBBox(bezierBox);
+ bbox.embed(bezierBox);
+ verticalCurve(i, bezier);
+ bezier.getBBox(bezierBox);
+ bbox.embed(bezierBox);
+ }
+}
+
+
+#if 0
+void TensorProductPatch::approximate (int gridsize, Callback &callback) const {
+ const double inc = 1.0/gridsize;
+ Bezier ubezier0; verticalCurve(0, ubezier0);
+ Bezier ubezier1; verticalCurve(1, ubezier1);
+ Bezier vbezier0; horizontalCurve(0, vbezier0);
+ Bezier vbezier1; horizontalCurve(1, vbezier1);
+ for (double v1=0; v1 < 1; v1=snap(v1+inc)) {
+ double v2 = snap(v1+inc);
+ DPair p0 = valueAt(0, v1);
+ DPair p2 = valueAt(0, v2);
+ Color c0 = colorAt(0, v1);
+ Color c2 = colorAt(0, v2);
+ double u1 = 0;
+ for (double u2=inc; u2 <= 1; u2=snap(u2+inc)) {
+ DPair p1 = valueAt(u2, v1);
+ DPair p3 = valueAt(u2, v2);
+ Color c1 = colorAt(u2, v1);
+ Color c3 = colorAt(u2, v2);
+ // Compute a single patch segment. Only those segment edges that lay on the
+ // patch boundary are drawn as Bézier curves, all other edges are approximated
+ // with straight lines. This ensures a smooth outline and reduces the number of
+ // time consuming computations.
+ GraphicsPath<double> path;
+ path.moveto(p0);
+ if (v1 > 0)
+ path.lineto(p1);
+ else {
+ Bezier bezier(vbezier0, u1, u2);
+ path.cubicto(bezier.point(1), bezier.point(2), bezier.point(3));
+ }
+ if (u2 < 1)
+ path.lineto(p3);
+ else {
+ Bezier bezier(ubezier1, v1, v2);
+ path.cubicto(bezier.point(1), bezier.point(2), bezier.point(3));
+ }
+ if (v2 < 1)
+ path.lineto(p2);
+ else {
+ Bezier bezier(vbezier1, u1, u2);
+ path.cubicto(bezier.point(2), bezier.point(1), bezier.point(0));
+ }
+ if (u1 > 0)
+ path.closepath();
+ else {
+ Bezier bezier(ubezier0, v1, v2);
+ path.cubicto(bezier.point(2), bezier.point(1), bezier.point(0));
+ path.closepath();
+ }
+ callback.patchSegment(path, averageColor(c0, c1, c2, c3));
+ p0 = p1;
+ p2 = p3;
+ c0 = c1;
+ c2 = c3;
+ u1 = u2;
+ }
+ }
+}
+#endif
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+
+
+CoonsPatch::CoonsPatch (const PointVec &points, const ColorVec &colors, Color::ColorSpace cspace, int edgeflag, CoonsPatch *patch)
+ : TensorProductPatch(cspace)
+{
+ setPoints(points, edgeflag, patch);
+ setColors(colors, edgeflag, patch);
+}
+
+
+DPair CoonsPatch::valueAt (double u, double v) const {
+ // Compute the value of P(u,v) using the Coons equation rather than the
+ // tensor product since the "inner" control points of the tensor matrix
+ // might not be set yet.
+ Bezier bezier1(_points[3][0], _points[3][1], _points[3][2], _points[3][3]);
+ Bezier bezier2(_points[0][0], _points[0][1], _points[0][2], _points[0][3]);
+ Bezier bezier3(_points[3][0], _points[2][0], _points[1][0], _points[0][0]);
+ Bezier bezier4(_points[3][3], _points[2][3], _points[1][3], _points[0][3]);
+ DPair ph = bezier1.valueAt(u)*(1-v) + bezier2.valueAt(u)*v;
+ DPair pv = bezier3.valueAt(v)*(1-u) + bezier4.valueAt(v)*u;
+ DPair pc = (_points[3][0]*(1-u) + _points[3][3]*u)*(1-v) + (_points[0][0]*(1-u) + _points[0][3]*u)*v;
+ return ph+pv-pc;
+}
+
+
+/** Sets the 12 control points defining the geometry of the coons patch. The points
+ * must be given in the following order:
+ * 3 4 5 6
+ * 2 7
+ * 1 8
+ * 0 11 10 9
+ * where each edge of the square represents the four control points of a cubic Bézier curve.
+ * If the edge flag is 1, 2, or 3, the points 0 to 3 are omitted, and taken from a reference
+ * patch instead.
+ * @param[in] points the control points in cyclic order as described in the PS reference, p. 281
+ * @param[in] edgeflag defines how to connect this patch to another one
+ * @param[in] patch reference patch required if edgeflag > 0 */
+void CoonsPatch::setPoints (const PointVec &points, int edgeflag, ShadingPatch *patch) {
+ auto coonsPatch = dynamic_cast<CoonsPatch*>(patch);
+ if (edgeflag > 0 && !coonsPatch)
+ throw ShadingException("missing preceding data in definition of relative Coons patch");
+ if ((edgeflag == 0 && points.size() != 12) || (edgeflag > 0 && points.size() != 8))
+ throw ShadingException("invalid number of control points in Coons patch definition");
+
+ // Since a Coons patch is a special tensor product patch, we only have to reorder the
+ // control points and compute the additional "inner" points of the 4x4 point tensor matrix.
+
+ // set outer control points of the tensor matrix except those of the first column
+ // because these points depend on the edge flag
+ int i = (edgeflag == 0 ? 4 : 0);
+ _points[3][1] = points[i++];
+ _points[3][2] = points[i++];
+ _points[3][3] = points[i++];
+ _points[2][3] = points[i++];
+ _points[1][3] = points[i++];
+ _points[0][3] = points[i++];
+ _points[0][2] = points[i++];
+ _points[0][1] = points[i];
+
+ // set control points of first matrix column
+ switch (edgeflag) {
+ case 0: setFirstMatrixColumn(&points[0], false); break;
+ case 1: setFirstMatrixColumn(coonsPatch->_points[3], false); break;
+ case 2: setFirstMatrixColumn(coonsPatch->_points, 3, true); break;
+ case 3: setFirstMatrixColumn(coonsPatch->_points[0], true); break;
+ }
+ // compute inner control points of the tensor matrix
+ _points[1][1] = valueAt(1.0/3.0, 2.0/3.0);
+ _points[1][2] = valueAt(2.0/3.0, 2.0/3.0);
+ _points[2][1] = valueAt(1.0/3.0, 1.0/3.0);
+ _points[2][2] = valueAt(2.0/3.0, 1.0/3.0);
+}
+
+
+void CoonsPatch::setColors (const ColorVec &colors, int edgeflag, ShadingPatch *patch) {
+ auto coonsPatch = dynamic_cast<CoonsPatch*>(patch);
+ if (edgeflag > 0 && !coonsPatch)
+ throw ShadingException("missing preceding data in definition of relative Coons patch");
+ if ((edgeflag == 0 && colors.size() != 4) || (edgeflag > 0 && colors.size() != 2))
+ throw ShadingException("invalid number of colors in Coons patch definition");
+
+ int i = (edgeflag == 0 ? 2 : 0);
+ _colors[3] = colors[i];
+ _colors[1] = colors[i+1];
+ switch (edgeflag) {
+ case 0: _colors[0] = colors[0]; _colors[2] = colors[1]; break;
+ case 1: _colors[0] = coonsPatch->_colors[2]; _colors[2] = coonsPatch->_colors[3]; break;
+ case 2: _colors[0] = coonsPatch->_colors[3]; _colors[2] = coonsPatch->_colors[1]; break;
+ case 3: _colors[0] = coonsPatch->_colors[1]; _colors[2] = coonsPatch->_colors[0]; break;
+ }
+}
+
diff --git a/dviware/dvisvgm/src/TensorProductPatch.hpp b/dviware/dvisvgm/src/TensorProductPatch.hpp
new file mode 100644
index 0000000000..444b93fd39
--- /dev/null
+++ b/dviware/dvisvgm/src/TensorProductPatch.hpp
@@ -0,0 +1,92 @@
+/*************************************************************************
+** TensorProductPatch.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef TENSORPRODUCTPATCH_HPP
+#define TENSORPRODUCTPATCH_HPP
+
+#include <map>
+#include <ostream>
+#include <vector>
+#include "Bezier.hpp"
+#include "Color.hpp"
+#include "MessageException.hpp"
+#include "Pair.hpp"
+#include "ShadingPatch.hpp"
+
+
+/** This class represents a single tensor product patch P which is defined by 16 control points
+ * and 4 color values. The mapping of the unit square to the patch surface is defined as the sum
+ * \f[P(u,v):=\sum_{i=0}^3\sum_{j=0}^3 p_{ij} B_i(u) B_j(v)\f]
+ * where \f$B_k(t)={3\choose k}(1-t)^k t^k\f$ and \f$u,v \in [0,1]\f$. The four colors assigned
+ * to the vertices are interpolated bilinearily over the unit square. */
+class TensorProductPatch : public ShadingPatch {
+ friend class CoonsPatch;
+ public:
+ TensorProductPatch () : ShadingPatch(Color::ColorSpace::RGB) {}
+ explicit TensorProductPatch (Color::ColorSpace cspace) : ShadingPatch(cspace) {}
+ TensorProductPatch (const PointVec &points, const ColorVec &colors, Color::ColorSpace cspace, int edgeflag, TensorProductPatch *patch);
+ int psShadingType() const override {return 7;}
+ void setPoints (const DPair points[4][4], int edgeflag, TensorProductPatch *patch);
+ void setPoints (const PointVec &points, int edgeflag, ShadingPatch *patch) override;
+ void setColors (const ColorVec &colors, int edgeflag, ShadingPatch *patch) override;
+ virtual DPair valueAt (double u, double v) const;
+ Color colorAt (double u, double v) const;
+ Color averageColor () const override;
+ void horizontalCurve (double v, Bezier &bezier) const;
+ void verticalCurve (double u, Bezier &bezier) const;
+ void getBoundaryPath (GraphicsPath<double> &path) const override;
+ void subpatch (double u1, double u2, double v1, double v2, TensorProductPatch &patch) const;
+ DPair blossomValue (double u1, double u2, double u3, double v1, double v2, double v3) const;
+ DPair blossomValue (double u[3], double v[3]) const {return blossomValue(u[0], u[1], u[2], v[0], v[1], v[2]);}
+ void approximate (int gridsize, bool overlap, double delta, Callback &callback) const override;
+ void getBBox (BoundingBox &bbox) const override;
+ int numPoints (int edgeflag) const override {return edgeflag == 0 ? 16 : 12;}
+ int numColors (int edgeflag) const override {return edgeflag == 0 ? 4 : 2;}
+
+ protected:
+ Color averageColor (const Color &c1, const Color &c2, const Color &c3, const Color &c4) const;
+ void approximateRow (double v1, double inc, bool overlap, double delta, const std::vector<Bezier> &beziers, Callback &callback) const;
+ void setFirstMatrixColumn (const DPair source[4], bool reverse);
+ void setFirstMatrixColumn (DPair source[4][4], int col, bool reverse);
+
+ private:
+ DPair _points[4][4]; ///< control point matrix defining the patch surface
+ Color _colors[4]; ///< vertex colors cK (c0->p00, c1->p03, c2->p30, c3->p33)
+};
+
+
+/** Coons patches are special tensor product patches where the four "inner" control points
+ * depend on the outer ones, i.e. they are computed automatically and can't be set by the user.
+ * Thus, a Coons patch is defined by 12 control points, 4 vertex colors and a corresponding
+ * color space. */
+class CoonsPatch : public TensorProductPatch {
+ public:
+ CoonsPatch () =default;
+ explicit CoonsPatch (Color::ColorSpace cspace) : TensorProductPatch(cspace) {}
+ CoonsPatch (const PointVec &points, const ColorVec &colors, Color::ColorSpace cspace, int edgeflag, CoonsPatch *patch);
+ int psShadingType() const override {return 6;}
+ void setPoints (const PointVec &points, int edgeflag, ShadingPatch *patch) override;
+ void setColors (const ColorVec &colors, int edgeflag, ShadingPatch *patch) override;
+ DPair valueAt (double u, double v) const override;
+ int numPoints (int edgeflag) const override {return edgeflag == 0 ? 12 : 8;}
+ int numColors (int edgeflag) const override {return edgeflag == 0 ? 4 : 2;}
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/Terminal.cpp b/dviware/dvisvgm/src/Terminal.cpp
new file mode 100644
index 0000000000..0e46874895
--- /dev/null
+++ b/dviware/dvisvgm/src/Terminal.cpp
@@ -0,0 +1,208 @@
+/*************************************************************************
+** Terminal.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <config.h>
+#include "Terminal.hpp"
+
+#ifdef HAVE_TERMIOS_H
+#include <termios.h>
+#endif
+
+#ifdef GWINSZ_IN_SYS_IOCTL
+#include <sys/ioctl.h>
+#endif
+
+#ifdef _WIN32
+#include "windows.hpp"
+#endif
+
+#include <cstdio>
+
+
+using namespace std;
+
+
+const int Terminal::RED = 1;
+const int Terminal::GREEN = 2;
+const int Terminal::BLUE = 4;
+
+const int Terminal::CYAN = GREEN|BLUE;
+const int Terminal::YELLOW = RED|GREEN;
+const int Terminal::MAGENTA = RED|BLUE;
+const int Terminal::WHITE = RED|GREEN|BLUE;
+const int Terminal::DEFAULT = -1;
+const int Terminal::BLACK = 0;
+
+#ifdef _WIN32
+int Terminal::_defaultColor;
+int Terminal::_cursorHeight;
+#endif
+
+int Terminal::_fgcolor = Terminal::DEFAULT;
+int Terminal::_bgcolor = Terminal::DEFAULT;
+
+
+/** Initializes the terminal. This method should be called before any of the others.
+ * @param[in,out] os terminal output stream (currently unused) */
+void Terminal::init (ostream &os) {
+#ifdef _WIN32
+ HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
+ if (h != INVALID_HANDLE_VALUE) {
+ CONSOLE_SCREEN_BUFFER_INFO buffer_info;
+ GetConsoleScreenBufferInfo(h, &buffer_info);
+ _defaultColor = (buffer_info.wAttributes & 0xff);
+ CONSOLE_CURSOR_INFO cursor_info;
+ GetConsoleCursorInfo(h, &cursor_info);
+ _cursorHeight = cursor_info.dwSize;
+ }
+#endif
+}
+
+
+/** Finishes the terminal output. Should be called after last terminal action.
+ * @param[in,out] os terminal output stream */
+void Terminal::finish (ostream &os) {
+ fgcolor(DEFAULT, os);
+ bgcolor(DEFAULT, os);
+ cursor(true);
+}
+
+
+/** Returns the number of terminal columns (number of characters per row).
+ * If it's not possible to retrieve information about the terminal size, 0 is returned. */
+int Terminal::columns () {
+#if defined(TIOCGWINSZ)
+ struct winsize ws;
+ if (ioctl(fileno(stderr), TIOCGWINSZ, &ws) < 0)
+ return 0;
+ return ws.ws_col;
+#elif defined(_WIN32)
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_ERROR_HANDLE), &info))
+ return 0;
+ return info.dwSize.X;
+#else
+ return 0;
+#endif
+}
+
+
+/** Returns the number of terminal rows.
+ * If it's not possible to retrieve information about the terminal size, 0 is returned. */
+int Terminal::rows () {
+#if defined(TIOCGWINSZ)
+ struct winsize ws;
+ if (ioctl(fileno(stderr), TIOCGWINSZ, &ws) < 0)
+ return 0;
+ return ws.ws_row;
+#elif defined(_WIN32)
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_ERROR_HANDLE), &info))
+ return 0;
+ return info.dwSize.Y;
+#else
+ return 0;
+#endif
+}
+
+
+/** Sets the foreground color.
+ * @param[in] color color code
+ * @param[in] os terminal output stream */
+void Terminal::fgcolor (int color, ostream &os) {
+ _fgcolor = color;
+
+#ifdef _WIN32
+ HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
+ if (h != INVALID_HANDLE_VALUE) {
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ GetConsoleScreenBufferInfo(h, &info);
+ if (_fgcolor == DEFAULT)
+ color = _defaultColor & 0x0f;
+ else {
+ // swap red and blue bits
+ color = (color & 0x0a) | ((color & 1) << 2) | ((color & 4) >> 2);
+ }
+ color = (info.wAttributes & 0xf0) | (color & 0x0f);
+ SetConsoleTextAttribute(h, (DWORD)color);
+ }
+#else
+ bool light = false;
+ if (color != DEFAULT && color > 7) {
+ light = true;
+ color %= 8;
+ }
+ if (color == DEFAULT) {
+ os << "\x1B[0m";
+ if (_bgcolor != DEFAULT)
+ bgcolor(_bgcolor, os);
+ }
+ else
+ os << "\x1B[" << (light ? '1': '0') << ';' << (30+(color & 0x07)) << 'm';
+#endif
+}
+
+
+/** Sets the background color.
+ * @param[in] color color code
+ * @param[in] os terminal output stream */
+void Terminal::bgcolor (int color, ostream &os) {
+ _bgcolor = color;
+#ifdef _WIN32
+ HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
+ if (h != INVALID_HANDLE_VALUE) {
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ GetConsoleScreenBufferInfo(h, &info);
+ if (_bgcolor == DEFAULT)
+ color = (_defaultColor & 0xf0) >> 4;
+ else {
+ // swap red and blue bits
+ color = (color & 0x0a) | ((color & 1) << 2) | ((color & 4) >> 2);
+ }
+ color = (info.wAttributes & 0x0f) | ((color & 0x0f) << 4);
+ SetConsoleTextAttribute(h, (DWORD)color);
+ }
+#else
+ if (color != DEFAULT && color > 7)
+ color %= 8;
+ if (color == DEFAULT) {
+ os << "\x1B[0m";
+ if (_fgcolor != DEFAULT)
+ fgcolor(_fgcolor, os);
+ }
+ else
+ os << "\x1B[" << (40+(color & 0x07)) << 'm';
+#endif
+}
+
+
+/** Disables or enables the console cursor
+ * @param[in] visible if false, the cursor is disabled, and enabled otherwise */
+void Terminal::cursor (bool visible) {
+#ifdef _WIN32
+ HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
+ if (h != INVALID_HANDLE_VALUE) {
+ CONSOLE_CURSOR_INFO cursor_info;
+ cursor_info.bVisible = visible;
+ cursor_info.dwSize = _cursorHeight;
+ SetConsoleCursorInfo(h, &cursor_info);
+ }
+#endif
+}
diff --git a/dviware/dvisvgm/src/Terminal.hpp b/dviware/dvisvgm/src/Terminal.hpp
new file mode 100644
index 0000000000..e452aa042f
--- /dev/null
+++ b/dviware/dvisvgm/src/Terminal.hpp
@@ -0,0 +1,58 @@
+/*************************************************************************
+** Terminal.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef TERMINAL_HPP
+#define TERMINAL_HPP
+
+#include <ostream>
+
+class Terminal
+{
+ public:
+ static const int DEFAULT;
+ static const int BLACK;
+ static const int RED;
+ static const int GREEN;
+ static const int BLUE;
+ static const int CYAN;
+ static const int YELLOW;
+ static const int MAGENTA;
+ static const int WHITE;
+
+ public:
+ static void init (std::ostream &os);
+ static void finish (std::ostream &os);
+ static int columns ();
+ static int rows ();
+ static void fgcolor (int color, std::ostream &os);
+ static void bgcolor (int color, std::ostream &os);
+ static void cursor (bool visible);
+
+ private:
+ static int _fgcolor; ///< current foreground color
+ static int _bgcolor; ///< current background color
+
+#ifdef _WIN32
+ static int _defaultColor;
+ static int _cursorHeight; ///< current height of the cursor in percent
+#endif
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/ToUnicodeMap.cpp b/dviware/dvisvgm/src/ToUnicodeMap.cpp
new file mode 100644
index 0000000000..f002038aec
--- /dev/null
+++ b/dviware/dvisvgm/src/ToUnicodeMap.cpp
@@ -0,0 +1,106 @@
+/*************************************************************************
+** ToUnicodeMap.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <limits>
+#include "ToUnicodeMap.hpp"
+#include "Unicode.hpp"
+
+using namespace std;
+
+
+/** Adds valid but random mappings for all missing character indexes.
+ * If a font's cmap table doesn't provide Unicode mappings for some
+ * glyphs in the font, it's necessary to fill the gaps in order to
+ * handle all characters correctly. This functions assumes that the
+ * characters are numbered from 1 to maxIndex.
+ * @param[in] maxIndex largest character index to consider
+ * @return true on success */
+bool ToUnicodeMap::addMissingMappings (uint32_t maxIndex) {
+ bool success=true;
+ // collect Unicode points already in assigned
+ NumericRanges<uint32_t> codepoints;
+ for (size_t i=0; i < numRanges() && success; i++)
+ codepoints.addRange(rangeAt(i).minval(), rangeAt(i).maxval());
+ // fill unmapped ranges
+ if (empty()) // no Unicode mapping present at all?
+ success = fillRange(1, maxIndex, 1, codepoints, true);
+ else { // (partial) Unicode mapping present?
+ success = fillRange(1, rangeAt(0).min()-1, rangeAt(0).minval()-1, codepoints, false);
+ for (size_t i=0; i < numRanges()-1 && success; i++)
+ success = fillRange(rangeAt(i).max()+1, rangeAt(i+1).min()-1, rangeAt(i).maxval()+1, codepoints, true);
+ if (success)
+ success = fillRange(rangeAt(numRanges()-1).max()+1, maxIndex, rangeAt(numRanges()-1).maxval()+1, codepoints, true);
+ }
+ return success;
+}
+
+
+/** Checks if a given codepoint is valid and unused. Otherwise, try to find an alternative.
+ * @param[in,out] ucp codepoint to fix
+ * @param[in] used_codepoints codepoints already in use
+ * @param[in] ascending if true, increase ucp to look for valid/unused codepoints
+ * @return true on success */
+static bool fix_codepoint (uint32_t &ucp, const NumericRanges<uint32_t> &used_codepoints, bool ascending) {
+ uint32_t start = ucp;
+ while (!Unicode::isValidCodepoint(ucp) && used_codepoints.valueExists(ucp)) {
+ if (ascending)
+ ucp = (ucp == numeric_limits<uint32_t>::max()) ? 0 : ucp+1;
+ else
+ ucp = (ucp == 0) ? numeric_limits<uint32_t>::max() : ucp-1;
+ if (ucp == start) // no free Unicode point found
+ return false;
+ }
+ return true;
+}
+
+
+static bool is_less_or_equal (uint32_t a, uint32_t b) {return a <= b;}
+static bool is_greater_or_equal (uint32_t a, uint32_t b) {return a >= b;}
+
+
+/** Adds index to Unicode mappings for a given range of character indexes.
+ * @param[in] minIndex lower bound of range to fill
+ * @param[in] maxIndex upper bound of range to fill
+ * @param[in] ucp first Unicode point to add (if possible)
+ * @param[in,out] used_ucps Unicode points already in use
+ * @param[in] ascending if true, fill range from lower to upper bound
+ * @return true on success */
+bool ToUnicodeMap::fillRange (uint32_t minIndex, uint32_t maxIndex, uint32_t ucp, NumericRanges<uint32_t> &used_ucps, bool ascending) {
+ if (minIndex <= maxIndex) {
+ uint32_t first=minIndex, last=maxIndex;
+ int inc=1;
+ bool (*cmp)(uint32_t, uint32_t) = is_less_or_equal;
+ if (!ascending) {
+ swap(first, last);
+ inc = -1;
+ cmp = is_greater_or_equal;
+ }
+ for (uint32_t i=first; cmp(i, last); i += inc) {
+ if (!fix_codepoint(ucp, used_ucps, ascending))
+ return false;
+ else {
+ addRange(i, i, ucp);
+ used_ucps.addRange(ucp);
+ ucp += inc; // preferred Unicode point for the next character of the current range
+ }
+ }
+ }
+ return true;
+} \ No newline at end of file
diff --git a/dviware/dvisvgm/src/ToUnicodeMap.hpp b/dviware/dvisvgm/src/ToUnicodeMap.hpp
new file mode 100644
index 0000000000..8ab3474e65
--- /dev/null
+++ b/dviware/dvisvgm/src/ToUnicodeMap.hpp
@@ -0,0 +1,38 @@
+/*************************************************************************
+** ToUnicodeMap.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef TOUNICODEMAP_HPP
+#define TOUNICODEMAP_HPP
+
+#include "NumericRanges.hpp"
+#include "RangeMap.hpp"
+
+
+/** Represents a mapping from character indexes to unicode points. */
+class ToUnicodeMap : public RangeMap
+{
+ public:
+ bool addMissingMappings (uint32_t maxIndex);
+
+ protected:
+ bool fillRange (uint32_t minIndex, uint32_t maxIndex, uint32_t ucp, NumericRanges<uint32_t> &used_ucps, bool ascending);
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/TpicSpecialHandler.cpp b/dviware/dvisvgm/src/TpicSpecialHandler.cpp
new file mode 100644
index 0000000000..ee539f6b02
--- /dev/null
+++ b/dviware/dvisvgm/src/TpicSpecialHandler.cpp
@@ -0,0 +1,364 @@
+/*************************************************************************
+** TpicSpecialHandler.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <bitset>
+#include <cstring>
+#include <sstream>
+#include <string>
+#include "Color.hpp"
+#include "InputBuffer.hpp"
+#include "InputReader.hpp"
+#include "GraphicsPath.hpp"
+#include "SpecialActions.hpp"
+#include "SVGTree.hpp"
+#include "TpicSpecialHandler.hpp"
+#include "utility.hpp"
+
+using namespace std;
+
+
+TpicSpecialHandler::TpicSpecialHandler () {
+ reset();
+}
+
+
+void TpicSpecialHandler::dviEndPage (unsigned pageno, SpecialActions&) {
+ reset();
+}
+
+
+void TpicSpecialHandler::reset () {
+ _points.clear();
+ _penwidth = 1.0;
+ _grayLevel = -1.0; // no fill
+}
+
+
+/** Returns the current color used to fill closed paths or shapes.
+ * The TPIC documentation only specifies grayscale colors set by a floating point value
+ * in the range of 0 (white) and 1 (black). Most DVI drivers extend the color support
+ * by taking color specials into account: The grayscale colors are only applied if
+ * the DVI color is set to black. All other colors are directly used as fill color
+ * without evaluating the TPIC gray level. */
+Color TpicSpecialHandler::fillColor (bool grayOnly) const {
+ if (_grayLevel >= 0 && (_dviColor == Color::BLACK || grayOnly))
+ return Color::WHITE*(1.0-_grayLevel);
+ return _dviColor;
+}
+
+
+/** Adds stroke attributes to a given element depending on the pen width.
+ * @param[in] elem element node the attributes are added to
+ * @param[in] penwidth pen with used to compute the stroke parameters
+ * @param[in] pencolor the drawing color
+ * @param[in] ddist dash/dot distance of line in PS point units (0:solid line, >0:dashed line, <0:dotted line) */
+static void add_stroke_attribs (XMLElement *elem, double penwidth, Color pencolor, double ddist) {
+ if (penwidth > 0) { // attributes actually required?
+ elem->addAttribute("stroke", pencolor.svgColorString());
+ elem->addAttribute("stroke-width", XMLString(penwidth));
+ if (ddist > 0)
+ elem->addAttribute("stroke-dasharray", XMLString(ddist));
+ else if (ddist < 0)
+ elem->addAttribute("stroke-dasharray", XMLString(penwidth) + ' ' + XMLString(-ddist));
+ }
+}
+
+
+static unique_ptr<XMLElement> create_ellipse_element (double cx, double cy, double rx, double ry) {
+ bool is_circle = (rx == ry);
+ auto elem = util::make_unique<XMLElement>(is_circle ? "circle" : "ellipse");
+ elem->addAttribute("cx", XMLString(cx));
+ elem->addAttribute("cy", XMLString(cy));
+ if (is_circle)
+ elem->addAttribute("r", XMLString(rx));
+ else {
+ elem->addAttribute("rx", XMLString(rx));
+ elem->addAttribute("ry", XMLString(ry));
+ }
+ return elem;
+}
+
+
+/** Creates SVG elements that draw lines through the recorded points.
+ * @param[in] ddist dash/dot distance of line in PS point units (0:solid line, >0:dashed line, <0:dotted line)
+ * @param[in] actions object providing the actions that can be performed by the SpecialHandler */
+void TpicSpecialHandler::drawLines (double ddist, SpecialActions &actions) {
+ if (!_points.empty() && (_penwidth > 0 || _grayLevel >= 0)) {
+ unique_ptr<XMLElement> elem;
+ if (_points.size() == 1) {
+ const DPair &p = _points.back();
+ elem = create_ellipse_element(p.x()+actions.getX(), p.y()+actions.getY(), _penwidth/2.0, _penwidth/2.0);
+ actions.embed(p, _penwidth/2.0);
+ }
+ else {
+ if (_points.size() == 2 || (_grayLevel < 0 && _points.front() != _points.back())) {
+ elem = util::make_unique<XMLElement>("polyline");
+ elem->addAttribute("fill", "none");
+ elem->addAttribute("stroke-linecap", "round");
+ }
+ else {
+ while (_points.front() == _points.back())
+ _points.pop_back();
+ elem = util::make_unique<XMLElement>("polygon");
+ elem->addAttribute("fill", _grayLevel < 0 ? "none" : fillColor(false).svgColorString());
+ }
+ ostringstream oss;
+ for (auto it=_points.begin(); it != _points.end(); ++it) {
+ if (it != _points.begin())
+ oss << ' ';
+ double x = it->x()+actions.getX();
+ double y = it->y()+actions.getY();
+ oss << XMLString(x) << ',' << XMLString(y);
+ actions.embed(DPair(x, y));
+ }
+ elem->addAttribute("points", oss.str());
+ add_stroke_attribs(elem.get(), _penwidth, Color::BLACK, ddist);
+ }
+ actions.svgTree().appendToPage(std::move(elem));
+ }
+ reset();
+}
+
+
+/** Stroke a quadratic spline through the midpoints of the lines defined by
+ * the previously recorded points. The spline starts with a straight line
+ * from the first point to the mid-point of the first line. The spline ends
+ * with a straight line from the mid-point of the last line to the last point.
+ * If ddist=0, the spline is stroked solid. Otherwise ddist denotes the length
+ * of the dashes and the gaps inbetween.
+ * @param[in] ddist length of dashes and gaps
+ * @param[in] actions object providing the actions that can be performed by the SpecialHandler */
+void TpicSpecialHandler::drawSplines (double ddist, SpecialActions &actions) {
+ if (!_points.empty() && _penwidth > 0) {
+ const size_t numPoints = _points.size();
+ if (numPoints < 3) {
+ _grayLevel = -1;
+ drawLines(ddist, actions);
+ }
+ else {
+ DPair p(actions.getX(), actions.getY());
+ GraphicsPath<double> path;
+ path.moveto(p+_points[0]);
+ DPair mid = p+_points[0]+(_points[1]-_points[0])/2.0;
+ path.lineto(mid);
+ actions.embed(p+_points[0]);
+ for (size_t i=1; i < numPoints-1; i++) {
+ const DPair p0 = p+_points[i-1];
+ const DPair p1 = p+_points[i];
+ const DPair p2 = p+_points[i+1];
+ mid = p1+(p2-p1)/2.0;
+ path.conicto(p1, mid);
+ actions.embed(mid);
+ actions.embed((p0+p1*6.0+p2)/8.0, _penwidth);
+ }
+ if (_points[0] == _points[numPoints-1]) // closed path?
+ path.closepath();
+ else {
+ path.lineto(p+_points[numPoints-1]);
+ actions.embed(p+_points[numPoints-1]);
+ }
+ auto pathElem = util::make_unique<XMLElement>("path");
+ pathElem->addAttribute("fill", "none");
+ ostringstream oss;
+ path.writeSVG(oss, SVGTree::RELATIVE_PATH_CMDS);
+ pathElem->addAttribute("d", oss.str());
+ add_stroke_attribs(pathElem.get(), _penwidth, _dviColor, ddist);
+ actions.svgTree().appendToPage(std::move(pathElem));
+ }
+ }
+ reset();
+}
+
+
+static double normalized_angle (double rad) {
+ rad = fmod(rad, math::TWO_PI);
+ if (rad < 0)
+ rad += math::TWO_PI;
+ return rad;
+}
+
+
+/** Draws an elliptical arc.
+ * @param[in] cx x-coordinate of arc center relative to current DVI position
+ * @param[in] cy y-coordinate of arc center relative to current DVI position
+ * @param[in] rx length of horizontal semi-axis
+ * @param[in] ry length of vertical semi-axis
+ * @param[in] angle1 starting angle (clockwise) relative to x-axis
+ * @param[in] angle2 ending angle (clockwise) relative to x-axis
+ * @param[in] actions object providing the actions that can be performed by the SpecialHandler */
+void TpicSpecialHandler::drawArc (double cx, double cy, double rx, double ry, double angle1, double angle2, SpecialActions &actions) {
+ if (_penwidth > 0 || _grayLevel >= 0) {
+ angle1 = -angle1;
+ angle2 = -angle2;
+ cx += actions.getX();
+ cy += actions.getY();
+ unique_ptr<XMLElement> elem;
+ bool closed=true;
+ if (abs(angle2-angle1) >= math::TWO_PI) // closed ellipse?
+ elem = create_ellipse_element(cx, cy, rx, ry);
+ else {
+ angle1 = normalized_angle(angle1);
+ angle2 = normalized_angle(angle2);
+ double delta = normalized_angle(angle2-angle1);
+ int large_arg = (delta < math::PI) ? 1 : 0;
+ ostringstream oss;
+ oss << 'M' << XMLString(cx+rx*cos(angle1)) << ' ' << XMLString(cy+ry*sin(-angle1))
+ << 'A' << XMLString(rx) << ' ' << XMLString(ry)
+ << " 0 " // no rotation of x-axis
+ << large_arg << " 1 " // always draw arc clockwise (sweep flag == 1)
+ << XMLString(cx+rx*cos(angle2)) << ' ' << XMLString(cy-ry*sin(angle2));
+ if (_grayLevel >= 0)
+ oss << 'Z';
+ else
+ closed = false;
+ elem = util::make_unique<XMLElement>("path");
+ elem->addAttribute("d", oss.str());
+ }
+ if (_penwidth > 0) {
+ elem->addAttribute("stroke-width", _penwidth);
+ elem->addAttribute("stroke", actions.getColor().svgColorString());
+ if (!closed)
+ elem->addAttribute("stroke-linecap", "round");
+ }
+ elem->addAttribute("fill", _grayLevel < 0 ? "none" : fillColor(true).svgColorString());
+ actions.svgTree().appendToPage(std::move(elem));
+ double pw = _penwidth/2.0;
+ actions.embed(BoundingBox(cx-rx-pw, cy-ry-pw, cx+rx+pw, cy+ry+pw));
+ }
+ reset();
+}
+
+
+/** Computes the gray level based on the ratio of set bits to the total
+ * number of bits of a given hex value.
+ * @param[in] hexstr a sequence of hexadecimal digits
+ * @return the computed gray level [0-1] */
+static double bit_sequence_to_gray (const string &hexstr) {
+ if (hexstr.empty())
+ return 1.0;
+ int setbits=0; // number of bits set
+ int totalbits=0; // number of bits processed
+ constexpr int CHUNKBITS = 8*sizeof(unsigned long long);
+ for (size_t pos=0; pos < hexstr.length(); pos+=CHUNKBITS/4) {
+ size_t digitcount; // number of hex digits processed
+ unsigned long long val = stoull(hexstr.substr(pos, CHUNKBITS/4), &digitcount, 16);
+ setbits += bitset<CHUNKBITS>(val).count();
+ totalbits += 4*digitcount;
+ }
+ return totalbits == 0 ? 1.0 : 1.0-double(setbits)/double(totalbits);
+}
+
+
+/** Returns a unique integer for a TPIC command (consisting of two letters). */
+constexpr int cmd_id (const char *cmd) {
+ return (cmd[0] << 8) | cmd[1];
+};
+
+
+bool TpicSpecialHandler::process (const string &prefix, istream &is, SpecialActions &actions) {
+ if (prefix.length() != 2)
+ return false;
+ _dviColor = actions.getColor();
+ const double mi2bp=0.072; // factor for milli-inch to PS points
+ StreamInputBuffer ib(is);
+ BufferInputReader ir(ib);
+ switch (cmd_id(prefix.c_str())) {
+ case cmd_id("pn"): // set pen width in milli-inches
+ _penwidth = max(0.0, ir.getDouble()*mi2bp);
+ break;
+ case cmd_id("bk"): // set fill color to black
+ _grayLevel = 1;
+ break;
+ case cmd_id("wh"): // set fill color to white
+ _grayLevel = 0;
+ break;
+ case cmd_id("sh"): // set fill color to given gray level (0=white, 1=black)
+ ir.skipSpace();
+ _grayLevel = ir.eof() ? 0.5 : max(0.0, min(1.0, ir.getDouble()));
+ break;
+ case cmd_id("tx"): { // set fill color depending on a sequence of bits (given as hex value)
+ string hexstr;
+ while (!ir.eof()) {
+ ir.skipSpace();
+ int c = ir.get();
+ if (isxdigit(c))
+ hexstr += static_cast<char>(tolower(c));
+ else
+ break;
+ }
+ _grayLevel = bit_sequence_to_gray(hexstr);
+ break;
+ }
+ case cmd_id("pa"): { // add point to path
+ double x = ir.getDouble()*mi2bp;
+ double y = ir.getDouble()*mi2bp;
+ _points.emplace_back(DPair(x,y));
+ break;
+ }
+ case cmd_id("fp"): // draw solid lines through recorded points; close and fill path if fill color was defined
+ drawLines(0, actions);
+ break;
+ case cmd_id("ip"): // don't draw outlines but close the recorded path and fill the resulting polygon
+ _penwidth = 0;
+ drawLines(0, actions);
+ break;
+ case cmd_id("da"): // as fp but draw dashed lines
+ drawLines(ir.getDouble()*72, actions);
+ break;
+ case cmd_id("dt"): // as fp but draw dotted lines
+ drawLines(-ir.getDouble()*72, actions);
+ break;
+ case cmd_id("sp"): { // draw quadratic splines through recorded points
+ double ddist = ir.getDouble()*72;
+ drawSplines(ddist, actions);
+ break;
+ }
+ case cmd_id("ar"): { // draw elliptical arc
+ double cx = ir.getDouble()*mi2bp;
+ double cy = ir.getDouble()*mi2bp;
+ double rx = ir.getDouble()*mi2bp;
+ double ry = ir.getDouble()*mi2bp;
+ double a1 = ir.getDouble();
+ double a2 = ir.getDouble();
+ drawArc(cx, cy, rx, ry, a1, a2, actions);
+ break;
+ }
+ case cmd_id("ia"): { // fill elliptical arc
+ double cx = ir.getDouble()*mi2bp;
+ double cy = ir.getDouble()*mi2bp;
+ double rx = ir.getDouble()*mi2bp;
+ double ry = ir.getDouble()*mi2bp;
+ double a1 = ir.getDouble();
+ double a2 = ir.getDouble();
+ _penwidth = 0;
+ drawArc(cx, cy, rx, ry, a1, a2, actions);
+ break;
+ }
+ default:
+ return false;
+ }
+ return true;
+}
+
+
+vector<const char*> TpicSpecialHandler::prefixes() const {
+ vector<const char*> pfx {"ar", "bk", "da", "dt", "fp", "ia", "ip", "pa", "pn", "sh", "sp", "tx", "wh"};
+ return pfx;
+}
diff --git a/dviware/dvisvgm/src/TpicSpecialHandler.hpp b/dviware/dvisvgm/src/TpicSpecialHandler.hpp
new file mode 100644
index 0000000000..fa8ba706fd
--- /dev/null
+++ b/dviware/dvisvgm/src/TpicSpecialHandler.hpp
@@ -0,0 +1,53 @@
+/*************************************************************************
+** TpicSpecialHandler.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef TPICSPECIALHANDLER_HPP
+#define TPICSPECIALHANDLER_HPP
+
+#include <list>
+#include "Pair.hpp"
+#include "SpecialHandler.hpp"
+
+class TpicSpecialHandler : public SpecialHandler {
+ public:
+ TpicSpecialHandler ();
+ const char* info () const override {return "TPIC specials";}
+ const char* name () const override {return "tpic";}
+ std::vector<const char*> prefixes() const override;
+ bool process (const std::string &prefix, std::istream &is, SpecialActions &actions) override;
+ double penwidth () const {return _penwidth;}
+ double grayLevel () const {return _grayLevel;}
+ Color fillColor (bool grayOnly) const;
+
+ protected:
+ void dviEndPage (unsigned pageno, SpecialActions &actions) override;
+ void reset ();
+ void drawLines (double ddist, SpecialActions &actions);
+ void drawSplines (double ddist, SpecialActions &actions);
+ void drawArc (double cx, double cy, double rx, double ry, double angle1, double angle2, SpecialActions &actions);
+
+ private:
+ double _penwidth; ///< pen width in PS point units
+ double _grayLevel; ///< [0,1]; 0=white, 1=black, if < 0, we don't fill anything
+ std::vector<DPair> _points;
+ Color _dviColor; ///< current DVI color
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/TriangularPatch.cpp b/dviware/dvisvgm/src/TriangularPatch.cpp
new file mode 100644
index 0000000000..929fe1b885
--- /dev/null
+++ b/dviware/dvisvgm/src/TriangularPatch.cpp
@@ -0,0 +1,214 @@
+/*************************************************************************
+** TriangularPatch.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include "TriangularPatch.hpp"
+
+using namespace std;
+
+TriangularPatch::TriangularPatch (const PointVec &points, const ColorVec &colors, Color::ColorSpace cspace, int edgeflag, TriangularPatch *patch)
+ : ShadingPatch(cspace)
+{
+ setPoints(points, edgeflag, patch);
+ setColors(colors, edgeflag, patch);
+}
+
+
+void TriangularPatch::setPoints (const PointVec &points, int edgeflag, ShadingPatch *patch) {
+ auto triangularPatch = dynamic_cast<TriangularPatch*>(patch);
+ if (edgeflag > 0 && !triangularPatch)
+ throw ShadingException("missing preceding data in definition of triangular patch");
+ if ((edgeflag == 0 && points.size() != 3) || (edgeflag > 0 && points.size() != 1))
+ throw ShadingException("invalid number of vertices in triangular patch definition");
+
+ _points[0] = points[0];
+ switch (edgeflag) {
+ case 0:
+ _points[1] = points[1];
+ _points[2] = points[2];
+ break;
+ case 1:
+ _points[1] = triangularPatch->_points[1];
+ _points[2] = triangularPatch->_points[2];
+ break;
+ case 2:
+ _points[1] = triangularPatch->_points[2];
+ _points[2] = triangularPatch->_points[0];
+ }
+}
+
+
+void TriangularPatch::setPoints (const DPair &p1, const DPair &p2, const DPair &p3) {
+ _points[0] = p1;
+ _points[1] = p2;
+ _points[2] = p3;
+}
+
+
+void TriangularPatch::setColors (const ColorVec &colors, int edgeflag, ShadingPatch *patch) {
+ auto triangularPatch = dynamic_cast<TriangularPatch*>(patch);
+ if (edgeflag > 0 && !triangularPatch)
+ throw ShadingException("missing preceding data in definition of triangular patch");
+ if ((edgeflag == 0 && colors.size() != 3) || (edgeflag > 0 && colors.size() != 1))
+ throw ShadingException("invalid number of colors in triangular patch definition");
+
+ _colors[0] = colors[0];
+ switch (edgeflag) {
+ case 0:
+ _colors[1] = colors[1];
+ _colors[2] = colors[2];
+ break;
+ case 1:
+ _colors[1] = triangularPatch->_colors[1];
+ _colors[2] = triangularPatch->_colors[2];
+ break;
+ case 2:
+ _colors[1] = triangularPatch->_colors[2];
+ _colors[2] = triangularPatch->_colors[0];
+ }
+}
+
+
+void TriangularPatch::setColors (const Color &c1, const Color &c2, const Color &c3) {
+ _colors[0] = c1;
+ _colors[1] = c2;
+ _colors[2] = c3;
+}
+
+
+/** Returns the Cartesian coordinates for the barycentric coordinates \f$(u, v, 1-u-v)\f$
+ * of a point of the triangle, where \f$u, v \in [0,1]\f$ and \f$u+v \le 1\f$.
+ * The relation between the vertices of the triangle and their barycentric coordinates
+ * is as follows: \f$(1,0,0)=p_1, (0,1,0)=p_2, (0,0,1)=p_0\f$. */
+DPair TriangularPatch::valueAt (double u, double v) const {
+ return _points[0] + (_points[1]-_points[0])*u + (_points[2]-_points[0])*v;
+}
+
+
+/** Returns the color at a given point of the triangle. The point must be given
+ * in barycentric coordinates \f$(u, v, 1-u-v)\f$, where \f$u, v \in [0,1]\f$
+ * and \f$u+v \le 1\f$.
+ * The relation between the vertices of the triangle and their barycentric coordinates
+ * is as follows: \f$(1,0,0)=p_1, (0,1,0)=p_2, (0,0,1)=p_0\f$. */
+Color TriangularPatch::colorAt (double u, double v) const {
+ ColorGetter getComponents;
+ ColorSetter setComponents;
+ colorQueryFuncs(getComponents, setComponents);
+ valarray<double> comp[3];
+ for (int i=0; i < 3; i++)
+ (_colors[i].*getComponents)(comp[i]);
+ Color color;
+ (color.*setComponents)(comp[0]*(1-u-v) + comp[1]*u + comp[2]*v);
+ return color;
+}
+
+
+Color TriangularPatch::averageColor () const {
+ return averageColor(_colors[0], _colors[1], _colors[2]);
+}
+
+
+/** Compute the average of three given colors depending on the assigned color space. */
+Color TriangularPatch::averageColor (const Color &c1, const Color &c2, const Color &c3) const {
+ ColorGetter getComponents;
+ ColorSetter setComponents;
+ colorQueryFuncs(getComponents, setComponents);
+ valarray<double> va1, va2, va3;
+ (c1.*getComponents)(va1);
+ (c2.*getComponents)(va2);
+ (c3.*getComponents)(va3);
+ Color averageColor;
+ (averageColor.*setComponents)((va1+va2+va3)/3.0);
+ return averageColor;
+}
+
+
+/** Snaps value x to the interval [0,1]. Values lesser than or near 0 are mapped to 0, values
+ * greater than or near 1 are mapped to 1. */
+static inline double snap (double x) {
+ if (fabs(x) < 0.001)
+ return 0;
+ if (fabs(1-x) < 0.001)
+ return 1;
+ return x;
+}
+
+
+/** Approximate the patch by dividing it into a grid of triangular segments that are filled
+ * with the average color of the corresponding region. In order to prevent visual gaps between
+ * adjacent segments due to anti-aliasing, the flag 'overlap' can be set. It enlarges the
+ * segments so that they overlap with their right and bottom neighbors (which are drawn on
+ * top of the overlapping regions).
+ * @param[in] gridsize number of segments per row/column
+ * @param[in] overlap if true, enlarge each segment to overlap with its right and bottom neighbors
+ * @param[in] delta reduce level of detail if the segment size is smaller than the given value
+ * @param[in] callback object notified */
+void TriangularPatch::approximate (int gridsize, bool overlap, double delta, Callback &callback) const {
+ if (_colors[0] == _colors[1] && _colors[1] == _colors[2]) {
+ GraphicsPath<double> path;
+ getBoundaryPath(path);
+ callback.patchSegment(path, _colors[0]);
+ }
+ else {
+ const double inc = 1.0/gridsize;
+ for (double u1=0; u1 < 1; u1=snap(u1+inc)) {
+ double u2 = snap(u1+inc);
+ double ou2 = (overlap && snap(u2+inc) <= 1 ? snap(u2+inc) : u2);
+ for (double v1=0; snap(u1+v1) < 1; v1=snap(v1+inc)) {
+ double v2 = snap(v1+inc);
+ double ov2 = (overlap && snap(v2+inc) <= 1 ? snap(v2+inc) : v2);
+ if (!overlap || (snap(u1+ov2) <= 1 && snap(ou2+v1) <= 1)) {
+ // create triangular segments pointing in the same direction as the whole patch
+ GraphicsPath<double> path;
+ path.moveto(valueAt(u1, v1));
+ path.lineto(valueAt(ou2, v1));
+ path.lineto(valueAt(u1, ov2));
+ path.closepath();
+ callback.patchSegment(path, averageColor(colorAt(u1, v1), colorAt(u2, v1), colorAt(u1, v2)));
+ if (snap(u2+v2) <= 1 && (!overlap || inc > delta)) {
+ // create triangular segments pointing in the opposite direction as the whole patch
+ path.clear();
+ path.moveto(valueAt(u1, v2));
+ path.lineto(valueAt(u2, v1));
+ path.lineto(valueAt(u2, v2));
+ path.closepath();
+ callback.patchSegment(path, averageColor(colorAt(u1, v2), colorAt(u2, v1), colorAt(u2, v2)));
+ }
+ }
+ }
+ }
+ }
+}
+
+
+void TriangularPatch::getBoundaryPath(GraphicsPath<double> &path) const {
+ path.clear();
+ path.moveto(_points[0]);
+ path.lineto(_points[1]);
+ path.lineto(_points[2]);
+ path.closepath();
+}
+
+
+void TriangularPatch::getBBox (BoundingBox &bbox) const {
+ bbox.invalidate();
+ bbox.embed(_points[0]);
+ bbox.embed(_points[1]);
+ bbox.embed(_points[2]);
+}
diff --git a/dviware/dvisvgm/src/TriangularPatch.hpp b/dviware/dvisvgm/src/TriangularPatch.hpp
new file mode 100644
index 0000000000..8c6405ef08
--- /dev/null
+++ b/dviware/dvisvgm/src/TriangularPatch.hpp
@@ -0,0 +1,62 @@
+/*************************************************************************
+** TriangularPatch.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef TRIANGULARPATCH_HPP
+#define TRIANGULARPATCH_HPP
+
+#include "Color.hpp"
+#include "Pair.hpp"
+#include "ShadingPatch.hpp"
+
+class TriangularPatch : public ShadingPatch {
+ public:
+ TriangularPatch ();
+ explicit TriangularPatch (Color::ColorSpace cspace) : ShadingPatch(cspace) {}
+ TriangularPatch (const PointVec &points, const ColorVec &colors, Color::ColorSpace cspace, int edgeflag, TriangularPatch *patch);
+ int psShadingType() const override {return 4;}
+ DPair valueAt (double u, double v) const;
+ Color colorAt (double u, double v) const;
+ Color averageColor() const override;
+ void setPoints (const PointVec &points, int edgeflag, ShadingPatch *patch) override;
+ void setPoints (const DPair &p1, const DPair &p2, const DPair &p3);
+ void setColors (const ColorVec &colors, int edgeflag, ShadingPatch *patch) override;
+ void setColors (const Color &c1, const Color &c2, const Color &c3);
+ void approximate (int gridsize, bool overlap, double delta, Callback &listener) const override;
+ void getBBox (BoundingBox &bbox) const override;
+ void getBoundaryPath(GraphicsPath<double> &path) const override;
+ int numPoints (int edgeflag) const override {return edgeflag == 0 ? 3 : 1;}
+ int numColors (int edgeflag) const override {return edgeflag == 0 ? 3 : 1;}
+
+ protected:
+ Color averageColor (const Color &c1, const Color &c2, const Color &c3) const;
+
+ private:
+ DPair _points[3];
+ Color _colors[3];
+};
+
+
+class LatticeTriangularPatch : public TriangularPatch {
+ public:
+ explicit LatticeTriangularPatch (Color::ColorSpace cspace) : TriangularPatch(cspace) {}
+ int psShadingType() const override {return 5;}
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/TrueTypeFont.cpp b/dviware/dvisvgm/src/TrueTypeFont.cpp
new file mode 100644
index 0000000000..51056c36cc
--- /dev/null
+++ b/dviware/dvisvgm/src/TrueTypeFont.cpp
@@ -0,0 +1,175 @@
+/*************************************************************************
+** TrueTypeFont.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <iterator>
+#include <sstream>
+#include <woff2/encode.h>
+#include <zlib.h>
+#include "StreamReader.hpp"
+#include "StreamWriter.hpp"
+#include "TrueTypeFont.hpp"
+#include "utility.hpp"
+
+using namespace std;
+
+
+/** Reads the font data from a TTF file.
+ * OTF specification: https://www.microsoft.com/typography/otspec/otff.htm
+ * @param[in] fname name/path of file to read
+ * @return true on success */
+bool TrueTypeFont::read (const string &fname) {
+ _tableRecords.clear();
+ _buffer = util::read_file_contents(fname);
+ if (_buffer.empty())
+ return false;
+ istringstream iss(_buffer);
+ StreamReader reader(iss);
+ _version = reader.readUnsigned(4);
+ uint16_t numTables = reader.readUnsigned(2);
+ if (numTables == 0 || _buffer.size() <= 12u+16u*numTables)
+ return false;
+
+ // read table records
+ reader.seek(12); // skip searchRange, entrySelector, and rangeShift
+ for (uint16_t i=0; i < numTables; i++) {
+ uint32_t tag = reader.readUnsigned(4);
+ uint32_t checksum = reader.readUnsigned(4);
+ uint32_t offset = reader.readUnsigned(4);
+ uint32_t length = reader.readUnsigned(4);
+ if ((offset | length) > _buffer.size() || offset+length > _buffer.size())
+ return false;
+ TTFTableRecord record = {tag, checksum, length, reinterpret_cast<const uint8_t*>(_buffer.data())+offset};
+ _tableRecords.emplace_back(std::move(record));
+ }
+ return true;
+}
+
+
+/** Compresses the data of the referenced TTF table and updated the record data accordingly.
+ * @return true if the data was compressed */
+bool TrueTypeFont::WOFFTableRecord::compressTableData () {
+ if (ttfRecord.size < 16)
+ return false;
+ uLong compressedSize = compressBound(ttfRecord.size);
+ compressedData.resize(compressedSize);
+ // Compress with zlib for now. We could also use zopfli for slightly better but much slower compression.
+ // Only use the compressed data if it actually leads to a size reduction. Otherwise, use the original table data.
+ if (compress2(compressedData.data(), &compressedSize, ttfRecord.data, ttfRecord.size, Z_BEST_COMPRESSION) != Z_OK
+ || compressedSize >= ttfRecord.size) {
+ compressedData.clear();
+ return false;
+ }
+ size = compressedSize;
+ data = compressedData.data();
+ return true;
+}
+
+
+/** Writes font data in WOFF format to a given output stream.
+ WOFF specifiction: https://www.w3.org/TR/WOFF */
+void TrueTypeFont::writeWOFF (ostream &os) const {
+ // compute WOFF table records first
+ vector<WOFFTableRecord> woffRecords;
+ uint32_t woffSize = static_cast<uint32_t>(44 + 20*_tableRecords.size());
+ uint32_t ttfSize = static_cast<uint32_t>(12 + 16*_tableRecords.size());
+ for (const TTFTableRecord &ttfRecord : _tableRecords) {
+ WOFFTableRecord woffRecord(woffSize, ttfRecord);
+ woffRecord.compressTableData();
+ woffSize += woffRecord.paddedSize();
+ ttfSize += ttfRecord.paddedSize();
+ woffRecords.emplace_back(std::move(woffRecord));
+ }
+ // write WOFF header
+ StreamWriter writer(os);
+ writer.writeUnsigned(0x774F4646, 4); // "WOFF"
+ writer.writeUnsigned(_version, 4); // version of contained TTF font
+ writer.writeUnsigned(woffSize, 4); // total length of WOFF file
+ writer.writeUnsigned(_tableRecords.size(), 2); // number of tables
+ writer.writeBytes(0, 2); // reserved
+ writer.writeUnsigned(ttfSize, 4); // size of uncompressed TTF data
+ writer.writeBytes(0, 4); // WOFF version (not required)
+ writer.writeBytes(0, 12); // offset, compressed and uncompressed size of metadata block
+ writer.writeBytes(0, 8); // offset and size of private data block
+
+ // write WOFF table directory
+ for (const WOFFTableRecord &woffRecord : woffRecords) {
+ writer.writeUnsigned(woffRecord.ttfRecord.tag, 4);
+ writer.writeUnsigned(woffRecord.offset, 4);
+ writer.writeUnsigned(woffRecord.size, 4);
+ writer.writeUnsigned(woffRecord.ttfRecord.size, 4);
+ writer.writeUnsigned(woffRecord.ttfRecord.checksum, 4);
+ }
+
+ // write WOFF tables
+ for (const WOFFTableRecord &woffRecord : woffRecords) {
+ writer.writeBytes(woffRecord.data, woffRecord.size);
+ size_t padding = woffRecord.paddedSize()-woffRecord.size;
+ writer.writeBytes(0, padding);
+ }
+}
+
+
+void TrueTypeFont::writeWOFF (const string &fname) const {
+ ofstream ofs(fname.c_str(), ios::binary);
+ writeWOFF(ofs);
+}
+
+
+/** Puts the TrueType data in a WOFF2 container and writes the resulting data to a given stream.
+ * @param[out] os stream to write the WOFF2 data to
+ * @return true on success */
+bool TrueTypeFont::writeWOFF2 (ostream &os) const {
+ auto input_data = reinterpret_cast<const uint8_t*>(_buffer.data());
+ size_t output_size = woff2::MaxWOFF2CompressedSize(input_data, _buffer.size());
+ string output(output_size, 0);
+ auto output_data = reinterpret_cast<uint8_t*>(&output[0]);
+ woff2::WOFF2Params params;
+ if (woff2::ConvertTTFToWOFF2(input_data, _buffer.size(), output_data, &output_size, params)) {
+ output.resize(output_size);
+ copy(output.begin(), output.end(), ostream_iterator<uint8_t>(os));
+ return true;
+ }
+ return false;
+}
+
+
+bool TrueTypeFont::writeWOFF2 (const string &fname) const {
+ ofstream ofs(fname.c_str(), ios::binary);
+ return writeWOFF2(ofs);
+}
+
+
+/** Converts the numeric table tag (which represents a four-character ID) to a string. */
+string TrueTypeFont::TTFTableRecord::name () const {
+ string ret;
+ for (int i=24; i >= 0; i-= 8)
+ ret += static_cast<char>(tag >> i);
+ return ret;
+}
+
+
+uint32_t TrueTypeFont::TTFTableRecord::computeChecksum () const {
+ uint32_t sum=0;
+ auto startptr = reinterpret_cast<const uint32_t*>(data);
+ auto endptr = startptr + paddedSize()/sizeof(uint32_t);
+ while (startptr < endptr)
+ sum += *startptr++;
+ return sum;
+} \ No newline at end of file
diff --git a/dviware/dvisvgm/src/TrueTypeFont.hpp b/dviware/dvisvgm/src/TrueTypeFont.hpp
new file mode 100644
index 0000000000..d2e5b6e98d
--- /dev/null
+++ b/dviware/dvisvgm/src/TrueTypeFont.hpp
@@ -0,0 +1,74 @@
+/*************************************************************************
+** TrueTypeFont.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef TRUETYPEFONT_HPP
+#define TRUETYPEFONT_HPP
+
+#include <istream>
+#include <fstream>
+#include <ostream>
+#include <string>
+#include <vector>
+#include "MessageException.hpp"
+
+
+class TrueTypeFont {
+ struct TTFTableRecord {
+ std::string name () const;
+ uint32_t paddedSize () const {return (size+3) & ~3;}
+ uint32_t computeChecksum () const;
+ uint32_t tag; ///< table tag
+ uint32_t checksum; ///< checksum of the table data
+ uint32_t size; ///< number of bytes in the table
+ const uint8_t *data; ///< points to the begin of the table
+ };
+
+ struct WOFFTableRecord {
+ WOFFTableRecord (uint32_t off, const TTFTableRecord &rec) : offset(off), size(rec.size), data(rec.data), ttfRecord(rec) {}
+ size_t paddedSize () const {return (size+3) & ~3;}
+ bool compressTableData ();
+ uint32_t offset; ///< file offset to first byte of the table
+ uint32_t size; ///< number of bytes in the table
+ const uint8_t *data; ///< points to the begin of the WOFF table
+ std::vector<uint8_t> compressedData; ///< compressed table data
+ const TTFTableRecord &ttfRecord; ///< corresponding TTF table record
+ };
+
+ public:
+ TrueTypeFont () : _version(0) {}
+ explicit TrueTypeFont (const std::string &fname) {read(fname);}
+ bool read (const std::string &fname);
+ void writeWOFF (std::ostream &os) const;
+ void writeWOFF (const std::string &fname) const;
+ bool writeWOFF2 (std::ostream &os) const;
+ bool writeWOFF2 (const std::string &fname) const;
+
+ private:
+ uint32_t _version; ///< TTF version
+ std::string _buffer; ///< contents of TTF file
+ std::vector<TTFTableRecord> _tableRecords;
+};
+
+
+struct TrueTypeFontException : public MessageException {
+ explicit TrueTypeFontException (const std::string &msg) : MessageException(msg) {}
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/Unicode.cpp b/dviware/dvisvgm/src/Unicode.cpp
new file mode 100644
index 0000000000..d4a18a7bb9
--- /dev/null
+++ b/dviware/dvisvgm/src/Unicode.cpp
@@ -0,0 +1,186 @@
+/*************************************************************************
+** Unicode.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <cctype>
+#include <cstddef>
+#include <iomanip>
+#include <sstream>
+#include <xxhash.h>
+#include "Unicode.hpp"
+
+using namespace std;
+
+
+/** Returns true if c is a valid Unicode point in XML documents.
+ * XML version 1.0 doesn't allow various Unicode character references
+ * (&#1; for example). */
+bool Unicode::isValidCodepoint (uint32_t c) {
+ if ((c & 0xffff) == 0xfffe || (c & 0xffff) == 0xffff)
+ return false;
+
+ uint32_t ranges[] = {
+ 0x0000, 0x0020, // basic control characters + space
+ 0x007f, 0x009f, // use of control characters is discouraged by the XML standard
+ 0x202a, 0x202e, // bidi control characters
+ 0xd800, 0xdfff, // High Surrogates are not allowed in XML
+ 0xfdd0, 0xfdef, // non-characters for internal use by applications
+ };
+ for (size_t i=0; i < sizeof(ranges)/sizeof(uint32_t) && c >= ranges[i]; i+=2)
+ if (c <= ranges[i+1])
+ return false;
+ return true;
+}
+
+
+/** Returns a valid Unicode point for the given character code. Character codes
+ * that are invalid code points because the XML standard forbids or discourages
+ * their usage, are mapped to the Private Use Zone U+E000-U+F8FF. */
+uint32_t Unicode::charToCodepoint (uint32_t c) {
+ uint32_t ranges[] = {
+ 0x0000, 0x0020, 0xe000, // basic control characters + space
+ 0x007f, 0x009f, 0xe021, // use of control characters is discouraged by the XML standard
+ 0x202a, 0x202e, 0xe042, // bidi control characters
+ 0xd800, 0xdfff, 0xe047, // High Surrogates are not allowed in XML
+ 0xfdd0, 0xfdef, 0xe847, // non-characters for internal use by applications
+ 0xfffe, 0xffff, 0xe867,
+ 0x1fffe, 0x1ffff, 0xe869,
+ 0x2fffe, 0x2ffff, 0xe86b,
+ 0x3fffe, 0x3ffff, 0xe86d,
+ 0x4fffe, 0x4ffff, 0xe86f,
+ 0x5fffe, 0x5ffff, 0xe871,
+ 0x6fffe, 0x6ffff, 0xe873,
+ 0x7fffe, 0x7ffff, 0xe875,
+ 0x8fffe, 0x8ffff, 0xe877,
+ 0x9fffe, 0x9ffff, 0xe879,
+ 0xafffe, 0xaffff, 0xe87b,
+ 0xbfffe, 0xbffff, 0xe87d,
+ 0xcfffe, 0xcffff, 0xe87f,
+ 0xdfffe, 0xdffff, 0xe881,
+ 0xefffe, 0xeffff, 0xe883,
+ 0xffffe, 0xfffff, 0xe885,
+ 0x10fffe, 0x10ffff, 0xe887
+ };
+ for (size_t i=0; i < sizeof(ranges)/sizeof(unsigned) && c >= ranges[i]; i+=3)
+ if (c <= ranges[i+1])
+ return ranges[i+2]+c-ranges[i];
+ return c;
+}
+
+
+/** Converts a Unicode point to a UTF-8 byte sequence.
+ * @param[in] cp code point
+ * @return utf8 sequence consisting of 1-4 bytes */
+string Unicode::utf8 (int32_t cp) {
+ string utf8;
+ if (cp >= 0) {
+ if (cp < 0x80)
+ utf8 += char(cp);
+ else if (cp < 0x800) {
+ utf8 += char(0xC0 + (cp >> 6));
+ utf8 += char(0x80 + (cp & 0x3F));
+ }
+ else if (cp < 0x10000) {
+ utf8 += char(0xE0 + (cp >> 12));
+ utf8 += char(0x80 + ((cp >> 6) & 0x3F));
+ utf8 += char(0x80 + (cp & 0x3F));
+ }
+ else if (cp < 0x110000) {
+ utf8 += char(0xF0 + (cp >> 18));
+ utf8 += char(0x80 + ((cp >> 12) & 0x3F));
+ utf8 += char(0x80 + ((cp >> 6) & 0x3F));
+ utf8 += char(0x80 + (cp & 0x3F));
+ }
+ // UTF-8 does not support codepoints >= 0x110000
+ }
+ return utf8;
+}
+
+#include "AGLTable.hpp"
+
+/** Tries to extract the codepoint from AGL character names like "uni1234" or "u1234".
+ * Returns 0 if the given name doesn't satisfy the constraints.
+ * https://github.com/adobe-type-tools/agl-specification
+ * @param[in] name AGL character name
+ * @return the extracted codepoint or 0 on failure */
+static int32_t extract_codepoint_from_name (const string &name) {
+ size_t offset=1;
+ auto is_hex_digit = [](char c) {return isdigit(c) || (c >= 'A' && c <= 'F');};
+ if (name.substr(0, 3) == "uni" && is_hex_digit(name[4]) && name.length() >= 7)
+ offset = 3;
+ else if (name[0] != 'u' || !is_hex_digit(name[1]) || name.length() < 5)
+ return 0;
+
+ string::const_iterator it = name.begin()+offset;
+ while (it != name.end() && is_hex_digit(*it) && *it != '.' && *it != '_')
+ ++it;
+ if (it != name.end() && *it != '.' && *it != '_')
+ return 0;
+
+ string hexstr(name.begin()+offset, it);
+ if (hexstr.length() < 4 || (offset == 3 && hexstr.length() % 4 != 0))
+ return 0;
+ if (offset == 3)
+ hexstr = hexstr.substr(0, 4);
+ int32_t codepoint;
+ istringstream iss(hexstr);
+ iss >> hex >> codepoint;
+ if (!iss.fail() && (codepoint <= 0xD7FF || (codepoint >= 0xE000 && codepoint <= 0x10FFFF)))
+ return codepoint;
+ return 0;
+}
+
+
+#if 0
+static const char* get_suffix (const string &name) {
+ static const char *suffixes[] = {
+ "small", "swash", "superior", "inferior", "numerator", "denominator", "oldstyle",
+ "display", "text", "big", "bigg", "Big", "Bigg", 0
+ };
+ size_t pos = name.rfind('.');
+ if (pos != string::npos) {
+ string suffix = name.substr(pos+1);
+ for (const char **p=suffixes; *p; p++)
+ if (suffix == *p)
+ return *p;
+ }
+ return 0;
+}
+#endif
+
+
+/** Returns the Unicode point for a given AGL character name.
+ * @param name AGL name of the character to look up
+ * @return codepoint of the character */
+int32_t Unicode::aglNameToCodepoint (const string &name) {
+ if (int32_t cp = extract_codepoint_from_name(name))
+ return cp;
+
+ uint32_t hash = XXH32(&name[0], name.length(), 0);
+ const HashCodepointPair cmppair = {hash, 0};
+ auto it = lower_bound(hash2unicode.begin(), hash2unicode.end(), cmppair,
+ [](const HashCodepointPair &p1, const HashCodepointPair &p2) {
+ return p1.hash < p2.hash;
+ }
+ );
+ if (it != hash2unicode.end() && it->hash == hash)
+ return it->codepoint;
+ return 0;
+}
diff --git a/dviware/dvisvgm/src/Unicode.hpp b/dviware/dvisvgm/src/Unicode.hpp
new file mode 100644
index 0000000000..e281edc791
--- /dev/null
+++ b/dviware/dvisvgm/src/Unicode.hpp
@@ -0,0 +1,34 @@
+/*************************************************************************
+** Unicode.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef UNICODE_HPP
+#define UNICODE_HPP
+
+#include <string>
+
+struct Unicode
+{
+ static bool isValidCodepoint (uint32_t code);
+ static uint32_t charToCodepoint (uint32_t c);
+ static std::string utf8 (int32_t c);
+ static int32_t aglNameToCodepoint (const std::string &name);
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/VFActions.hpp b/dviware/dvisvgm/src/VFActions.hpp
new file mode 100644
index 0000000000..73567109f7
--- /dev/null
+++ b/dviware/dvisvgm/src/VFActions.hpp
@@ -0,0 +1,37 @@
+/*************************************************************************
+** VFActions.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef VFACTIONS_HPP
+#define VFACTIONS_HPP
+
+#include <string>
+#include <vector>
+
+
+struct VFActions
+{
+ virtual ~VFActions () =default;
+ virtual void vfPreamble (const std::string &comment, uint32_t checksum, double dsize) {}
+ virtual void vfPostamble () {}
+ virtual void defineVFFont (uint32_t fontnum, const std::string &path, const std::string &name, uint32_t checksum, double dsize, double ssize) {}
+ virtual void defineVFChar (uint32_t c, std::vector<uint8_t> &&dvi) {}
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/VFReader.cpp b/dviware/dvisvgm/src/VFReader.cpp
new file mode 100644
index 0000000000..a618f96ef7
--- /dev/null
+++ b/dviware/dvisvgm/src/VFReader.cpp
@@ -0,0 +1,178 @@
+/*************************************************************************
+** VFReader.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <sstream>
+#include "FixWord.hpp"
+#include "Font.hpp"
+#include "Length.hpp"
+#include "VFActions.hpp"
+#include "VFReader.hpp"
+
+using namespace std;
+
+
+VFReader::VFReader (istream &is) : StreamReader(is)
+{
+}
+
+
+VFActions* VFReader::replaceActions (VFActions *a) {
+ VFActions *ret = _actions;
+ _actions = a;
+ return ret;
+}
+
+
+/** Reads a single VF command from the current position of the input stream and calls the
+ * corresponding cmdFOO method. The execution can be influenced by a function of type ApproveOpcode.
+ * It takes an opcode and returns true if the command is supposed to be executed.
+ * @param[in] approve function to approve invocation of the action assigned to command
+ * @return opcode of the executed command */
+int VFReader::executeCommand (ApproveAction approve) {
+ int opcode = readByte();
+ if (!isStreamValid() || opcode < 0) // at end of file?
+ throw VFException("invalid VF file");
+
+ bool approved = !approve || approve(opcode);
+ VFActions *actions = _actions;
+ if (!approved)
+ replaceActions(nullptr); // disable actions
+
+ if (opcode <= 241) // short character definition?
+ cmdShortChar(opcode);
+ else if (opcode >= 243 && opcode <= 246) // font definition?
+ cmdFontDef(opcode-243+1);
+ else {
+ switch (opcode) {
+ case 242: cmdLongChar(); break; // long character definition
+ case 247: cmdPre(); break; // preamble
+ case 248: cmdPost(); break; // postamble
+ default : { // invalid opcode
+ replaceActions(actions); // reenable actions
+ ostringstream oss;
+ oss << "undefined VF command (opcode " << opcode << ')';
+ throw VFException(oss.str());
+ }
+ }
+ }
+ replaceActions(actions); // reenable actions
+ return opcode;
+}
+
+
+bool VFReader::executeAll () {
+ clearStream(); // reset all status bits
+ if (!isStreamValid())
+ return false;
+ seek(0); // move file pointer to first byte of the input stream
+ while (!eof() && executeCommand() != 248); // stop reading after post (248)
+ return true;
+}
+
+
+bool VFReader::executePreambleAndFontDefs () {
+ clearStream();
+ if (!isStreamValid())
+ return false;
+ seek(0); // move file pointer to first byte of the input stream
+ auto is_pre_or_fontdef = [](int op) {return op > 242;};
+ while (!eof() && executeCommand(is_pre_or_fontdef) > 242); // stop reading after last font definition
+ return true;
+}
+
+
+bool VFReader::executeCharDefs () {
+ clearStream();
+ if (!isStreamValid())
+ return false;
+ seek(0);
+ auto is_chardef = [](int op) {return op < 243;};
+ while (!eof() && executeCommand(is_chardef) < 243); // stop reading after last char definition
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+/** Reads and executes DVI preamble command. */
+void VFReader::cmdPre () {
+ uint32_t i = readUnsigned(1); // identification number (should be 2)
+ uint32_t k = readUnsigned(1); // length of following comment
+ string cmt = readString(k); // comment
+ uint32_t cs = readUnsigned(4); // check sum to be compared with TFM cecksum
+ int32_t ds = readUnsigned(4); // design size (same as TFM design size) (fix_word)
+ _designSize = double(FixWord(ds))*Length::pt2bp;
+ if (i != 202)
+ throw VFException("invalid identification value in preamble");
+ if (_actions)
+ _actions->vfPreamble(cmt, cs, ds);
+}
+
+
+void VFReader::cmdPost () {
+ while ((readUnsigned(1)) == 248); // skip fill bytes
+ if (_actions)
+ _actions->vfPostamble();
+}
+
+
+void VFReader::cmdLongChar () {
+ uint32_t pl = readUnsigned(4); // packet length (length of DVI subroutine)
+ if (!_actions)
+ seek(8+pl, ios::cur); // skip remaining char definition bytes
+ else {
+ uint32_t cc = readUnsigned(4); // character code
+ readUnsigned(4); // equals character width from corresponding TFM file
+ vector<uint8_t> dvi(pl); // DVI subroutine
+ readBytes(pl, dvi);
+ _actions->defineVFChar(cc, std::move(dvi)); // call template method for user actions
+ }
+}
+
+
+/** Reads and executes short_char_x command.
+ * @param[in] pl packet length (length of DVI subroutine) */
+void VFReader::cmdShortChar (int pl) {
+ if (!_actions)
+ seek(4+pl, ios::cur); // skip char definition bytes
+ else {
+ uint32_t cc = readUnsigned(1); // character code
+ readUnsigned(3); // character width from corresponding TFM file
+ vector<uint8_t> dvi(pl); // DVI subroutine
+ readBytes(pl, dvi);
+ _actions->defineVFChar(cc, std::move(dvi)); // call template method for user actions
+ }
+}
+
+
+void VFReader::cmdFontDef (int len) {
+ uint32_t fontnum = readUnsigned(len); // font number
+ uint32_t checksum = readUnsigned(4); // font checksum (to be compared with corresponding TFM checksum)
+ uint32_t ssize = readUnsigned(4); // scaled size of font relative to design size (fix_word)
+ uint32_t dsize = readUnsigned(4); // design size of font (same as TFM design size) (fix_word)
+ uint32_t pathlen = readUnsigned(1); // length of font path
+ uint32_t namelen = readUnsigned(1); // length of font name
+ string fontpath = readString(pathlen);
+ string fontname = readString(namelen);
+ if (_actions) {
+ double ss = double(FixWord(ssize))*Length::pt2bp;
+ double ds = double(FixWord(dsize))*Length::pt2bp;
+ _actions->defineVFFont(fontnum, fontpath, fontname, checksum, ds, ss*_designSize);
+ }
+}
diff --git a/dviware/dvisvgm/src/VFReader.hpp b/dviware/dvisvgm/src/VFReader.hpp
new file mode 100644
index 0000000000..8ddba805e8
--- /dev/null
+++ b/dviware/dvisvgm/src/VFReader.hpp
@@ -0,0 +1,59 @@
+/*************************************************************************
+** VFReader.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef VFREADER_HPP
+#define VFREADER_HPP
+
+#include "MessageException.hpp"
+#include "StreamReader.hpp"
+
+
+struct VFException : public MessageException {
+ explicit VFException (const std::string &msg) : MessageException(msg) {}
+};
+
+struct VFActions;
+
+class VFReader : public StreamReader{
+ using ApproveAction = bool (*)(int);
+ public:
+ explicit VFReader (std::istream &is);
+ VFActions* replaceActions (VFActions *a);
+ bool executeAll ();
+ bool executePreambleAndFontDefs ();
+ bool executeCharDefs ();
+
+ protected:
+ int executeCommand (ApproveAction approve=nullptr);
+
+ // the following methods represent the VF commands
+ // they are called by executeCommand and should not be used directly
+ void cmdPre ();
+ void cmdPost ();
+ void cmdShortChar (int pl);
+ void cmdLongChar ();
+ void cmdFontDef (int len);
+
+ private:
+ VFActions *_actions=nullptr; ///< actions to execute when reading a VF command
+ double _designSize=0; ///< design size of currently read VF in PS points
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/VectorIterator.hpp b/dviware/dvisvgm/src/VectorIterator.hpp
new file mode 100644
index 0000000000..17707fbc96
--- /dev/null
+++ b/dviware/dvisvgm/src/VectorIterator.hpp
@@ -0,0 +1,108 @@
+/*************************************************************************
+** VectorIterator.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef VECTORITERATOR_HPP
+#define VECTORITERATOR_HPP
+
+#include <vector>
+#include "MessageException.hpp"
+
+
+struct IteratorException : public MessageException {
+ explicit IteratorException (const std::string &msg) : MessageException(msg) {}
+};
+
+
+template <typename T>
+class VectorIterator {
+ public:
+ explicit VectorIterator (std::vector<T> &vec) : _vector(vec), _pos(0) {}
+
+ VectorIterator operator ++ () {
+ _pos++;
+ return *this;
+ }
+
+ VectorIterator operator ++ (int) {
+ VectorIterator it = *this;
+ _pos++;
+ return it;
+ }
+
+ VectorIterator operator -- () {
+ _pos--;
+ return *this;
+ }
+
+ VectorIterator operator -- (int) {
+ VectorIterator it = *this;
+ _pos--;
+ return it;
+ }
+
+ VectorIterator operator += (int n) {
+ _pos += n;
+ return *this;
+ }
+
+ VectorIterator operator -= (int n) {
+ _pos -= n;
+ return *this;
+ }
+
+ VectorIterator operator + (int n) {
+ return VectorIterator(_vector, _pos+n);
+ }
+
+ VectorIterator operator - (int n) {
+ return VectorIterator(_vector, _pos-n);
+ }
+
+ T& operator * () {
+ if (valid())
+ return _vector[_pos];
+ throw IteratorException("invalid access");
+ }
+
+ T* operator -> () {
+ if (valid())
+ return &_vector[_pos];
+ throw IteratorException("invalid access");
+ }
+
+ bool operator == (const VectorIterator &it) const {return _pos == it._pos;}
+ bool operator != (const VectorIterator &it) const {return _pos != it._pos;}
+ bool operator <= (const VectorIterator &it) const {return _pos <= it._pos;}
+ bool operator >= (const VectorIterator &it) const {return _pos >= it._pos;}
+ bool operator < (const VectorIterator &it) const {return _pos < it._pos;}
+ bool operator > (const VectorIterator &it) const {return _pos > it._pos;}
+ bool valid () const {return _pos >= 0 && _pos < _vector.size();}
+ void invalidate () {_pos = _vector.size();}
+ void reset () {_pos = 0;}
+
+ protected:
+ VectorIterator (std::vector<T> &vec, size_t pos) : _vector(vec), _pos(pos) {}
+
+ private:
+ std::vector<T> &_vector;
+ size_t _pos;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/VectorStream.hpp b/dviware/dvisvgm/src/VectorStream.hpp
new file mode 100644
index 0000000000..c74388e076
--- /dev/null
+++ b/dviware/dvisvgm/src/VectorStream.hpp
@@ -0,0 +1,86 @@
+/*************************************************************************
+** VectorStream.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef VECTORSTREAM_HPP
+#define VECTORSTREAM_HPP
+
+#include <istream>
+#include <vector>
+
+template <typename T>
+class VectorStreamBuffer : public std::streambuf {
+ public:
+ explicit VectorStreamBuffer (const std::vector<T> &v) {
+ if (v.empty())
+ _begin = _end = _curr = nullptr;
+ else {
+ _begin = _curr = &v[0];
+ _end = &v[0]+v.size();
+ }
+ }
+
+ protected:
+ int_type underflow () override {
+ return _curr == _end ? traits_type::eof() : traits_type::to_int_type(*_curr);
+ }
+
+ int_type uflow() override {
+ return _curr == _end ? traits_type::eof() : traits_type::to_int_type(*_curr++);
+ }
+
+ std::streamsize showmanyc () override {return _end-_curr;}
+
+ int_type pbackfail (int_type c) override {
+ if (_curr == _begin || (c != traits_type::eof() && c != _curr[-1]))
+ return traits_type::eof();
+ return traits_type::to_int_type(*--_curr);
+ }
+
+ pos_type seekoff (off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which=std::ios_base::in) override {
+ switch (dir) {
+ case std::ios_base::cur:
+ _curr += off; break;
+ case std::ios_base::beg:
+ _curr = _begin+off; break;
+ case std::ios_base::end:
+ _curr = _end-off; break;
+ default:
+ break;
+ }
+ return _curr-_begin;
+ }
+
+ private:
+ const T *_begin;
+ const T *_end;
+ const T *_curr;
+};
+
+
+template <typename T>
+class VectorInputStream : public std::istream {
+ public:
+ explicit VectorInputStream (const std::vector<T> &source) : std::istream(&_buf), _buf(source) {}
+
+ private:
+ VectorStreamBuffer<T> _buf;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/XMLDocument.cpp b/dviware/dvisvgm/src/XMLDocument.cpp
new file mode 100644
index 0000000000..be14eaf678
--- /dev/null
+++ b/dviware/dvisvgm/src/XMLDocument.cpp
@@ -0,0 +1,62 @@
+/*************************************************************************
+** XMLDocument.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include "XMLDocument.hpp"
+
+using namespace std;
+
+XMLDocument::XMLDocument (unique_ptr<XMLElement> root)
+ : _rootElement(std::move(root))
+{
+}
+
+
+void XMLDocument::clear () {
+ _rootElement.reset();
+ _nodes.clear();
+}
+
+
+void XMLDocument::append (unique_ptr<XMLNode> node) {
+ if (node) {
+ if (node->toElement())
+ _rootElement = util::static_unique_ptr_cast<XMLElement>(std::move(node));
+ else
+ _nodes.emplace_back(std::move(node));
+ }
+}
+
+
+void XMLDocument::setRootNode (unique_ptr<XMLElement> root) {
+ _rootElement = std::move(root);
+}
+
+
+ostream& XMLDocument::write (ostream &os) const {
+ if (_rootElement) { // no root element => no output
+ os << "<?xml version='1.0' encoding='UTF-8'?>\n";
+ for (const auto &node : _nodes) {
+ node->write(os);
+ os << '\n';
+ }
+ _rootElement->write(os);
+ }
+ return os;
+}
diff --git a/dviware/dvisvgm/src/XMLDocument.hpp b/dviware/dvisvgm/src/XMLDocument.hpp
new file mode 100644
index 0000000000..e9132c44d8
--- /dev/null
+++ b/dviware/dvisvgm/src/XMLDocument.hpp
@@ -0,0 +1,44 @@
+/*************************************************************************
+** XMLDocument.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef XMLDOCUMENT_HPP
+#define XMLDOCUMENT_HPP
+
+#include <memory>
+#include <vector>
+#include "XMLNode.hpp"
+
+class XMLDocument {
+ public:
+ XMLDocument () =default;
+ explicit XMLDocument(std::unique_ptr<XMLElement> root);
+ void clear ();
+ void append (std::unique_ptr<XMLElement> node);
+ void append (std::unique_ptr<XMLNode> node);
+ void setRootNode (std::unique_ptr<XMLElement> root);
+ const XMLElement* getRootElement () const {return _rootElement.get();}
+ std::ostream& write (std::ostream &os) const;
+
+ private:
+ std::vector<std::unique_ptr<XMLNode>> _nodes;
+ std::unique_ptr<XMLElement> _rootElement;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/XMLNode.cpp b/dviware/dvisvgm/src/XMLNode.cpp
new file mode 100644
index 0000000000..128be5b9e5
--- /dev/null
+++ b/dviware/dvisvgm/src/XMLNode.cpp
@@ -0,0 +1,456 @@
+/*************************************************************************
+** XMLNode.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <map>
+#include <list>
+#include <sstream>
+#include "utility.hpp"
+#include "XMLNode.hpp"
+#include "XMLString.hpp"
+
+using namespace std;
+
+bool XMLElement::WRITE_NEWLINES=true;
+
+
+/** Inserts a sibling node after this one.
+ * @param[in] node node to insert
+ * @return raw pointer to inserted node */
+XMLNode* XMLNode::insertNext (unique_ptr<XMLNode> node) {
+ if (_next) {
+ _next->_prev = node.get();
+ node->_next = std::move(_next);
+ }
+ node->_prev = this;
+ node->_parent = _parent;
+ _next = std::move(node);
+ return _next.get();
+}
+
+
+/** Removes the following sibling node of this one.
+ * @return pointer to the removed node */
+unique_ptr<XMLNode> XMLNode::removeNext () {
+ unique_ptr<XMLNode> oldnext = std::move(_next);
+ if (oldnext) {
+ oldnext->_parent = oldnext->_prev = nullptr;
+ if ((_next = std::move(oldnext->_next))) {
+ _next->_prev = this;
+ oldnext->_next.reset();
+ }
+ }
+ return oldnext;
+}
+
+/////////////////////////////////////////////////////////////////////
+
+XMLElement::XMLElement (string name) : _name(std::move(name)) {
+}
+
+
+XMLElement::XMLElement (const XMLElement &node)
+ : XMLNode(node), _name(node._name), _attributes(node._attributes)
+{
+ for (XMLNode *child=_firstChild.get(); child; child = child->next())
+ insertLast(child->clone());
+}
+
+
+XMLElement::XMLElement (XMLElement &&node) noexcept
+ : XMLNode(std::move(node)),
+ _name(std::move(node._name)),
+ _attributes(std::move(node._attributes)),
+ _firstChild(std::move(node._firstChild)),
+ _lastChild(node._lastChild)
+{
+}
+
+
+/** Removes all attributes and children. */
+void XMLElement::clear () {
+ _attributes.clear();
+ _firstChild.reset();
+ _lastChild = nullptr;
+}
+
+
+void XMLElement::addAttribute (const string &name, const string &value) {
+ if (Attribute *attr = getAttribute(name))
+ attr->value = value;
+ else
+ _attributes.emplace_back(Attribute(name, value));
+}
+
+
+void XMLElement::addAttribute (const string &name, double value) {
+ addAttribute(name, XMLString(value));
+}
+
+
+void XMLElement::removeAttribute (const std::string &name) {
+ _attributes.erase(find_if(_attributes.begin(), _attributes.end(), [&](const Attribute &attr) {
+ return attr.name == name;
+ }));
+}
+
+
+/** Inserts a new last child node and returns a raw pointer to it. */
+XMLNode* XMLElement::insertLast (unique_ptr<XMLNode> child) {
+ if (!child)
+ return nullptr;
+ child->parent(this);
+ if (!empty())
+ _lastChild = _lastChild->insertNext(std::move(child));
+ else {
+ _firstChild = std::move(child);
+ _lastChild = _firstChild.get();
+ }
+ return _lastChild;
+}
+
+
+/** Inserts a new first child node and returns a raw pointer to it. */
+XMLNode* XMLElement::insertFirst (unique_ptr<XMLNode> child) {
+ if (!child)
+ return nullptr;
+ child->parent(this);
+ if (empty()) {
+ _firstChild = std::move(child);
+ _lastChild = _firstChild.get();
+ }
+ else {
+ child->insertNext(std::move(_firstChild));
+ _firstChild = std::move(child);
+ }
+ return _firstChild.get();
+}
+
+
+/** Appends a child node to this element. The element also takes the ownership of the child.
+ * @param[in] child node to be appended
+ * @return raw pointer to the appended child node */
+XMLNode* XMLElement::append (unique_ptr<XMLNode> child) {
+ if (!child)
+ return nullptr;
+ XMLText *textNode1 = child->toText();
+ if (!textNode1 || empty())
+ insertLast(std::move(child));
+ else {
+ if (XMLText *textNode2 = _lastChild->toText())
+ textNode2->append(util::static_unique_ptr_cast<XMLText>(std::move(child))); // merge two consecutive text nodes
+ else
+ insertLast(std::move(child));
+ }
+ return _lastChild;
+}
+
+
+/** Appends a string to this element. If the last child is a text node, the string is
+ * appended there, otherwise a new text node is created.
+ * @param[in] str string to be appended
+ * @return raw pointer to the text node the string was appended to */
+XMLNode* XMLElement::append (const string &str) {
+ XMLText *last;
+ if (!empty() && (last = _lastChild->toText()))
+ last->append(str);
+ else
+ insertLast(util::make_unique<XMLText>(str));
+ return _lastChild;
+}
+
+
+/** Prepends a child node to this element. The element also takes the ownership of the child.
+ * @param[in] child node to be prepended
+ * @return raw pointer to the prepended child node */
+XMLNode* XMLElement::prepend (unique_ptr<XMLNode> child) {
+ if (!child)
+ return nullptr;
+ XMLText *textNode1 = child->toText();
+ if (textNode1 && !empty()) {
+ if (XMLText *textNode2 = _firstChild->toText()) {
+ textNode2->prepend(util::static_unique_ptr_cast<XMLText>(std::move(child))); // merge two consecutive text nodes
+ return textNode2;
+ }
+ }
+ insertFirst(std::move(child));
+ return _firstChild.get();
+}
+
+
+/** Inserts a new child node before a given child node already present. The latter
+ * will be the following sibling of the node to be inserted. If there's no such
+ * node present, nothing is inserted.
+ * @param[in] child node to be inserted
+ * @param[in] sibling following sibling of 'child'
+ * @return raw pointer to inserted node */
+XMLNode* XMLElement::insertBefore (unique_ptr<XMLNode> child, XMLNode *sibling) {
+ if (!child || (sibling && sibling->parent() != this))
+ return nullptr;
+ if (!sibling)
+ return insertLast(std::move(child));
+ if (sibling == _firstChild.get())
+ return insertFirst(std::move(child));
+ return sibling->prev()->insertNext(std::move(child));
+}
+
+
+/** Inserts a new child node after a given child node already present. The latter
+ * will be the preceding sibling of the node to be inserted. If there's no such
+ * node present, nothing is inserted.
+ * @param[in] child node to be inserted
+ * @param[in] sibling preceding sibling of 'child'
+ * @return raw pointer to inserted node */
+XMLNode* XMLElement::insertAfter (unique_ptr<XMLNode> child, XMLNode *sibling) {
+ if (!child || (sibling && sibling->parent() != this))
+ return nullptr;
+ if (!sibling)
+ return insertFirst(std::move(child));
+ if (sibling == _lastChild)
+ return insertLast(std::move(child));
+ return sibling->insertNext(std::move(child));
+}
+
+
+/** Moves a sequence of child nodes to a new element of a given name and inserts
+ * this (wrapper) element at the former position of the first node of the sequence.
+ * Example: wrap 3 child nodes of element a with b:
+ * <a>text1<c/>text2<d/></a> => <a>text1<b><c/>text2<d/></b></a>
+ * @param[in] first first node to wrap
+ * @param[in] last last node to wrap (or nullptr if all following siblings of 'first' are to be wrapped)
+ * @param[in] name name of the wrapper element to be created
+ * @return raw pointer to the new wrapper element */
+XMLElement* XMLElement::wrap (XMLNode *first, XMLNode *last, const string &name) {
+ if (!first || !first->parent() || (last && first->parent() != last->parent()))
+ return nullptr;
+ XMLElement *parent = first->parent()->toElement();
+ XMLNode *prev = first->prev();
+ auto wrapper = util::make_unique<XMLElement>(name);
+ if (last)
+ last = last->next();
+ XMLNode *child = first;
+ while (child && child != last) {
+ XMLNode *next = child->next();
+ wrapper->insertLast(remove(child));
+ child = next;
+ }
+ XMLElement *ret = wrapper.get();
+ if (prev)
+ parent->insertAfter(std::move(wrapper), prev);
+ else
+ parent->insertFirst(std::move(wrapper));
+ return ret;
+}
+
+
+/** Moves all child nodes C1,...,Cn of a given element E to its parent and
+ * removes E afterwards, so that C1 is located at the former position of E
+ * followed by C2,...,Cn.
+ * Example: unwrap a child element b of a:
+ * <a>text1<b><c/>text2<d/></b></a> => <a>text1<c/>text2<d/></a>
+ * @param[in] child child element to unwrap
+ * @return raw pointer to the first node C1 of the unwrapped sequence */
+XMLNode* XMLElement::unwrap (XMLElement *child) {
+ if (!child || !child->parent())
+ return nullptr;
+ XMLElement *parent = child->parent()->toElement();
+ auto removedChild = remove(child);
+ if (child->empty())
+ return child->next();
+ XMLNode *firstGrandchild = child->firstChild();
+ XMLNode *prev = child->prev();
+ unique_ptr<XMLNode> grandchild = std::move(child->_firstChild);
+ while (grandchild) {
+ prev = parent->insertAfter(std::move(grandchild), prev);
+ grandchild = std::move(prev->_next);
+ }
+ return firstGrandchild;
+}
+
+
+/** Removes a child node from the element.
+ * @param[in] child pointer to child to remove
+ * @return pointer to removed child or nullptr if given child doesn't belong to this element */
+unique_ptr<XMLNode> XMLElement::remove (XMLNode *child) {
+ unique_ptr<XMLNode> node;
+ if (child && child->parent()) {
+ XMLElement *parent = child->parent()->toElement();
+ if (child == parent->_lastChild)
+ parent->_lastChild = child->prev();
+ if (child != parent->firstChild())
+ node = child->prev()->removeNext();
+ else {
+ node = std::move(parent->_firstChild);
+ if ((parent->_firstChild = std::move(node->_next)))
+ parent->_firstChild->prev(nullptr);
+ }
+ child->parent(nullptr);
+ }
+ return node;
+}
+
+
+/** Gets all descendant elements with a given name and given attribute.
+ * @param[in] name name of elements to find
+ * @param[in] attrName name of attribute to find
+ * @param[out] descendants all elements found
+ * @return true if at least one element was found */
+bool XMLElement::getDescendants (const char *name, const char *attrName, vector<XMLElement*> &descendants) const {
+ for (XMLNode *child = _firstChild.get(); child; child = child->next()) {
+ if (XMLElement *elem = child->toElement()) {
+ if ((!name || elem->name() == name) && (!attrName || elem->hasAttribute(attrName)))
+ descendants.push_back(elem);
+ elem->getDescendants(name, attrName, descendants);
+ }
+ }
+ return !descendants.empty();
+}
+
+
+/** Returns the first descendant element that matches the given properties in depth first order.
+ * @param[in] name element name; if 0, all elements are taken into account
+ * @param[in] attrName if not 0, only elements with an attribute of this name are considered
+ * @param[in] attrValue if not 0, only elements with attribute attrName="attrValue" are considered
+ * @return pointer to the found element or 0 */
+XMLElement* XMLElement::getFirstDescendant (const char *name, const char *attrName, const char *attrValue) const {
+ for (XMLNode *child = _firstChild.get(); child; child = child->next()) {
+ if (XMLElement *elem = child->toElement()) {
+ if (!name || elem->name() == name) {
+ const char *value;
+ if (!attrName || (((value = elem->getAttributeValue(attrName)) != nullptr) && (!attrValue || string(value) == attrValue)))
+ return elem;
+ }
+ if (XMLElement *descendant = elem->getFirstDescendant(name, attrName, attrValue))
+ return descendant;
+ }
+ }
+ return nullptr;
+}
+
+
+ostream& XMLElement::write (ostream &os) const {
+ os << '<' << _name;
+ for (const auto &attrib : _attributes)
+ os << ' ' << attrib.name << "='" << attrib.value << '\'';
+ if (empty())
+ os << "/>";
+ else {
+ os << '>';
+ // Insert newlines around children except text nodes. According to the
+ // SVG specification, pure whitespace nodes are ignored by the SVG renderer.
+ if (WRITE_NEWLINES && !_firstChild->toText())
+ os << '\n';
+ for (XMLNode *child = _firstChild.get(); child; child = child->next()) {
+ child->write(os);
+ if (!child->toText()) {
+ if (WRITE_NEWLINES && (!child->next() || !child->next()->toText()))
+ os << '\n';
+ }
+ }
+ os << "</" << _name << '>';
+ }
+ return os;
+}
+
+
+/** Returns true if this element has an attribute of given name. */
+bool XMLElement::hasAttribute (const string &name) const {
+ return getAttribute(name) != nullptr;
+}
+
+
+/** Returns the value of an attribute.
+ * @param[in] name name of attribute
+ * @return attribute value or 0 if attribute doesn't exist */
+const char* XMLElement::getAttributeValue (const std::string& name) const {
+ if (const Attribute *attr = getAttribute(name))
+ return attr->value.c_str();
+ return nullptr;
+}
+
+
+XMLElement::Attribute* XMLElement::getAttribute (const string &name) {
+ auto it = find_if(_attributes.begin(), _attributes.end(), [&](const Attribute &attr) {
+ return attr.name == name;
+ });
+ return it != _attributes.end() ? &(*it) : nullptr;
+}
+
+
+const XMLElement::Attribute* XMLElement::getAttribute (const string &name) const {
+ auto it = find_if(_attributes.begin(), _attributes.end(), [&](const Attribute &attr) {
+ return attr.name == name;
+ });
+ return it != _attributes.end() ? &(*it) : nullptr;
+}
+
+
+//////////////////////
+
+void XMLText::append (unique_ptr<XMLNode> node) {
+ if (!node)
+ return;
+ if (node->toText())
+ append(util::static_unique_ptr_cast<XMLText>(std::move(node)));
+ else {
+ // append text representation of the node
+ ostringstream oss;
+ node->write(oss);
+ append(XMLString(oss.str()));
+ }
+}
+
+
+void XMLText::append (unique_ptr<XMLText> node) {
+ if (node)
+ _text += node->_text;
+}
+
+
+void XMLText::append (const string &str) {
+ _text += str;
+}
+
+
+void XMLText::prepend (unique_ptr<XMLNode> node) {
+ if (XMLText *textNode = node->toText())
+ _text = textNode->_text + _text;
+}
+
+
+const XMLText* XMLText::toWSNode () const {
+ return _text.find_first_not_of(" \t\n\r") == string::npos ? this : nullptr;
+}
+
+/////////////////////////////////////////////////////////////////////
+
+ostream& XMLCData::write (ostream &os) const {
+ if (!_data.empty())
+ os << "<![CDATA[" << _data << "]]>";
+ return os;
+}
+
+
+void XMLCData::append (string &&str) {
+ if (_data.empty())
+ _data = move(str);
+ else
+ _data += str;
+}
diff --git a/dviware/dvisvgm/src/XMLNode.hpp b/dviware/dvisvgm/src/XMLNode.hpp
new file mode 100644
index 0000000000..a8def772d8
--- /dev/null
+++ b/dviware/dvisvgm/src/XMLNode.hpp
@@ -0,0 +1,226 @@
+/*************************************************************************
+** XMLNode.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef XMLNODE_HPP
+#define XMLNODE_HPP
+
+#include <deque>
+#include <map>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <vector>
+#include "utility.hpp"
+
+class XMLCData;
+class XMLComment;
+class XMLElement;
+class XMLText;
+
+class XMLNode {
+ friend class XMLElement;
+
+ template <typename T>
+ T* cast (const T* (XMLNode::*func)() const) {
+ return const_cast<T*>((const_cast<const XMLNode*>(this)->*func)());
+ }
+
+ public:
+ XMLNode () =default;
+ XMLNode (const XMLNode &node) : _next(nullptr) {}
+ XMLNode (XMLNode &&node) noexcept : _parent(node._parent), _prev(node._prev), _next(std::move(node._next)) {}
+ virtual ~XMLNode () =default;
+ virtual std::unique_ptr<XMLNode> clone () const =0;
+ virtual void clear () =0;
+ virtual std::ostream& write (std::ostream &os) const =0;
+ virtual const XMLElement* toElement () const {return nullptr;}
+ virtual const XMLText* toText () const {return nullptr;}
+ virtual const XMLText* toWSNode () const {return nullptr;}
+ virtual const XMLComment* toComment () const {return nullptr;}
+ virtual const XMLCData* toCData () const {return nullptr;}
+ XMLElement* toElement () {return cast<XMLElement>(&XMLNode::toElement);}
+ XMLText* toText () {return cast<XMLText>(&XMLNode::toText);}
+ XMLComment* toComment () {return cast<XMLComment>(&XMLNode::toComment);}
+ XMLCData* toCData () {return cast<XMLCData>(&XMLNode::toCData);}
+ XMLNode* parent () const {return _parent;}
+ XMLNode* prev () const {return _prev;}
+ XMLNode* next () const {return _next.get();}
+
+ protected:
+ XMLNode* insertNext (std::unique_ptr<XMLNode> node);
+ std::unique_ptr<XMLNode> removeNext ();
+ void parent (XMLNode *p) {_parent = p;}
+ void prev (XMLNode *p) {_prev = p;}
+
+ private:
+ XMLNode *_parent=nullptr; ///< pointer to parent node
+ XMLNode *_prev=nullptr; ///< pointer to preceding sibling
+ std::unique_ptr<XMLNode> _next; ///< pointer to next sibling (incl. ownership)
+};
+
+
+class XMLNodeIterator {
+ public:
+ XMLNodeIterator () =default;
+ explicit XMLNodeIterator (XMLNode *curr) : _curr(curr) {}
+ XMLNodeIterator& operator ++ () {_curr = _curr->next(); return *this;}
+ XMLNodeIterator& operator -- () {_curr = _curr->prev(); return *this;}
+ XMLNodeIterator operator ++ (int) {auto p=_curr; _curr = _curr->next(); return XMLNodeIterator(p);}
+ XMLNodeIterator operator -- (int) {auto p=_curr; _curr = _curr->prev(); return XMLNodeIterator(p);}
+ XMLNode* operator * () {return _curr;}
+ XMLNode& operator -> () {return *_curr;}
+ bool operator == (const XMLNodeIterator &it) const {return _curr == it._curr;}
+ bool operator != (const XMLNodeIterator &it) const {return _curr != it._curr;}
+
+ private:
+ XMLNode *_curr=nullptr;
+};
+
+
+class ConstXMLNodeIterator {
+ public:
+ ConstXMLNodeIterator () =default;
+ explicit ConstXMLNodeIterator (const XMLNode *curr) : _curr(curr) {}
+ ConstXMLNodeIterator& operator ++ () {_curr = _curr->next(); return *this;}
+ ConstXMLNodeIterator& operator -- () {_curr = _curr->prev(); return *this;}
+ ConstXMLNodeIterator operator ++ (int) {auto p=_curr; _curr = _curr->next(); return ConstXMLNodeIterator(p);}
+ ConstXMLNodeIterator operator -- (int) {auto p=_curr; _curr = _curr->prev(); return ConstXMLNodeIterator(p);}
+ const XMLNode* operator * () {return _curr;}
+ const XMLNode& operator -> () {return *_curr;}
+ bool operator == (const ConstXMLNodeIterator &it) const {return _curr == it._curr;}
+ bool operator != (const ConstXMLNodeIterator &it) const {return _curr != it._curr;}
+
+ private:
+ const XMLNode *_curr=nullptr;
+};
+
+
+class XMLElement : public XMLNode {
+ public:
+ struct Attribute {
+ Attribute (std::string nam, std::string val) : name(std::move(nam)), value(std::move(val)) {}
+ std::string name;
+ std::string value;
+ };
+ using Attributes = std::vector<Attribute>;
+ static bool WRITE_NEWLINES; ///< insert line breaks after element tags?
+
+ public:
+ explicit XMLElement (std::string name);
+ XMLElement (const XMLElement &node);
+ XMLElement (XMLElement &&node) noexcept;
+ std::unique_ptr<XMLNode> clone () const override {return util::make_unique<XMLElement>(*this);}
+ void clear () override;
+ void addAttribute (const std::string &name, const std::string &value);
+ void addAttribute (const std::string &name, double value);
+ void removeAttribute (const std::string &name);
+ XMLNode* append (std::unique_ptr<XMLNode> child);
+ XMLNode* append (const std::string &str);
+ XMLNode* prepend (std::unique_ptr<XMLNode> child);
+ XMLNode* insertAfter (std::unique_ptr<XMLNode> child, XMLNode *sibling);
+ XMLNode* insertBefore (std::unique_ptr<XMLNode> child, XMLNode *sibling);
+ bool hasAttribute (const std::string &name) const;
+ const char* getAttributeValue (const std::string &name) const;
+ bool getDescendants (const char *name, const char *attrName, std::vector<XMLElement*> &descendants) const;
+ XMLElement* getFirstDescendant (const char *name, const char *attrName, const char *attrValue) const;
+ XMLNode* firstChild () const {return _firstChild.get();}
+ XMLNode* lastChild () const {return _lastChild;}
+ std::ostream& write (std::ostream &os) const override;
+ bool empty () const {return !_firstChild;}
+// const Children& children () const {return _children;}
+ Attributes& attributes () {return _attributes;}
+ XMLNodeIterator begin () {return XMLNodeIterator(_firstChild.get());}
+ XMLNodeIterator end () {return XMLNodeIterator(nullptr);}
+ ConstXMLNodeIterator begin () const {return ConstXMLNodeIterator(_firstChild.get());}
+ ConstXMLNodeIterator end () const {return ConstXMLNodeIterator(nullptr);}
+ const std::string& name () const {return _name;}
+ const XMLElement* toElement () const override {return this;}
+
+ static std::unique_ptr<XMLNode> remove (XMLNode *child);
+ static XMLElement* wrap (XMLNode *first, XMLNode *last, const std::string &name);
+ static XMLNode* unwrap (XMLElement *child);
+
+ protected:
+ Attribute* getAttribute (const std::string &name);
+ const Attribute* getAttribute (const std::string &name) const;
+ XMLNode* insertFirst (std::unique_ptr<XMLNode> child);
+ XMLNode* insertLast (std::unique_ptr<XMLNode> child);
+
+ private:
+ std::string _name; // element name (<name a1="v1" .. an="vn">...</name>)
+ std::vector<Attribute> _attributes;
+ std::unique_ptr<XMLNode> _firstChild; ///< pointer to first child node (incl. ownership)
+ XMLNode *_lastChild=nullptr; ///< pointer to last child node
+};
+
+
+class XMLText : public XMLNode {
+ public:
+ explicit XMLText (std::string str) : _text(std::move(str)) {}
+ std::unique_ptr<XMLNode> clone () const override {return util::make_unique<XMLText>(*this);}
+ void clear () override {_text.clear();}
+ void append (std::unique_ptr<XMLNode> node);
+ void append (std::unique_ptr<XMLText> node);
+ void append (const std::string &str);
+ void prepend (std::unique_ptr<XMLNode> node);
+ std::ostream& write (std::ostream &os) const override {return os << _text;}
+ const std::string& getText () const {return _text;}
+ const XMLText* toText () const override {return this;}
+ const XMLText* toWSNode () const override;
+
+ private:
+ std::string _text;
+};
+
+
+class XMLComment : public XMLNode {
+ public:
+ explicit XMLComment (std::string str) : _text(std::move(str)) {}
+ std::unique_ptr<XMLNode> clone () const override {return util::make_unique<XMLComment>(*this);}
+ void clear () override {_text.clear();}
+ std::ostream& write (std::ostream &os) const override {return os << "<!--" << _text << "-->";}
+ const XMLComment* toComment () const override {return this;}
+
+ private:
+ std::string _text;
+};
+
+
+class XMLCData : public XMLNode {
+ public:
+ XMLCData () =default;
+ explicit XMLCData (std::string data) : _data(std::move(data)) {}
+ std::unique_ptr<XMLNode> clone () const override {return util::make_unique<XMLCData>(*this);}
+ void clear () override {_data.clear();}
+ void append (std::string &&str);
+ std::ostream& write (std::ostream &os) const override;
+ const XMLCData* toCData () const override {return this;}
+
+ private:
+ std::string _data;
+};
+
+
+inline std::ostream& operator << (std::ostream &os, const XMLElement &node) {return node.write(os);}
+inline std::ostream& operator << (std::ostream &os, const XMLText &node) {return node.write(os);}
+inline std::ostream& operator << (std::ostream &os, const XMLComment &node) {return node.write(os);}
+inline std::ostream& operator << (std::ostream &os, const XMLCData &node) {return node.write(os);}
+
+#endif
diff --git a/dviware/dvisvgm/src/XMLString.cpp b/dviware/dvisvgm/src/XMLString.cpp
new file mode 100644
index 0000000000..b07e0e287b
--- /dev/null
+++ b/dviware/dvisvgm/src/XMLString.cpp
@@ -0,0 +1,105 @@
+/*************************************************************************
+** XMLString.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <cmath>
+#include <cstdlib>
+#include "Unicode.hpp"
+#include "XMLString.hpp"
+
+using namespace std;
+
+int XMLString::DECIMAL_PLACES = 0;
+
+
+static string translate (uint32_t c) {
+ switch (c) {
+ case '<' : return "&lt;";
+ case '&' : return "&amp;";
+ case '"' : return "&quot;";
+ case '\'': return "&apos;";
+ }
+ return Unicode::utf8(c);
+}
+
+
+XMLString::XMLString (const string &str, bool plain) {
+ if (plain)
+ assign(str);
+ else {
+ for (char c : str)
+ append(translate(c));
+ }
+}
+
+
+XMLString::XMLString (const char *str, bool plain) {
+ if (str) {
+ if (plain)
+ assign(str);
+ else {
+ while (*str)
+ append(translate(*str++));
+ }
+ }
+}
+
+
+XMLString::XMLString (int n, bool cast)
+ : string(cast ? to_string(n) : translate(n))
+{
+}
+
+
+/** Rounds a floating point value to a given number of decimal places.
+ * @param[in] x number to round
+ * @param[in] n number of decimal places (must be between 1 and 6)
+ * @return rounded value */
+static inline double round (double x, int n) {
+ const long pow10[] = {10L, 100L, 1000L, 10000L, 100000L, 1000000L};
+ const double eps = 1e-7;
+ n--;
+ if (x >= 0)
+ return floor(x*pow10[n]+0.5+eps)/pow10[n];
+ return ceil(x*pow10[n]-0.5-eps)/pow10[n];
+}
+
+
+XMLString::XMLString (double x) {
+ if (DECIMAL_PLACES > 0) {
+ // don't use fixed and setprecision() manipulators here to avoid
+ // banker's rounding applied in some STL implementations
+ x = round(x, DECIMAL_PLACES);
+ }
+ if (std::abs(x) < 1e-6)
+ x = 0;
+ assign(to_string(x));
+ size_t pos = find('.');
+ if (pos != string::npos) {
+ pos = find_last_not_of('0');
+ if (pos != string::npos) {
+ erase(pos+1); // remove trailing zeros
+ if (at(length()-1) == '.')
+ pop_back(); // remove trailing dot
+ }
+ }
+ pos = find("0.");
+ if (pos != string::npos && (pos == 0 || at(pos-1) == '-'))
+ erase(pos, 1);
+}
diff --git a/dviware/dvisvgm/src/XMLString.hpp b/dviware/dvisvgm/src/XMLString.hpp
new file mode 100644
index 0000000000..fa9cf15f9e
--- /dev/null
+++ b/dviware/dvisvgm/src/XMLString.hpp
@@ -0,0 +1,39 @@
+/*************************************************************************
+** XMLString.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef XMLSTRING_HPP
+#define XMLSTRING_HPP
+
+#include <string>
+
+
+class XMLString : public std::string {
+ public:
+ XMLString () : std::string() {}
+ explicit XMLString (const char *str, bool plain=false);
+ explicit XMLString (const std::string &str, bool plain=false);
+ explicit XMLString (int n, bool cast=true);
+ explicit XMLString (double x);
+
+ static int DECIMAL_PLACES; ///< number of decimal places applied to floating point values (0-6)
+};
+
+
+#endif
diff --git a/dviware/dvisvgm/src/XXHashFunction.hpp b/dviware/dvisvgm/src/XXHashFunction.hpp
new file mode 100644
index 0000000000..c0ae14e20b
--- /dev/null
+++ b/dviware/dvisvgm/src/XXHashFunction.hpp
@@ -0,0 +1,88 @@
+/*************************************************************************
+** XXHashFunction.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef XXHASHFUNCTION_HPP
+#define XXHASHFUNCTION_HPP
+
+#include <xxhash.h>
+#include "HashFunction.hpp"
+
+
+template <int HASH_SIZE>
+struct XXHInterface {
+};
+
+template<>
+struct XXHInterface<4> {
+ using State = XXH32_state_t;
+ static constexpr auto createState = &XXH32_createState;
+ static constexpr auto freeState = &XXH32_freeState;
+ static constexpr auto reset = &XXH32_reset;
+ static constexpr auto update = &XXH32_update;
+ static constexpr auto digest = &XXH32_digest;
+};
+
+template<>
+struct XXHInterface<8> {
+ using State = XXH64_state_t;
+ static constexpr auto createState = &XXH64_createState;
+ static constexpr auto freeState = &XXH64_freeState;
+ static constexpr auto reset = &XXH64_reset;
+ static constexpr auto update = &XXH64_update;
+ static constexpr auto digest = &XXH64_digest;
+};
+
+
+/** Implements the HashFunction class for the xxHash algorithms. */
+template <int HASH_BYTES>
+class XXHashFunction : public HashFunction {
+ using Interface = XXHInterface<HASH_BYTES>;
+ public:
+ XXHashFunction () : _state(Interface::createState()) {Interface::reset(_state, 0);}
+ XXHashFunction(const char *data, size_t length) : XXHashFunction() {update(data, length);}
+ explicit XXHashFunction(const std::string &data) : XXHashFunction() {update(data);}
+ explicit XXHashFunction(const std::vector<uint8_t> &data) : XXHashFunction() {update(data);}
+ ~XXHashFunction () override {Interface::freeState(_state);}
+ int digestSize () const override {return HASH_BYTES;}
+ void reset () override {Interface::reset(_state, 0);}
+ void update (const char *data, size_t length) override {Interface::update(_state, data, length);}
+ void update (const std::string &data) override {update(data.data(), data.length());}
+ void update (const std::vector<uint8_t> &data) override {update(reinterpret_cast<const char*>(data.data()), data.size());}
+
+ std::vector<uint8_t> digestValue () const override {
+ std::vector<uint8_t> hash(HASH_BYTES);
+ auto digest = Interface::digest(_state);
+ for (int i=HASH_BYTES-1; i >= 0; i--) {
+ hash[i] = digest & 0xff;
+ digest >>= 8;
+ }
+ return hash;
+ }
+
+ static unsigned version () {return XXH_versionNumber();}
+
+ private:
+ typename Interface::State *_state;
+};
+
+using XXH32HashFunction = XXHashFunction<4>;
+using XXH64HashFunction = XXHashFunction<8>;
+
+#endif
diff --git a/dviware/dvisvgm/src/ZLibOutputStream.hpp b/dviware/dvisvgm/src/ZLibOutputStream.hpp
new file mode 100644
index 0000000000..8331cb9eed
--- /dev/null
+++ b/dviware/dvisvgm/src/ZLibOutputStream.hpp
@@ -0,0 +1,181 @@
+/*************************************************************************
+** ZLibOutputStream.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef ZLIBOUTPUTSTREAM_HPP
+#define ZLIBOUTPUTSTREAM_HPP
+
+#include <fstream>
+#include <ostream>
+#include <vector>
+#include <zlib.h>
+#include "MessageException.hpp"
+
+#ifdef _WIN32
+# include <fcntl.h>
+# include <io.h>
+#endif
+
+struct ZLibException : public MessageException {
+ explicit ZLibException (const std::string &msg) : MessageException(msg) {}
+};
+
+enum ZLibCompressionFormat {ZLIB_DEFLATE=0, ZLIB_GZIP=16};
+
+class ZLibOutputBuffer : public std::streambuf {
+ public:
+ ZLibOutputBuffer () =default;
+
+ ZLibOutputBuffer (std::streambuf *sbuf, ZLibCompressionFormat format, int zipLevel) {
+ open(sbuf, format, zipLevel);
+ }
+
+ ~ZLibOutputBuffer () override {
+ close();
+ }
+
+ /** Opens the buffer for writing.
+ * @param[in] sink stream buffer taking the compressed data
+ * @param[in] format compression format (deflate or gzip)
+ * @param[in] zipLevel compression level (1-9)
+ * @return true if buffer is ready for writing */
+ bool open (std::streambuf *sink, ZLibCompressionFormat format, int zipLevel) {
+ if (sink) {
+ _inbuf.reserve(4096);
+ _zbuf.resize(4096);
+ _zstream.zalloc = Z_NULL;
+ _zstream.zfree = Z_NULL;
+ _zstream.opaque = Z_NULL;
+ zipLevel = std::max(1, std::min(9, zipLevel));
+ if (deflateInit2(&_zstream, zipLevel, Z_DEFLATED, 15+format, 8, Z_DEFAULT_STRATEGY) != Z_OK)
+ throw ZLibException("failed to initialize deflate compression");
+ _sink = sink;
+ _opened = true;
+ }
+ return _opened;
+ }
+
+ /** Flushes the remaining data, finishes the compression process, and
+ * closes the buffer so that further output doesn't reach the sink. */
+ void close () {
+ close(true);
+ }
+
+ int_type overflow (int_type c) override {
+ if (c == traits_type::eof()) {
+ close();
+ }
+ else {
+ if (_inbuf.size() == _inbuf.capacity())
+ flush(Z_NO_FLUSH);
+ _inbuf.push_back(static_cast<unsigned char>(c));
+ }
+ return c;
+ }
+
+ int sync () override {
+ flush(Z_NO_FLUSH);
+ return 0;
+ }
+
+ protected:
+ /** Compresses the chunk of data present in the input buffer
+ * and writes it to the assigned output stream.
+ * @param[in] flushmode flush mode of deflate function (Z_NO_FLUSH or Z_FINISH)
+ * @throws ZLibException if compression failed */
+ void flush (int flushmode) {
+ if (_opened) {
+ _zstream.avail_in = static_cast<uInt>(_inbuf.size());
+ _zstream.next_in = _inbuf.data();
+ do {
+ _zstream.avail_out = static_cast<uInt>(_zbuf.size());
+ _zstream.next_out = _zbuf.data();
+ int ret = deflate(&_zstream, flushmode);
+ if (ret == Z_STREAM_ERROR) {
+ close(false);
+ throw ZLibException("stream error during data compression");
+ }
+ auto have = _zbuf.size()-_zstream.avail_out;
+ _sink->sputn(reinterpret_cast<char*>(_zbuf.data()), have);
+ } while (_zstream.avail_out == 0);
+ }
+ _inbuf.clear();
+ }
+
+ /** Closes the buffer so that further output doesn't reach the sink.
+ * @param[in] finish if true, flushes the remaining data and finishes the compression process */
+ void close (bool finish) {
+ if (_opened) {
+ if (finish)
+ flush(Z_FINISH);
+ deflateEnd(&_zstream);
+ _sink = nullptr;
+ _opened = false;
+ }
+ }
+
+ private:
+ z_stream _zstream;
+ std::streambuf *_sink = nullptr; ///< target buffer where the compressded data is flushed to
+ std::vector<Bytef> _inbuf; ///< buffer holding a chunk of data to be compressed
+ std::vector<Bytef> _zbuf; ///< buffer holding a chunk of compressed data
+ bool _opened = false; ///< true if ready to process the incoming data correctly
+};
+
+
+class ZLibOutputStream : private ZLibOutputBuffer, public std::ostream {
+ public:
+ ZLibOutputStream () : std::ostream(this) {}
+
+ ZLibOutputStream (std::ostream &os, ZLibCompressionFormat format, int zipLevel)
+ : ZLibOutputBuffer(os.rdbuf(), format, zipLevel), std::ostream(this) {}
+
+ ~ZLibOutputStream () override {close();}
+
+ bool open (std::ostream &os, ZLibCompressionFormat format, int zipLevel) {
+ ZLibOutputBuffer::close();
+ return ZLibOutputBuffer::open(os.rdbuf(), format, zipLevel);
+ }
+
+ void close () {
+ ZLibOutputBuffer::close();
+ }
+};
+
+
+class ZLibOutputFileStream : public ZLibOutputStream {
+ public:
+ ZLibOutputFileStream (const std::string &fname, ZLibCompressionFormat format, int zipLevel)
+ : _ofs(fname, std::ios::binary)
+ {
+ if (_ofs) {
+ if (_ofs.rdbuf())
+ open(_ofs, format, zipLevel);
+ else
+ _ofs.close();
+ }
+ }
+
+ ~ZLibOutputFileStream () override {close();}
+
+ private:
+ std::ofstream _ofs;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/dvisvgm.cpp b/dviware/dvisvgm/src/dvisvgm.cpp
new file mode 100644
index 0000000000..a2c28f8a77
--- /dev/null
+++ b/dviware/dvisvgm/src/dvisvgm.cpp
@@ -0,0 +1,478 @@
+/*************************************************************************
+** dvisvgm.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <config.h>
+#include <algorithm>
+#include <clipper.hpp>
+#include <fstream>
+#include <iostream>
+#include <potracelib.h>
+#include <sstream>
+#include <vector>
+#include <zlib.h>
+#include "CommandLine.hpp"
+#include "DVIToSVG.hpp"
+#include "DVIToSVGActions.hpp"
+#include "EPSToSVG.hpp"
+#include "FileFinder.hpp"
+#include "FileSystem.hpp"
+#include "Font.hpp"
+#include "FontEngine.hpp"
+#include "Ghostscript.hpp"
+#include "HashFunction.hpp"
+#include "HyperlinkManager.hpp"
+#include "Message.hpp"
+#include "PageSize.hpp"
+#include "PDFToSVG.hpp"
+#include "PSInterpreter.hpp"
+#include "PsSpecialHandler.hpp"
+#include "SignalHandler.hpp"
+#include "SourceInput.hpp"
+#include "optimizer/SVGOptimizer.hpp"
+#include "SVGOutput.hpp"
+#include "System.hpp"
+#include "XXHashFunction.hpp"
+#include "utility.hpp"
+#include "version.hpp"
+
+#ifndef DISABLE_WOFF
+#include <brotli/encode.h>
+//#include <woff2/version.h>
+#include "ffwrapper.h"
+#include "TTFAutohint.hpp"
+#endif
+
+using namespace std;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static string remove_path (string fname) {
+ fname = FileSystem::adaptPathSeperators(fname);
+ size_t slashpos = fname.rfind('/');
+ if (slashpos == string::npos)
+ return fname;
+ return fname.substr(slashpos+1);
+}
+
+
+static string ensure_suffix (string fname, const string &suffix) {
+ if (!fname.empty()) {
+ size_t dotpos = remove_path(fname).rfind('.');
+ if (dotpos == string::npos)
+ fname += "." + suffix;
+ }
+ return fname;
+}
+
+
+static string get_transformation_string (const CommandLine &args) {
+ ostringstream oss;
+ if (args.rotateOpt.given())
+ oss << 'R' << args.rotateOpt.value() << ",w/2,h/2";
+ if (args.translateOpt.given())
+ oss << 'T' << args.translateOpt.value();
+ if (args.scaleOpt.given())
+ oss << 'S' << args.scaleOpt.value();
+ if (args.transformOpt.given())
+ oss << args.transformOpt.value();
+ return oss.str();
+}
+
+
+static void set_libgs (CommandLine &args) {
+#if !defined(DISABLE_GS) && !defined(HAVE_LIBGS)
+ if (args.libgsOpt.given())
+ Ghostscript::LIBGS_NAME = args.libgsOpt.value();
+ else if (getenv("LIBGS"))
+ Ghostscript::LIBGS_NAME = getenv("LIBGS");
+#endif
+}
+
+
+static bool set_cache_dir (const CommandLine &args) {
+ if (args.cacheOpt.given() && !args.cacheOpt.value().empty()) {
+ if (args.cacheOpt.value() == "none")
+ PhysicalFont::CACHE_PATH = nullptr;
+ else if (FileSystem::exists(args.cacheOpt.value()))
+ PhysicalFont::CACHE_PATH = args.cacheOpt.value().c_str();
+ else
+ Message::wstream(true) << "cache directory '" << args.cacheOpt.value() << "' does not exist (caching disabled)\n";
+ }
+ else if (const char *userdir = FileSystem::userdir()) {
+#ifdef _WIN32
+ string cachedir = "\\.dvisvgm\\cache";
+#else
+ string cachedir = "/.dvisvgm/cache";
+#endif
+ static string cachepath = userdir + cachedir;
+ if (!FileSystem::exists(cachepath))
+ FileSystem::mkdir(cachepath);
+ PhysicalFont::CACHE_PATH = cachepath.c_str();
+ }
+ if (args.cacheOpt.given() && args.cacheOpt.value().empty()) {
+ cout << "cache directory: " << (PhysicalFont::CACHE_PATH ? PhysicalFont::CACHE_PATH : "(none)") << '\n';
+ try {
+ if (PhysicalFont::CACHE_PATH)
+ FontCache::fontinfo(PhysicalFont::CACHE_PATH, cout, true);
+ }
+ catch (StreamReaderException &e) {
+ Message::wstream(true) << "failed reading cache data\n";
+ }
+ return false;
+ }
+ return true;
+}
+
+
+static bool set_temp_dir (const CommandLine &args) {
+ if (args.tmpdirOpt.given()) {
+ if (!args.tmpdirOpt.value().empty())
+ FileSystem::TMPDIR = args.tmpdirOpt.value();
+ else {
+ cout << "temporary folder: " << FileSystem::tmpdir() << '\n';
+ return false;
+ }
+ }
+ return true;
+}
+
+
+static void check_bbox (const string &bboxstr) {
+ for (const char *fmt : {"none", "min", "preview", "papersize", "dvi"})
+ if (bboxstr == fmt)
+ return;
+ if (isalpha(bboxstr[0])) {
+ try {
+ PageSize size(bboxstr);
+ }
+ catch (const PageSizeException &e) {
+ throw MessageException("invalid bounding box format '" + bboxstr + "'");
+ }
+ }
+ else {
+ // check if given bbox argument is valid, i.e. doesn't throw an exception
+ BoundingBox bbox;
+ bbox.set(bboxstr);
+ }
+}
+
+
+// Helper class to generate a list of version information of the used libraries.
+class VersionInfo {
+ public:
+ void add (const string &name, const string &version, bool ignoreEmpty=false) {
+ if (!version.empty() || !ignoreEmpty)
+ append(name, util::trim(version));
+ }
+
+ void add (const string &name, const char *version, bool ignoreEmpty=false) {
+ if (version && *version)
+ append(name, util::trim(version));
+ else if (!ignoreEmpty)
+ append(name, "");
+ }
+
+ void add (const string &name, const vector<int> &versionComponents) {
+ string version;
+ for (auto it=versionComponents.begin(); it != versionComponents.end(); ++it) {
+ if (it != versionComponents.begin())
+ version += '.';
+ version += to_string(*it);
+ }
+ append(name, version);
+ }
+
+ /** Adds a version number given as a single unsigned integer, and optionally
+ * extracts its components, e.g. 0x00010203 => "1.2.3" (3 components separated
+ * by multiples of 256).
+ * @param[in] name library name
+ * @param[in] version version number
+ * @param[in] compcount number of components the version consists of
+ * @param[in] factor factor used to separate the components */
+ void add (const string &name, uint32_t version, int compcount=1, uint32_t factor=0xffffffff) {
+ string str;
+ while (compcount-- > 0) {
+ if (!str.empty())
+ str.insert(0, ".");
+ str.insert(0, to_string(version % factor));
+ version /= factor;
+ }
+ append(name, str);
+ }
+
+ /** Writes the version information to the given output stream. */
+ void write (ostream &os) {
+ using Entry = pair<string,string>;
+ sort(_versionPairs.begin(), _versionPairs.end(), [](const Entry &e1, const Entry &e2) {
+ return util::tolower(e1.first) < util::tolower(e2.first);
+ });
+ size_t maxNameLength=0;
+ for (const Entry &versionPair : _versionPairs)
+ maxNameLength = max(maxNameLength, versionPair.first.length());
+ for (const Entry &versionPair : _versionPairs) {
+ string name = versionPair.first+":";
+ os << left << setw(maxNameLength+2) << name;
+ os << (versionPair.second.empty() ? "unknown" : versionPair.second) << '\n';
+ }
+ }
+
+ protected:
+ void append (const string &name, const string &version) {
+ _versionPairs.emplace_back(pair<string,string>(name, version));
+ }
+
+ private:
+ vector<pair<string,string>> _versionPairs;
+};
+
+
+static void print_version (bool extended) {
+ string versionstr = string(PROGRAM_NAME)+" "+PROGRAM_VERSION;
+#ifdef TARGET_SYSTEM
+ if (extended && strlen(TARGET_SYSTEM) > 0)
+ versionstr += " (" TARGET_SYSTEM ")";
+#endif
+ cout << versionstr << '\n';
+ if (extended) {
+ cout << string(versionstr.length(), '-') << '\n';
+ VersionInfo versionInfo;
+ versionInfo.add("clipper", CLIPPER_VERSION);
+ versionInfo.add("freetype", FontEngine::version());
+ versionInfo.add("potrace", strchr(potrace_version(), ' '));
+ versionInfo.add("xxhash", XXH64HashFunction::version(), 3, 100);
+ versionInfo.add("zlib", zlibVersion());
+ versionInfo.add("Ghostscript", Ghostscript().revisionstr(), true);
+#ifndef DISABLE_WOFF
+ versionInfo.add("brotli", BrotliEncoderVersion(), 3, 0x1000);
+// versionInfo.add("woff2", woff2::version, 3, 0x100);
+ versionInfo.add("fontforge", ff_version());
+ versionInfo.add("ttfautohint", TTFAutohint().version(), true);
+#endif
+#ifdef MIKTEX
+ versionInfo.add("MiKTeX", FileFinder::instance().version());
+#else
+ versionInfo.add("kpathsea", FileFinder::instance().version());
+#endif
+ versionInfo.write(cout);
+ }
+}
+
+
+static void init_fontmap (const CommandLine &cmdline) {
+ string mapseq;
+ if (cmdline.fontmapOpt.given())
+ mapseq = cmdline.fontmapOpt.value();
+ bool additional = !mapseq.empty() && strchr("+-=", mapseq[0]);
+ if (mapseq.empty() || additional) {
+ bool found = false;
+ for (string mapfile : {"ps2pk", "pdftex", "dvipdfm", "psfonts"}) {
+ if ((found = FontMap::instance().read(mapfile+".map")))
+ break;
+ }
+ if (!found)
+ Message::wstream(true) << "none of the default map files could be found\n";
+ }
+ if (!mapseq.empty())
+ FontMap::instance().read(mapseq);
+}
+
+
+/** Returns a unique string for the current state of the command-line
+ * options affecting the SVG output. */
+static string svg_options_hash (const CommandLine &cmdline) {
+ // options affecting the SVG output
+ vector<const CL::Option*> svg_options = {
+ &cmdline.bboxOpt, &cmdline.clipjoinOpt, &cmdline.colornamesOpt, &cmdline.commentsOpt,
+ &cmdline.exactBboxOpt, &cmdline.fontFormatOpt, &cmdline.fontmapOpt, &cmdline.gradOverlapOpt,
+ &cmdline.gradSegmentsOpt, &cmdline.gradSimplifyOpt, &cmdline.linkmarkOpt, &cmdline.magOpt,
+ &cmdline.noFontsOpt, &cmdline.noMergeOpt, &cmdline.noSpecialsOpt, &cmdline.noStylesOpt,
+ &cmdline.optimizeOpt, &cmdline.precisionOpt, &cmdline.relativeOpt, &cmdline.zoomOpt
+ };
+ string idString = get_transformation_string(cmdline);
+ for (const CL::Option *opt : svg_options) {
+ idString += char(opt->given());
+ idString += opt->valueString();
+ }
+ return XXH64HashFunction(idString).digestString();
+}
+
+
+static bool list_page_hashes (const CommandLine &cmdline, DVIToSVG &dvisvg) {
+ if (cmdline.pageHashesOpt.given()) {
+ DVIToSVG::PAGE_HASH_SETTINGS.setParameters(cmdline.pageHashesOpt.value());
+ DVIToSVG::PAGE_HASH_SETTINGS.setOptionHash(svg_options_hash(cmdline));
+ if (DVIToSVG::PAGE_HASH_SETTINGS.isSet(DVIToSVG::HashSettings::P_LIST)) {
+ dvisvg.listHashes(cmdline.pageOpt.value(), cout);
+ return true;
+ }
+ }
+ return false;
+}
+
+
+static void set_variables (const CommandLine &cmdline) {
+ Message::COLORIZE = cmdline.colorOpt.given();
+ if (cmdline.progressOpt.given()) {
+ DVIToSVG::COMPUTE_PROGRESS = true;
+ SpecialActions::PROGRESSBAR_DELAY = cmdline.progressOpt.value();
+ }
+ Color::SUPPRESS_COLOR_NAMES = !cmdline.colornamesOpt.given();
+ SVGTree::CREATE_CSS = !cmdline.noStylesOpt.given();
+ SVGTree::USE_FONTS = !cmdline.noFontsOpt.given();
+ if (!SVGTree::setFontFormat(cmdline.fontFormatOpt.value())) {
+ string msg = "unknown font format '"+cmdline.fontFormatOpt.value()+"' (supported formats: ";
+ for (const string &format : FontWriter::supportedFormats())
+ msg += format + ", ";
+ msg.erase(msg.end()-2);
+ throw CL::CommandLineException(msg);
+ }
+ SVGTree::CREATE_USE_ELEMENTS = cmdline.noFontsOpt.value() < 1;
+ SVGTree::ZOOM_FACTOR = cmdline.zoomOpt.value();
+ SVGTree::RELATIVE_PATH_CMDS = cmdline.relativeOpt.given();
+ SVGTree::MERGE_CHARS = !cmdline.noMergeOpt.given();
+ SVGTree::ADD_COMMENTS = cmdline.commentsOpt.given();
+ DVIToSVG::TRACE_MODE = cmdline.traceAllOpt.given() ? (cmdline.traceAllOpt.value() ? 'a' : 'm') : 0;
+ Message::LEVEL = cmdline.verbosityOpt.value();
+ PhysicalFont::EXACT_BBOX = cmdline.exactBboxOpt.given();
+ PhysicalFont::KEEP_TEMP_FILES = cmdline.keepOpt.given();
+ PhysicalFont::METAFONT_MAG = max(1.0, cmdline.magOpt.value());
+ XMLString::DECIMAL_PLACES = max(0, min(6, cmdline.precisionOpt.value()));
+ PsSpecialHandler::COMPUTE_CLIPPATHS_INTERSECTIONS = cmdline.clipjoinOpt.given();
+ PsSpecialHandler::SHADING_SEGMENT_OVERLAP = cmdline.gradOverlapOpt.given();
+ PsSpecialHandler::SHADING_SEGMENT_SIZE = max(1, cmdline.gradSegmentsOpt.value());
+ PsSpecialHandler::SHADING_SIMPLIFY_DELTA = cmdline.gradSimplifyOpt.value();
+ if (cmdline.optimizeOpt.given()) {
+ SVGOptimizer::MODULE_SEQUENCE = cmdline.optimizeOpt.value();
+ vector<string> modnames;
+ if (!SVGOptimizer().checkModuleString(SVGOptimizer::MODULE_SEQUENCE, modnames)) {
+ string msg = "invalid optimizer module";
+ if (modnames.size() > 1) msg += 's';
+ msg += ": ";
+ for (const string &modname : modnames)
+ msg += modname + ", ";
+ msg.erase(msg.end()-2);
+ throw CL::CommandLineException(msg);
+ }
+ }
+}
+
+
+static void timer_message (double start_time, const pair<int,int> *pageinfo) {
+ Message::mstream().indent(0);
+ if (!pageinfo)
+ Message::mstream(false, Message::MC_PAGE_NUMBER) << "\n" << "file";
+ else {
+ Message::mstream(false, Message::MC_PAGE_NUMBER) << "\n" << pageinfo->first << " of " << pageinfo->second << " page";
+ if (pageinfo->second > 1)
+ Message::mstream(false, Message::MC_PAGE_NUMBER) << 's';
+ }
+ Message::mstream(false, Message::MC_PAGE_NUMBER) << " converted in " << (System::time()-start_time) << " seconds\n";
+}
+
+
+int main (int argc, char *argv[]) {
+ try {
+ CommandLine cmdline;
+ cmdline.parse(argc, argv);
+ if (argc == 1 || cmdline.helpOpt.given()) {
+ cmdline.help(cout, cmdline.helpOpt.value());
+ return 0;
+ }
+ FileFinder::init(argv[0], "dvisvgm", !cmdline.noMktexmfOpt.given());
+ set_libgs(cmdline);
+ if (cmdline.versionOpt.given()) {
+ print_version(cmdline.versionOpt.value());
+ return 0;
+ }
+ if (cmdline.listSpecialsOpt.given()) {
+ DVIToSVG::setProcessSpecials();
+ SpecialManager::instance().writeHandlerInfo(cout);
+ return 0;
+ }
+ if (cmdline.optimizeOpt.value() == "list") {
+ SVGOptimizer().listModules(cout);
+ return 0;
+ }
+ if (!set_cache_dir(cmdline) || !set_temp_dir(cmdline))
+ return 0;
+ check_bbox(cmdline.bboxOpt.value());
+ if (!HyperlinkManager::setLinkMarker(cmdline.linkmarkOpt.value()))
+ Message::wstream(true) << "invalid argument '"+cmdline.linkmarkOpt.value()+"' supplied for option --linkmark\n";
+ if (cmdline.stdinOpt.given() || cmdline.singleDashGiven()) {
+ if (!cmdline.filenames().empty())
+ throw MessageException("option - or --stdin can't be used together with a filename");
+ cmdline.addFilename(""); // empty filename => read from stdin
+ }
+ if (argc > 1 && cmdline.filenames().empty())
+ throw MessageException("no input file given");
+
+ SignalHandler::instance().start();
+ string inputfile = ensure_suffix(cmdline.filenames()[0],
+ cmdline.epsOpt.given() ? "eps" : cmdline.pdfOpt.given() ? "pdf" : "dvi");
+ SourceInput srcin(inputfile);
+ if (!srcin.getInputStream(true))
+ throw MessageException("can't open file '" + srcin.getMessageFileName() + "' for reading");
+
+ double start_time = System::time();
+ set_variables(cmdline);
+ SVGOutput out(cmdline.stdoutOpt.given() ? "" : srcin.getFileName(),
+ cmdline.outputOpt.value(),
+ cmdline.zipOpt.given() ? cmdline.zipOpt.value() : 0);
+ pair<int,int> pageinfo;
+ if (cmdline.epsOpt.given() || cmdline.pdfOpt.given()) {
+ auto img2svg = unique_ptr<ImageToSVG>(
+ cmdline.epsOpt.given()
+ ? static_cast<ImageToSVG*>(new EPSToSVG(srcin.getFilePath(), out))
+ : static_cast<ImageToSVG*>(new PDFToSVG(srcin.getFilePath(), out)));
+ img2svg->setPageTransformation(get_transformation_string(cmdline));
+ img2svg->convert(cmdline.pageOpt.value(), &pageinfo);
+ timer_message(start_time, img2svg->isSinglePageFormat() ? nullptr : &pageinfo);
+ }
+ else {
+ init_fontmap(cmdline);
+ DVIToSVG dvi2svg(srcin.getInputStream(), out);
+ if (list_page_hashes(cmdline, dvi2svg))
+ return 0;
+ const char *ignore_specials=nullptr;
+ if (cmdline.noSpecialsOpt.given())
+ ignore_specials = cmdline.noSpecialsOpt.value().empty() ? "*" : cmdline.noSpecialsOpt.value().c_str();
+ dvi2svg.setProcessSpecials(ignore_specials, true);
+ dvi2svg.setPageTransformation(get_transformation_string(cmdline));
+ dvi2svg.setPageSize(cmdline.bboxOpt.value());
+
+ dvi2svg.convert(cmdline.pageOpt.value(), &pageinfo);
+ timer_message(start_time, &pageinfo);
+ }
+ }
+ catch (DVIException &e) {
+ Message::estream() << "\nDVI error: " << e.what() << '\n';
+ }
+ catch (PSException &e) {
+ Message::estream() << "\nPostScript error: " << e.what() << '\n';
+ }
+ catch (SignalException &e) {
+ Message::wstream().clearline();
+ Message::wstream(true) << "execution interrupted by user\n";
+ }
+ catch (exception &e) {
+ Message::estream(true) << e.what() << '\n';
+ }
+ return 0;
+}
diff --git a/dviware/dvisvgm/src/ffwrapper.c b/dviware/dvisvgm/src/ffwrapper.c
new file mode 100644
index 0000000000..68377f0762
--- /dev/null
+++ b/dviware/dvisvgm/src/ffwrapper.c
@@ -0,0 +1,84 @@
+/*************************************************************************
+** ffwrapper.c **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <fontforge.h>
+#include "ffwrapper.h"
+
+#ifdef _NDEBUG
+static void no_post_error (const char *title, const char *error, ...) {}
+static void no_warning (const char *format, ...) {}
+#endif
+
+
+/** Initializes the FontForge library. */
+void ff_init () {
+ InitSimpleStuff();
+#ifdef _NDEBUG
+ ui_interface.post_error = &no_post_error;
+ ui_interface.logwarning = &no_warning;
+#endif
+}
+
+
+int ff_version () {
+ return FONTFORGE_VERSIONDATE_RAW;
+}
+
+
+/** Runs the autohinter for all glyphs in a given font.
+ * @param[in,out] sf the font to be autohinted
+ * @param[in] order2 if != 0, target font requires quadratic rather than cubic splines */
+static void ff_autohint (SplineFont *sf, int order2) {
+ BlueData bd;
+ GlobalInstrCt gic;
+ if (order2)
+ SFConvertToOrder2(sf);
+ QuickBlues(sf, ly_fore, &bd);
+ if (order2)
+ InitGlobalInstrCt(&gic, sf, ly_fore, &bd);
+ for (int i=0; i < sf->glyphcnt; i++) {
+ SplineChar *sc = sf->glyphs[i];
+ if (sc != NULL) {
+ SplineChar *sc = sf->glyphs[i];
+ SplineCharAutoHint(sc, ly_fore, &bd); // generate hints
+ if (order2)
+ NowakowskiSCAutoInstr(&gic, sc); // generate TTF instructions
+ }
+ }
+ if (order2)
+ FreeGlobalInstrCt(&gic);
+}
+
+
+/** Creates a TrueType font from a FontForge SFD file.
+ * @param[in] sfdname name of SFD file
+ * @param[in] ttfname name of TrueType file
+ * @param[in] autohint run the autohinter if != 0 */
+int ff_sfd_to_ttf (const char *sfdname, const char *ttfname, int autohint) {
+ int ret=0;
+ SplineFont *sf = SFDRead((char*)sfdname);
+ if (sf) {
+ if (autohint)
+ ff_autohint(sf, true);
+ ret = WriteTTFFont((char*)ttfname, sf, ff_ttf, 0, 0, 0, sf->map, ly_fore);
+ SplineFontFree(sf);
+ }
+ return ret;
+}
diff --git a/dviware/dvisvgm/src/ffwrapper.h b/dviware/dvisvgm/src/ffwrapper.h
new file mode 100644
index 0000000000..f652a97f9a
--- /dev/null
+++ b/dviware/dvisvgm/src/ffwrapper.h
@@ -0,0 +1,37 @@
+/*************************************************************************
+** ffwrapper.h **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef FFWRAPPER_H
+#define FFWRAPPER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void ff_init ();
+int ff_version ();
+int ff_sfd_to_ttf (const char *sfdname, const char *ttfname, int autohint);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/dviware/dvisvgm/src/iapi.h b/dviware/dvisvgm/src/iapi.h
new file mode 100644
index 0000000000..8e29d938d0
--- /dev/null
+++ b/dviware/dvisvgm/src/iapi.h
@@ -0,0 +1,300 @@
+/* Copyright (C) 2001-2006 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* $Id: iapi.h 9043 2008-08-28 22:48:19Z giles $ */
+
+/*
+ * Public API for Ghostscript interpreter
+ * for use both as DLL and for static linking.
+ *
+ * Should work for Windows, OS/2, Linux, Mac.
+ *
+ * DLL exported functions should be as similar as possible to imain.c
+ * You will need to include "ierrors.h".
+ *
+ * Current problems:
+ * 1. Ghostscript does not support multiple instances.
+ * 2. Global variables in gs_main_instance_default()
+ * and gsapi_instance_counter
+ */
+
+/* Exported functions may need different prefix
+ * GSDLLEXPORT marks functions as exported
+ * GSDLLAPI is the calling convention used on functions exported
+ * by Ghostscript
+ * GSDLLCALL is used on callback functions called by Ghostscript
+ * When you include this header file in the caller, you may
+ * need to change the definitions by defining these
+ * before including this header file.
+ * Make sure you get the calling convention correct, otherwise your
+ * program will crash either during callbacks or soon after returning
+ * due to stack corruption.
+ */
+
+#ifndef iapi_INCLUDED
+# define iapi_INCLUDED
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(_WINDOWS_) || defined(__WINDOWS__)
+# ifndef _Windows
+# define _Windows
+# endif
+#endif
+
+#ifdef _Windows
+# ifndef GSDLLEXPORT
+# define GSDLLEXPORT __declspec(dllexport)
+# endif
+# ifndef GSDLLAPI
+# define GSDLLAPI __stdcall
+# endif
+# ifndef GSDLLCALL
+# define GSDLLCALL __stdcall
+# endif
+#endif /* _Windows */
+
+#if defined(OS2) && defined(__IBMC__)
+# ifndef GSDLLAPI
+# define GSDLLAPI _System
+# endif
+# ifndef GSDLLCALL
+# define GSDLLCALL _System
+# endif
+#endif /* OS2 && __IBMC */
+
+#ifdef __MACOS__
+# pragma export on
+#endif
+
+#ifndef GSDLLEXPORT
+# define GSDLLEXPORT
+#endif
+#ifndef GSDLLAPI
+# define GSDLLAPI
+#endif
+#ifndef GSDLLCALL
+# define GSDLLCALL
+#endif
+
+#if defined(__IBMC__)
+# define GSDLLAPIPTR * GSDLLAPI
+# define GSDLLCALLPTR * GSDLLCALL
+#else
+# define GSDLLAPIPTR GSDLLAPI *
+# define GSDLLCALLPTR GSDLLCALL *
+#endif
+
+#ifndef display_callback_DEFINED
+# define display_callback_DEFINED
+typedef struct display_callback_s display_callback;
+#endif
+
+typedef struct gsapi_revision_s {
+ const char *product;
+ const char *copyright;
+ long revision;
+ long revisiondate;
+} gsapi_revision_t;
+
+
+/* Get version numbers and strings.
+ * This is safe to call at any time.
+ * You should call this first to make sure that the correct version
+ * of the Ghostscript is being used.
+ * pr is a pointer to a revision structure.
+ * len is the size of this structure in bytes.
+ * Returns 0 if OK, or if len too small (additional parameters
+ * have been added to the structure) it will return the required
+ * size of the structure.
+ */
+GSDLLEXPORT int GSDLLAPI
+gsapi_revision(gsapi_revision_t *pr, int len);
+
+/*
+ * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+ * Ghostscript supports only one instance.
+ * The current implementation uses a global static instance
+ * counter to make sure that only a single instance is used.
+ * If you try to create two instances, the second attempt
+ * will return < 0 and set pinstance to NULL.
+ * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+ */
+/* Create a new instance of Ghostscript.
+ * This instance is passed to most other API functions.
+ * The caller_handle will be provided to callback functions.
+ */
+
+GSDLLEXPORT int GSDLLAPI
+gsapi_new_instance(void **pinstance, void *caller_handle);
+
+/*
+ * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+ * Ghostscript supports only one instance.
+ * The current implementation uses a global static instance
+ * counter to make sure that only a single instance is used.
+ * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+ */
+/* Destroy an instance of Ghostscript
+ * Before you call this, Ghostscript must have finished.
+ * If Ghostscript has been initialised, you must call gsapi_exit()
+ * before gsapi_delete_instance.
+ */
+GSDLLEXPORT void GSDLLAPI
+gsapi_delete_instance(void *instance);
+
+/* Set the callback functions for stdio
+ * The stdin callback function should return the number of
+ * characters read, 0 for EOF, or -1 for error.
+ * The stdout and stderr callback functions should return
+ * the number of characters written.
+ * If a callback address is NULL, the real stdio will be used.
+ */
+GSDLLEXPORT int GSDLLAPI
+gsapi_set_stdio(void *instance,
+ int (GSDLLCALLPTR stdin_fn)(void *caller_handle, char *buf, int len),
+ int (GSDLLCALLPTR stdout_fn)(void *caller_handle, const char *str, int len),
+ int (GSDLLCALLPTR stderr_fn)(void *caller_handle, const char *str, int len));
+
+/* Set the callback function for polling.
+ * This is used for handling window events or cooperative
+ * multitasking. This function will only be called if
+ * Ghostscript was compiled with CHECK_INTERRUPTS
+ * as described in gpcheck.h.
+ * The polling function should return 0 if all is well,
+ * and negative if it wants ghostscript to abort.
+ * The polling function must be fast.
+ */
+GSDLLEXPORT int GSDLLAPI gsapi_set_poll(void *instance,
+ int (GSDLLCALLPTR poll_fn)(void *caller_handle));
+
+/* Set the display device callback structure.
+ * If the display device is used, this must be called
+ * after gsapi_new_instance() and before gsapi_init_with_args().
+ * See gdevdisp.h for more details.
+ */
+GSDLLEXPORT int GSDLLAPI gsapi_set_display_callback(
+ void *instance, display_callback *callback);
+
+
+/* Initialise the interpreter.
+ * This calls gs_main_init_with_args() in imainarg.c
+ * 1. If quit or EOF occur during gsapi_init_with_args(),
+ * the return value will be e_Quit. This is not an error.
+ * You must call gsapi_exit() and must not call any other
+ * gsapi_XXX functions.
+ * 2. If usage info should be displayed, the return value will be e_Info
+ * which is not an error. Do not call gsapi_exit().
+ * 3. Under normal conditions this returns 0. You would then
+ * call one or more gsapi_run_*() functions and then finish
+ * with gsapi_exit().
+ */
+GSDLLEXPORT int GSDLLAPI gsapi_init_with_args(void *instance,
+ int argc, char **argv);
+
+/*
+ * The gsapi_run_* functions are like gs_main_run_* except
+ * that the error_object is omitted.
+ * If these functions return <= -100, either quit or a fatal
+ * error has occured. You then call gsapi_exit() next.
+ * The only exception is gsapi_run_string_continue()
+ * which will return e_NeedInput if all is well.
+ */
+
+GSDLLEXPORT int GSDLLAPI
+gsapi_run_string_begin(void *instance,
+ int user_errors, int *pexit_code);
+
+GSDLLEXPORT int GSDLLAPI
+gsapi_run_string_continue(void *instance,
+ const char *str, unsigned int length, int user_errors, int *pexit_code);
+
+GSDLLEXPORT int GSDLLAPI
+gsapi_run_string_end(void *instance,
+ int user_errors, int *pexit_code);
+
+GSDLLEXPORT int GSDLLAPI
+gsapi_run_string_with_length(void *instance,
+ const char *str, unsigned int length, int user_errors, int *pexit_code);
+
+GSDLLEXPORT int GSDLLAPI
+gsapi_run_string(void *instance,
+ const char *str, int user_errors, int *pexit_code);
+
+GSDLLEXPORT int GSDLLAPI
+gsapi_run_file(void *instance,
+ const char *file_name, int user_errors, int *pexit_code);
+
+
+/* Exit the interpreter.
+ * This must be called on shutdown if gsapi_init_with_args()
+ * has been called, and just before gsapi_delete_instance().
+ */
+GSDLLEXPORT int GSDLLAPI
+gsapi_exit(void *instance);
+
+/* Visual Tracer */
+/* This function is only for debug purpose clients */
+struct vd_trace_interface_s;
+GSDLLEXPORT void GSDLLAPI
+gsapi_set_visual_tracer(struct vd_trace_interface_s *I);
+
+
+/* function prototypes */
+typedef int (GSDLLAPIPTR PFN_gsapi_revision)(
+ gsapi_revision_t *pr, int len);
+typedef int (GSDLLAPIPTR PFN_gsapi_new_instance)(
+ void **pinstance, void *caller_handle);
+typedef void (GSDLLAPIPTR PFN_gsapi_delete_instance)(
+ void *instance);
+typedef int (GSDLLAPIPTR PFN_gsapi_set_stdio)(void *instance,
+ int (GSDLLCALLPTR stdin_fn)(void *caller_handle, char *buf, int len),
+ int (GSDLLCALLPTR stdout_fn)(void *caller_handle, const char *str, int len),
+ int (GSDLLCALLPTR stderr_fn)(void *caller_handle, const char *str, int len));
+typedef int (GSDLLAPIPTR PFN_gsapi_set_poll)(void *instance,
+ int(GSDLLCALLPTR poll_fn)(void *caller_handle));
+typedef int (GSDLLAPIPTR PFN_gsapi_set_display_callback)(
+ void *instance, display_callback *callback);
+typedef int (GSDLLAPIPTR PFN_gsapi_init_with_args)(
+ void *instance, int argc, char **argv);
+typedef int (GSDLLAPIPTR PFN_gsapi_run_string_begin)(
+ void *instance, int user_errors, int *pexit_code);
+typedef int (GSDLLAPIPTR PFN_gsapi_run_string_continue)(
+ void *instance, const char *str, unsigned int length,
+ int user_errors, int *pexit_code);
+typedef int (GSDLLAPIPTR PFN_gsapi_run_string_end)(
+ void *instance, int user_errors, int *pexit_code);
+typedef int (GSDLLAPIPTR PFN_gsapi_run_string_with_length)(
+ void *instance, const char *str, unsigned int length,
+ int user_errors, int *pexit_code);
+typedef int (GSDLLAPIPTR PFN_gsapi_run_string)(
+ void *instance, const char *str,
+ int user_errors, int *pexit_code);
+typedef int (GSDLLAPIPTR PFN_gsapi_run_file)(void *instance,
+ const char *file_name, int user_errors, int *pexit_code);
+typedef int (GSDLLAPIPTR PFN_gsapi_exit)(void *instance);
+typedef void (GSDLLAPIPTR PFN_gsapi_set_visual_tracer)
+ (struct vd_trace_interface_s *I);
+
+
+#ifdef __MACOS__
+#pragma export off
+#endif
+
+#ifdef __cplusplus
+} /* extern 'C' protection */
+#endif
+
+#endif /* iapi_INCLUDED */
diff --git a/dviware/dvisvgm/src/ierrors.h b/dviware/dvisvgm/src/ierrors.h
new file mode 100644
index 0000000000..3184341177
--- /dev/null
+++ b/dviware/dvisvgm/src/ierrors.h
@@ -0,0 +1,153 @@
+/* Copyright (C) 2001-2006 Artifex Software, Inc.
+ All Rights Reserved.
+
+ This software is provided AS-IS with no warranty, either express or
+ implied.
+
+ This software is distributed under license and may not be copied, modified
+ or distributed except as expressly authorized under the terms of that
+ license. Refer to licensing information at http://www.artifex.com/
+ or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
+ San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
+*/
+
+/* $Id: ierrors.h 8022 2007-06-05 22:23:38Z giles $ */
+/* Definition of error codes */
+
+#ifndef ierrors_INCLUDED
+# define ierrors_INCLUDED
+
+/*
+ * DO NOT USE THIS FILE IN THE GRAPHICS LIBRARY.
+ * THIS FILE IS PART OF THE POSTSCRIPT INTERPRETER.
+ * USE gserrors.h IN THE LIBRARY.
+ */
+
+/*
+ * A procedure that may return an error always returns
+ * a non-negative value (zero, unless otherwise noted) for success,
+ * or negative for failure.
+ * We use ints rather than an enum to avoid a lot of casting.
+ */
+
+/* Define the error name table */
+extern const char *const gs_error_names[];
+
+ /* ------ PostScript Level 1 errors ------ */
+
+#define e_unknownerror (-1) /* unknown error */
+#define e_dictfull (-2)
+#define e_dictstackoverflow (-3)
+#define e_dictstackunderflow (-4)
+#define e_execstackoverflow (-5)
+#define e_interrupt (-6)
+#define e_invalidaccess (-7)
+#define e_invalidexit (-8)
+#define e_invalidfileaccess (-9)
+#define e_invalidfont (-10)
+#define e_invalidrestore (-11)
+#define e_ioerror (-12)
+#define e_limitcheck (-13)
+#define e_nocurrentpoint (-14)
+#define e_rangecheck (-15)
+#define e_stackoverflow (-16)
+#define e_stackunderflow (-17)
+#define e_syntaxerror (-18)
+#define e_timeout (-19)
+#define e_typecheck (-20)
+#define e_undefined (-21)
+#define e_undefinedfilename (-22)
+#define e_undefinedresult (-23)
+#define e_unmatchedmark (-24)
+#define e_VMerror (-25) /* must be the last Level 1 error */
+
+#define LEVEL1_ERROR_NAMES\
+ "unknownerror", "dictfull", "dictstackoverflow", "dictstackunderflow",\
+ "execstackoverflow", "interrupt", "invalidaccess", "invalidexit",\
+ "invalidfileaccess", "invalidfont", "invalidrestore", "ioerror",\
+ "limitcheck", "nocurrentpoint", "rangecheck", "stackoverflow",\
+ "stackunderflow", "syntaxerror", "timeout", "typecheck", "undefined",\
+ "undefinedfilename", "undefinedresult", "unmatchedmark", "VMerror"
+
+ /* ------ Additional Level 2 errors (also in DPS) ------ */
+
+#define e_configurationerror (-26)
+#define e_undefinedresource (-27)
+#define e_unregistered (-28)
+
+#define LEVEL2_ERROR_NAMES\
+ "configurationerror", "undefinedresource", "unregistered"
+
+ /* ------ Additional DPS errors ------ */
+
+#define e_invalidcontext (-29)
+/* invalidid is for the NeXT DPS extension. */
+#define e_invalidid (-30)
+
+#define DPS_ERROR_NAMES\
+ "invalidcontext", "invalidid"
+
+#define ERROR_NAMES\
+ LEVEL1_ERROR_NAMES, LEVEL2_ERROR_NAMES, DPS_ERROR_NAMES
+
+ /* ------ Pseudo-errors used internally ------ */
+
+/*
+ * Internal code for a fatal error.
+ * gs_interpret also returns this for a .quit with a positive exit code.
+ */
+#define e_Fatal (-100)
+
+/*
+ * Internal code for the .quit operator.
+ * The real quit code is an integer on the operand stack.
+ * gs_interpret returns this only for a .quit with a zero exit code.
+ */
+#define e_Quit (-101)
+
+/*
+ * Internal code for a normal exit from the interpreter.
+ * Do not use outside of interp.c.
+ */
+#define e_InterpreterExit (-102)
+
+/*
+ * Internal code that indicates that a procedure has been stored in the
+ * remap_proc of the graphics state, and should be called before retrying
+ * the current token. This is used for color remapping involving a call
+ * back into the interpreter -- inelegant, but effective.
+ */
+#define e_RemapColor (-103)
+
+/*
+ * Internal code to indicate we have underflowed the top block
+ * of the e-stack.
+ */
+#define e_ExecStackUnderflow (-104)
+
+/*
+ * Internal code for the vmreclaim operator with a positive operand.
+ * We need to handle this as an error because otherwise the interpreter
+ * won't reload enough of its state when the operator returns.
+ */
+#define e_VMreclaim (-105)
+
+/*
+ * Internal code for requesting more input from run_string.
+ */
+#define e_NeedInput (-106)
+
+/*
+ * Internal code for a normal exit when usage info is displayed.
+ * This allows Window versions of Ghostscript to pause until
+ * the message can be read.
+ */
+#define e_Info (-110)
+
+/*
+ * Define which error codes require re-executing the current object.
+ */
+#define ERROR_IS_INTERRUPT(ecode)\
+ ((ecode) == e_interrupt || (ecode) == e_timeout)
+
+#endif /* ierrors_INCLUDED */
diff --git a/dviware/dvisvgm/src/macros.hpp b/dviware/dvisvgm/src/macros.hpp
new file mode 100644
index 0000000000..beeb54f04b
--- /dev/null
+++ b/dviware/dvisvgm/src/macros.hpp
@@ -0,0 +1,39 @@
+/*************************************************************************
+** macros.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef MACROS_HPP
+#define MACROS_HPP
+
+#define IMPLEMENT_ARITHMETIC_OPERATOR(class, op) \
+ inline class operator op (class a, const class &b) { \
+ return a op##= b; \
+ }
+
+#define IMPLEMENT_ARITHMETIC_OPERATOR2(class, scalar, op) \
+ inline class operator op (class a, scalar b) { \
+ return a op##= b; \
+ }
+
+#define IMPLEMENT_OUTPUT_OPERATOR(class) \
+ inline std::ostream& operator << (std::ostream &os, class obj) { \
+ return obj.write(os); \
+ }
+
+#endif
diff --git a/dviware/dvisvgm/src/optimizer/AttributeExtractor.cpp b/dviware/dvisvgm/src/optimizer/AttributeExtractor.cpp
new file mode 100644
index 0000000000..ae2a0e36d7
--- /dev/null
+++ b/dviware/dvisvgm/src/optimizer/AttributeExtractor.cpp
@@ -0,0 +1,176 @@
+/*************************************************************************
+** AttributeExtractor.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <array>
+#include "AttributeExtractor.hpp"
+
+using namespace std;
+
+/** Constructs a new run object for an attribute and a sequence of sibling nodes.
+ * @param[in] attr attribute to look for
+ * @param[in] first first element of node sequence to scan */
+AttributeExtractor::AttributeRun::AttributeRun (const Attribute &attr, XMLElement *first) {
+ _length = 1;
+ _first = first;
+ for (_last=_first->next(); _last; _last=_last->next()) {
+ if (_last->toText() || _last->toCData()) // don't include text/CDATA nodes
+ break;
+ if (XMLElement *childElem = _last->toElement()) {
+ if (!groupable(*childElem))
+ break;
+ const char *val = childElem->getAttributeValue(attr.name);
+ if (val && val == attr.value)
+ ++_length;
+ else
+ break;
+ }
+ }
+ if (_first != _last && _last)
+ _last = _last->prev();
+}
+
+
+const char* AttributeExtractor::info () const {
+ return "move common attributes from a sequence of elements to enclosing groups";
+}
+
+
+/** Performs the attribute extraction on a given context node. Each extracted
+ * attribute gets its own group, i.e. the extraction of multiple attributes
+ * of the same elements leads to nested groups.
+ * @param[in] context attributes of all children in this element are extracted
+ * @param[in] recurse if true, the algorithm is recursively performed on all descendant elements */
+void AttributeExtractor::execute (XMLElement *context, bool recurse) {
+ if (!context || context->empty())
+ return;
+ if (recurse) {
+ for (auto node : *context) {
+ if (XMLElement *elem = node->toElement())
+ execute(elem, true);
+ }
+ }
+ for (XMLNode *child=context->firstChild(); child; child=child->next()) {
+ if (XMLElement *elem = child->toElement())
+ child = extractAttribute(elem);
+ }
+}
+
+
+/** Looks for the first attribute not yet processed and tries to group it. If
+ * there is a sequence of adjacent sibling nodes N1,...,Nn with an identical inheritable
+ * attribute, the function creates a group element with this attribute and moves the
+ * nodes N1,...,Nn into that group. The group is inserted at the former position of N1.
+ * @param[in] elem first element of a node sequence with potentially identical attributes
+ * @return the new group element if attributes could be grouped, 'elem' otherwise */
+XMLNode* AttributeExtractor::extractAttribute (XMLElement *elem) {
+ for (const auto &currentAttribute : elem->attributes()) {
+ if (!inheritable(currentAttribute) || extracted(currentAttribute))
+ continue;
+ AttributeRun run(currentAttribute, elem);
+ if (run.length() >= MIN_RUN_LENGTH) {
+ XMLElement::Attribute attrib = currentAttribute;
+ XMLElement *group = XMLElement::wrap(run.first(), run.last(), "g");
+ group->addAttribute(attrib.name, attrib.value);
+ // remove attribute from the grouped elements but keep it on elements with 'id' attribute
+ // since they can be referenced, and keep 'fill' attribute on animation elements
+ for (XMLNode *node : *group) {
+ XMLElement *elem = node->toElement();
+ if (elem && extractable(attrib, *elem))
+ elem->removeAttribute(attrib.name);
+ }
+ // continue with children of the new group but ignore the just extracted attribute
+ _extractedAttributes.insert(attrib.name);
+ execute(group, false);
+ _extractedAttributes.erase(attrib.name);
+ return group;
+ }
+ }
+ return elem;
+}
+
+
+/** Checks whether an element type is allowed to be put in a group element (<g>...</g>).
+ * For now we only consider a subset of the actually allowed set of elements.
+ * @param[in] elem name of element to check
+ * @return true if the element is groupable */
+bool AttributeExtractor::groupable (const XMLElement &elem) {
+ // https://www.w3.org/TR/SVG/struct.html#GElement
+ static constexpr auto names = util::make_array(
+ "a", "altGlyphDef", "animate", "animateColor", "animateMotion", "animateTransform",
+ "circle", "clipPath", "color-profile", "cursor", "defs", "desc", "ellipse", "filter",
+ "font", "font-face", "foreignObject", "g", "image", "line", "linearGradient", "marker",
+ "mask", "path", "pattern", "polygon", "polyline", "radialGradient", "rect", "set",
+ "style", "switch", "symbol", "text", "title", "use", "view"
+ );
+ return binary_search(names.begin(), names.end(), elem.name(), [](const string &name1, const string &name2) {
+ return name1 < name2;
+ });
+}
+
+
+/** Checks whether an SVG attribute A of an element E implicitly propagates its properties
+ * to all child elements of E that don't specify A. For now we only consider a subset of
+ * the inheritable properties.
+ * @param[in] attrib name of attribute to check
+ * @return true if the attribute is inheritable */
+bool AttributeExtractor::inheritable (const Attribute &attrib) {
+ // subset of inheritable properties listed on https://www.w3.org/TR/SVG11/propidx.html
+ // clip-path is not inheritable but can be moved to the parent element as long as
+ // no child gets an different clip-path attribute
+ // https://www.w3.org/TR/SVG11/styling.html#Inheritance
+ static constexpr auto names = util::make_array(
+ "clip-path", "clip-rule", "color", "color-interpolation", "color-interpolation-filters", "color-profile",
+ "color-rendering", "direction", "fill", "fill-opacity", "fill-rule", "font", "font-family", "font-size",
+ "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "glyph-orientation-horizontal",
+ "glyph-orientation-vertical", "letter-spacing", "paint-order", "stroke", "stroke-dasharray", "stroke-dashoffset",
+ "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "transform",
+ "visibility", "word-spacing", "writing-mode"
+ );
+ return binary_search(names.begin(), names.end(), attrib.name, [](const string &name1, const string &name2) {
+ return name1 < name2;
+ });
+}
+
+
+/** Checks whether an attribute is allowed to be removed from a given element. */
+bool AttributeExtractor::extractable (const Attribute &attrib, XMLElement &element) {
+ if (element.hasAttribute("id"))
+ return false;
+ if (attrib.name != "fill")
+ return true;
+ // the 'fill' attribute of animation elements has different semantics than
+ // that of graphics elements => don't extract it from animation nodes
+ // https://www.w3.org/TR/SVG11/animate.html#TimingAttributes
+ static constexpr auto names = util::make_array(
+ "animate", "animateColor", "animateMotion", "animateTransform", "set"
+ );
+ auto it = find_if(names.begin(), names.end(), [&](const string &name) {
+ return element.name() == name;
+ });
+ return it == names.end();
+}
+
+
+/** Returns true if a given attribute was already extracted from the
+ * current run of elements. */
+bool AttributeExtractor::extracted (const Attribute &attr) const {
+ return _extractedAttributes.find(attr.name) != _extractedAttributes.end();
+}
diff --git a/dviware/dvisvgm/src/optimizer/AttributeExtractor.hpp b/dviware/dvisvgm/src/optimizer/AttributeExtractor.hpp
new file mode 100644
index 0000000000..8cb9de5bab
--- /dev/null
+++ b/dviware/dvisvgm/src/optimizer/AttributeExtractor.hpp
@@ -0,0 +1,64 @@
+/*************************************************************************
+** AttributeExtractor.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#pragma once
+
+#include <set>
+#include <string>
+#include "OptimizerModule.hpp"
+#include "../XMLNode.hpp"
+
+/** Moves common attributes of adjacent elements to enclosing groups. */
+class AttributeExtractor : public OptimizerModule {
+ friend class GroupCollapser;
+ using Attribute = XMLElement::Attribute;
+
+ /** Represents a range of adjacent nodes where all elements have a common attribute. */
+ struct AttributeRun {
+ public:
+ AttributeRun (const Attribute &attr, XMLElement *first);
+ XMLNode* first () {return _first;}
+ XMLNode* last () {return _last;}
+// XMLNodeIterator begin () {return XMLNodeIterator(_first);}
+// XMLNodeIterator end () {return XMLNodeIterator(_last);}
+ int length () const {return _length;}
+
+ private:
+ int _length; ///< run length excluding non-element nodes
+ XMLNode *_first, *_last; ///< first and last node in run
+ };
+
+ public:
+ void execute (XMLElement*, XMLElement *context) override {execute(context, true);};
+ const char* info () const override;
+
+ protected:
+ void execute (XMLElement *context, bool recurse);
+ XMLNode* extractAttribute (XMLElement *elem);
+ bool extracted (const Attribute &attr) const;
+ static bool groupable (const XMLElement &elem);
+ static bool inheritable (const Attribute &attrib);
+ static bool extractable (const Attribute &attr, XMLElement &element);
+
+ private:
+ std::set<std::string> _extractedAttributes;
+ static constexpr int MIN_RUN_LENGTH = 3;
+};
+
diff --git a/dviware/dvisvgm/src/optimizer/DependencyGraph.hpp b/dviware/dvisvgm/src/optimizer/DependencyGraph.hpp
new file mode 100644
index 0000000000..c9111b43d5
--- /dev/null
+++ b/dviware/dvisvgm/src/optimizer/DependencyGraph.hpp
@@ -0,0 +1,133 @@
+/*************************************************************************
+** DependencyGraph.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef DEPENDENCYGRAPH_HPP
+#define DEPENDENCYGRAPH_HPP
+
+#include <map>
+#include <memory>
+#include <set>
+#include <vector>
+#include "../utility.hpp"
+
+template <typename T>
+class DependencyGraph {
+ struct GraphNode {
+ explicit GraphNode (const T &k) : key(k), dependent() {}
+
+ void addDependee (GraphNode *node) {
+ if (node) {
+ node->dependent = this;
+ dependees.insert(node);
+ }
+ }
+
+ void unlinkDependees () {
+ for (GraphNode *dependee : dependees)
+ dependee->dependent = nullptr;
+ dependees.clear();
+ }
+
+ void unlinkDependee (GraphNode *dependee) {
+ auto it = dependees.find(dependee);
+ if (it != dependees.end()) {
+ (*it)->dependent = nullptr;
+ dependees.erase(it);
+ }
+ }
+
+ T key;
+ GraphNode *dependent;
+ std::set<GraphNode*> dependees;
+ };
+
+ using NodeMap = std::map<T, std::unique_ptr<GraphNode>>;
+
+ public:
+ /** Inserts a new isolated node into the dependency graph. */
+ void insert (const T &key) {
+ if (!contains(key))
+ _nodeMap.emplace(key, util::make_unique<GraphNode>(key));
+ }
+
+ /** Inserts a new node to the graph and adds a dependency on an existing one to it.
+ * @param[in] key ID of new node to insert
+ * @param[in] dependantKey ID of node the new node should depend on */
+ void insert (const T &dependentKey, const T &key) {
+ if (!contains(key)) {
+ auto dependentIter = _nodeMap.find(dependentKey);
+ if (dependentIter != _nodeMap.end()) {
+ auto node = util::make_unique<GraphNode>(key);
+ dependentIter->second->addDependee(node.get());
+ _nodeMap.emplace(key, std::move(node));
+ }
+ }
+ }
+
+ /** Removes a node and all its dependents from the graph. */
+ void removeDependencyPath (const T &key) {
+ auto it = _nodeMap.find(key);
+ if (it != _nodeMap.end()) {
+ for (GraphNode *node = it->second.get(); node;) {
+ GraphNode *dependent = node->dependent;
+ node->unlinkDependees();
+ if (dependent)
+ dependent->unlinkDependee(node);
+ _nodeMap.erase(node->key);
+ node = dependent;
+ }
+ }
+ }
+
+ /** Returns the IDs of all nodes present in the graph. */
+ std::vector<T> getKeys () const {
+ std::vector<T> keys;
+ for (auto &entry : _nodeMap)
+ keys.emplace_back(entry.first);
+ return keys;
+ }
+
+ bool contains (const T &value) const {
+ return _nodeMap.find(value) != _nodeMap.end();
+ }
+
+ bool empty () const {
+ return _nodeMap.empty();
+ }
+
+#if 0
+ void writeDOT (std::ostream &os) const {
+ os << "digraph {\n";
+ for (auto it=_nodeMap.begin(); it != _nodeMap.end(); ++it) {
+ GraphNode *node = it->second;
+ if (node->dependent)
+ os << (node->key) << " -> " << (node->dependent->key) << ";\n";
+ else if (node->dependees.empty())
+ os << (node->key) << ";\n";
+ }
+ os << "}\n";
+ }
+#endif
+
+ private:
+ NodeMap _nodeMap;
+};
+
+#endif
diff --git a/dviware/dvisvgm/src/optimizer/GroupCollapser.cpp b/dviware/dvisvgm/src/optimizer/GroupCollapser.cpp
new file mode 100644
index 0000000000..5d580f5ba0
--- /dev/null
+++ b/dviware/dvisvgm/src/optimizer/GroupCollapser.cpp
@@ -0,0 +1,165 @@
+/*************************************************************************
+** GroupCollapser.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <array>
+#include <string>
+#include <vector>
+#include "AttributeExtractor.hpp"
+#include "GroupCollapser.hpp"
+#include "../XMLNode.hpp"
+
+using namespace std;
+
+
+const char* GroupCollapser::info () const {
+ return "join nested group elements";
+}
+
+
+/** Checks if there's only a single child element and optional whitespace
+ * siblings in a given element.
+ * @param[in] elem element to check
+ * @return pointer to the only child element or nullptr */
+static XMLElement* only_child_element (XMLElement *elem) {
+ XMLElement *firstChildElement=nullptr;
+ for (XMLNode *child : *elem) {
+ if (XMLElement *childElement = child->toElement()) {
+ if (firstChildElement)
+ return nullptr;
+ firstChildElement = childElement;
+ }
+ else if (!child->toWSNode())
+ return nullptr;
+ }
+ return firstChildElement;
+}
+
+
+/** Removes all whitespace child nodes from a given element. */
+static void remove_ws_nodes (XMLElement *elem) {
+ XMLNode *node = elem->firstChild();
+ while (node) {
+ if (!node->toWSNode())
+ node = node->next();
+ else {
+ XMLNode *next = node->next();
+ XMLElement::remove(node);
+ node = next;
+ }
+ }
+}
+
+
+/** Recursively removes all redundant group elements from the given context element
+ * and moves their attributes to the corresponding parent element.
+ * @param[in] context root of the subtree to process */
+void GroupCollapser::execute (XMLElement *context) {
+ if (!context)
+ return;
+ XMLNode *node=context->firstChild();
+ while (node) {
+ XMLNode *next = node->next(); // keep safe pointer to next node
+ if (XMLElement *elem = node->toElement())
+ execute(elem);
+ node = next;
+ }
+ if (context->name() == "g" && context->attributes().empty()) {
+ // unwrap group without attributes
+ remove_ws_nodes(context);
+ XMLElement::unwrap(context);
+ }
+ else {
+ XMLElement *child = only_child_element(context);
+ if (child && collapsible(*context)) {
+ if (child->name() == "g" && unwrappable(*child, *context) && moveAttributes(*child, *context)) {
+ remove_ws_nodes(context);
+ XMLElement::unwrap(child);
+ }
+ }
+ }
+}
+
+
+/** Moves all attributes from an element to another one. Attributes already
+ * present in the destination element are overwritten or combined.
+ * @param[in] source element the attributes are taken from
+ * @param[in] dest element that receives the attributes
+ * @return true if all attributes have been moved */
+bool GroupCollapser::moveAttributes (XMLElement &source, XMLElement &dest) {
+ vector<string> movedAttributes;
+ for (const XMLElement::Attribute &attr : source.attributes()) {
+ if (attr.name == "transform") {
+ string transform;
+ if (const char *destvalue = dest.getAttributeValue("transform"))
+ transform = destvalue+attr.value;
+ else
+ transform = attr.value;
+ dest.addAttribute("transform", transform);
+ movedAttributes.emplace_back("transform");
+ }
+ else if (AttributeExtractor::inheritable(attr)) {
+ dest.addAttribute(attr.name, attr.value);
+ movedAttributes.emplace_back(attr.name);
+ }
+ }
+ for (const string &attrname : movedAttributes)
+ source.removeAttribute(attrname);
+ return source.attributes().empty();
+}
+
+
+/** Returns true if a given element is allowed to take the inheritable attributes
+ * and children of a child group without changing the semantics.
+ * @param[in] element group element to check */
+bool GroupCollapser::collapsible (const XMLElement &element) {
+ // the 'fill' attribute of animation elements has different semantics than
+ // that of graphics elements => don't collapse them
+ static constexpr auto names = util::make_array(
+ "animate", "animateColor", "animateMotion", "animateTransform", "set"
+ );
+ auto it = find_if(names.begin(), names.end(), [&](const string &name) {
+ return element.name() == name;
+ });
+ return it == names.end();
+}
+
+
+/** Returns true if a given group element is allowed to be unwrapped, i.e. its
+ * attributes and children can be moved to the parent without changing the semantics.
+ * @param[in] source element whose children and attributes should be moved
+ * @param[in] dest element that should receive the children and attributes */
+bool GroupCollapser::unwrappable (const XMLElement &source, const XMLElement &dest) {
+ // check for colliding clip-path attributes
+ if (const char *cp1 = source.getAttributeValue("clip-path")) {
+ if (const char *cp2 = dest.getAttributeValue("clip-path")) {
+ if (string(cp1) != cp2)
+ return false;
+ }
+ }
+ // these attributes prevent a group from being unwrapped
+ static constexpr auto attribs = util::make_array(
+ "class", "id", "filter", "mask", "style"
+ );
+ auto it = find_if(attribs.begin(), attribs.end(), [&](const string &name) {
+ return source.hasAttribute(name) || dest.hasAttribute(name);
+ });
+ return it == attribs.end();
+}
diff --git a/dviware/dvisvgm/src/optimizer/GroupCollapser.hpp b/dviware/dvisvgm/src/optimizer/GroupCollapser.hpp
new file mode 100644
index 0000000000..8f8a22d4ba
--- /dev/null
+++ b/dviware/dvisvgm/src/optimizer/GroupCollapser.hpp
@@ -0,0 +1,36 @@
+/*************************************************************************
+** GroupCollapser.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#pragma once
+
+#include "OptimizerModule.hpp"
+
+/** Joins the attributes of nested groups and removes groups without attributes. */
+class GroupCollapser : public OptimizerModule {
+ public:
+ void execute (XMLElement*, XMLElement *context) override {execute(context);};
+ void execute (XMLElement *context);
+ const char* info () const override;
+
+ protected:
+ bool moveAttributes (XMLElement &source, XMLElement &dest);
+ static bool collapsible (const XMLElement &elem);
+ static bool unwrappable (const XMLElement &source, const XMLElement &dest);
+};
diff --git a/dviware/dvisvgm/src/optimizer/Makefile.am b/dviware/dvisvgm/src/optimizer/Makefile.am
new file mode 100644
index 0000000000..41c0266009
--- /dev/null
+++ b/dviware/dvisvgm/src/optimizer/Makefile.am
@@ -0,0 +1,12 @@
+noinst_LTLIBRARIES = liboptimizer.la
+
+liboptimizer_la_SOURCES = \
+ AttributeExtractor.hpp AttributeExtractor.cpp \
+ DependencyGraph.hpp \
+ GroupCollapser.hpp GroupCollapser.cpp \
+ OptimizerModule.hpp \
+ RedundantElementRemover.hpp RedundantElementRemover.cpp \
+ SVGOptimizer.hpp SVGOptimizer.cpp \
+ TransformSimplifier.hpp TransformSimplifier.cpp \
+ WSNodeRemover.hpp WSNodeRemover.cpp
+
diff --git a/dviware/dvisvgm/src/optimizer/Makefile.in b/dviware/dvisvgm/src/optimizer/Makefile.in
new file mode 100644
index 0000000000..64b288a723
--- /dev/null
+++ b/dviware/dvisvgm/src/optimizer/Makefile.in
@@ -0,0 +1,690 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+target_triplet = @target@
+subdir = src/optimizer
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4/ax_code_coverage.m4 \
+ $(top_srcdir)/m4/ax_cxx_compile_stdcxx.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+liboptimizer_la_LIBADD =
+am_liboptimizer_la_OBJECTS = AttributeExtractor.lo GroupCollapser.lo \
+ RedundantElementRemover.lo SVGOptimizer.lo \
+ TransformSimplifier.lo WSNodeRemover.lo
+liboptimizer_la_OBJECTS = $(am_liboptimizer_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/AttributeExtractor.Plo \
+ ./$(DEPDIR)/GroupCollapser.Plo \
+ ./$(DEPDIR)/RedundantElementRemover.Plo \
+ ./$(DEPDIR)/SVGOptimizer.Plo \
+ ./$(DEPDIR)/TransformSimplifier.Plo \
+ ./$(DEPDIR)/WSNodeRemover.Plo
+am__mv = mv -f
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(liboptimizer_la_SOURCES)
+DIST_SOURCES = $(liboptimizer_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_CPPFLAGS = @AM_CPPFLAGS@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AM_LDFLAGS = @AM_LDFLAGS@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BROTLI_CFLAGS = @BROTLI_CFLAGS@
+BROTLI_LIBS = @BROTLI_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CODE_COVERAGE_CFLAGS = @CODE_COVERAGE_CFLAGS@
+CODE_COVERAGE_CPPFLAGS = @CODE_COVERAGE_CPPFLAGS@
+CODE_COVERAGE_CXXFLAGS = @CODE_COVERAGE_CXXFLAGS@
+CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@
+CODE_COVERAGE_LDFLAGS = @CODE_COVERAGE_LDFLAGS@
+CODE_COVERAGE_LIBS = @CODE_COVERAGE_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DATE = @DATE@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FREETYPE_CFLAGS = @FREETYPE_CFLAGS@
+FREETYPE_LIBS = @FREETYPE_LIBS@
+GCOV = @GCOV@
+GENHTML = @GENHTML@
+GREP = @GREP@
+HAVE_CXX11 = @HAVE_CXX11@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KPSE_CFLAGS = @KPSE_CFLAGS@
+KPSE_LIBS = @KPSE_LIBS@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBCRYPTO_CFLAGS = @LIBCRYPTO_CFLAGS@
+LIBCRYPTO_LIBS = @LIBCRYPTO_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+TTFAUTOHINT_CFLAGS = @TTFAUTOHINT_CFLAGS@
+TTFAUTOHINT_LIBS = @TTFAUTOHINT_LIBS@
+VERSION = @VERSION@
+WOFF2_CFLAGS = @WOFF2_CFLAGS@
+WOFF2_LIBS = @WOFF2_LIBS@
+XMLTO = @XMLTO@
+XSLTPROC = @XSLTPROC@
+ZLIB_CFLAGS = @ZLIB_CFLAGS@
+ZLIB_LIBS = @ZLIB_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+dvisvgm_srcdir = @dvisvgm_srcdir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target = @target@
+target_alias = @target_alias@
+target_cpu = @target_cpu@
+target_os = @target_os@
+target_vendor = @target_vendor@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = liboptimizer.la
+liboptimizer_la_SOURCES = \
+ AttributeExtractor.hpp AttributeExtractor.cpp \
+ DependencyGraph.hpp \
+ GroupCollapser.hpp GroupCollapser.cpp \
+ OptimizerModule.hpp \
+ RedundantElementRemover.hpp RedundantElementRemover.cpp \
+ SVGOptimizer.hpp SVGOptimizer.cpp \
+ TransformSimplifier.hpp TransformSimplifier.cpp \
+ WSNodeRemover.hpp WSNodeRemover.cpp
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .cpp .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/optimizer/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/optimizer/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+liboptimizer.la: $(liboptimizer_la_OBJECTS) $(liboptimizer_la_DEPENDENCIES) $(EXTRA_liboptimizer_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(CXXLINK) $(liboptimizer_la_OBJECTS) $(liboptimizer_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/AttributeExtractor.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/GroupCollapser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RedundantElementRemover.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SVGOptimizer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TransformSimplifier.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/WSNodeRemover.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.cpp.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cpp.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cpp.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCXX_TRUE@ $(LTCXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/AttributeExtractor.Plo
+ -rm -f ./$(DEPDIR)/GroupCollapser.Plo
+ -rm -f ./$(DEPDIR)/RedundantElementRemover.Plo
+ -rm -f ./$(DEPDIR)/SVGOptimizer.Plo
+ -rm -f ./$(DEPDIR)/TransformSimplifier.Plo
+ -rm -f ./$(DEPDIR)/WSNodeRemover.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/AttributeExtractor.Plo
+ -rm -f ./$(DEPDIR)/GroupCollapser.Plo
+ -rm -f ./$(DEPDIR)/RedundantElementRemover.Plo
+ -rm -f ./$(DEPDIR)/SVGOptimizer.Plo
+ -rm -f ./$(DEPDIR)/TransformSimplifier.Plo
+ -rm -f ./$(DEPDIR)/WSNodeRemover.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/dviware/dvisvgm/src/optimizer/OptimizerModule.hpp b/dviware/dvisvgm/src/optimizer/OptimizerModule.hpp
new file mode 100644
index 0000000000..ff4fec1b4d
--- /dev/null
+++ b/dviware/dvisvgm/src/optimizer/OptimizerModule.hpp
@@ -0,0 +1,30 @@
+/*************************************************************************
+** OptimizerModule.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#pragma once
+
+class XMLElement;
+
+class OptimizerModule {
+ public:
+ virtual ~OptimizerModule () =default;
+ virtual void execute (XMLElement *defs, XMLElement *context) =0;
+ virtual const char* info () const =0;
+};
diff --git a/dviware/dvisvgm/src/optimizer/RedundantElementRemover.cpp b/dviware/dvisvgm/src/optimizer/RedundantElementRemover.cpp
new file mode 100644
index 0000000000..85d8e5b1fa
--- /dev/null
+++ b/dviware/dvisvgm/src/optimizer/RedundantElementRemover.cpp
@@ -0,0 +1,68 @@
+/*************************************************************************
+** RedundantElementRemover.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include "DependencyGraph.hpp"
+#include "RedundantElementRemover.hpp"
+#include "../XMLNode.hpp"
+
+using namespace std;
+
+const char* RedundantElementRemover::info () const {
+ return "remove redundant 'clipPath' elements";
+}
+
+
+/** Extracts the ID from a local URL reference like url(#abcde) */
+static inline string extract_id_from_url (const string &url) {
+ return url.substr(5, url.length()-6);
+}
+
+
+/** Removes elements present in the SVG tree that are not required.
+ * For now, only clipPath elements are removed. */
+void RedundantElementRemover::execute (XMLElement *defs, XMLElement *context) {
+ vector<XMLElement*> clipPathElements;
+ if (!defs || !context || !defs->getDescendants("clipPath", nullptr, clipPathElements))
+ return;
+
+ // collect dependencies between clipPath elements in the defs section of the SVG tree
+ DependencyGraph<string> idTree;
+ for (const XMLElement *clip : clipPathElements) {
+ if (const char *id = clip->getAttributeValue("id")) {
+ if (const char *url = clip->getAttributeValue("clip-path"))
+ idTree.insert(extract_id_from_url(url), id);
+ else
+ idTree.insert(id);
+ }
+ }
+ // collect elements that reference a clipPath, i.e. have a clip-path attribute
+ vector<XMLElement*> descendants;
+ context->getDescendants(nullptr, "clip-path", descendants);
+ // remove referenced IDs and their dependencies from the dependency graph
+ for (const XMLElement *elem : descendants) {
+ string idref = extract_id_from_url(elem->getAttributeValue("clip-path"));
+ idTree.removeDependencyPath(idref);
+ }
+ descendants.clear();
+ for (const string &str : idTree.getKeys()) {
+ XMLElement *node = defs->getFirstDescendant("clipPath", "id", str.c_str());
+ XMLElement::remove(node);
+ }
+}
diff --git a/dviware/dvisvgm/src/optimizer/RedundantElementRemover.hpp b/dviware/dvisvgm/src/optimizer/RedundantElementRemover.hpp
new file mode 100644
index 0000000000..dd46cfeba5
--- /dev/null
+++ b/dviware/dvisvgm/src/optimizer/RedundantElementRemover.hpp
@@ -0,0 +1,29 @@
+/*************************************************************************
+** RedundantElementRemover.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#pragma once
+
+#include "OptimizerModule.hpp"
+
+class RedundantElementRemover : public OptimizerModule {
+ public:
+ void execute (XMLElement *defs, XMLElement *context) override;
+ const char* info () const override;
+};
diff --git a/dviware/dvisvgm/src/optimizer/SVGOptimizer.cpp b/dviware/dvisvgm/src/optimizer/SVGOptimizer.cpp
new file mode 100644
index 0000000000..31cfeff6ff
--- /dev/null
+++ b/dviware/dvisvgm/src/optimizer/SVGOptimizer.cpp
@@ -0,0 +1,105 @@
+/*************************************************************************
+** SVGOptimizer.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <array>
+#include <map>
+#include "SVGOptimizer.hpp"
+#include "../SVGTree.hpp"
+
+#include "AttributeExtractor.hpp"
+#include "GroupCollapser.hpp"
+#include "RedundantElementRemover.hpp"
+#include "TransformSimplifier.hpp"
+#include "WSNodeRemover.hpp"
+
+using namespace std;
+
+string SVGOptimizer::MODULE_SEQUENCE;
+
+SVGOptimizer::SVGOptimizer (SVGTree *svg) : _svg(svg) {
+ // optimizer modules available to the user; must be listed in default order
+// _moduleEntries.emplace_back(ModuleEntry("remove-ws", util::make_unique<WSNodeRemover>()));
+ _moduleEntries.emplace_back(ModuleEntry("group-attributes", util::make_unique<AttributeExtractor>()));
+ _moduleEntries.emplace_back(ModuleEntry("collapse-groups", util::make_unique<GroupCollapser>()));
+ _moduleEntries.emplace_back(ModuleEntry("simplify-transform", util::make_unique<TransformSimplifier>()));
+ _moduleEntries.emplace_back(ModuleEntry("remove-clippath", util::make_unique<RedundantElementRemover>()));
+}
+
+
+void SVGOptimizer::execute () {
+ if (!_svg || MODULE_SEQUENCE == "none")
+ return;
+ if (MODULE_SEQUENCE.empty())
+ MODULE_SEQUENCE = "remove-clippath"; // default behaviour of previous dvisvgm releases
+ if (MODULE_SEQUENCE == "all") {
+ for (const auto &entry : _moduleEntries)
+ entry.module->execute(_svg->defsNode(), _svg->pageNode());
+ }
+ else {
+ vector<string> names = util::split(MODULE_SEQUENCE, ",");
+ for (const string &name : names) {
+ if (OptimizerModule *module = getModule(name))
+ module->execute(_svg->defsNode(), _svg->pageNode());
+ }
+ }
+}
+
+
+void SVGOptimizer::listModules (ostream &os) const {
+ size_t maxlen=0;
+ map<string, const char*> infos;
+ for (const auto &entry : _moduleEntries) {
+ maxlen = max(maxlen, entry.modname.length());
+ infos.emplace(entry.modname, entry.module->info());
+ }
+ for (const auto &infopair : infos) {
+ os << setw(maxlen) << left << infopair.first;
+ os << " | " << infopair.second << '\n';
+ }
+}
+
+
+/** Checks if all module names given in a comma-separated list are known.
+ * @param[in] namestr comma-separated list of module names
+ * @param[out] unknownNames names not recognized
+ * @return true if all names are known */
+bool SVGOptimizer::checkModuleString (string &namestr, vector<string> &unknownNames) const {
+ unknownNames.clear();
+ if (namestr.empty() || namestr == "all" || namestr == "none")
+ return true;
+ vector<string> givenNames = util::split(namestr, ",");
+ for (const string &name : givenNames) {
+ if (!getModule(name))
+ unknownNames.emplace_back(name);
+ }
+ return unknownNames.empty();
+}
+
+
+OptimizerModule* SVGOptimizer::getModule (const string &name) const {
+ auto it = find_if(_moduleEntries.begin(), _moduleEntries.end(), [&](const ModuleEntry &entry) {
+ return entry.modname == name;
+ });
+ if (it != _moduleEntries.end())
+ return (*it).module.get();
+ return nullptr;
+}
+
diff --git a/dviware/dvisvgm/src/optimizer/SVGOptimizer.hpp b/dviware/dvisvgm/src/optimizer/SVGOptimizer.hpp
new file mode 100644
index 0000000000..809f1f497c
--- /dev/null
+++ b/dviware/dvisvgm/src/optimizer/SVGOptimizer.hpp
@@ -0,0 +1,59 @@
+/*************************************************************************
+** SVGOptimizer.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#pragma once
+
+#include <memory>
+#include <ostream>
+#include <set>
+#include <vector>
+#include "OptimizerModule.hpp"
+#include "../XMLNode.hpp"
+
+class SVGTree;
+
+class SVGOptimizer {
+ struct ModuleEntry {
+ ModuleEntry (std::string name, std::unique_ptr<OptimizerModule> mod)
+ : modname(std::move(name)), module(std::move(mod)) {}
+
+ std::string modname;
+ std::unique_ptr<OptimizerModule> module;
+ };
+ public:
+ explicit SVGOptimizer (SVGTree *svg=nullptr);
+ explicit SVGOptimizer (SVGTree &svg) : SVGOptimizer(&svg) {}
+ void execute ();
+ void listModules (std::ostream &os) const;
+ bool checkModuleString (std::string &namestr, std::vector<std::string> &unknownNames) const;
+
+ static std::string MODULE_SEQUENCE;
+
+ protected:
+ OptimizerModule* getModule (const std::string &name) const;
+
+ private:
+ SVGTree *_svg;
+ std::vector<ModuleEntry> _moduleEntries;
+};
+
+
+
+
diff --git a/dviware/dvisvgm/src/optimizer/TransformSimplifier.cpp b/dviware/dvisvgm/src/optimizer/TransformSimplifier.cpp
new file mode 100644
index 0000000000..06d28926d4
--- /dev/null
+++ b/dviware/dvisvgm/src/optimizer/TransformSimplifier.cpp
@@ -0,0 +1,149 @@
+/*************************************************************************
+** TransformSimplifier.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <cmath>
+#include "TransformSimplifier.hpp"
+#include "../Matrix.hpp"
+#include "../utility.hpp"
+#include "../XMLNode.hpp"
+#include "../XMLString.hpp"
+
+using namespace std;
+
+const char* TransformSimplifier::info () const {
+ return "try to simplify and shorten the values of 'transform' attributes";
+}
+
+
+/** Tries to simplify the transform attributes of the context node and all its descendants. */
+void TransformSimplifier::execute (XMLElement *context) {
+ if (!context)
+ return;
+ if (const char *transform = context->getAttributeValue("transform")) {
+ Matrix matrix = Matrix::parseSVGTransform(transform);
+ string decomp = decompose(matrix);
+ if (decomp.length() > matrix.toSVG().length())
+ context->addAttribute("transform", matrix.toSVG());
+ else {
+ if (decomp.empty())
+ context->removeAttribute("transform");
+ else
+ context->addAttribute("transform", decomp);
+ }
+ }
+ // continue with child elements
+ for (XMLNode *child : *context) {
+ if (XMLElement *elem = child->toElement())
+ execute(elem);
+ }
+}
+
+
+static string translate_cmd (double dx, double dy) {
+ string ret;
+ XMLString dxstr(dx), dystr(dy);
+ if (dxstr != "0" || dystr != "0") {
+ ret = "translate("+dxstr;
+ if (dystr != "0")
+ ret += " "+dystr;
+ ret += ')';
+ }
+ return ret;
+}
+
+
+static string scale_cmd (double sx, double sy) {
+ string ret;
+ XMLString sxstr(sx), systr(sy);
+ if (sxstr != "1" || systr != "1") {
+ ret = "scale("+sxstr;
+ if (systr != "1")
+ ret += " "+systr;
+ ret += ')';
+ }
+ return ret;
+}
+
+
+static string rotate_cmd (double rad) {
+ string ret;
+ XMLString degstr(math::rad2deg(fmod(rad, math::TWO_PI)));
+ if (degstr != "0")
+ ret = "rotate("+degstr+")";
+ return ret;
+}
+
+
+static string skewx_cmd (double rad) {
+ string ret;
+ XMLString degstr(math::rad2deg(fmod(rad, math::PI)));
+ if (degstr != "0")
+ ret = "skewX("+degstr+")";
+ return ret;
+}
+
+
+static string skewy_cmd (double rad) {
+ string ret;
+ XMLString degstr(math::rad2deg(fmod(rad, math::PI)));
+ if (degstr != "0")
+ ret = "skewY("+degstr+")";
+ return ret;
+}
+
+
+static bool not_equal (double x, double y) {
+ return abs(x-y) >= 1e-6;
+}
+
+
+/** Decomposes a transformation matrix into a sequence of basic SVG transformations, i.e.
+ * translation, rotation, scaling, and skewing. The algorithm (QR-based decomposition)
+ * is taken from http://frederic-wang.fr/decomposition-of-2d-transform-matrices.html.
+ * @param[in] matrix matrix to decompose
+ * @return string containing the SVG transformation commands */
+string TransformSimplifier::decompose (const Matrix &matrix) {
+ // transformation matrix [a b c d e f] according to
+ // https://www.w3.org/TR/SVG11/coords.html#EstablishingANewUserSpace
+ double a = matrix.get(0, 0);
+ double b = matrix.get(1, 0);
+ double c = matrix.get(0, 1);
+ double d = matrix.get(1, 1);
+ double e = matrix.get(0, 2);
+ double f = matrix.get(1, 2);
+ string ret = translate_cmd(e, f);
+ double delta = a*d - b*c;
+ if (not_equal(a, 0) || not_equal(b, 0)) {
+ double r = sqrt(a*a + b*b);
+ ret += rotate_cmd(b > 0 ? acos(a/r) : -acos(a/r));
+ ret += scale_cmd(r, delta/r);
+ ret += skewx_cmd(atan((a*c + b*d)/(r*r)));
+ }
+ else if (not_equal(c, 0) || not_equal(d, 0)) {
+ double s = sqrt(c*c + d*d);
+ ret += rotate_cmd(math::HALF_PI - (d > 0 ? acos(-c/s) : -acos(c/s)));
+ ret += scale_cmd(delta/s, s);
+ ret += skewy_cmd(atan((a*c + b*d)/(s*s)));
+ }
+ else
+ ret += scale_cmd(0, 0);
+ return ret;
+}
+
diff --git a/dviware/dvisvgm/src/optimizer/TransformSimplifier.hpp b/dviware/dvisvgm/src/optimizer/TransformSimplifier.hpp
new file mode 100644
index 0000000000..40ac21a465
--- /dev/null
+++ b/dviware/dvisvgm/src/optimizer/TransformSimplifier.hpp
@@ -0,0 +1,36 @@
+/*************************************************************************
+** TransformSimplifier.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#pragma once
+
+#include <string>
+#include "OptimizerModule.hpp"
+
+class Matrix;
+
+class TransformSimplifier : public OptimizerModule {
+ public:
+ void execute (XMLElement*, XMLElement *context) override {execute(context);}
+ void execute (XMLElement *context);
+ const char* info () const override;
+
+ protected:
+ std::string decompose (const Matrix &matrix);
+};
diff --git a/dviware/dvisvgm/src/optimizer/WSNodeRemover.cpp b/dviware/dvisvgm/src/optimizer/WSNodeRemover.cpp
new file mode 100644
index 0000000000..2135e285bb
--- /dev/null
+++ b/dviware/dvisvgm/src/optimizer/WSNodeRemover.cpp
@@ -0,0 +1,45 @@
+/*************************************************************************
+** WSNodeRemover.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include "WSNodeRemover.hpp"
+#include "../XMLNode.hpp"
+
+const char* WSNodeRemover::info () const {
+ return "remove redundant whitespace nodes";
+}
+
+
+void WSNodeRemover::execute (XMLElement *context) {
+ if (!context)
+ return;
+ bool removeWS = context->name() != "text" && context->name() != "tspan";
+ XMLNode *child = context->firstChild();
+ while (child) {
+ if (removeWS && child->toWSNode()) {
+ XMLNode *next = child->next();
+ XMLElement::remove(child);
+ child = next;
+ continue;
+ }
+ if (XMLElement *elem = child->toElement())
+ execute(elem);
+ child = child->next();
+ }
+}
diff --git a/dviware/dvisvgm/src/optimizer/WSNodeRemover.hpp b/dviware/dvisvgm/src/optimizer/WSNodeRemover.hpp
new file mode 100644
index 0000000000..c4fe7f989e
--- /dev/null
+++ b/dviware/dvisvgm/src/optimizer/WSNodeRemover.hpp
@@ -0,0 +1,30 @@
+/*************************************************************************
+** WSNodeRemover.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#pragma once
+
+#include "OptimizerModule.hpp"
+
+class WSNodeRemover : public OptimizerModule {
+ public:
+ void execute (XMLElement*, XMLElement *context) override {execute(context);};
+ void execute (XMLElement *context);
+ const char* info () const override;
+};
diff --git a/dviware/dvisvgm/src/options.dtd b/dviware/dvisvgm/src/options.dtd
new file mode 100644
index 0000000000..7e29babc43
--- /dev/null
+++ b/dviware/dvisvgm/src/options.dtd
@@ -0,0 +1,49 @@
+<?xml encoding="UTF-8"?>
+<!-- *********************************************************************
+** options.dtd **
+** **
+** This file is part of dvisvgm - a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+***********************************************************************-->
+
+<!ELEMENT cmdline (program,options)>
+<!ATTLIST cmdline
+ class NMTOKEN #REQUIRED>
+
+<!ELEMENT program (usage+,description,copyright)>
+<!ELEMENT options (section)+>
+<!ELEMENT name (#PCDATA)>
+<!ELEMENT usage (#PCDATA)>
+<!ELEMENT copyright (#PCDATA)>
+
+<!ELEMENT section (option)+>
+<!ATTLIST section
+ title CDATA #REQUIRED>
+
+<!ELEMENT option (arg?,description)>
+<!ATTLIST option
+ long ID #REQUIRED
+ short NMTOKEN #IMPLIED
+ if CDATA #IMPLIED>
+
+<!ELEMENT arg EMPTY>
+<!ATTLIST arg
+ default NMTOKEN #IMPLIED
+ name CDATA #REQUIRED
+ optional (yes|no) #IMPLIED
+ type NMTOKEN #REQUIRED>
+
+<!ELEMENT description (#PCDATA)>
diff --git a/dviware/dvisvgm/src/options.xml b/dviware/dvisvgm/src/options.xml
new file mode 100644
index 0000000000..27975f1591
--- /dev/null
+++ b/dviware/dvisvgm/src/options.xml
@@ -0,0 +1,203 @@
+<?xml version="1.0"?>
+<!-- *********************************************************************
+** options.xml **
+** **
+** This file is part of dvisvgm - a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+***********************************************************************-->
+<!DOCTYPE cmdline SYSTEM "options.dtd">
+
+<cmdline class="CommandLine">
+ <program>
+ <usage>[options] dvifile</usage>
+ <usage>--eps [options] epsfile</usage>
+ <usage>--pdf [options] pdffile</usage>
+ <description>This program converts DVI files, as created by TeX/LaTeX, as well as\nEPS and PDF files to the XML-based scalable vector graphics format SVG.</description>
+ <copyright>Copyright (C) 2005-2019 Martin Gieseking &lt;martin.gieseking@uos.de></copyright>
+ </program>
+ <options>
+ <section title="Input options">
+ <option long="page" short="p">
+ <arg type="string" name="ranges" default="1"/>
+ <description>choose page(s) to convert</description>
+ </option>
+ <option long="fontmap" short="m">
+ <arg type="string" name="filenames"/>
+ <description>evaluate (additional) font map files</description>
+ </option>
+ <option long="eps" short="E" if="!defined(DISABLE_GS)">
+ <description>convert EPS file to SVG</description>
+ </option>
+ <option long="pdf" short="P" if="!defined(DISABLE_GS)">
+ <description>convert PDF file to SVG</description>
+ </option>
+ <option long="stdin">
+ <description>read input file from stdin</description>
+ </option>
+ </section>
+ <section title="SVG output options">
+ <option long="bbox" short="b">
+ <arg type="string" name="size" default="min"/>
+ <description>set size of bounding box</description>
+ </option>
+ <option long="clipjoin" short="j" if="!defined(DISABLE_GS)">
+ <description>compute intersection of clipping paths</description>
+ </option>
+ <option long="colornames">
+ <description>prefer color names to RGB values if possible</description>
+ </option>
+ <option long="comments">
+ <description>add comments with additional information</description>
+ </option>
+ <option long="font-format" short="f" if="!defined(DISABLE_WOFF)">
+ <arg type="string" name="format" default="svg"/>
+ <description>select file format of embedded fonts</description>
+ </option>
+ <option long="grad-overlap" if="!defined(DISABLE_GS)">
+ <description>create overlapping color gradient segments</description>
+ </option>
+ <option long="grad-segments" if="!defined(DISABLE_GS)">
+ <arg type="int" name="number" default="20"/>
+ <description>number of color gradient segments per row</description>
+ </option>
+ <option long="grad-simplify" if="!defined(DISABLE_GS)">
+ <arg type="double" name="delta" default="0.05"/>
+ <description>reduce level of detail for small segments</description>
+ </option>
+ <option long="linkmark" short="L">
+ <arg type="string" name="style" default="box"/>
+ <description>select how to mark hyperlinked areas</description>
+ </option>
+ <option long="optimize" short="O">
+ <arg name="modules" type="string" default="all" optional="yes"/>
+ <description>perform several SVG optimizations</description>
+ </option>
+ <option long="output" short="o">
+ <arg type="string" name="pattern"/>
+ <description>set name pattern of output files</description>
+ </option>
+ <option long="precision" short="d">
+ <arg type="int" name="number" default="0"/>
+ <description>set number of decimal points (0-6)</description>
+ </option>
+ <option long="relative" short="R">
+ <description>create relative path commands</description>
+ </option>
+ <option long="stdout" short="s">
+ <description>write SVG output to stdout</description>
+ </option>
+ <option long="tmpdir">
+ <arg type="string" name="path" optional="yes"/>
+ <description>set/print the directory for temporary files</description>
+ </option>
+ <option long="no-fonts" short="n">
+ <arg type="int" name="variant" default="0" optional="yes"/>
+ <description>draw glyphs by using path elements</description>
+ </option>
+ <option long="no-merge">
+ <description>don't merge adjacent text elements</description>
+ </option>
+ <option long="no-styles">
+ <description>don't use CSS styles to reference fonts</description>
+ </option>
+ <option long="zip" short="z">
+ <arg type="int" name="level" default="9" optional="yes"/>
+ <description>create compressed .svgz file</description>
+ </option>
+ </section>
+ <section title="SVG transformations">
+ <option long="rotate" short="r">
+ <arg type="double" name="angle"/>
+ <description>rotate page content clockwise</description>
+ </option>
+ <option long="scale" short="c">
+ <arg type="string" name="sx[,sy]"/>
+ <description>scale page content</description>
+ </option>
+ <option long="translate" short="t">
+ <arg type="string" name="tx[,ty]"/>
+ <description>shift page content</description>
+ </option>
+ <option long="transform" short="T">
+ <arg type="string" name="commands"/>
+ <description>transform page content</description>
+ </option>
+ <option long="zoom" short="Z">
+ <arg type="double" name="factor" default="1.0"/>
+ <description>zoom page content</description>
+ </option>
+ </section>
+ <section title="Processing options">
+ <option long="cache" short="C">
+ <arg type="string" name="dir" optional="yes"/>
+ <description>set/print path of cache directory</description>
+ </option>
+ <option long="exact-bbox" short="e">
+ <description>compute exact glyph bounding boxes</description>
+ </option>
+ <option long="keep">
+ <description>keep temporary files</description>
+ </option>
+ <option long="libgs" if="!defined(HAVE_LIBGS) &amp;&amp; !defined(DISABLE_GS)">
+ <arg name="filename" type="string"/>
+ <description>set name of Ghostscript shared library</description>
+ </option>
+ <option long="mag" short="M">
+ <arg type="double" name="factor" default="4"/>
+ <description>magnification of Metafont output</description>
+ </option>
+ <option long="no-mktexmf">
+ <description>don't try to create missing fonts</description>
+ </option>
+ <option long="no-specials" short="S">
+ <arg type="string" name="prefixes" optional="yes"/>
+ <description>don't process [selected] specials</description>
+ </option>
+ <option long="page-hashes" short="H">
+ <arg type="string" name="params" optional="yes" default="xxh64"/>
+ <description>activate usage of page hashes</description>
+ </option>
+ <option long="trace-all" short="a">
+ <arg name="retrace" type="bool" optional="yes" default="false"/>
+ <description>trace all glyphs of bitmap fonts</description>
+ </option>
+ </section>
+ <section title="Message options">
+ <option long="color">
+ <description>colorize messages</description>
+ </option>
+ <option long="help" short="h">
+ <arg name="mode" type="int" optional="yes" default="0"/>
+ <description>print this summary of options and exit</description>
+ </option>
+ <option long="list-specials" short="l">
+ <description>print supported special sets and exit</description>
+ </option>
+ <option long="progress">
+ <arg name="delay" type="double" optional="yes" default="0.5"/>
+ <description>enable progress indicator</description>
+ </option>
+ <option long="verbosity" short="v">
+ <arg type="unsigned" name="level" default="7"/>
+ <description>set verbosity level (0-7)</description>
+ </option>
+ <option long="version" short="V">
+ <arg type="bool" name="extended" optional="yes" default="false"/>
+ <description>print version and exit</description>
+ </option>
+ </section>
+ </options>
+</cmdline>
diff --git a/dviware/dvisvgm/src/psdefs.cpp b/dviware/dvisvgm/src/psdefs.cpp
new file mode 100644
index 0000000000..d8a07177b7
--- /dev/null
+++ b/dviware/dvisvgm/src/psdefs.cpp
@@ -0,0 +1,119 @@
+/*************************************************************************
+** psdefs.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include "PSInterpreter.hpp"
+
+const char *PSInterpreter::PSDEFS =
+"<</Install{matrix setmatrix}/HWResolution[72 72]/PageSize[10000 10000]/Imaging"
+"BBox null>>setpagedevice/@dodraw true store/@patcnt 0 store/@SD systemdict def"
+"/@UD userdict def true setglobal @SD/:save @SD/save get put @SD/:restore @SD/r"
+"estore get put @SD/:gsave @SD/gsave get put @SD/:grestore @SD/grestore get put"
+" @SD/:grestoreall @SD/grestoreall get put @SD/:newpath @SD/newpath get put @SD"
+"/:stroke @SD/stroke get put @SD/:fill @SD/fill get put @SD/:eofill @SD/eofill "
+"get put @SD/:clip @SD/clip get put @SD/:eoclip @SD/eoclip get put @SD/:charpat"
+"h @SD/charpath get put @SD/:show @SD/show get put @SD/:stringwidth @SD/stringw"
+"idth get put @SD/.setopacityalpha known not{@SD/.setopacityalpha{pop}put}if @S"
+"D/.setshapealpha known not{@SD/.setshapealpha{pop}put}if @SD/.setblendmode kno"
+"wn not{@SD/.setblendmode{pop}put}if @SD/prseq{-1 1{-1 roll =only( )print}for(\\"
+"n)print}put @SD/prcmd{( )exch(\\ndvi.)3{print}repeat prseq}put @SD/cvxall{{cvx"
+"}forall}put @SD/defpr{[exch[/copy @SD]cvxall 5 -1 roll dup 6 1 roll[/get/exec]"
+"cvxall 6 -1 roll dup 7 1 roll 4 -1 roll dup 5 1 roll dup length string cvs/prc"
+"md cvx]cvx def}put @SD/querypos{{currentpoint}stopped{$error/newerror false pu"
+"t}{2(querypos)prcmd}ifelse}put @SD/applyscalevals{1 0 dtransform exch dup mul "
+"exch dup mul add sqrt 0 1 dtransform exch dup mul exch dup mul add sqrt 1 0 dt"
+"ransform dup mul exch dup dup mul 3 -1 roll add dup 0 eq{pop}{sqrt div}ifelse "
+"3(applyscalevals)prcmd}put @SD/prpath{{2(moveto)prcmd}{2(lineto)prcmd}{6(curve"
+"to)prcmd}{0(closepath)prcmd}pathforall}put @SD/charpath{/@dodraw false store :"
+"charpath/@dodraw true store}put @SD/stringwidth{/@dodraw false store :stringwi"
+"dth/@dodraw true store}put @SD/show{@dodraw{dup :gsave currentpoint 2{50 mul e"
+"xch}repeat :newpath moveto 50 50/scale sysexec true charpath fill :grestore/@d"
+"odraw false store :show/@dodraw true store}{:show}ifelse}put @SD/varxyshow{exc"
+"h dup type/arraytype eq{<</arr 3 -1 roll/prc 5 -1 roll/chr 1 string/idx 0>>beg"
+"in{chr 0 3 -1 roll put :gsave chr show :grestore currentpoint prc moveto/idx i"
+"dx 1 add store}forall end}{pop show}ifelse}put @SD/xyshow{{exch arr idx 2 mul "
+"get add exch arr idx 2 mul 1 add get add}varxyshow}put @SD/xshow{{exch arr idx"
+" get add exch}varxyshow}put @SD/yshow{{arr idx get add}varxyshow}put @SD/awidt"
+"hshow{{1 string dup 0 5 index put :gsave show :grestore pop 0 rmoveto 3 index "
+"eq{4 index 4 index rmoveto}if 1 index 1 index rmoveto}exch cshow 5{pop}repeat}"
+"put @SD/widthshow{0 0 3 -1 roll pstack awidthshow}put @SD/ashow{0 0 0 6 3 roll"
+" awidthshow}put @SD/newpath{:newpath 1 1(newpath)prcmd}put @SD/stroke{@dodraw{"
+"prcolor 0 1(newpath)prcmd prpath 0(stroke)prcmd :newpath}{:stroke}ifelse}put @"
+"SD/fill{@dodraw{prcolor 0 1(newpath)prcmd prpath 0(fill)prcmd :newpath}{:fill}"
+"ifelse}put @SD/eofill{@dodraw{prcolor 0 1(newpath)prcmd prpath 0(eofill)prcmd "
+":newpath}{:eofill}ifelse}put @SD/clip{:clip 0 1(newpath)prcmd prpath 0(clip)pr"
+"cmd}put @SD/eoclip{:eoclip 0 1(newpath)prcmd prpath 0(eoclip)prcmd}put @SD/shf"
+"ill{begin currentdict/ShadingType known currentdict/ColorSpace known and curre"
+"ntdict/DataSource known and currentdict/Function known not and ShadingType 4 g"
+"e and DataSource type/arraytype eq and{<</DeviceGray 1/DeviceRGB 3/DeviceCMYK "
+"4/bgknown currentdict/Background known/bbknown currentdict/BBox known>>begin c"
+"urrentdict ColorSpace known{ShadingType ColorSpace load bgknown{1 Background a"
+"load pop}{0}ifelse bbknown{1 BBox aload pop}{0}ifelse ShadingType 5 eq{Vertice"
+"sPerRow}if DataSource aload length 4 add bgknown{ColorSpace load add}if bbknow"
+"n{4 add}if ShadingType 5 eq{1 add}if(shfill)prcmd}if end}if end}put/@rect{4 -2"
+" roll moveto exch dup 0 rlineto exch 0 exch rlineto neg 0 rlineto closepath}bi"
+"nd def/@rectcc{4 -2 roll moveto 2 copy 0 lt exch 0 lt xor{dup 0 exch rlineto e"
+"xch 0 rlineto neg 0 exch rlineto}{exch dup 0 rlineto exch 0 exch rlineto neg 0"
+" rlineto}ifelse closepath}bind def @SD/rectclip{:newpath dup type/arraytype eq"
+"{aload length 4 idiv{@rectcc}repeat}{@rectcc}ifelse clip :newpath}put @SD/rect"
+"fill{gsave :newpath dup type/arraytype eq{aload length 4 idiv{@rectcc}repeat}{"
+"@rectcc}ifelse fill grestore}put @SD/rectstroke{gsave :newpath dup type/arrayt"
+"ype eq{aload length 4 idiv{@rect}repeat}{@rect}ifelse stroke grestore}put fals"
+"e setglobal @SD readonly pop/initclip 0 defpr/clippath 0 defpr/sysexec{@SD exc"
+"h get exec}def/adddot{dup length 1 add string dup 0 46 put dup 3 -1 roll 1 exc"
+"h putinterval}def/setlinewidth{dup/setlinewidth sysexec 1(setlinewidth)prcmd}d"
+"ef/setlinecap 1 defpr/setlinejoin 1 defpr/setmiterlimit 1 defpr/setdash{mark 3"
+" 1 roll 2 copy/setdash sysexec exch aload length 1 add -1 roll counttomark(set"
+"dash)prcmd pop}def/@setpagedevice{pop<<>>/setpagedevice sysexec matrix setmatr"
+"ix newpath 0(setpagedevice)prcmd}def/prcolor{currentrgbcolor 3(setrgbcolor)prc"
+"md}def/printgstate{@dodraw{matrix currentmatrix aload pop 6(setmatrix)prcmd ap"
+"plyscalevals currentlinewidth 1(setlinewidth)prcmd currentlinecap 1(setlinecap"
+")prcmd currentlinejoin 1(setlinejoin)prcmd currentmiterlimit 1(setmiterlimit)p"
+"rcmd currentrgbcolor 3(setrgbcolor)prcmd currentdash mark 3 1 roll exch aload "
+"length 1 add -1 roll counttomark(setdash)prcmd pop}if}def/setgstate{/setgstate"
+" sysexec printgstate}def/save{@UD begin/@saveID vmstatus pop pop def end :save"
+" @saveID 1(save)prcmd}def/restore{:restore printgstate @UD/@saveID known{@UD b"
+"egin @saveID end}{0}ifelse 1(restore)prcmd}def/gsave 0 defpr/grestore{:grestor"
+"e printgstate 0(grestore)prcmd}def/grestoreall{:grestoreall setstate 0(grestor"
+"eall)prcmd}def/rotate{dup type/arraytype ne @dodraw and{dup 1(rotate)prcmd}if/"
+"rotate sysexec applyscalevals}def/scale{dup type/arraytype ne @dodraw and{2 co"
+"py 2(scale)prcmd}if/scale sysexec applyscalevals}def/translate{dup type/arrayt"
+"ype ne @dodraw and{2 copy 2(translate)prcmd}if/translate sysexec}def/setmatrix"
+"{dup/setmatrix sysexec @dodraw{aload pop 6(setmatrix)prcmd applyscalevals}if}d"
+"ef/initmatrix{matrix setmatrix}def/concat{matrix currentmatrix matrix concatma"
+"trix setmatrix}def/makepattern{gsave<</mx 3 -1 roll>>begin dup/XUID[1000000 @p"
+"atcnt]put mx/makepattern sysexec dup dup begin PatternType @patcnt BBox aload "
+"pop XStep YStep PaintType mx aload pop 15(makepattern)prcmd :newpath matrix se"
+"tmatrix PaintProc 0 1(makepattern)prcmd end/@patcnt @patcnt 1 add store end gr"
+"estore}def/setpattern{begin PatternType 1 eq{PaintType 1 eq{XUID aload pop exc"
+"h pop 1}{:gsave[currentcolorspace aload length -1 roll pop]setcolorspace/setco"
+"lor sysexec XUID aload pop exch pop currentrgbcolor :grestore 4}ifelse(setpatt"
+"ern)prcmd}{/setpattern sysexec}ifelse end}def/setcolor{dup type/dicttype eq{se"
+"tpattern}{/setcolor sysexec/currentrgbcolor sysexec setrgbcolor}ifelse}def/set"
+"gray 1 defpr/setcmykcolor 4 defpr/sethsbcolor 3 defpr/setrgbcolor 3 defpr/.set"
+"opacityalpha{dup/.setopacityalpha sysexec 1(setopacityalpha)prcmd}def/.setshap"
+"ealpha{dup/.setshapealpha sysexec 1(setshapealpha)prcmd}def/.setblendmode{dup/"
+".setblendmode sysexec<</Normal 0/Compatible 0/Multiply 1/Screen 2/Overlay 3/So"
+"ftLight 4/HardLight 5/ColorDodge 6/ColorBurn 7/Darken 8/Lighten 9/Difference 1"
+"0/Exclusion 11/Hue 12/Saturation 13/Color 14/Luminosity 15/CompatibleOverprint"
+" 16>>exch get 1(setblendmode)prcmd}def/@pdfpagecount{GS_PDF_ProcSet begin pdfd"
+"ict begin(r)file pdfopen begin pdfpagecount currentdict pdfclose end end end}d"
+"ef/@pdfpagebox{GS_PDF_ProcSet begin pdfdict begin(r)file pdfopen begin dup dup"
+" 1 lt exch pdfpagecount gt or{pop}{pdfgetpage/MediaBox pget pop aload pop}ifel"
+"se currentdict pdfclose end end end}def DELAYBIND{.bindnow}if ";
diff --git a/dviware/dvisvgm/src/utility.cpp b/dviware/dvisvgm/src/utility.cpp
new file mode 100644
index 0000000000..99e8e194a0
--- /dev/null
+++ b/dviware/dvisvgm/src/utility.cpp
@@ -0,0 +1,139 @@
+/*************************************************************************
+** utility.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <cctype>
+#include <fstream>
+#include <functional>
+#include <iterator>
+#include "utility.hpp"
+
+using namespace std;
+
+
+/** Returns a given string with leading and trailing whitespace removed.
+ * @param[in] str the string to process
+ * @param[in] ws characters treated as whitespace
+ * @return the trimmed string */
+string util::trim (const std::string &str, const char *ws) {
+ size_t first = str.find_first_not_of(ws);
+ if (first == string::npos)
+ return "";
+ size_t last = str.find_last_not_of(ws);
+ return str.substr(first, last-first+1);
+}
+
+
+/** Removes leading and trailing whitespace from a given string, and replaces
+ * all other whitespace sequences by single spaces.
+ * @param[in] str the string to process
+ * @param[in] ws characters treated as whitespace
+ * @return the normalized string */
+string util::normalize_space (string str, const char *ws) {
+ str = trim(str);
+ size_t first = str.find_first_of(ws);
+ while (first != string::npos) {
+ size_t last = str.find_first_not_of(ws, first);
+ str.replace(first, last-first, " ");
+ first = str.find_first_of(ws, first+1);
+ }
+ return str;
+}
+
+
+/** Replaces all occurences of a substring with another string.
+ * @param[in] str string to search through
+ * @param[in] find string to look for
+ * @param[in] repl replacement for "find"
+ * @return the resulting string */
+string util::replace (string str, const string &find, const string &repl) {
+ if (!find.empty() && !repl.empty()) {
+ size_t first = str.find(find);
+ while (first != string::npos) {
+ str.replace(first, find.length(), repl);
+ first = str.find(find, first+repl.length());
+ }
+ }
+ return str;
+}
+
+
+/** Splits a string at all occurences of a given separator string and
+ * returns the substrings.
+ * @param[in] str string to split
+ * @param[in] sep separator to look for
+ * @return the substrings between the separators */
+vector<string> util::split (const string &str, const string &sep) {
+ vector<string> parts;
+ if (str.empty() || sep.empty())
+ parts.emplace_back(str);
+ else {
+ size_t left=0;
+ while (left <= str.length()) {
+ size_t right = str.find(sep, left);
+ if (right == string::npos) {
+ parts.emplace_back(str.substr(left));
+ left = string::npos;
+ }
+ else {
+ parts.emplace_back(str.substr(left, right-left));
+ left = right+sep.length();
+ }
+ }
+ }
+ return parts;
+}
+
+
+string util::tolower (const string &str) {
+ string ret=str;
+ transform(str.begin(), str.end(), ret.begin(), ::tolower);
+ return ret;
+}
+
+
+/** Returns the integer part of log10 of a given integer \f$n>0\f$.
+ * If \f$n<0\f$, the result is 0. */
+int util::ilog10 (int n) {
+ int result = 0;
+ while (n >= 10) {
+ result++;
+ n /= 10;
+ }
+ return result;
+}
+
+
+/** Returns the contents of a file.
+ * @param[in] fname name/path of the file */
+string util::read_file_contents (const string &fname) {
+ ifstream ifs(fname, ios::binary);
+ return string(istreambuf_iterator<char>(ifs.rdbuf()), istreambuf_iterator<char>());
+}
+
+
+/** Writes a sequence of bytes given as a string to a file.
+ * @param[in] name/path of the file to write
+ * @param[in] start iterator pointing to the begin of the byte sequence
+ * @param[in] end iterator pointing to the first byte after the byte sequence to write */
+void util::write_file_contents (const string &fname, string::iterator start, string::iterator end) {
+ ofstream ofs(fname, ios::binary);
+ copy(start, end, ostream_iterator<char>(ofs));
+}
diff --git a/dviware/dvisvgm/src/utility.hpp b/dviware/dvisvgm/src/utility.hpp
new file mode 100644
index 0000000000..ca0b0873dc
--- /dev/null
+++ b/dviware/dvisvgm/src/utility.hpp
@@ -0,0 +1,117 @@
+/*************************************************************************
+** utility.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef UTILITY_HPP
+#define UTILITY_HPP
+
+#include <iomanip>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace math {
+
+constexpr const double PI = 3.141592653589793238462643383279502884;
+constexpr const double HALF_PI = 1.570796326794896619231321691639751442;
+constexpr const double TWO_PI = 6.283185307179586476925286766559005768;
+constexpr const double SQRT2 = 1.414213562373095048801688724209698079;
+
+inline double deg2rad (double deg) {return PI*deg/180.0;}
+inline double rad2deg (double rad) {return 180.0*rad/PI;}
+
+} // namespace math
+
+namespace util {
+
+template <typename T>
+std::string tohex (T val) {
+ std::ostringstream oss;
+ oss << std::hex << val;
+ return oss.str();
+}
+
+std::string trim (const std::string &str, const char *ws=" \t\n\r\f");
+std::string normalize_space (std::string str, const char *ws=" \t\n\r\f");
+std::string tolower (const std::string &str);
+std::string replace (std::string str, const std::string &find, const std::string &repl);
+std::vector<std::string> split (const std::string &str, const std::string &sep);
+int ilog10 (int n);
+
+std::string read_file_contents (const std::string &fname);
+void write_file_contents (const std::string &fname, std::string::iterator start, std::string::iterator end);
+
+/** Encodes the bytes in the half-open range [first,last) to Base64 and writes
+ * the result to the range starting at 'dest'.
+ * @param[in] first initial position of the range to be encoded
+ * @param[in] last final position of the range to be encoded
+ * @param[in] dest first position of the destination range */
+template <typename InputIterator, typename OutputIterator>
+void base64_copy (InputIterator first, InputIterator last, OutputIterator dest) {
+ static const char *base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ while (first != last) {
+ int padding = 0;
+ unsigned char c0 = *first++, c1=0, c2=0;
+ if (first == last)
+ padding = 2;
+ else {
+ c1 = *first++;
+ if (first == last)
+ padding = 1;
+ else
+ c2 = *first++;
+ }
+ uint32_t n = (c0 << 16) | (c1 << 8) | c2;
+ for (int i=0; i <= 3-padding; i++) {
+ *dest++ = base64_chars[(n >> 18) & 0x3f];
+ n <<= 6;
+ }
+ while (padding--)
+ *dest++ = '=';
+ }
+}
+
+
+/** Simple implementation mimicking std::make_unique introduced in C++14.
+ * Constructs an object of class T on the heap and returns a unique_ptr<T> to it.
+ * @param[in] args arguments forwarded to an constructor of T */
+template<typename T, typename... Args>
+std::unique_ptr<T> make_unique (Args&&... args) {
+ return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
+}
+
+
+template<typename T, typename U>
+std::unique_ptr<T> static_unique_ptr_cast (std::unique_ptr<U> &&old){
+ return std::unique_ptr<T>{static_cast<T*>(old.release())};
+}
+
+
+template <typename... T>
+constexpr auto make_array (T&&... values) ->
+ std::array<typename std::decay<typename std::common_type<T...>::type>::type, sizeof...(T)> {
+ return std::array<typename std::decay<typename std::common_type<T...>::type>::type, sizeof...(T)>{
+ std::forward<T>(values)...
+ };
+ }
+
+} // namespace util
+
+#endif
diff --git a/dviware/dvisvgm/src/version.hpp.in b/dviware/dvisvgm/src/version.hpp.in
new file mode 100644
index 0000000000..efbe631d5b
--- /dev/null
+++ b/dviware/dvisvgm/src/version.hpp.in
@@ -0,0 +1,28 @@
+/*************************************************************************
+** version.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef VERSION_HPP
+#define VERSION_HPP
+
+constexpr const char *PROGRAM_NAME = "dvisvgm";
+constexpr const char *PROGRAM_VERSION = "@VERSION@";
+
+#endif
+
diff --git a/dviware/dvisvgm/src/windows.hpp b/dviware/dvisvgm/src/windows.hpp
new file mode 100644
index 0000000000..e548ee099a
--- /dev/null
+++ b/dviware/dvisvgm/src/windows.hpp
@@ -0,0 +1,32 @@
+/*************************************************************************
+** windows.hpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#ifndef WINDOWS_HPP
+#define WINDOWS_HPP
+
+// Prevent defining the macros min and max present in windows.h
+#ifndef NOMINMAX
+ #define NOMINMAX
+#endif
+
+#include <windows.h>
+
+#endif
+