diff options
Diffstat (limited to 'dviware/dvisvgm/src')
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 ¶m) { + 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 ¶m); + 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 ¶mstr) { + 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 ¶mstr); + 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 ¤t_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 ¤t_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> ¶ms) { + 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 ¶mstr, vector<const char*> ¶ms) { + 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(¶mstr[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*>(¶ms[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> ¶ms) { + 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 = ∈ + 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 + * ( 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 "<"; + case '&' : return "&"; + case '"' : return """; + case '\'': return "'"; + } + 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 ¤tAttribute : 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 <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) && !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 + |