New upstream version 2.12
Hilmar Preusse
2 years ago
0 | version: 2.11.1-{build} | |
0 | version: 2.12-{build} | |
1 | 1 | configuration: Release |
2 | 2 | |
3 | 3 | image: Visual Studio 2019 Preview |
11 | 11 | - tmp |
12 | 12 | |
13 | 13 | install: |
14 | - set FREETYPE_VER=2.10.4 | |
14 | - set FREETYPE_VER=2.11.0 | |
15 | 15 | - set ZLIB_VER=1.2.11 |
16 | 16 | - set TTFA_VER=1.8.2 |
17 | 17 | - set TTFA_REV=3 |
0 | name: C/C++ CI | |
1 | ||
2 | on: | |
3 | push: | |
4 | branches: [ master ] | |
5 | pull_request: | |
6 | branches: [ master ] | |
7 | ||
8 | jobs: | |
9 | build: | |
10 | ||
11 | runs-on: ubuntu-latest | |
12 | ||
13 | steps: | |
14 | - uses: actions/checkout@v2 | |
15 | - name: install dependencies | |
16 | run: sudo apt-get install -qq autotools-dev libkpathsea-dev libfreetype6-dev libgs-dev libz-dev texlive-base python-lxml asciidoc xmlto xsltproc | |
17 | - name: autogen | |
18 | run: ./autogen.sh | |
19 | - name: configure | |
20 | run: ./configure --enable-bundled-libs | |
21 | - name: make clean | |
22 | run: make clean | |
23 | - name: make | |
24 | run: make | |
25 | - name: update timestamps | |
26 | run: make -C src -t | |
27 | - name: make check | |
28 | run: make check |
96 | 96 | eeaf1a69d766b12cc417acc7b8a723311417e362 2.10 |
97 | 97 | 585e39596d7473b92af9814128cdc2dd0a7615f3 2.10.1 |
98 | 98 | ea2da8135fbbefed47cb954382c90e851cd28189 2.11 |
99 | 552dd227ac4ef6fb804e31a71e72a1d5a76a0738 2.11.1 |
28 | 28 | project: |
29 | 29 | name: mgieseki/dvisvgm |
30 | 30 | description: "dvisvgm -- A fast DVI to SVG converter" |
31 | version: 2.11.1 | |
31 | version: 2.12 | |
32 | 32 | notification_email: martin.gieseking@uos.de |
33 | 33 | build_command_prepend: "./configure --enable-bundled-libs; make clean" |
34 | 34 | build_command: "make -j" |
0 | dvisvgm-2.12 (2021-08-16) | |
1 | - added transparency support of SVG elements created outside the PS handler | |
2 | (GH issue #148) | |
3 | - fixed spacing issue caused by unexpected newline characters in SVG output | |
4 | - fixed PS error occurred when defining (yet unsupported) PS shading patterns | |
5 | - fixed issue in color handling of PS tiling patterns (GH issue #158) | |
6 | - fixed displaced graphics occurred if PDF MediBox is not located at the origin | |
7 | - fixed handling of root directories of file paths | |
8 | - improved handling of drive letters (Windows only) | |
9 | - several code refactorings and improvements | |
10 | ||
0 | 11 | dvisvgm-2.11.1 (2021-01-21) |
1 | 12 | - fixed possible ambiguity of GID to charcode mappings (GH issue #147) |
2 | 13 | - refactored representation of token objects in calculator class |
0 | 0 | _dvisvgm_ – A fast DVI to SVG converter |
1 | 1 | ============================================= |
2 | [![C/C++ CI](https://github.com/mgieseki/dvisvgm/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/mgieseki/dvisvgm/actions/workflows/c-cpp.yml) | |
2 | 3 | [![Build Status](https://travis-ci.org/mgieseki/dvisvgm.svg?branch=master)](https://travis-ci.org/mgieseki/dvisvgm) |
3 | 4 | [![Build Status](https://ci.appveyor.com/api/projects/status/0rbkw88js1on4g2u/branch/master?svg=true)](https://ci.appveyor.com/project/mgieseki/dvisvgm/branch/master) |
4 | [![Copr Status](https://copr.fedorainfracloud.org/coprs/mgieseki/dvisvgm/package/dvisvgm/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/mgieseki/dvisvgm) | |
5 | 5 | [![Code Status](https://scan.coverity.com/projects/1099/badge.svg)](https://scan.coverity.com/projects/1099) |
6 | 6 | [![License](https://img.shields.io/:license-GPL%20v3+-blue.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html) |
7 | 7 | [![Releases](https://img.shields.io/github/release/mgieseki/dvisvgm.svg)](https://github.com/mgieseki/dvisvgm/releases) |
3 | 3 | # Process this file with autoconf to produce a configure script. |
4 | 4 | |
5 | 5 | AC_PREREQ(2.59) |
6 | AC_INIT([dvisvgm],[2.11.1],[martin.gieseking@uos.de]) | |
7 | DATE="January 2021" | |
6 | AC_INIT([dvisvgm],[2.12],[martin.gieseking@uos.de]) | |
7 | DATE="August 2021" | |
8 | 8 | AC_CONFIG_SRCDIR(src) |
9 | 9 | AC_CONFIG_HEADERS([config.h]) |
10 | 10 | AC_CONFIG_MACRO_DIR([m4]) |
1 | 1 | .\" Title: dvisvgm |
2 | 2 | .\" Author: Martin Gieseking <martin.gieseking@uos.de> |
3 | 3 | .\" Generator: DocBook XSL Stylesheets vsnapshot <http://docbook.sf.net/> |
4 | .\" Date: 2021-01-03 | |
4 | .\" Date: 2021-04-10 | |
5 | 5 | .\" Manual: dvisvgm Manual |
6 | .\" Source: dvisvgm 2.11.1 | |
6 | .\" Source: dvisvgm 2.12 | |
7 | 7 | .\" Language: English |
8 | 8 | .\" |
9 | .TH "DVISVGM" "1" "2021\-01\-03" "dvisvgm 2\&.11\&.1" "dvisvgm Manual" | |
9 | .TH "DVISVGM" "1" "2021\-04\-10" "dvisvgm 2\&.12" "dvisvgm Manual" | |
10 | 10 | .\" ----------------------------------------------------------------- |
11 | 11 | .\" * Define some portability stuff |
12 | 12 | .\" ----------------------------------------------------------------- |
45 | 45 | .sp |
46 | 46 | However, TeX\(cqs main source for font descriptions is Metafont, which produces bitmap output (GF files)\&. That\(cqs why not all obtainable TeX fonts are available in a scalable format\&. In these cases, dvisvgm tries to vectorize Metafont\(cqs output by tracing the glyph bitmaps\&. The results are not as perfect as most (manually optimized) PFB or OTF counterparts, but are nonetheless really nice in most cases\&. |
47 | 47 | .sp |
48 | When running dvisvgm without option \fB\-\-no\-fonts\fR, it creates \fIfont\fR elements (\fB<font>\fR\&...\fB</font>\fR) to embed the font data into the SVG files\&. Unfortunately, only few SVG renderers support these elements yet\&. Most web browsers and vector graphics applications don\(cqt evaluate them properly so that the text components of the resulting graphics might look strange\&. In order to create more compatible SVG files, command\-line option \fB\-\-no\-fonts\fR can be given to replace the font elements by plain graphics paths\&. Most web browsers (but only few external SVG renderers) also suppport WOFF and WOFF2 fonts that can be used instead of the default SVG fonts\&. Option \fB\-\-font\-format\fR offers the functionality to change the format applied to the fonts being embedded\&. This, however, only works when converting DVI files\&. Text present in PDF and PostScript files is always converted to path elements\&. | |
48 | When running dvisvgm without option \fB\-\-no\-fonts\fR, it creates \fIfont\fR elements (\fB<font>\fR\&...\fB</font>\fR) to embed the font data into the SVG files\&. Unfortunately, only few SVG renderers support these elements yet\&. Most web browsers and vector graphics applications don\(cqt evaluate them properly so that the text components of the resulting graphics might look strange\&. In order to create more compatible SVG files, command\-line option \fB\-\-no\-fonts\fR can be given to replace the font elements by plain graphics paths\&. Most web browsers (but only few external SVG renderers) also support WOFF and WOFF2 fonts that can be used instead of the default SVG fonts\&. Option \fB\-\-font\-format\fR offers the functionality to change the format applied to the fonts being embedded\&. This, however, only works when converting DVI files\&. Text present in PDF and PostScript files is always converted to path elements\&. | |
49 | 49 | .SH "OPTIONS" |
50 | 50 | .sp |
51 | 51 | dvisvgm provides a POSIX\-compliant command\-line interface with short and long option names\&. They may be given before and/or after the name of the file to be converted\&. Also, the order of specifying the options is not significant, i\&.e\&. you can add them in any order without changing dvisvgm\(cqs behavior\&. Certain options accept or require additional parameters which are directly appended to or separated by whitespace from a short option (e\&.g\&. \fB\-v0\fR or \fB\-v 0\fR)\&. Long options require an additional equals sign (\fB=\fR) between option name and argument but without any surrounding whitespace (e\&.g\&. \fB\-\-verbosity=0\fR)\&. Multiple short options that don\(cqt expect a further parameter can be combined after a single dash (e\&.g\&. \fB\-ejs\fR rather than \fB\-e \-j \-s\fR)\&. |
661 | 661 | .PP |
662 | 662 | \fB\-o, \-\-output\fR=\fIpattern\fR |
663 | 663 | .RS 4 |
664 | Sets the pattern specifying the names of the generated SVG files\&. Parameter | |
664 | Sets the pattern that determines the names of the generated SVG files\&. The required parameter | |
665 | 665 | \fIpattern\fR |
666 | is a string that may contain static character sequences as well as the variables | |
666 | may consist of an arbitrary sequence of characters which make up the filenames\&. With the exception of the following mentioned variables and expressions, all characters are treated as static parts of the filenames and are therefore identical for all pages processed during a run of dvisvgm\&. The strings | |
667 | 667 | \fB%f\fR, |
668 | 668 | \fB%p\fR, |
669 | 669 | \fB%P\fR, |
670 | 670 | \fB%hd\fR, |
671 | 671 | \fB%ho\fR, and |
672 | \fB%hc\fR\&. | |
672 | \fB%hc\fR | |
673 | are variables that can be used as part of the pattern\&. | |
673 | 674 | \fB%f\fR |
674 | 675 | expands to the base name of the DVI file, i\&.e\&. the filename without suffix, |
675 | 676 | \fB%p\fR |
676 | 677 | is the current page number, and |
677 | 678 | \fB%P\fR |
678 | the total number of pages in the DVI file\&. An optional number (0\-9) given directly after the percent sign specifies the minimal number of digits to be written\&. If a particular value consists of less digits, the number is padded with leading zeros\&. Example: | |
679 | the total number of pages in the DVI file\&. An optional number (0\-9) given directly after the percent sign of a variable holding a numeric value denotes the minimal number of digits to be created\&. If a particular value consists of less digits, the number is padded with leading zeros\&. Example: | |
679 | 680 | \fB%3p\fR |
680 | 681 | enforces 3 digits for the current page number (001, 002, etc\&.)\&. Without an explicit width specifier, |
681 | 682 | \fB%p\fR |
702 | 703 | \fB%hc\fR |
703 | 704 | are only set if option |
704 | 705 | \fB\-\-page\-hashes\fR |
705 | is present\&. Otherwise, it\(cqs empty\&. For further information, see the description of option | |
706 | is present\&. Otherwise, they are empty\&. For further information, see the description of option | |
706 | 707 | \fB\-\-page\-hashes\fR |
707 | 708 | below\&. |
708 | 709 | .sp |
21 | 21 | :man source: dvisvgm |
22 | 22 | :man version: @VERSION@ |
23 | 23 | :man manual: dvisvgm Manual |
24 | :revdate: 2021-01-03 19:25 +0100 | |
24 | :revdate: 2021-04-10 01:45 +0200 | |
25 | 25 | |
26 | 26 | Name |
27 | 27 | ---- |
68 | 68 | elements yet. Most web browsers and vector graphics applications don't evaluate them properly so |
69 | 69 | that the text components of the resulting graphics might look strange. In order to create more |
70 | 70 | compatible SVG files, command-line option *--no-fonts* can be given to replace the font elements |
71 | by plain graphics paths. Most web browsers (but only few external SVG renderers) also suppport | |
71 | by plain graphics paths. Most web browsers (but only few external SVG renderers) also support | |
72 | 72 | WOFF and WOFF2 fonts that can be used instead of the default SVG fonts. Option *--font-format* |
73 | 73 | offers the functionality to change the format applied to the fonts being embedded. This, however, |
74 | 74 | only works when converting DVI files. Text present in PDF and PostScript files is always |
453 | 453 | expression is used. |
454 | 454 | |
455 | 455 | *-o, --output*='pattern':: |
456 | Sets the pattern specifying the names of the generated SVG files. Parameter 'pattern' is a string | |
457 | that may contain static character sequences as well as the variables +%f+, +%p+, +%P+, +%hd+, | |
458 | +%ho+, and +%hc+. +%f+ expands to the base name of the DVI file, i.e. the filename without | |
459 | suffix, +%p+ is the current page number, and +%P+ the total number of pages in the DVI file. An | |
460 | optional number (0-9) given directly after the percent sign specifies the minimal number of digits | |
461 | to be written. If a particular value consists of less digits, the number is padded with leading | |
462 | zeros. Example: +%3p+ enforces 3 digits for the current page number (001, 002, etc.). Without an | |
463 | explicit width specifier, +%p+ gets the same number of digits as +%P+. | |
456 | Sets the pattern that determines the names of the generated SVG files. The required parameter | |
457 | 'pattern' may consist of an arbitrary sequence of characters which make up the filenames. With the | |
458 | exception of the following mentioned variables and expressions, all characters are treated as static | |
459 | parts of the filenames and are therefore identical for all pages processed during a run of dvisvgm. | |
460 | The strings +%f+, +%p+, +%P+, +%hd+, +%ho+, and +%hc+ are variables that can be used as part of the | |
461 | pattern. +%f+ expands to the base name of the DVI file, i.e. the filename without suffix, +%p+ is the | |
462 | current page number, and +%P+ the total number of pages in the DVI file. | |
463 | An optional number (0-9) given directly after the percent sign of a variable holding a numeric value | |
464 | denotes the minimal number of digits to be created. If a particular value consists of less digits, | |
465 | the number is padded with leading zeros. | |
466 | Example: +%3p+ enforces 3 digits for the current page number (001, 002, etc.). Without an explicit | |
467 | width specifier, +%p+ gets the same number of digits as +%P+. | |
464 | 468 | + |
465 | 469 | If you need more control over the numbering, you can use arithmetic expressions as part of a pattern. |
466 | 470 | The syntax is +%(+'expr'+)+ where 'expr' may contain additions, subtractions, multiplications, and |
470 | 474 | + |
471 | 475 | The variables +%hX+ contain different hash values computed from the DVI page data and the options |
472 | 476 | given on the command-line. +%hd+ and +%hc+ are only set if option *--page-hashes* is present. |
473 | Otherwise, it's empty. For further information, see the description of option *--page-hashes* below. | |
477 | Otherwise, they are empty. For further information, see the description of option *--page-hashes* | |
478 | below. | |
474 | 479 | + |
475 | 480 | The default pattern is +%f-%p.svg+ if the DVI file consists of more than one page, and |
476 | 481 | +%f.svg+ otherwise. That means, a DVI file 'foo.dvi' is converted to 'foo.svg' if 'foo.dvi' |
9 | 9 | lines = infile.readlines() |
10 | 10 | for line in lines: |
11 | 11 | if re.match(r'(.*\\def)|(.*\\href)', line) == None: |
12 | line = re.sub(r'([a-zA-Z0-9]+)/', r'\1\slash{}', line) | |
12 | line = re.sub(r'([a-zA-Z0-9]+)/', r'\1\\slash{}', line) | |
13 | 13 | line = re.sub(r'-{}-{}', r'\=/\=/', line) |
14 | 14 | line = re.sub(r'([^a-zA-Z0-9])-{}', r'\1\=/', line) |
15 | print >>outfile, line.rstrip() | |
15 | print(line.rstrip(), file=outfile) | |
16 | 16 | os.remove(latex_file_old) |
17 | 17 | return 0 |
15 | 15 | # The second argument, if specified, indicates whether you insist on an |
16 | 16 | # extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. |
17 | 17 | # -std=c++11). If neither is specified, you get whatever works, with |
18 | # preference for an extended mode. | |
18 | # preference for no added switch, and then for an extended mode. | |
19 | 19 | # |
20 | 20 | # The third argument, if specified 'mandatory' or if left unspecified, |
21 | 21 | # indicates that baseline support for the specified C++ standard is |
34 | 34 | # Copyright (c) 2015 Moritz Klammler <moritz@klammler.eu> |
35 | 35 | # Copyright (c) 2016, 2018 Krzesimir Nowak <qdlacz@gmail.com> |
36 | 36 | # Copyright (c) 2019 Enji Cooper <yaneurabeya@gmail.com> |
37 | # Copyright (c) 2020 Jason Merrill <jason@redhat.com> | |
37 | 38 | # |
38 | 39 | # Copying and distribution of this file, with or without modification, are |
39 | 40 | # permitted in any medium without royalty provided the copyright notice |
40 | 41 | # and this notice are preserved. This file is offered as-is, without any |
41 | 42 | # warranty. |
42 | 43 | |
43 | #serial 11 | |
44 | #serial 12 | |
44 | 45 | |
45 | 46 | dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro |
46 | 47 | dnl (serial version number 13). |
60 | 61 | [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])]) |
61 | 62 | AC_LANG_PUSH([C++])dnl |
62 | 63 | ac_success=no |
64 | ||
65 | m4_if([$2], [], [dnl | |
66 | AC_CACHE_CHECK(whether $CXX supports C++$1 features by default, | |
67 | ax_cv_cxx_compile_cxx$1, | |
68 | [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], | |
69 | [ax_cv_cxx_compile_cxx$1=yes], | |
70 | [ax_cv_cxx_compile_cxx$1=no])]) | |
71 | if test x$ax_cv_cxx_compile_cxx$1 = xyes; then | |
72 | ac_success=yes | |
73 | fi]) | |
63 | 74 | |
64 | 75 | m4_if([$2], [noext], [], [dnl |
65 | 76 | if test x$ac_success = xno; then |
35 | 35 | if (!_pageColors.empty() && _pageColors.back().first == pageno) |
36 | 36 | _pageColors.back().second = color; |
37 | 37 | else |
38 | _pageColors.emplace_back(PageColor(pageno, color)); | |
38 | _pageColors.emplace_back(pageno, color); | |
39 | 39 | } |
40 | 40 | } |
41 | 41 |
75 | 75 | vector<string> lengthStrings = util::split(boxstr, " "); |
76 | 76 | for (const string &lenstr : lengthStrings) { |
77 | 77 | if (!lenstr.empty()) |
78 | lengths.emplace_back(Length(lenstr)); | |
78 | lengths.emplace_back(lenstr); | |
79 | 79 | } |
80 | 80 | return lengths; |
81 | 81 | } |
118 | 118 | |
119 | 119 | |
120 | 120 | // try to find a compatible encoding CMap |
121 | const bool is_unicode_map = bool(dynamic_cast<const UnicodeCMap*>(cmap)); | |
121 | const bool is_unicode_map = cmap->mapsToUnicode(); | |
122 | 122 | const string ro = cmap->getROString(); |
123 | 123 | for (const CharMapIDToEncName &enc : encodings) { |
124 | 124 | for (const CharMapID &id : charmapIDs) { |
25 | 25 | #include "Font.hpp" |
26 | 26 | #include "FontManager.hpp" |
27 | 27 | #include "HashFunction.hpp" |
28 | #include "JFM.hpp" | |
29 | 28 | #include "utility.hpp" |
30 | 29 | #include "VectorStream.hpp" |
31 | 30 | |
202 | 201 | * @param[in] font current font (corresponding to _currFontNum) |
203 | 202 | * @param[in] c character to typeset */ |
204 | 203 | void DVIReader::putVFChar (Font *font, uint32_t c) { |
205 | if (auto vf = dynamic_cast<VirtualFont*>(font)) { // is current font a virtual font? | |
204 | if (auto vf = font_cast<VirtualFont*>(font)) { // is current font a virtual font? | |
206 | 205 | FontManager &fm = FontManager::instance(); |
207 | 206 | const vector<uint8_t> *dvi = vf->getDVI(c); // try to get DVI snippet that represents character c |
208 | 207 | Font *firstFont = fm.vfFirstFont(vf); |
209 | if (!dvi && (!firstFont || !dynamic_cast<const JFM*>(firstFont->getMetrics()))) | |
208 | if (!dvi && (!firstFont || !firstFont->getMetrics()->isJFM())) | |
210 | 209 | return; |
211 | 210 | fm.enterVF(vf); // enter VF font number context |
212 | 211 | int savedFontNum = _currFontNum; // save current font number |
484 | 483 | else { // TFM-based font specified by name |
485 | 484 | int id = fm.registerFont(fontnum, name, cs, dsize, ssize); |
486 | 485 | font = fm.getFontById(id); |
487 | if (auto vf = dynamic_cast<VirtualFont*>(font)) { | |
486 | if (auto vf = font_cast<VirtualFont*>(font)) { | |
488 | 487 | // read vf file, register its font and character definitions |
489 | 488 | fm.enterVF(vf); |
490 | 489 | ifstream ifs(vf->path(), ios::binary); |
591 | 590 | FontManager::instance().registerFont(fontnum, fontname, fontIndex, ptsize, style, color); |
592 | 591 | font = FontManager::instance().getFont(fontnum); |
593 | 592 | } |
594 | dviXFontDef(fontnum, dynamic_cast<const NativeFont*>(font)); | |
593 | dviXFontDef(fontnum, font_cast<const NativeFont*>(font)); | |
595 | 594 | } |
596 | 595 | |
597 | 596 |
73 | 73 | DVIToSVG::HashSettings DVIToSVG::PAGE_HASH_SETTINGS; |
74 | 74 | |
75 | 75 | |
76 | DVIToSVG::DVIToSVG (istream &is, SVGOutputBase &out) : DVIReader(is), _out(out) | |
76 | DVIToSVG::DVIToSVG (istream &is, SVGOutputBase &out) | |
77 | : DVIReader(is), _out(out), _prevWritingMode(WritingMode::LR) | |
77 | 78 | { |
78 | _pageHeight = _pageWidth = 0; | |
79 | _tx = _ty = 0; // no cursor translation | |
80 | _pageByte = 0; | |
81 | 79 | _prevXPos = _prevYPos = numeric_limits<double>::min(); |
82 | _prevWritingMode = WritingMode::LR; | |
83 | 80 | _actions = util::make_unique<DVIToSVGActions>(*this, _svg); |
84 | 81 | } |
85 | 82 | |
361 | 358 | unordered_set<const Font*> tracedFonts; // collect unique fonts already traced |
362 | 359 | for (const auto &fontchar : usedCharsMap) { |
363 | 360 | const Font *font = fontchar.first; |
364 | if (auto ph_font = dynamic_cast<const PhysicalFont*>(font)) { | |
361 | if (auto ph_font = font_cast<const PhysicalFont*>(font)) { | |
365 | 362 | // Check if glyphs should be traced. Only trace the glyphs of unique fonts, i.e. |
366 | 363 | // avoid retracing the same glyphs again if they are referenced in various sizes. |
367 | 364 | if (TRACE_MODE != 0 && tracedFonts.find(ph_font->uniqueFont()) == tracedFonts.end()) { |
480 | 477 | |
481 | 478 | |
482 | 479 | void DVIToSVG::dviSetChar0 (uint32_t c, const Font *font) { |
483 | if (_actions && !dynamic_cast<const VirtualFont*>(font)) | |
480 | if (_actions && !font_cast<const VirtualFont*>(font)) | |
484 | 481 | _actions->setChar(dviState().h+_tx, dviState().v+_ty, c, dviState().d != WritingMode::LR, *font); |
485 | 482 | } |
486 | 483 | |
519 | 516 | |
520 | 517 | |
521 | 518 | void DVIToSVG::dviFontNum (uint32_t fontnum, SetFontMode, const Font *font) { |
522 | if (_actions && font && !dynamic_cast<const VirtualFont*>(font)) | |
519 | if (_actions && font && !font_cast<const VirtualFont*>(font)) | |
523 | 520 | _actions->setFont(FontManager::instance().fontID(fontnum), *font); // all fonts get a recomputed ID |
524 | 521 | } |
525 | 522 |
100 | 100 | SVGTree _svg; |
101 | 101 | SVGOutputBase &_out; |
102 | 102 | std::unique_ptr<DVIActions> _actions; |
103 | std::string _bboxFormatString; ///< bounding box size/format set by the user | |
104 | std::string _transCmds; ///< page transformation commands set by the user | |
105 | double _pageHeight, _pageWidth; ///< global page height and width stored in the postamble | |
106 | double _tx, _ty; ///< translation of cursor position | |
107 | double _prevXPos, _prevYPos; ///< previous cursor position | |
108 | WritingMode _prevWritingMode; ///< previous writing mode | |
109 | std::streampos _pageByte; ///< position of the stream pointer relative to the preceding bop (in bytes) | |
103 | std::string _bboxFormatString; ///< bounding box size/format set by the user | |
104 | std::string _transCmds; ///< page transformation commands set by the user | |
105 | double _pageHeight=0, _pageWidth=0; ///< global page height and width stored in the postamble | |
106 | double _tx=0, _ty=0; ///< translation of cursor position | |
107 | double _prevXPos, _prevYPos; ///< previous cursor position | |
108 | WritingMode _prevWritingMode; ///< previous writing mode | |
109 | std::streampos _pageByte=0; ///< position of the stream pointer relative to the preceding bop (in bytes) | |
110 | 110 | }; |
111 | 111 | |
112 | 112 | #endif |
104 | 104 | |
105 | 105 | GlyphMetrics metrics; |
106 | 106 | font.getGlyphMetrics(c, vertical, metrics); |
107 | auto pf = dynamic_cast<const PhysicalFont*>(&font); | |
107 | auto pf = font_cast<const PhysicalFont*>(&font); | |
108 | 108 | if (PhysicalFont::EXACT_BBOX && pf) { |
109 | 109 | GlyphMetrics exact_metrics; |
110 | 110 | pf->getExactGlyphBox(c, exact_metrics, vertical, &callback); |
165 | 165 | return; |
166 | 166 | |
167 | 167 | // (x,y) is the lower left corner of the rectangle |
168 | auto rect = util::make_unique<XMLElement>("rect"); | |
168 | auto rect = util::make_unique<SVGElement>("rect"); | |
169 | 169 | rect->addAttribute("x", x); |
170 | 170 | rect->addAttribute("y", y-height); |
171 | 171 | rect->addAttribute("height", height); |
172 | 172 | rect->addAttribute("width", width); |
173 | if (!getMatrix().isIdentity()) | |
174 | rect->addAttribute("transform", getMatrix().toSVG()); | |
175 | if (getColor() != Color::BLACK) | |
176 | rect->addAttribute("fill", _svg.getColor().svgColorString()); | |
173 | rect->setTransform(getMatrix()); | |
174 | rect->setFillColor(_svg.getColor()); | |
177 | 175 | _svg.appendToPage(std::move(rect)); |
178 | 176 | |
179 | 177 | // update bounding box |
236 | 234 | _svg.transformPage(matrix); |
237 | 235 | if (_bgcolor != Color::TRANSPARENT) { |
238 | 236 | // create a rectangle filled with the background color |
239 | auto rect = util::make_unique<XMLElement>("rect"); | |
237 | auto rect = util::make_unique<SVGElement>("rect"); | |
240 | 238 | rect->addAttribute("x", _bbox.minX()); |
241 | 239 | rect->addAttribute("y", _bbox.minY()); |
242 | 240 | rect->addAttribute("width", _bbox.width()); |
243 | 241 | rect->addAttribute("height", _bbox.height()); |
244 | rect->addAttribute("fill", _bgcolor.svgColorString()); | |
242 | rect->setFillColor(_bgcolor); | |
245 | 243 | _svg.prependToPage(std::move(rect)); |
246 | 244 | } |
247 | 245 | } |
48 | 48 | void setBgColor (const Color &color) override; |
49 | 49 | void setColor (const Color &color) override {_svg.setColor(color);} |
50 | 50 | void setMatrix (const Matrix &m) override {_svg.setMatrix(m);} |
51 | void setOpacity (const Opacity &opacity) override {_svg.setOpacity(opacity);} | |
52 | const Opacity& getOpacity () const override {return _svg.getOpacity();} | |
51 | 53 | const Matrix& getMatrix () const override {return _svg.getMatrix();} |
52 | 54 | Matrix getPageTransformation () const override {return _dvireader->getPageTransformation();} |
53 | 55 | Color getColor () const override {return _svg.getColor();} |
37 | 37 | # could be handy for archiving the generated documentation or if some version |
38 | 38 | # control system is used. |
39 | 39 | |
40 | PROJECT_NUMBER = 2.11.1 | |
40 | PROJECT_NUMBER = 2.12 | |
41 | 41 | |
42 | 42 | # Using the PROJECT_BRIEF tag one can provide an optional one line description |
43 | 43 | # for a project that appears at the top of each page and should give viewer a |
80 | 80 | _currentMacro = _macros.end(); |
81 | 81 | throw SpecialException("redefinition of SVG fragment '" + id + "'"); |
82 | 82 | } |
83 | pair<string, StringVector> entry(id, StringVector()); | |
84 | pair<MacroMap::iterator, bool> state = _macros.emplace(move(entry)); | |
83 | pair<MacroMap::iterator, bool> state = _macros.emplace(id, StringVector()); | |
85 | 84 | _currentMacro = state.first; |
86 | 85 | } |
87 | 86 | |
193 | 192 | /** Evaluates substrings of the form {?(expr)} where 'expr' is a math expression, |
194 | 193 | * and replaces the substring by the computed value. |
195 | 194 | * @param[in,out] str string to scan for expressions */ |
196 | static void evaluate_expressions (string &str, SpecialActions &actions) { | |
195 | static void evaluate_expressions (string &str, const SpecialActions &actions) { | |
197 | 196 | size_t left = str.find("{?("); // start position of expression macro |
198 | 197 | while (left != string::npos) { |
199 | 198 | size_t right = str.find(")}", left+2); // end position of expression macro |
385 | 384 | Length h = read_length(ir); |
386 | 385 | string f = ir.getString(); |
387 | 386 | update_bbox(w, h, Length(0), false, actions); |
388 | auto img = util::make_unique<XMLElement>("image"); | |
387 | auto img = util::make_unique<SVGElement>("image"); | |
389 | 388 | img->addAttribute("x", actions.getX()); |
390 | 389 | img->addAttribute("y", actions.getY()); |
391 | 390 | img->addAttribute("width", w.bp()); |
392 | 391 | img->addAttribute("height", h.bp()); |
393 | 392 | img->addAttribute("xlink:href", f); |
394 | if (!actions.getMatrix().isIdentity()) | |
395 | img->addAttribute("transform", actions.getMatrix().toSVG()); | |
393 | img->setTransform(actions.getMatrix()); | |
396 | 394 | actions.svgTree().appendToPage(std::move(img)); |
397 | 395 | } |
398 | 396 | catch (const UnitException &e) { |
447 | 445 | // collect/extract an XML fragment that only contains complete tags |
448 | 446 | // incomplete tags are held back |
449 | 447 | _xmlbuf += xml; |
450 | size_t left=0, right; | |
448 | size_t left=0; | |
451 | 449 | try { |
452 | 450 | while (left != string::npos) { |
453 | right = _xmlbuf.find('<', left); | |
451 | size_t right = _xmlbuf.find('<', left); | |
454 | 452 | if (left < right && left < _xmlbuf.length()) // plain text found? |
455 | 453 | (actions.svgTree().*_append)(util::make_unique<XMLText>(_xmlbuf.substr(left, right-left))); |
456 | 454 | if (right != string::npos) { |
522 | 520 | BufferInputReader ir(ib); |
523 | 521 | string name = ir.getString("/ \t\n\r"); |
524 | 522 | ir.skipSpace(); |
525 | auto elemNode = util::make_unique<XMLElement>(name); | |
523 | auto elemNode = util::make_unique<SVGElement>(name); | |
526 | 524 | map<string, string> attribs; |
527 | 525 | if (ir.parseAttributes(attribs, true, "\"'")) { |
528 | 526 | for (const auto &attrpair : attribs) |
28 | 28 | |
29 | 29 | class InputReader; |
30 | 30 | class SpecialActions; |
31 | class SVGElement; | |
31 | 32 | class SVGTree; |
32 | 33 | class XMLElement; |
33 | 34 | class XMLNode; |
43 | 44 | class DvisvgmSpecialHandler : public SpecialHandler { |
44 | 45 | class XMLParser { |
45 | 46 | using AppendFunc = void (SVGTree::*)(std::unique_ptr<XMLNode>); |
46 | using PushFunc = void (SVGTree::*)(std::unique_ptr<XMLElement>); | |
47 | using PushFunc = void (SVGTree::*)(std::unique_ptr<SVGElement>); | |
47 | 48 | using PopFunc = void (SVGTree::*)(); |
48 | 49 | using NameStack = std::vector<std::string>; |
49 | 50 |
176 | 176 | if (isStraightLine()) { |
177 | 177 | DPair dir = (_endPoint - _startPoint); |
178 | 178 | dir /= dir.length()/3.0; |
179 | beziers.emplace_back(Bezier(_startPoint, _startPoint+dir, _endPoint-dir, _endPoint)); | |
179 | beziers.emplace_back(_startPoint, _startPoint+dir, _endPoint-dir, _endPoint); | |
180 | 180 | } |
181 | 181 | else { |
182 | 182 | CenterParams cparams = getCenterParams(); |
189 | 189 | if (numCurves > 0) { |
190 | 190 | double c = cos(_rotationAngle); |
191 | 191 | double s = sin(_rotationAngle); |
192 | Matrix ellipse = {_rx*c, -_ry*s, cparams.center.x(), _rx*s, _ry*c, cparams.center.y()}; | |
192 | Matrix ellipse{_rx*c, -_ry*s, cparams.center.x(), _rx*s, _ry*c, cparams.center.y()}; | |
193 | 193 | double angle = cparams.startAngle; |
194 | 194 | double diff = cparams.deltaAngle/numCurves; |
195 | 195 | while (numCurves-- > 0) { |
196 | beziers.emplace_back(approx_unit_arc(angle, diff).transform(ellipse)); | |
196 | beziers.push_back(approx_unit_arc(angle, diff).transform(ellipse)); | |
197 | 197 | angle += diff; |
198 | 198 | } |
199 | 199 | } |
23 | 23 | #include "InputReader.hpp" |
24 | 24 | #include "Length.hpp" |
25 | 25 | #include "SpecialActions.hpp" |
26 | #include "SVGElement.hpp" | |
26 | 27 | #include "SVGTree.hpp" |
27 | #include "XMLNode.hpp" | |
28 | #include "XMLString.hpp" | |
29 | 28 | |
30 | 29 | using namespace std; |
31 | 30 | |
76 | 75 | static void create_line (const DPair &p1, const DPair &p2, char c1, char c2, double lw, SpecialActions &actions) { |
77 | 76 | if (actions.outputLocked()) |
78 | 77 | return; |
79 | unique_ptr<XMLElement> node; | |
78 | unique_ptr<SVGElement> node; | |
80 | 79 | DPair dir = p2-p1; |
81 | 80 | if (dir.x() == 0 || dir.y() == 0 || (c1 == 'p' && c2 == 'p')) { |
82 | 81 | // draw regular line |
83 | node = util::make_unique<XMLElement>("line"); | |
82 | node = util::make_unique<SVGElement>("line"); | |
84 | 83 | node->addAttribute("x1", p1.x()); |
85 | 84 | node->addAttribute("y1", p1.y()); |
86 | 85 | node->addAttribute("x2", p2.x()); |
87 | 86 | node->addAttribute("y2", p2.y()); |
88 | node->addAttribute("stroke-width", lw); | |
89 | node->addAttribute("stroke", actions.getColor().svgColorString()); | |
87 | node->setStrokeWidth(lw); | |
88 | node->setStrokeColor(actions.getColor()); | |
89 | node->setStrokeOpacity(actions.getOpacity()); | |
90 | ||
90 | 91 | // update bounding box |
91 | 92 | DPair cv = cut_vector('p', dir, lw); |
92 | 93 | actions.embed(p1+cv); |
96 | 97 | } |
97 | 98 | else { |
98 | 99 | // draw polygon |
100 | vector<DPair> points; | |
99 | 101 | DPair cv1 = cut_vector(c1, dir, lw); |
100 | 102 | DPair cv2 = cut_vector(c2, dir, lw); |
101 | DPair q11 = p1+cv1, q12 = p1-cv1; | |
102 | DPair q21 = p2+cv2, q22 = p2-cv2; | |
103 | ostringstream oss; | |
104 | oss << XMLString(q11.x()) << ',' << XMLString(q11.y()) << ' ' | |
105 | << XMLString(q12.x()) << ',' << XMLString(q12.y()) << ' ' | |
106 | << XMLString(q22.x()) << ',' << XMLString(q22.y()) << ' ' | |
107 | << XMLString(q21.x()) << ',' << XMLString(q21.y()); | |
108 | node = util::make_unique<XMLElement>("polygon"); | |
109 | node->addAttribute("points", oss.str()); | |
110 | if (actions.getColor() != Color::BLACK) | |
111 | node->addAttribute("fill", actions.getColor().svgColorString()); | |
103 | points.push_back(p1+cv1); | |
104 | points.push_back(p1-cv1); | |
105 | points.push_back(p2-cv2); | |
106 | points.push_back(p2+cv2); | |
107 | ||
108 | node = util::make_unique<SVGElement>("polygon"); | |
109 | node->setPoints(points); | |
110 | node->setFillColor(actions.getColor()); | |
111 | node->setFillOpacity(actions.getOpacity()); | |
112 | ||
112 | 113 | // update bounding box |
113 | actions.embed(q11); | |
114 | actions.embed(q12); | |
115 | actions.embed(q21); | |
116 | actions.embed(q22); | |
114 | actions.embed(points[0]); | |
115 | actions.embed(points[1]); | |
116 | actions.embed(points[2]); | |
117 | actions.embed(points[3]); | |
117 | 118 | } |
118 | 119 | actions.svgTree().appendToPage(std::move(node)); |
119 | 120 | } |
235 | 236 | // Line endpoints don't necessarily have to be defined before |
236 | 237 | // a line definition. If a point isn't defined yet, we put the line |
237 | 238 | // in a wait list and process the lines at the end of the page. |
238 | _lines.emplace_back(Line(pointnum1, pointnum2, char(cut1), char(cut2), linewidth)); | |
239 | _lines.emplace_back(pointnum1, pointnum2, char(cut1), char(cut2), linewidth); | |
239 | 240 | } |
240 | 241 | } |
241 | 242 |
36 | 36 | |
37 | 37 | |
38 | 38 | #ifdef _WIN32 |
39 | /** Returns the drive letter of a given path string or 0 if there's none. */ | |
40 | static char drive_letter (const string &path) { | |
41 | if (path.length() >= 2 && path[1] == ':' && isalpha(path[0])) | |
42 | return tolower(path[0]); | |
43 | return '\0'; | |
44 | } | |
45 | ||
46 | /** Removes the drive letter and following colon from a given path string if present. | |
47 | * @param[in] path path to strip drive letter from | |
48 | * @return drive letter or 0 if there was none */ | |
39 | 49 | static char strip_drive_letter (string &path) { |
40 | char letter = 0; | |
50 | char letter = '\0'; | |
41 | 51 | if (path.length() >= 2 && path[1] == ':' && isalpha(path[0])) { |
42 | 52 | letter = path[0]; |
43 | 53 | path.erase(0, 2); |
44 | 54 | } |
45 | return letter; | |
46 | } | |
47 | ||
48 | ||
49 | static char adapt_current_path (string &path, char target_drive) { | |
50 | if (char current_drive = strip_drive_letter(path)) { | |
51 | if (target_drive != current_drive) { | |
52 | if (target_drive == 0) | |
53 | target_drive = current_drive; | |
54 | if (path.empty() || path[0] != '/') { | |
55 | if (FileSystem::chdir(string(1, target_drive) + ":")) { | |
56 | path.insert(0, FileSystem::getcwd()+"/"); | |
57 | strip_drive_letter(path); | |
58 | } | |
59 | else | |
60 | throw MessageException("drive " + string(1, target_drive) + ": not accessible"); | |
61 | } | |
62 | } | |
63 | } | |
64 | return target_drive; | |
65 | } | |
66 | ||
55 | return tolower(letter); | |
56 | } | |
67 | 57 | #endif |
68 | 58 | |
69 | 59 | |
98 | 88 | * @param[in] path absolute or relative path to a file or directory |
99 | 89 | * @param[in] isfile true if 'path' references a file, false if a directory is referenced */ |
100 | 90 | void FilePath::set (const string &path, bool isfile) { |
101 | init(path, isfile, FileSystem::getcwd()); | |
91 | init(path, isfile, ""); | |
102 | 92 | } |
103 | 93 | |
104 | 94 | |
120 | 110 | _fname.clear(); |
121 | 111 | single_slashes(path); |
122 | 112 | single_slashes(current_dir); |
123 | #ifdef _WIN32 | |
113 | #ifndef _WIN32 | |
114 | if (current_dir.empty()) | |
115 | current_dir = FileSystem::getcwd(); | |
116 | #else | |
117 | _drive = strip_drive_letter(path); | |
118 | if (current_dir.empty() || drive_letter(current_dir) != _drive) | |
119 | current_dir = FileSystem::getcwd(_drive); | |
120 | if (!_drive) | |
121 | _drive = drive_letter(current_dir); | |
122 | strip_drive_letter(current_dir); | |
124 | 123 | path = FileSystem::ensureForwardSlashes(path); |
125 | _drive = strip_drive_letter(path); | |
126 | 124 | #endif |
127 | 125 | if (isfile) { |
128 | 126 | size_t pos = path.rfind('/'); |
129 | 127 | _fname = path.substr((pos == string::npos) ? 0 : pos+1); |
130 | if (pos != string::npos) | |
128 | // remove filename from path | |
129 | if (pos == 0 && _fname.length() > 1) // file in root directory? | |
130 | path.erase(1); | |
131 | else if (pos != string::npos) | |
131 | 132 | path.erase(pos); |
132 | 133 | else |
133 | 134 | path.clear(); |
134 | 135 | } |
135 | if (current_dir.empty()) | |
136 | current_dir = FileSystem::getcwd(); | |
137 | #ifdef _WIN32 | |
138 | _drive = adapt_current_path(current_dir, _drive); | |
139 | #endif | |
140 | if (!path.empty()) { | |
141 | if (path[0] == '/') | |
142 | current_dir.clear(); | |
143 | else if (current_dir[0] != '/') | |
144 | current_dir = "/"; | |
145 | else { | |
146 | FilePath curr(current_dir, false, "/"); | |
147 | current_dir = curr.absolute(); | |
148 | #ifdef _WIN32 | |
149 | adapt_current_path(current_dir, _drive); | |
150 | #endif | |
151 | } | |
152 | } | |
153 | path.insert(0, current_dir + "/"); | |
136 | if ((path.empty() || path[0] != '/') && !current_dir.empty()) | |
137 | path.insert(0, current_dir + "/"); | |
154 | 138 | string elem; |
155 | 139 | for (char c : path) { |
156 | 140 | if (c != '/') |
157 | 141 | elem += c; |
158 | else { | |
142 | else if (!elem.empty()){ | |
159 | 143 | add(elem); |
160 | 144 | elem.clear(); |
161 | 145 | } |
162 | 146 | } |
163 | add(elem); | |
147 | if (!elem.empty()) | |
148 | add(elem); | |
164 | 149 | } |
165 | 150 | |
166 | 151 | |
168 | 153 | void FilePath::add (const string &dir) { |
169 | 154 | if (dir == ".." && !_dirs.empty()) |
170 | 155 | _dirs.pop_back(); |
171 | else if (dir.length() > 0 && dir != ".") | |
156 | else if (!dir.empty() && dir != ".") | |
172 | 157 | _dirs.emplace_back(dir); |
173 | 158 | } |
174 | 159 | |
222 | 207 | * @return the absolute path string */ |
223 | 208 | string FilePath::absolute (bool with_filename) const { |
224 | 209 | string path; |
210 | #ifdef _WIN32 | |
211 | if (_drive) | |
212 | path = string(1, _drive) + ":"; | |
213 | #endif | |
225 | 214 | for (const Directory &dir : _dirs) |
226 | 215 | path += "/" + string(dir); |
227 | 216 | if (path.empty()) |
228 | 217 | path = "/"; |
229 | 218 | if (with_filename && !_fname.empty()) |
230 | 219 | path += "/"+_fname; |
231 | #ifdef _WIN32 | |
232 | if (_drive) | |
233 | path.insert(0, string(1, _drive) + ":"); | |
234 | #endif | |
235 | 220 | return single_slashes(path); |
236 | 221 | } |
237 | 222 | |
244 | 229 | * @param[in] with_filename if false, the filename is omitted |
245 | 230 | * @return the relative path string */ |
246 | 231 | string FilePath::relative (string reldir, bool with_filename) const { |
232 | #ifdef _WIN32 | |
233 | char reldrive = drive_letter(reldir); | |
234 | if (reldir.empty()) { | |
235 | reldir = FileSystem::getcwd(_drive); | |
236 | reldrive = drive_letter(FileSystem::getcwd()); | |
237 | } | |
238 | bool isAbsolute = (reldir[0] == '/') | |
239 | || (reldir.length() >= 3 && isalpha(reldir[0]) && reldir[1] == ':' && reldir[2] == '/'); | |
240 | #else | |
247 | 241 | if (reldir.empty()) |
248 | 242 | reldir = FileSystem::getcwd(); |
249 | #ifdef _WIN32 | |
250 | adapt_current_path(reldir, _drive); | |
251 | #endif | |
252 | if (reldir[0] != '/') | |
243 | bool isAbsolute = (reldir[0] == '/'); | |
244 | #endif | |
245 | if (!isAbsolute) | |
253 | 246 | return absolute(); |
254 | FilePath rel(reldir, false); | |
247 | FilePath relpath(reldir, false); | |
255 | 248 | string path; |
256 | #ifdef _WIN32 | |
257 | if (rel._drive && _drive && tolower(rel._drive) != tolower(_drive)) | |
258 | path += string(1, _drive) + ":"; | |
259 | #endif | |
260 | 249 | auto it1 = _dirs.begin(); |
261 | auto it2 = rel._dirs.begin(); | |
262 | while (it1 != _dirs.end() && it2 != rel._dirs.end() && *it1 == *it2) | |
250 | auto it2 = relpath._dirs.begin(); | |
251 | while (it1 != _dirs.end() && it2 != relpath._dirs.end() && *it1 == *it2) | |
263 | 252 | ++it1, ++it2; |
264 | for (; it2 != rel._dirs.end(); ++it2) | |
253 | for (; it2 != relpath._dirs.end(); ++it2) | |
265 | 254 | path += "../"; |
266 | 255 | for (; it1 != _dirs.end(); ++it1) |
267 | 256 | path += string(*it1) + "/"; |
268 | if (!path.empty()) | |
257 | if (!path.empty() && path.back() == '/') | |
269 | 258 | path.erase(path.length()-1, 1); // remove trailing slash |
270 | 259 | if (with_filename && !_fname.empty()) { |
271 | 260 | if (!path.empty() && path != "/") |
274 | 263 | } |
275 | 264 | if (path.empty()) |
276 | 265 | path = "."; |
266 | #ifdef _WIN32 | |
267 | if (relpath._drive && _drive && _drive != reldrive) | |
268 | path.insert(0, string(1, _drive) + ":"); | |
269 | #endif | |
277 | 270 | return single_slashes(path); |
278 | 271 | } |
279 | 272 |
134 | 134 | } |
135 | 135 | |
136 | 136 | |
137 | /** Returns the absolute path of the current working directory. */ | |
137 | 138 | string FileSystem::getcwd () { |
138 | 139 | char buf[1024]; |
139 | 140 | #ifdef _WIN32 |
140 | return ensureForwardSlashes(_getcwd(buf, 1024)); | |
141 | GetCurrentDirectoryA(1024, buf); | |
142 | return ensureForwardSlashes(buf); | |
141 | 143 | #else |
142 | 144 | return ::getcwd(buf, 1024); |
143 | 145 | #endif |
144 | 146 | } |
147 | ||
148 | ||
149 | #ifdef _WIN32 | |
150 | /** Returns the absolute path of the current directory of a given drive. | |
151 | * Windows keeps a current directory for every drive, i.e. when accessing a drive | |
152 | * without specifying a path (e.g. with "cd z:"), the current directory of that | |
153 | * drive is used. | |
154 | * @param[in] drive letter of drive to get the current directory from | |
155 | * @return absolute path of the directory */ | |
156 | string FileSystem::getcwd (char drive) { | |
157 | string cwd = getcwd(); | |
158 | if (cwd.length() > 1 && cwd[1] == ':' && tolower(cwd[0]) != tolower(drive)) { | |
159 | chdir(string(1, drive)+":"); | |
160 | string cwd2 = cwd; | |
161 | cwd = getcwd(); | |
162 | chdir(string(1, cwd2[0])+":"); | |
163 | } | |
164 | return cwd; | |
165 | } | |
166 | #endif | |
145 | 167 | |
146 | 168 | |
147 | 169 | /** Changes the work directory. |
47 | 47 | static uint64_t filesize (const std::string &fname); |
48 | 48 | static std::string ensureForwardSlashes (std::string path); |
49 | 49 | static std::string getcwd (); |
50 | #ifdef _WIN32 | |
51 | static std::string getcwd (char drive); | |
52 | #endif | |
50 | 53 | static std::string tmpdir (); |
51 | 54 | static bool chdir (const std::string &dir); |
52 | 55 | static bool exists (const std::string &fname); |
678 | 678 | return (it == _charDefs.end() ? nullptr : &it->second); |
679 | 679 | } |
680 | 680 | |
681 | ////////////////////////////////////////////////////////////////////////////// | |
682 | ||
683 | void PhysicalFont::visit (FontVisitor &visitor) {visitor.visited(this);} | |
684 | void VirtualFont::visit (FontVisitor &visitor) {visitor.visited(this);} | |
685 | void NativeFont::visit (FontVisitor &visitor) {visitor.visited(this);} | |
686 | void PhysicalFont::visit (FontVisitor &visitor) const {visitor.visited(this);} | |
687 | void VirtualFont::visit (FontVisitor &visitor) const {visitor.visited(this);} | |
688 | void NativeFont::visit (FontVisitor &visitor) const {visitor.visited(this);} |
49 | 49 | double wl, wr, h, d; |
50 | 50 | }; |
51 | 51 | |
52 | class FontVisitor; | |
52 | 53 | |
53 | 54 | /** Abstract base for all font classes. */ |
54 | 55 | class Font { |
68 | 69 | virtual const char* path () const =0; |
69 | 70 | virtual const char* filename () const; |
70 | 71 | virtual const FontEncoding* encoding () const; |
71 | virtual bool getGlyph (int c, Glyph &glyph, GFGlyphTracer::Callback *callback=nullptr) const =0; | |
72 | virtual bool getGlyph (int c, Glyph &glyph, GFGlyphTracer::Callback *callback) const =0; | |
72 | 73 | virtual void getGlyphMetrics (int c, bool vertical, GlyphMetrics &metrics) const; |
73 | 74 | virtual uint32_t unicode (uint32_t c) const; |
74 | 75 | virtual void tidy () const {} |
75 | 76 | virtual bool findAndAssignBaseFontMap () {return true;} |
76 | virtual bool verticalLayout () const {return getMetrics() ? getMetrics()->verticalLayout() : false;} | |
77 | virtual bool verticalLayout () const {return getMetrics() != nullptr && getMetrics()->verticalLayout();} | |
77 | 78 | virtual bool verifyChecksums () const {return true;} |
78 | 79 | virtual int fontIndex () const {return 0;} |
79 | 80 | virtual const FontStyle* style () const {return nullptr;} |
80 | 81 | virtual Color color () const {return Color::BLACK;} |
81 | 82 | virtual const FontMap::Entry* fontMapEntry () const; |
83 | virtual void visit (FontVisitor &visitor) =0; | |
84 | virtual void visit (FontVisitor &visitor) const =0; | |
82 | 85 | }; |
83 | 86 | |
84 | 87 | |
99 | 102 | double italicCorr (int c) const override {return 0;} |
100 | 103 | const FontMetrics* getMetrics () const override {return nullptr;} |
101 | 104 | const char* path () const override {return nullptr;} |
102 | bool getGlyph (int c, Glyph &glyph, GFGlyphTracer::Callback *cb=nullptr) const override {return false;} | |
105 | bool getGlyph (int c, Glyph &glyph, GFGlyphTracer::Callback*) const override {return false;} | |
106 | void visit (FontVisitor &visitor) override {} | |
107 | void visit (FontVisitor &visitor) const override {} | |
103 | 108 | |
104 | 109 | private: |
105 | 110 | std::string _fontname; |
114 | 119 | static std::unique_ptr<Font> create (const std::string &name, uint32_t checksum, double dsize, double ssize, PhysicalFont::Type type); |
115 | 120 | static std::unique_ptr<Font> create (const std::string &name, int fontindex, uint32_t checksum, double dsize, double ssize); |
116 | 121 | virtual Type type () const =0; |
117 | bool getGlyph (int c, Glyph &glyph, GFGlyphTracer::Callback *cb=nullptr) const override; | |
118 | virtual bool getExactGlyphBox (int c, BoundingBox &bbox, GFGlyphTracer::Callback *cb=nullptr) const; | |
119 | virtual bool getExactGlyphBox (int c, GlyphMetrics &metrics, bool vertical, GFGlyphTracer::Callback *cb=nullptr) const; | |
122 | bool getGlyph (int c, Glyph &glyph, GFGlyphTracer::Callback *cb) const override; | |
123 | virtual bool getExactGlyphBox (int c, BoundingBox &bbox, GFGlyphTracer::Callback *cb) const; | |
124 | virtual bool getExactGlyphBox (int c, GlyphMetrics &metrics, bool vertical, GFGlyphTracer::Callback *cb) const; | |
120 | 125 | virtual bool isCIDFont () const; |
121 | 126 | virtual int hAdvance () const; |
122 | 127 | virtual std::string familyName () const; |
128 | 133 | virtual double scaledAscent () const; |
129 | 134 | virtual int ascent () const; |
130 | 135 | virtual int descent () const; |
131 | virtual int traceAllGlyphs (bool includeCached, GFGlyphTracer::Callback *cb=nullptr) const; | |
136 | virtual int traceAllGlyphs (bool includeCached, GFGlyphTracer::Callback *cb) const; | |
132 | 137 | virtual int collectCharMapIDs (std::vector<CharMapID> &charmapIDs) const; |
133 | 138 | virtual CharMapID getCharMapID () const =0; |
134 | 139 | virtual void setCharMapID (const CharMapID &id) {} |
135 | 140 | virtual Character decodeChar (uint32_t c) const; |
136 | 141 | const char* path () const override; |
142 | void visit (FontVisitor &visitor) override; | |
143 | void visit (FontVisitor &visitor) const override; | |
137 | 144 | |
138 | 145 | protected: |
139 | 146 | bool createGF (std::string &gfname) const; |
158 | 165 | public: |
159 | 166 | static std::unique_ptr<Font> create (const std::string &name, uint32_t checksum, double dsize, double ssize); |
160 | 167 | virtual const DVIVector* getDVI (int c) const =0; |
161 | bool getGlyph (int c, Glyph &glyph, GFGlyphTracer::Callback *cb=nullptr) const override {return false;} | |
168 | bool getGlyph (int c, Glyph &glyph, GFGlyphTracer::Callback*) const override {return false;} | |
169 | void visit (FontVisitor &visitor) override; | |
170 | void visit (FontVisitor &visitor) const override; | |
162 | 171 | |
163 | 172 | protected: |
164 | 173 | virtual void assignChar (uint32_t c, DVIVector &&dvi) =0; |
271 | 280 | Color color () const override {return _color;} |
272 | 281 | const FontMap::Entry* fontMapEntry () const override {return nullptr;} |
273 | 282 | static std::string uniqueName (const std::string &path, const FontStyle &style); |
283 | void visit (FontVisitor &visitor) override; | |
284 | void visit (FontVisitor &visitor) const override; | |
274 | 285 | |
275 | 286 | protected: |
276 | 287 | NativeFont (double ptsize, const FontStyle &style, Color color) : _ptsize(ptsize), _style(style), _color(color) {} |
389 | 400 | }; |
390 | 401 | |
391 | 402 | |
403 | struct FontVisitor { | |
404 | virtual ~FontVisitor () =default; | |
405 | virtual void visited (const PhysicalFont *font) {} | |
406 | virtual void visited (const VirtualFont *font) {} | |
407 | virtual void visited (const NativeFont *font) {visited(static_cast<const PhysicalFont*>(font));} | |
408 | virtual void visited (PhysicalFont *font) {} | |
409 | virtual void visited (VirtualFont *font) {} | |
410 | virtual void visited (NativeFont *font) {visited(static_cast<PhysicalFont*>(font));} | |
411 | }; | |
412 | ||
413 | ||
414 | /** This function works similar to dynamic_cast but only on pointers to Font classes. | |
415 | * It uses double dispatch instead of RTTI and should therefore be faster. | |
416 | * @param[in] font font pointer to be cast | |
417 | * @return cast pointer on success, nullptr otherwise */ | |
418 | template <typename C> | |
419 | C font_cast (typename util::set_const_of<Font>::by<typename std::remove_pointer<C>::type>::type *font) { | |
420 | struct : FontVisitor { | |
421 | void visited (C font) override {result = font;} | |
422 | C result = nullptr; | |
423 | } visitor; | |
424 | if (font) | |
425 | font->visit(visitor); | |
426 | return visitor.result; | |
427 | } | |
428 | ||
429 | ||
392 | 430 | struct FontException : public MessageException { |
393 | 431 | explicit FontException (const std::string &msg) : MessageException(msg) {} |
394 | 432 | }; |
280 | 280 | string path = dirname+"/"+(fname.substr(1)); |
281 | 281 | ifstream ifs(path, ios::binary); |
282 | 282 | if (fontinfo(ifs, info)) |
283 | infos.emplace_back(move(info)); | |
283 | infos.push_back(move(info)); | |
284 | 284 | else |
285 | invalid.emplace_back(fname.substr(1)); | |
285 | invalid.push_back(fname.substr(1)); | |
286 | 286 | } |
287 | 287 | } |
288 | 288 | } |
102 | 102 | return true; |
103 | 103 | |
104 | 104 | if (const char *path=font.path()) { |
105 | auto pf = dynamic_cast<const PhysicalFont*>(&font); | |
105 | auto pf = font_cast<const PhysicalFont*>(&font); | |
106 | 106 | if (setFont(path, font.fontIndex(), pf ? pf->getCharMapID() : CharMapID())) { |
107 | 107 | _currentFont = &font; |
108 | 108 | return true; |
307 | 307 | if (_currentFace) { |
308 | 308 | for (int i=0; i < _currentFace->num_charmaps; i++) { |
309 | 309 | FT_CharMap charmap = _currentFace->charmaps[i]; |
310 | charmapIDs.emplace_back(CharMapID(charmap->platform_id, charmap->encoding_id)); | |
310 | charmapIDs.emplace_back(charmap->platform_id, charmap->encoding_id); | |
311 | 311 | } |
312 | 312 | } |
313 | 313 | return charmapIDs.size(); |
220 | 220 | } |
221 | 221 | _name2id[name] = newid; |
222 | 222 | } |
223 | _fonts.emplace_back(std::move(newfont)); | |
223 | _fonts.push_back(std::move(newfont)); | |
224 | 224 | if (_vfStack.empty()) // register font referenced in dvi file? |
225 | 225 | _num2id[fontnum] = newid; |
226 | 226 | else { // register font referenced in vf file |
227 | 227 | const VirtualFont *vf = _vfStack.top(); |
228 | 228 | _vfnum2id[vf][fontnum] = newid; |
229 | if (_vfFirstFontNumMap.find(vf) == _vfFirstFontNumMap.end()) { // first fontdef of VF? | |
230 | _vfFirstFontNumMap.emplace(vf, fontnum); | |
231 | _vfFirstFontMap.emplace(vf, _fonts.back().get()); | |
232 | } | |
229 | _vfFirstFontNumMap.emplace(vf, fontnum); | |
230 | _vfFirstFontMap.emplace(vf, _fonts.back().get()); | |
233 | 231 | } |
234 | 232 | return newid; |
235 | 233 | } |
268 | 266 | const int newid = _fonts.size(); // the new font gets this ID |
269 | 267 | auto it = _name2id.find(fontname); |
270 | 268 | if (it != _name2id.end()) { // font with same name already registered? |
271 | if (auto font = dynamic_cast<NativeFont*>(_fonts[it->second].get())) | |
269 | if (auto font = font_cast<NativeFont*>(_fonts[it->second].get())) | |
272 | 270 | newfont = font->clone(ptsize, style, color); |
273 | 271 | } |
274 | 272 | else { |
290 | 288 | } |
291 | 289 | _name2id[fontname] = newid; |
292 | 290 | } |
293 | _fonts.emplace_back(std::move(newfont)); | |
291 | _fonts.push_back(std::move(newfont)); | |
294 | 292 | _num2id[fontnum] = newid; |
295 | 293 | return newid; |
296 | 294 | } |
329 | 327 | if (_fonts[i] == font) |
330 | 328 | id = i; |
331 | 329 | |
332 | VirtualFont *vf = dynamic_cast<VirtualFont*>(font); | |
330 | VirtualFont *vf = font_cast<VirtualFont*>(font); | |
333 | 331 | for (int j=0; j < level+1; j++) |
334 | 332 | os << " "; |
335 | 333 | os << "id " << id |
42 | 42 | virtual uint32_t getChecksum () const =0; |
43 | 43 | virtual uint16_t firstChar () const =0; |
44 | 44 | virtual uint16_t lastChar () const =0; |
45 | virtual bool isJFM () const {return false;} | |
45 | 46 | static std::unique_ptr<FontMetrics> read (const std::string &fontname); |
46 | 47 | }; |
47 | 48 |
58 | 58 | vector<string> FontWriter::supportedFormats () { |
59 | 59 | vector<string> formats; |
60 | 60 | for (const FontFormatInfo &info : _formatInfos) |
61 | formats.emplace_back(info.formatstr_short); | |
61 | formats.push_back(info.formatstr_short); | |
62 | 62 | return formats; |
63 | 63 | } |
64 | 64 |
24 | 24 | /** Constructs a new glyph tracer. |
25 | 25 | * @param[in] is GF input stream |
26 | 26 | * @param[in] upp target units per PS point */ |
27 | GFGlyphTracer::GFGlyphTracer (string &fname, double upp, Callback *cb) | |
27 | GFGlyphTracer::GFGlyphTracer (const string &fname, double upp, Callback *cb) | |
28 | 28 | : GFTracer(_ifs, upp), _callback(cb) |
29 | 29 | { |
30 | 30 | if (_callback) |
33 | 33 | } |
34 | 34 | |
35 | 35 | |
36 | void GFGlyphTracer::reset (string &fname, double upp) { | |
36 | void GFGlyphTracer::reset (const string &fname, double upp) { | |
37 | 37 | if (_callback) |
38 | 38 | _callback->setFont(fname); |
39 | 39 | if (_ifs.is_open()) |
37 | 37 | |
38 | 38 | public: |
39 | 39 | GFGlyphTracer () : GFTracer(_ifs, 0) {} |
40 | GFGlyphTracer (std::string &fname, double upp, Callback *cb=nullptr); | |
41 | void reset (std::string &fname, double upp); | |
40 | GFGlyphTracer (const std::string &fname, double upp, Callback *cb=nullptr); | |
41 | void reset (const std::string &fname, double upp); | |
42 | 42 | void setCallback (Callback *cb) {_callback = cb;} |
43 | 43 | bool executeChar (uint8_t c) override; |
44 | 44 | void moveTo (double x, double y) override; |
91 | 91 | if (opcode < 0) // at end of file? |
92 | 92 | throw GFException("unexpected end of file"); |
93 | 93 | |
94 | if (opcode >= 0 && opcode <= 63) | |
94 | if (opcode <= 63) | |
95 | 95 | cmdPaint0(opcode); |
96 | 96 | else if (opcode >= 74 && opcode <= 238) |
97 | 97 | cmdNewRow(opcode-74); |
214 | 214 | /** Retrieves version information about Ghostscript. |
215 | 215 | * @param[out] r takes the revision information (see GS API documentation for further details) |
216 | 216 | * @return true on success */ |
217 | bool Ghostscript::revision (gsapi_revision_t *r) { | |
217 | bool Ghostscript::revision (gsapi_revision_t *r) const { | |
218 | 218 | #if defined(HAVE_LIBGS) |
219 | 219 | return (gsapi_revision(r, sizeof(gsapi_revision_t)) == 0); |
220 | 220 | #else |
226 | 226 | |
227 | 227 | |
228 | 228 | /** Returns the revision number of the GS library. */ |
229 | int Ghostscript::revision () { | |
229 | int Ghostscript::revision () const { | |
230 | 230 | gsapi_revision_t r; |
231 | 231 | if (revision(&r)) |
232 | 232 | return static_cast<int>(r.revision); |
77 | 77 | ~Ghostscript (); |
78 | 78 | bool init (int argc, const char **argv, void *caller=nullptr); |
79 | 79 | bool available (); |
80 | bool revision (gsapi_revision_t *r); | |
81 | int revision (); | |
80 | bool revision (gsapi_revision_t *r) const; | |
81 | int revision () const; | |
82 | 82 | std::string revisionstr (); |
83 | 83 | int set_stdio (Stdin in, Stdout out, Stderr err); |
84 | 84 | int run_string_begin (int user_errors, int *pexit_code); |
19 | 19 | |
20 | 20 | #pragma once |
21 | 21 | |
22 | #include <algorithm> | |
22 | 23 | #include <array> |
23 | 24 | #include <cctype> |
24 | 25 | #include <cmath> |
661 | 662 | bool operator == (const GraphicsPath &path) const { |
662 | 663 | if (size() != path.size()) |
663 | 664 | return false; |
664 | auto it = _commands.begin(); | |
665 | for (const auto &cmd : path._commands) { | |
666 | if (*it++ != cmd) | |
667 | return false; | |
668 | } | |
669 | return true; | |
665 | return std::equal(_commands.begin(), _commands.end(), path._commands.begin()); | |
670 | 666 | } |
671 | 667 | |
672 | 668 | /** Returns true if this path differs from another one (command-wise). */ |
673 | 669 | bool operator != (const GraphicsPath &path) const { |
674 | 670 | if (size() != path.size()) |
675 | 671 | return true; |
676 | auto it = _commands.begin(); | |
677 | for (const auto &cmd : path._commands) { | |
678 | if (*it++ != cmd) | |
679 | return true; | |
680 | } | |
681 | return false; | |
672 | return !std::equal(_commands.begin(), _commands.end(), path._commands.begin()); | |
682 | 673 | } |
683 | 674 | |
684 | 675 | /** Iterates over all commands defining this path and calls the corresponding template methods. |
105 | 105 | uri = "/" + uri; |
106 | 106 | uri = _base + uri; |
107 | 107 | } |
108 | auto anchorNode = util::make_unique<XMLElement>("a"); | |
108 | auto anchorNode = util::make_unique<SVGElement>("a"); | |
109 | 109 | anchorNode->addAttribute("xlink:href", uri); |
110 | 110 | anchorNode->addAttribute("xlink:title", XMLString(name.empty() ? uri : name, false)); |
111 | 111 | actions.svgTree().pushPageContext(std::move(anchorNode)); |
147 | 147 | if (bbox.width() > 0 && bbox.height() > 0) { // does the bounding box extend in both dimensions? |
148 | 148 | if (MARKER_TYPE != MarkerType::NONE) { |
149 | 149 | const double linewidth = _linewidth >= 0 ? _linewidth : min(0.5, bbox.height()/15); |
150 | auto rect = util::make_unique<XMLElement>("rect"); | |
150 | auto rect = util::make_unique<SVGElement>("rect"); | |
151 | 151 | double x = bbox.minX(); |
152 | 152 | double y = bbox.maxY()+linewidth; |
153 | 153 | double w = bbox.width(); |
154 | 154 | double h = linewidth; |
155 | 155 | const Color linecolor = COLORSOURCE == ColorSource::DEFAULT ? actions.getColor() : LINK_LINECOLOR; |
156 | 156 | if (MARKER_TYPE == MarkerType::LINE) |
157 | rect->addAttribute("fill", linecolor.svgColorString()); | |
157 | rect->setFillColor(linecolor); | |
158 | 158 | else { |
159 | 159 | const double offset = _linewidth < 0 ? linewidth : 0 ; |
160 | 160 | x -= offset; |
162 | 162 | w += 2*offset; |
163 | 163 | h += bbox.height()+offset; |
164 | 164 | if (MARKER_TYPE == MarkerType::BGCOLOR) { |
165 | rect->addAttribute("fill", LINK_BGCOLOR.svgColorString()); | |
165 | rect->setFillColor(LINK_BGCOLOR); | |
166 | 166 | if (COLORSOURCE != ColorSource::DEFAULT) { |
167 | rect->addAttribute("stroke", linecolor.svgColorString()); | |
168 | rect->addAttribute("stroke-width", linewidth); | |
167 | rect->setStrokeColor(linecolor); | |
168 | rect->setStrokeWidth(linewidth); | |
169 | 169 | } |
170 | 170 | } |
171 | 171 | else { // LM_BOX |
172 | rect->addAttribute("fill", "none"); | |
173 | rect->addAttribute("stroke", linecolor.svgColorString()); | |
174 | rect->addAttribute("stroke-width", linewidth); | |
172 | rect->setNoFillColor(); | |
173 | rect->setStrokeColor(linecolor); | |
174 | rect->setStrokeWidth(linewidth); | |
175 | 175 | } |
176 | 176 | } |
177 | 177 | rect->addAttribute("x", x); |
191 | 191 | // Create an invisible rectangle around the linked area so that it's easier to access. |
192 | 192 | // This is only necessary when using paths rather than real text elements together with fonts. |
193 | 193 | if (!SVGTree::USE_FONTS) { |
194 | auto rect = util::make_unique<XMLElement>("rect"); | |
194 | auto rect = util::make_unique<SVGElement>("rect"); | |
195 | 195 | rect->addAttribute("x", bbox.minX()); |
196 | 196 | rect->addAttribute("y", bbox.minY()); |
197 | 197 | rect->addAttribute("width", bbox.width()); |
198 | 198 | rect->addAttribute("height", bbox.height()); |
199 | rect->addAttribute("fill", "white"); | |
200 | rect->addAttribute("fill-opacity", 0); | |
199 | rect->setFillColor(Color::WHITE); | |
200 | rect->setFillOpacity(OpacityAlpha(0, 0)); | |
201 | 201 | actions.svgTree().appendToPage(std::move(rect)); |
202 | 202 | } |
203 | 203 | } |
51 | 51 | void checkNewLine (SpecialActions &actions); |
52 | 52 | void createLink (std::string uri, SpecialActions &actions); |
53 | 53 | void createViews (unsigned pageno, SpecialActions &actions); |
54 | void setBaseUrl (std::string &base) {_base = base;} | |
54 | void setBaseUrl (const std::string &base) {_base = base;} | |
55 | 55 | void setLineWidth (double w) {_linewidth = w;} |
56 | 56 | static HyperlinkManager& instance (); |
57 | 57 | static bool setLinkMarker (const std::string &marker); |
56 | 56 | void setY (double y) override {_y = y; _svg.setY(y);} |
57 | 57 | void finishLine () override {} |
58 | 58 | void setColor (const Color &color) override {_svg.setColor(color);} |
59 | void setOpacity (const Opacity &opacity) override {_svg.setOpacity(opacity);} | |
59 | 60 | Color getColor () const override {return _svg.getColor();} |
60 | 61 | void setMatrix (const Matrix &m) override {_svg.setMatrix(m);} |
61 | 62 | const Matrix& getMatrix () const override {return _svg.getMatrix();} |
63 | const Opacity& getOpacity () const override {return _svg.getOpacity();} | |
62 | 64 | const SVGTree& svgTree () const override {return _svg;} |
63 | 65 | void setBgColor (const Color &color) override {} |
64 | 66 | void embed (const BoundingBox &bbox) override {_bbox.embed(bbox);} |
39 | 39 | class StreamInputBuffer : public InputBuffer { |
40 | 40 | public: |
41 | 41 | explicit StreamInputBuffer (std::istream &is, size_t bufsize=1024); |
42 | StreamInputBuffer (const StreamInputBuffer &ib) =delete; | |
42 | 43 | ~StreamInputBuffer () override; |
43 | 44 | int get () override; |
44 | 45 | int peek () const override; |
45 | 46 | int peek (size_t n) const override; |
46 | 47 | bool eof () const override {return pos() == _size1 && _size2 == 0;} |
47 | 48 | void invalidate () override {_bufptr = _buf1+_size1; _size2 = 0;} |
49 | void operator = (const StreamInputBuffer &ib) =delete; | |
48 | 50 | |
49 | 51 | protected: |
50 | 52 | int fillBuffer (uint8_t *buf); |
64 | 66 | class StringInputBuffer : public InputBuffer { |
65 | 67 | public: |
66 | 68 | explicit StringInputBuffer (const std::string &str) : _str(&str) {} |
69 | StringInputBuffer (const StreamInputBuffer &ib) =delete; | |
67 | 70 | void assign (const std::string &str) {_str = &str; _pos=0;} |
68 | 71 | int get () override {return _pos < _str->length() ? _str->at(_pos++) : -1;} |
69 | 72 | int peek () const override {return _pos < _str->length() ? _str->at(_pos) : -1;} |
80 | 83 | class CharInputBuffer : public InputBuffer { |
81 | 84 | public: |
82 | 85 | CharInputBuffer (const char *buf, size_t size) : _pos(buf), _size(buf ? size : 0) {} |
86 | CharInputBuffer (const CharInputBuffer &ib) =delete; | |
83 | 87 | |
84 | 88 | int get () override { |
85 | 89 | if (_size == 0) |
111 | 115 | class SplittedCharInputBuffer : public InputBuffer { |
112 | 116 | public: |
113 | 117 | SplittedCharInputBuffer (const char *buf1, size_t s1, const char *buf2, size_t s2); |
118 | SplittedCharInputBuffer (const SplittedCharInputBuffer &ib) =delete; | |
114 | 119 | int get () override; |
115 | 120 | int peek () const override; |
116 | 121 | int peek (size_t n) const override; |
28 | 28 | public: |
29 | 29 | explicit JFM (std::istream &is); |
30 | 30 | bool verticalLayout () const override {return _vertical;} |
31 | bool isJFM () const override {return true;} | |
31 | 32 | uint32_t minChar () const {return _minchar;} |
32 | 33 | uint32_t maxChar () const {return static_cast<uint32_t>(_minchar+_charTypeTable.size()-1);} |
33 | 34 |
103 | 103 | MetafontWrapper.hpp MetafontWrapper.cpp \ |
104 | 104 | NoPsSpecialHandler.hpp NoPsSpecialHandler.cpp \ |
105 | 105 | NumericRanges.hpp \ |
106 | Opacity.hpp Opacity.cpp \ | |
106 | 107 | PageRanges.hpp PageRanges.cpp \ |
107 | 108 | PageSize.hpp PageSize.cpp \ |
108 | 109 | Pair.hpp \ |
133 | 134 | SVGCharHandlerFactory.hpp SVGCharHandlerFactory.cpp \ |
134 | 135 | SVGCharPathHandler.hpp SVGCharPathHandler.cpp \ |
135 | 136 | SVGCharTspanTextHandler.hpp SVGCharTspanTextHandler.cpp \ |
137 | SVGElement.hpp SVGElement.cpp \ | |
136 | 138 | SVGOutput.hpp SVGOutput.cpp \ |
137 | 139 | SVGSingleCharTextHandler.hpp SVGSingleCharTextHandler.cpp \ |
138 | 140 | SVGTree.hpp SVGTree.cpp \ |
519 | 519 | vector<double> params; |
520 | 520 | if (parse_transform_cmd(iss, "matrix", 6, 6, params)) { |
521 | 521 | 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)) |
522 | matrix.rmultiply({params[0], params[2], params[4], params[1], params[3], params[5]}); | |
522 | matrix.rmultiply(Matrix{params[0], params[2], params[4], params[1], params[3], params[5]}); | |
523 | 523 | } |
524 | 524 | else if (parse_transform_cmd(iss, "rotate", 1, 3, params)) { |
525 | 525 | if (params.size() == 1) { |
39 | 39 | friend double det (const Matrix &m, int row, int col); |
40 | 40 | |
41 | 41 | public: |
42 | Matrix () {set(0);} | |
43 | Matrix (double d); | |
42 | 44 | Matrix (const std::string &cmds, Calculator &calc); |
43 | Matrix (double d=0); | |
44 | 45 | explicit Matrix (const double *v, unsigned size=9); |
45 | 46 | explicit Matrix (const std::vector<double> &v, int start=0); |
46 | Matrix (std::initializer_list<double> initlist); | |
47 | explicit Matrix (std::initializer_list<double> initlist); | |
47 | 48 | Matrix& set (double d); |
48 | 49 | Matrix& set (const double *v, unsigned size); |
49 | 50 | Matrix& set (const std::vector<double> &v, int start=0); |
73 | 73 | _col++; |
74 | 74 | } |
75 | 75 | _nl = false; |
76 | if (!_nl || c != '\n') | |
76 | if (c != '\n') | |
77 | 77 | os << c; |
78 | 78 | } |
79 | 79 | } |
0 | /************************************************************************* | |
1 | ** Opacity.cpp ** | |
2 | ** ** | |
3 | ** This file is part of dvisvgm -- a fast DVI to SVG converter ** | |
4 | ** Copyright (C) 2005-2021 Martin Gieseking <martin.gieseking@uos.de> ** | |
5 | ** ** | |
6 | ** This program is free software; you can redistribute it and/or ** | |
7 | ** modify it under the terms of the GNU General Public License as ** | |
8 | ** published by the Free Software Foundation; either version 3 of ** | |
9 | ** the License, or (at your option) any later version. ** | |
10 | ** ** | |
11 | ** This program is distributed in the hope that it will be useful, but ** | |
12 | ** WITHOUT ANY WARRANTY; without even the implied warranty of ** | |
13 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** | |
14 | ** GNU General Public License for more details. ** | |
15 | ** ** | |
16 | ** You should have received a copy of the GNU General Public License ** | |
17 | ** along with this program; if not, see <http://www.gnu.org/licenses/>. ** | |
18 | *************************************************************************/ | |
19 | ||
20 | #include "Opacity.hpp" | |
21 | ||
22 | using namespace std; | |
23 | ||
24 | string Opacity::cssBlendMode (BlendMode bm) { | |
25 | switch (bm) { | |
26 | case BM_NORMAL : return "normal"; | |
27 | case BM_MULTIPLY : return "multiply"; | |
28 | case BM_SCREEN : return "screen"; | |
29 | case BM_OVERLAY : return "overlay"; | |
30 | case BM_SOFTLIGHT : return "soft-light"; | |
31 | case BM_HARDLIGHT : return "hard-light"; | |
32 | case BM_COLORDODGE: return "color-dodge"; | |
33 | case BM_COLORBURN : return "color-burn"; | |
34 | case BM_DARKEN : return "darken"; | |
35 | case BM_LIGHTEN : return "lighten"; | |
36 | case BM_DIFFERENCE: return "difference"; | |
37 | case BM_EXCLUSION : return "exclusion"; | |
38 | case BM_HUE : return "hue"; | |
39 | case BM_SATURATION: return "saturation"; | |
40 | case BM_COLOR : return "color"; | |
41 | case BM_LUMINOSITY: return "luminosity"; | |
42 | } | |
43 | return ""; | |
44 | } | |
45 | ||
46 | ||
47 | bool Opacity::operator == (const Opacity &opacity) const { | |
48 | return opacity._fillalpha == _fillalpha | |
49 | && opacity._strokealpha == _strokealpha | |
50 | && opacity._blendMode == _blendMode; | |
51 | } | |
52 | ||
53 | ||
54 | bool Opacity::operator != (const Opacity &opacity) const { | |
55 | return opacity._fillalpha != _fillalpha | |
56 | || opacity._strokealpha != _strokealpha | |
57 | || opacity._blendMode != _blendMode; | |
58 | }⏎ |
0 | /************************************************************************* | |
1 | ** Opacity.hpp ** | |
2 | ** ** | |
3 | ** This file is part of dvisvgm -- a fast DVI to SVG converter ** | |
4 | ** Copyright (C) 2005-2021 Martin Gieseking <martin.gieseking@uos.de> ** | |
5 | ** ** | |
6 | ** This program is free software; you can redistribute it and/or ** | |
7 | ** modify it under the terms of the GNU General Public License as ** | |
8 | ** published by the Free Software Foundation; either version 3 of ** | |
9 | ** the License, or (at your option) any later version. ** | |
10 | ** ** | |
11 | ** This program is distributed in the hope that it will be useful, but ** | |
12 | ** WITHOUT ANY WARRANTY; without even the implied warranty of ** | |
13 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** | |
14 | ** GNU General Public License for more details. ** | |
15 | ** ** | |
16 | ** You should have received a copy of the GNU General Public License ** | |
17 | ** along with this program; if not, see <http://www.gnu.org/licenses/>. ** | |
18 | *************************************************************************/ | |
19 | ||
20 | #ifndef OPACITY_HPP | |
21 | #define OPACITY_HPP | |
22 | ||
23 | #include <string> | |
24 | ||
25 | class OpacityAlpha { | |
26 | public: | |
27 | OpacityAlpha () =default; | |
28 | OpacityAlpha (double constalpha, double shapealpha) : _constalpha(constalpha), _shapealpha(shapealpha) {} | |
29 | void setConstAlpha (double alpha) { _constalpha = alpha;} | |
30 | void setShapeAlpha (double shapealpha) {_shapealpha = shapealpha;} | |
31 | double value () const {return _constalpha * _shapealpha;} | |
32 | bool operator == (const OpacityAlpha &alpha) const {return alpha._constalpha == _constalpha && alpha._shapealpha == _shapealpha;} | |
33 | bool operator != (const OpacityAlpha &alpha) const {return alpha._constalpha != _constalpha || alpha._shapealpha != _shapealpha;} | |
34 | bool isOpaque () const {return _constalpha == 1.0 && _shapealpha == 1.0;} | |
35 | ||
36 | private: | |
37 | double _constalpha=1.0; | |
38 | double _shapealpha=1.0; | |
39 | }; | |
40 | ||
41 | class Opacity { | |
42 | public: | |
43 | enum BlendMode { | |
44 | BM_NORMAL, BM_MULTIPLY, BM_SCREEN, BM_OVERLAY, | |
45 | BM_SOFTLIGHT, BM_HARDLIGHT, BM_COLORDODGE, BM_COLORBURN, | |
46 | BM_DARKEN, BM_LIGHTEN, BM_DIFFERENCE, BM_EXCLUSION, | |
47 | BM_HUE, BM_SATURATION, BM_COLOR, BM_LUMINOSITY | |
48 | }; | |
49 | ||
50 | public: | |
51 | Opacity () =default; | |
52 | Opacity (OpacityAlpha fillalpha, OpacityAlpha strokealpha, BlendMode bm) : _fillalpha(fillalpha), _strokealpha(strokealpha), _blendMode(bm) {} | |
53 | Opacity (OpacityAlpha fillalpha, OpacityAlpha strokealpha) : Opacity(fillalpha, strokealpha, BM_NORMAL) {} | |
54 | explicit Opacity (BlendMode bm) : _blendMode(bm) {} | |
55 | OpacityAlpha& fillalpha () {return _fillalpha;} | |
56 | OpacityAlpha& strokealpha () {return _strokealpha;} | |
57 | const OpacityAlpha& fillalpha () const {return _fillalpha;} | |
58 | const OpacityAlpha& strokealpha () const {return _strokealpha;} | |
59 | BlendMode blendMode () const {return _blendMode;} | |
60 | void setBlendMode (BlendMode mode) {_blendMode = mode;} | |
61 | std::string cssBlendMode () const {return cssBlendMode(_blendMode);} | |
62 | static std::string cssBlendMode (BlendMode bm); | |
63 | bool isFillDefault () const {return _fillalpha.isOpaque() && _blendMode == BM_NORMAL;} | |
64 | bool isStrokeDefault () const {return _strokealpha.isOpaque() && _blendMode == BM_NORMAL;} | |
65 | bool operator == (const Opacity &opacity) const; | |
66 | bool operator != (const Opacity &opacity) const; | |
67 | ||
68 | private: | |
69 | OpacityAlpha _fillalpha; | |
70 | OpacityAlpha _strokealpha; | |
71 | BlendMode _blendMode=BM_NORMAL; | |
72 | }; | |
73 | ||
74 | #endif |
58 | 58 | // in conjunction with -dDELAYBIND and -dWRITESYSTEMDICT. |
59 | 59 | // Thus, -dDELAYSAFER (or -dNOSAFER) must be added. |
60 | 60 | // https://www.ghostscript.com/doc/9.50/Use.htm#Safer |
61 | if (gsrev >= 950) | |
61 | if (gsrev >= 950) { | |
62 | 62 | gsargs.emplace_back("-dDELAYSAFER"); |
63 | gsargs.emplace_back("-dALLOWPSTRANSPARENCY"); | |
64 | } | |
63 | 65 | } |
64 | 66 | _gs.init(gsargs.size(), gsargs.data(), this); |
65 | 67 | _gs.set_stdio(input, output, error); |
129 | 131 | // feed Ghostscript with code chunks that are not larger than 64KB |
130 | 132 | // => see documentation of gsapi_run_string_foo() |
131 | 133 | const char *p=str; |
132 | while (PS_RUNNING && len > 0) { | |
134 | while (_mode == PS_RUNNING && len > 0) { | |
133 | 135 | SignalHandler::instance().check(); |
134 | 136 | size_t chunksize = min(len, (size_t)0xffff); |
135 | 137 | _gs.run_string_continue(p, chunksize, 0, &status); |
278 | 280 | {"rotate", { 1, &PSActions::rotate}}, |
279 | 281 | {"save", { 1, &PSActions::save}}, |
280 | 282 | {"scale", { 2, &PSActions::scale}}, |
283 | {"setalphaisshape", { 1, &PSActions::setalphaisshape}}, | |
281 | 284 | {"setblendmode", { 1, &PSActions::setblendmode}}, |
282 | 285 | {"setcolorspace", { 1, &PSActions::setcolorspace}}, |
283 | 286 | {"setcmykcolor", { 4, &PSActions::setcmykcolor}}, |
285 | 288 | {"setfillconstantalpha", { 1, &PSActions::setfillconstantalpha}}, |
286 | 289 | {"setgray", { 1, &PSActions::setgray}}, |
287 | 290 | {"sethsbcolor", { 3, &PSActions::sethsbcolor}}, |
288 | {"setisshapealpha", { 1, &PSActions::setisshapealpha}}, | |
289 | 291 | {"setlinecap", { 1, &PSActions::setlinecap}}, |
290 | 292 | {"setlinejoin", { 1, &PSActions::setlinejoin}}, |
291 | 293 | {"setlinewidth", { 1, &PSActions::setlinewidth}}, |
308 | 310 | _rawData.clear(); |
309 | 311 | in.skipSpace(); |
310 | 312 | while (!in.eof()) { |
311 | _rawData.emplace_back(in.getString()); | |
313 | _rawData.push_back(in.getString()); | |
312 | 314 | in.skipSpace(); |
313 | 315 | } |
314 | 316 | } |
319 | 321 | if (pcount < 0) { // variable number of parameters? |
320 | 322 | in.skipSpace(); |
321 | 323 | while (!in.eof()) { // read all available parameters |
322 | params.emplace_back(in.getString()); | |
324 | params.push_back(in.getString()); | |
323 | 325 | in.skipSpace(); |
324 | 326 | } |
325 | 327 | } |
326 | 328 | else { // fix number of parameters |
327 | 329 | for (int i=0; i < pcount; i++) { |
328 | 330 | in.skipSpace(); |
329 | params.emplace_back(in.getString()); | |
331 | params.push_back(in.getString()); | |
330 | 332 | } |
331 | 333 | } |
332 | 334 | // convert parameter strings to doubles |
414 | 416 | return false; |
415 | 417 | deviceStr = deviceStr.substr(0, deviceStr.find(':')); // strip optional argument |
416 | 418 | auto infos = getImageDeviceInfos(); |
417 | auto it = find_if(infos.begin(), infos.end(), [&](PSDeviceInfo &info) { | |
419 | auto it = find_if(infos.begin(), infos.end(), [&](const PSDeviceInfo &info) { | |
418 | 420 | return info.name == deviceStr; |
419 | 421 | }); |
420 | 422 | return it != infos.end(); |
61 | 61 | virtual void rotate (std::vector<double> &p) =0; |
62 | 62 | virtual void save (std::vector<double> &p) =0; |
63 | 63 | virtual void scale (std::vector<double> &p) =0; |
64 | virtual void setalphaisshape (std::vector<double> &p) =0; | |
64 | 65 | virtual void setblendmode (std::vector<double> &p) =0; |
65 | 66 | virtual void setcolorspace (std::vector<double> &p) =0; |
66 | 67 | virtual void setcmykcolor (std::vector<double> &cmyk) =0; |
68 | 69 | virtual void setfillconstantalpha (std::vector<double> &p) =0; |
69 | 70 | virtual void setgray (std::vector<double> &p) =0; |
70 | 71 | virtual void sethsbcolor (std::vector<double> &hsb) =0; |
71 | virtual void setisshapealpha (std::vector<double> &p) =0; | |
72 | 72 | virtual void setlinecap (std::vector<double> &p) =0; |
73 | 73 | virtual void setlinejoin (std::vector<double> &p) =0; |
74 | 74 | virtual void setlinewidth (std::vector<double> &p) =0; |
113 | 113 | BoundingBox pdfPageBox (const std::string &fname, int pageno); |
114 | 114 | const std::vector<std::string>& rawData () const {return _rawData;} |
115 | 115 | bool setImageDevice (const std::string &deviceStr); |
116 | bool hasFullOpacitySupport () const {return _gs.revision() >= 952;} | |
116 | 117 | static std::vector<PSDeviceInfo> getImageDeviceInfos (); |
117 | 118 | static void listImageDeviceInfos (std::ostream &os); |
118 | 119 | static bool imageDeviceKnown (std::string deviceStr); |
105 | 105 | * define the pattern graphic. */ |
106 | 106 | unique_ptr<XMLElement> PSTilingPattern::createGroupNode () const { |
107 | 107 | // add all succeeding path elements to this group |
108 | auto group = util::make_unique<XMLElement>("g"); | |
109 | group->addAttribute("clip-path", XMLString("url(#pc")+XMLString(psID())+")"); | |
108 | auto group = util::make_unique<SVGElement>("g"); | |
109 | group->setClipPathUrl("pc"+XMLString(psID())); | |
110 | 110 | return group; |
111 | 111 | } |
112 | 112 |
39 | 39 | virtual int psID () const {return _id;} |
40 | 40 | virtual std::string svgID () const; |
41 | 41 | virtual void apply (SpecialActions &actions); |
42 | virtual void setColor (Color color) {} | |
43 | virtual bool tiled () const =0; | |
42 | 44 | |
43 | 45 | protected: |
44 | 46 | explicit PSPattern (int id) : _id(id) {} |
53 | 55 | public: |
54 | 56 | virtual XMLElement* getContainerNode () {return _groupNode.get();} |
55 | 57 | void apply (SpecialActions &actions) override; |
58 | bool tiled () const override {return true;} | |
59 | ||
56 | 60 | |
57 | 61 | protected: |
58 | 62 | PSTilingPattern (int id, BoundingBox &bbox, Matrix &matrix, double xstep, double ystep); |
81 | 85 | public: |
82 | 86 | PSUncoloredTilingPattern (int id, BoundingBox &bbox, Matrix &matrix, double xstep, double ystep); |
83 | 87 | std::string svgID () const override; |
84 | void setColor (Color color) {_currentColor = color;} | |
88 | void setColor (Color color) override {_currentColor = color;} | |
85 | 89 | void apply (SpecialActions &actions) override; |
86 | 90 | |
87 | 91 | protected: |
57 | 57 | if (!_pageSizes.empty() && _pageSizes.back().first == pageno) |
58 | 58 | _pageSizes.back().second = whpair; |
59 | 59 | else |
60 | _pageSizes.emplace_back(PageSize(pageno, whpair)); | |
60 | _pageSizes.emplace_back(pageno, whpair); | |
61 | 61 | } |
62 | 62 | } |
63 | 63 |
94 | 94 | void PdfSpecialHandler::preprocessPagesize (StreamInputReader &ir, SpecialActions &actions) { |
95 | 95 | // add page sizes to collection of paper sizes in order to handle them equally |
96 | 96 | SpecialHandler *handler = SpecialManager::instance().findHandlerByName("papersize"); |
97 | if (auto papersizeHandler = dynamic_cast<PapersizeSpecialHandler*>(handler)) { | |
97 | if (auto papersizeHandler = static_cast<PapersizeSpecialHandler*>(handler)) { | |
98 | 98 | try { |
99 | 99 | Length width, height; |
100 | 100 | // parse parameter sequence of the form (name length)+ |
31 | 31 | #include "PSPreviewFilter.hpp" |
32 | 32 | #include "PsSpecialHandler.hpp" |
33 | 33 | #include "SpecialActions.hpp" |
34 | #include "SVGTree.hpp" | |
34 | #include "SVGElement.hpp" | |
35 | 35 | #include "TensorProductPatch.hpp" |
36 | 36 | #include "TriangularPatch.hpp" |
37 | 37 | #include "utility.hpp" |
80 | 80 | _linecap = _linejoin = 0; // butt end caps and miter joins |
81 | 81 | _miterlimit = 4; |
82 | 82 | _xmlnode = _savenode = nullptr; |
83 | _isshapealpha = false; // opacity operators change constant component by default | |
84 | _fillalpha = _strokealpha = {1, 1}; // set constant and shape opacity to non-transparent | |
85 | _blendmode = 0; // "normal" mode (no blending) | |
83 | _isshapealpha = false; // opacity operators change constant component by default | |
84 | _opacity = Opacity(); | |
86 | 85 | _sx = _sy = _cos = 1.0; |
87 | 86 | _pattern = nullptr; |
87 | _makingPattern = false; | |
88 | 88 | _patternEnabled = false; |
89 | 89 | _currentcolor = Color::BLACK; |
90 | 90 | _dashoffset = 0; |
286 | 286 | return true; |
287 | 287 | } |
288 | 288 | |
289 | ||
290 | 289 | /** Handles a psfile/pdffile special which places an external EPS/PDF graphic |
291 | 290 | * at the current DVI position. The lower left corner (llx,lly) of the |
292 | 291 | * given bounding box is placed at the DVI position. |
365 | 364 | matrix.scale(sx, sy).rotate(-angle).scale(hscale/100, vscale/100); // apply transformation attributes |
366 | 365 | matrix.translate(x+hoffset, y-voffset); // move image to current DVI position |
367 | 366 | matrix.lmultiply(_actions->getMatrix()); |
368 | ||
369 | 367 | // update bounding box |
370 | 368 | BoundingBox bbox(0, 0, urx-llx, ury-lly); |
371 | 369 | bbox.transform(matrix); |
372 | 370 | _actions->embed(bbox); |
373 | 371 | |
374 | 372 | // insert element containing the image data |
375 | matrix.rmultiply(TranslationMatrix(-llx, -lly)); // move lower left corner of image to origin | |
376 | if (!matrix.isIdentity()) | |
377 | imgNode->addAttribute("transform", matrix.toSVG()); | |
373 | if (filetype != FileType::PDF) | |
374 | matrix.rmultiply(TranslationMatrix(-llx, -lly)); // move lower left corner of image to origin | |
375 | imgNode->setTransform(matrix); | |
378 | 376 | _actions->svgTree().appendToPage(std::move(imgNode)); |
379 | 377 | } |
380 | 378 | // restore DVI position |
385 | 383 | |
386 | 384 | |
387 | 385 | /** Returns path + basename of temporary bitmap images. */ |
388 | static string image_base_path (SpecialActions &actions) { | |
386 | static string image_base_path (const SpecialActions &actions) { | |
389 | 387 | FilePath imgpath = actions.getSVGFilePath(actions.getCurrentPageNumber()); |
390 | 388 | return FileSystem::tmpdir() + "/" + imgpath.basename() + "-tmp-"; |
391 | 389 | } |
398 | 396 | * @param[in] bbox bounding box of the image |
399 | 397 | * @param[in] clip if true, the image is clipped to its bounding box |
400 | 398 | * @return pointer to the element or nullptr if there's no image data */ |
401 | unique_ptr<XMLElement> PsSpecialHandler::createImageNode (FileType type, const string &fname, int pageno, BoundingBox bbox, bool clip) { | |
402 | unique_ptr<XMLElement> node; | |
399 | unique_ptr<SVGElement> PsSpecialHandler::createImageNode (FileType type, const string &fname, int pageno, BoundingBox bbox, bool clip) { | |
400 | unique_ptr<SVGElement> node; | |
403 | 401 | string pathstr; |
404 | 402 | if (const char *path = FileFinder::instance().lookup(fname, false)) |
405 | 403 | pathstr = FileSystem::ensureForwardSlashes(path); |
408 | 406 | if (pathstr.empty()) |
409 | 407 | Message::wstream(true) << "file '" << fname << "' not found\n"; |
410 | 408 | else if (type == FileType::BITMAP || type == FileType::SVG) { |
411 | node = util::make_unique<XMLElement>("image"); | |
409 | node = util::make_unique<SVGElement>("image"); | |
412 | 410 | node->addAttribute("x", 0); |
413 | 411 | node->addAttribute("y", 0); |
414 | 412 | node->addAttribute("width", bbox.width()); |
428 | 426 | if (clip) |
429 | 427 | rectclip = to_string(bbox.minX())+" "+to_string(bbox.minY())+" "+to_string(bbox.width())+" "+to_string(bbox.height())+" rectclip"; |
430 | 428 | |
431 | node = util::make_unique<XMLElement>("g"); // put SVG nodes created from the EPS/PDF file in this group | |
429 | node = util::make_unique<SVGElement>("g"); // put SVG nodes created from the EPS/PDF file in this group | |
432 | 430 | _xmlnode = node.get(); |
433 | 431 | _psi.execute( |
434 | 432 | "\n@beginspecial @setspecial" // enter special environment |
555 | 553 | _linewidth = 1; |
556 | 554 | _linecap = _linejoin = 0; // butt end caps and miter joins |
557 | 555 | _miterlimit = 4; |
558 | _isshapealpha = false; // opacity operators change constant component by default | |
559 | _fillalpha = _strokealpha = {1, 1}; // set constant and shape opacity to non-transparent | |
560 | _blendmode = 0; // "normal" mode (no blending) | |
556 | _isshapealpha = false; // opacity operators change constant component by default | |
557 | _opacity = Opacity(); | |
561 | 558 | _sx = _sy = _cos = 1.0; |
562 | 559 | _pattern = nullptr; |
563 | 560 | _currentcolor = Color::BLACK; |
612 | 609 | } |
613 | 610 | |
614 | 611 | |
615 | static string css_blendmode_name (int mode) { | |
616 | static const array<const char*,16> modenames = {{ | |
617 | "normal", "multiply", "screen", "overlay", "soft-light", "hard-light", "color-dodge", "color-burn", | |
618 | "darken", "lighten", "difference", "exclusion", "hue", "saturation", "color", "luminosity" | |
619 | }}; | |
612 | void PsSpecialHandler::setblendmode (vector<double> &p) { | |
613 | int mode = static_cast<int>(p[0]); | |
614 | static const Opacity::BlendMode blendmodes[] = { | |
615 | Opacity::BM_NORMAL, Opacity::BM_MULTIPLY, Opacity::BM_SCREEN, Opacity::BM_OVERLAY, | |
616 | Opacity::BM_SOFTLIGHT, Opacity::BM_HARDLIGHT, Opacity::BM_COLORDODGE, Opacity::BM_COLORBURN, | |
617 | Opacity::BM_DARKEN, Opacity::BM_LIGHTEN, Opacity::BM_DIFFERENCE, Opacity::BM_EXCLUSION, | |
618 | Opacity::BM_HUE, Opacity::BM_SATURATION, Opacity::BM_COLOR, Opacity::BM_LUMINOSITY | |
619 | }; | |
620 | 620 | if (mode < 0 || mode > 15) |
621 | return ""; | |
622 | return modenames[mode]; | |
621 | mode = Opacity::BM_NORMAL; | |
622 | _opacity.setBlendMode(blendmodes[mode]); | |
623 | } | |
624 | ||
625 | ||
626 | void PsSpecialHandler::setfillconstantalpha (vector<double> &p) { | |
627 | if (_isshapealpha) | |
628 | _opacity.fillalpha().setShapeAlpha(p[0]); | |
629 | else | |
630 | _opacity.fillalpha().setConstAlpha(p[0]); | |
631 | if (_actions) | |
632 | _actions->setOpacity(_opacity); | |
633 | } | |
634 | ||
635 | ||
636 | void PsSpecialHandler::setstrokeconstantalpha (vector<double> &p) { | |
637 | if (_isshapealpha) | |
638 | _opacity.strokealpha().setShapeAlpha(p[0]); | |
639 | else | |
640 | _opacity.strokealpha().setConstAlpha(p[0]); | |
641 | if (_actions) | |
642 | _actions->setOpacity(_opacity); | |
623 | 643 | } |
624 | 644 | |
625 | 645 | |
638 | 658 | } |
639 | 659 | if (_clipStack.prependedPath()) |
640 | 660 | _path.prepend(*_clipStack.prependedPath()); |
641 | unique_ptr<XMLElement> path; | |
661 | unique_ptr<SVGElement> path; | |
642 | 662 | Pair<double> point; |
643 | 663 | if (_path.isDot(point)) { // zero-length path? |
644 | 664 | if (_linecap == 1) { // round line ends? => draw dot |
645 | 665 | double x = point.x(); |
646 | 666 | double y = point.y(); |
647 | 667 | double r = _linewidth/2.0; |
648 | path = util::make_unique<XMLElement>("circle"); | |
668 | path = util::make_unique<SVGElement>("circle"); | |
649 | 669 | path->addAttribute("cx", x); |
650 | 670 | path->addAttribute("cy", y); |
651 | 671 | path->addAttribute("r", r); |
652 | path->addAttribute("fill", _actions->getColor().svgColorString()); | |
672 | path->setFillColor(_actions->getColor()); | |
653 | 673 | bbox = BoundingBox(x-r, y-r, x+r, y+r); |
654 | 674 | } |
655 | 675 | } |
660 | 680 | |
661 | 681 | ostringstream oss; |
662 | 682 | _path.writeSVG(oss, SVGTree::RELATIVE_PATH_CMDS); |
663 | path = util::make_unique<XMLElement>("path"); | |
683 | path = util::make_unique<SVGElement>("path"); | |
664 | 684 | path->addAttribute("d", oss.str()); |
665 | path->addAttribute("stroke", _actions->getColor().svgColorString()); | |
666 | path->addAttribute("fill", "none"); | |
667 | if (_linewidth != 1) | |
668 | path->addAttribute("stroke-width", _linewidth); | |
669 | if (_miterlimit != 4) | |
670 | path->addAttribute("stroke-miterlimit", _miterlimit); | |
671 | if (_linecap > 0) // default value is "butt", no need to set it explicitly | |
672 | path->addAttribute("stroke-linecap", _linecap == 1 ? "round" : "square"); | |
673 | if (_linejoin > 0) // default value is "miter", no need to set it explicitly | |
674 | path->addAttribute("stroke-linejoin", _linecap == 1 ? "round" : "bevel"); | |
675 | if (_strokealpha[0] < 1 || _strokealpha[1] < 1) | |
676 | path->addAttribute("stroke-opacity", _strokealpha[0] * _strokealpha[1]); | |
677 | if (_blendmode > 0 && _blendmode < 16) | |
678 | path->addAttribute("style", "mix-blend-mode:"+css_blendmode_name(_blendmode)); | |
679 | if (!_dashpattern.empty()) { | |
680 | string patternStr; | |
681 | for (double dashValue : _dashpattern) | |
682 | patternStr += XMLString(dashValue)+","; | |
683 | patternStr.pop_back(); | |
684 | path->addAttribute("stroke-dasharray", patternStr); | |
685 | if (_dashoffset != 0) | |
686 | path->addAttribute("stroke-dashoffset", _dashoffset); | |
687 | } | |
688 | } | |
689 | if (path && _clipStack.path() && !_savenode) { | |
685 | path->setStrokeColor(_actions->getColor()); | |
686 | path->setNoFillColor(); | |
687 | path->setStrokeWidth(_linewidth); | |
688 | path->setStrokeMiterLimit(_miterlimit); | |
689 | path->setStrokeLineCap(_linecap == 0 ? SVGElement::LC_BUTT : _linecap == 1 ? SVGElement::LC_ROUND : SVGElement::LC_SQUARE); | |
690 | path->setStrokeLineJoin(_linejoin == 0 ? SVGElement::LJ_MITER : _linecap == 1 ? SVGElement::LJ_ROUND : SVGElement::LJ_BEVEL); | |
691 | path->setStrokeOpacity(_opacity); | |
692 | path->setStrokeDash(_dashpattern, _dashoffset); | |
693 | } | |
694 | if (path && _clipStack.path() && !_makingPattern) { | |
690 | 695 | // assign clipping path and clip bounding box |
691 | path->addAttribute("clip-path", XMLString("url(#clip")+XMLString(_clipStack.topID())+")"); | |
696 | path->setClipPathUrl("clip"+XMLString(_clipStack.topID())); | |
692 | 697 | bbox.intersect(_clipStack.path()->computeBBox()); |
693 | 698 | _clipStack.removePrependedPath(); |
694 | 699 | } |
707 | 712 | * @param[in] evenodd true: use even-odd fill algorithm, false: use nonzero fill algorithm */ |
708 | 713 | void PsSpecialHandler::fill (vector<double> &p, bool evenodd) { |
709 | 714 | _path.removeRedundantCommands(); |
710 | if ((_path.empty() && !_clipStack.prependedPath()) || !_actions) | |
715 | if ((_path.empty() && !_clipStack.prependedPath()) || (_patternEnabled && !_pattern) || !_actions) | |
711 | 716 | return; |
712 | 717 | |
713 | 718 | // compute bounding box |
722 | 727 | |
723 | 728 | ostringstream oss; |
724 | 729 | _path.writeSVG(oss, SVGTree::RELATIVE_PATH_CMDS); |
725 | unique_ptr<XMLElement> path = util::make_unique<XMLElement>("path"); | |
730 | auto path = util::make_unique<SVGElement>("path"); | |
726 | 731 | path->addAttribute("d", oss.str()); |
727 | 732 | if (_pattern) |
728 | path->addAttribute("fill", XMLString("url(#")+_pattern->svgID()+")"); | |
729 | else if (_actions->getColor() != Color::BLACK || _savenode) | |
730 | path->addAttribute("fill", _actions->getColor().svgColorString()); | |
731 | if (_clipStack.path() && !_savenode) { // clip path active and not inside pattern definition? | |
733 | path->setFillPatternUrl(XMLString(_pattern->svgID())); | |
734 | else if (_actions->getColor() != Color::BLACK || _makingPattern) | |
735 | path->setFillColor(_actions->getColor(), false); | |
736 | if (_clipStack.path() && !_makingPattern) { // clip path active and not inside pattern definition? | |
732 | 737 | // assign clipping path and clip bounding box |
733 | path->addAttribute("clip-path", XMLString("url(#clip")+XMLString(_clipStack.topID())+")"); | |
738 | path->setClipPathUrl("clip"+XMLString(_clipStack.topID())); | |
734 | 739 | bbox.intersect(_clipStack.path()->computeBBox()); |
735 | 740 | _clipStack.removePrependedPath(); |
736 | 741 | } |
737 | if (evenodd) // SVG default fill rule is "nonzero" algorithm | |
738 | path->addAttribute("fill-rule", "evenodd"); | |
739 | if (_fillalpha[0] < 1 || _fillalpha[1] < 1) | |
740 | path->addAttribute("fill-opacity", _fillalpha[0] * _fillalpha[1]); | |
741 | if (_blendmode > 0 && _blendmode < 16) | |
742 | path->addAttribute("style", "mix-blend-mode:"+css_blendmode_name(_blendmode)); | |
742 | path->setFillRule(evenodd ? SVGElement::FR_EVENODD : SVGElement::FR_NONZERO); | |
743 | path->setFillOpacity(_opacity); | |
743 | 744 | if (_xmlnode) |
744 | 745 | _xmlnode->append(std::move(path)); |
745 | 746 | else { |
787 | 788 | |
788 | 789 | // if set, assign clipping path to image |
789 | 790 | if (_clipStack.path()) { |
790 | auto group = util::make_unique<XMLElement>("g"); | |
791 | group->addAttribute("clip-path", XMLString("url(#clip")+XMLString(_clipStack.topID())+")"); | |
791 | auto group = util::make_unique<SVGElement>("g"); | |
792 | group->setClipPathUrl("clip"+XMLString(_clipStack.topID())); | |
792 | 793 | group->append(std::move(image)); |
793 | 794 | image = std::move(group); // handle the entire group as image to add |
794 | 795 | } |
845 | 846 | switch (pattern_type) { |
846 | 847 | case 0: |
847 | 848 | // pattern definition completed |
848 | if (_savenode) { | |
849 | _xmlnode = _savenode; | |
850 | _savenode = nullptr; | |
851 | } | |
849 | _xmlnode = _savenode; | |
850 | _savenode = nullptr; | |
851 | _makingPattern = false; | |
852 | 852 | break; |
853 | 853 | case 1: { // tiling pattern |
854 | 854 | int id = static_cast<int>(p[1]); |
868 | 868 | _savenode = _xmlnode; |
869 | 869 | _xmlnode = pattern->getContainerNode(); // insert the following SVG elements into this node |
870 | 870 | _patterns[id] = std::move(pattern); |
871 | _makingPattern = true; | |
871 | 872 | break; |
872 | 873 | } |
873 | 874 | case 2: { |
890 | 891 | if (it == _patterns.end()) |
891 | 892 | _pattern = nullptr; |
892 | 893 | else { |
893 | if (auto pattern = dynamic_cast<PSUncoloredTilingPattern*>(it->second.get())) | |
894 | pattern->setColor(color); | |
894 | it->second->setColor(color); | |
895 | 895 | it->second->apply(*_actions); |
896 | if (auto pattern = dynamic_cast<PSTilingPattern*>(it->second.get())) | |
897 | _pattern = pattern; | |
898 | else | |
899 | _pattern = nullptr; | |
896 | _pattern = it->second->tiled() ? static_cast<PSTilingPattern*>(it->second.get()) : nullptr; | |
900 | 897 | } |
901 | 898 | } |
902 | 899 | |
960 | 957 | intersectedPath.writeSVG(oss, SVGTree::RELATIVE_PATH_CMDS); |
961 | 958 | } |
962 | 959 | if (pathReplaced) { |
963 | auto pathElem = util::make_unique<XMLElement>("path"); | |
960 | auto pathElem = util::make_unique<SVGElement>("path"); | |
964 | 961 | pathElem->addAttribute("d", oss.str()); |
965 | if (evenodd) | |
966 | pathElem->addAttribute("clip-rule", "evenodd"); | |
962 | pathElem->setClipRule(evenodd ? SVGElement::FR_EVENODD : SVGElement::FR_NONZERO); | |
967 | 963 | |
968 | 964 | int newID = _clipStack.topID(); |
969 | auto clipElem = util::make_unique<XMLElement>("clipPath"); | |
965 | auto clipElem = util::make_unique<SVGElement>("clipPath"); | |
970 | 966 | clipElem->addAttribute("id", XMLString("clip")+XMLString(newID)); |
971 | 967 | if (!COMPUTE_CLIPPATHS_INTERSECTIONS && oldID) |
972 | clipElem->addAttribute("clip-path", XMLString("url(#clip")+XMLString(oldID)+")"); | |
968 | clipElem->setClipPathUrl("clip"+XMLString(oldID)); | |
973 | 969 | |
974 | 970 | clipElem->append(std::move(pathElem)); |
975 | 971 | _actions->svgTree().appendToDefs(std::move(clipElem)); |
1045 | 1041 | * @param[in,out] it iterator used to sequentially access the patch data |
1046 | 1042 | * @param[out] points the points defining the geometry of the patch |
1047 | 1043 | * @param[out] colors the colors assigned to the vertices of the patch */ |
1048 | static void read_patch_data (ShadingPatch &patch, int edgeflag, | |
1044 | static void read_patch_data (const ShadingPatch &patch, int edgeflag, | |
1049 | 1045 | VectorIterator<double> &it, vector<DPair> &points, vector<Color> &colors) |
1050 | 1046 | { |
1051 | 1047 | // number of control points and colors required to define a single patch |
1086 | 1082 | ShadingCallback (SpecialActions &actions, XMLElement *parent, int clippathID) |
1087 | 1083 | : _actions(actions) |
1088 | 1084 | { |
1089 | auto group = util::make_unique<XMLElement>("g"); | |
1085 | auto group = util::make_unique<SVGElement>("g"); | |
1090 | 1086 | _group = group.get(); |
1091 | 1087 | if (parent) |
1092 | 1088 | parent->append(std::move(group)); |
1093 | 1089 | else |
1094 | 1090 | actions.svgTree().appendToPage(std::move(group)); |
1095 | 1091 | if (clippathID > 0) |
1096 | _group->addAttribute("clip-path", XMLString("url(#clip")+XMLString(clippathID)+")"); | |
1092 | _group->setClipPathUrl("clip"+XMLString(clippathID)); | |
1097 | 1093 | } |
1098 | 1094 | |
1099 | 1095 | void patchSegment (GraphicsPath<double> &path, const Color &color) override { |
1103 | 1099 | // draw a single patch segment |
1104 | 1100 | ostringstream oss; |
1105 | 1101 | path.writeSVG(oss, SVGTree::RELATIVE_PATH_CMDS); |
1106 | auto pathElem = util::make_unique<XMLElement>("path"); | |
1102 | auto pathElem = util::make_unique<SVGElement>("path"); | |
1107 | 1103 | pathElem->addAttribute("d", oss.str()); |
1108 | pathElem->addAttribute("fill", color.svgColorString()); | |
1104 | pathElem->setFillColor(color); | |
1109 | 1105 | _group->append(std::move(pathElem)); |
1110 | 1106 | } |
1111 | 1107 | |
1112 | 1108 | private: |
1113 | 1109 | SpecialActions &_actions; |
1114 | XMLElement *_group; | |
1110 | SVGElement *_group; | |
1115 | 1111 | }; |
1116 | 1112 | |
1117 | 1113 |
27 | 27 | #include <vector> |
28 | 28 | #include "GraphicsPath.hpp" |
29 | 29 | #include "PSInterpreter.hpp" |
30 | #include "SpecialHandler.hpp" | |
30 | #include "Opacity.hpp" | |
31 | 31 | #include "PSPattern.hpp" |
32 | 32 | #include "PSPreviewFilter.hpp" |
33 | ||
33 | #include "SpecialHandler.hpp" | |
34 | 34 | |
35 | 35 | class PSPattern; |
36 | class SVGElement; | |
36 | 37 | class XMLElement; |
37 | 38 | |
38 | 39 | class PsSpecialHandler : public SpecialHandler, protected PSActions { |
103 | 104 | void executeAndSync (std::istream &is, bool updatePos); |
104 | 105 | void processHeaderFile (const char *fname); |
105 | 106 | void imgfile (FileType type, const std::string &fname, const std::map<std::string,std::string> &attr); |
106 | std::unique_ptr<XMLElement> createImageNode (FileType type, const std::string &fname, int pageno, BoundingBox bbox, bool clip); | |
107 | std::unique_ptr<SVGElement> createImageNode (FileType type, const std::string &fname, int pageno, BoundingBox bbox, bool clip); | |
107 | 108 | void dviBeginPage (unsigned int pageno, SpecialActions &actions) override; |
108 | 109 | void dviEndPage (unsigned pageno, SpecialActions &actions) override; |
109 | 110 | void clip (Path path, bool evenodd); |
137 | 138 | void rotate (std::vector<double> &p) override; |
138 | 139 | void save (std::vector<double> &p) override; |
139 | 140 | void scale (std::vector<double> &p) override; |
140 | void setblendmode (std::vector<double> &p) override {_blendmode = int(p[0]);} | |
141 | void setalphaisshape (std::vector<double> &p) override { _isshapealpha = bool(p[0]);} | |
142 | void setblendmode (std::vector<double> &p) override; | |
141 | 143 | void setcolorspace (std::vector<double> &p) override {_patternEnabled = bool(p[0]);} |
142 | 144 | void setcmykcolor (std::vector<double> &cmyk) override; |
143 | 145 | void setdash (std::vector<double> &p) override; |
144 | void setfillconstantalpha (std::vector<double> &p) override {_fillalpha[_isshapealpha ? 1 : 0] = p[0];} | |
146 | void setfillconstantalpha (std::vector<double> &p) override; | |
145 | 147 | void setgray (std::vector<double> &p) override; |
146 | 148 | void sethsbcolor (std::vector<double> &hsb) override; |
147 | void setisshapealpha (std::vector<double> &p) override {_isshapealpha = bool(p[0]);} | |
148 | 149 | void setlinecap (std::vector<double> &p) override {_linecap = uint8_t(p[0]);} |
149 | 150 | void setlinejoin (std::vector<double> &p) override {_linejoin = uint8_t(p[0]);} |
150 | 151 | void setlinewidth (std::vector<double> &p) override {_linewidth = scale(p[0] ? p[0] : 0.5);} |
154 | 155 | void setpagedevice (std::vector<double> &p) override; |
155 | 156 | void setpattern (std::vector<double> &p) override; |
156 | 157 | void setrgbcolor (std::vector<double> &rgb) override; |
157 | void setstrokeconstantalpha (std::vector<double> &p) override {_strokealpha[_isshapealpha ? 1 : 0] = p[0];} | |
158 | void setstrokeconstantalpha (std::vector<double> &p) override; | |
158 | 159 | void shfill (std::vector<double> &p) override; |
159 | 160 | void stroke (std::vector<double> &p) override; |
160 | 161 | void translate (std::vector<double> &p) override; |
175 | 176 | double _cos; ///< cosine of angle between (1,0) and transform(1,0) |
176 | 177 | double _linewidth; ///< current line width in bp units |
177 | 178 | double _miterlimit; ///< current miter limit in bp units |
178 | bool _isshapealpha; ///< if true, opacity operators act on index 1 (shape component), otherwise on index 0 (constant component) | |
179 | std::array<double,2> _fillalpha; ///< constant and shape opacity used for fill operations (0=fully transparent, ..., 1=opaque) | |
180 | std::array<double,2> _strokealpha; ///< constant and shape opacity used for stroke operations (0=fully transparent, ..., 1=opaque) | |
181 | int _blendmode; ///< blend mode used when overlaying colored areas | |
179 | bool _isshapealpha; ///< if true, opacity operators act on shape component, otherwise on constant component | |
180 | Opacity _opacity; | |
182 | 181 | uint8_t _linecap : 2; ///< current line cap (0=butt, 1=round, 2=projecting square) |
183 | 182 | uint8_t _linejoin : 2; ///< current line join (0=miter, 1=round, 2=bevel) |
184 | 183 | double _dashoffset; ///< current dash offset |
185 | 184 | std::vector<double> _dashpattern; |
186 | 185 | ClippingStack _clipStack; |
186 | bool _makingPattern=false; ///< true if executing makepattern operator | |
187 | 187 | std::map<int, std::unique_ptr<PSPattern>> _patterns; |
188 | 188 | PSTilingPattern *_pattern; ///< current pattern |
189 | 189 | bool _patternEnabled; ///< true if active color space is a pattern |
81 | 81 | |
82 | 82 | Range range(cmin, cmax, vmin); |
83 | 83 | if (_ranges.empty()) |
84 | _ranges.emplace_back(std::move(range)); | |
84 | _ranges.push_back(std::move(range)); | |
85 | 85 | else { |
86 | 86 | // check for simple cases that can be handled pretty fast |
87 | 87 | Range &lrange = *_ranges.begin(); |
88 | 88 | Range &rrange = *_ranges.rbegin(); |
89 | 89 | if (cmin > rrange.max()) { // non-overlapping range at end of vector? |
90 | 90 | if (!rrange.join(range)) |
91 | _ranges.emplace_back(std::move(range)); | |
91 | _ranges.push_back(std::move(range)); | |
92 | 92 | } |
93 | 93 | else if (cmax < lrange.min()) { // non-overlapping range at begin of vector? |
94 | 94 | if (!lrange.join(range)) |
19 | 19 | |
20 | 20 | #include "SVGCharHandler.hpp" |
21 | 21 | #include "utility.hpp" |
22 | #include "XMLNode.hpp" | |
22 | #include "SVGElement.hpp" | |
23 | 23 | |
24 | 24 | using namespace std; |
25 | 25 | |
26 | 26 | |
27 | void SVGCharHandler::setInitialContextNode (XMLElement *node) { | |
27 | void SVGCharHandler::setInitialContextNode (SVGElement *node) { | |
28 | 28 | resetContextNode(); |
29 | 29 | _initialContextNode = node; |
30 | 30 | } |
33 | 33 | /** Changes the context element. All following nodes will be appended to this node. |
34 | 34 | * @param[in] node the new context node |
35 | 35 | * @return bare pointer to the new context node or 0 if context hasn't changed */ |
36 | XMLElement* SVGCharHandler::pushContextNode (unique_ptr<XMLElement> node) { | |
36 | SVGElement* SVGCharHandler::pushContextNode (unique_ptr<SVGElement> node) { | |
37 | 37 | if (node && (_contextNodeStack.empty() || node.get() != _contextNodeStack.top())) { |
38 | XMLElement *nodeptr = node.get(); | |
38 | SVGElement *nodeptr = node.get(); | |
39 | 39 | contextNode()->append(std::move(node)); |
40 | 40 | _contextNodeStack.push(nodeptr); |
41 | 41 | return nodeptr; |
60 | 60 | /** Creates and returns a new SVG text element. |
61 | 61 | * @param[in] x current x coordinate |
62 | 62 | * @param[in] y current y coordinate */ |
63 | unique_ptr<XMLElement> SVGCharTextHandler::createTextNode (double x, double y) const { | |
63 | unique_ptr<SVGElement> SVGCharTextHandler::createTextNode (double x, double y) const { | |
64 | 64 | const Font *font = _font.get(); |
65 | 65 | if (!font) |
66 | 66 | return nullptr; |
67 | auto textNode = util::make_unique<XMLElement>("text"); | |
67 | auto textNode = util::make_unique<SVGElement>("text"); | |
68 | 68 | if (_selectFontByClass) |
69 | 69 | textNode->addAttribute("class", string("f")+XMLString(_fontnum)); |
70 | 70 | else { |
71 | 71 | textNode->addAttribute("font-family", font->name()); |
72 | textNode->addAttribute("font-size", XMLString(font->scaledSize())); | |
73 | if (font->color() != Color::BLACK) | |
74 | textNode->addAttribute("fill", font->color().svgColorString()); | |
72 | textNode->addAttribute("font-size", font->scaledSize()); | |
73 | textNode->setFillColor(font->color()); | |
75 | 74 | } |
76 | 75 | if (_vertical) { |
77 | 76 | textNode->addAttribute("writing-mode", "tb"); |
78 | 77 | // align glyphs designed for horizontal layout properly |
79 | if (auto pf = dynamic_cast<const PhysicalFont*>(font)) { | |
78 | if (auto pf = font_cast<const PhysicalFont*>(font)) { | |
80 | 79 | if (!pf->getMetrics()->verticalLayout()) { // alphabetic text designed for horizontal layout? |
81 | 80 | x += pf->scaledAscent()/2.5; // move vertical baseline to the right by strikethrough offset |
82 | 81 | textNode->addAttribute("glyph-orientation-vertical", 90); // ensure rotation |
85 | 84 | } |
86 | 85 | textNode->addAttribute("x", x); |
87 | 86 | textNode->addAttribute("y", y); |
88 | if (!_matrix.get().isIdentity()) | |
89 | textNode->addAttribute("transform", _matrix.get().toSVG()); | |
87 | if (!_matrix->isIdentity()) | |
88 | textNode->addAttribute("transform", _matrix->toSVG()); | |
90 | 89 | return textNode; |
91 | 90 | } |
25 | 25 | #include "Color.hpp" |
26 | 26 | #include "Font.hpp" |
27 | 27 | #include "Matrix.hpp" |
28 | #include "Opacity.hpp" | |
28 | 29 | |
29 | 30 | |
30 | 31 | template <typename T> |
31 | 32 | class CharProperty { |
32 | 33 | public: |
34 | CharProperty () =default; | |
33 | 35 | CharProperty (const T &v) : _value(v) {} |
36 | CharProperty (T &&v) : _value(std::move(v)) {} | |
34 | 37 | |
35 | 38 | void set (const T &v) { |
36 | 39 | if (v != _value) { |
39 | 42 | } |
40 | 43 | } |
41 | 44 | |
42 | const T& get () const {return _value;} | |
43 | operator const T& () const {return _value;} | |
44 | bool changed () const {return _changed;} | |
45 | void changed (bool c) {_changed = c;} | |
45 | const T& get () const {return _value;} | |
46 | const T* operator -> () const {return &_value;} | |
47 | operator const T& () const {return _value;} | |
48 | bool changed () const {return _changed;} | |
49 | void changed (bool c) {_changed = c;} | |
46 | 50 | |
47 | 51 | private: |
48 | 52 | T _value; |
50 | 54 | }; |
51 | 55 | |
52 | 56 | |
53 | class XMLElement; | |
57 | class SVGElement; | |
54 | 58 | |
55 | 59 | |
56 | 60 | /** Base class for all character handlers. These handlers create SVG representations |
59 | 63 | public: |
60 | 64 | SVGCharHandler () : _matrix(1) {} |
61 | 65 | virtual ~SVGCharHandler() =default; |
62 | virtual void setInitialContextNode (XMLElement *node); | |
66 | virtual void setInitialContextNode (SVGElement *node); | |
63 | 67 | virtual void appendChar (uint32_t c, double x, double y) =0; |
64 | 68 | virtual void notifyXAdjusted () {} |
65 | 69 | virtual void notifyYAdjusted () {} |
66 | void setColor (const Color &color) {_color.set(color);} | |
67 | void setFont (const Font &font, int id) {_font.set(&font); _fontnum = id;} | |
68 | void setMatrix (const Matrix &matrix) {_matrix.set(matrix);} | |
69 | void setVertical (bool vertical) {_vertical.set(vertical);} | |
70 | Color getColor () const {return _color.get();} | |
71 | const Font* getFont () const {return _font.get();} | |
72 | const Matrix& getMatrix () const {return _matrix.get();} | |
70 | void setColor (const Color &color) {_color.set(color);} | |
71 | void setOpacity (const Opacity &opacity) {_opacity.set(opacity);} | |
72 | void setFont (const Font &font, int id) {_font.set(&font); _fontnum = id;} | |
73 | void setMatrix (const Matrix &matrix) {_matrix.set(matrix);} | |
74 | void setVertical (bool vertical) {_vertical.set(vertical);} | |
75 | Color getColor () const {return _color.get();} | |
76 | const Opacity& getOpacity () const {return _opacity.get();} | |
77 | const Font* getFont () const {return _font.get();} | |
78 | const Matrix& getMatrix () const {return _matrix.get();} | |
73 | 79 | |
74 | 80 | protected: |
75 | 81 | virtual void resetContextNode (); |
76 | XMLElement* pushContextNode (std::unique_ptr<XMLElement> node); | |
82 | SVGElement* pushContextNode (std::unique_ptr<SVGElement> node); | |
77 | 83 | void popContextNode (); |
78 | 84 | |
79 | XMLElement* contextNode () const { | |
85 | SVGElement* contextNode () const { | |
80 | 86 | return _contextNodeStack.empty() ? _initialContextNode : _contextNodeStack.top(); |
81 | 87 | } |
82 | 88 | |
83 | 89 | CharProperty<Color> _color=Color::BLACK; ///< current color |
90 | CharProperty<Opacity> _opacity; ///< current opacity values | |
84 | 91 | CharProperty<const Font*> _font=0; ///< current font |
85 | 92 | int _fontnum=0; ///< current font ID |
86 | 93 | CharProperty<Matrix> _matrix; ///< current transformation |
87 | 94 | CharProperty<bool> _vertical=false; ///< current writing mode |
88 | 95 | |
89 | 96 | private: |
90 | XMLElement *_initialContextNode= nullptr; ///< SVG element the generated character nodes are attached to | |
91 | std::stack<XMLElement*> _contextNodeStack; | |
97 | SVGElement *_initialContextNode= nullptr; ///< SVG element the generated character nodes are attached to | |
98 | std::stack<SVGElement*> _contextNodeStack; | |
92 | 99 | }; |
93 | 100 | |
94 | 101 | |
98 | 105 | explicit SVGCharTextHandler (bool selectFontByClass) : _selectFontByClass(selectFontByClass) {} |
99 | 106 | |
100 | 107 | protected: |
101 | std::unique_ptr<XMLElement> createTextNode (double x, double y) const; | |
108 | std::unique_ptr<SVGElement> createTextNode (double x, double y) const; | |
102 | 109 | |
103 | 110 | private: |
104 | 111 | bool _selectFontByClass; |
22 | 22 | #include "FontManager.hpp" |
23 | 23 | #include "SVGCharPathHandler.hpp" |
24 | 24 | #include "utility.hpp" |
25 | #include "XMLNode.hpp" | |
25 | #include "SVGElement.hpp" | |
26 | 26 | |
27 | 27 | using namespace std; |
28 | 28 | |
60 | 60 | // Glyphs of non-black fonts (e.g. defined in a XeTeX document) can't change their color. |
61 | 61 | CharProperty<Color> &color = (_fontColor.get() != Color::BLACK) ? _fontColor : _color; |
62 | 62 | bool applyColor = color.get() != Color::BLACK; |
63 | bool applyMatrix = !_matrix.get().isIdentity(); | |
63 | bool applyMatrix = !_matrix->isIdentity(); | |
64 | bool applyOpacity = !_opacity->isFillDefault(); | |
64 | 65 | if (!_groupNode) { |
65 | 66 | color.changed(applyColor); |
66 | 67 | _matrix.changed(applyMatrix); |
67 | 68 | } |
68 | if (color.changed() || _matrix.changed()) { | |
69 | if (color.changed() || _matrix.changed() || _opacity.changed()) { | |
69 | 70 | resetContextNode(); |
70 | if (applyColor || applyMatrix) { | |
71 | _groupNode = pushContextNode(util::make_unique<XMLElement>("g")); | |
72 | if (applyColor) | |
73 | contextNode()->addAttribute("fill", color.get().svgColorString()); | |
74 | if (applyMatrix) | |
75 | contextNode()->addAttribute("transform", _matrix.get().toSVG()); | |
71 | if (applyColor || applyMatrix || applyOpacity) { | |
72 | _groupNode = pushContextNode(util::make_unique<SVGElement>("g")); | |
73 | contextNode()->setFillColor(color); | |
74 | contextNode()->setFillOpacity(_opacity->fillalpha()); | |
75 | contextNode()->setTransform(_matrix); | |
76 | 76 | } |
77 | 77 | color.changed(false); |
78 | 78 | _matrix.changed(false); |
79 | _opacity.changed(false); | |
79 | 80 | } |
80 | 81 | const Font *font = _font.get(); |
81 | 82 | if (font->verticalLayout()) { |
83 | 84 | GlyphMetrics metrics; |
84 | 85 | font->getGlyphMetrics(c, _vertical, metrics); |
85 | 86 | x -= metrics.wl; |
86 | if (auto pf = dynamic_cast<const PhysicalFont*>(font)) { | |
87 | if (auto pf = font_cast<const PhysicalFont*>(font)) { | |
87 | 88 | // Center glyph between top and bottom border of the TFM box. |
88 | 89 | // This is just an approximation used until I find a way to compute |
89 | 90 | // the exact location in vertical mode. |
90 | 91 | GlyphMetrics exact_metrics; |
91 | pf->getExactGlyphBox(c, exact_metrics, false); | |
92 | pf->getExactGlyphBox(c, exact_metrics, false, nullptr); | |
92 | 93 | y += exact_metrics.h+(metrics.d-exact_metrics.h-exact_metrics.d)/2; |
93 | 94 | } |
94 | 95 | else |
108 | 109 | |
109 | 110 | void SVGCharPathHandler::appendUseElement (uint32_t c, double x, double y, const Matrix &matrix) { |
110 | 111 | string id = "#g" + to_string(FontManager::instance().fontID(_font)) + "-" + to_string(c); |
111 | auto useNode = util::make_unique<XMLElement>("use"); | |
112 | useNode->addAttribute("x", XMLString(x)); | |
113 | useNode->addAttribute("y", XMLString(y)); | |
112 | auto useNode = util::make_unique<SVGElement>("use"); | |
113 | useNode->addAttribute("x", x); | |
114 | useNode->addAttribute("y", y); | |
114 | 115 | useNode->addAttribute("xlink:href", id); |
115 | if (!matrix.isIdentity()) | |
116 | useNode->addAttribute("transform", matrix.toSVG()); | |
116 | useNode->setFillOpacity(_opacity->blendMode()); // add blend mode style here because it's not inheritable | |
117 | useNode->setTransform(matrix); | |
117 | 118 | contextNode()->append(std::move(useNode)); |
118 | 119 | } |
119 | 120 | |
120 | 121 | |
121 | 122 | void SVGCharPathHandler::appendPathElement (uint32_t c, double x, double y, const Matrix &matrix) { |
122 | 123 | Glyph glyph; |
123 | auto pf = dynamic_cast<const PhysicalFont*>(_font.get()); | |
124 | if (pf && pf->getGlyph(c, glyph)) { | |
124 | auto pf = font_cast<const PhysicalFont*>(_font.get()); | |
125 | if (pf && pf->getGlyph(c, glyph, nullptr)) { | |
125 | 126 | double sx = pf->scaledSize()/pf->unitsPerEm(); |
126 | 127 | double sy = -sx; |
127 | 128 | ostringstream oss; |
128 | 129 | glyph.writeSVG(oss, _relativePathCommands, sx, sy, x, y); |
129 | auto glyphNode = util::make_unique<XMLElement>("path"); | |
130 | auto glyphNode = util::make_unique<SVGElement>("path"); | |
130 | 131 | glyphNode->addAttribute("d", oss.str()); |
131 | if (!matrix.isIdentity()) | |
132 | glyphNode->addAttribute("transform", matrix.toSVG()); | |
132 | glyphNode->setTransform(matrix); | |
133 | 133 | contextNode()->append(std::move(glyphNode)); |
134 | 134 | } |
135 | 135 | } |
38 | 38 | private: |
39 | 39 | AppendMethod _appendChar; ///< method called to append a single character |
40 | 40 | bool _relativePathCommands; ///< if true, create relative rather than absolute SVG path commands |
41 | XMLElement *_groupNode=nullptr; ///< current group node taking the path elements | |
41 | SVGElement *_groupNode=nullptr; ///< current group node taking the path elements | |
42 | 42 | CharProperty<Color> _fontColor=Color::BLACK; ///< color of current font |
43 | 43 | }; |
44 | 44 |
19 | 19 | |
20 | 20 | #include "SVGCharTspanTextHandler.hpp" |
21 | 21 | #include "utility.hpp" |
22 | #include "XMLNode.hpp" | |
22 | #include "SVGElement.hpp" | |
23 | 23 | |
24 | 24 | using namespace std; |
25 | 25 | |
35 | 35 | if (!_textNode || _font.changed() || _matrix.changed() || _vertical.changed()) { |
36 | 36 | resetContextNode(); |
37 | 37 | _textNode = pushContextNode(createTextNode(x, y)); |
38 | _color.changed(true); // force creating tspan with color attribute if current color differs from font color | |
38 | _color.changed(true); // force creating tspan with color attribute if current color differs from font color | |
39 | _opacity.changed(true); // dito for opacity properties | |
39 | 40 | } |
40 | if (_tspanNode && (_xchanged || _ychanged || _color.changed())) { | |
41 | if (_tspanNode && (_xchanged || _ychanged || _color.changed() || _opacity.changed())) { | |
41 | 42 | // if drawing position or color was explicitly changed, finish current tspan element |
42 | 43 | popContextNode(); |
43 | 44 | _tspanNode = nullptr; |
45 | 46 | // Apply text color changes only if the color of the entire font is black. |
46 | 47 | // Glyphs of non-black fonts (e.g. defined in a XeTeX document) can't change their color. |
47 | 48 | bool applyColor = _color.get() != Color::BLACK && _font.get()->color() == Color::BLACK; |
48 | if (_xchanged || _ychanged || (_color.changed() && applyColor)) { | |
49 | _tspanNode = pushContextNode(util::make_unique<XMLElement>("tspan")); | |
49 | bool applyOpacity = !_opacity->isFillDefault(); | |
50 | if (_xchanged || _ychanged || (_color.changed() && applyColor) || (_opacity.changed() && applyOpacity)) { | |
51 | _tspanNode = pushContextNode(util::make_unique<SVGElement>("tspan")); | |
50 | 52 | if (applyColor) |
51 | _tspanNode->addAttribute("fill", _color.get().svgColorString()); | |
53 | _tspanNode->setFillColor(_color); | |
52 | 54 | _color.changed(false); |
55 | _tspanNode->setFillOpacity(_opacity); | |
56 | _opacity.changed(false); | |
53 | 57 | if (_xchanged) { |
54 | 58 | if (_vertical) { |
55 | 59 | // align glyphs designed for horizontal layout properly |
56 | if (auto pf = dynamic_cast<const PhysicalFont*>(_font.get())) | |
60 | if (auto pf = font_cast<const PhysicalFont*>(_font.get())) | |
57 | 61 | if (!pf->getMetrics()->verticalLayout()) |
58 | 62 | x += pf->scaledAscent()/2.5; // move vertical baseline to the right by strikethrough offset |
59 | 63 | } |
69 | 73 | } |
70 | 74 | |
71 | 75 | |
72 | void SVGCharTspanTextHandler::setInitialContextNode (XMLElement *node) { | |
76 | void SVGCharTspanTextHandler::setInitialContextNode (SVGElement *node) { | |
73 | 77 | SVGCharHandler::setInitialContextNode(node); |
74 | 78 | _textNode = _tspanNode = nullptr; |
75 | 79 | _xchanged = _ychanged = false; |
28 | 28 | void notifyXAdjusted () override {_xchanged = true;} |
29 | 29 | void notifyYAdjusted() override {_ychanged = true;} |
30 | 30 | void appendChar (uint32_t c, double x, double y) override; |
31 | void setInitialContextNode (XMLElement *node) override; | |
31 | void setInitialContextNode (SVGElement *node) override; | |
32 | 32 | |
33 | 33 | protected: |
34 | 34 | void resetContextNode () override; |
35 | 35 | |
36 | 36 | private: |
37 | 37 | bool _xchanged, _ychanged; |
38 | XMLElement *_textNode; | |
39 | XMLElement *_tspanNode; | |
38 | SVGElement *_textNode; | |
39 | SVGElement *_tspanNode; | |
40 | 40 | }; |
41 | 41 | |
42 | 42 | #endif |
0 | /************************************************************************* | |
1 | ** SVGElement.cpp ** | |
2 | ** ** | |
3 | ** This file is part of dvisvgm -- a fast DVI to SVG converter ** | |
4 | ** Copyright (C) 2005-2021 Martin Gieseking <martin.gieseking@uos.de> ** | |
5 | ** ** | |
6 | ** This program is free software; you can redistribute it and/or ** | |
7 | ** modify it under the terms of the GNU General Public License as ** | |
8 | ** published by the Free Software Foundation; either version 3 of ** | |
9 | ** the License, or (at your option) any later version. ** | |
10 | ** ** | |
11 | ** This program is distributed in the hope that it will be useful, but ** | |
12 | ** WITHOUT ANY WARRANTY; without even the implied warranty of ** | |
13 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** | |
14 | ** GNU General Public License for more details. ** | |
15 | ** ** | |
16 | ** You should have received a copy of the GNU General Public License ** | |
17 | ** along with this program; if not, see <http://www.gnu.org/licenses/>. ** | |
18 | *************************************************************************/ | |
19 | ||
20 | #include <sstream> | |
21 | #include "Color.hpp" | |
22 | #include "Matrix.hpp" | |
23 | #include "Opacity.hpp" | |
24 | #include "SVGElement.hpp" | |
25 | #include "XMLString.hpp" | |
26 | ||
27 | using namespace std; | |
28 | ||
29 | ||
30 | void SVGElement::setClipPathUrl (const string &url) { | |
31 | if (!url.empty()) | |
32 | addAttribute("clip-path", "url(#"+url+")"); | |
33 | } | |
34 | ||
35 | ||
36 | void SVGElement::setClipRule (FillRule rule) { | |
37 | if (rule != FR_NONZERO) | |
38 | addAttribute("clip-rule", "evenodd"); | |
39 | } | |
40 | ||
41 | ||
42 | void SVGElement::setFillColor (Color color, bool skipBlack) { | |
43 | if (color != Color::BLACK || !skipBlack) | |
44 | addAttribute("fill", color.svgColorString()); | |
45 | } | |
46 | ||
47 | ||
48 | void SVGElement::setFillOpacity (const Opacity &opacity) { | |
49 | if (!opacity.isFillDefault()) { | |
50 | setFillOpacity(opacity.fillalpha()); | |
51 | setFillOpacity(opacity.blendMode()); | |
52 | } | |
53 | } | |
54 | ||
55 | ||
56 | void SVGElement::setFillOpacity (const OpacityAlpha &alpha) { | |
57 | if (!alpha.isOpaque()) | |
58 | addAttribute("fill-opacity", alpha.value()); | |
59 | } | |
60 | ||
61 | ||
62 | void SVGElement::setFillOpacity (Opacity::BlendMode blendMode) { | |
63 | if (blendMode != Opacity::BM_NORMAL) | |
64 | addAttribute("style", "mix-blend-mode:"+Opacity::cssBlendMode(blendMode)); | |
65 | } | |
66 | ||
67 | ||
68 | void SVGElement::setFillRule (FillRule rule) { | |
69 | if (rule != FR_NONZERO) | |
70 | addAttribute("fill-rule", "evenodd"); | |
71 | } | |
72 | ||
73 | ||
74 | void SVGElement::setFillPatternUrl (const std::string &url) { | |
75 | if (!url.empty()) | |
76 | addAttribute("fill", "url(#" + url + ")"); | |
77 | } | |
78 | ||
79 | ||
80 | void SVGElement::setNoFillColor () { | |
81 | addAttribute("fill", "none"); | |
82 | } | |
83 | ||
84 | ||
85 | void SVGElement::setPoints (const vector<DPair> &points) { | |
86 | if (!points.empty()) { | |
87 | ostringstream oss; | |
88 | for (const DPair &p : points) | |
89 | oss << XMLString(p.x()) << ' ' << XMLString(p.y()) << ' '; | |
90 | string str = oss.str(); | |
91 | str.pop_back(); | |
92 | addAttribute("points", str); | |
93 | } | |
94 | } | |
95 | ||
96 | ||
97 | void SVGElement::setStrokeColor (Color color) { | |
98 | addAttribute("stroke", color.svgColorString()); | |
99 | } | |
100 | ||
101 | ||
102 | void SVGElement::setStrokeDash (const vector<double> &pattern, double offset) { | |
103 | if (!pattern.empty()) { | |
104 | string patternStr; | |
105 | for (double dashValue : pattern) | |
106 | patternStr += XMLString(dashValue)+" "; | |
107 | patternStr.pop_back(); | |
108 | addAttribute("stroke-dasharray", patternStr); | |
109 | if (offset != 0) | |
110 | addAttribute("stroke-dashoffset", offset); | |
111 | } | |
112 | } | |
113 | ||
114 | ||
115 | void SVGElement::setStrokeLineCap (LineCap cap) { | |
116 | if (cap != LC_BUTT) | |
117 | addAttribute("stroke-linecap", cap == LC_ROUND ? "round" : "square"); | |
118 | } | |
119 | ||
120 | ||
121 | void SVGElement::setStrokeLineJoin (LineJoin join) { | |
122 | if (join != LJ_MITER) | |
123 | addAttribute("stroke-linejoin", join == LJ_BEVEL ? "bevel" : "round"); | |
124 | } | |
125 | ||
126 | ||
127 | void SVGElement::setStrokeMiterLimit (double limit) { | |
128 | if (limit != 4) | |
129 | addAttribute("stroke-miterlimit", limit); | |
130 | } | |
131 | ||
132 | ||
133 | void SVGElement::setStrokeOpacity (const Opacity &opacity) { | |
134 | if (!opacity.isStrokeDefault()) { | |
135 | if (!opacity.strokealpha().isOpaque()) | |
136 | addAttribute("stroke-opacity", opacity.strokealpha().value()); | |
137 | if (opacity.blendMode() != Opacity::BM_NORMAL) | |
138 | addAttribute("style", "mix-blend-mode:"+opacity.cssBlendMode()); | |
139 | } | |
140 | } | |
141 | ||
142 | ||
143 | void SVGElement::setStrokeWidth (double width) { | |
144 | if (width != 1) | |
145 | addAttribute("stroke-width", width); | |
146 | } | |
147 | ||
148 | ||
149 | void SVGElement::setTransform (const Matrix &matrix) { | |
150 | if (!matrix.isIdentity()) | |
151 | addAttribute("transform", matrix.toSVG()); | |
152 | } |
0 | /************************************************************************* | |
1 | ** SVGElement.hpp ** | |
2 | ** ** | |
3 | ** This file is part of dvisvgm -- a fast DVI to SVG converter ** | |
4 | ** Copyright (C) 2005-2021 Martin Gieseking <martin.gieseking@uos.de> ** | |
5 | ** ** | |
6 | ** This program is free software; you can redistribute it and/or ** | |
7 | ** modify it under the terms of the GNU General Public License as ** | |
8 | ** published by the Free Software Foundation; either version 3 of ** | |
9 | ** the License, or (at your option) any later version. ** | |
10 | ** ** | |
11 | ** This program is distributed in the hope that it will be useful, but ** | |
12 | ** WITHOUT ANY WARRANTY; without even the implied warranty of ** | |
13 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** | |
14 | ** GNU General Public License for more details. ** | |
15 | ** ** | |
16 | ** You should have received a copy of the GNU General Public License ** | |
17 | ** along with this program; if not, see <http://www.gnu.org/licenses/>. ** | |
18 | *************************************************************************/ | |
19 | ||
20 | #ifndef SVGELEMENT_HPP | |
21 | #define SVGELEMENT_HPP | |
22 | ||
23 | #include "XMLNode.hpp" | |
24 | ||
25 | class Color; | |
26 | class Matrix; | |
27 | class Opacity; | |
28 | ||
29 | class SVGElement : public XMLElement { | |
30 | public: | |
31 | enum FillRule {FR_EVENODD, FR_NONZERO}; | |
32 | enum LineCap {LC_BUTT, LC_ROUND, LC_SQUARE}; | |
33 | enum LineJoin {LJ_BEVEL, LJ_MITER, LJ_ROUND}; | |
34 | ||
35 | public: | |
36 | explicit SVGElement (std::string name) : XMLElement(std::move(name)) {} | |
37 | explicit SVGElement (const XMLElement &node) : XMLElement(node) {} | |
38 | explicit SVGElement (XMLElement &&node) noexcept : XMLElement(std::move(node)) {} | |
39 | void setClipPathUrl (const std::string &url); | |
40 | void setClipRule (FillRule rule); | |
41 | void setFillColor (Color color, bool skipBlack=true); | |
42 | void setFillOpacity (const Opacity &opacity); | |
43 | void setFillOpacity (const OpacityAlpha &alpha); | |
44 | void setFillOpacity (Opacity::BlendMode blendMode); | |
45 | void setFillPatternUrl (const std::string &url); | |
46 | void setFillRule (FillRule rule); | |
47 | void setNoFillColor (); | |
48 | void setPoints (const std::vector<DPair> &points); | |
49 | void setStrokeColor (Color color); | |
50 | void setStrokeDash (const std::vector<double> &pattern, double offset=0); | |
51 | void setStrokeLineCap (LineCap cap); | |
52 | void setStrokeLineJoin (LineJoin join); | |
53 | void setStrokeOpacity (const Opacity &opacity); | |
54 | void setStrokeWidth (double width); | |
55 | void setStrokeMiterLimit (double limit); | |
56 | void setTransform (const Matrix &matrix); | |
57 | }; | |
58 | ||
59 | #endif |
18 | 18 | *************************************************************************/ |
19 | 19 | |
20 | 20 | #include "SVGSingleCharTextHandler.hpp" |
21 | #include "XMLNode.hpp" | |
21 | #include "SVGElement.hpp" | |
22 | 22 | |
23 | 23 | using namespace std; |
24 | 24 | |
28 | 28 | textNode->append(XMLString(font->unicode(c), false)); |
29 | 29 | // Apply color changes only if the color differs from black and if the font color itself is black. |
30 | 30 | // Glyphs from non-black fonts (e.g. defined in a XeTeX document) can't change their color. |
31 | if (_color.get() != Color::BLACK && font->color() == Color::BLACK) { | |
32 | textNode->addAttribute("fill", _color.get().svgColorString()); | |
33 | _color.changed(false); | |
34 | } | |
31 | if (_color.get() != Color::BLACK && font->color() == Color::BLACK) | |
32 | textNode->setFillColor(_color); | |
33 | _color.changed(false); | |
34 | if (!_opacity->isFillDefault()) | |
35 | textNode->setFillOpacity(_opacity); | |
36 | _opacity.changed(false); | |
35 | 37 | contextNode()->append(std::move(textNode)); |
36 | 38 | } |
30 | 30 | #include "SVGCharHandlerFactory.hpp" |
31 | 31 | #include "SVGTree.hpp" |
32 | 32 | #include "XMLDocument.hpp" |
33 | #include "XMLNode.hpp" | |
34 | 33 | #include "XMLString.hpp" |
35 | 34 | |
36 | 35 | using namespace std; |
54 | 53 | /** Clears the SVG tree and initializes the root element. */ |
55 | 54 | void SVGTree::reset () { |
56 | 55 | _doc.clear(); |
57 | auto rootNode = util::make_unique<XMLElement>("svg"); | |
56 | auto rootNode = util::make_unique<SVGElement>("svg"); | |
58 | 57 | rootNode->addAttribute("version", "1.1"); |
59 | 58 | rootNode->addAttribute("xmlns", "http://www.w3.org/2000/svg"); |
60 | 59 | rootNode->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); |
110 | 109 | /** Starts a new page. |
111 | 110 | * @param[in] pageno number of new page */ |
112 | 111 | void SVGTree::newPage (int pageno) { |
113 | auto pageNode = util::make_unique<XMLElement>("g"); | |
112 | auto pageNode = util::make_unique<SVGElement>("g"); | |
114 | 113 | if (pageno >= 0) |
115 | 114 | pageNode->addAttribute("id", string("page")+XMLString(pageno)); |
116 | 115 | _charHandler->setInitialContextNode(pageNode.get()); |
117 | 116 | _page = pageNode.get(); |
118 | 117 | _root->append(std::move(pageNode)); |
119 | _defsContextStack = stack<XMLElement*>(); | |
120 | _pageContextStack = stack<XMLElement*>(); | |
118 | _defsContextStack = stack<SVGElement*>(); | |
119 | _pageContextStack = stack<SVGElement*>(); | |
121 | 120 | } |
122 | 121 | |
123 | 122 | |
124 | 123 | void SVGTree::appendToDefs (unique_ptr<XMLNode> node) { |
125 | 124 | if (!_defs) { |
126 | auto defsNode = util::make_unique<XMLElement>("defs"); | |
125 | auto defsNode = util::make_unique<SVGElement>("defs"); | |
127 | 126 | _defs = defsNode.get(); |
128 | 127 | _root->prepend(std::move(defsNode)); |
129 | 128 | } |
133 | 132 | |
134 | 133 | |
135 | 134 | void SVGTree::appendToPage (unique_ptr<XMLNode> node) { |
136 | XMLElement *parent = _pageContextStack.empty() ? _page : _pageContextStack.top(); | |
135 | SVGElement *parent = _pageContextStack.empty() ? _page : _pageContextStack.top(); | |
137 | 136 | parent->append(std::move(node)); |
138 | 137 | _charHandler->setInitialContextNode(parent); |
139 | 138 | } |
148 | 147 | |
149 | 148 | |
150 | 149 | void SVGTree::transformPage (const Matrix &usermatrix) { |
151 | if (!usermatrix.isIdentity()) | |
152 | _page->addAttribute("transform", usermatrix.toSVG()); | |
150 | _page->setTransform(usermatrix); | |
153 | 151 | } |
154 | 152 | |
155 | 153 | |
170 | 168 | double extend = font.style() ? font.style()->extend : 1; |
171 | 169 | glyphNode = util::make_unique<XMLElement>("glyph"); |
172 | 170 | glyphNode->addAttribute("unicode", XMLString(font.unicode(c), false)); |
173 | glyphNode->addAttribute("horiz-adv-x", XMLString(font.hAdvance(c)*extend)); | |
174 | glyphNode->addAttribute("vert-adv-y", XMLString(font.vAdvance(c))); | |
171 | glyphNode->addAttribute("horiz-adv-x", font.hAdvance(c)*extend); | |
172 | glyphNode->addAttribute("vert-adv-y", font.vAdvance(c)); | |
175 | 173 | string name = font.glyphName(c); |
176 | 174 | if (!name.empty()) |
177 | 175 | glyphNode->addAttribute("glyph-name", name); |
191 | 189 | |
192 | 190 | static string font_info (const Font &font) { |
193 | 191 | ostringstream oss; |
194 | if (auto nf = dynamic_cast<const NativeFont*>(&font)) { | |
192 | if (auto nf = font_cast<const NativeFont*>(&font)) { | |
195 | 193 | oss << nf->familyName() << ' ' << nf->styleName() << "; " << nf->filename(); |
196 | 194 | if (nf->style()) { |
197 | 195 | if (nf->style()->bold != 0) |
210 | 208 | if (CREATE_CSS && USE_FONTS && !fonts.empty() && _page) { |
211 | 209 | map<int, const Font*> sortmap; |
212 | 210 | for (const Font *font : fonts) |
213 | if (!dynamic_cast<const VirtualFont*>(font)) // skip virtual fonts | |
211 | if (!font_cast<const VirtualFont*>(font)) // skip virtual fonts | |
214 | 212 | sortmap[FontManager::instance().fontID(font)] = font; |
215 | 213 | ostringstream style; |
216 | 214 | // add font style definitions in ascending order |
259 | 257 | auto fontNode = util::make_unique<XMLElement>("font"); |
260 | 258 | string fontname = font.name(); |
261 | 259 | fontNode->addAttribute("id", fontname); |
262 | fontNode->addAttribute("horiz-adv-x", XMLString(font.hAdvance())); | |
260 | fontNode->addAttribute("horiz-adv-x", font.hAdvance()); | |
263 | 261 | |
264 | 262 | auto faceNode = util::make_unique<XMLElement>("font-face"); |
265 | 263 | faceNode->addAttribute("font-family", fontname); |
266 | faceNode->addAttribute("units-per-em", XMLString(font.unitsPerEm())); | |
264 | faceNode->addAttribute("units-per-em", font.unitsPerEm()); | |
267 | 265 | if (!font.verticalLayout()) { |
268 | faceNode->addAttribute("ascent", XMLString(font.ascent())); | |
269 | faceNode->addAttribute("descent", XMLString(font.descent())); | |
266 | faceNode->addAttribute("ascent", font.ascent()); | |
267 | faceNode->addAttribute("descent", font.descent()); | |
270 | 268 | } |
271 | 269 | fontNode->append(std::move(faceNode)); |
272 | 270 | for (int c : chars) |
296 | 294 | } |
297 | 295 | |
298 | 296 | |
299 | void SVGTree::pushDefsContext (unique_ptr<XMLElement> node) { | |
300 | XMLElement *nodePtr = node.get(); | |
297 | void SVGTree::pushDefsContext (unique_ptr<SVGElement> node) { | |
298 | SVGElement *nodePtr = node.get(); | |
301 | 299 | if (_defsContextStack.empty()) |
302 | 300 | appendToDefs(std::move(node)); |
303 | 301 | else |
313 | 311 | |
314 | 312 | |
315 | 313 | /** Pushes a new context element that will take all following nodes added to the page. */ |
316 | void SVGTree::pushPageContext (unique_ptr<XMLElement> node) { | |
317 | XMLElement *nodePtr = node.get(); | |
314 | void SVGTree::pushPageContext (unique_ptr<SVGElement> node) { | |
315 | SVGElement *nodePtr = node.get(); | |
318 | 316 | if (_pageContextStack.empty()) |
319 | 317 | _page->append(std::move(node)); |
320 | 318 | else |
30 | 30 | #include "GFGlyphTracer.hpp" |
31 | 31 | #include "Matrix.hpp" |
32 | 32 | #include "SVGCharHandler.hpp" |
33 | #include "SVGElement.hpp" | |
33 | 34 | #include "XMLDocument.hpp" |
34 | #include "XMLNode.hpp" | |
35 | 35 | |
36 | 36 | class BoundingBox; |
37 | 37 | class Color; |
38 | 38 | class Font; |
39 | 39 | class Matrix; |
40 | class Opacity; | |
40 | 41 | class PhysicalFont; |
41 | 42 | |
42 | 43 | class SVGTree { |
53 | 54 | void appendChar (int c, double x, double y) {_charHandler->appendChar(c, x, y);} |
54 | 55 | void appendFontStyles (const std::unordered_set<const Font*> &fonts); |
55 | 56 | void append (const PhysicalFont &font, const std::set<int> &chars, GFGlyphTracer::Callback *callback=nullptr); |
56 | void pushDefsContext (std::unique_ptr<XMLElement> node); | |
57 | void pushDefsContext (std::unique_ptr<SVGElement> node); | |
57 | 58 | void popDefsContext (); |
58 | void pushPageContext (std::unique_ptr<XMLElement> node); | |
59 | void pushPageContext (std::unique_ptr<SVGElement> node); | |
59 | 60 | void popPageContext (); |
60 | 61 | void setBBox (const BoundingBox &bbox); |
61 | 62 | void setFont (int id, const Font &font); |
62 | 63 | static bool setFontFormat (std::string formatstr); |
63 | void setX (double x) {_charHandler->notifyXAdjusted();} | |
64 | void setY (double y) {_charHandler->notifyYAdjusted();} | |
65 | void setMatrix (const Matrix &m) {_charHandler->setMatrix(m);} | |
64 | void setX (double x) {_charHandler->notifyXAdjusted();} | |
65 | void setY (double y) {_charHandler->notifyYAdjusted();} | |
66 | void setMatrix (const Matrix &m) {_charHandler->setMatrix(m);} | |
66 | 67 | void setColor (const Color &c); |
67 | void setVertical (bool state) {_charHandler->setVertical(state);} | |
68 | void setOpacity (const Opacity &op) {_charHandler->setOpacity(op);} | |
69 | void setVertical (bool state) {_charHandler->setVertical(state);} | |
68 | 70 | void transformPage (const Matrix &m); |
69 | Color getColor () const {return _charHandler->getColor();} | |
70 | const Matrix& getMatrix () const {return _charHandler->getMatrix();} | |
71 | XMLElement* rootNode () const {return _root;} | |
72 | XMLElement* defsNode () const {return _defs;} | |
73 | XMLElement* pageNode () const {return _page;} | |
71 | Color getColor () const {return _charHandler->getColor();} | |
72 | const Opacity& getOpacity () const {return _charHandler->getOpacity();} | |
73 | const Matrix& getMatrix () const {return _charHandler->getMatrix();} | |
74 | XMLElement* rootNode () const {return _root;} | |
75 | XMLElement* defsNode () const {return _defs;} | |
76 | XMLElement* pageNode () const {return _page;} | |
74 | 77 | |
75 | 78 | protected: |
76 | 79 | XMLCData* styleCDataNode (); |
87 | 90 | |
88 | 91 | private: |
89 | 92 | XMLDocument _doc; |
90 | XMLElement *_root, *_page, *_defs; | |
93 | SVGElement *_root, *_page, *_defs; | |
91 | 94 | XMLCData *_styleCDataNode; |
92 | 95 | std::unique_ptr<SVGCharHandler> _charHandler; |
93 | std::stack<XMLElement*> _defsContextStack; | |
94 | std::stack<XMLElement*> _pageContextStack; | |
96 | std::stack<SVGElement*> _defsContextStack; | |
97 | std::stack<SVGElement*> _pageContextStack; | |
95 | 98 | }; |
96 | 99 | |
97 | 100 | #endif |
26 | 26 | #include "Color.hpp" |
27 | 27 | #include "FilePath.hpp" |
28 | 28 | #include "Matrix.hpp" |
29 | #include "Opacity.hpp" | |
29 | 30 | #include "SVGTree.hpp" |
30 | 31 | |
31 | 32 | class XMLElement; |
45 | 46 | virtual const Matrix& getMatrix () const =0; |
46 | 47 | virtual Matrix getPageTransformation () const {return Matrix(1);} |
47 | 48 | virtual void setBgColor (const Color &color) =0; |
49 | virtual void setOpacity (const Opacity &opacity) =0; | |
50 | virtual const Opacity& getOpacity () const =0; | |
48 | 51 | virtual const SVGTree& svgTree () const =0; |
49 | 52 | SVGTree& svgTree () {return const_cast<SVGTree&>(const_cast<const SpecialActions*>(this)->svgTree());} |
50 | 53 | virtual BoundingBox& bbox () =0; |
74 | 77 | void finishLine () override {} |
75 | 78 | void setColor (const Color &color) override {} |
76 | 79 | void setBgColor (const Color &color) override {} |
80 | void setOpacity (const Opacity &opacity) override {} | |
81 | const Opacity& getOpacity () const override {return _svg.getOpacity();} | |
77 | 82 | Color getColor () const override {return Color::BLACK;} |
78 | 83 | void setMatrix (const Matrix &m) override {} |
79 | 84 | const Matrix& getMatrix () const override {return _matrix;} |
52 | 52 | while (is && !isspace(is.peek())) |
53 | 53 | id += char(is.get()); |
54 | 54 | if (!id.empty()) { |
55 | auto state = _subfonts.emplace(pair<string,unique_ptr<Subfont>>(id, unique_ptr<Subfont>())); | |
55 | auto state = _subfonts.emplace(id, unique_ptr<Subfont>()); | |
56 | 56 | if (state.second) // id was not present in map already |
57 | 57 | state.first->second = unique_ptr<Subfont>(new Subfont(*this, state.first->first)); |
58 | 58 | skip_mapping_data(is); |
62 | 62 | * @param[in] edgeflag defines how to connect this patch with another one |
63 | 63 | * @param[in] patch reference patch required if edgeflag > 0 */ |
64 | 64 | void TensorProductPatch::setPoints (const PointVec &points, int edgeflag, ShadingPatch *patch) { |
65 | auto tpPatch = dynamic_cast<TensorProductPatch*>(patch); | |
65 | TensorProductPatch *tpPatch = nullptr; | |
66 | if (patch && patch->psShadingType() == psShadingType()) | |
67 | tpPatch = static_cast<TensorProductPatch*>(patch); | |
66 | 68 | if (edgeflag > 0 && !tpPatch) |
67 | 69 | throw ShadingException("missing preceding data in definition of tensor-product patch"); |
68 | 70 | if ((edgeflag == 0 && points.size() != 16) || (edgeflag > 0 && points.size() != 12)) |
106 | 108 | * @param[in] edgeflag defines how to connect this patch with another one |
107 | 109 | * @param[in] patch reference patch required if edgeflag > 0 */ |
108 | 110 | void TensorProductPatch::setColors(const ColorVec &colors, int edgeflag, ShadingPatch* patch) { |
109 | auto tpPatch = dynamic_cast<TensorProductPatch*>(patch); | |
111 | TensorProductPatch *tpPatch = nullptr; | |
112 | if (patch && patch->psShadingType() == psShadingType()) | |
113 | tpPatch = static_cast<TensorProductPatch*>(patch); | |
110 | 114 | if (edgeflag > 0 && !tpPatch) |
111 | 115 | throw ShadingException("missing preceding data in definition of tensor-product patch"); |
112 | 116 | if ((edgeflag == 0 && colors.size() != 4) || (edgeflag > 0 && colors.size() != 2)) |
491 | 495 | * @param[in] edgeflag defines how to connect this patch to another one |
492 | 496 | * @param[in] patch reference patch required if edgeflag > 0 */ |
493 | 497 | void CoonsPatch::setPoints (const PointVec &points, int edgeflag, ShadingPatch *patch) { |
494 | auto coonsPatch = dynamic_cast<CoonsPatch*>(patch); | |
498 | CoonsPatch *coonsPatch = nullptr; | |
499 | if (patch && patch->psShadingType() == psShadingType()) | |
500 | coonsPatch = static_cast<CoonsPatch*>(patch); | |
495 | 501 | if (edgeflag > 0 && !coonsPatch) |
496 | 502 | throw ShadingException("missing preceding data in definition of relative Coons patch"); |
497 | 503 | if ((edgeflag == 0 && points.size() != 12) || (edgeflag > 0 && points.size() != 8)) |
528 | 534 | |
529 | 535 | |
530 | 536 | void CoonsPatch::setColors (const ColorVec &colors, int edgeflag, ShadingPatch *patch) { |
531 | auto coonsPatch = dynamic_cast<CoonsPatch*>(patch); | |
537 | CoonsPatch *coonsPatch = nullptr; | |
538 | if (patch && patch->psShadingType() == psShadingType()) | |
539 | coonsPatch = static_cast<CoonsPatch*>(patch); | |
532 | 540 | if (edgeflag > 0 && !coonsPatch) |
533 | 541 | throw ShadingException("missing preceding data in definition of relative Coons patch"); |
534 | 542 | if ((edgeflag == 0 && colors.size() != 4) || (edgeflag > 0 && colors.size() != 2)) |
27 | 27 | #include "InputReader.hpp" |
28 | 28 | #include "GraphicsPath.hpp" |
29 | 29 | #include "SpecialActions.hpp" |
30 | #include "SVGElement.hpp" | |
30 | 31 | #include "SVGTree.hpp" |
31 | 32 | #include "TpicSpecialHandler.hpp" |
32 | 33 | #include "utility.hpp" |
69 | 70 | * @param[in] penwidth pen with used to compute the stroke parameters |
70 | 71 | * @param[in] pencolor the drawing color |
71 | 72 | * @param[in] ddist dash/dot distance of line in PS point units (0:solid line, >0:dashed line, <0:dotted line) */ |
72 | static void add_stroke_attribs (XMLElement *elem, double penwidth, Color pencolor, double ddist) { | |
73 | static void add_stroke_attribs (SVGElement *elem, double penwidth, Color pencolor, double ddist) { | |
73 | 74 | if (penwidth > 0) { // attributes actually required? |
74 | elem->addAttribute("stroke", pencolor.svgColorString()); | |
75 | elem->addAttribute("stroke-width", XMLString(penwidth)); | |
75 | elem->setStrokeColor(pencolor); | |
76 | elem->setStrokeWidth(penwidth); | |
77 | vector<double> dasharray; | |
76 | 78 | if (ddist > 0) |
77 | elem->addAttribute("stroke-dasharray", XMLString(ddist)); | |
78 | else if (ddist < 0) | |
79 | elem->addAttribute("stroke-dasharray", XMLString(penwidth) + ' ' + XMLString(-ddist)); | |
80 | } | |
81 | } | |
82 | ||
83 | ||
84 | static unique_ptr<XMLElement> create_ellipse_element (double cx, double cy, double rx, double ry) { | |
79 | dasharray.push_back(ddist); | |
80 | else if (ddist < 0) { | |
81 | dasharray.push_back(penwidth); | |
82 | dasharray.push_back(-ddist); | |
83 | } | |
84 | elem->setStrokeDash(dasharray); | |
85 | } | |
86 | } | |
87 | ||
88 | ||
89 | static unique_ptr<SVGElement> create_ellipse_element (double cx, double cy, double rx, double ry) { | |
85 | 90 | bool is_circle = (rx == ry); |
86 | auto elem = util::make_unique<XMLElement>(is_circle ? "circle" : "ellipse"); | |
87 | elem->addAttribute("cx", XMLString(cx)); | |
88 | elem->addAttribute("cy", XMLString(cy)); | |
91 | auto elem = util::make_unique<SVGElement>(is_circle ? "circle" : "ellipse"); | |
92 | elem->addAttribute("cx", cx); | |
93 | elem->addAttribute("cy", cy); | |
89 | 94 | if (is_circle) |
90 | elem->addAttribute("r", XMLString(rx)); | |
95 | elem->addAttribute("r", rx); | |
91 | 96 | else { |
92 | elem->addAttribute("rx", XMLString(rx)); | |
93 | elem->addAttribute("ry", XMLString(ry)); | |
97 | elem->addAttribute("rx", rx); | |
98 | elem->addAttribute("ry", ry); | |
94 | 99 | } |
95 | 100 | return elem; |
96 | 101 | } |
101 | 106 | * @param[in] actions object providing the actions that can be performed by the SpecialHandler */ |
102 | 107 | void TpicSpecialHandler::drawLines (double ddist, SpecialActions &actions) { |
103 | 108 | if (!_points.empty() && (_penwidth > 0 || _grayLevel >= 0) && !actions.outputLocked()) { |
104 | unique_ptr<XMLElement> elem; | |
109 | unique_ptr<SVGElement> elem; | |
105 | 110 | if (_points.size() == 1) { |
106 | 111 | const DPair &p = _points.back(); |
107 | 112 | elem = create_ellipse_element(p.x()+actions.getX(), p.y()+actions.getY(), _penwidth/2.0, _penwidth/2.0); |
109 | 114 | } |
110 | 115 | else { |
111 | 116 | if (_points.size() == 2 || (_grayLevel < 0 && _points.front() != _points.back())) { |
112 | elem = util::make_unique<XMLElement>("polyline"); | |
113 | elem->addAttribute("fill", "none"); | |
114 | elem->addAttribute("stroke-linecap", "round"); | |
117 | elem = util::make_unique<SVGElement>("polyline"); | |
118 | elem->setNoFillColor(); | |
119 | elem->setStrokeLineCap(SVGElement::LC_ROUND); | |
115 | 120 | } |
116 | 121 | else { |
117 | 122 | while (_points.front() == _points.back()) |
118 | 123 | _points.pop_back(); |
119 | elem = util::make_unique<XMLElement>("polygon"); | |
120 | elem->addAttribute("fill", _grayLevel < 0 ? "none" : fillColor(false).svgColorString()); | |
121 | } | |
122 | ostringstream oss; | |
123 | for (auto it=_points.begin(); it != _points.end(); ++it) { | |
124 | if (it != _points.begin()) | |
125 | oss << ' '; | |
126 | double x = it->x()+actions.getX(); | |
127 | double y = it->y()+actions.getY(); | |
128 | oss << XMLString(x) << ',' << XMLString(y); | |
124 | elem = util::make_unique<SVGElement>("polygon"); | |
125 | if (_grayLevel < 0) | |
126 | elem->setNoFillColor(); | |
127 | else | |
128 | elem->setFillColor(fillColor(false)); | |
129 | } | |
130 | vector<DPair> points; | |
131 | for (const DPair &p : _points) { | |
132 | double x = p.x()+actions.getX(); | |
133 | double y = p.y()+actions.getY(); | |
134 | points.emplace_back(x, y); | |
129 | 135 | actions.embed(DPair(x, y)); |
130 | 136 | } |
131 | elem->addAttribute("points", oss.str()); | |
137 | elem->setPoints(points); | |
132 | 138 | add_stroke_attribs(elem.get(), _penwidth, Color::BLACK, ddist); |
133 | 139 | } |
134 | 140 | actions.svgTree().appendToPage(std::move(elem)); |
174 | 180 | path.lineto(p+_points[numPoints-1]); |
175 | 181 | actions.embed(p+_points[numPoints-1]); |
176 | 182 | } |
177 | auto pathElem = util::make_unique<XMLElement>("path"); | |
178 | pathElem->addAttribute("fill", "none"); | |
183 | auto pathElem = util::make_unique<SVGElement>("path"); | |
184 | pathElem->setNoFillColor(); | |
179 | 185 | ostringstream oss; |
180 | 186 | path.writeSVG(oss, SVGTree::RELATIVE_PATH_CMDS); |
181 | 187 | pathElem->addAttribute("d", oss.str()); |
199 | 205 | if ((_penwidth > 0 || _grayLevel >= 0) && !actions.outputLocked()) { |
200 | 206 | cx += actions.getX(); |
201 | 207 | cy += actions.getY(); |
202 | unique_ptr<XMLElement> elem; | |
208 | unique_ptr<SVGElement> elem; | |
203 | 209 | bool closed=true; |
204 | 210 | if (abs(angle2-angle1) >= math::TWO_PI) // closed ellipse? |
205 | 211 | elem = create_ellipse_element(cx, cy, rx, ry); |
212 | 218 | path.closepath(); |
213 | 219 | else |
214 | 220 | closed = false; |
215 | elem = util::make_unique<XMLElement>("path"); | |
221 | elem = util::make_unique<SVGElement>("path"); | |
216 | 222 | ostringstream oss; |
217 | 223 | path.writeSVG(oss, SVGTree::RELATIVE_PATH_CMDS); |
218 | 224 | elem->addAttribute("d", oss.str()); |
219 | 225 | } |
220 | 226 | if (_penwidth > 0) { |
221 | elem->addAttribute("stroke-width", _penwidth); | |
222 | elem->addAttribute("stroke", actions.getColor().svgColorString()); | |
227 | elem->setStrokeWidth(_penwidth); | |
228 | elem->setStrokeColor(actions.getColor()); | |
223 | 229 | if (!closed) |
224 | elem->addAttribute("stroke-linecap", "round"); | |
225 | } | |
226 | elem->addAttribute("fill", _grayLevel < 0 ? "none" : fillColor(true).svgColorString()); | |
230 | elem->setStrokeLineCap(SVGElement::LC_ROUND); | |
231 | } | |
232 | if (_grayLevel < 0) | |
233 | elem->setNoFillColor(); | |
234 | else | |
235 | elem->setFillColor(fillColor(true)); | |
227 | 236 | actions.svgTree().appendToPage(std::move(elem)); |
228 | 237 | double pw = _penwidth/2.0; |
229 | 238 | actions.embed(BoundingBox(cx-rx-pw, cy-ry-pw, cx+rx+pw, cy+ry+pw)); |
295 | 304 | case cmd_id("pa"): { // add point to path |
296 | 305 | double x = ir.getDouble()*mi2bp; |
297 | 306 | double y = ir.getDouble()*mi2bp; |
298 | _points.emplace_back(DPair(x,y)); | |
307 | _points.emplace_back(x, y); | |
299 | 308 | break; |
300 | 309 | } |
301 | 310 | case cmd_id("fp"): // draw solid lines through recorded points; close and fill path if fill color was defined |
30 | 30 | |
31 | 31 | |
32 | 32 | void TriangularPatch::setPoints (const PointVec &points, int edgeflag, ShadingPatch *patch) { |
33 | auto triangularPatch = dynamic_cast<TriangularPatch*>(patch); | |
33 | TriangularPatch *triangularPatch = nullptr; | |
34 | if (patch && patch->psShadingType() == psShadingType()) | |
35 | triangularPatch = static_cast<TriangularPatch*>(patch); | |
34 | 36 | if (edgeflag > 0 && !triangularPatch) |
35 | 37 | throw ShadingException("missing preceding data in definition of triangular patch"); |
36 | 38 | if ((edgeflag == 0 && points.size() != 3) || (edgeflag > 0 && points.size() != 1)) |
61 | 63 | |
62 | 64 | |
63 | 65 | void TriangularPatch::setColors (const ColorVec &colors, int edgeflag, ShadingPatch *patch) { |
64 | auto triangularPatch = dynamic_cast<TriangularPatch*>(patch); | |
66 | TriangularPatch *triangularPatch = nullptr; | |
67 | if (patch && patch->psShadingType() == psShadingType()) | |
68 | triangularPatch = static_cast<TriangularPatch*>(patch); | |
65 | 69 | if (edgeflag > 0 && !triangularPatch) |
66 | 70 | throw ShadingException("missing preceding data in definition of triangular patch"); |
67 | 71 | if ((edgeflag == 0 && colors.size() != 3) || (edgeflag > 0 && colors.size() != 1)) |
55 | 55 | if ((offset | length) > _buffer.size() || offset+length > _buffer.size()) |
56 | 56 | return false; |
57 | 57 | TTFTableRecord record = {tag, checksum, length, reinterpret_cast<const uint8_t*>(_buffer.data())+offset}; |
58 | _tableRecords.emplace_back(std::move(record)); | |
58 | _tableRecords.push_back(std::move(record)); | |
59 | 59 | } |
60 | 60 | return true; |
61 | 61 | } |
93 | 93 | woffRecord.compressTableData(); |
94 | 94 | woffSize += woffRecord.paddedSize(); |
95 | 95 | ttfSize += ttfRecord.paddedSize(); |
96 | woffRecords.emplace_back(std::move(woffRecord)); | |
96 | woffRecords.push_back(std::move(woffRecord)); | |
97 | 97 | } |
98 | 98 | // write WOFF header |
99 | 99 | StreamWriter writer(os); |
92 | 92 | bool operator >= (const VectorIterator &it) const {return _pos >= it._pos;} |
93 | 93 | bool operator < (const VectorIterator &it) const {return _pos < it._pos;} |
94 | 94 | bool operator > (const VectorIterator &it) const {return _pos > it._pos;} |
95 | bool valid () const {return _pos >= 0 && _pos < _vector.size();} | |
95 | bool valid () const {return _pos < _vector.size();} | |
96 | 96 | void invalidate () {_pos = _vector.size();} |
97 | 97 | void reset () {_pos = 0;} |
98 | 98 |
38 | 38 | if (node->toElement()) |
39 | 39 | _rootElement = util::static_unique_ptr_cast<XMLElement>(std::move(node)); |
40 | 40 | else |
41 | _nodes.emplace_back(std::move(node)); | |
41 | _nodes.push_back(std::move(node)); | |
42 | 42 | } |
43 | 43 | } |
44 | 44 |
106 | 106 | if (Attribute *attr = getAttribute(name)) |
107 | 107 | attr->value = value; |
108 | 108 | else |
109 | _attributes.emplace_back(Attribute(name, value)); | |
109 | _attributes.emplace_back(name, value); | |
110 | 110 | } |
111 | 111 | |
112 | 112 | |
386 | 386 | os << '>'; |
387 | 387 | // Insert newlines around children except text nodes. According to the |
388 | 388 | // SVG specification, pure whitespace nodes are ignored by the SVG renderer. |
389 | if (WRITE_NEWLINES && !_firstChild->toText()) | |
389 | if (WRITE_NEWLINES && name() != "text" && !_firstChild->toText()) | |
390 | 390 | os << '\n'; |
391 | 391 | for (XMLNode *child = _firstChild.get(); child; child = child->next()) { |
392 | 392 | child->write(os); |
393 | 393 | if (!child->toText()) { |
394 | if (WRITE_NEWLINES && (!child->next() || !child->next()->toText())) | |
394 | if (WRITE_NEWLINES && name() != "text" && (!child->next() || !child->next()->toText())) | |
395 | 395 | os << '\n'; |
396 | 396 | } |
397 | 397 | } |
127 | 127 | explicit XMLElement (std::string name); |
128 | 128 | XMLElement (const XMLElement &node); |
129 | 129 | XMLElement (XMLElement &&node) noexcept; |
130 | ~XMLElement (); | |
130 | ~XMLElement () override; | |
131 | 131 | std::unique_ptr<XMLNode> clone () const override {return util::make_unique<XMLElement>(*this);} |
132 | 132 | void clear () override; |
133 | 133 | void addAttribute (const std::string &name, const std::string &value); |
243 | 243 | |
244 | 244 | protected: |
245 | 245 | void append (const string &name, const string &version) { |
246 | _versionPairs.emplace_back(pair<string,string>(name, version)); | |
246 | _versionPairs.emplace_back(name, version); | |
247 | 247 | } |
248 | 248 | |
249 | 249 | private: |
348 | 348 | string msg = "unknown font format '"+cmdline.fontFormatOpt.value()+"' (supported formats: "; |
349 | 349 | for (const string &format : FontWriter::supportedFormats()) |
350 | 350 | msg += format + ", "; |
351 | msg.erase(msg.end()-2); | |
351 | msg.erase(msg.end()-2, msg.end()); | |
352 | msg += ")"; | |
352 | 353 | throw CL::CommandLineException(msg); |
353 | 354 | } |
354 | 355 | SVGTree::CREATE_USE_ELEMENTS = cmdline.noFontsOpt.value() < 1; |
106 | 106 | * @return true if all attributes have been moved */ |
107 | 107 | bool GroupCollapser::moveAttributes (XMLElement &source, XMLElement &dest) { |
108 | 108 | vector<string> movedAttributes; |
109 | for (const XMLElement::Attribute &attr : source.attributes()) { | |
109 | for (const auto &attr : source.attributes()) { | |
110 | 110 | if (attr.name == "transform") { |
111 | 111 | string transform; |
112 | 112 | if (const char *destvalue = dest.getAttributeValue("transform")) |
38 | 38 | if (commonAttribs.empty()) { |
39 | 39 | if (intersected) |
40 | 40 | break; |
41 | for (const auto attrib : elem->attributes()) { | |
41 | for (const auto &attrib : elem->attributes()) { | |
42 | 42 | if (AttributeExtractor::inheritable(attrib)) |
43 | 43 | commonAttribs.push_back(attrib); |
44 | 44 | } |
93 | 93 | vector<XMLElement::Attribute> attribs = common_inheritable_attributes(tspans); |
94 | 94 | if (!tspans.empty() && !attribs.empty()) { |
95 | 95 | // move all common tspan attributes to the parent text element |
96 | for (const XMLElement::Attribute &attr : attribs) | |
96 | for (const auto &attr : attribs) | |
97 | 97 | context->addAttribute(attr.name, attr.value); |
98 | 98 | // remove all common attributes from the tspan elements |
99 | 99 | for (XMLElement *tspan : tspans) { |
100 | for (const XMLElement::Attribute &attr : attribs) | |
100 | for (const auto &attr : attribs) | |
101 | 101 | tspan->removeAttribute(attr.name); |
102 | 102 | // unwrap the tspan if there are no remaining attributes |
103 | 103 | if (tspan->attributes().empty()) |
114 | 114 | "rgbcolor 3(setrgbcolor)prcmd}def/printgstate{@dodraw @GD/@nulldev get not and{" |
115 | 115 | "matrix currentmatrix aload pop 6(setmatrix)prcmd applyscalevals currentlinewid" |
116 | 116 | "th 1(setlinewidth)prcmd currentlinecap 1(setlinecap)prcmd currentlinejoin 1(se" |
117 | "tlinejoin)prcmd currentmiterlimit 1(setmiterlimit)prcmd prcolor currentdash ma" | |
118 | "rk 3 1 roll exch aload length 1 add -1 roll counttomark(setdash)prcmd pop}if}d" | |
119 | "ef/strconcat{exch dup length 2 index length add string dup dup 4 2 roll copy l" | |
120 | "ength 4 -1 roll putinterval}def/setgstate{/setgstate sysexec printgstate}def/s" | |
121 | "ave{@UD begin/@saveID vmstatus pop pop def end :save @saveID 1(save)prcmd}def/" | |
122 | "restore{:restore @checknulldev printgstate @UD/@saveID known{@UD begin @saveID" | |
123 | " end}{0}ifelse 1(restore)prcmd}def/gsave 0 defpr/grestore{:grestore @checknull" | |
124 | "dev printgstate 0(grestore)prcmd}def/grestoreall{:grestoreall @checknulldev se" | |
125 | "tstate 0(grestoreall)prcmd}def/rotate{dup type/arraytype ne @dodraw and{dup 1(" | |
126 | "rotate)prcmd}if/rotate sysexec applyscalevals}def/scale{dup type/arraytype ne " | |
127 | "@dodraw and{2 copy 2(scale)prcmd}if/scale sysexec applyscalevals}def/translate" | |
128 | "{dup type/arraytype ne @dodraw and{2 copy 2(translate)prcmd}if/translate sysex" | |
129 | "ec}def/setmatrix{dup/setmatrix sysexec @dodraw{aload pop 6(setmatrix)prcmd app" | |
130 | "lyscalevals}{pop}ifelse}def/initmatrix{matrix setmatrix}def/concat{matrix curr" | |
131 | "entmatrix matrix concatmatrix setmatrix}def/makepattern{gsave<</mx 3 -1 roll>>" | |
132 | "begin<</XUID[1000000 @patcnt]>>copy mx/makepattern sysexec dup begin PatternTy" | |
133 | "pe 2 lt{PatternType @patcnt BBox aload pop XStep YStep PaintType mx aload pop " | |
134 | "15(makepattern)prcmd :newpath matrix setmatrix dup PaintProc 0 1(makepattern)p" | |
135 | "rcmd @GD/@patcnt @patcnt 1 add put}if end end grestore}def/setpattern{begin Pa" | |
136 | "tternType 1 eq{PaintType 1 eq{XUID aload pop exch pop 1}{:gsave[currentcolorsp" | |
137 | "ace aload length -1 roll pop]/setcolorspace sysexec/setcolor sysexec XUID aloa" | |
138 | "d pop exch pop currentrgbcolor :grestore 4}ifelse(setpattern)prcmd currentcolo" | |
139 | "rspace 0 get/Pattern ne{[/Pattern currentcolorspace]/setcolorspace sysexec}if " | |
140 | "currentcolorspace @setcolorspace}{/setpattern sysexec}ifelse end}def/setcolor{" | |
141 | "dup type/dicttype eq{setpattern}{/setcolor sysexec/currentrgbcolor sysexec set" | |
142 | "rgbcolor}ifelse}def/setcolorspace{dup/setcolorspace sysexec @setcolorspace}def" | |
143 | "/@setcolorspace{dup type/arraytype eq{0 get}if/Pattern eq{1}{0}ifelse 1(setcol" | |
144 | "orspace)prcmd}def/setgray 1 defpr/setcmykcolor 4 defpr/sethsbcolor 3 defpr/set" | |
145 | "rgbcolor 3 defpr/.setalphaisshape{@SD/.setalphaisshape known{dup/.setalphaissh" | |
146 | "ape sysexec}if{1}{0}ifelse 1(setisshapealpha)prcmd}bind def/.setfillconstantal" | |
147 | "pha{@SD/.setfillconstantalpha known{dup/.setfillconstantalpha sysexec}if 1(set" | |
148 | "fillconstantalpha)prcmd}bind def/.setstrokeconstantalpha{@SD/.setstrokeconstan" | |
149 | "talpha known{dup/.setstrokeconstantalpha sysexec}if 1(setstrokeconstantalpha)p" | |
150 | "rcmd}bind def/.setopacityalpha{false .setalphaisshape dup .setfillconstantalph" | |
151 | "a .setstrokeconstantalpha}bind def/.setshapealpha{true .setalphaisshape dup .s" | |
152 | "etfillconstantalpha .setstrokeconstantalpha}bind def/.setblendmode{dup/.setble" | |
153 | "ndmode sysexec<</Normal 0/Compatible 0/Multiply 1/Screen 2/Overlay 3/SoftLight" | |
154 | " 4/HardLight 5/ColorDodge 6/ColorBurn 7/Darken 8/Lighten 9/Difference 10/Exclu" | |
155 | "sion 11/Hue 12/Saturation 13/Color 14/Luminosity 15/CompatibleOverprint 16>>ex" | |
156 | "ch get 1(setblendmode)prcmd}def/@pdfpagecount{(r)file runpdfbegin pdfpagecount" | |
157 | " runpdfend}def/@pdfpagebox{(r)file runpdfbegin dup dup 1 lt exch pdfpagecount " | |
158 | "gt or{pop}{pdfgetpage/MediaBox pget pop aload pop}ifelse runpdfend}def DELAYBI" | |
159 | "ND{.bindnow}if "; | |
117 | "tlinejoin)prcmd currentmiterlimit 1(setmiterlimit)prcmd revision dup 952 lt{po" | |
118 | "p}{.currentblendmode .setblendmode 952 eq{.currentopacityalpha .setopacityalph" | |
119 | "a .currentshapealpha .setshapealpha}{.currentalphaisshape{1}{0}ifelse 1(setalp" | |
120 | "haisshape)prcmd .currentstrokeconstantalpha 1(setstrokeconstantalpha)prcmd .cu" | |
121 | "rrentfillconstantalpha 1(setfillconstantalpha)prcmd}ifelse}ifelse prcolor curr" | |
122 | "entdash mark 3 1 roll exch aload length 1 add -1 roll counttomark(setdash)prcm" | |
123 | "d pop}if}def/strconcat{exch dup length 2 index length add string dup dup 4 2 r" | |
124 | "oll copy length 4 -1 roll putinterval}def/setgstate{/setgstate sysexec printgs" | |
125 | "tate}def/save{@UD begin/@saveID vmstatus pop pop def end :save @saveID 1(save)" | |
126 | "prcmd}def/restore{:restore @checknulldev printgstate @UD/@saveID known{@UD beg" | |
127 | "in @saveID end}{0}ifelse 1(restore)prcmd}def/gsave 0 defpr/grestore{:grestore " | |
128 | "@checknulldev printgstate 0(grestore)prcmd}def/grestoreall{:grestoreall @check" | |
129 | "nulldev setstate 0(grestoreall)prcmd}def/rotate{dup type/arraytype ne @dodraw " | |
130 | "and{dup 1(rotate)prcmd}if/rotate sysexec applyscalevals}def/scale{dup type/arr" | |
131 | "aytype ne @dodraw and{2 copy 2(scale)prcmd}if/scale sysexec applyscalevals}def" | |
132 | "/translate{dup type/arraytype ne @dodraw and{2 copy 2(translate)prcmd}if/trans" | |
133 | "late sysexec}def/setmatrix{dup/setmatrix sysexec @dodraw{aload pop 6(setmatrix" | |
134 | ")prcmd applyscalevals}{pop}ifelse}def/initmatrix{matrix setmatrix}def/concat{m" | |
135 | "atrix currentmatrix matrix concatmatrix setmatrix}def/makepattern{gsave<</mx 3" | |
136 | " -1 roll>>begin<</XUID[1000000 @patcnt]>>copy mx/makepattern sysexec dup begin" | |
137 | " PatternType 2 lt{PatternType @patcnt BBox aload pop XStep YStep PaintType mx " | |
138 | "aload pop 15(makepattern)prcmd :newpath matrix setmatrix dup PaintProc 0 1(mak" | |
139 | "epattern)prcmd @GD/@patcnt @patcnt 1 add put}if end end grestore}def/setpatter" | |
140 | "n{dup begin PatternType end 1 eq{begin PaintType 1 eq{XUID aload pop exch pop " | |
141 | "1}{:gsave[currentcolorspace aload length -1 roll pop]/setcolorspace sysexec/se" | |
142 | "tcolor sysexec XUID aload pop exch pop currentrgbcolor :grestore 4}ifelse(setp" | |
143 | "attern)prcmd currentcolorspace 0 get/Pattern ne{[/Pattern currentcolorspace]/s" | |
144 | "etcolorspace sysexec}if currentcolorspace @setcolorspace end}{/setpattern syse" | |
145 | "xec}ifelse}def/setcolor{dup type/dicttype eq{setpattern}{/setcolor sysexec/cur" | |
146 | "rentrgbcolor sysexec setrgbcolor}ifelse}def/setcolorspace{dup/setcolorspace sy" | |
147 | "sexec @setcolorspace}def/@setcolorspace{dup type/arraytype eq{0 get}if/Pattern" | |
148 | " eq{1}{0}ifelse 1(setcolorspace)prcmd}def/setgray 1 defpr/setcmykcolor 4 defpr" | |
149 | "/sethsbcolor 3 defpr/setrgbcolor 3 defpr/.setalphaisshape{@SD/.setalphaisshape" | |
150 | " known{dup/.setalphaisshape sysexec}if{1}{0}ifelse 1(setalphaisshape)prcmd}bin" | |
151 | "d def/.setfillconstantalpha{@SD/.setfillconstantalpha known{dup/.setfillconsta" | |
152 | "ntalpha sysexec}if 1(setfillconstantalpha)prcmd}bind def/.setstrokeconstantalp" | |
153 | "ha{@SD/.setstrokeconstantalpha known{dup/.setstrokeconstantalpha sysexec}if 1(" | |
154 | "setstrokeconstantalpha)prcmd}bind def/.setopacityalpha{false .setalphaisshape " | |
155 | "dup .setfillconstantalpha .setstrokeconstantalpha}bind def/.setshapealpha{true" | |
156 | " .setalphaisshape dup .setfillconstantalpha .setstrokeconstantalpha}bind def/." | |
157 | "setblendmode{dup/.setblendmode sysexec<</Normal 0/Compatible 0/Multiply 1/Scre" | |
158 | "en 2/Overlay 3/SoftLight 4/HardLight 5/ColorDodge 6/ColorBurn 7/Darken 8/Light" | |
159 | "en 9/Difference 10/Exclusion 11/Hue 12/Saturation 13/Color 14/Luminosity 15/Co" | |
160 | "mpatibleOverprint 16>>exch get 1(setblendmode)prcmd}def/@pdfpagecount{(r)file " | |
161 | "runpdfbegin pdfpagecount runpdfend}def/@pdfpagebox{(r)file runpdfbegin dup dup" | |
162 | " 1 lt exch pdfpagecount gt or{pop}{pdfgetpage/MediaBox pget pop aload pop}ifel" | |
163 | "se runpdfend}def DELAYBIND{.bindnow}if "; | |
160 | 164 |
133 | 133 | vector<string> util::split (const string &str, const string &sep) { |
134 | 134 | vector<string> parts; |
135 | 135 | if (str.empty() || sep.empty()) |
136 | parts.emplace_back(str); | |
136 | parts.push_back(str); | |
137 | 137 | else { |
138 | 138 | size_t left=0; |
139 | 139 | while (left <= str.length()) { |
140 | 140 | size_t right = str.find(sep, left); |
141 | 141 | if (right == string::npos) { |
142 | parts.emplace_back(str.substr(left)); | |
142 | parts.push_back(str.substr(left)); | |
143 | 143 | left = string::npos; |
144 | 144 | } |
145 | 145 | else { |
146 | parts.emplace_back(str.substr(left, right-left)); | |
146 | parts.push_back(str.substr(left, right-left)); | |
147 | 147 | left = right+sep.length(); |
148 | 148 | } |
149 | 149 | } |
24 | 24 | #include <memory> |
25 | 25 | #include <sstream> |
26 | 26 | #include <string> |
27 | #include <type_traits> | |
27 | 28 | #include <vector> |
28 | 29 | |
29 | 30 | namespace math { |
144 | 145 | return std::unique_ptr<T>{static_cast<T*>(old.release())}; |
145 | 146 | } |
146 | 147 | |
148 | template <typename T> | |
149 | struct set_const_of { | |
150 | template <typename U> | |
151 | struct by { | |
152 | using type = typename std::conditional< | |
153 | std::is_const<U>::value, | |
154 | typename std::add_const<T>::type, | |
155 | typename std::remove_const<T>::type | |
156 | >::type; | |
157 | }; | |
158 | }; | |
147 | 159 | } // namespace util |
148 | 160 | |
149 | 161 | #endif |
21 | 21 | #define VERSION_HPP |
22 | 22 | |
23 | 23 | constexpr const char *PROGRAM_NAME = "dvisvgm"; |
24 | constexpr const char *PROGRAM_VERSION = "2.11.1"; | |
24 | constexpr const char *PROGRAM_VERSION = "2.12"; | |
25 | 25 | |
26 | 26 | #endif |
27 | 27 |
151 | 151 | handler.processSpecial("point 1, 10, 10"); |
152 | 152 | handler.processSpecial("point 2, 100, 100"); |
153 | 153 | handler.processSpecial("line 1v, 2v, 10bp"); // cut line ends vertically |
154 | EXPECT_EQ(recorder.getPageXML(), "<g id='page1'>\n<polygon points='10,17.07 10,2.93 100,92.93 100,107.07'/>\n</g>"); | |
154 | EXPECT_EQ(recorder.getPageXML(), "<g id='page1'>\n<polygon points='10 17.07 10 2.93 100 92.93 100 107.07'/>\n</g>"); | |
155 | 155 | } |
156 | 156 | |
157 | 157 | |
159 | 159 | handler.processSpecial("point 1, 10, 10"); |
160 | 160 | handler.processSpecial("point 2, 100, 100"); |
161 | 161 | handler.processSpecial("line 1h, 2h, 10bp"); // cut line ends horizontally |
162 | EXPECT_EQ(recorder.getPageXML(), "<g id='page1'>\n<polygon points='2.93,10 17.07,10 107.07,100 92.93,100'/>\n</g>"); | |
162 | EXPECT_EQ(recorder.getPageXML(), "<g id='page1'>\n<polygon points='2.93 10 17.07 10 107.07 100 92.93 100'/>\n</g>"); | |
163 | 163 | } |
164 | 164 | |
165 | 165 | |
167 | 167 | handler.processSpecial("point 1, 10, 10"); |
168 | 168 | handler.processSpecial("point 2, 100, 100"); |
169 | 169 | handler.processSpecial("line 1h, 2v, 10bp"); // cut line ends horizontally |
170 | EXPECT_EQ(recorder.getPageXML(), "<g id='page1'>\n<polygon points='2.93,10 17.07,10 100,92.93 100,107.07'/>\n</g>"); | |
170 | EXPECT_EQ(recorder.getPageXML(), "<g id='page1'>\n<polygon points='2.93 10 17.07 10 100 92.93 100 107.07'/>\n</g>"); | |
171 | 171 | |
172 | 172 | recorder.clear(); |
173 | 173 | recorder.setColor(Color(0.0, 0.0, 1.0)); |
174 | 174 | handler.processSpecial("point 1, 10, 10"); |
175 | 175 | handler.processSpecial("point 2, 100, 100"); |
176 | 176 | handler.processSpecial("line 1v, 2h, 10bp"); // cut line ends horizontally |
177 | EXPECT_EQ(recorder.getPageXML(), "<g id='page1'>\n<polygon points='10,17.07 10,2.93 107.07,100 92.93,100' fill='#00f'/>\n</g>"); | |
177 | EXPECT_EQ(recorder.getPageXML(), "<g id='page1'>\n<polygon points='10 17.07 10 2.93 107.07 100 92.93 100' fill='#00f'/>\n</g>"); | |
178 | 178 | } |
179 | 179 | |
180 | 180 |
89 | 89 | } |
90 | 90 | |
91 | 91 | |
92 | TEST(FilePathTest, file3) { | |
93 | FilePath fp("/f.ext", true, "/x/y"); | |
94 | ASSERT_EQ(fp.absolute(), "/f.ext"); | |
95 | ASSERT_EQ(fp.relative("/a/b"), "../../f.ext"); | |
96 | } | |
97 | ||
98 | ||
92 | 99 | TEST(FilePathTest, autodetect) { |
93 | 100 | FileSystem::chdir(SRCDIR); |
94 | 101 | FilePath fp1("FilePathTest.cpp"); |
95 | 102 | ASSERT_TRUE(fp1.isFile()); |
96 | 103 | ASSERT_FALSE(fp1.empty()); |
97 | 104 | string cwd = FileSystem::getcwd(); |
105 | #ifdef _WIN32 | |
106 | if (cwd.length() >=2 && isalpha(cwd[0]) && cwd[1] == ':') | |
107 | cwd[0] = tolower(cwd[0]); | |
108 | #endif | |
98 | 109 | ASSERT_EQ(fp1.absolute(), cwd + "/FilePathTest.cpp") << "fp1=" << fp1.absolute(); |
99 | 110 | |
100 | 111 | FilePath fp2(""); |
101 | 112 | ASSERT_FALSE(fp2.isFile()); |
102 | 113 | ASSERT_FALSE(fp2.empty()); |
103 | ASSERT_EQ(fp2.absolute(), FileSystem::getcwd()); | |
114 | ASSERT_EQ(fp2.absolute(), cwd); | |
104 | 115 | } |
22 | 22 | #include "Font.hpp" |
23 | 23 | #include "FontManager.hpp" |
24 | 24 | |
25 | #ifndef SRCDIR | |
26 | #define SRCDIR "." | |
27 | #endif | |
28 | ||
29 | ||
25 | 30 | class FontManagerTest : public ::testing::Test { |
26 | 31 | public: |
27 | 32 | FontManagerTest () : fm(FontManager::instance()) { |
28 | 33 | fm.registerFont(10, "cmr10", 1274110073, 10, 10); |
29 | 34 | fm.registerFont(11, "cmr10", 1274110073, 10, 12); |
30 | 35 | fm.registerFont( 9, "cmr10", 1274110073, 10, 14); |
36 | fm.registerFont(12, SRCDIR"/data/lmmono12-regular.otf", 0, 12, _fontStyle, Color(.0, .0, 1.0)); | |
31 | 37 | } |
32 | 38 | |
33 | 39 | protected: |
34 | 40 | FontManager &fm; |
41 | FontStyle _fontStyle; | |
35 | 42 | }; |
36 | 43 | |
37 | 44 | |
39 | 46 | EXPECT_EQ(fm.fontID(10), 0); |
40 | 47 | EXPECT_EQ(fm.fontID(11), 1); |
41 | 48 | EXPECT_EQ(fm.fontID(9), 2); |
49 | EXPECT_EQ(fm.fontID(12), 3); | |
42 | 50 | EXPECT_EQ(fm.fontID(1), -1); |
43 | 51 | } |
44 | 52 | |
45 | 53 | |
46 | TEST_F(FontManagerTest, font_ID2) { | |
54 | TEST_F(FontManagerTest, fontID2) { | |
47 | 55 | EXPECT_EQ(fm.fontID("cmr10"), 0); |
48 | 56 | } |
49 | 57 | |
53 | 61 | EXPECT_TRUE(f1); |
54 | 62 | EXPECT_EQ(f1->name(), "cmr10"); |
55 | 63 | EXPECT_TRUE(dynamic_cast<const PhysicalFontImpl*>(f1)); |
64 | EXPECT_EQ(f1->color(), Color::BLACK); | |
56 | 65 | |
57 | 66 | const Font *f2 = fm.getFont(11); |
58 | 67 | EXPECT_TRUE(f2); |
60 | 69 | EXPECT_EQ(f2->name(), "cmr10"); |
61 | 70 | EXPECT_TRUE(dynamic_cast<const PhysicalFontProxy*>(f2)); |
62 | 71 | EXPECT_EQ(f2->uniqueFont(), f1); |
72 | EXPECT_EQ(f2->color(), Color::BLACK); | |
73 | ||
74 | const Font *f3 = fm.getFont(12); | |
75 | EXPECT_TRUE(f3); | |
76 | EXPECT_NE(f2, f3); | |
77 | EXPECT_EQ(f3->name(), "nf0"); | |
78 | EXPECT_TRUE(dynamic_cast<const NativeFontImpl*>(f3)); | |
79 | EXPECT_TRUE(dynamic_cast<const PhysicalFont*>(f3)); | |
80 | EXPECT_EQ(f3->uniqueFont(), f3); | |
81 | EXPECT_EQ(f3->color(), Color(.0, .0, 1.0)); | |
82 | } | |
83 | ||
84 | ||
85 | TEST_F(FontManagerTest, font_cast) { | |
86 | const Font *f1 = fm.getFont(10); | |
87 | EXPECT_TRUE(f1); | |
88 | EXPECT_EQ(font_cast<const PhysicalFont*>(f1), f1); | |
89 | EXPECT_EQ(font_cast<const NativeFont*>(f1), nullptr); | |
90 | EXPECT_EQ(font_cast<const VirtualFont*>(f1), nullptr); | |
91 | ||
92 | const Font *f2 = fm.getFont(11); | |
93 | EXPECT_TRUE(f2); | |
94 | EXPECT_EQ(font_cast<const PhysicalFont*>(f2), f2); | |
95 | EXPECT_EQ(font_cast<const NativeFont*>(f1), nullptr); | |
96 | EXPECT_EQ(font_cast<const VirtualFont*>(f1), nullptr); | |
97 | ||
98 | const Font *f3 = fm.getFont(12); | |
99 | EXPECT_TRUE(f3); | |
100 | EXPECT_EQ(font_cast<const PhysicalFont*>(f3), f3); | |
101 | EXPECT_EQ(font_cast<const NativeFont*>(f3), f3); | |
102 | EXPECT_EQ(font_cast<const VirtualFont*>(f3), nullptr); | |
63 | 103 | } |
64 | 104 | |
65 | 105 | |
66 | 106 | TEST_F(FontManagerTest, getFontById) { |
67 | 107 | EXPECT_EQ(fm.getFont(10), fm.getFontById(0)); |
68 | 108 | EXPECT_EQ(fm.getFont("cmr10"), fm.getFontById(0)); |
109 | EXPECT_EQ(fm.getFont(12), fm.getFontById(3)); | |
69 | 110 | } |
70 |
47 | 47 | |
48 | 48 | |
49 | 49 | TEST(MatrixTest, construct2) { |
50 | Matrix m1 = {1, 2, 3, 4, 5, 6, 7, 8, 9}; | |
50 | Matrix m1{1, 2, 3, 4, 5, 6, 7, 8, 9}; | |
51 | 51 | for (int row=0; row < 3; row++) |
52 | 52 | for (int col=0; col < 3; col++) |
53 | 53 | ASSERT_EQ(m1.get(row, col), 3*row+col+1) << "row=" << row << ", col=" << col; |
54 | 54 | |
55 | Matrix m2 = {1, 2, 3, 4, 5, 6}; | |
55 | Matrix m2{1, 2, 3, 4, 5, 6}; | |
56 | 56 | for (int row=0; row < 2; row++) |
57 | 57 | for (int col=0; col < 3; col++) |
58 | 58 | ASSERT_EQ(m2.get(row, col), 3*row+col+1) << "row=" << row << ", col=" << col; |
51 | 51 | void rotate (vector<double> &p) override {print("rotate", p);} |
52 | 52 | void save(std::vector<double> &p) override {print("save", p);} |
53 | 53 | void scale (vector<double> &p) override {print("scale", p);} |
54 | void setalphaisshape (vector<double> &p) override {print("setalphaisshape", p);} | |
54 | 55 | void setblendmode (vector<double> &p) override {print("setblendmode", p);} |
55 | 56 | void setcolorspace (vector<double> &p) override {print("setcolorspace", p);} |
56 | 57 | void setcmykcolor (vector<double> &p) override {print("setcmykcolor", p);} |
58 | 59 | void setfillconstantalpha (vector<double> &p) override {print("setfillconstantalpha", p);} |
59 | 60 | void setgray (vector<double> &p) override {print("setgray", p);} |
60 | 61 | void sethsbcolor (vector<double> &p) override {print("sethsbcolor", p);} |
61 | void setisshapealpha (vector<double> &p) override {print("setisshapealpha", p);} | |
62 | 62 | void setlinecap (vector<double> &p) override {print("setlinecap", p);} |
63 | 63 | void setlinejoin (vector<double> &p) override {print("setlinejoin", p);} |
64 | 64 | void setlinewidth (vector<double> &p) override {print("setlinewidth", p);} |
104 | 104 | actions.clear(); |
105 | 105 | |
106 | 106 | psi.execute("grestore "); |
107 | EXPECT_EQ(actions.result(), "setmatrix 1 0 0 1 0 0;applyscalevals 1 1 1;setlinewidth 1;setlinecap 0;setlinejoin 0;setmiterlimit 10;setcolorspace 0;setrgbcolor 0 0 0;setdash 0;grestore;"); | |
107 | if (psi.hasFullOpacitySupport()) | |
108 | EXPECT_EQ(actions.result(), "setmatrix 1 0 0 1 0 0;applyscalevals 1 1 1;setlinewidth 1;setlinecap 0;setlinejoin 0;setmiterlimit 10;setblendmode 0;setalphaisshape 0;setstrokeconstantalpha 1;setfillconstantalpha 1;setcolorspace 0;setrgbcolor 0 0 0;setdash 0;grestore;"); | |
109 | else | |
110 | EXPECT_EQ(actions.result(), "setmatrix 1 0 0 1 0 0;applyscalevals 1 1 1;setlinewidth 1;setlinecap 0;setlinejoin 0;setmiterlimit 10;setcolorspace 0;setrgbcolor 0 0 0;setdash 0;grestore;"); | |
108 | 111 | actions.clear(); |
109 | 112 | |
110 | 113 | psi.execute("1 setlinecap 5 setmiterlimit 0 1 0 setrgbcolor gsave 0 setlinecap 10 setmiterlimit "); |
112 | 115 | actions.clear(); |
113 | 116 | |
114 | 117 | psi.execute("grestore "); |
115 | EXPECT_EQ(actions.result(), "setmatrix 1 0 0 1 0 0;applyscalevals 1 1 1;setlinewidth 1;setlinecap 1;setlinejoin 0;setmiterlimit 5;setcolorspace 0;setrgbcolor 0 1 0;setdash 0;grestore;"); | |
118 | if (psi.hasFullOpacitySupport()) | |
119 | EXPECT_EQ(actions.result(), "setmatrix 1 0 0 1 0 0;applyscalevals 1 1 1;setlinewidth 1;setlinecap 1;setlinejoin 0;setmiterlimit 5;setblendmode 0;setalphaisshape 0;setstrokeconstantalpha 1;setfillconstantalpha 1;setcolorspace 0;setrgbcolor 0 1 0;setdash 0;grestore;"); | |
120 | else | |
121 | EXPECT_EQ(actions.result(), "setmatrix 1 0 0 1 0 0;applyscalevals 1 1 1;setlinewidth 1;setlinecap 1;setlinejoin 0;setmiterlimit 5;setcolorspace 0;setrgbcolor 0 1 0;setdash 0;grestore;"); | |
116 | 122 | } |
117 | 123 | |
118 | 124 |
153 | 153 | handler.processSpecial("pa", "1000 0"); |
154 | 154 | handler.processSpecial("fp"); |
155 | 155 | EXPECT_EQ(recorder.getXMLSnippet(), |
156 | "<polyline fill='none' stroke-linecap='round' points='0,0 72,72 72,0' stroke='#000' stroke-width='1'/>" | |
156 | "<polyline fill='none' stroke-linecap='round' points='0 0 72 72 72 0' stroke='#000'/>" | |
157 | 157 | ); |
158 | 158 | EXPECT_DOUBLE_EQ(handler.penwidth(), 1.0); |
159 | 159 | EXPECT_LT(handler.grayLevel(), 0); |
168 | 168 | handler.processSpecial("pa", "0 0"); |
169 | 169 | handler.processSpecial("fp"); |
170 | 170 | EXPECT_EQ(recorder.getXMLSnippet(), |
171 | "<polygon fill='none' points='0,0 72,72 72,0' stroke='#000' stroke-width='1'/>" | |
171 | "<polygon fill='none' points='0 0 72 72 72 0' stroke='#000'/>" | |
172 | 172 | ); |
173 | 173 | EXPECT_DOUBLE_EQ(handler.penwidth(), 1.0); |
174 | 174 | EXPECT_LT(handler.grayLevel(), 0); |
183 | 183 | handler.processSpecial("wh"); |
184 | 184 | handler.processSpecial("fp"); |
185 | 185 | EXPECT_EQ(recorder.getXMLSnippet(), |
186 | "<polygon fill='#fff' points='0,0 72,72 72,0' stroke='#000' stroke-width='1'/>" | |
186 | "<polygon fill='#fff' points='0 0 72 72 72 0' stroke='#000'/>" | |
187 | 187 | ); |
188 | 188 | EXPECT_DOUBLE_EQ(handler.penwidth(), 1.0); |
189 | 189 | EXPECT_LT(handler.grayLevel(), 0); |
195 | 195 | handler.processSpecial("wh"); |
196 | 196 | handler.processSpecial("ip"); |
197 | 197 | EXPECT_EQ(recorder.getXMLSnippet(), |
198 | "<polygon fill='#fff' points='0,0 72,72 72,0'/>" | |
198 | "<polygon fill='#fff' points='0 0 72 72 72 0'/>" | |
199 | 199 | ); |
200 | 200 | EXPECT_DOUBLE_EQ(handler.penwidth(), 1.0); |
201 | 201 | EXPECT_LT(handler.grayLevel(), 0); |
210 | 210 | handler.processSpecial("wh"); |
211 | 211 | handler.processSpecial("da", "2"); |
212 | 212 | EXPECT_EQ(recorder.getXMLSnippet(), |
213 | "<polygon fill='#fff' points='0,0 72,72 72,0' stroke='#000' stroke-width='1' stroke-dasharray='144'/>" | |
213 | "<polygon fill='#fff' points='0 0 72 72 72 0' stroke='#000' stroke-dasharray='144'/>" | |
214 | 214 | ); |
215 | 215 | EXPECT_DOUBLE_EQ(handler.penwidth(), 1.0); |
216 | 216 | EXPECT_LT(handler.grayLevel(), 0); |
226 | 226 | handler.processSpecial("wh"); |
227 | 227 | handler.processSpecial("dt", "2 2"); |
228 | 228 | EXPECT_EQ(recorder.getXMLSnippet(), |
229 | "<polygon fill='#fff' points='0,0 72,72 72,0' stroke='#000' stroke-width='36' stroke-dasharray='36 144'/>" | |
229 | "<polygon fill='#fff' points='0 0 72 72 72 0' stroke='#000' stroke-width='36' stroke-dasharray='36 144'/>" | |
230 | 230 | ); |
231 | 231 | EXPECT_DOUBLE_EQ(handler.penwidth(), 1.0); |
232 | 232 | EXPECT_LT(handler.grayLevel(), 0); |
238 | 238 | handler.processSpecial("pa", "1000 1000"); |
239 | 239 | handler.processSpecial("sp"); |
240 | 240 | EXPECT_EQ(recorder.getXMLSnippet(), |
241 | "<polyline fill='none' stroke-linecap='round' points='0,0 72,72' stroke='#000' stroke-width='1'/>" | |
241 | "<polyline fill='none' stroke-linecap='round' points='0 0 72 72' stroke='#000'/>" | |
242 | 242 | ); |
243 | 243 | recorder.clear(); |
244 | 244 | handler.processSpecial("pa", "0 0"); |
249 | 249 | handler.processSpecial("pa", "1000 500"); |
250 | 250 | handler.processSpecial("sp"); |
251 | 251 | EXPECT_EQ(recorder.getXMLSnippet(), |
252 | "<path fill='none' d='M0 0L36 36Q72 72 90 54T126 54T180 108T144 90L72 36' stroke='#000' stroke-width='1'/>" | |
252 | "<path fill='none' d='M0 0L36 36Q72 72 90 54T126 54T180 108T144 90L72 36' stroke='#000'/>" | |
253 | 253 | ); |
254 | 254 | EXPECT_DOUBLE_EQ(handler.penwidth(), 1.0); |
255 | 255 | EXPECT_LT(handler.grayLevel(), 0); |
265 | 265 | handler.processSpecial("pa", "0 0"); |
266 | 266 | handler.processSpecial("sp", "1"); |
267 | 267 | EXPECT_EQ(recorder.getXMLSnippet(), |
268 | "<path fill='none' d='M0 0L36 36Q72 72 90 54T126 54T180 108T108 72Z' stroke='#000' stroke-width='1' stroke-dasharray='72'/>" | |
268 | "<path fill='none' d='M0 0L36 36Q72 72 90 54T126 54T180 108T108 72Z' stroke='#000' stroke-dasharray='72'/>" | |
269 | 269 | ); |
270 | 270 | EXPECT_DOUBLE_EQ(handler.penwidth(), 1.0); |
271 | 271 | EXPECT_LT(handler.grayLevel(), 0); |
281 | 281 | handler.processSpecial("pa", "1000 500"); |
282 | 282 | handler.processSpecial("sp", "-1"); |
283 | 283 | EXPECT_EQ(recorder.getXMLSnippet(), |
284 | "<path fill='none' d='M0 0L36 36Q72 72 90 54T126 54T180 108T144 90L72 36' stroke='#000' stroke-width='1' stroke-dasharray='1 72'/>" | |
284 | "<path fill='none' d='M0 0L36 36Q72 72 90 54T126 54T180 108T144 90L72 36' stroke='#000' stroke-dasharray='1 72'/>" | |
285 | 285 | ); |
286 | 286 | EXPECT_DOUBLE_EQ(handler.penwidth(), 1.0); |
287 | 287 | EXPECT_LT(handler.grayLevel(), 0); |
291 | 291 | TEST_F(TpicSpecialTest, stroke_ellipse) { |
292 | 292 | handler.processSpecial("ar", "0 0 500 500 0 7"); |
293 | 293 | EXPECT_EQ(recorder.getXMLSnippet(), |
294 | "<circle cx='0' cy='0' r='36' stroke-width='1' stroke='#000' fill='none'/>" | |
294 | "<circle cx='0' cy='0' r='36' stroke='#000' fill='none'/>" | |
295 | 295 | ); |
296 | 296 | recorder.clear(); |
297 | 297 | handler.processSpecial("ar", "0 0 1000 500 0 7"); |
298 | 298 | EXPECT_EQ(recorder.getXMLSnippet(), |
299 | "<ellipse cx='0' cy='0' rx='72' ry='36' stroke-width='1' stroke='#000' fill='none'/>" | |
299 | "<ellipse cx='0' cy='0' rx='72' ry='36' stroke='#000' fill='none'/>" | |
300 | 300 | ); |
301 | 301 | recorder.clear(); |
302 | 302 | handler.processSpecial("pn", "100"); |
311 | 311 | handler.processSpecial("bk"); |
312 | 312 | handler.processSpecial("ia", "0 0 500 500 0 7"); |
313 | 313 | EXPECT_EQ(recorder.getXMLSnippet(), |
314 | "<circle cx='0' cy='0' r='36' fill='#000'/>" | |
314 | "<circle cx='0' cy='0' r='36'/>" | |
315 | 315 | ); |
316 | 316 | recorder.clear(); |
317 | 317 | handler.processSpecial("bk"); |
318 | 318 | handler.processSpecial("ia", "0 0 1000 500 0 7"); |
319 | 319 | EXPECT_EQ(recorder.getXMLSnippet(), |
320 | "<ellipse cx='0' cy='0' rx='72' ry='36' fill='#000'/>" | |
320 | "<ellipse cx='0' cy='0' rx='72' ry='36'/>" | |
321 | 321 | ); |
322 | 322 | recorder.clear(); |
323 | 323 | handler.processSpecial("pn", "100"); |
332 | 332 | TEST_F(TpicSpecialTest, stroke_arc) { |
333 | 333 | handler.processSpecial("ar", "0 0 1000 500 0 "+to_string(math::PI/4)); |
334 | 334 | EXPECT_EQ(recorder.getXMLSnippet(), |
335 | "<path d='M72 0A72 36 0 0 1 50.91 25.46' stroke-width='1' stroke='#000' stroke-linecap='round' fill='none'/>" | |
335 | "<path d='M72 0A72 36 0 0 1 50.91 25.46' stroke='#000' stroke-linecap='round' fill='none'/>" | |
336 | 336 | ); |
337 | 337 | recorder.clear(); |
338 | 338 | handler.processSpecial("ar", "0 0 1000 500 0 "+to_string(math::PI/2)); |
339 | 339 | EXPECT_EQ(recorder.getXMLSnippet(), |
340 | "<path d='M72 0A72 36 0 0 1 0 36' stroke-width='1' stroke='#000' stroke-linecap='round' fill='none'/>" | |
340 | "<path d='M72 0A72 36 0 0 1 0 36' stroke='#000' stroke-linecap='round' fill='none'/>" | |
341 | 341 | ); |
342 | 342 | recorder.clear(); |
343 | 343 | handler.processSpecial("ar", "0 0 1000 500 0 "+to_string(3*math::PI/4)); |
344 | 344 | EXPECT_EQ(recorder.getXMLSnippet(), |
345 | "<path d='M72 0A72 36 0 0 1-50.91 25.46' stroke-width='1' stroke='#000' stroke-linecap='round' fill='none'/>" | |
345 | "<path d='M72 0A72 36 0 0 1-50.91 25.46' stroke='#000' stroke-linecap='round' fill='none'/>" | |
346 | 346 | ); |
347 | 347 | recorder.clear(); |
348 | 348 | handler.processSpecial("ar", "0 0 1000 500 0 "+to_string(math::PI)); |
349 | 349 | EXPECT_EQ(recorder.getXMLSnippet(), |
350 | "<path d='M72 0A72 36 0 1 1-72 0' stroke-width='1' stroke='#000' stroke-linecap='round' fill='none'/>" | |
350 | "<path d='M72 0A72 36 0 1 1-72 0' stroke='#000' stroke-linecap='round' fill='none'/>" | |
351 | 351 | ); |
352 | 352 | recorder.clear(); |
353 | 353 | handler.processSpecial("ar", "0 0 1000 500 0 "+to_string(5*math::PI/4)); |
354 | 354 | EXPECT_EQ(recorder.getXMLSnippet(), |
355 | "<path d='M72 0A72 36 0 1 1-50.91-25.46' stroke-width='1' stroke='#000' stroke-linecap='round' fill='none'/>" | |
355 | "<path d='M72 0A72 36 0 1 1-50.91-25.46' stroke='#000' stroke-linecap='round' fill='none'/>" | |
356 | 356 | ); |
357 | 357 | recorder.clear(); |
358 | 358 | handler.processSpecial("ar", "0 0 1000 500 0 "+to_string(3*math::PI/2)); |
359 | 359 | EXPECT_EQ(recorder.getXMLSnippet(), |
360 | "<path d='M72 0A72 36 0 1 1 0-36' stroke-width='1' stroke='#000' stroke-linecap='round' fill='none'/>" | |
360 | "<path d='M72 0A72 36 0 1 1 0-36' stroke='#000' stroke-linecap='round' fill='none'/>" | |
361 | 361 | ); |
362 | 362 | recorder.clear(); |
363 | 363 | handler.processSpecial("ar", "0 0 1000 500 0 "+to_string(-3*math::PI/2)); |
364 | 364 | EXPECT_EQ(recorder.getXMLSnippet(), |
365 | "<path d='M72 0A72 36 0 0 1 0 36' stroke-width='1' stroke='#000' stroke-linecap='round' fill='none'/>" | |
365 | "<path d='M72 0A72 36 0 0 1 0 36' stroke='#000' stroke-linecap='round' fill='none'/>" | |
366 | 366 | ); |
367 | 367 | } |
368 | 368 |
12 | 12 | frktest.dvi \ |
13 | 13 | frktest-nf-cmp.svg \ |
14 | 14 | frktest-wf-cmp.svg \ |
15 | lmmono12-regular.otf \ | |
15 | 16 | sample.dvi \ |
16 | 17 | sample-nf-cmp.svg \ |
17 | 18 | sample.sfd \ |
Binary diff not shown
0 | 0 | #include <winver.h> |
1 | 1 | |
2 | #define DVISVGM_VERSION 2,11,1,0 | |
3 | #define DVISVGM_VERSION_STR "2.11.1\0" | |
2 | #define DVISVGM_VERSION 2,12,0,0 | |
3 | #define DVISVGM_VERSION_STR "2.12\0" | |
4 | 4 | |
5 | 5 | VS_VERSION_INFO VERSIONINFO |
6 | 6 | FILEVERSION DVISVGM_VERSION |
208 | 208 | <ClCompile Include="..\src\EllipticalArc.cpp" /> |
209 | 209 | <ClCompile Include="..\src\HashFunction.cpp" /> |
210 | 210 | <ClCompile Include="..\src\ImageToSVG.cpp" /> |
211 | <ClCompile Include="..\src\Opacity.cpp" /> | |
211 | 212 | <ClCompile Include="..\src\optimizer\AttributeExtractor.cpp" /> |
212 | 213 | <ClCompile Include="..\src\optimizer\GroupCollapser.cpp" /> |
213 | 214 | <ClCompile Include="..\src\optimizer\RedundantElementRemover.cpp" /> |
287 | 288 | <ClCompile Include="..\src\SVGCharHandler.cpp" /> |
288 | 289 | <ClCompile Include="..\src\SVGCharHandlerFactory.cpp" /> |
289 | 290 | <ClCompile Include="..\src\SVGCharPathHandler.cpp" /> |
291 | <ClCompile Include="..\src\SVGElement.cpp" /> | |
290 | 292 | <ClCompile Include="..\src\SVGSingleCharTextHandler.cpp" /> |
291 | 293 | <ClCompile Include="..\src\SVGCharTspanTextHandler.cpp" /> |
292 | 294 | <ClCompile Include="..\src\SVGOutput.cpp" /> |
328 | 330 | <ClInclude Include="..\src\HashFunction.hpp" /> |
329 | 331 | <ClInclude Include="..\src\ImageToSVG.hpp" /> |
330 | 332 | <ClInclude Include="..\src\MD5HashFunction.hpp" /> |
333 | <ClInclude Include="..\src\Opacity.hpp" /> | |
331 | 334 | <ClInclude Include="..\src\optimizer\AttributeExtractor.hpp" /> |
332 | 335 | <ClInclude Include="..\src\optimizer\DependencyGraph.hpp" /> |
333 | 336 | <ClInclude Include="..\src\optimizer\GroupCollapser.hpp" /> |
373 | 376 | <ClInclude Include="..\src\SVGCharHandler.hpp" /> |
374 | 377 | <ClInclude Include="..\src\SVGCharHandlerFactory.hpp" /> |
375 | 378 | <ClInclude Include="..\src\SVGCharPathHandler.hpp" /> |
379 | <ClInclude Include="..\src\SVGElement.hpp" /> | |
376 | 380 | <ClInclude Include="..\src\SVGSingleCharTextHandler.hpp" /> |
377 | 381 | <ClInclude Include="..\src\SVGCharTspanTextHandler.hpp" /> |
378 | 382 | <ClInclude Include="..\src\SVGOutput.hpp" /> |
331 | 331 | <ClCompile Include="..\src\optimizer\TextSimplifier.cpp"> |
332 | 332 | <Filter>Source Files\optimizer</Filter> |
333 | 333 | </ClCompile> |
334 | <ClCompile Include="..\src\Opacity.cpp"> | |
335 | <Filter>Source Files</Filter> | |
336 | </ClCompile> | |
337 | <ClCompile Include="..\src\SVGElement.cpp"> | |
338 | <Filter>Source Files</Filter> | |
339 | </ClCompile> | |
334 | 340 | </ItemGroup> |
335 | 341 | <ItemGroup> |
336 | 342 | <ClInclude Include="..\src\BgColorSpecialHandler.hpp"> |
674 | 680 | </ClInclude> |
675 | 681 | <ClInclude Include="..\src\optimizer\TextSimplifier.hpp"> |
676 | 682 | <Filter>Header Files\optimizer</Filter> |
683 | </ClInclude> | |
684 | <ClInclude Include="..\src\Opacity.hpp"> | |
685 | <Filter>Header Files</Filter> | |
686 | </ClInclude> | |
687 | <ClInclude Include="..\src\SVGElement.hpp"> | |
688 | <Filter>Header Files</Filter> | |
677 | 689 | </ClInclude> |
678 | 690 | </ItemGroup> |
679 | 691 | <ItemGroup> |
157 | 157 | <ItemGroup> |
158 | 158 | <ClCompile Include="src\autofit\autofit.c" /> |
159 | 159 | <ClCompile Include="src\base\ftcid.c" /> |
160 | <ClCompile Include="src\base\ftfntfmt.c" /> | |
161 | 160 | <ClCompile Include="src\bdf\bdf.c" /> |
162 | 161 | <ClCompile Include="src\cff\cff.c" /> |
163 | 162 | <ClCompile Include="src\base\ftbase.c" /> |
172 | 171 | <ClCompile Include="src\lzw\ftlzw.c" /> |
173 | 172 | <ClCompile Include="src\base\ftstroke.c" /> |
174 | 173 | <ClCompile Include="src\base\ftsystem.c" /> |
174 | <ClCompile Include="src\sdf\ftsdfcommon.c" /> | |
175 | <ClCompile Include="src\sdf\sdf.c" /> | |
175 | 176 | <ClCompile Include="src\smooth\smooth.c" /> |
176 | 177 | <ClCompile Include="src\base\ftbbox.c" /> |
177 | 178 | <ClCompile Include="src\base\ftmm.c" /> |
179 | 180 | <ClCompile Include="src\base\ftsynth.c" /> |
180 | 181 | <ClCompile Include="src\base\fttype1.c" /> |
181 | 182 | <ClCompile Include="src\base\ftwinfnt.c" /> |
182 | <ClCompile Include="src\base\ftlcdfil.c" /> | |
183 | 183 | <ClCompile Include="src\base\ftgxval.c" /> |
184 | 184 | <ClCompile Include="src\base\ftotval.c" /> |
185 | 185 | <ClCompile Include="src\base\ftpatent.c" /> |
79 | 79 | <ClCompile Include="src\base\ftxf86.c"> |
80 | 80 | <Filter>Source Files\FT_MODULES</Filter> |
81 | 81 | </ClCompile> |
82 | <ClCompile Include="src\base\ftlcdfil.c"> | |
83 | <Filter>Source Files\FT_MODULES</Filter> | |
84 | </ClCompile> | |
85 | 82 | <ClCompile Include="src\base\ftgxval.c"> |
86 | 83 | <Filter>Source Files\FT_MODULES</Filter> |
87 | 84 | </ClCompile> |