From 7bf00f1510b86cee76ba6d035199ed8bf62b0436 Mon Sep 17 00:00:00 2001 From: Martin Gieseking Date: Fri, 13 Oct 2017 13:37:12 +0200 Subject: [PATCH] added evaluation of PDF hyperlink specials (closes #74) --- AUTHORS | 23 + configure.ac | 1 + doc/dvisvgm.txt.in | 16 +- libs/Makefile.am | 2 +- libs/variant/CMakeLists.txt | 1 + libs/variant/LICENSE.md | 23 + libs/variant/Makefile.am | 7 + libs/variant/include/mpark/in_place.hpp | 35 + libs/variant/include/mpark/lib.hpp | 437 ++++++ libs/variant/include/mpark/variant.hpp | 1872 +++++++++++++++++++++++ src/HyperlinkManager.cpp | 31 +- src/HyperlinkManager.hpp | 9 +- src/InputReader.hpp | 1 + src/Makefile.am | 5 +- src/PDFParser.cpp | 468 ++++++ src/PDFParser.hpp | 186 +++ src/PdfSpecialHandler.cpp | 254 ++- src/PdfSpecialHandler.hpp | 22 +- tests/Makefile.am | 8 +- tests/PDFParserTest.cpp | 239 +++ tests/SpecialManagerTest.cpp | 4 +- tests/create-makefile | 2 +- vc/dvisvgm.vcxproj | 10 +- vc/dvisvgm.vcxproj.filters | 6 + 24 files changed, 3599 insertions(+), 63 deletions(-) create mode 100644 libs/variant/CMakeLists.txt create mode 100644 libs/variant/LICENSE.md create mode 100644 libs/variant/Makefile.am create mode 100644 libs/variant/include/mpark/in_place.hpp create mode 100644 libs/variant/include/mpark/lib.hpp create mode 100644 libs/variant/include/mpark/variant.hpp create mode 100644 src/PDFParser.cpp create mode 100644 src/PDFParser.hpp create mode 100644 tests/PDFParserTest.cpp diff --git a/AUTHORS b/AUTHORS index 28871240..6fe5ecc9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,3 +1,26 @@ Martin Gieseking author of dvisvgm +Yann Collet + libs/xxHash/xxhash.* + https://github.com/Cyan4973/xxHash + +Google Inc. + libs/woff2/brotli/* + libs/woff2/include/* + libs/woff2/src/* + https://github.com/google/brotli + https://github.com/google/woff2 + +Angus Johnson + libs/clipper/clipper.* + https://sourceforge.net/projects/polyclipping/ + +Michael Park + libs/variant/include/mpark/* + https://github.com/mpark/variant + +George Williams + libs/ff-woff/* + https://github.com/fontforge/fontforge + diff --git a/configure.ac b/configure.ac index ea6642da..4c041bb5 100644 --- a/configure.ac +++ b/configure.ac @@ -189,6 +189,7 @@ AC_CONFIG_FILES([ libs/Makefile libs/clipper/Makefile libs/ff-woff/Makefile + libs/variant/Makefile libs/woff2/Makefile libs/woff2/brotli/Makefile libs/xxHash/Makefile diff --git a/doc/dvisvgm.txt.in b/doc/dvisvgm.txt.in index 452febb7..c4dbbc24 100644 --- a/doc/dvisvgm.txt.in +++ b/doc/dvisvgm.txt.in @@ -657,12 +657,16 @@ the command-line. Otherwise, dvisvgm ignores them and computes tight bounding bo *pdf*:: pdfTeX and dvipdfmx introduced several special commands related to the generation of PDF files. Currently, -only 'pdf:mapfile', 'pdf:mapline', and 'pdf:pagesize' are supported by dvisvgm. The latter is similar to the -'papersize' special (see above) which specifies the size of the current and all folowing pages. In order to -actually apply the extents to the generated SVG files, option *--bbox=papersize* must be given. -The other two PDF specials allow modifying the font map tree during while processing the DVI file. They are -used by CTeX, for example. dvisvgm supports both, the dvips and dvipdfm font map format. For further information -on the command syntax and semantics, see the documentation of +\pdfmapfile+ in the +only 'pdf:mapfile', 'pdf:mapline', 'pdf:pagesize', and PDF hyperlink specials are supported by dvisvgm. +The latter are the PDF pendants to the HTML HyperTeX specials generated by the hyperref package in PDF mode. ++ +'pdf:pagesize' is similar to the 'papersize' special (see above) which specifies the size of the current +and all folowing pages. In order to actually apply the extents to the generated SVG files, +option *--bbox=papersize* must be given. ++ +'pdf:mapfile' and 'pdf:mapline' allow modifying the font map tree while processing the DVI file. +They are used by CTeX, for example. dvisvgm supports both, the dvips and dvipdfm font map format. For further +information on the command syntax and semantics, see the documentation of +\pdfmapfile+ in the http://www.ctan.org/pkg/pdftex[pdfTeX user manual]. *ps*:: diff --git a/libs/Makefile.am b/libs/Makefile.am index 52d81d8b..dfcc6fb2 100644 --- a/libs/Makefile.am +++ b/libs/Makefile.am @@ -3,7 +3,7 @@ ## ## Process this file with automake. -SUBDIRS = clipper xxHash +SUBDIRS = clipper variant xxHash if ENABLE_WOFF SUBDIRS += ff-woff woff2 diff --git a/libs/variant/CMakeLists.txt b/libs/variant/CMakeLists.txt new file mode 100644 index 00000000..e1b0cb76 --- /dev/null +++ b/libs/variant/CMakeLists.txt @@ -0,0 +1 @@ +set(VARIANT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include CACHE PATH "variant include directory") diff --git a/libs/variant/LICENSE.md b/libs/variant/LICENSE.md new file mode 100644 index 00000000..36b7cd93 --- /dev/null +++ b/libs/variant/LICENSE.md @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/libs/variant/Makefile.am b/libs/variant/Makefile.am new file mode 100644 index 00000000..ff07a8cf --- /dev/null +++ b/libs/variant/Makefile.am @@ -0,0 +1,7 @@ +noinst_HEADERS = \ + include/mpark/config.hpp \ + include/mpark/in_place.hpp \ + include/mpark/lib.hpp \ + include/mpark/variant.hpp + +EXTRA_DIST = LICENSE.md diff --git a/libs/variant/include/mpark/in_place.hpp b/libs/variant/include/mpark/in_place.hpp new file mode 100644 index 00000000..56cae131 --- /dev/null +++ b/libs/variant/include/mpark/in_place.hpp @@ -0,0 +1,35 @@ +// MPark.Variant +// +// Copyright Michael Park, 2015-2017 +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt) + +#ifndef MPARK_IN_PLACE_HPP +#define MPARK_IN_PLACE_HPP + +#include + +#include "config.hpp" + +namespace mpark { + + struct in_place_t { explicit in_place_t() = default; }; + + template + struct in_place_index_t { explicit in_place_index_t() = default; }; + + template + struct in_place_type_t { explicit in_place_type_t() = default; }; + +#ifdef MPARK_VARIABLE_TEMPLATES + constexpr in_place_t in_place{}; + + template constexpr in_place_index_t in_place_index{}; + + template constexpr in_place_type_t in_place_type{}; +#endif + +} // namespace mpark + +#endif // MPARK_IN_PLACE_HPP diff --git a/libs/variant/include/mpark/lib.hpp b/libs/variant/include/mpark/lib.hpp new file mode 100644 index 00000000..a9b3dee9 --- /dev/null +++ b/libs/variant/include/mpark/lib.hpp @@ -0,0 +1,437 @@ +// MPark.Variant +// +// Copyright Michael Park, 2015-2017 +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt) + +#ifndef MPARK_LIB_HPP +#define MPARK_LIB_HPP + +#include +#include +#include +#include + +#include "config.hpp" + +#define RETURN(...) \ + noexcept(noexcept(__VA_ARGS__)) -> decltype(__VA_ARGS__) { \ + return __VA_ARGS__; \ + } + +namespace mpark { + namespace lib { + template + struct identity { using type = T; }; + + inline namespace cpp14 { + template + struct array { + constexpr const T &operator[](std::size_t index) const { + return data[index]; + } + + T data[N == 0 ? 1 : N]; + }; + + template + using add_pointer_t = typename std::add_pointer::type; + + template + using common_type_t = typename std::common_type::type; + + template + using decay_t = typename std::decay::type; + + template + using enable_if_t = typename std::enable_if::type; + + template + using remove_const_t = typename std::remove_const::type; + + template + using remove_reference_t = typename std::remove_reference::type; + + template + inline constexpr T &&forward(remove_reference_t &t) noexcept { + return static_cast(t); + } + + template + inline constexpr T &&forward(remove_reference_t &&t) noexcept { + static_assert(!std::is_lvalue_reference::value, + "can not forward an rvalue as an lvalue"); + return static_cast(t); + } + + template + inline constexpr remove_reference_t &&move(T &&t) noexcept { + return static_cast &&>(t); + } + +#ifdef MPARK_INTEGER_SEQUENCE + using std::integer_sequence; + using std::index_sequence; + using std::make_index_sequence; + using std::index_sequence_for; +#else + template + struct integer_sequence { + using value_type = T; + static constexpr std::size_t size() noexcept { return sizeof...(Is); } + }; + + template + using index_sequence = integer_sequence; + + template + struct make_index_sequence_concat; + + template + struct make_index_sequence_concat, + index_sequence> + : identity> {}; + + template + struct make_index_sequence_impl; + + template + using make_index_sequence = typename make_index_sequence_impl::type; + + template + struct make_index_sequence_impl + : make_index_sequence_concat, + make_index_sequence> {}; + + template <> + struct make_index_sequence_impl<0> : identity> {}; + + template <> + struct make_index_sequence_impl<1> : identity> {}; + + template + using index_sequence_for = make_index_sequence; +#endif + + // +#ifdef MPARK_TRANSPARENT_OPERATORS + using equal_to = std::equal_to<>; +#else + struct equal_to { + template + inline constexpr auto operator()(Lhs &&lhs, Rhs &&rhs) const + RETURN(lib::forward(lhs) == lib::forward(rhs)) + }; +#endif + +#ifdef MPARK_TRANSPARENT_OPERATORS + using not_equal_to = std::not_equal_to<>; +#else + struct not_equal_to { + template + inline constexpr auto operator()(Lhs &&lhs, Rhs &&rhs) const + RETURN(lib::forward(lhs) != lib::forward(rhs)) + }; +#endif + +#ifdef MPARK_TRANSPARENT_OPERATORS + using less = std::less<>; +#else + struct less { + template + inline constexpr auto operator()(Lhs &&lhs, Rhs &&rhs) const + RETURN(lib::forward(lhs) < lib::forward(rhs)) + }; +#endif + +#ifdef MPARK_TRANSPARENT_OPERATORS + using greater = std::greater<>; +#else + struct greater { + template + inline constexpr auto operator()(Lhs &&lhs, Rhs &&rhs) const + RETURN(lib::forward(lhs) > lib::forward(rhs)) + }; +#endif + +#ifdef MPARK_TRANSPARENT_OPERATORS + using less_equal = std::less_equal<>; +#else + struct less_equal { + template + inline constexpr auto operator()(Lhs &&lhs, Rhs &&rhs) const + RETURN(lib::forward(lhs) <= lib::forward(rhs)) + }; +#endif + +#ifdef MPARK_TRANSPARENT_OPERATORS + using greater_equal = std::greater_equal<>; +#else + struct greater_equal { + template + inline constexpr auto operator()(Lhs &&lhs, Rhs &&rhs) const + RETURN(lib::forward(lhs) >= lib::forward(rhs)) + }; +#endif + } // namespace cpp14 + + inline namespace cpp17 { + + // + template + using bool_constant = std::integral_constant; + + template + struct voider : identity {}; + + template + using void_t = typename voider::type; + + namespace detail { + namespace swappable { + + using std::swap; + + template + struct is_swappable_impl { + private: + template (), + std::declval()))> + inline static std::true_type test(int); + + template + inline static std::false_type test(...); + + public: + using type = decltype(test(0)); + }; + + template + using is_swappable = typename is_swappable_impl::type; + + template ::value> + struct is_nothrow_swappable { + static constexpr bool value = + noexcept(swap(std::declval(), std::declval())); + }; + + template + struct is_nothrow_swappable : std::false_type {}; + + } // namespace swappable + } // namespace detail + + template + using is_swappable = detail::swappable::is_swappable; + + template + using is_nothrow_swappable = detail::swappable::is_nothrow_swappable; + + // +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4100) +#endif + template + inline constexpr auto invoke(F &&f, As &&... as) + RETURN(lib::forward(f)(lib::forward(as)...)) +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + template + inline constexpr auto invoke(T B::*pmv, D &&d) + RETURN(lib::forward(d).*pmv) + + template + inline constexpr auto invoke(Pmv pmv, Ptr &&ptr) + RETURN((*lib::forward(ptr)).*pmv) + + template + inline constexpr auto invoke(T B::*pmf, D &&d, As &&... as) + RETURN((lib::forward(d).*pmf)(lib::forward(as)...)) + + template + inline constexpr auto invoke(Pmf pmf, Ptr &&ptr, As &&... as) + RETURN(((*lib::forward(ptr)).*pmf)(lib::forward(as)...)) + + namespace detail { + + template + struct invoke_result {}; + + template + struct invoke_result(), std::declval()...))>, + F, + Args...> + : identity(), std::declval()...))> {}; + + } // namespace detail + + template + using invoke_result = detail::invoke_result; + + template + using invoke_result_t = typename invoke_result::type; + + namespace detail { + + template + struct is_invocable : std::false_type {}; + + template + struct is_invocable>, F, Args...> + : std::true_type {}; + + template + struct is_invocable_r : std::false_type {}; + + template + struct is_invocable_r>, + R, + F, + Args...> + : std::is_convertible, R> {}; + + } // namespace detail + + template + using is_invocable = detail::is_invocable; + + template + using is_invocable_r = detail::is_invocable_r; + + // +#ifdef MPARK_BUILTIN_ADDRESSOF + template + inline constexpr T *addressof(T &arg) { + return __builtin_addressof(arg); + } +#else + namespace detail { + + namespace has_addressof_impl { + + struct fail; + + template + inline fail operator&(T &&); + + template + inline static constexpr bool impl() { + return (std::is_class::value || std::is_union::value) && + !std::is_same()), fail>::value; + } + + } // namespace has_addressof_impl + + template + using has_addressof = bool_constant()>; + + template + inline constexpr T *addressof(T &arg, std::true_type) { + return std::addressof(arg); + } + + template + inline constexpr T *addressof(T &arg, std::false_type) { + return &arg; + } + + } // namespace detail + + template + inline constexpr T *addressof(T &arg) { + return detail::addressof(arg, detail::has_addressof{}); + } +#endif + + template + inline constexpr T *addressof(const T &&) = delete; + + } // namespace cpp17 + + template + struct remove_all_extents : identity {}; + + template + struct remove_all_extents> : remove_all_extents {}; + + template + using remove_all_extents_t = typename remove_all_extents::type; + + template + using size_constant = std::integral_constant; + + template + using bool_sequence = integer_sequence; + + template + struct indexed_type : size_constant, identity {}; + + template + using all = + std::is_same, bool_sequence>; + +#ifdef MPARK_TYPE_PACK_ELEMENT + template + using type_pack_element_t = __type_pack_element; +#else + template + struct type_pack_element_impl { + private: + template + struct set; + + template + struct set> : indexed_type... {}; + + template + inline static std::enable_if impl(indexed_type); + + inline static std::enable_if impl(...); + + public: + using type = decltype(impl(set>{})); + }; + + template + using type_pack_element = typename type_pack_element_impl::type; + + template + using type_pack_element_t = typename type_pack_element::type; +#endif + +#ifdef MPARK_TRIVIALITY_TYPE_TRAITS + using std::is_trivially_copy_constructible; + using std::is_trivially_move_constructible; + using std::is_trivially_copy_assignable; + using std::is_trivially_move_assignable; +#else + template + struct is_trivially_copy_constructible + : bool_constant< + std::is_copy_constructible::value && __has_trivial_copy(T)> {}; + + template + struct is_trivially_move_constructible : bool_constant<__is_trivial(T)> {}; + + template + struct is_trivially_copy_assignable + : bool_constant< + std::is_copy_assignable::value && __has_trivial_assign(T)> {}; + + template + struct is_trivially_move_assignable : bool_constant<__is_trivial(T)> {}; +#endif + + } // namespace lib +} // namespace mpark + +#undef RETURN + +#endif // MPARK_LIB_HPP diff --git a/libs/variant/include/mpark/variant.hpp b/libs/variant/include/mpark/variant.hpp new file mode 100644 index 00000000..8f9c17c6 --- /dev/null +++ b/libs/variant/include/mpark/variant.hpp @@ -0,0 +1,1872 @@ +// MPark.Variant +// +// Copyright Michael Park, 2015-2017 +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt) + +#ifndef MPARK_VARIANT_HPP +#define MPARK_VARIANT_HPP + +/* + variant synopsis + +namespace std { + + // 20.7.2, class template variant + template + class variant { + public: + + // 20.7.2.1, constructors + constexpr variant() noexcept(see below); + variant(const variant&); + variant(variant&&) noexcept(see below); + + template constexpr variant(T&&) noexcept(see below); + + template + constexpr explicit variant(in_place_type_t, Args&&...); + + template + constexpr explicit variant( + in_place_type_t, initializer_list, Args&&...); + + template + constexpr explicit variant(in_place_index_t, Args&&...); + + template + constexpr explicit variant( + in_place_index_t, initializer_list, Args&&...); + + // 20.7.2.2, destructor + ~variant(); + + // 20.7.2.3, assignment + variant& operator=(const variant&); + variant& operator=(variant&&) noexcept(see below); + + template variant& operator=(T&&) noexcept(see below); + + // 20.7.2.4, modifiers + template + T& emplace(Args&&...); + + template + T& emplace(initializer_list, Args&&...); + + template + variant_alternative& emplace(Args&&...); + + template + variant_alternative& emplace(initializer_list, Args&&...); + + // 20.7.2.5, value status + constexpr bool valueless_by_exception() const noexcept; + constexpr size_t index() const noexcept; + + // 20.7.2.6, swap + void swap(variant&) noexcept(see below); + }; + + // 20.7.3, variant helper classes + template struct variant_size; // undefined + + template + constexpr size_t variant_size_v = variant_size::value; + + template struct variant_size; + template struct variant_size; + template struct variant_size; + + template + struct variant_size>; + + template struct variant_alternative; // undefined + + template + using variant_alternative_t = typename variant_alternative::type; + + template struct variant_alternative; + template struct variant_alternative; + template struct variant_alternative; + + template + struct variant_alternative>; + + constexpr size_t variant_npos = -1; + + // 20.7.4, value access + template + constexpr bool holds_alternative(const variant&) noexcept; + + template + constexpr variant_alternative_t>& + get(variant&); + + template + constexpr variant_alternative_t>&& + get(variant&&); + + template + constexpr variant_alternative_t> const& + get(const variant&); + + template + constexpr variant_alternative_t> const&& + get(const variant&&); + + template + constexpr T& get(variant&); + + template + constexpr T&& get(variant&&); + + template + constexpr const T& get(const variant&); + + template + constexpr const T&& get(const variant&&); + + template + constexpr add_pointer_t>> + get_if(variant*) noexcept; + + template + constexpr add_pointer_t>> + get_if(const variant*) noexcept; + + template + constexpr add_pointer_t + get_if(variant*) noexcept; + + template + constexpr add_pointer_t + get_if(const variant*) noexcept; + + // 20.7.5, relational operators + template + constexpr bool operator==(const variant&, const variant&); + + template + constexpr bool operator!=(const variant&, const variant&); + + template + constexpr bool operator<(const variant&, const variant&); + + template + constexpr bool operator>(const variant&, const variant&); + + template + constexpr bool operator<=(const variant&, const variant&); + + template + constexpr bool operator>=(const variant&, const variant&); + + // 20.7.6, visitation + template + constexpr see below visit(Visitor&&, Variants&&...); + + // 20.7.7, class monostate + struct monostate; + + // 20.7.8, monostate relational operators + constexpr bool operator<(monostate, monostate) noexcept; + constexpr bool operator>(monostate, monostate) noexcept; + constexpr bool operator<=(monostate, monostate) noexcept; + constexpr bool operator>=(monostate, monostate) noexcept; + constexpr bool operator==(monostate, monostate) noexcept; + constexpr bool operator!=(monostate, monostate) noexcept; + + // 20.7.9, specialized algorithms + template + void swap(variant&, variant&) noexcept(see below); + + // 20.7.10, class bad_variant_access + class bad_variant_access; + + // 20.7.11, hash support + template struct hash; + template struct hash>; + template <> struct hash; + +} // namespace std + +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "config.hpp" +#include "in_place.hpp" +#include "lib.hpp" + +namespace mpark { + +#ifdef MPARK_RETURN_TYPE_DEDUCTION + +#define AUTO auto +#define AUTO_RETURN(...) { return __VA_ARGS__; } + +#define AUTO_REFREF auto && +#define AUTO_REFREF_RETURN(...) { return __VA_ARGS__; } + +#define DECLTYPE_AUTO decltype(auto) +#define DECLTYPE_AUTO_RETURN(...) { return __VA_ARGS__; } + +#else + +#define AUTO auto +#define AUTO_RETURN(...) \ + -> lib::decay_t { return __VA_ARGS__; } + +#define AUTO_REFREF auto +#define AUTO_REFREF_RETURN(...) \ + -> decltype((__VA_ARGS__)) { \ + static_assert(std::is_reference::value, ""); \ + return __VA_ARGS__; \ + } + +#define DECLTYPE_AUTO auto +#define DECLTYPE_AUTO_RETURN(...) \ + -> decltype(__VA_ARGS__) { return __VA_ARGS__; } + +#endif + + class bad_variant_access : public std::exception { + public: + virtual const char *what() const noexcept { return "bad_variant_access"; } + }; + + [[noreturn]] inline void throw_bad_variant_access() { +#ifdef MPARK_EXCEPTIONS + throw bad_variant_access{}; +#else + std::terminate(); +#ifdef MPARK_BUILTIN_UNREACHABLE + __builtin_unreachable(); +#endif +#endif + } + + template + class variant; + + template + struct variant_size; + +#ifdef MPARK_VARIABLE_TEMPLATES + template + constexpr std::size_t variant_size_v = variant_size::value; +#endif + + template + struct variant_size : variant_size {}; + + template + struct variant_size : variant_size {}; + + template + struct variant_size : variant_size {}; + + template + struct variant_size> : lib::size_constant {}; + + template + struct variant_alternative; + + template + using variant_alternative_t = typename variant_alternative::type; + + template + struct variant_alternative + : std::add_const> {}; + + template + struct variant_alternative + : std::add_volatile> {}; + + template + struct variant_alternative + : std::add_cv> {}; + + template + struct variant_alternative> { + static_assert(I < sizeof...(Ts), + "Index out of bounds in std::variant_alternative<>"); + using type = lib::type_pack_element_t; + }; + + constexpr std::size_t variant_npos = static_cast(-1); + + namespace detail { + + constexpr std::size_t not_found = static_cast(-1); + constexpr std::size_t ambiguous = static_cast(-2); + +#ifdef MPARK_CPP14_CONSTEXPR + template + inline constexpr std::size_t find_index() { + constexpr lib::array matches = { + {std::is_same::value...} + }; + std::size_t result = not_found; + for (std::size_t i = 0; i < sizeof...(Ts); ++i) { + if (matches[i]) { + if (result != not_found) { + return ambiguous; + } + result = i; + } + } + return result; + } +#else + inline constexpr std::size_t find_index_impl(std::size_t result, + std::size_t) { + return result; + } + + template + inline constexpr std::size_t find_index_impl(std::size_t result, + std::size_t idx, + bool b, + Bs... bs) { + return b ? (result != not_found ? ambiguous + : find_index_impl(idx, idx + 1, bs...)) + : find_index_impl(result, idx + 1, bs...); + } + + template + inline constexpr std::size_t find_index() { + return find_index_impl(not_found, 0, std::is_same::value...); + } +#endif + + template + using find_index_sfinae_impl = + lib::enable_if_t>; + + template + using find_index_sfinae = find_index_sfinae_impl()>; + + template + struct find_index_checked_impl : lib::size_constant { + static_assert(I != not_found, "the specified type is not found."); + static_assert(I != ambiguous, "the specified type is ambiguous."); + }; + + template + using find_index_checked = find_index_checked_impl()>; + + struct valueless_t {}; + + enum class Trait { TriviallyAvailable, Available, Unavailable }; + + template class IsTriviallyAvailable, + template class IsAvailable> + inline constexpr Trait trait() { + return IsTriviallyAvailable::value + ? Trait::TriviallyAvailable + : IsAvailable::value ? Trait::Available + : Trait::Unavailable; + } + +#ifdef MPARK_CPP14_CONSTEXPR + template + inline constexpr Trait common_trait(Traits... traits) { + Trait result = Trait::TriviallyAvailable; + for (Trait t : {traits...}) { + if (static_cast(t) > static_cast(result)) { + result = t; + } + } + return result; + } +#else + inline constexpr Trait common_trait_impl(Trait result) { return result; } + + template + inline constexpr Trait common_trait_impl(Trait result, + Trait t, + Traits... ts) { + return static_cast(t) > static_cast(result) + ? common_trait_impl(t, ts...) + : common_trait_impl(result, ts...); + } + + template + inline constexpr Trait common_trait(Traits... ts) { + return common_trait_impl(Trait::TriviallyAvailable, ts...); + } +#endif + + template + struct traits { + static constexpr Trait copy_constructible_trait = + common_trait(trait()...); + + static constexpr Trait move_constructible_trait = + common_trait(trait()...); + + static constexpr Trait copy_assignable_trait = + common_trait(copy_constructible_trait, + trait()...); + + static constexpr Trait move_assignable_trait = + common_trait(move_constructible_trait, + trait()...); + + static constexpr Trait destructible_trait = + common_trait(trait()...); + }; + + namespace access { + + struct recursive_union { +#ifdef MPARK_RETURN_TYPE_DEDUCTION + template + inline static constexpr auto &&get_alt(V &&v, in_place_index_t<0>) { + return lib::forward(v).head_; + } + + template + inline static constexpr auto &&get_alt(V &&v, in_place_index_t) { + return get_alt(lib::forward(v).tail_, in_place_index_t{}); + } +#else + template + struct get_alt_impl { + template + inline constexpr AUTO_REFREF operator()(V &&v) const + AUTO_REFREF_RETURN(get_alt_impl{}(lib::forward(v).tail_)) + }; + + template + struct get_alt_impl<0, Dummy> { + template + inline constexpr AUTO_REFREF operator()(V &&v) const + AUTO_REFREF_RETURN(lib::forward(v).head_) + }; + + template + inline static constexpr AUTO_REFREF get_alt(V &&v, in_place_index_t) + AUTO_REFREF_RETURN(get_alt_impl{}(lib::forward(v))) +#endif + }; + + struct base { + template + inline static constexpr AUTO_REFREF get_alt(V &&v) + AUTO_REFREF_RETURN(recursive_union::get_alt( + data(lib::forward(v)), in_place_index_t{})) + }; + + struct variant { + template + inline static constexpr AUTO_REFREF get_alt(V &&v) + AUTO_REFREF_RETURN(base::get_alt(lib::forward(v).impl_)) + }; + + } // namespace access + + namespace visitation { + + struct base { + private: + template + inline static constexpr const T &at(const T &elem) { + return elem; + } + + template + inline static constexpr const lib::remove_all_extents_t &at( + const lib::array &elems, std::size_t i, Is... is) { + return at(elems[i], is...); + } + + template + inline static constexpr int visit_visitor_return_type_check() { + static_assert(lib::all::value...>::value, + "`mpark::visit` requires the visitor to have a single " + "return type."); + return 0; + } + + template + inline static constexpr lib::array< + lib::common_type_t...>, + sizeof...(Fs)> + make_farray(Fs &&... fs) { + using result = lib::array...>, + sizeof...(Fs)>; + return visit_visitor_return_type_check...>(), + result{{lib::forward(fs)...}}; + } + + template + struct dispatcher { + template + struct impl { + inline static constexpr DECLTYPE_AUTO dispatch(F f, Vs... vs) + DECLTYPE_AUTO_RETURN(lib::invoke( + static_cast(f), + access::base::get_alt(static_cast(vs))...)) + }; + }; + + template + inline static constexpr AUTO make_dispatch(lib::index_sequence) + AUTO_RETURN(&dispatcher::template impl::dispatch) + + template + inline static constexpr AUTO make_fdiagonal_impl() + AUTO_RETURN(make_dispatch( + lib::index_sequence::value...>{})) + + template + inline static constexpr AUTO make_fdiagonal_impl( + lib::index_sequence) + AUTO_RETURN(make_farray(make_fdiagonal_impl()...)) + + template + inline static constexpr /* auto * */ auto make_fdiagonal() + -> decltype(make_fdiagonal_impl( + lib::make_index_sequence::size()>{})) { + static_assert(lib::all<(lib::decay_t::size() == + lib::decay_t::size())...>::value, + "all of the variants must be the same size."); + return make_fdiagonal_impl( + lib::make_index_sequence::size()>{}); + } + +#ifdef MPARK_RETURN_TYPE_DEDUCTION + template + inline static constexpr auto make_fmatrix_impl( + lib::index_sequence is) { + return make_dispatch(is); + } + + template + inline static constexpr auto make_fmatrix_impl( + lib::index_sequence, lib::index_sequence, Ls... ls) { + return make_farray(make_fmatrix_impl( + lib::index_sequence{}, ls...)...); + } + + template + inline static constexpr auto make_fmatrix() { + return make_fmatrix_impl( + lib::index_sequence<>{}, + lib::make_index_sequence::size()>{}...); + } +#else + template + struct make_fmatrix_impl { + template + struct impl; + + template + struct impl> { + inline constexpr AUTO operator()() const + AUTO_RETURN( + make_dispatch(lib::index_sequence{})) + }; + + template + struct impl, + lib::index_sequence, + Ls...> { + inline constexpr AUTO operator()() const + AUTO_RETURN(make_farray( + impl, Ls...>{}()...)) + }; + }; + + template + inline static constexpr AUTO make_fmatrix() + AUTO_RETURN( + typename make_fmatrix_impl::template impl< + lib::index_sequence<>, + lib::make_index_sequence::size()>...>{}()) +#endif + + public: + template + inline static constexpr DECLTYPE_AUTO visit_alt_at(std::size_t index, + Visitor &&visitor, + Vs &&... vs) + DECLTYPE_AUTO_RETURN( + at(make_fdiagonal(vs)))...>(), + index)(lib::forward(visitor), + as_base(lib::forward(vs))...)) + + template + inline static constexpr DECLTYPE_AUTO visit_alt(Visitor &&visitor, + Vs &&... vs) + DECLTYPE_AUTO_RETURN( + at(make_fmatrix(vs)))...>(), + vs.index()...)(lib::forward(visitor), + as_base(lib::forward(vs))...)) + }; + + struct variant { + private: + template + struct visit_exhaustive_visitor_check { + static_assert( + lib::is_invocable::value, + "`mpark::visit` requires the visitor to be exhaustive."); + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4100) +#endif + inline constexpr DECLTYPE_AUTO operator()(Visitor &&visitor, + Values &&... values) const + DECLTYPE_AUTO_RETURN(lib::invoke(lib::forward(visitor), + lib::forward(values)...)) +#ifdef _MSC_VER +#pragma warning(pop) +#endif + }; + + template + struct value_visitor { + Visitor &&visitor_; + + template + inline constexpr DECLTYPE_AUTO operator()(Alts &&... alts) const + DECLTYPE_AUTO_RETURN( + visit_exhaustive_visitor_check< + Visitor, + decltype((lib::forward(alts).value))...>{}( + lib::forward(visitor_), + lib::forward(alts).value...)) + }; + + template + inline static constexpr AUTO make_value_visitor(Visitor &&visitor) + AUTO_RETURN(value_visitor{lib::forward(visitor)}) + + public: + template + inline static constexpr DECLTYPE_AUTO visit_alt_at(std::size_t index, + Visitor &&visitor, + Vs &&... vs) + DECLTYPE_AUTO_RETURN( + base::visit_alt_at(index, + lib::forward(visitor), + lib::forward(vs).impl_...)) + + template + inline static constexpr DECLTYPE_AUTO visit_alt(Visitor &&visitor, + Vs &&... vs) + DECLTYPE_AUTO_RETURN(base::visit_alt(lib::forward(visitor), + lib::forward(vs).impl_...)) + + template + inline static constexpr DECLTYPE_AUTO visit_value_at(std::size_t index, + Visitor &&visitor, + Vs &&... vs) + DECLTYPE_AUTO_RETURN( + visit_alt_at(index, + make_value_visitor(lib::forward(visitor)), + lib::forward(vs)...)) + + template + inline static constexpr DECLTYPE_AUTO visit_value(Visitor &&visitor, + Vs &&... vs) + DECLTYPE_AUTO_RETURN( + visit_alt(make_value_visitor(lib::forward(visitor)), + lib::forward(vs)...)) + }; + + } // namespace visitation + + template + struct alt { + using value_type = T; + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4244) +#endif + template + inline explicit constexpr alt(in_place_t, Args &&... args) + : value(lib::forward(args)...) {} +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + T value; + }; + + template + union recursive_union; + + template + union recursive_union {}; + +#define MPARK_VARIANT_RECURSIVE_UNION(destructible_trait, destructor) \ + template \ + union recursive_union { \ + public: \ + inline explicit constexpr recursive_union(valueless_t) noexcept \ + : dummy_{} {} \ + \ + template \ + inline explicit constexpr recursive_union(in_place_index_t<0>, \ + Args &&... args) \ + : head_(in_place_t{}, lib::forward(args)...) {} \ + \ + template \ + inline explicit constexpr recursive_union(in_place_index_t, \ + Args &&... args) \ + : tail_(in_place_index_t{}, lib::forward(args)...) {} \ + \ + recursive_union(const recursive_union &) = default; \ + recursive_union(recursive_union &&) = default; \ + \ + destructor \ + \ + recursive_union &operator=(const recursive_union &) = default; \ + recursive_union &operator=(recursive_union &&) = default; \ + \ + private: \ + char dummy_; \ + alt head_; \ + recursive_union tail_; \ + \ + friend struct access::recursive_union; \ + } + + MPARK_VARIANT_RECURSIVE_UNION(Trait::TriviallyAvailable, + ~recursive_union() = default;); + MPARK_VARIANT_RECURSIVE_UNION(Trait::Available, + ~recursive_union() {}); + MPARK_VARIANT_RECURSIVE_UNION(Trait::Unavailable, + ~recursive_union() = delete;); + +#undef MPARK_VARIANT_RECURSIVE_UNION + + using index_t = unsigned int; + + template + class base { + public: + inline explicit constexpr base(valueless_t tag) noexcept + : data_(tag), index_(static_cast(-1)) {} + + template + inline explicit constexpr base(in_place_index_t, Args &&... args) + : data_(in_place_index_t{}, lib::forward(args)...), + index_(I) {} + + inline constexpr bool valueless_by_exception() const noexcept { + return index_ == static_cast(-1); + } + + inline constexpr std::size_t index() const noexcept { + return valueless_by_exception() ? variant_npos : index_; + } + + protected: + using data_t = recursive_union; + + friend inline constexpr base &as_base(base &b) { return b; } + friend inline constexpr const base &as_base(const base &b) { return b; } + friend inline constexpr base &&as_base(base &&b) { return lib::move(b); } + friend inline constexpr const base &&as_base(const base &&b) { return lib::move(b); } + + friend inline constexpr data_t &data(base &b) { return b.data_; } + friend inline constexpr const data_t &data(const base &b) { return b.data_; } + friend inline constexpr data_t &&data(base &&b) { return lib::move(b).data_; } + friend inline constexpr const data_t &&data(const base &&b) { return lib::move(b).data_; } + + inline static constexpr std::size_t size() { return sizeof...(Ts); } + + data_t data_; + index_t index_; + + friend struct access::base; + friend struct visitation::base; + }; + + struct dtor { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4100) +#endif + template + inline void operator()(Alt &alt) const noexcept { alt.~Alt(); } +#ifdef _MSC_VER +#pragma warning(pop) +#endif + }; + +#if defined(_MSC_VER) && _MSC_VER < 1910 +#define INHERITING_CTOR(type, base) \ + template \ + inline explicit constexpr type(Args &&... args) \ + : base(lib::forward(args)...) {} +#else +#define INHERITING_CTOR(type, base) using base::base; +#endif + + template + class destructor; + +#define MPARK_VARIANT_DESTRUCTOR(destructible_trait, definition, destroy) \ + template \ + class destructor, destructible_trait> \ + : public base { \ + using super = base; \ + \ + public: \ + INHERITING_CTOR(destructor, super) \ + using super::operator=; \ + \ + destructor(const destructor &) = default; \ + destructor(destructor &&) = default; \ + definition \ + destructor &operator=(const destructor &) = default; \ + destructor &operator=(destructor &&) = default; \ + \ + protected: \ + destroy \ + } + + MPARK_VARIANT_DESTRUCTOR( + Trait::TriviallyAvailable, + ~destructor() = default;, + inline void destroy() noexcept { + this->index_ = static_cast(-1); + }); + + MPARK_VARIANT_DESTRUCTOR( + Trait::Available, + ~destructor() { destroy(); }, + inline void destroy() noexcept { + if (!this->valueless_by_exception()) { + visitation::base::visit_alt(dtor{}, *this); + } + this->index_ = static_cast(-1); + }); + + MPARK_VARIANT_DESTRUCTOR( + Trait::Unavailable, + ~destructor() = delete;, + inline void destroy() noexcept = delete;); + +#undef MPARK_VARIANT_DESTRUCTOR + + template + class constructor : public destructor { + using super = destructor; + + public: + INHERITING_CTOR(constructor, super) + using super::operator=; + + protected: +#ifndef MPARK_GENERIC_LAMBDAS + struct ctor { + template + inline void operator()(LhsAlt &lhs_alt, RhsAlt &&rhs_alt) const { + constructor::construct_alt(lhs_alt, + lib::forward(rhs_alt).value); + } + }; +#endif + + template + inline static T &construct_alt(alt &a, Args &&... args) { + ::new (static_cast(lib::addressof(a))) + alt(in_place_t{}, lib::forward(args)...); + return a.value; + } + + template + inline static void generic_construct(constructor &lhs, Rhs &&rhs) { + lhs.destroy(); + if (!rhs.valueless_by_exception()) { + visitation::base::visit_alt_at( + rhs.index(), +#ifdef MPARK_GENERIC_LAMBDAS + [](auto &lhs_alt, auto &&rhs_alt) { + constructor::construct_alt( + lhs_alt, lib::forward(rhs_alt).value); + } +#else + ctor{} +#endif + , + lhs, + lib::forward(rhs)); + lhs.index_ = rhs.index_; + } + } + }; + + template + class move_constructor; + +#define MPARK_VARIANT_MOVE_CONSTRUCTOR(move_constructible_trait, definition) \ + template \ + class move_constructor, move_constructible_trait> \ + : public constructor> { \ + using super = constructor>; \ + \ + public: \ + INHERITING_CTOR(move_constructor, super) \ + using super::operator=; \ + \ + move_constructor(const move_constructor &) = default; \ + definition \ + ~move_constructor() = default; \ + move_constructor &operator=(const move_constructor &) = default; \ + move_constructor &operator=(move_constructor &&) = default; \ + } + + MPARK_VARIANT_MOVE_CONSTRUCTOR( + Trait::TriviallyAvailable, + move_constructor(move_constructor &&that) = default;); + + MPARK_VARIANT_MOVE_CONSTRUCTOR( + Trait::Available, + move_constructor(move_constructor &&that) noexcept( + lib::all::value...>::value) + : move_constructor(valueless_t{}) { + this->generic_construct(*this, lib::move(that)); + }); + + MPARK_VARIANT_MOVE_CONSTRUCTOR( + Trait::Unavailable, + move_constructor(move_constructor &&) = delete;); + +#undef MPARK_VARIANT_MOVE_CONSTRUCTOR + + template + class copy_constructor; + +#define MPARK_VARIANT_COPY_CONSTRUCTOR(copy_constructible_trait, definition) \ + template \ + class copy_constructor, copy_constructible_trait> \ + : public move_constructor> { \ + using super = move_constructor>; \ + \ + public: \ + INHERITING_CTOR(copy_constructor, super) \ + using super::operator=; \ + \ + definition \ + copy_constructor(copy_constructor &&) = default; \ + ~copy_constructor() = default; \ + copy_constructor &operator=(const copy_constructor &) = default; \ + copy_constructor &operator=(copy_constructor &&) = default; \ + } + + MPARK_VARIANT_COPY_CONSTRUCTOR( + Trait::TriviallyAvailable, + copy_constructor(const copy_constructor &that) = default;); + + MPARK_VARIANT_COPY_CONSTRUCTOR( + Trait::Available, + copy_constructor(const copy_constructor &that) + : copy_constructor(valueless_t{}) { + this->generic_construct(*this, that); + }); + + MPARK_VARIANT_COPY_CONSTRUCTOR( + Trait::Unavailable, + copy_constructor(const copy_constructor &) = delete;); + +#undef MPARK_VARIANT_COPY_CONSTRUCTOR + + template + class assignment : public copy_constructor { + using super = copy_constructor; + + public: + INHERITING_CTOR(assignment, super) + using super::operator=; + + template + inline /* auto & */ auto emplace(Args &&... args) + -> decltype(this->construct_alt(access::base::get_alt(*this), + lib::forward(args)...)) { + this->destroy(); + auto &result = this->construct_alt(access::base::get_alt(*this), + lib::forward(args)...); + this->index_ = I; + return result; + } + + protected: +#ifndef MPARK_GENERIC_LAMBDAS + template + struct assigner { + template + inline void operator()(ThisAlt &this_alt, ThatAlt &&that_alt) const { + self->assign_alt(this_alt, lib::forward(that_alt).value); + } + assignment *self; + }; +#endif + + template + inline void assign_alt(alt &a, Arg &&arg) { + if (this->index() == I) { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4244) +#endif + a.value = lib::forward(arg); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + } else { + struct { + void operator()(std::true_type) const { + this_->emplace(lib::forward(arg_)); + } + void operator()(std::false_type) const { + this_->emplace(T(lib::forward(arg_))); + } + assignment *this_; + Arg &&arg_; + } impl{this, lib::forward(arg)}; + impl(lib::bool_constant< + std::is_nothrow_constructible::value || + !std::is_nothrow_move_constructible::value>{}); + } + } + + template + inline void generic_assign(That &&that) { + if (this->valueless_by_exception() && that.valueless_by_exception()) { + // do nothing. + } else if (that.valueless_by_exception()) { + this->destroy(); + } else { + visitation::base::visit_alt_at( + that.index(), +#ifdef MPARK_GENERIC_LAMBDAS + [this](auto &this_alt, auto &&that_alt) { + this->assign_alt( + this_alt, lib::forward(that_alt).value); + } +#else + assigner{this} +#endif + , + *this, + lib::forward(that)); + } + } + }; + + template + class move_assignment; + +#define MPARK_VARIANT_MOVE_ASSIGNMENT(move_assignable_trait, definition) \ + template \ + class move_assignment, move_assignable_trait> \ + : public assignment> { \ + using super = assignment>; \ + \ + public: \ + INHERITING_CTOR(move_assignment, super) \ + using super::operator=; \ + \ + move_assignment(const move_assignment &) = default; \ + move_assignment(move_assignment &&) = default; \ + ~move_assignment() = default; \ + move_assignment &operator=(const move_assignment &) = default; \ + definition \ + } + + MPARK_VARIANT_MOVE_ASSIGNMENT( + Trait::TriviallyAvailable, + move_assignment &operator=(move_assignment &&that) = default;); + + MPARK_VARIANT_MOVE_ASSIGNMENT( + Trait::Available, + move_assignment & + operator=(move_assignment &&that) noexcept( + lib::all<(std::is_nothrow_move_constructible::value && + std::is_nothrow_move_assignable::value)...>::value) { + this->generic_assign(lib::move(that)); + return *this; + }); + + MPARK_VARIANT_MOVE_ASSIGNMENT( + Trait::Unavailable, + move_assignment &operator=(move_assignment &&) = delete;); + +#undef MPARK_VARIANT_MOVE_ASSIGNMENT + + template + class copy_assignment; + +#define MPARK_VARIANT_COPY_ASSIGNMENT(copy_assignable_trait, definition) \ + template \ + class copy_assignment, copy_assignable_trait> \ + : public move_assignment> { \ + using super = move_assignment>; \ + \ + public: \ + INHERITING_CTOR(copy_assignment, super) \ + using super::operator=; \ + \ + copy_assignment(const copy_assignment &) = default; \ + copy_assignment(copy_assignment &&) = default; \ + ~copy_assignment() = default; \ + definition \ + copy_assignment &operator=(copy_assignment &&) = default; \ + } + + MPARK_VARIANT_COPY_ASSIGNMENT( + Trait::TriviallyAvailable, + copy_assignment &operator=(const copy_assignment &that) = default;); + + MPARK_VARIANT_COPY_ASSIGNMENT( + Trait::Available, + copy_assignment &operator=(const copy_assignment &that) { + this->generic_assign(that); + return *this; + }); + + MPARK_VARIANT_COPY_ASSIGNMENT( + Trait::Unavailable, + copy_assignment &operator=(const copy_assignment &) = delete;); + +#undef MPARK_VARIANT_COPY_ASSIGNMENT + + template + class impl : public copy_assignment> { + using super = copy_assignment>; + + public: + INHERITING_CTOR(impl, super) + using super::operator=; + + template + inline void assign(Arg &&arg) { + this->assign_alt(access::base::get_alt(*this), + lib::forward(arg)); + } + + inline void swap(impl &that) { + if (this->valueless_by_exception() && that.valueless_by_exception()) { + // do nothing. + } else if (this->index() == that.index()) { + visitation::base::visit_alt_at(this->index(), +#ifdef MPARK_GENERIC_LAMBDAS + [](auto &this_alt, auto &that_alt) { + using std::swap; + swap(this_alt.value, + that_alt.value); + } +#else + swapper{} +#endif + , + *this, + that); + } else { + impl *lhs = this; + impl *rhs = lib::addressof(that); + if (lhs->move_nothrow() && !rhs->move_nothrow()) { + std::swap(lhs, rhs); + } + impl tmp(lib::move(*rhs)); +#ifdef MPARK_EXCEPTIONS + // EXTENSION: When the move construction of `lhs` into `rhs` throws + // and `tmp` is nothrow move constructible then we move `tmp` back + // into `rhs` and provide the strong exception safety guarantee. + try { + this->generic_construct(*rhs, lib::move(*lhs)); + } catch (...) { + if (tmp.move_nothrow()) { + this->generic_construct(*rhs, lib::move(tmp)); + } + throw; + } +#else + this->generic_construct(*rhs, lib::move(*lhs)); +#endif + this->generic_construct(*lhs, lib::move(tmp)); + } + } + + private: +#ifndef MPARK_GENERIC_LAMBDAS + struct swapper { + template + inline void operator()(ThisAlt &this_alt, ThatAlt &that_alt) const { + using std::swap; + swap(this_alt.value, that_alt.value); + } + }; +#endif + + inline constexpr bool move_nothrow() const { + return this->valueless_by_exception() || + lib::array{ + {std::is_nothrow_move_constructible::value...} + }[this->index()]; + } + }; + + template + struct overload_leaf { + using F = lib::size_constant (*)(T); + operator F() const { return nullptr; } + }; + + template + struct overload_impl { + private: + template + struct impl; + + template + struct impl> : overload_leaf... {}; + + public: + using type = impl>; + }; + + template + using overload = typename overload_impl::type; + + template + using best_match = lib::invoke_result_t, T &&>; + + template + struct is_in_place_index : std::false_type {}; + + template + struct is_in_place_index> : std::true_type {}; + + template + struct is_in_place_type : std::false_type {}; + + template + struct is_in_place_type> : std::true_type {}; + + } // detail + + template + class variant { + static_assert(0 < sizeof...(Ts), + "variant must consist of at least one alternative."); + + static_assert(lib::all::value...>::value, + "variant can not have an array type as an alternative."); + + static_assert(lib::all::value...>::value, + "variant can not have a reference type as an alternative."); + + static_assert(lib::all::value...>::value, + "variant can not have a void type as an alternative."); + + public: + template < + typename Front = lib::type_pack_element_t<0, Ts...>, + lib::enable_if_t::value, int> = 0> + inline constexpr variant() noexcept( + std::is_nothrow_default_constructible::value) + : impl_(in_place_index_t<0>{}) {} + + variant(const variant &) = default; + variant(variant &&) = default; + + template < + typename Arg, + typename Decayed = lib::decay_t, + lib::enable_if_t::value, int> = 0, + lib::enable_if_t::value, int> = 0, + lib::enable_if_t::value, int> = 0, + std::size_t I = detail::best_match::value, + typename T = lib::type_pack_element_t, + lib::enable_if_t::value, int> = 0> + inline constexpr variant(Arg &&arg) noexcept( + std::is_nothrow_constructible::value) + : impl_(in_place_index_t{}, lib::forward(arg)) {} + + template < + std::size_t I, + typename... Args, + typename T = lib::type_pack_element_t, + lib::enable_if_t::value, int> = 0> + inline explicit constexpr variant( + in_place_index_t, + Args &&... args) noexcept(std::is_nothrow_constructible::value) + : impl_(in_place_index_t{}, lib::forward(args)...) {} + + template < + std::size_t I, + typename Up, + typename... Args, + typename T = lib::type_pack_element_t, + lib::enable_if_t &, + Args...>::value, + int> = 0> + inline explicit constexpr variant( + in_place_index_t, + std::initializer_list il, + Args &&... args) noexcept(std:: + is_nothrow_constructible< + T, + std::initializer_list &, + Args...>::value) + : impl_(in_place_index_t{}, il, lib::forward(args)...) {} + + template < + typename T, + typename... Args, + std::size_t I = detail::find_index_sfinae::value, + lib::enable_if_t::value, int> = 0> + inline explicit constexpr variant( + in_place_type_t, + Args &&... args) noexcept(std::is_nothrow_constructible::value) + : impl_(in_place_index_t{}, lib::forward(args)...) {} + + template < + typename T, + typename Up, + typename... Args, + std::size_t I = detail::find_index_sfinae::value, + lib::enable_if_t &, + Args...>::value, + int> = 0> + inline explicit constexpr variant( + in_place_type_t, + std::initializer_list il, + Args &&... args) noexcept(std:: + is_nothrow_constructible< + T, + std::initializer_list &, + Args...>::value) + : impl_(in_place_index_t{}, il, lib::forward(args)...) {} + + ~variant() = default; + + variant &operator=(const variant &) = default; + variant &operator=(variant &&) = default; + + template , variant>::value, + int> = 0, + std::size_t I = detail::best_match::value, + typename T = lib::type_pack_element_t, + lib::enable_if_t<(std::is_assignable::value && + std::is_constructible::value), + int> = 0> + inline variant &operator=(Arg &&arg) noexcept( + (std::is_nothrow_assignable::value && + std::is_nothrow_constructible::value)) { + impl_.template assign(lib::forward(arg)); + return *this; + } + + template < + std::size_t I, + typename... Args, + typename T = lib::type_pack_element_t, + lib::enable_if_t::value, int> = 0> + inline T &emplace(Args &&... args) { + return impl_.template emplace(lib::forward(args)...); + } + + template < + std::size_t I, + typename Up, + typename... Args, + typename T = lib::type_pack_element_t, + lib::enable_if_t &, + Args...>::value, + int> = 0> + inline T &emplace(std::initializer_list il, Args &&... args) { + return impl_.template emplace(il, lib::forward(args)...); + } + + template < + typename T, + typename... Args, + std::size_t I = detail::find_index_sfinae::value, + lib::enable_if_t::value, int> = 0> + inline T &emplace(Args &&... args) { + return impl_.template emplace(lib::forward(args)...); + } + + template < + typename T, + typename Up, + typename... Args, + std::size_t I = detail::find_index_sfinae::value, + lib::enable_if_t &, + Args...>::value, + int> = 0> + inline T &emplace(std::initializer_list il, Args &&... args) { + return impl_.template emplace(il, lib::forward(args)...); + } + + inline constexpr bool valueless_by_exception() const noexcept { + return impl_.valueless_by_exception(); + } + + inline constexpr std::size_t index() const noexcept { + return impl_.index(); + } + + template < + bool Dummy = true, + lib::enable_if_t::value && + lib::is_swappable::value)...>::value, + int> = 0> + inline void swap(variant &that) noexcept( + lib::all<(std::is_nothrow_move_constructible::value && + lib::is_nothrow_swappable::value)...>::value) { + impl_.swap(that.impl_); + } + + private: + detail::impl impl_; + + friend struct detail::access::variant; + friend struct detail::visitation::variant; + }; + + template + inline constexpr bool holds_alternative(const variant &v) noexcept { + return v.index() == I; + } + + template + inline constexpr bool holds_alternative(const variant &v) noexcept { + return holds_alternative::value>(v); + } + + namespace detail { + template + struct generic_get_impl { + constexpr generic_get_impl(int) {} + + constexpr AUTO_REFREF operator()(V &&v) const + AUTO_REFREF_RETURN( + access::variant::get_alt(lib::forward(v)).value) + }; + + template + inline constexpr AUTO_REFREF generic_get(V &&v) + AUTO_REFREF_RETURN(generic_get_impl( + holds_alternative(v) ? 0 : (throw_bad_variant_access(), 0))( + lib::forward(v))) + } // namespace detail + + template + inline constexpr variant_alternative_t> &get( + variant &v) { + return detail::generic_get(v); + } + + template + inline constexpr variant_alternative_t> &&get( + variant &&v) { + return detail::generic_get(lib::move(v)); + } + + template + inline constexpr const variant_alternative_t> &get( + const variant &v) { + return detail::generic_get(v); + } + + template + inline constexpr const variant_alternative_t> &&get( + const variant &&v) { + return detail::generic_get(lib::move(v)); + } + + template + inline constexpr T &get(variant &v) { + return get::value>(v); + } + + template + inline constexpr T &&get(variant &&v) { + return get::value>(lib::move(v)); + } + + template + inline constexpr const T &get(const variant &v) { + return get::value>(v); + } + + template + inline constexpr const T &&get(const variant &&v) { + return get::value>(lib::move(v)); + } + + namespace detail { + + template + inline constexpr /* auto * */ AUTO generic_get_if(V *v) noexcept + AUTO_RETURN(v && holds_alternative(*v) + ? lib::addressof(access::variant::get_alt(*v).value) + : nullptr) + + } // namespace detail + + template + inline constexpr lib::add_pointer_t>> + get_if(variant *v) noexcept { + return detail::generic_get_if(v); + } + + template + inline constexpr lib::add_pointer_t< + const variant_alternative_t>> + get_if(const variant *v) noexcept { + return detail::generic_get_if(v); + } + + template + inline constexpr lib::add_pointer_t + get_if(variant *v) noexcept { + return get_if::value>(v); + } + + template + inline constexpr lib::add_pointer_t + get_if(const variant *v) noexcept { + return get_if::value>(v); + } + + template + inline constexpr bool operator==(const variant &lhs, + const variant &rhs) { + using detail::visitation::variant; + using lib::equal_to; +#ifdef MPARK_CPP14_CONSTEXPR + if (lhs.index() != rhs.index()) return false; + if (lhs.valueless_by_exception()) return true; + return variant::visit_value_at(lhs.index(), equal_to{}, lhs, rhs); +#else + return lhs.index() == rhs.index() && + (lhs.valueless_by_exception() || + variant::visit_value_at(lhs.index(), equal_to{}, lhs, rhs)); +#endif + } + + template + inline constexpr bool operator!=(const variant &lhs, + const variant &rhs) { + using detail::visitation::variant; + using lib::not_equal_to; +#ifdef MPARK_CPP14_CONSTEXPR + if (lhs.index() != rhs.index()) return true; + if (lhs.valueless_by_exception()) return false; + return variant::visit_value_at(lhs.index(), not_equal_to{}, lhs, rhs); +#else + return lhs.index() != rhs.index() || + (!lhs.valueless_by_exception() && + variant::visit_value_at(lhs.index(), not_equal_to{}, lhs, rhs)); +#endif + } + + template + inline constexpr bool operator<(const variant &lhs, + const variant &rhs) { + using detail::visitation::variant; + using lib::less; +#ifdef MPARK_CPP14_CONSTEXPR + if (rhs.valueless_by_exception()) return false; + if (lhs.valueless_by_exception()) return true; + if (lhs.index() < rhs.index()) return true; + if (lhs.index() > rhs.index()) return false; + return variant::visit_value_at(lhs.index(), less{}, lhs, rhs); +#else + return !rhs.valueless_by_exception() && + (lhs.valueless_by_exception() || lhs.index() < rhs.index() || + (lhs.index() == rhs.index() && + variant::visit_value_at(lhs.index(), less{}, lhs, rhs))); +#endif + } + + template + inline constexpr bool operator>(const variant &lhs, + const variant &rhs) { + using detail::visitation::variant; + using lib::greater; +#ifdef MPARK_CPP14_CONSTEXPR + if (lhs.valueless_by_exception()) return false; + if (rhs.valueless_by_exception()) return true; + if (lhs.index() > rhs.index()) return true; + if (lhs.index() < rhs.index()) return false; + return variant::visit_value_at(lhs.index(), greater{}, lhs, rhs); +#else + return !lhs.valueless_by_exception() && + (rhs.valueless_by_exception() || lhs.index() > rhs.index() || + (lhs.index() == rhs.index() && + variant::visit_value_at(lhs.index(), greater{}, lhs, rhs))); +#endif + } + + template + inline constexpr bool operator<=(const variant &lhs, + const variant &rhs) { + using detail::visitation::variant; + using lib::less_equal; +#ifdef MPARK_CPP14_CONSTEXPR + if (lhs.valueless_by_exception()) return true; + if (rhs.valueless_by_exception()) return false; + if (lhs.index() < rhs.index()) return true; + if (lhs.index() > rhs.index()) return false; + return variant::visit_value_at(lhs.index(), less_equal{}, lhs, rhs); +#else + return lhs.valueless_by_exception() || + (!rhs.valueless_by_exception() && + (lhs.index() < rhs.index() || + (lhs.index() == rhs.index() && + variant::visit_value_at(lhs.index(), less_equal{}, lhs, rhs)))); +#endif + } + + template + inline constexpr bool operator>=(const variant &lhs, + const variant &rhs) { + using detail::visitation::variant; + using lib::greater_equal; +#ifdef MPARK_CPP14_CONSTEXPR + if (rhs.valueless_by_exception()) return true; + if (lhs.valueless_by_exception()) return false; + if (lhs.index() > rhs.index()) return true; + if (lhs.index() < rhs.index()) return false; + return variant::visit_value_at(lhs.index(), greater_equal{}, lhs, rhs); +#else + return rhs.valueless_by_exception() || + (!lhs.valueless_by_exception() && + (lhs.index() > rhs.index() || + (lhs.index() == rhs.index() && + variant::visit_value_at( + lhs.index(), greater_equal{}, lhs, rhs)))); +#endif + } + + struct monostate {}; + + inline constexpr bool operator<(monostate, monostate) noexcept { + return false; + } + + inline constexpr bool operator>(monostate, monostate) noexcept { + return false; + } + + inline constexpr bool operator<=(monostate, monostate) noexcept { + return true; + } + + inline constexpr bool operator>=(monostate, monostate) noexcept { + return true; + } + + inline constexpr bool operator==(monostate, monostate) noexcept { + return true; + } + + inline constexpr bool operator!=(monostate, monostate) noexcept { + return false; + } + +#ifdef MPARK_CPP14_CONSTEXPR + namespace detail { + + inline constexpr bool all(std::initializer_list bs) { + for (bool b : bs) { + if (!b) { + return false; + } + } + return true; + } + + } // namespace detail + + template + inline constexpr decltype(auto) visit(Visitor &&visitor, Vs &&... vs) { + return (detail::all({!vs.valueless_by_exception()...}) + ? (void)0 + : throw_bad_variant_access()), + detail::visitation::variant::visit_value( + lib::forward(visitor), lib::forward(vs)...); + } +#else + namespace detail { + + template + inline constexpr bool all_impl(const lib::array &bs, + std::size_t idx) { + return idx >= N || (bs[idx] && all_impl(bs, idx + 1)); + } + + template + inline constexpr bool all(const lib::array &bs) { + return all_impl(bs, 0); + } + + } // namespace detail + + template + inline constexpr DECLTYPE_AUTO visit(Visitor &&visitor, Vs &&... vs) + DECLTYPE_AUTO_RETURN( + (detail::all( + lib::array{{!vs.valueless_by_exception()...}}) + ? (void)0 + : throw_bad_variant_access()), + detail::visitation::variant::visit_value(lib::forward(visitor), + lib::forward(vs)...)) +#endif + + template + inline auto swap(variant &lhs, + variant &rhs) noexcept(noexcept(lhs.swap(rhs))) + -> decltype(lhs.swap(rhs)) { + lhs.swap(rhs); + } + + namespace detail { + + template + using enabled_type = T; + + namespace hash { + + template + constexpr bool meets_requirements() { + return std::is_copy_constructible::value && + std::is_move_constructible::value && + lib::is_invocable_r::value; + } + + template + constexpr bool is_enabled() { + using H = std::hash; + return meets_requirements() && + std::is_default_constructible::value && + std::is_copy_assignable::value && + std::is_move_assignable::value; + } + + } // namespace hash + + } // namespace detail + +#undef AUTO +#undef AUTO_RETURN + +#undef AUTO_REFREF +#undef AUTO_REFREF_RETURN + +#undef DECLTYPE_AUTO +#undef DECLTYPE_AUTO_RETURN + +} // namespace mpark + +namespace std { + + template + struct hash, + mpark::lib::enable_if_t>()...>::value>>> { + using argument_type = mpark::variant; + using result_type = std::size_t; + + inline result_type operator()(const argument_type &v) const { + using mpark::detail::visitation::variant; + std::size_t result = + v.valueless_by_exception() + ? 299792458 // Random value chosen by the universe upon creation + : variant::visit_alt( +#ifdef MPARK_GENERIC_LAMBDAS + [](const auto &alt) { + using alt_type = mpark::lib::decay_t; + using value_type = mpark::lib::remove_const_t< + typename alt_type::value_type>; + return hash{}(alt.value); + } +#else + hasher{} +#endif + , + v); + return hash_combine(result, hash{}(v.index())); + } + + private: +#ifndef MPARK_GENERIC_LAMBDAS + struct hasher { + template + inline std::size_t operator()(const Alt &alt) const { + using alt_type = mpark::lib::decay_t; + using value_type = + mpark::lib::remove_const_t; + return hash{}(alt.value); + } + }; +#endif + + static std::size_t hash_combine(std::size_t lhs, std::size_t rhs) { + return lhs ^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2); + } + }; + + template <> + struct hash { + using argument_type = mpark::monostate; + using result_type = std::size_t; + + inline result_type operator()(const argument_type &) const noexcept { + return 66740831; // return a fundamentally attractive random value. + } + }; + +} // namespace std + +#endif // MPARK_VARIANT_HPP diff --git a/src/HyperlinkManager.cpp b/src/HyperlinkManager.cpp index cecd9df8..7efb7c06 100644 --- a/src/HyperlinkManager.cpp +++ b/src/HyperlinkManager.cpp @@ -28,10 +28,10 @@ using namespace std; // variable to select the link marker variant (none, underlined, boxed, or colored background) -HyperlinkManager::MarkerType HyperlinkManager::MARKER_TYPE = HyperlinkManager::MarkerType::LINE; +HyperlinkManager::MarkerType HyperlinkManager::MARKER_TYPE = MarkerType::LINE; Color HyperlinkManager::LINK_BGCOLOR; Color HyperlinkManager::LINK_LINECOLOR; -bool HyperlinkManager::USE_LINECOLOR = false; +HyperlinkManager::ColorSource HyperlinkManager::COLORSOURCE = ColorSource::DEFAULT; HyperlinkManager& HyperlinkManager::instance () { @@ -146,23 +146,24 @@ void HyperlinkManager::markLinkedBox (SpecialActions &actions) { const BoundingBox &bbox = actions.bbox("{anchor}"); if (bbox.width() > 0 && bbox.height() > 0) { // does the bounding box extend in both dimensions? if (MARKER_TYPE != MarkerType::NONE) { - const double linewidth = min(0.5, bbox.height()/15); + const double linewidth = _linewidth >= 0 ? _linewidth : min(0.5, bbox.height()/15); XMLElementNode *rect = new XMLElementNode("rect"); double x = bbox.minX(); double y = bbox.maxY()+linewidth; double w = bbox.width(); double h = linewidth; - const Color &linecolor = USE_LINECOLOR ? LINK_LINECOLOR : actions.getColor(); + const Color linecolor = COLORSOURCE == ColorSource::DEFAULT ? actions.getColor() : LINK_LINECOLOR; if (MARKER_TYPE == MarkerType::LINE) rect->addAttribute("fill", linecolor.svgColorString()); else { - x -= linewidth; - y = bbox.minY()-linewidth; - w += 2*linewidth; - h += bbox.height()+linewidth; + const double offset = _linewidth < 0 ? linewidth : 0 ; + x -= offset; + y = bbox.minY()-offset; + w += 2*offset; + h += bbox.height()+offset; if (MARKER_TYPE == MarkerType::BGCOLOR) { rect->addAttribute("fill", LINK_BGCOLOR.svgColorString()); - if (USE_LINECOLOR) { + if (COLORSOURCE != ColorSource::DEFAULT) { rect->addAttribute("stroke", linecolor.svgColorString()); rect->addAttribute("stroke-width", linewidth); } @@ -245,11 +246,19 @@ bool HyperlinkManager::setLinkMarker (const string &marker) { return false; MARKER_TYPE = MarkerType::BGCOLOR; } - USE_LINECOLOR = false; + COLORSOURCE = ColorSource::DEFAULT; if (MARKER_TYPE != MarkerType::NONE && !color.empty()) { if (!LINK_LINECOLOR.setPSName(color, false)) return false; - USE_LINECOLOR = true; + COLORSOURCE = ColorSource::LINKMARKER; } return true; } + + +void HyperlinkManager::setDefaultLinkColor (Color color) { + if (COLORSOURCE != ColorSource::LINKMARKER) { + COLORSOURCE = ColorSource::STATIC; + LINK_LINECOLOR = color; + } +} \ No newline at end of file diff --git a/src/HyperlinkManager.hpp b/src/HyperlinkManager.hpp index 7e12b665..e99508e9 100644 --- a/src/HyperlinkManager.hpp +++ b/src/HyperlinkManager.hpp @@ -24,6 +24,7 @@ #include #include #include "Color.hpp" +#include "SpecialActions.hpp" class SpecialActions; @@ -38,6 +39,7 @@ class HyperlinkManager { }; enum class AnchorType {NONE, HREF, NAME}; + enum class ColorSource {DEFAULT, LINKMARKER, STATIC}; using NamedAnchors = std::unordered_map; public: @@ -51,22 +53,25 @@ class HyperlinkManager { void createLink (std::string uri, SpecialActions &actions); void createViews (unsigned pageno, SpecialActions &actions); void setBaseUrl (std::string &base) {_base = base;} + void setLineWidth (double w) {_linewidth = w;} static HyperlinkManager& instance (); static bool setLinkMarker (const std::string &marker); + static void setDefaultLinkColor (Color color); protected: - HyperlinkManager () : _anchorType(AnchorType::NONE), _depthThreshold(0) {} + HyperlinkManager () : _anchorType(AnchorType::NONE), _depthThreshold(0), _linewidth(-1) {} void markLinkedBox (SpecialActions &actions); enum class MarkerType {NONE, LINE, BOX, BGCOLOR}; static MarkerType MARKER_TYPE; ///< selects how to mark linked areas static Color LINK_BGCOLOR; ///< background color if linkmark type == LT_BGCOLOR static Color LINK_LINECOLOR; ///< line color if linkmark type is LM_LINE or LM_BOX - static bool USE_LINECOLOR; ///< if true, LINK_LINECOLOR is applied + static ColorSource COLORSOURCE; ///< if true, LINK_LINECOLOR is applied private: AnchorType _anchorType; ///< type of active anchor int _depthThreshold; ///< break anchor box if the DVI stack depth underruns this threshold + double _linewidth; ///< line width of link marker (-1 => compute individual value per link) std::string _base; ///< base URL that is prepended to all relative targets NamedAnchors _namedAnchors; ///< information about all named anchors processed }; diff --git a/src/InputReader.hpp b/src/InputReader.hpp index c15b55dd..a04c1b4f 100644 --- a/src/InputReader.hpp +++ b/src/InputReader.hpp @@ -66,6 +66,7 @@ class StreamInputReader : public InputReader { int peek () const override {return _is.peek();} int peek (size_t n) const override; bool eof () const override {return !_is || _is.eof();} + std::istream& getStream () {return _is;} private: std::istream &_is; diff --git a/src/Makefile.am b/src/Makefile.am index 6e853254..9a4184d2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -8,7 +8,7 @@ noinst_LIBRARIES = libdvisvgm.a dvisvgm_SOURCES = \ CommandLine.hpp \ - dvisvgm.cpp + dvisvgm.cpp dvisvgm_LDADD = \ $(noinst_LIBRARIES) \ @@ -150,6 +150,8 @@ libdvisvgm_a_SOURCES = \ PapersizeSpecialHandler.hpp \ PathClipper.cpp \ PathClipper.hpp \ + PDFParser.cpp \ + PDFParser.hpp \ PdfSpecialHandler.cpp \ PdfSpecialHandler.hpp \ PreScanDVIReader.cpp \ @@ -241,6 +243,7 @@ AM_CFLAGS = -Wall \ AM_CXXFLAGS = -Wall -Wnon-virtual-dtor \ -I$(top_srcdir)/libs/clipper \ + -I$(top_srcdir)/libs/variant/include \ -I$(top_srcdir)/libs/xxHash \ $(FREETYPE_CFLAGS) \ $(ZLIB_CFLAGS) \ diff --git a/src/PDFParser.cpp b/src/PDFParser.cpp new file mode 100644 index 00000000..d88618d7 --- /dev/null +++ b/src/PDFParser.cpp @@ -0,0 +1,468 @@ +/************************************************************************* +** PDFObjects.cpp ** +** ** +** This file is part of dvisvgm -- a fast DVI to SVG converter ** +** Copyright (C) 2005-2017 Martin Gieseking ** +** ** +** This program is free software; you can redistribute it and/or ** +** modify it under the terms of the GNU General Public License as ** +** published by the Free Software Foundation; either version 3 of ** +** the License, or (at your option) any later version. ** +** ** +** This program is distributed in the hope that it will be useful, but ** +** WITHOUT ANY WARRANTY; without even the implied warranty of ** +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** +** GNU General Public License for more details. ** +** ** +** You should have received a copy of the GNU General Public License ** +** along with this program; if not, see . ** +*************************************************************************/ + +#include +#include +#include +#include +#include +#include "InputReader.hpp" +#include "PDFParser.hpp" + +using namespace std; + + +/** Parses PDF from an input stream and returns the corresponding object representation. + * @param[in] is input stream the PDF data is read from + * @param[in] opHandler handler used to treat PDF operators + * @return the parsed objects */ +vector PDFParser::parse (std::istream &is, const PDFOperatorHandler &opHandler) { + StreamInputReader ir(is); + return parse(ir, opHandler); +} + + +/** Parses PDF from a string and returns the corresponding object representation. + * @param[in] str string that contains the PDF data + * @param[in] opHandler handler used to treat PDF operators + * @return the parsed objects */ +vector PDFParser::parse (const std::string &str, const PDFOperatorHandler &opHandler) { + istringstream iss(str); + return parse(iss, opHandler); +} + + +/** Parses PDF from an InputReader object and returns the corresponding object representation. + * @param[in] ir InputReader the PDF data is read from + * @param[in] opHandler handler used to treat PDF operators + * @return the parsed objects */ +vector PDFParser::parse (InputReader &ir, const PDFOperatorHandler &opHandler) { + vector objects; + while (!ir.eof()) { + ir.skipSpace(); + if (ir.peek() == '%') // comment? + while (ir.get() != '\n' && !ir.eof()); + else if (!ir.eof()) + parse(ir, objects, opHandler); + } + return objects; +} + + +/** Default handler for PDF operators. Just adds the operators to the + * object vector without evaluating them. + * @param[in] opname name of the operator + * @param[in,out] objects vector holding the parsed objects */ +static void append_operator (const string &opname, vector &objects) { + objects.emplace_back(PDFOperator(opname)); +} + + +/** Parses PDF from an input stream and returns the corresponding object representation. + * @param[in] is input stream the PDF data is read from + * @return the parsed objects */ +vector PDFParser::parse (std::istream &is) { + return parse(is, append_operator); +} + + +/** Parses PDF from a string and returns the corresponding object representation. + * @param[in] str string that contains the PDF data + * @return the parsed objects */ +vector PDFParser::parse (const std::string &str) { + return parse(str, append_operator); +} + +/** Parses PDF from an InputReader object and returns the corresponding object representation. + * @param[in] ir InputReader the PDF data is read from + * @return the parsed objects */ +vector PDFParser::parse (InputReader &ir) { + return parse(ir, append_operator); +} + + +/** Parses PDF from an InputReader object and appends the recognized objects to a vector. + * @param[in] ir InputReader the PDF data is read from + * @param[in,out] objects the parsed PDF objects are appended to this vector + * @return the parsed objects */ +void PDFParser::parse (InputReader &ir, vector &objects) { + parse(ir, objects, append_operator); +} + + +inline bool isoctaldigit (int c) {return c >= '0' && c <= '7';} + +/** Parses a PDF escape sequence of the form \FOO, where FOO is a single + * character or a sequence of 1-3 octal digits + * @return pair (s,c), s=true if c contains a parsed character */ +static pair parse_escape_seq (InputReader &ir) { + // leading backslash has already been read + if (isoctaldigit(ir.peek())) { + string str; + for (int i=0; i < 3 && isoctaldigit(ir.peek()); i++) + str += static_cast(ir.get()); + return pair{true, stoi(str, 0, 8)}; + } + char c = static_cast(ir.get()); + switch (c) { + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case '\n': + case '\r': + if ((c == '\n' && ir.peek() == '\r') || (c == '\r' && ir.peek() == '\n')) + ir.get(); + return pair{false, 0}; + } + return pair{true, c}; +} + + +/** Parses a literal PDF string of the form (FOO). */ +static string parse_literal_string (InputReader &ir) { + string str; + ir.get(); // skip initial '(' + int open_parens=1; + while (ir.peek() >= 0 && open_parens > 0) { + if (ir.peek() == '\n' || ir.peek() == '\r') + break; + int c = ir.get(); + switch (c) { + case '(': open_parens++; break; + case ')': open_parens--; break; + case '\\': + pair state = parse_escape_seq(ir); + c = state.first ? state.second : -1; + break; + } + if (open_parens > 0 && c >= 0) + str += static_cast(c); + } + if (open_parens > 0) + throw PDFException("missing ')' at end of literal string"); + return str; +} + + +/** Gets a single hex digit from the InputReader. */ +static char get_hex_digit (InputReader &ir) { + int c = ir.get(); + if (isxdigit(c)) + return static_cast(c); + ostringstream oss; + oss << "invalid hexadecimal digit '" << static_cast(c) << "'"; + throw PDFException(oss.str()); +} + + +/** Parses a PDF hex string of the form , where FOO is a sequence of + * hex digits optionally separated by whitespace. */ +static string parse_hex_string (InputReader &ir) { + // initial '<' has already been read + string str; + ir.skipSpace(); + while (ir.peek() > 0 && ir.peek() != '>') { + string hexpair; + hexpair += get_hex_digit(ir); + ir.skipSpace(); + if (ir.peek() > 0 && ir.peek() != '>') + hexpair += get_hex_digit(ir); + else if (ir.peek() == '>') + hexpair += '0'; + ir.skipSpace(); + str += static_cast(stoi(hexpair, 0, 16)); + } + if (ir.peek() != '>') + throw PDFException("missing '>' at end of hexadecimal string"); + ir.get(); // skip closing '>' + return str; +} + + +using NumberVariant = mpark::variant; + +/** Parses a PDF number from a string. The number is either integer or real. + * @param[in] str string to parse + * @param[out] nv variant holding the numeric value + * @return true if entire string has been parsed succesfully */ +static bool parse_number (const string &str, NumberVariant &nv) { + if (str.empty()) + return false; + try { + size_t dotpos = str.find('.'); + if (dotpos == string::npos) { // not a real number? + size_t count; + nv = NumberVariant(stoi(str, &count, 10)); // then try to convert str to int + return count == str.length(); // successful only if all characters have been processed + } + string postdot = str.substr(dotpos+1); + // ensure signless integer after dot to exclude exponental notation + // which is not allowed in PDF real number constants + if (!postdot.empty() && isdigit(postdot[0])) { + size_t count; + stoi(postdot, &count, 10); + if (count != postdot.length()) + return false; + } + size_t count; + nv = NumberVariant(stod(str, &count)); + return count == str.length(); + } + catch (invalid_argument &e) { + return false; + } +} + + +/** Parses a PDF array from the input stream and returns a corresponding object. */ +PDFArray PDFParser::parseArray (InputReader &ir, const PDFOperatorHandler &opHandler) { + ir.get(); // skip '[' + vector localObjects; + while (!ir.eof() && ir.peek() != ']') + parse(ir, localObjects, opHandler); + ir.skipSpace(); + if (ir.peek() != ']') + throw PDFException("missing ']' at end of array"); + ir.get(); + PDFArray arr; + std::move(localObjects.begin(), localObjects.end(), back_inserter(arr)); + return arr; +} + + +/** Parses a PDF dictionary from the input stream and returns a corresponding object. + * The function expects that the first opening angle bracket denoting the start of an + * dictionary has already been swallowed from the stream. */ +PDFDict PDFParser::parseDict (InputReader &ir, const PDFOperatorHandler &opHandler) { + ir.get(); // skip second "<" + vector localObjects; + while (!ir.eof() && ir.peek() != '>') + parse(ir, localObjects, opHandler); + if (ir.getString(2) != ">>") + throw PDFException("missing '>>' at end of dictionary"); + PDFDict dict; + for (auto it=localObjects.begin(); it != localObjects.end(); ++it) { + if (!it->get()) + throw PDFException("name key expected in dictionary"); + const PDFName &key = *it->get(); + if (++it == localObjects.end()) + throw PDFException(string("missing dictionary value for key '")+key.str+"'"); + dict.emplace(key.str, std::move(*it)); + } + return dict; +} + + +static PDFStream parse_stream (InputReader &ir, const char *delim) { + do + ir.skipUntil("endstream"); + while (ir.peek() >= 0 && !strchr(delim, ir.peek())); // ensure delimiter after "endstream" + return PDFStream(); +} + + +static PDFIndirectObject parse_indirect_object (InputReader &ir, const char *delim, vector &objects) { + do + ir.skipUntil("endobj"); + while (ir.peek() >= 0 && !strchr(delim, ir.peek())); // ensure delimiter after "endobj" + if (objects.size() >= 2) { + const int *genno = objects.back().get(); + objects.pop_back(); + const int *objno = objects.back().get(); + objects.pop_back(); + if (objno && genno) + return PDFIndirectObject(*objno, *genno); + } + throw PDFException("object and generation number expected before 'obj'"); +} + + +static PDFObjectRef parse_object_ref (vector &objects) { + if (objects.size() >= 2) { + const int *genno = objects.back().get(); + objects.pop_back(); + const int *objno = objects.back().get(); + objects.pop_back(); + if (objno && genno) + return PDFObjectRef(*objno, *genno); + } + throw PDFException("object and generation number expected before 'R'"); +} + + +/** Replaces all occurences of "#XX" (XX are two hex digits) with the corresponding character. */ +static string& subst_numeric_chars (string &str) { + for (size_t pos=str.find('#'); pos != string::npos; pos=str.find('#', pos+1)) { + if (pos > str.length()-3) + throw PDFException("sign character # must be followed by two hexadecimal digits"); + if (isxdigit(str[pos+1]) && isxdigit(str[pos+2])) { + int c = stoi(str.substr(pos+1, 2), 0, 16); + if (c == 0) + throw PDFException("null character not permitted in name"); + str.replace(pos, 3, 1, static_cast(c)); + } + else + throw PDFException("sign character # must be followed by two hexadecimal digits"); + } + return str; +} + + +/** Parses a single PDF object from an InputReader object. + * @param[in,out] ir reader object to read the PDF data from + * @param[out] objects the parsed object is appended to this vector + * @param[in] opHandler handler used to treat PDF operators + * @throws PDFException on failure */ +void PDFParser::parse (InputReader &ir, vector &objects, const PDFOperatorHandler &opHandler) { + static const char *delim = "()<>[]{}/% \t\n\r\f"; + ir.skipSpace(); + if (ir.peek() < 0) + return; + switch (ir.peek()) { + case '(': + objects.emplace_back(parse_literal_string(ir)); break; + case '[': + objects.emplace_back(make_shared(parseArray(ir, opHandler))); break; + case '<': + ir.get(); + if (ir.peek() != '<') + objects.emplace_back(parse_hex_string(ir)); + else + objects.emplace_back(make_shared(parseDict(ir, opHandler))); + break; + case '/': { + ir.get(); + string name = ir.getString(delim); + objects.emplace_back(PDFName(subst_numeric_chars(name))); + break; + } + default: { + string str = ir.getString(delim); + if (str.empty()) + break; + if (str == "null") + objects.emplace_back(PDFNull()); + else if (str == "true") + objects.emplace_back(true); + else if (str == "false") + objects.emplace_back(false); + else if (str == "stream") + objects.emplace_back(parse_stream(ir, delim)); + else if (str == "obj") + objects.emplace_back(parse_indirect_object(ir, delim, objects)); + else if (str == "R") + objects.emplace_back(parse_object_ref(objects)); + else { + NumberVariant number; + if (!parse_number(str, number)) + opHandler(str, objects); + else { + if (mpark::get_if(&number)) + objects.emplace_back(mpark::get(number)); + else + objects.emplace_back(mpark::get(number)); + } + } + } + } +} + +////////////////////////////////////////////////////////////////////////// + +struct ToDoubleVisitor { + template + double operator () (const V &val) {return 0;} +}; + +template<> double ToDoubleVisitor::operator () (const int &val) {return static_cast(val);} +template<> double ToDoubleVisitor::operator () (const double &val) {return val;} +template<> double ToDoubleVisitor::operator () (const string &val) { + try { + return stod(val); + } + catch (exception &e) { + return 0; + } +} + + +PDFObject::operator double () const { + return mpark::visit(ToDoubleVisitor(), _value); +} + + +PDFObject::operator std::string () const { + ostringstream oss; + oss << *this; + return oss.str(); +} + + +static std::ostream& operator << (std::ostream &os, const PDFName &name) {return os << name.str;} +static ostream& operator << (ostream &os, const PDFNull&) {return os << "null";} +static ostream& operator << (ostream &os, const PDFStream&) {return os << "stream";} +static ostream& operator << (ostream &os, const PDFOperator &op) {return os << op.opname;} + +static ostream& operator << (ostream &os, const PDFIndirectObject &obj) { + return os << "obj(" << obj.objnum << ", " << obj.gennum << ')'; +} + + +static ostream& operator << (ostream &os, const PDFObjectRef &ref) { + return os << "obj(" << ref.objnum << ", " << ref.gennum << ')'; +} + + +static ostream& operator << (ostream &os, const shared_ptr> &val) { + os << '['; + for (auto it=val->begin(); it != val->end(); ++it) { + if (it != val->begin()) + os << ", "; + it->write(os); + } + os << ']'; + return os; +} + + +static ostream& operator << (ostream &os, const shared_ptr> &val) { + os << "<<"; + for (auto it=val->begin(); it != val->end(); ++it) { + if (it != val->begin()) + os << ", "; + os << it->first << ':' << it->second; + } + os << ">>"; + return os; +} + + +struct WriteVisitor { + WriteVisitor (ostream &os) : _os(os) {} + template void operator () (const T &val) {_os << val;} + ostream &_os; +}; + + +void PDFObject::write (ostream &os) const { + mpark::visit(WriteVisitor(os), _value); +} diff --git a/src/PDFParser.hpp b/src/PDFParser.hpp new file mode 100644 index 00000000..bc1769c0 --- /dev/null +++ b/src/PDFParser.hpp @@ -0,0 +1,186 @@ +/************************************************************************* +** PDFObjects.hpp ** +** ** +** This file is part of dvisvgm -- a fast DVI to SVG converter ** +** Copyright (C) 2005-2017 Martin Gieseking ** +** ** +** This program is free software; you can redistribute it and/or ** +** modify it under the terms of the GNU General Public License as ** +** published by the Free Software Foundation; either version 3 of ** +** the License, or (at your option) any later version. ** +** ** +** This program is distributed in the hope that it will be useful, but ** +** WITHOUT ANY WARRANTY; without even the implied warranty of ** +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** +** GNU General Public License for more details. ** +** ** +** You should have received a copy of the GNU General Public License ** +** along with this program; if not, see . ** +*************************************************************************/ + +#ifndef PDFPARSER_HPP +#define PDFPARSER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "MessageException.hpp" + +class PDFObject; + +template +class Dictionary { + using Map = std::map; + + public: + typename Map::const_iterator begin () const {return _map.begin();} + typename Map::const_iterator end () const {return _map.end();} + typename Map::iterator begin () {return _map.begin();} + typename Map::iterator end () {return _map.end();} + typename Map::const_iterator find (const K &key) const {return _map.find(key);} + bool empty () const {return _map.empty();} + bool exists (const K &key) const {return _map.find(key) != _map.end();} + size_t size () const {return _map.size();} + + const V* get (const K &key) const { + auto it = _map.find(key); + if (it != _map.end()) + return &it->second; + return nullptr; + } + + std::pair emplace (const K &key, V &&value) { + return _map.emplace(key, std::forward(value)); + } + + private: + Map _map; +}; + +////////////////////////////////////////////////////////////////////////// +// PDF object types + +struct PDFNull {}; +struct PDFStream {}; + +struct PDFIndirectObject { + PDFIndirectObject (int n, int gen) : objnum(n), gennum(gen) {} + int objnum, gennum; +}; + +struct PDFObjectRef { + PDFObjectRef (int n, int gen) : objnum(n), gennum(gen) {} + int objnum, gennum; +}; + +struct PDFOperator { + PDFOperator (const std::string &name) : opname(name) {} + std::string opname; +}; + +struct PDFName { + PDFName (const std::string &val) : str(val) {} + bool operator == (const PDFName &name) const {return str == name.str;} + std::string str; +}; + +using PDFArray = std::vector; +using PDFDict = Dictionary; + +////////////////////////////////////////////////////////////////////////// + +class InputReader; + +/** This class represents a single variadic PDF object. */ +class PDFObject { + using Value = mpark::variant< + PDFNull, + bool, + int, + double, + PDFName, + PDFStream, + PDFIndirectObject, + PDFObjectRef, + PDFOperator, + std::string, + std::shared_ptr, + std::shared_ptr + >; + + public: + PDFObject () : _value(0) {} + + template + explicit PDFObject (const T &value) : _value(value) {} + + explicit PDFObject (const char *value) : _value(std::string(value)) {} + explicit operator std::string () const; + explicit operator double () const; + + template + const T* get () const {return mpark::get_if(&_value);} + + void write (std::ostream &os) const; + + private: + Value _value; +}; + + +template<> inline const PDFArray* PDFObject::get() const { + if (auto *p = mpark::get_if>(&_value)) + return &(**p); + return nullptr; +} + + +template<> inline const PDFDict* PDFObject::get() const { + if (auto *p = mpark::get_if>(&_value)) + return &(**p); + return nullptr; +} + + +inline std::ostream& operator << (std::ostream &os, const PDFObject &obj) { + obj.write(os); + return os; +} + +////////////////////////////////////////////////////////////////////////// + + +class PDFParser { + public: + using PDFOperatorHandler = std::function&)>; + + public: + std::vector parse (std::istream &is); + std::vector parse (const std::string &str); + std::vector parse (InputReader &ir); + void parse (InputReader &ir, std::vector &objects); + + std::vector parse (std::istream &is, const PDFOperatorHandler &opHandler); + std::vector parse (const std::string &str, const PDFOperatorHandler &opHandler); + std::vector parse (InputReader &ir, const PDFOperatorHandler &opHandler); + void parse (InputReader &ir, std::vector &objects, const PDFOperatorHandler &opHandler); + + protected: + PDFArray parseArray (InputReader &ir, const PDFOperatorHandler &opHandler); + PDFDict parseDict (InputReader &ir, const PDFOperatorHandler &opHandler); +}; + + +/** If errors occur while parsing a sequence of PDF objects, an instance of this exception is thrown. */ +struct PDFException : public MessageException { + PDFException (const std::string &msg) : MessageException(msg) {} +}; + +#endif diff --git a/src/PdfSpecialHandler.cpp b/src/PdfSpecialHandler.cpp index 0db9feb0..dc953364 100644 --- a/src/PdfSpecialHandler.cpp +++ b/src/PdfSpecialHandler.cpp @@ -18,21 +18,29 @@ ** along with this program; if not, see . ** *************************************************************************/ +#include #include +#include #include +#include "Color.hpp" +#include "HyperlinkManager.hpp" #include "InputReader.hpp" #include "MapLine.hpp" #include "PdfSpecialHandler.hpp" #include "FontMap.hpp" #include "Message.hpp" #include "PapersizeSpecialHandler.hpp" +#include "PDFParser.hpp" #include "SpecialActions.hpp" #include "SpecialManager.hpp" using namespace std; -PdfSpecialHandler::PdfSpecialHandler () : _maplineProcessed(false) +using CmdHandler = void (PdfSpecialHandler::*)(StreamInputReader&, SpecialActions&); + + +PdfSpecialHandler::PdfSpecialHandler () : _active(false), _maplineProcessed(false) { } @@ -40,12 +48,64 @@ PdfSpecialHandler::PdfSpecialHandler () : _maplineProcessed(false) void PdfSpecialHandler::preprocess (const char*, istream &is, SpecialActions &actions) { StreamInputReader ir(is); ir.skipSpace(); - string cmd = ir.getWord(); - if (cmd != "pagesize") - return; + string cmdstr = ir.getWord(); + static unordered_map commands = { + {"bann", &PdfSpecialHandler::preprocessBeginAnn}, + {"bannot", &PdfSpecialHandler::preprocessBeginAnn}, + {"beginann", &PdfSpecialHandler::preprocessBeginAnn}, + {"dest", &PdfSpecialHandler::preprocessDest}, + {"pagesize", &PdfSpecialHandler::preprocessPagesize} + }; + auto it = commands.find(cmdstr); + if (it != commands.end()) + (this->*it->second)(ir, actions); +} + + +bool PdfSpecialHandler::process (const char*, istream &is, SpecialActions &actions) { + _active = true; + StreamInputReader ir(is); + ir.skipSpace(); + string cmdstr = ir.getWord(); + ir.skipSpace(); + // dvipdfm(x) specials currently supported + static unordered_map commands = { + {"bann", &PdfSpecialHandler::processBeginAnn}, + {"bannot", &PdfSpecialHandler::processBeginAnn}, + {"beginann", &PdfSpecialHandler::processBeginAnn}, + {"eann", &PdfSpecialHandler::processEndAnn}, + {"eannot", &PdfSpecialHandler::processEndAnn}, + {"endann", &PdfSpecialHandler::processEndAnn}, + {"dest", &PdfSpecialHandler::processDest}, + {"mapfile", &PdfSpecialHandler::processMapfile}, + {"mapline", &PdfSpecialHandler::processMapline} + }; + auto it = commands.find(cmdstr); + if (it != commands.end()) + (this->*it->second)(ir, actions); + return true; +} + + +static char prepare_mode (InputReader &ir, bool maplineProcessed) { + // read mode selector ('+', '-', or '=') + char modechar = '+'; // default mode (append if new, do not replace existing mapping) + if (strchr("=+-", ir.peek())) // leading modifier given? + modechar = static_cast(ir.get()); + else if (!maplineProcessed) { // no modifier given? + // remove default map entries if this is the first mapline/mapfile special called + FontMap::instance().clear(); + } + return modechar; +} + + +/** Sets the page size. This command is similar to the papersize special. + * Syntax pdf:pagesize ( )+ */ +void PdfSpecialHandler::preprocessPagesize (StreamInputReader &ir, SpecialActions &actions) { // add page sizes to collection of paper sizes in order to handle them equally SpecialHandler *handler = SpecialManager::instance().findHandlerByName("papersize"); - if (PapersizeSpecialHandler *papersizeHandler = dynamic_cast(handler)) { + if (auto *papersizeHandler = dynamic_cast(handler)) { try { Length width, height; // parse parameter sequence of the form (name length)+ @@ -66,38 +126,170 @@ void PdfSpecialHandler::preprocess (const char*, istream &is, SpecialActions &ac } -bool PdfSpecialHandler::process (const char*, istream &is, SpecialActions&) { - StreamInputReader ir(is); - ir.skipSpace(); - string cmd = ir.getWord(); - ir.skipSpace(); - if (cmd == "mapline" || cmd == "mapfile") { - // read mode selector ('+', '-', or '=') - char modechar = '+'; // default mode (append if new, do not replace existing mapping) - if (strchr("=+-", ir.peek())) // leading modifier given? - modechar = char(ir.get()); - else if (!_maplineProcessed) { // no modifier given? - // remove default map entries if this is the first mapline/mapfile special called - FontMap::instance().clear(); +void PdfSpecialHandler::processMapfile (StreamInputReader &ir, SpecialActions&) { + char modechar = prepare_mode(ir, _maplineProcessed); + string fname = ir.getString(); + if (!FontMap::instance().read(fname, modechar)) + Message::wstream(true) << "can't open map file " << fname << '\n'; +} + + +void PdfSpecialHandler::processMapline (StreamInputReader &ir, SpecialActions&) { + char modechar = prepare_mode(ir, _maplineProcessed); + try { + MapLine mapline(ir.getStream()); + FontMap::instance().apply(mapline, modechar); + } + catch (const MapLineException &ex) { + Message::wstream(true) << "pdf:mapline: " << ex.what() << '\n'; + } +} + + +/** Defines a named destination, e.g. a link target. + * Syntax: dest PDFString PDFDest */ +void PdfSpecialHandler::preprocessDest (StreamInputReader &ir, SpecialActions &actions) { + PDFParser parser; + vector objects = parser.parse(ir, [&](const string &opname, vector &objects) { + if (!opname.empty() && opname[0] == '@') { + if (opname == "@thispage") + objects.emplace_back(static_cast(actions.getCurrentPageNumber())); + else if (opname == "@xpos") + objects.emplace_back(actions.getX()); + else if (opname == "@ypos") + objects.emplace_back(actions.getY()); } + }); + if (objects.size() < 2) + return; + auto *name = objects[0].get(); + auto *dest = objects[1].get(); + // get target info from array [pageno /XYZ xpos ypos zpos] + if (name && dest && dest->size() >= 4 && dest->at(0).get()) { + int pageno = *dest->at(0).get(); + HyperlinkManager::instance().addNameAchor(*name, pageno); + } +} - if (cmd == "mapline") { - try { - MapLine mapline(is); - FontMap::instance().apply(mapline, modechar); - } - catch (const MapLineException &ex) { - Message::wstream(true) << "pdf:mapline: " << ex.what() << '\n'; + +/** Extracts the URI from a PDF annotation dictionary. "GoTo" targets (named anchors) + * are prefixed with a '#'. + * @param[in] annotDict annotation dictionary containing the target URI + * @return the URI if one was found, "" otherwise */ +static string get_uri (const PDFDict &annotDict) { + // At the moment, we only support link annotations + const PDFObject *type = annotDict.get("Type"); + if (type && string(*type) == "Annot") { + const PDFObject *subtype = annotDict.get("Subtype"); + if (subtype && string(*subtype) == "Link") { + const PDFObject *dict = annotDict.get("A"); + if (const PDFDict *actionDict = dict->get()) { + if (const PDFObject *s = actionDict->get("S")) { + if (string(*s) == "GoTo") { + if (const PDFObject *dest = actionDict->get("D")) + return "#" + string(*dest); + } + else if (string(*s) == "URI") { + if (const PDFObject *uri = actionDict->get("URI")) + return string(*uri); + } + } } } - else { // mapfile - string fname = ir.getString(); - if (!FontMap::instance().read(fname, modechar)) - Message::wstream(true) << "can't open map file " << fname << '\n'; + } + return ""; +} + + +void PdfSpecialHandler::preprocessBeginAnn (StreamInputReader &ir, SpecialActions&) { + PDFParser parser; + vector pdfobjs = parser.parse(ir); + if (pdfobjs.empty() || !pdfobjs[0].get()) + return; + const PDFDict &annotDict = *pdfobjs[0].get(); + string uri = get_uri(annotDict); + if (!uri.empty()) + HyperlinkManager::instance().addHrefAnchor(uri); +} + + +/** Converts a PDFObject to a Color, where a single number denotes a gray value. + * Number arrays are treated as gray, RGB, or CMYK colors depending on the + * number of components. */ +static Color to_color (const PDFObject &obj) { + Color color; + if (obj.get() || obj.get()) + color.setGray(double(obj)); + else if (auto *colorArray = obj.get()) { + size_t size = min(size_t(4), colorArray->size()); + valarray colorComps(size); + for (size_t i=0; i < size; i++) + colorComps[i] = double(colorArray->at(i)); + switch (size) { + case 1: color.setGray(colorComps); break; + case 3: color.setRGB(colorComps); break; + case 4: color.setCMYK(colorComps); break; } - _maplineProcessed = true; } - return true; + return color; +} + + +/** Begins a breakable annotation, e.g. a hyperlink. + * Syntax: beginann PDFDict */ +void PdfSpecialHandler::processBeginAnn (StreamInputReader &ir, SpecialActions &actions) { + PDFParser parser; + vector pdfobjs = parser.parse(ir); + if (pdfobjs.empty() || !pdfobjs[0].get()) + return; + const PDFDict &annotDict = *pdfobjs[0].get(); + string uri = get_uri(annotDict); + if (uri.empty()) + return; + + // check presence of entry /Border [hr vr bw] defining the horizontal/vertical + // corner radius and the border width + auto it = annotDict.find("Border"); + if (it != annotDict.end() && it->second.get() && it->second.get()->size() > 2) + HyperlinkManager::instance().setLineWidth(double(it->second.get()->at(2))); + + // check presence of entry /C defining the border color + it = annotDict.find("C"); + if (it != annotDict.end()) + HyperlinkManager::setDefaultLinkColor(to_color(it->second)); + HyperlinkManager::instance().createLink(uri, actions); +} + + +/** Terminates the preceding breakable annotation. + * Syntax: endann */ +void PdfSpecialHandler::processEndAnn (StreamInputReader&, SpecialActions &actions) { + HyperlinkManager::instance().closeAnchor(actions); +} + + +void PdfSpecialHandler::processDest (StreamInputReader &ir, SpecialActions &actions) { + PDFParser parser; + vector objects = parser.parse(ir); + if (!objects.empty()) { + if (auto *name = objects[0].get()) + HyperlinkManager::instance().setActiveNameAnchor(*name, actions); + } +} + + +/** This method is called every time the DVI position changes. */ +void PdfSpecialHandler::dviMovedTo (double x, double y, SpecialActions &actions) { + if (_active) + HyperlinkManager::instance().checkNewLine(actions); +} + + +void PdfSpecialHandler::dviEndPage (unsigned pageno, SpecialActions &actions) { + if (_active) { + HyperlinkManager::instance().createViews(pageno, actions); + _active = false; + } } diff --git a/src/PdfSpecialHandler.hpp b/src/PdfSpecialHandler.hpp index c5edcd96..2bbca020 100644 --- a/src/PdfSpecialHandler.hpp +++ b/src/PdfSpecialHandler.hpp @@ -23,17 +23,33 @@ #include "SpecialHandler.hpp" -class PdfSpecialHandler : public SpecialHandler -{ +class StreamInputReader; + +class PdfSpecialHandler : public SpecialHandler, public DVIPositionListener, public DVIEndPageListener { public: PdfSpecialHandler (); - const char* info () const override {return "pdfTeX font map specials";} + const char* info () const override {return "PDF hyperlink, font map, and pagesize specials";} const char* name () const override {return "pdf";} const std::vector prefixes () const override; void preprocess (const char *prefix, std::istream &is, SpecialActions &actions) override; bool process (const char *prefix, std::istream &is, SpecialActions &actions) override; + protected: + // handlers for corresponding PDF specials + void preprocessBeginAnn (StreamInputReader &ir, SpecialActions &actions); + void preprocessDest (StreamInputReader &ir, SpecialActions &actions); + void preprocessPagesize (StreamInputReader &ir, SpecialActions &actions); + void processBeginAnn (StreamInputReader &ir, SpecialActions &actions); + void processEndAnn (StreamInputReader &ir, SpecialActions &actions); + void processDest (StreamInputReader &ir, SpecialActions &actions); + void processMapfile (StreamInputReader &ir, SpecialActions &actions); + void processMapline (StreamInputReader &ir, SpecialActions &actions); + + void dviMovedTo (double x, double y, SpecialActions &actions) override; + void dviEndPage (unsigned pageno, SpecialActions &actions) override; + private: + bool _active; bool _maplineProcessed; ///< true if a mapline or mapfile special has already been processed }; diff --git a/tests/Makefile.am b/tests/Makefile.am index 19f09d63..8eec24e4 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -21,7 +21,7 @@ EXTRA_DIST = gtest/LICENSE \ gtest/src/gtest-test-part.cc \ gtest/src/gtest-typed-test.cc -AM_CXXFLAGS = -I$(top_srcdir)/src -Wall -DBUILDDIR='"$(abs_builddir)"' -DSRCDIR='"$(abs_srcdir)"' $(CODE_COVERAGE_CFLAGS) +AM_CXXFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/libs/variant/include -Wall -DBUILDDIR='"$(abs_builddir)"' -DSRCDIR='"$(abs_srcdir)"' $(CODE_COVERAGE_CFLAGS) TESTS = hashcheck check_PROGRAMS = hashcheck @@ -245,6 +245,12 @@ PapersizeSpecialTest_SOURCES = PapersizeSpecialTest.cpp testmain.cpp PapersizeSpecialTest_CPPFLAGS = -I$(top_srcdir)/tests/gtest/include PapersizeSpecialTest_LDADD = $(TESTLIBS) +TESTS += PDFParserTest +check_PROGRAMS += PDFParserTest +PDFParserTest_SOURCES = PDFParserTest.cpp testmain.cpp +PDFParserTest_CPPFLAGS = -I$(top_srcdir)/tests/gtest/include +PDFParserTest_LDADD = $(TESTLIBS) + TESTS += PSInterpreterTest check_PROGRAMS += PSInterpreterTest PSInterpreterTest_SOURCES = PSInterpreterTest.cpp testmain.cpp diff --git a/tests/PDFParserTest.cpp b/tests/PDFParserTest.cpp new file mode 100644 index 00000000..c7ac9f8f --- /dev/null +++ b/tests/PDFParserTest.cpp @@ -0,0 +1,239 @@ +/************************************************************************* +** PDFParserTest.cpp ** +** ** +** This file is part of dvisvgm -- a fast DVI to SVG converter ** +** Copyright (C) 2005-2017 Martin Gieseking ** +** ** +** This program is free software; you can redistribute it and/or ** +** modify it under the terms of the GNU General Public License as ** +** published by the Free Software Foundation; either version 3 of ** +** the License, or (at your option) any later version. ** +** ** +** This program is distributed in the hope that it will be useful, but ** +** WITHOUT ANY WARRANTY; without even the implied warranty of ** +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** +** GNU General Public License for more details. ** +** ** +** You should have received a copy of the GNU General Public License ** +** along with this program; if not, see . ** +*************************************************************************/ + +#include +#include "PDFParser.hpp" + +using namespace std; + +TEST(PDFParserTest, empty) { + PDFParser parser; + vector objects = parser.parse(""); + EXPECT_TRUE(objects.empty()); + objects = parser.parse(" \n \r %comment 1 2 3 "); + EXPECT_TRUE(objects.empty()); +} + + +TEST(PDFParserTest, numbers) { + PDFParser parser; + vector objects = parser.parse("5 1 +2 -3 1.1 9. .1 -7.2"); + ASSERT_EQ(objects.size(), 8u); + + size_t count=0; + for (int n : {5, 1, 2, -3}) { + ASSERT_NE(objects[count].get(), nullptr); + EXPECT_EQ(*objects[count++].get(), n); + } + for (double n : {1.1, 9.0, 0.1, -7.2}) { + ASSERT_NE(objects[count].get(), nullptr); + EXPECT_DOUBLE_EQ(*objects[count++].get(), n); + } +} + + +TEST(PDFParserTest, literal_strings) { + PDFParser parser; + vector objects = parser.parse("()"); + ASSERT_EQ(objects.size(), 1u); + ASSERT_NE(objects[0].get(), nullptr); + EXPECT_TRUE(objects[0].get()->empty()); + + objects = parser.parse("(literal string) (balanced (bra(ck)ets) inside)(a\\n\\(b\\c)"); + ASSERT_EQ(objects.size(), 3u); + + size_t count=0; + for (string str : {"literal string", "balanced (bra(ck)ets) inside", "a\n(bc"}) { + ASSERT_NE(objects[count].get(), nullptr); + ASSERT_EQ(*objects[count++].get(), str); + } + + objects = parser.parse(R"*( ( octal \1\12\123\1234 ))*"); + ASSERT_EQ(objects.size(), 1u); + ASSERT_NE(objects[0].get(), nullptr); + EXPECT_EQ(objects[0].get()->size(), 13u); + EXPECT_EQ(*objects[0].get(), " octal \001\012\123\1234 "); + + objects = parser.parse("(split \\\nline)"); + ASSERT_EQ(objects.size(), 1u); + ASSERT_NE(objects[0].get(), nullptr); + EXPECT_EQ(*objects[0].get(), "split line"); + + EXPECT_THROW(parser.parse("(missing parentheses"), PDFException); +} + + +TEST(PDFParserTest, hex_strings) { + PDFParser parser; + vector objects = parser.parse("<>< ><\n\r\f>"); + ASSERT_EQ(objects.size(), 3u); + for (size_t i=0; i < objects.size(); i++) { + ASSERT_NE(objects[i].get(), nullptr); + EXPECT_TRUE(objects[i].get()->empty()); + } + + objects = parser.parse("<202>"); + ASSERT_EQ(objects.size(), 1u); + ASSERT_NE(objects[0].get(), nullptr); + EXPECT_EQ(*objects[0].get(), " "); + + objects = parser.parse("<616263646566 6768\n696A6b6c6D6E6F 7>"); + ASSERT_EQ(objects.size(), 1u); + ASSERT_NE(objects[0].get(), nullptr); + EXPECT_EQ(*objects[0].get(), "abcdefghijklmnop"); + + EXPECT_THROW(parser.parse(""), PDFException); + EXPECT_THROW(parser.parse(" objects = parser.parse("[1 2.0/name(string) [5] <6162>]"); + ASSERT_EQ(objects.size(), 1u); + const PDFArray *arr = objects[0].get(); + ASSERT_NE(arr, nullptr); + ASSERT_EQ(arr->size(), 6u); + + // integer number + ASSERT_NE((*arr)[0].get(), nullptr); + ASSERT_EQ(*(*arr)[0].get(), 1); + + // real number + ASSERT_NE((*arr)[1].get(), nullptr); + ASSERT_DOUBLE_EQ(*(*arr)[1].get(), 2.0); + + // name + ASSERT_NE((*arr)[2].get(), nullptr); + ASSERT_EQ(*(*arr)[2].get(), PDFName("name")); + + // literal string + ASSERT_NE((*arr)[3].get(), nullptr); + ASSERT_EQ(*(*arr)[3].get(), "string"); + + // inner array + ASSERT_NE((*arr)[4].get(), nullptr); + ASSERT_EQ((*arr)[4].get()->size(), 1u); + ASSERT_NE((*arr)[4].get()->at(0).get(), nullptr); + ASSERT_EQ(*(*arr)[4].get()->at(0).get(), 5); + + // hex string + ASSERT_NE((*arr)[5].get(), nullptr); + ASSERT_EQ(*(*arr)[5].get(), "ab"); +} + + +TEST(PDFParserTest, dictionary) { + PDFParser parser; + vector objects = parser.parse("<< >>"); + ASSERT_EQ(objects.size(), 1u); + const PDFDict *dict = objects[0].get(); + ASSERT_NE(dict, nullptr); + ASSERT_TRUE(dict->empty()); + + objects = parser.parse("<>"); + ASSERT_EQ(objects.size(), 1u); + dict = objects[0].get(); + ASSERT_NE(dict, nullptr); + ASSERT_EQ(dict->size(), 4u); + + // integer number + ASSERT_NE(dict->find("int"), dict->end()); + ASSERT_NE(dict->find("int")->second.get(), nullptr); + EXPECT_EQ(*dict->find("int")->second.get(), 4); + + ASSERT_NE(dict->find("real"), dict->end()); + ASSERT_NE(dict->find("real")->second.get(), nullptr); + EXPECT_DOUBLE_EQ(*dict->find("real")->second.get(), 5.5); + + ASSERT_NE(dict->find("str"), dict->end()); + ASSERT_NE(dict->find("str")->second.get(), nullptr); + EXPECT_EQ(*dict->find("str")->second.get(), "string value"); + + ASSERT_NE(dict->find("color"), dict->end()); + ASSERT_NE(dict->find("color")->second.get(), nullptr); + const PDFArray &arr = *dict->find("color")->second.get(); + ASSERT_EQ(arr.size(), 3u); + EXPECT_EQ(*arr[0].get(), 0); + EXPECT_EQ(*arr[1].get(), 1); + EXPECT_EQ(*arr[2].get(), 1); + + EXPECT_EQ(dict->find("nokey"), dict->end()); + + EXPECT_THROW(parser.parse("<>"), PDFException); // missing value + EXPECT_THROW(parser.parse("<"), PDFException); // missing ">" + EXPECT_THROW(parser.parse("<>" +} + + +TEST(PDFParserTest, indirect_objects) { + PDFParser parser; + vector objects = parser.parse("1 0 obj\n1 2 3 4 5endobj 2 5 R"); + ASSERT_EQ(objects.size(), 2u); + ASSERT_NE(objects[0].get(), nullptr); + EXPECT_EQ(objects[0].get()->objnum, 1); + EXPECT_EQ(objects[0].get()->gennum, 0); + + ASSERT_NE(objects[1].get(), nullptr); + EXPECT_EQ(objects[1].get()->objnum, 2); + EXPECT_EQ(objects[1].get()->gennum, 5); + + EXPECT_THROW(parser.parse("1 obj\n1 2 3 4 5endobj"), PDFException); + EXPECT_THROW(parser.parse("1 (string)obj\n1 2 3 4 5endobj"), PDFException); + + EXPECT_THROW(parser.parse("1 R"), PDFException); + EXPECT_THROW(parser.parse("1 (string)R"), PDFException); +} + + +TEST(PDFParserTest, stream) { + PDFParser parser; + vector objects = parser.parse("stream\n1 2 3 4 endstream"); + ASSERT_EQ(objects.size(), 1u); + ASSERT_NE(objects[0].get(), nullptr); +} + + +TEST(PDFParserTest, ops1) { + PDFParser parser; + vector objects = parser.parse("@xpos 1op op2 1..2"); + ASSERT_EQ(objects.size(), 4u); + size_t count=0; + for (string str : {"@xpos", "1op", "op2", "1..2"}) { + ASSERT_NE(objects[count].get(), nullptr); + ASSERT_EQ(objects[count++].get()->opname, str); + } +} + + +TEST(PDFParserTest, ops2) { + PDFParser parser; + vector objects = parser.parse("@xpos 1op op2 @ypos", [](const string &str, vector &objects) { + if (str == "@xpos" || str == "@ypos") + objects.emplace_back(PDFObject(str == "@xpos" ? 1.23 : 3.21)); + else + objects.emplace_back(PDFOperator(str)); + }); + ASSERT_EQ(objects.size(), 4u); + size_t count=0; + for (string str : {"1.23", "1op", "op2", "3.21"}) { + ASSERT_EQ(string(objects[count++]), str); + } +} diff --git a/tests/SpecialManagerTest.cpp b/tests/SpecialManagerTest.cpp index 37a107e5..0ea60393 100644 --- a/tests/SpecialManagerTest.cpp +++ b/tests/SpecialManagerTest.cpp @@ -65,7 +65,7 @@ TEST_F(SpecialManagerTest, info1) { "em line drawing statements of the emTeX special set\n" "html hyperref specials\n" "papersize special to set the page size\n" - "pdf pdfTeX font map specials\n" + "pdf PDF hyperlink, font map, and pagesize specials\n" "tpic TPIC specials\n"; EXPECT_EQ(oss.str(), expected); } @@ -82,7 +82,7 @@ TEST_F(SpecialManagerTest, info2) { "dvisvgm special set for embedding raw SVG snippets\n" "html hyperref specials\n" "papersize special to set the page size\n" - "pdf pdfTeX font map specials\n" + "pdf PDF hyperlink, font map, and pagesize specials\n" "tpic TPIC specials\n"; EXPECT_EQ(oss.str(), expected); } diff --git a/tests/create-makefile b/tests/create-makefile index 6f38b2f8..496c0326 100755 --- a/tests/create-makefile +++ b/tests/create-makefile @@ -29,7 +29,7 @@ EXTRA_DIST = gtest/LICENSE \\ gtest/src/gtest-test-part.cc \\ gtest/src/gtest-typed-test.cc -AM_CXXFLAGS = -I\$(top_srcdir)/src -Wall -DBUILDDIR='"\$(abs_builddir)"' -DSRCDIR='"\$(abs_srcdir)"' \$(CODE_COVERAGE_CFLAGS) +AM_CXXFLAGS = -I\$(top_srcdir)/src -I\$(top_srcdir)/libs/variant/include -Wall -DBUILDDIR='"\$(abs_builddir)"' -DSRCDIR='"\$(abs_srcdir)"' \$(CODE_COVERAGE_CFLAGS) TESTS = hashcheck check_PROGRAMS = hashcheck diff --git a/vc/dvisvgm.vcxproj b/vc/dvisvgm.vcxproj index b4143b5b..37dc5c2f 100644 --- a/vc/dvisvgm.vcxproj +++ b/vc/dvisvgm.vcxproj @@ -75,7 +75,7 @@ Disabled - ..\libs\clipper;..\libs\ff-woff\fontforge;..\libs\ff-woff\inc;..\libs\woff2\brotli\include;..\libs\woff2\include;..\libs\xxhash;..\vc\miktex-2.9.4106-sdk\include;..\vc;..\vc\freetype\include;..\vc\potrace\src;..\vc\zlib;%(AdditionalIncludeDirectories) + ..\libs\clipper;..\libs\ff-woff\fontforge;..\libs\ff-woff\inc;..\libs\variant\include;..\libs\woff2\brotli\include;..\libs\woff2\include;..\libs\xxhash;..\vc\miktex-2.9.4106-sdk\include;..\vc;..\vc\freetype\include;..\vc\potrace\src;..\vc\zlib;%(AdditionalIncludeDirectories) MIKTEX;%(PreprocessorDefinitions) true MultiThreadedDebug @@ -110,7 +110,7 @@ Disabled - ..\libs\clipper;..\libs\ff-woff\fontforge;..\libs\ff-woff\inc;..\libs\woff2\brotli\include;..\libs\woff2\include;..\libs\xxhash;..\vc\miktex-2.9.4106-sdk\include;..\vc;..\vc\freetype\include;..\vc\potrace\src;..\vc\zlib;%(AdditionalIncludeDirectories) + ..\libs\clipper;..\libs\ff-woff\fontforge;..\libs\ff-woff\inc;..\libs\variant\include;..\libs\woff2\brotli\include;..\libs\woff2\include;..\libs\xxhash;..\vc\miktex-2.9.4106-sdk\include;..\vc;..\vc\freetype\include;..\vc\potrace\src;..\vc\zlib;%(AdditionalIncludeDirectories) MIKTEX;%(PreprocessorDefinitions) true MultiThreadedDebug @@ -137,7 +137,7 @@ - ..\libs\clipper;..\libs\ff-woff\fontforge;..\libs\ff-woff\inc;..\libs\woff2\brotli\include;..\libs\woff2\include;..\libs\xxhash;..\vc\miktex-2.9.4106-sdk\include;..\vc;..\vc\freetype\include;..\vc\potrace\src;..\vc\zlib;%(AdditionalIncludeDirectories) + ..\libs\clipper;..\libs\ff-woff\fontforge;..\libs\ff-woff\inc;..\libs\variant\include;..\libs\woff2\brotli\include;..\libs\woff2\include;..\libs\xxhash;..\vc\miktex-2.9.4106-sdk\include;..\vc;..\vc\freetype\include;..\vc\potrace\src;..\vc\zlib;%(AdditionalIncludeDirectories) MIKTEX;%(PreprocessorDefinitions) false MultiThreaded @@ -162,7 +162,7 @@ true - ..\libs\clipper;..\libs\ff-woff\fontforge;..\libs\ff-woff\inc;..\libs\woff2\brotli\include;..\libs\woff2\include;..\libs\xxhash;..\vc\miktex-2.9.4106-sdk\include;..\vc;..\vc\freetype\include;..\vc\potrace\src;..\vc\zlib;%(AdditionalIncludeDirectories) + ..\libs\clipper;..\libs\ff-woff\fontforge;..\libs\ff-woff\inc;..\libs\variant\include;..\libs\woff2\brotli\include;..\libs\woff2\include;..\libs\xxhash;..\vc\miktex-2.9.4106-sdk\include;..\vc;..\vc\freetype\include;..\vc\potrace\src;..\vc\zlib;%(AdditionalIncludeDirectories) MIKTEX;%(PreprocessorDefinitions) false Full @@ -244,6 +244,7 @@ + @@ -317,6 +318,7 @@ + diff --git a/vc/dvisvgm.vcxproj.filters b/vc/dvisvgm.vcxproj.filters index d5b500b5..1a5676f9 100644 --- a/vc/dvisvgm.vcxproj.filters +++ b/vc/dvisvgm.vcxproj.filters @@ -287,6 +287,9 @@ Source Files + + Source Files + @@ -586,5 +589,8 @@ Header Files + + Header Files + \ No newline at end of file