summaryrefslogtreecommitdiffstats
path: root/testfiles
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--testfiles/CMakeLists.txt105
-rw-r--r--testfiles/CTestCustom.cmake.in5
-rw-r--r--testfiles/cli_tests/CMakeLists.txt441
-rw-r--r--testfiles/cli_tests/check_output.sh69
-rw-r--r--testfiles/cli_tests/compare.sh13
-rw-r--r--testfiles/cli_tests/match_regex.sh13
-rw-r--r--testfiles/cli_tests/match_regex_fail.sh13
-rw-r--r--testfiles/cli_tests/testcases/areas.svg10
-rw-r--r--testfiles/cli_tests/testcases/empty.svg3
-rw-r--r--testfiles/cli_tests/testcases/export-area-drawing_expected.emfbin0 -> 1856 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-area-drawing_expected.eps443
-rw-r--r--testfiles/cli_tests/testcases/export-area-drawing_expected.pdfbin0 -> 1338 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-area-drawing_expected.pngbin0 -> 13995 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-area-drawing_expected.ps480
-rw-r--r--testfiles/cli_tests/testcases/export-area-drawing_expected.svg79
-rw-r--r--testfiles/cli_tests/testcases/export-area-drawing_expected.wmfbin0 -> 1006 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-area-page_expected.emfbin0 -> 1856 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-area-page_expected.eps443
-rw-r--r--testfiles/cli_tests/testcases/export-area-page_expected.pdfbin0 -> 1350 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-area-page_expected.pngbin0 -> 15117 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-area-page_expected.ps482
-rw-r--r--testfiles/cli_tests/testcases/export-area-page_expected.svg61
-rw-r--r--testfiles/cli_tests/testcases/export-area-page_expected.wmfbin0 -> 1006 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-area-page_export-id.pdf69
-rw-r--r--testfiles/cli_tests/testcases/export-area-page_export-id.pngbin0 -> 10167 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-area-page_export-id.ps130
-rw-r--r--testfiles/cli_tests/testcases/export-area-page_export-id.svg52
-rw-r--r--testfiles/cli_tests/testcases/export-area-snap_expected.pngbin0 -> 1048 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-area_expected.pngbin0 -> 5778 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-dpi_expected.eps261
-rw-r--r--testfiles/cli_tests/testcases/export-dpi_expected.pdfbin0 -> 2177 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-dpi_expected.pngbin0 -> 763 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-dpi_expected.ps298
-rw-r--r--testfiles/cli_tests/testcases/export-height_expected.pngbin0 -> 248 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_drawing_expected.emfbin0 -> 812 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_drawing_expected.eps82
-rw-r--r--testfiles/cli_tests/testcases/export-margin_drawing_expected.pdfbin0 -> 940 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_drawing_expected.pngbin0 -> 508 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_drawing_expected.ps119
-rw-r--r--testfiles/cli_tests/testcases/export-margin_drawing_expected.svg58
-rw-r--r--testfiles/cli_tests/testcases/export-margin_drawing_expected.wmfbin0 -> 356 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_export-area_expected.pngbin0 -> 356 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_export-id_expected.emfbin0 -> 832 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_export-id_expected.eps82
-rw-r--r--testfiles/cli_tests/testcases/export-margin_export-id_expected.pdfbin0 -> 943 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_export-id_expected.pngbin0 -> 485 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_export-id_expected.ps119
-rw-r--r--testfiles/cli_tests/testcases/export-margin_export-id_expected.svg58
-rw-r--r--testfiles/cli_tests/testcases/export-margin_export-id_expected.wmfbin0 -> 356 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.emfbin0 -> 692 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.eps80
-rw-r--r--testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.pdfbin0 -> 933 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.pngbin0 -> 482 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.ps117
-rw-r--r--testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.svg51
-rw-r--r--testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.wmfbin0 -> 290 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_expected.emfbin0 -> 824 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_expected.eps82
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_expected.pdfbin0 -> 1152 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_expected.pngbin0 -> 6068 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_expected.ps119
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_expected.svg58
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_expected.wmfbin0 -> 356 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.emfbin0 -> 856 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.eps82
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.pdfbin0 -> 974 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.pngbin0 -> 3222 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.ps119
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.svg64
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.wmfbin0 -> 356 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.emfbin0 -> 844 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.eps82
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.pdfbin0 -> 972 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.pngbin0 -> 2664 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.ps119
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.svg64
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.wmfbin0 -> 356 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.emfbin0 -> 824 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.eps82
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.pdfbin0 -> 1152 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.pngbin0 -> 6068 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.ps119
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.svg64
-rw-r--r--testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.wmfbin0 -> 356 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_px_expected.emfbin0 -> 820 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_px_expected.eps82
-rw-r--r--testfiles/cli_tests/testcases/export-margin_px_expected.pdfbin0 -> 943 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_px_expected.pngbin0 -> 737 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-margin_px_expected.ps119
-rw-r--r--testfiles/cli_tests/testcases/export-margin_px_expected.svg58
-rw-r--r--testfiles/cli_tests/testcases/export-margin_px_expected.wmfbin0 -> 356 bytes
-rw-r--r--testfiles/cli_tests/testcases/export-width_expected.pngbin0 -> 644 bytes
-rw-r--r--testfiles/cli_tests/testcases/export_hints.svg7
-rw-r--r--testfiles/cli_tests/testcases/filter.svg9
-rw-r--r--testfiles/cli_tests/testcases/librevenge_formats/corel_draw.cdrbin0 -> 13873 bytes
-rw-r--r--testfiles/cli_tests/testcases/librevenge_formats/corel_draw2.cdrbin0 -> 19596 bytes
-rw-r--r--testfiles/cli_tests/testcases/librevenge_formats/corel_draw2_expected.pngbin0 -> 22021 bytes
-rw-r--r--testfiles/cli_tests/testcases/librevenge_formats/corel_draw_expected.pngbin0 -> 15577 bytes
-rw-r--r--testfiles/cli_tests/testcases/librevenge_formats/visio.vsdbin0 -> 52224 bytes
-rw-r--r--testfiles/cli_tests/testcases/librevenge_formats/visio.vsd_expected.pngbin0 -> 21469 bytes
-rw-r--r--testfiles/cli_tests/testcases/librevenge_formats/visio.vsdxbin0 -> 24693 bytes
-rw-r--r--testfiles/cli_tests/testcases/librevenge_formats/visio.vsdx_expected.pngbin0 -> 2947 bytes
-rw-r--r--testfiles/cli_tests/testcases/librevenge_formats/word_perfect.wpgbin0 -> 2525 bytes
-rw-r--r--testfiles/cli_tests/testcases/librevenge_formats/word_perfect_expected.pngbin0 -> 708 bytes
-rw-r--r--testfiles/cli_tests/testcases/pdf-mesh.pdfbin0 -> 1728 bytes
-rw-r--r--testfiles/cli_tests/testcases/pyramids.svg22
-rw-r--r--testfiles/cli_tests/testcases/rects.svg6
-rw-r--r--testfiles/cli_tests/testcases/shapes.svg8
-rw-r--r--testfiles/cli_tests/testcases/shapes_expected.emfbin0 -> 1540 bytes
-rw-r--r--testfiles/cli_tests/testcases/shapes_expected.eps109
-rw-r--r--testfiles/cli_tests/testcases/shapes_expected.pdfbin0 -> 1333 bytes
-rw-r--r--testfiles/cli_tests/testcases/shapes_expected.pngbin0 -> 13382 bytes
-rw-r--r--testfiles/cli_tests/testcases/shapes_expected.ps146
-rw-r--r--testfiles/cli_tests/testcases/shapes_expected.wmfbin0 -> 1120 bytes
-rw-r--r--testfiles/cli_tests/testcases/shapes_expected.xaml18
-rw-r--r--testfiles/cli_tests/testcases/square_mm.svg5
-rw-r--r--testfiles/cli_tests/testcases/square_mm_viewbox.svg10
-rw-r--r--testfiles/cli_tests/testcases/square_px.svg5
-rw-r--r--testfiles/cli_tests/testcases/systemLanguage.svg20
-rw-r--r--testfiles/cli_tests/testcases/systemLanguage_RDF.svg15
-rw-r--r--testfiles/cli_tests/testcases/systemLanguage_de.pngbin0 -> 516 bytes
-rw-r--r--testfiles/cli_tests/testcases/systemLanguage_default.pngbin0 -> 516 bytes
-rw-r--r--testfiles/cli_tests/testcases/systemLanguage_en.pngbin0 -> 519 bytes
-rw-r--r--testfiles/cli_tests/testcases/systemLanguage_fr.pngbin0 -> 518 bytes
-rw-r--r--testfiles/cli_tests/testcases/systemLanguage_pt.pngbin0 -> 516 bytes
-rw-r--r--testfiles/cli_tests/testcases/text.svg4
-rw-r--r--testfiles/doc-per-case-test.cpp53
-rw-r--r--testfiles/doc-per-case-test.h44
-rw-r--r--testfiles/fuzzer.cpp24
-rw-r--r--testfiles/fuzzer.dict526
-rw-r--r--testfiles/rendering_tests/CMakeLists.txt52
-rw-r--r--testfiles/rendering_tests/README26
-rw-r--r--testfiles/rendering_tests/expected_rendering/multi-style.pngbin0 -> 1433 bytes
-rw-r--r--testfiles/rendering_tests/expected_rendering/selector-important-002-large.pngbin0 -> 11306 bytes
-rw-r--r--testfiles/rendering_tests/expected_rendering/selector-important-002.pngbin0 -> 921 bytes
-rw-r--r--testfiles/rendering_tests/expected_rendering/selector-important-003-large.pngbin0 -> 11308 bytes
-rw-r--r--testfiles/rendering_tests/expected_rendering/selector-important-003.pngbin0 -> 925 bytes
-rw-r--r--testfiles/rendering_tests/expected_rendering/test-baseline-shift-large.pngbin0 -> 97340 bytes
-rw-r--r--testfiles/rendering_tests/expected_rendering/test-baseline-shift.pngbin0 -> 20024 bytes
-rw-r--r--testfiles/rendering_tests/expected_rendering/test-dont-crash.pngbin0 -> 453 bytes
-rw-r--r--testfiles/rendering_tests/expected_rendering/test-empty-large.pngbin0 -> 69082 bytes
-rw-r--r--testfiles/rendering_tests/expected_rendering/test-empty.pngbin0 -> 6920 bytes
-rw-r--r--testfiles/rendering_tests/expected_rendering/test-glyph-y-pos-large.pngbin0 -> 72842 bytes
-rw-r--r--testfiles/rendering_tests/expected_rendering/test-glyph-y-pos.pngbin0 -> 13506 bytes
-rw-r--r--testfiles/rendering_tests/expected_rendering/test-powerstroke-join-large.pngbin0 -> 8037 bytes
-rw-r--r--testfiles/rendering_tests/expected_rendering/test-powerstroke-join.pngbin0 -> 1796 bytes
-rw-r--r--testfiles/rendering_tests/expected_rendering/test-rtl-vertical-large.pngbin0 -> 54382 bytes
-rw-r--r--testfiles/rendering_tests/expected_rendering/test-rtl-vertical.pngbin0 -> 9856 bytes
-rw-r--r--testfiles/rendering_tests/expected_rendering/text-glyphs-combining-large.pngbin0 -> 46628 bytes
-rw-r--r--testfiles/rendering_tests/expected_rendering/text-glyphs-combining.pngbin0 -> 20611 bytes
-rw-r--r--testfiles/rendering_tests/expected_rendering/text-glyphs-vertical-large.pngbin0 -> 30462 bytes
-rw-r--r--testfiles/rendering_tests/expected_rendering/text-glyphs-vertical.pngbin0 -> 14121 bytes
-rw-r--r--testfiles/rendering_tests/expected_rendering/text-shaping-large.pngbin0 -> 83064 bytes
-rw-r--r--testfiles/rendering_tests/expected_rendering/text-shaping.pngbin0 -> 33675 bytes
-rw-r--r--testfiles/rendering_tests/fonts/Estedad-Medium.ttfbin0 -> 60832 bytes
-rwxr-xr-xtestfiles/rendering_tests/fonts/GeomTest-Regular.otfbin0 -> 2984 bytes
-rw-r--r--testfiles/rendering_tests/fonts/LICENSES10
-rw-r--r--testfiles/rendering_tests/fonts/Lohit-Telugu.ttfbin0 -> 341752 bytes
-rw-r--r--testfiles/rendering_tests/fonts/NotoSans-Regular.ttfbin0 -> 468584 bytes
-rw-r--r--testfiles/rendering_tests/fonts/NotoSansCJKjp-Regular.otfbin0 -> 16427228 bytes
-rw-r--r--testfiles/rendering_tests/fonts/NotoSansHebrew-Regular.ttfbin0 -> 27696 bytes
-rw-r--r--testfiles/rendering_tests/multi-style.svg96
-rw-r--r--testfiles/rendering_tests/selector-important-002.svg58
-rw-r--r--testfiles/rendering_tests/selector-important-003.svg57
-rw-r--r--testfiles/rendering_tests/test-baseline-shift.svg27
-rw-r--r--testfiles/rendering_tests/test-dont-crash.svg22
-rw-r--r--testfiles/rendering_tests/test-empty.svg65
-rw-r--r--testfiles/rendering_tests/test-glyph-y-pos.svg26
-rw-r--r--testfiles/rendering_tests/test-powerstroke-join.svg6
-rw-r--r--testfiles/rendering_tests/test-rtl-vertical.svg26
-rwxr-xr-xtestfiles/rendering_tests/test.sh46
-rw-r--r--testfiles/rendering_tests/text-glyphs-combining.svg34
-rw-r--r--testfiles/rendering_tests/text-glyphs-vertical.svg48
-rw-r--r--testfiles/rendering_tests/text-shaping.svg93
-rw-r--r--testfiles/src/2geom-characterization-test.cpp31
-rw-r--r--testfiles/src/attributes-test.cpp646
-rw-r--r--testfiles/src/color-profile-test.cpp127
-rw-r--r--testfiles/src/curve-test.cpp258
-rw-r--r--testfiles/src/cxxtests-to-migrate/marker-test.h42
-rw-r--r--testfiles/src/cxxtests-to-migrate/mod360-test.h65
-rw-r--r--testfiles/src/cxxtests-to-migrate/preferences-test.h139
-rw-r--r--testfiles/src/cxxtests-to-migrate/sp-style-elem-test.h175
-rw-r--r--testfiles/src/cxxtests-to-migrate/test-helpers.h75
-rw-r--r--testfiles/src/cxxtests-to-migrate/verbs-test.h95
-rw-r--r--testfiles/src/dir-util-test.cpp64
-rw-r--r--testfiles/src/drag-and-drop-svgz.cpp62
-rw-r--r--testfiles/src/extract-uri-test.cpp75
-rw-r--r--testfiles/src/lpe-bool-test.cpp70
-rw-r--r--testfiles/src/object-set-test.cpp636
-rw-r--r--testfiles/src/object-style-test.cpp197
-rw-r--r--testfiles/src/object-test.cpp206
-rw-r--r--testfiles/src/sp-gradient-test.cpp130
-rw-r--r--testfiles/src/sp-item-group-test.cpp46
-rw-r--r--testfiles/src/sp-object-test.cpp121
-rw-r--r--testfiles/src/style-elem-test.cpp73
-rw-r--r--testfiles/src/style-test.cpp572
-rw-r--r--testfiles/src/svg-stringstream-test.cpp156
-rw-r--r--testfiles/src/uri-test.cpp304
-rw-r--r--testfiles/unittest.cpp49
199 files changed, 11686 insertions, 0 deletions
diff --git a/testfiles/CMakeLists.txt b/testfiles/CMakeLists.txt
new file mode 100644
index 0000000..9fbffc1
--- /dev/null
+++ b/testfiles/CMakeLists.txt
@@ -0,0 +1,105 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# -----------------------------------------------------------------------------
+
+# custom "check" target with proper dependencies (builds inkscape and tests)
+add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure
+ DEPENDS tests
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
+add_dependencies(check inkscape)
+
+
+# create symlink "inkscape_datadir" to use as INKSCAPE_DATADIR
+# - ensures tests can be run without installing the project
+# - also helpful for running Inkscape uninstalled: 'INKSVAPE_DATADIR=inkscape_datadir bin/inkscape'
+set(INKSCAPE_DATADIR ${CMAKE_BINARY_DIR}/inkscape_datadir)
+if(NOT EXISTS ${INKSCAPE_DATADIR}/inkscape)
+ set(link_source ${INKSCAPE_DATADIR}/inkscape)
+ set(link_target ${CMAKE_SOURCE_DIR}/share)
+ message(STATUS "Creating link '${link_source}' --> '${link_target}'")
+ execute_process(COMMAND mkdir inkscape_datadir)
+ execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink ${link_target} ${link_source}
+ RESULT_VARIABLE result)
+ if(result)
+ message(WARNING "Creation of link failed: ${result}")
+ endif()
+endif()
+# check if creation succeeded
+if(EXISTS ${INKSCAPE_DATADIR}/inkscape)
+ set(CMAKE_CTEST_ENV INKSCAPE_DATADIR=${INKSCAPE_DATADIR})
+else()
+ message(WARNING "Directory 'inkscape_datadir/inkscape' missing. Tests might not run properly.\n"
+ "Possible solutions:\n"
+ " - create a suitable symlink yourself, e.g.\n"
+ " ln -s ${CMAKE_SOURCE_DIR}/share ${INKSCAPE_DATADIR}/inkscape\n"
+ " - run '${CMAKE_MAKE_PROGRAM} install' before running tests (only for not relocatable packages.\n"
+ " - set the environment variable 'INKSCAPE_DATADIR' manually (every time you run tests)")
+endif()
+
+
+# Set custom profile directory for tests using environment variable.
+# Copy CTestCustom.cmake into binary dir, where it will be picked up automatically by ctest for cleanup.
+set(INKSCAPE_TEST_PROFILE_DIR ${CMAKE_CURRENT_BINARY_DIR}/test_profile_dir)
+set(INKSCAPE_TEST_PROFILE_DIR_ENV INKSCAPE_PROFILE_DIR=${INKSCAPE_TEST_PROFILE_DIR})
+configure_file(CTestCustom.cmake.in ${CMAKE_BINARY_DIR}/CTestCustom.cmake)
+
+
+
+### tests using gtest
+include_directories("${CMAKE_SOURCE_DIR}/src/3rdparty/adaptagrams") # TODO: remove this hack
+
+set(TEST_SOURCES
+ uri-test
+ drag-and-drop-svgz
+ extract-uri-test
+ attributes-test
+ color-profile-test
+ dir-util-test
+ sp-object-test
+ object-set-test
+ object-style-test
+ style-elem-test
+ style-test
+ svg-stringstream-test
+ sp-gradient-test
+ object-test
+ curve-test
+ 2geom-characterization-test
+ lpe-bool-test
+ sp-item-group-test)
+
+set(TEST_LIBS
+ ${GTEST_LIBRARIES}
+ inkscape_base)
+
+add_library(cpp_test_object_library OBJECT unittest.cpp doc-per-case-test.cpp)
+
+add_custom_target(tests)
+foreach(test_source ${TEST_SOURCES})
+ string(REPLACE "-test" "" testname "test_${test_source}")
+ add_executable(${testname} src/${test_source}.cpp $<TARGET_OBJECTS:cpp_test_object_library>)
+ target_include_directories(${testname} SYSTEM PRIVATE ${GTEST_INCLUDE_DIRS})
+ target_link_libraries(${testname} ${TEST_LIBS})
+ add_test(NAME ${testname} COMMAND ${testname})
+ set_tests_properties(${testname} PROPERTIES ENVIRONMENT "${INKSCAPE_TEST_PROFILE_DIR_ENV}/${testname};${CMAKE_CTEST_ENV}")
+ add_dependencies(tests ${testname})
+endforeach()
+
+
+### CLI and rendering tests
+add_subdirectory(cli_tests)
+add_subdirectory(rendering_tests)
+
+
+### Fuzz test
+if(WITH_FUZZ)
+ # to use the fuzzer, make sure you use the right compiler (clang)
+ # with the right flags -fsanitize=address -fsanitize-coverage=edge,trace-pc-guard,indirect-calls,trace-cmp,trace-div,trace-gep -fno-omit-frame-pointer
+ # (see libfuzzer doc for info in flags)
+ # first line is for integration into oss-fuzz https://github.com/google/oss-fuzz
+ add_executable(fuzz fuzzer.cpp)
+ if(LIB_FUZZING_ENGINE)
+ target_link_libraries(fuzz inkscape_base -lFuzzingEngine)
+ else()
+ target_link_libraries(fuzz inkscape_base -lFuzzer)
+ endif()
+endif()
diff --git a/testfiles/CTestCustom.cmake.in b/testfiles/CTestCustom.cmake.in
new file mode 100644
index 0000000..9c1c834
--- /dev/null
+++ b/testfiles/CTestCustom.cmake.in
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+# Cleanup test-specific profile directories whenever running tests (before and after, just to be safe)
+set(CTEST_CUSTOM_PRE_TEST "rm -rf ${INKSCAPE_TEST_PROFILE_DIR}")
+set(CTEST_CUSTOM_POST_TEST "rm -rf ${INKSCAPE_TEST_PROFILE_DIR}")
diff --git a/testfiles/cli_tests/CMakeLists.txt b/testfiles/cli_tests/CMakeLists.txt
new file mode 100644
index 0000000..032cd95
--- /dev/null
+++ b/testfiles/cli_tests/CMakeLists.txt
@@ -0,0 +1,441 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+
+# Helper function to add a CLI test
+#
+# Run an Inkscape command line and check for pass/fail condition (by default only exit status is checked)
+#
+# Command line options:
+# INPUT_FILENAME - name of input file (optional)
+# OUTPUT_FILENAME - name of output file (optional)
+# PARAMETERS - additional command line parameters to pass to Inkscape
+#
+# Pass/fail criteria:
+# PASS_FOR_OUTPUT - pass if output matches the given value, otherwise fail
+# see https://cmake.org/cmake/help/latest/prop_test/PASS_REGULAR_EXPRESSION.html for details
+# FAIL_FOR_OUTPUT - fail if output matches the given value
+# see https://cmake.org/cmake/help/latest/prop_test/FAIL_REGULAR_EXPRESSION.html for details
+# REFERENCE_FILENAME - compare OUTPUT_FILENAME with this pre-rendered reference file
+# both files are converted to PNG and compared with ImageMagick's 'compare'
+# EXPECTED_FILES - verify the command produced the expected files (i.e. they exist on disk)
+# TEST_SCRIPT - additional script to run after performing all checks and before cleaning up
+#
+# Other options:
+# ENVIRONMENT - Additional environment variables to set while running the test
+function(add_cli_test name)
+ # parse arguments
+ set(oneValueArgs INPUT_FILENAME OUTPUT_FILENAME PASS_FOR_OUTPUT FAIL_FOR_OUTPUT REFERENCE_FILENAME)
+ set(multiValueArgs PARAMETERS EXPECTED_FILES TEST_SCRIPT ENVIRONMENT)
+ cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+
+ set(testname cli_${name})
+
+ if(DEFINED ARG_OUTPUT_FILENAME)
+ set(ARG_PARAMETERS ${ARG_PARAMETERS} "--export-filename=${ARG_OUTPUT_FILENAME}")
+ endif()
+ if(DEFINED ARG_INPUT_FILENAME)
+ set(ARG_INPUT_FILENAME "${CMAKE_CURRENT_SOURCE_DIR}/testcases/${ARG_INPUT_FILENAME}")
+ set(ARG_PARAMETERS ${ARG_PARAMETERS} ${ARG_INPUT_FILENAME})
+ endif()
+
+ set(CMAKE_CTEST_ENV "${INKSCAPE_TEST_PROFILE_DIR_ENV}/${testname};${CMAKE_CTEST_ENV}")
+ if(DEFINED ARG_ENVIRONMENT)
+ if(ARG_ENVIRONMENT STREQUAL "unset")
+ unset(CMAKE_CTEST_ENV)
+ else()
+ # variables might already be set, however the last value wins
+ list(APPEND CMAKE_CTEST_ENV ${ARG_ENVIRONMENT})
+ endif()
+ endif()
+
+ # add test for main command line
+ add_test(NAME ${testname} COMMAND inkscape ${ARG_PARAMETERS})
+ set_tests_properties(${testname} PROPERTIES ENVIRONMENT "${CMAKE_CTEST_ENV}")
+ if(DEFINED ARG_PASS_FOR_OUTPUT)
+ set_tests_properties(${testname} PROPERTIES PASS_REGULAR_EXPRESSION ${ARG_PASS_FOR_OUTPUT})
+ endif()
+ if(DEFINED ARG_FAIL_FOR_OUTPUT)
+ set_tests_properties(${testname} PROPERTIES FAIL_REGULAR_EXPRESSION ${ARG_FAIL_FOR_OUTPUT})
+ endif()
+
+ # add test to check output files
+ if(DEFINED ARG_REFERENCE_FILENAME OR DEFINED ARG_EXPECTED_FILES OR DEFINED ARG_TEST_SCRIPT)
+ if(DEFINED ARG_REFERENCE_FILENAME)
+ file(TO_NATIVE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/testcases/${ARG_REFERENCE_FILENAME}" ARG_REFERENCE_FILENAME)
+ endif()
+ if(DEFINED ARG_EXPECTED_FILES)
+ string(REPLACE ";" " " ARG_EXPECTED_FILES "${ARG_EXPECTED_FILES}")
+ endif()
+ if(DEFINED ARG_TEST_SCRIPT)
+ set(ARG_TEST_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/${ARG_TEST_SCRIPT}")
+ endif()
+
+ add_test(NAME ${testname}_check_output
+ COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/check_output.sh
+ "${ARG_OUTPUT_FILENAME}" "${ARG_REFERENCE_FILENAME}" "${ARG_EXPECTED_FILES}" "${ARG_TEST_SCRIPT}")
+ set_tests_properties(${testname}_check_output PROPERTIES
+ ENVIRONMENT "${CMAKE_CTEST_ENV}" DEPENDS ${testname} SKIP_RETURN_CODE 42)
+ endif()
+endfunction(add_cli_test)
+
+
+
+##### Tests follow below #####
+
+
+
+#############################################################################################
+### Command line options (basic tests for all program options as listed in --help output) ###
+#############################################################################################
+
+# --help
+
+# --version (check if we can run inkscape and the revision is known)
+add_cli_test(version PARAMETERS --version)
+add_cli_test(version_known PARAMETERS --version FAIL_FOR_OUTPUT unknown)
+
+# --system-data-directory / --user-data-directory (unset environment variables to override our override)
+# TODO: Can we make these tests more specific without making too many assumptions?
+add_cli_test(system-data-directory PARAMETERS --system-data-directory ENVIRONMENT unset PASS_FOR_OUTPUT "inkscape\n$")
+add_cli_test(user-data-directory PARAMETERS --user-data-directory ENVIRONMENT unset PASS_FOR_OUTPUT "inkscape\n$")
+
+# --pipe
+
+# --pdf-page=PAGE
+
+# --pdf-poppler
+add_cli_test(pdf-poppler-mesh-import
+ PARAMETERS --pdf-poppler
+ INPUT_FILENAME pdf-mesh.pdf
+ OUTPUT_FILENAME pdf-mesh_poppler.svg
+ TEST_SCRIPT match_regex.sh pdf-mesh_poppler.svg "<image")
+add_cli_test(pdf-internal-mesh-import
+ INPUT_FILENAME pdf-mesh.pdf
+ OUTPUT_FILENAME pdf-mesh_internal.svg
+ TEST_SCRIPT match_regex_fail.sh pdf-mesh_internal.svg "<image")
+
+# --convert-dpi-method=METHOD
+
+# --no-convert-text-baseline-spacing
+
+# --export-filename=FILENAME
+
+# --export-overwrite
+
+# --export-type=TYPE[,TYPE]*
+
+## test whether we can export all default types in principle (and in one command)
+add_cli_test(export-type PARAMETERS --export-type=svg,png,ps,eps,pdf,emf,wmf,xaml
+ INPUT_FILENAME empty.svg OUTPUT_FILENAME empty
+ EXPECTED_FILES empty.svg empty.png empty.ps empty.eps empty.pdf empty.emf empty.wmf empty.xaml)
+
+## test whether we produce sane output for the default types
+add_cli_test(export-type_svg PARAMETERS --export-type=svg INPUT_FILENAME shapes.svg OUTPUT_FILENAME shapes.svg REFERENCE_FILENAME shapes.svg)
+add_cli_test(export-type_png PARAMETERS --export-type=png INPUT_FILENAME shapes.svg OUTPUT_FILENAME shapes.png REFERENCE_FILENAME shapes_expected.png)
+add_cli_test(export-type_ps PARAMETERS --export-type=ps INPUT_FILENAME shapes.svg OUTPUT_FILENAME shapes.ps REFERENCE_FILENAME shapes_expected.ps)
+add_cli_test(export-type_eps PARAMETERS --export-type=eps INPUT_FILENAME shapes.svg OUTPUT_FILENAME shapes.eps REFERENCE_FILENAME shapes_expected.eps)
+add_cli_test(export-type_pdf PARAMETERS --export-type=pdf INPUT_FILENAME shapes.svg OUTPUT_FILENAME shapes.pdf REFERENCE_FILENAME shapes_expected.pdf)
+add_cli_test(export-type_emf PARAMETERS --export-type=emf INPUT_FILENAME shapes.svg OUTPUT_FILENAME shapes.emf REFERENCE_FILENAME shapes_expected.emf)
+add_cli_test(export-type_wmf PARAMETERS --export-type=wmf INPUT_FILENAME shapes.svg OUTPUT_FILENAME shapes.wmf REFERENCE_FILENAME shapes_expected.wmf)
+# XAML is not supported by ImageMagick's convert, so simply compare binary
+add_cli_test(export-type_xaml PARAMETERS --export-type=xaml INPUT_FILENAME shapes.svg OUTPUT_FILENAME shapes.xaml
+ TEST_SCRIPT compare.sh shapes.xaml "${CMAKE_CURRENT_SOURCE_DIR}/testcases/shapes_expected.xaml")
+
+# --export-area-page
+add_cli_test(export-area-page_png PARAMETERS --export-area-page --export-type=png INPUT_FILENAME areas.svg OUTPUT_FILENAME export-area-page.png REFERENCE_FILENAME export-area-page_expected.png)
+add_cli_test(export-area-page_svg PARAMETERS --export-area-page --export-type=svg INPUT_FILENAME areas.svg OUTPUT_FILENAME export-area-page.svg REFERENCE_FILENAME export-area-page_expected.svg)
+add_cli_test(export-area-page_pdf PARAMETERS --export-area-page --export-type=pdf INPUT_FILENAME areas.svg OUTPUT_FILENAME export-area-page.pdf REFERENCE_FILENAME export-area-page_expected.pdf)
+add_cli_test(export-area-page_ps PARAMETERS --export-area-page --export-type=ps INPUT_FILENAME areas.svg OUTPUT_FILENAME export-area-page.ps REFERENCE_FILENAME export-area-page_expected.ps)
+# EPS: Currently not supported. Feature request: https://gitlab.com/inkscape/inkscape/-/issues/1074
+# add_cli_test(export-area-page_eps PARAMETERS --export-area-page --export-type=eps INPUT_FILENAME areas.svg OUTPUT_FILENAME export-area-page.eps REFERENCE_FILENAME export-area-page_expected.eps)
+add_cli_test(export-area-page_emf PARAMETERS --export-area-page --export-type=emf INPUT_FILENAME areas.svg OUTPUT_FILENAME export-area-page.emf REFERENCE_FILENAME export-area-page_expected.emf)
+add_cli_test(export-area-page_wmf PARAMETERS --export-area-page --export-type=wmf INPUT_FILENAME areas.svg OUTPUT_FILENAME export-area-page.wmf REFERENCE_FILENAME export-area-page_expected.wmf)
+
+# --export-area-page + --export-id (+ --export-id-only)
+# TODO: PDF/PS/EPS always behave as if --export-id-only was given, see https://gitlab.com/inkscape/inkscape/-/issues/1173
+add_cli_test(export-area-page_export-id_png PARAMETERS --export-area-page --export-id=MyStar --export-id-only INPUT_FILENAME areas.svg OUTPUT_FILENAME export-area-page_export-id.png REFERENCE_FILENAME export-area-page_export-id.png)
+add_cli_test(export-area-page_export-id_svg PARAMETERS --export-area-page --export-id=MyStar --export-id-only INPUT_FILENAME areas.svg OUTPUT_FILENAME export-area-page_export-id.svg REFERENCE_FILENAME export-area-page_export-id.svg)
+add_cli_test(export-area-page_export-id_pdf PARAMETERS --export-area-page --export-id=MyStar INPUT_FILENAME areas.svg OUTPUT_FILENAME export-area-page_export-id.pdf REFERENCE_FILENAME export-area-page_export-id.pdf)
+add_cli_test(export-area-page_export-id_ps PARAMETERS --export-area-page --export-id=MyStar INPUT_FILENAME areas.svg OUTPUT_FILENAME export-area-page_export-id.ps REFERENCE_FILENAME export-area-page_export-id.ps)
+# EPS: Currently not supported. Feature request: https://gitlab.com/inkscape/inkscape/-/issues/1074
+#add_cli_test(export-area-page_export-id_eps PARAMETERS --export-area-page --export-id=MyStar INPUT_FILENAME areas.svg OUTPUT_FILENAME export-area-page_export-id.eps REFERENCE_FILENAME export-area-page_export-id.eps)
+# EMF, WMF: Nont supported.
+
+# --export-area-drawing
+add_cli_test(export-area-drawing_png PARAMETERS --export-area-drawing --export-type=png INPUT_FILENAME areas.svg OUTPUT_FILENAME export-area-drawing.png REFERENCE_FILENAME export-area-drawing_expected.png)
+add_cli_test(export-area-drawing_svg PARAMETERS --export-area-drawing --export-type=svg INPUT_FILENAME areas.svg OUTPUT_FILENAME export-area-drawing.svg REFERENCE_FILENAME export-area-drawing_expected.svg)
+add_cli_test(export-area-drawing_pdf PARAMETERS --export-area-drawing --export-type=pdf INPUT_FILENAME areas.svg OUTPUT_FILENAME export-area-drawing.pdf REFERENCE_FILENAME export-area-drawing_expected.pdf)
+add_cli_test(export-area-drawing_ps PARAMETERS --export-area-drawing --export-type=ps INPUT_FILENAME areas.svg OUTPUT_FILENAME export-area-drawing.ps REFERENCE_FILENAME export-area-drawing_expected.ps)
+add_cli_test(export-area-drawing_eps PARAMETERS --export-area-drawing --export-type=eps INPUT_FILENAME areas.svg OUTPUT_FILENAME export-area-drawing.eps REFERENCE_FILENAME export-area-drawing_expected.eps)
+# EMF, WMF: Currently not supported. Feature request: https://gitlab.com/inkscape/inkscape/-/issues/1056
+# add_cli_test(export-area-drawing_emf PARAMETERS --export-area-drawing --export-type=emf INPUT_FILENAME areas.svg OUTPUT_FILENAME export-area-drawing.emf REFERENCE_FILENAME export-area-drawing_expected.emf)
+# add_cli_test(export-area-drawing_wmf PARAMETERS --export-area-drawing --export-type=wmf INPUT_FILENAME areas.svg OUTPUT_FILENAME export-area-drawing.wmf REFERENCE_FILENAME export-area-drawing_expected.wmf)
+
+# --export-area=x0:y0:x1:y1
+add_cli_test(export-area_png PARAMETERS --export-area=150:150:350:300 --export-type=png INPUT_FILENAME areas.svg OUTPUT_FILENAME export-area.png REFERENCE_FILENAME export-area_expected.png)
+# SVG, PDF, PS, EPS, EMF, WMF: Not supported. Feature request: https://gitlab.com/inkscape/inkscape/-/issues/678
+
+# --export-area-snap
+add_cli_test(export-area-snap_export-id PARAMETERS --export-id=rect_misaligned --export-area-snap --export-type=png INPUT_FILENAME pyramids.svg OUTPUT_FILENAME export-area-snap-id.png REFERENCE_FILENAME export-area-snap_expected.png)
+add_cli_test(export-area-snap_export-area-drawing PARAMETERS --export-area-drawing --export-area-snap --export-type=png INPUT_FILENAME pyramids.svg OUTPUT_FILENAME export-area-snap-drawing.png REFERENCE_FILENAME export-area-snap_expected.png)
+# SVG, PDF, PS, EPS, EMF, WMF: doesn't make sense
+
+# --export-dpi=DPI
+add_cli_test(export-dpi_png PARAMETERS --export-dpi=12 --export-type=png INPUT_FILENAME filter.svg OUTPUT_FILENAME export-dpi.png REFERENCE_FILENAME export-dpi_expected.png)
+# SVG: doesn't make sense
+add_cli_test(export-dpi_pdf PARAMETERS --export-dpi=12 --export-type=pdf INPUT_FILENAME filter.svg OUTPUT_FILENAME export-dpi.pdf REFERENCE_FILENAME export-dpi_expected.pdf)
+add_cli_test(export-dpi_ps PARAMETERS --export-dpi=12 --export-type=ps INPUT_FILENAME filter.svg OUTPUT_FILENAME export-dpi.ps REFERENCE_FILENAME export-dpi_expected.ps)
+add_cli_test(export-dpi_eps PARAMETERS --export-dpi=12 --export-type=eps INPUT_FILENAME filter.svg OUTPUT_FILENAME export-dpi.eps REFERENCE_FILENAME export-dpi_expected.eps)
+# EMF, WMF: doesn't make sense
+
+# --export-width=WIDTH / --export-height=HEIGHT
+add_cli_test(export-width PARAMETERS --export-width=380 --export-type=png INPUT_FILENAME export_hints.svg OUTPUT_FILENAME export-width.png REFERENCE_FILENAME export-width_expected.png)
+add_cli_test(export-width_export-dpi PARAMETERS --export-width=380 --export-dpi=300 --export-type=png INPUT_FILENAME export_hints.svg OUTPUT_FILENAME export-width2.png REFERENCE_FILENAME export-width_expected.png)
+add_cli_test(export-width_export-use-hints PARAMETERS --export-width=380 --export-use-hints --export-id=rect1 --export-type=png INPUT_FILENAME export_hints.svg
+ PASS_FOR_OUTPUT "Area 10:10:90:70 exported to 380 x 285 pixels \\(456 dpi\\)"
+ EXPECTED_FILES "${CMAKE_CURRENT_SOURCE_DIR}/testcases/export_hints_rectangle.png")
+add_cli_test(export-height PARAMETERS --export-height=40 --export-type=png INPUT_FILENAME export_hints.svg OUTPUT_FILENAME export-height.png REFERENCE_FILENAME export-height_expected.png)
+add_cli_test(export-height_export-dpi PARAMETERS --export-height=40 --export-dpi=300 --export-type=png INPUT_FILENAME export_hints.svg OUTPUT_FILENAME export-height2.png REFERENCE_FILENAME export-height_expected.png)
+add_cli_test(export-height_export-use-hints PARAMETERS --export-height=40 --export-use-hints --export-id=rect1 --export-type=png INPUT_FILENAME export_hints.svg
+ PASS_FOR_OUTPUT "Area 10:10:90:70 exported to 53 x 40 pixels \\(64 dpi\\)"
+ EXPECTED_FILES "${CMAKE_CURRENT_SOURCE_DIR}/testcases/export_hints_rectangle.png")
+# SVG, PDF, PS, EPS, EMF, WMF: doesn't make sense
+
+# --export-margin=MARGIN
+# There are many problems:
+# - PNG, EPS, EMF, WMF: --export-margin is't supported. This affects all PNG, EPS, EMF, WMF tests below. Feature request: https://gitlab.com/inkscape/inkscape/-/issues/1142
+# - PDF: Defaults to margin in millimeters. This affects all mm based PDF tests below. Feature request: https://gitlab.com/inkscape/inkscape/-/issues/1142
+# - PS: Defaults to margin in pixels. This affects all px based OS tests below. Feature request: https://gitlab.com/inkscape/inkscape/-/issues/1142
+# - --export-id for PDF/PS/EPS is buggy: works as --export-id + --export-id-only should work. See: https://gitlab.com/inkscape/inkscape/-/issues/1173
+# - --export-margin + --export-area=x0:y0:x1:y1 is PNG only feature.
+# - --export-margin + --export-use-hints is PNG only feature.
+# There is no test for --export-margin + --export-area-page combination because it's equivalent to --export-margin
+
+## simple --export-margin (millimeter based)
+# add_cli_test(export-margin_mm_png PARAMETERS --export-margin=50 --export-type=png INPUT_FILENAME square_mm.svg OUTPUT_FILENAME export-margin_mm.png REFERENCE_FILENAME export-margin_mm_expected.png )
+add_cli_test(export-margin_mm_svg PARAMETERS --export-margin=50 --export-type=svg INPUT_FILENAME square_mm.svg OUTPUT_FILENAME export-margin_mm.svg REFERENCE_FILENAME export-margin_mm_expected.svg )
+add_cli_test(export-margin_mm_pdf PARAMETERS --export-margin=50 --export-type=pdf INPUT_FILENAME square_mm.svg OUTPUT_FILENAME export-margin_mm.pdf REFERENCE_FILENAME export-margin_mm_expected.pdf )
+# add_cli_test(export-margin_mm_ps PARAMETERS --export-margin=50 --export-type=ps INPUT_FILENAME square_mm.svg OUTPUT_FILENAME export-margin_mm.ps REFERENCE_FILENAME export-margin_mm_expected.ps )
+# add_cli_test(export-margin_mm_eps PARAMETERS --export-margin=50 --export-type=eps INPUT_FILENAME square_mm.svg OUTPUT_FILENAME export-margin_mm.eps REFERENCE_FILENAME export-margin_mm_expected.eps )
+# add_cli_test(export-margin_mm_emf PARAMETERS --export-margin=50 --export-type=emf INPUT_FILENAME square_mm.svg OUTPUT_FILENAME export-margin_mm.emf REFERENCE_FILENAME export-margin_mm_expected.emf )
+# add_cli_test(export-margin_mm_wmf PARAMETERS --export-margin=50 --export-type=wmf INPUT_FILENAME square_mm.svg OUTPUT_FILENAME export-margin_mm.wmf REFERENCE_FILENAME export-margin_mm_expected.wmf )
+
+## simple --export-margin (pixel based)
+# add_cli_test(export-margin_px_png PARAMETERS --export-margin=50 --export-type=png INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_px.png REFERENCE_FILENAME export-margin_px_expected.png )
+add_cli_test(export-margin_px_svg PARAMETERS --export-margin=50 --export-type=svg INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_px.svg REFERENCE_FILENAME export-margin_px_expected.svg )
+# add_cli_test(export-margin_px_pdf PARAMETERS --export-margin=50 --export-type=pdf INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_px.pdf REFERENCE_FILENAME export-margin_px_expected.pdf )
+add_cli_test(export-margin_px_ps PARAMETERS --export-margin=50 --export-type=ps INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_px.ps REFERENCE_FILENAME export-margin_px_expected.ps )
+# add_cli_test(export-margin_px_eps PARAMETERS --export-margin=50 --export-type=eps INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_px.eps REFERENCE_FILENAME export-margin_px_expected.eps )
+# add_cli_test(export-margin_px_emf PARAMETERS --export-margin=50 --export-type=emf INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_px.emf REFERENCE_FILENAME export-margin_px_expected.emf )
+# add_cli_test(export-margin_px_wmf PARAMETERS --export-margin=50 --export-type=wmf INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_px.wmf REFERENCE_FILENAME export-margin_px_expected.wmf )
+
+## --export-margin + --export-id (pixel based)
+# add_cli_test(export-margin_export-id_png PARAMETERS --export-margin=50 --export-id=square-red --export-type=png INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_export-id.png REFERENCE_FILENAME export-margin_export-id_expected.png )
+add_cli_test(export-margin_export-id_svg PARAMETERS --export-margin=50 --export-id=square-red --export-type=svg INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_export-id.svg REFERENCE_FILENAME export-margin_export-id_expected.svg )
+# add_cli_test(export-margin_export-id_pdf PARAMETERS --export-margin=50 --export-id=square-red --export-type=pdf INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_export-id.pdf REFERENCE_FILENAME export-margin_export-id_expected.pdf )
+# add_cli_test(export-margin_export-id_ps PARAMETERS --export-margin=50 --export-id=square-red --export-type=ps INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_export-id.ps REFERENCE_FILENAME export-margin_export-id_expected.ps )
+# add_cli_test(export-margin_export-id_eps PARAMETERS --export-margin=50 --export-id=square-red --export-type=eps INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_export-id.eps REFERENCE_FILENAME export-margin_export-id_expected.eps )
+# add_cli_test(export-margin_export-id_emf PARAMETERS --export-margin=50 --export-id=square-red --export-type=emf INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_export-id.emf REFERENCE_FILENAME export-margin_export-id_expected.emf )
+# add_cli_test(export-margin_export-id_wmf PARAMETERS --export-margin=50 --export-id=square-red --export-type=wmf INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_export-id.wmf REFERENCE_FILENAME export-margin_export-id_expected.wmf )
+
+## --export-margin + --export-id + export-id-only (pixel based)
+# add_cli_test(export-margin_export-id_export-id-only_png PARAMETERS --export-margin=50 --export-id=square-red --export-id-only --export-type=png INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_export-id_export-id-only.png REFERENCE_FILENAME export-margin_export-id_export-id-only_expected.png )
+add_cli_test(export-margin_export-id_export-id-only_svg PARAMETERS --export-margin=50 --export-id=square-red --export-id-only --export-type=svg INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_export-id_export-id-only.svg REFERENCE_FILENAME export-margin_export-id_export-id-only_expected.svg )
+# add_cli_test(export-margin_export-id_export-id-only_pdf PARAMETERS --export-margin=50 --export-id=square-red --export-id-only --export-type=pdf INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_export-id_export-id-only.pdf REFERENCE_FILENAME export-margin_export-id_export-id-only_expected.pdf )
+add_cli_test(export-margin_export-id_export-id-only_ps PARAMETERS --export-margin=50 --export-id=square-red --export-id-only --export-type=ps INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_export-id_export-id-only.ps REFERENCE_FILENAME export-margin_export-id_export-id-only_expected.ps )
+# add_cli_test(export-margin_export-id_export-id-only_eps PARAMETERS --export-margin=50 --export-id=square-red --export-id-only --export-type=eps INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_export-id_export-id-only.eps REFERENCE_FILENAME export-margin_export-id_export-id-only_expected.eps )
+# add_cli_test(export-margin_export-id_export-id-only_emf PARAMETERS --export-margin=50 --export-id=square-red --export-id-only --export-type=emf INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_export-id_export-id-only.emf REFERENCE_FILENAME export-margin_export-id_export-id-only_expected.emf )
+# add_cli_test(export-margin_export-id_export-id-only_wmf PARAMETERS --export-margin=50 --export-id=square-red --export-id-only --export-type=wmf INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_export-id_export-id-only.wmf REFERENCE_FILENAME export-margin_export-id_export-id-only_expected.wmf )
+
+## --export-margin + --export-area-drawing (pixel based)
+# add_cli_test(export-margin_export-area-drawing_png PARAMETERS --export-margin=50 --export-area-drawing --export-type=png INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_drawing.png REFERENCE_FILENAME export-margin_drawing_expected.png )
+add_cli_test(export-margin_export-area-drawing_svg PARAMETERS --export-margin=50 --export-area-drawing --export-type=svg INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_drawing.svg REFERENCE_FILENAME export-margin_drawing_expected.svg )
+# add_cli_test(export-margin_export-area-drawing_pdf PARAMETERS --export-margin=50 --export-area-drawing --export-type=pdf INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_drawing.pdf REFERENCE_FILENAME export-margin_drawing_expected.pdf )
+add_cli_test(export-margin_export-area-drawing_ps PARAMETERS --export-margin=50 --export-area-drawing --export-type=ps INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_drawing.ps REFERENCE_FILENAME export-margin_drawing_expected.ps )
+# add_cli_test(export-margin_export-area-drawing_eps PARAMETERS --export-margin=50 --export-area-drawing --export-type=eps INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_drawing.eps REFERENCE_FILENAME export-margin_drawing_expected.eps )
+# add_cli_test(export-margin_export-area-drawing_emf PARAMETERS --export-margin=50 --export-area-drawing --export-type=emf INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_drawing.emf REFERENCE_FILENAME export-margin_drawing_expected.emf )
+# add_cli_test(export-margin_export-area-drawing_wmf PARAMETERS --export-margin=50 --export-area-drawing --export-type=wmf INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_drawing.wmf REFERENCE_FILENAME export-margin_drawing_expected.wmf )
+
+## --export-margin + --export-area=x0:y0:x1:y1 (pixel based)
+# add_cli_test(export-margin_export-area_png PARAMETERS --export-margin=50 --export-area=50:50:100:100 --export-type=png INPUT_FILENAME square_px.svg OUTPUT_FILENAME export-margin_export-area.png REFERENCE_FILENAME export-margin_export-area_expected.png )
+
+## --export-margin + --export-use-hints (pixel based)
+# add_cli_test(export-margin_export-use-hints_export-id PARAMETERS --export-margin=50 --export-use-hints --export-id=rect1 INPUT_FILENAME export_hints.svg PASS_FOR_OUTPUT "Area 10:10:90:70 exported to 203 x 177 pixels \\(123 dpi\\)" EXPECTED_FILES "${CMAKE_CURRENT_SOURCE_DIR}/testcases/export_hints_rectangle.png")
+
+## --export-margin (millimeter based, user units rescaled via viewBox)
+# add_cli_test(export-margin_viewbox_png PARAMETERS --export-margin=50 --export-type=png INPUT_FILENAME square_mm_viewbox.svg OUTPUT_FILENAME export-margin_viewbox.png REFERENCE_FILENAME export-margin_mm_viewbox_page_expected.png )
+add_cli_test(export-margin_viewbox_svg PARAMETERS --export-margin=50 --export-type=svg INPUT_FILENAME square_mm_viewbox.svg OUTPUT_FILENAME export-margin_viewbox.svg REFERENCE_FILENAME export-margin_mm_viewbox_page_expected.svg )
+# add_cli_test(export-margin_viewbox_pdf PARAMETERS --export-margin=50 --export-type=pdf INPUT_FILENAME square_mm_viewbox.svg OUTPUT_FILENAME export-margin_viewbox.pdf REFERENCE_FILENAME export-margin_mm_viewbox_page_expected.pdf )
+# add_cli_test(export-margin_viewbox_ps PARAMETERS --export-margin=50 --export-type=ps INPUT_FILENAME square_mm_viewbox.svg OUTPUT_FILENAME export-margin_viewbox.ps REFERENCE_FILENAME export-margin_mm_viewbox_page_expected.ps )
+# add_cli_test(export-margin_viewbox_eps PARAMETERS --export-margin=50 --export-type=eps INPUT_FILENAME square_mm_viewbox.svg OUTPUT_FILENAME export-margin_viewbox.eps REFERENCE_FILENAME export-margin_mm_viewbox_page_expected.eps )
+# add_cli_test(export-margin_viewbox_emf PARAMETERS --export-margin=50 --export-type=emf INPUT_FILENAME square_mm_viewbox.svg OUTPUT_FILENAME export-margin_viewbox.emf REFERENCE_FILENAME export-margin_mm_viewbox_page_expected.emf )
+# add_cli_test(export-margin_viewbox_wmf PARAMETERS --export-margin=50 --export-type=wmf INPUT_FILENAME square_mm_viewbox.svg OUTPUT_FILENAME export-margin_viewbox.wmf REFERENCE_FILENAME export-margin_mm_viewbox_page_expected.wmf )
+
+## --export-margin + --export-area-drawing (millimeter based, user units rescaled via viewBox)
+# add_cli_test(export-margin_viewbox_drawing_png PARAMETERS --export-margin=50 --export-area-drawing --export-type=png INPUT_FILENAME square_mm_viewbox.svg OUTPUT_FILENAME export-margin_viewbox_drawing.png REFERENCE_FILENAME export-margin_mm_viewbox_drawing_expected.png )
+add_cli_test(export-margin_viewbox_drawing_svg PARAMETERS --export-margin=50 --export-area-drawing --export-type=svg INPUT_FILENAME square_mm_viewbox.svg OUTPUT_FILENAME export-margin_viewbox_drawing.svg REFERENCE_FILENAME export-margin_mm_viewbox_drawing_expected.svg )
+# add_cli_test(export-margin_viewbox_drawing_pdf PARAMETERS --export-margin=50 --export-area-drawing --export-type=pdf INPUT_FILENAME square_mm_viewbox.svg OUTPUT_FILENAME export-margin_viewbox_drawing.pdf REFERENCE_FILENAME export-margin_mm_viewbox_drawing_expected.pdf )
+# add_cli_test(export-margin_viewbox_drawing_ps PARAMETERS --export-margin=50 --export-area-drawing --export-type=ps INPUT_FILENAME square_mm_viewbox.svg OUTPUT_FILENAME export-margin_viewbox_drawing.ps REFERENCE_FILENAME export-margin_mm_viewbox_drawing_expected.ps )
+# add_cli_test(export-margin_viewbox_drawing_eps PARAMETERS --export-margin=50 --export-area-drawing --export-type=eps INPUT_FILENAME square_mm_viewbox.svg OUTPUT_FILENAME export-margin_viewbox_drawing.eps REFERENCE_FILENAME export-margin_mm_viewbox_drawing_expected.eps )
+# add_cli_test(export-margin_viewbox_drawing_emf PARAMETERS --export-margin=50 --export-area-drawing --export-type=emf INPUT_FILENAME square_mm_viewbox.svg OUTPUT_FILENAME export-margin_viewbox_drawing.emf REFERENCE_FILENAME export-margin_mm_viewbox_drawing_expected.emf )
+# add_cli_test(export-margin_viewbox_drawing_wmf PARAMETERS --export-margin=50 --export-area-drawing --export-type=wmf INPUT_FILENAME square_mm_viewbox.svg OUTPUT_FILENAME export-margin_viewbox_drawing.wmf REFERENCE_FILENAME export-margin_mm_viewbox_drawing_expected.wmf )
+
+## --export-margin + --export-id (millimeter based, user units rescaled via viewBox)
+# add_cli_test(export-margin_viewbox_id_png PARAMETERS --export-margin=50 --export-id=square-red --export-type=png INPUT_FILENAME square_mm_viewbox.svg OUTPUT_FILENAME export-margin_viewbox_id.png REFERENCE_FILENAME export-margin_mm_viewbox_id_expected.png )
+add_cli_test(export-margin_viewbox_id_svg PARAMETERS --export-margin=50 --export-id=square-red --export-type=svg INPUT_FILENAME square_mm_viewbox.svg OUTPUT_FILENAME export-margin_viewbox_id.svg REFERENCE_FILENAME export-margin_mm_viewbox_id_expected.svg )
+# add_cli_test(export-margin_viewbox_id_pdf PARAMETERS --export-margin=50 --export-id=square-red --export-type=pdf INPUT_FILENAME square_mm_viewbox.svg OUTPUT_FILENAME export-margin_viewbox_id.pdf REFERENCE_FILENAME export-margin_mm_viewbox_id_expected.pdf )
+# add_cli_test(export-margin_viewbox_id_ps PARAMETERS --export-margin=50 --export-id=square-red --export-type=ps INPUT_FILENAME square_mm_viewbox.svg OUTPUT_FILENAME export-margin_viewbox_id.ps REFERENCE_FILENAME export-margin_mm_viewbox_id_expected.ps )
+# add_cli_test(export-margin_viewbox_id_eps PARAMETERS --export-margin=50 --export-id=square-red --export-type=eps INPUT_FILENAME square_mm_viewbox.svg OUTPUT_FILENAME export-margin_viewbox_id.eps REFERENCE_FILENAME export-margin_mm_viewbox_id_expected.eps )
+# add_cli_test(export-margin_viewbox_id_emf PARAMETERS --export-margin=50 --export-id=square-red --export-type=emf INPUT_FILENAME square_mm_viewbox.svg OUTPUT_FILENAME export-margin_viewbox_id.emf REFERENCE_FILENAME export-margin_mm_viewbox_id_expected.emf )
+# add_cli_test(export-margin_viewbox_id_wmf PARAMETERS --export-margin=50 --export-id=square-red --export-type=wmf INPUT_FILENAME square_mm_viewbox.svg OUTPUT_FILENAME export-margin_viewbox_id.wmf REFERENCE_FILENAME export-margin_mm_viewbox_id_expected.wmf )
+
+# --export-id=OBJECT-ID[;OBJECT-ID]*
+
+# --export-id-only
+
+# --export-plain-svg
+add_cli_test(export-plain-svg PARAMETERS --export-type=svg --export-plain-svg
+ INPUT_FILENAME shapes.svg OUTPUT_FILENAME shapes.svg REFERENCE_FILENAME shapes.svg
+ TEST_SCRIPT match_regex_fail.sh "shapes.svg" "inkscape:|sodipodi:")
+
+# --export-ps-level=LEVEL
+
+# --export-pdf-version=VERSION
+
+# --export-text-to-path
+add_cli_test(export-text-to-path PARAMETERS --export-text-to-path
+ INPUT_FILENAME text.svg OUTPUT_FILENAME text.svg
+ TEST_SCRIPT match_regex_fail.sh "text.svg" "<text")
+
+# --export-latex
+add_cli_test(export-latex PARAMETERS --export-type=pdf --export-latex
+ INPUT_FILENAME text.svg OUTPUT_FILENAME text.pdf
+ EXPECTED_FILES text.pdf text.pdf_tex
+ TEST_SCRIPT match_regex.sh "text.pdf_tex" "some text")
+
+# --export-ignore-filters
+
+# --export-use-hints
+add_cli_test(export-use-hints_export-id PARAMETERS --export-use-hints --export-id=rect1 INPUT_FILENAME export_hints.svg
+ PASS_FOR_OUTPUT "Area 10:10:90:70 exported to 103 x 77 pixels \\(123 dpi\\)"
+ EXPECTED_FILES "${CMAKE_CURRENT_SOURCE_DIR}/testcases/export_hints_rectangle.png")
+add_cli_test(export-use-hints_export-area-drawing PARAMETERS --export-use-hints --export-area-drawing INPUT_FILENAME export_hints.svg
+ PASS_FOR_OUTPUT "Area 10:10:180:70 exported to 197 x 69 pixels \\(111 dpi\\)"
+ EXPECTED_FILES "${CMAKE_CURRENT_SOURCE_DIR}/testcases/export_hints_drawing.png")
+
+# --export-background=COLOR
+
+# --export-background-opacity=VALUE
+
+# --query-id=OBJECT-ID[,OBJECT-ID]*
+
+# --query-all / --query-x / --query-y / --query-width / --query-height
+string(CONCAT query_all_expected "rect1,10,10,80,80\n"
+ "rect2,110,20,80,70\n"
+ "rect3,210,30,80,60")
+add_cli_test(query-all PARAMETERS --query-id=rect2 --query-all INPUT_FILENAME rects.svg PASS_FOR_OUTPUT ${query_all_expected})
+add_cli_test(query-x PARAMETERS --query-id=rect2 --query-x INPUT_FILENAME rects.svg PASS_FOR_OUTPUT 110)
+add_cli_test(query-y PARAMETERS --query-id=rect2 --query-y INPUT_FILENAME rects.svg PASS_FOR_OUTPUT 20)
+add_cli_test(query-width PARAMETERS --query-id=rect2 --query-width INPUT_FILENAME rects.svg PASS_FOR_OUTPUT 80)
+add_cli_test(query-height PARAMETERS --query-id=rect2 --query-height INPUT_FILENAME rects.svg PASS_FOR_OUTPUT 70)
+
+# --vacuum-defs
+
+# --select=OBJECT-ID[,OBJECT-ID]*
+
+# --actions / --verbs
+# (see below)
+
+# --action-list / --verb-list
+add_cli_test(action-list PARAMETERS --action-list PASS_FOR_OUTPUT "file-new")
+add_cli_test(verb-list PARAMETERS --verb-list PASS_FOR_OUTPUT "FileNew:")
+
+# --with-gui
+
+# --batch-process
+
+# --shell
+
+
+
+#########################
+### actions and verbs ###
+#########################
+
+# (TODO)
+
+
+
+###########################
+### file format support ###
+###########################
+
+# librevenge formats
+if(WITH_LIBCDR)
+ # add_cli_test(import_cdr PARAMETERS --export-type=png # fails to open (regression in libcdr 1.6.0)
+ # INPUT_FILENAME librevenge_formats/corel_draw.cdr OUTPUT_FILENAME format_corel_draw.png
+ # REFERENCE_FILENAME librevenge_formats/corel_draw_expected.png)
+ add_cli_test(import_cdr2 PARAMETERS --export-type=png
+ INPUT_FILENAME librevenge_formats/corel_draw2.cdr OUTPUT_FILENAME format_corel_draw2.png
+ REFERENCE_FILENAME librevenge_formats/corel_draw2_expected.png)
+endif()
+if(WITH_LIBVISIO)
+ add_cli_test(import_vsd PARAMETERS --export-type=png
+ INPUT_FILENAME librevenge_formats/visio.vsd OUTPUT_FILENAME format_visio.vsd.png
+ REFERENCE_FILENAME librevenge_formats/visio.vsd_expected.png)
+ add_cli_test(import_vsdx PARAMETERS --export-type=png
+ INPUT_FILENAME librevenge_formats/visio.vsdx OUTPUT_FILENAME format_visio.vsdx.png
+ REFERENCE_FILENAME librevenge_formats/visio.vsdx_expected.png)
+endif()
+if(WITH_LIBWPG)
+ add_cli_test(import_wpg PARAMETERS --export-type=png
+ INPUT_FILENAME librevenge_formats/word_perfect.wpg OUTPUT_FILENAME format_word_perfect.png
+ REFERENCE_FILENAME librevenge_formats/word_perfect_expected.png)
+endif()
+
+
+
+##############################
+### advanced functionality ###
+##############################
+
+# check whether INKSCAPE_DATADIR / INKSCAPE_PROFILE_DIR environment variables work
+# TODO: INKSCAPE_PROFILE_DIR does not seem to be sanitized at all (i.e. is used verbatim by Inkscape)
+set(fancy_dir "i_certainly_do_not_exist")
+file(TO_NATIVE_PATH "${fancy_dir}/inkscape" expected_dir)
+string(REPLACE "\\" "\\\\" expected_dir "${expected_dir}")
+add_cli_test(inkscape_datadir PARAMETERS --system-data-directory
+ ENVIRONMENT INKSCAPE_DATADIR=${fancy_dir}
+ PASS_FOR_OUTPUT "${expected_dir}\n$")
+add_cli_test(inkscape_profile_dir PARAMETERS --user-data-directory
+ ENVIRONMENT INKSCAPE_PROFILE_DIR=${fancy_dir}/inkscape
+ PASS_FOR_OUTPUT "${fancy_dir}/inkscape\n$")
+add_cli_test(inkscape_profile_dir_handle_illegal
+ ENVIRONMENT INKSCAPE_PROFILE_DIR=invalid:dir
+ INPUT_FILENAME empty.svg OUTPUT_FILENAME empty.svg)
+
+# check if "systemLanguage" attribute is properly handled
+add_cli_test(systemLanguage_en ENVIRONMENT LANGUAGE=en INPUT_FILENAME systemLanguage.svg
+ OUTPUT_FILENAME systemLanguage_en.png
+ REFERENCE_FILENAME systemLanguage_en.png)
+add_cli_test(systemLanguage_fr ENVIRONMENT LANGUAGE=fr_FR INPUT_FILENAME systemLanguage.svg
+ OUTPUT_FILENAME systemLanguage_fr.png
+ REFERENCE_FILENAME systemLanguage_fr.png)
+add_cli_test(systemLanguage_fr2 ENVIRONMENT LANGUAGE=fr_FR.UTF-8 INPUT_FILENAME systemLanguage.svg
+ OUTPUT_FILENAME systemLanguage_fr2.png
+ REFERENCE_FILENAME systemLanguage_fr.png)
+add_cli_test(systemLanguage_de ENVIRONMENT LANGUAGE=de INPUT_FILENAME systemLanguage.svg
+ OUTPUT_FILENAME systemLanguage_de.png
+ REFERENCE_FILENAME systemLanguage_de.png)
+add_cli_test(systemLanguage_de-CH ENVIRONMENT LANGUAGE=de_CH INPUT_FILENAME systemLanguage.svg
+ OUTPUT_FILENAME systemLanguage_de-CH.png
+ REFERENCE_FILENAME systemLanguage_de.png)
+add_cli_test(systemLanguage_pt ENVIRONMENT LANGUAGE=pt INPUT_FILENAME systemLanguage.svg
+ OUTPUT_FILENAME systemLanguage_pt.png
+ REFERENCE_FILENAME systemLanguage_pt.png)
+add_cli_test(systemLanguage_xy ENVIRONMENT LANGUAGE=xy INPUT_FILENAME systemLanguage.svg
+ OUTPUT_FILENAME systemLanguage_xy.png
+ REFERENCE_FILENAME systemLanguage_default.png)
+add_cli_test(systemLanguage_fr_RDF ENVIRONMENT LANGUAGE=xy INPUT_FILENAME systemLanguage_RDF.svg
+ OUTPUT_FILENAME systemLanguage_fr_RDF.png
+ REFERENCE_FILENAME systemLanguage_fr.png)
diff --git a/testfiles/cli_tests/check_output.sh b/testfiles/cli_tests/check_output.sh
new file mode 100644
index 0000000..66baa73
--- /dev/null
+++ b/testfiles/cli_tests/check_output.sh
@@ -0,0 +1,69 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+command -v convert >/dev/null 2>&1 || { echo >&2 "I require ImageMagick's 'convert' but it's not installed. Aborting."; exit 1; }
+command -v compare >/dev/null 2>&1 || { echo >&2 "I require ImageMagick's 'compare' but it's not installed. Aborting."; exit 1; }
+
+OUTPUT_FILENAME=$1
+REFERENCE_FILENAME=$2
+EXPECTED_FILES=$3
+TEST_SCRIPT=$4
+
+# check if expected files exist
+for file in ${EXPECTED_FILES}; do
+ test -f "${file}" || { echo "Error: Expected file '${file}' not found."; exit 1; }
+done
+
+# if reference file is given check if input files exist and continue with comparison
+if [ -n "${REFERENCE_FILENAME}" ]; then
+ if [ ! -f "${OUTPUT_FILENAME}" ]; then
+ echo "Error: Test file '${OUTPUT_FILENAME}' not found."
+ exit 1
+ fi
+ if [ ! -f "${REFERENCE_FILENAME}" ]; then
+ echo "Error: Reference file '${REFERENCE_FILENAME}' not found."
+ exit 1
+ fi
+
+ # convert testfile and reference file to PNG format
+ # - use internal MSVG delegate in SVG conversions for reproducibility reasons (avoid inkscape or rsvg delegates)
+ [ "${OUTPUT_FILENAME##*.}" = "svg" ] && delegate1=MSVG:
+ [ "${REFERENCE_FILENAME##*.}" = "svg" ] && delegate2=MSVG:
+ if ! convert ${delegate1}${OUTPUT_FILENAME} ${OUTPUT_FILENAME}.png; then
+ echo "Warning: Failed to convert test file '${OUTPUT_FILENAME}' to PNG format. Skipping comparison test."
+ exit 42
+ fi
+ if ! convert ${delegate2}${REFERENCE_FILENAME} ${OUTPUT_FILENAME}_reference.png; then
+ echo "Warning: Failed to convert reference file '${REFERENCE_FILENAME}' to PNG format. Skipping comparison test."
+ exit 42
+ fi
+
+ # compare files
+ if ! compare -metric AE ${OUTPUT_FILENAME}.png ${OUTPUT_FILENAME}_reference.png ${OUTPUT_FILENAME}_compare.png; then
+ echo && echo "Error: Comparison failed."
+ exit 1
+ fi
+fi
+
+# if additional test file is specified, check existence and execute the command
+if [ -n "${TEST_SCRIPT}" ]; then
+ script=${TEST_SCRIPT%%;*}
+ arguments=${TEST_SCRIPT#*;}
+ IFS_OLD=$IFS IFS=';' arguments_array=($arguments) IFS=$IFS_OLD
+
+ if [ ! -f "${script}" ]; then
+ echo "Error: Additional test script file '${script}' not found."
+ exit 1
+ fi
+
+ if ! sh ${script} "${arguments_array[@]}"; then
+ echo "Error: Additional test script failed."
+ echo "Full call: sh ${script} $(printf "\"%s\" " "${arguments_array[@]}")"
+ exit 1
+ fi
+fi
+
+# cleanup
+for file in ${OUTPUT_FILENAME}{,.png,_reference.png,_compare.png} ${EXPECTED_FILES}; do
+ rm -f ${file}
+done
diff --git a/testfiles/cli_tests/compare.sh b/testfiles/cli_tests/compare.sh
new file mode 100644
index 0000000..e928da3
--- /dev/null
+++ b/testfiles/cli_tests/compare.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+file1=$1
+file2=$2
+
+test -f "${file1}" || { echo "compare.sh: First file '${file1}' not found."; exit 1; }
+test -f "${file2}" || { echo "compare.sh: Second file '${file2}' not found."; exit 1; }
+
+if ! cmp "${file1}" "${file2}"; then
+ echo "compare.sh: Files '${file1}' and '${file2}' are not identical'."
+ exit 1
+fi
diff --git a/testfiles/cli_tests/match_regex.sh b/testfiles/cli_tests/match_regex.sh
new file mode 100644
index 0000000..24a6347
--- /dev/null
+++ b/testfiles/cli_tests/match_regex.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+testfile=$1
+regex=$2
+
+test -f "${testfile}" || { echo "match_regex.sh: testfile '${testfile}' not found."; exit 1; }
+test -n "${regex}" || { echo "match_regex.sh: no regex to match spoecified."; exit 1; }
+
+if ! grep -E "${regex}" "${testfile}"; then
+ echo "match_regex.sh: regex '${regex}' does not match in testfile '${testfile}'."
+ exit 1
+fi
diff --git a/testfiles/cli_tests/match_regex_fail.sh b/testfiles/cli_tests/match_regex_fail.sh
new file mode 100644
index 0000000..b6abca8
--- /dev/null
+++ b/testfiles/cli_tests/match_regex_fail.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+testfile=$1
+regex=$2
+
+test -f "${testfile}" || { echo "match_regex.sh: testfile '${testfile}' not found."; exit 1; }
+test -n "${regex}" || { echo "match_regex.sh: no regex to match spoecified."; exit 1; }
+
+if grep -E "${regex}" "${testfile}"; then
+ echo "match_regex.sh: regex '${regex}' matches in testfile '${testfile}'."
+ exit 1
+fi
diff --git a/testfiles/cli_tests/testcases/areas.svg b/testfiles/cli_tests/testcases/areas.svg
new file mode 100644
index 0000000..b07a5b5
--- /dev/null
+++ b/testfiles/cli_tests/testcases/areas.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="120mm" height="105mm" version="1.1" viewBox="0 0 120 105" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns="http://www.w3.org/2000/svg">
+ <rect x="15" y="15" width="50" height="30" rx="5" fill="royalblue" />
+ <path id="MyStar" d="m100.96 68.703-20.799-1.1756-12.76 16.461-5.3089-20.138-19.604-7.0445 17.518-11.27 0.64404-20.815 16.136 13.172 20.002-5.8198-7.5458 19.411z" fill="red" stroke-width="1.5" stroke="purple" />
+ <rect x="29" y="21" width="53" height="53" fill="yellow" fill-opacity="0.7" />
+ <path d="M 56,69 A 18,18 0 0 1 38,87 18,18 0 0 1 20,69 18,18 0 0 1 38,51 18,18 0 0 1 56,69 Z" fill="green" stroke-width="1" stroke="black" />
+ <rect id="MyRect" x="24.5" y="18.5" width="70" height="60" fill="none" inkscape:export-ydpi="600" inkscape:export-xdpi="600" inkscape:export-filename="export-use-hints.png"
+ />
+</svg>
diff --git a/testfiles/cli_tests/testcases/empty.svg b/testfiles/cli_tests/testcases/empty.svg
new file mode 100644
index 0000000..b1010c3
--- /dev/null
+++ b/testfiles/cli_tests/testcases/empty.svg
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="100px" height="100px">
+</svg>
diff --git a/testfiles/cli_tests/testcases/export-area-drawing_expected.emf b/testfiles/cli_tests/testcases/export-area-drawing_expected.emf
new file mode 100644
index 0000000..5f64188
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-area-drawing_expected.emf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-area-drawing_expected.eps b/testfiles/cli_tests/testcases/export-area-drawing_expected.eps
new file mode 100644
index 0000000..f225577
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-area-drawing_expected.eps
@@ -0,0 +1,443 @@
+%!PS-Adobe-3.0 EPSF-3.0
+%%Creator: cairo 1.16.0 (https://cairographics.org)
+%%CreationDate: Thu Feb 27 23:52:53 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 3
+%%BoundingBox: 0 0 248 206
+%%EndComments
+%%BeginProlog
+50 dict begin
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageBoundingBox: 0 0 248 206
+%%EndPageSetup
+q 0 0 248 206 rectclip
+1 0 0 -1 0 206 cm q
+0.254902 0.411765 0.882353 rg
+14.172 0 m 127.559 0 l 135.41 0 141.73 6.32 141.73 14.172 c 141.73 70.867
+ l 141.73 78.719 135.41 85.039 127.559 85.039 c 14.172 85.039 l 6.32 85.039
+ 0 78.719 0 70.867 c 0 14.172 l 0 6.32 6.32 0 14.172 0 c h
+14.172 0 m f
+1 0 0 rg
+243.668 152.23 m 184.707 148.898 l 148.539 195.559 l 133.488 138.473 l
+77.918 118.504 l 127.578 86.559 l 129.402 27.555 l 175.141 64.895 l 231.84
+ 48.395 l 210.449 103.418 l h
+243.668 152.23 m f
+0.501961 0 0.501961 rg
+4.251969 w
+0 J
+0 j
+[] 0.0 d
+4 M q 1 0 0 1 0 0 cm
+243.668 152.23 m 184.707 148.898 l 148.539 195.559 l 133.488 138.473 l
+77.918 118.504 l 127.578 86.559 l 129.402 27.555 l 175.141 64.895 l 231.84
+ 48.395 l 210.449 103.418 l h
+243.668 152.23 m S Q
+0 0.501961 0 rg
+116.219 153.07 m 116.219 181.25 93.375 204.094 65.195 204.094 c 37.016
+204.094 14.172 181.25 14.172 153.07 c 14.172 124.891 37.016 102.047 65.195
+ 102.047 c 93.375 102.047 116.219 124.891 116.219 153.07 c h
+116.219 153.07 m f
+0 g
+2.834646 w
+q 1 0 0 1 0 0 cm
+116.219 153.07 m 116.219 181.25 93.375 204.094 65.195 204.094 c 37.016
+204.094 14.172 181.25 14.172 153.07 c 14.172 124.891 37.016 102.047 65.195
+ 102.047 c 93.375 102.047 116.219 124.891 116.219 153.07 c h
+116.219 153.07 m S Q
+Q q
+39 17 151 151 re W n
+q
+39 17 151 151 re W n
+% Fallback Image: x=39 y=17 w=151 h=151 res=300ppi size=1190700
+[ 151.2 0 0 -151.2 39 168.2 ] concat
+/cairo_ascii85_file currentfile /ASCII85Decode filter def
+/DeviceRGB setcolorspace
+<<
+ /ImageType 1
+ /Width 630
+ /Height 630
+ /Interpolate false
+ /BitsPerComponent 8
+ /Decode [ 0 1 0 1 0 1 ]
+ /DataSource cairo_ascii85_file /FlateDecode filter
+ /ImageMatrix [ 630 0 0 -630 0 630 ]
+>>
+cairo_image
+ Gb"0WGFVoNIH^]DMs@>:hKuG,0.X(r,f,<D":W*XK,nD0\,\G>]L#qUH]rg8Q\:I]joW(",
+ UT5GOfW;#Y*)i!>1t<?OgJ;oZn#j7[<;+'RY=2/m*%TT3Tkb7bia8bs1$/s0@o^<pTmA2k'
+ OD+/BWFIk]aa_D"kaT!0Cru<WE'#>E8h,\<brKfOp8ZGO/`<82]"7f&+'H&b&%#Ka!R\-nI
+ JJ@0*/\$4G0V=9'e0_SS)C(-kKeYj`'T"d%.\/3H:.!MhOZ$dVXO!%EoE_D;lA5Y@fUn-k!
+ SOO>O.4@<NXUbiDdcVR^D.?23]1DCTi8u&W[%#"kHdO6Y/6X^"k1^XW::fm]HNP`Og'G`r+
+ UTj4rKa!Rd-nIJJ@0*/\$4G0V=9'e0_SS)C(-kKeYj`'T"d%.\/3H:.!MhOZ$dVXO!%EoE_
+ D;lAJ2M\6XUtLM&k-6f)%$beR`"SVj:M>G&jg$c)%$beRNlaq3MXV8gH@>\(UFekStW@gX?
+ $.>Co=LtPM<#'4hP%tkD9FtKkDeb!,IV_T%-_@a8<&7bW2mqr'uX$_SS)C(4\9_G0atVltr
+ 63+5F1fr249KKnX.f/-'IGmJ?4e7NHbiNqa.9"ptaa:fm_K*.NCV]nLU!fK!g;d=C*N6t,$
+ l$4FY<j0)7nDdaZa[SG\'BUSWkGV9C3cVR^Dei;9]%k=[@o]?JWEu'W4OE[D&n-k!S@*Qe6
+ ;qi^:MkI2JTWmnoK`tl/amE#9(=mJQf8MkDq,is"_D;lAJ2O75`DtRS8r>%u=\1nfi>Dt1^
+ m\2+-c]BBi2i`IYj`'T"^oUP>:.?sfTqd:L\ArYrL#)c4@<NX=EF>H":H/kFSG,bE^)@6Kk
+ Deb!,G>JSmYCI?`g+`OtH5\;cn:!_?-t(bbjoBKtY0_@`tR;K*!PP-nIJJ@0'J?SC\OS2iU
+ &+DkYKA1PLNq-nG?[p)IAsZEPp.?:dFki>Dt1^lH)uZE4i7N5$kl-nIJJ@0,#cp[E3-Klrp
+ @k0lp0$4G0V=9*O241*4@@F%>C;XE;Uf]sWK_SS)C(4[Dc#;$bc[S>cm^$"cT$470e:fm^@
+ j;_9:^T^SUG0f_s,mB!VB.,;248/,Mi_a,)-['aBP(LQf1DCUT*W'mnHWSiU^KO46eG>5#@
+ Gr(2$c%c)q\FH\fJC!<;G9'SfCDN@$4G0V=9*M(mIRon/WRJ$a5W.S@Gr(2$c%dTHPZBT=N
+ Bs`Xf\ClYj`'T"^oBEpZD?R=N@CXPP6*'n>$$k$dVXO!-T?.GFkF\Fe%<)@3l)2KkDeb!':
+ \(qiK-X]ku5CVsJ;MC^LQr:fm^@*.DP'5[gGCXJDMnn-k!S@)nD=k"$6<EH:qUZ]u4=(7q5
+ (!:2Zp>$/iB>:/JP=H_m/9juuu$4FY0UMnGD/aSnh8rA6-Y,%_pn-k!S@)oP.ZOE:I;%Knf
+ fctF]NW7%rYj`'T"^oBU2eEuXYYf%>f3S"?(qYnn^2nUj.k)kt4@<NXfPu;j%"f6Pl&8hMe
+ `dQtHDKB<`jq7a_SS)C(4[FG?_;5__2(j-$L;Y/>G?o*NU5oS'DCV-=@keei>Dt1^lB'iV\
+ qnleuoJIE,&U[qq6tqR8)]P^[k8@5;n4LB.,;24&5.0RV;\@p"7j.[]7d3Sc?CZc5t_'F3h
+ ,gIk46$i>Dt1^lFW5V^*1p4?_i`_E)FSVgLY]DIairP(LQf1DCUT*N-=l>Z`AKpFWJA;.g[
+ /@<NlBhj"li(7q5(!:2\QF`O4u/'t%4<7pETZK^QF+M3b!ZYB?R4U^]UYph4d6X^#JEGE[m
+ 7!j,YO(b;m/[";DQ6WHN*I2Eql]4bKi>Dt1^lJ.rS![mRcKX:B$gXbB=k6YPSCfi=Dfd9/K
+ nX.f/-%b,Dk<&(k)/f8GREce8rBa[=&MjsG_X':YiGAQnVK0,=:^CtYhlLX\6Q=EH4D0&;-
+ 3gA(^/,5B)I@"=\3U9pTY[jgeHh4jtDl=`CNT8(4ZR8Nik(7>H&[6:gq%A8TE_9m,fYo\eo
+ Tf=9'-'2(Iqr4ij;#/Lr7WCp0W_IV3t*h)aegN+>7`!EcT#8@Ch60'WILL%T+*7DY!U2_.s
+ %O(BRG$4FY$I>=+-'Wk('2_Jpia0O8<i1@Q+r3_]adZYQjJ\hJm@,IYh#!';kqme.l$SPOY
+ Pb4=e.j@0L&U"j(J-@q<Q^WS$pN9iq>LA]Mq6j@&PjFsgeA0,1E.c"/YTW,E&qc'AkWW#Sl
+ _rG&cfu*c,?qZnErD!JdSh(C7:?4!Y^c[^K7mOX]keG7dB(eiojGCoc.b"I\h%'3:a;ba/-
+ $(Ok:ACPf2^YU@UZ$68V,fmYQ-46Y]$4$]IJ%7gP*?.]'a<_-,i/N;BSgN(4ZPnpF\/@<gE
+ +@7:C+'T+k+p8(il<d9G'<C=bueD1i1K"^o0_4nB,OF\R)OW3EbD=\4ajI:DToLSa/l_LfW
+ +Yi*lTo*g4n?$GtEYd_8L8Q!]($H.S6!jLV0]-`66eleR[[RKg[!B)1g-p'JVPn]%._?-t"
+ n2h-uFK.fSp7\^NRbK>A/3bSVFRRH9#4?n+)^m6;$]_k_f-!u!\s#@4FYr<*p/b/)5.UYR.
+ k0s1'Gc3&8)B\E:oKkpUMkk"'jXCN?4X=\ft8mMDHpF!4in:0'Gc3&8)KhROJb.q_9^!,W6
+ i6F7b1FcC7b&O7fZCg!#?R14j+sA%D'7QKtS4,8Wh%c$@%TJ!jLV8)`%r'e>P/l$@=;7P+k
+ d6Zl@\>Dbb/J@3l(\;-3f6*8<?g+A#9g6`R@#kJ+]4&%*A0r,!*_.p,Ao().O7"ijuBDU>.
+ jSM"$s>LA]MA_'p\C`6Z+G-U[U7hAL!!#?R33R&[9:>fXjgEO4l_XbaQXJ>iM"^o<+*@?IT
+ DelCWh9#LcMW/iX2qG*r*qBk1@0'JLEr4?S"-f_(LkkXu%D(@UlROE-:;0l=;L6VU$AAXk_
+ 9$Om'!749bpZoK]<IG"<`)s!(7a)>3RRI5kY^>2]rrrtQef`SM6<Y16oIRHI/&Z;!jLV4Q:
+ VD_%B:Hu*n(@NY+unE2mVUfAi2NQOs6`!=9',Ls4YCZMfVp[&2.`:=@iPF-nG?+:2c_cBPr
+ _"E:JmqX\!/ikTP*oS%ifLc/f\gP99dsYQ-46]d_pW(@,Dg%?2g2dk9m6FsEKgC&HNZ7FK'
+ q5+o+[/3'tMI%m<S/e>o')CLSH;FHtapF3XM=9*7+3OZdI0r93#GWOmJd7T:n\olF9J9NCm
+ !4VYA2(@m;4\35T$#.nW7=(?Wa;4-0<*-J"3X"pXKa$DchBU\XEOi"QMFTI"&J%o?jDH#g]
+ l]Za$fD0q$c&mXLR/sEV+_C20;]Uf;BS.;(4]Wd%P9JMasGk>]TpL:FdD]c'W(\=ZB?EB:f
+ m^8jW%N?En.&aMMKUh031sbm[@C1A$pCp?jhYli)\-S,mH6;XS[fTdNpPI;WM\c^e0Jp6"'
+ f([=A3cK9T@!hVsB60;?pc?PVosQB`0+K#.S.COaVRK8s3a]u9e]M\dlm/f^>9gr.7=TgiG
+ k\922O8RX,l:[p2DjN1#OG?@EKC(qG6>6jmT1rtGnh'\d_]0'7gEO0\^F=CUP_?+]Bi1:)V
+ mC9%*M)WhnLebR\#M>#PmcO&k8sW)i!X:&(!4NHO2r>_tf6(0b+F:tiPnYV.h27"9!st[*!
+ 8KX-mQ?,4gsB?F;s55T<`51KS^Gs&a0HjT@0*l\pmm0KH;UHD`&m1^HJSS%*aq"5XJ?+:$4
+ FYO,B(mcLdeH6/07*\21)Tk@`sGiZ>AcK+<D44J6PP#RU]0OS%>A>!I_dVMNgP<!P!:Ec.N
+ ;hW7)c_gZP3p\uYdp'F.c4&TAF"J6PNfcS>bo]l#JE5!M'Zlih^"h8gAF26EQqNH@\[+;>T
+ A>K:p51oNcoDC8aRT<XP'`DBY:DB7JC;HP;*ilUea=">_W=\02j'Gc4'a'YHId"Q)&0&AZ=
+ d3f+MN3&f@kZh.d":Ttfi7@GMV]->d\XF0bSY0_gQ#D!q7HrZsbmiJ*'H@f&J6OCE:,BtBG
+ iQ_"k]YNY;OlQ#V>`M+.0>+<!c[JdNi@ki+BOU=%pTZ&lh'dpYhdC_D4,A'ic=:n?mC@/i8
+ 2TC;\H2t^)eM*\)=43^$S;?;/p3!FC.:A!P!8e>Jc(Gd%eRl(+@dWf0!)3,T@'u^o^O>-RU
+ tQ$h['\W!,ndmSISD-,mDRV`*,/Pn$O2U9FXQH@O,V6mr";4[Qep!S!o$MY0fXOj2cAZ*rY
+ #l.2XW$4FYOe#t'&fQ2AKGg:miU=77I=@l@iZpZr6.>YhBE9m/Y$&Qt*,!AW4AO.);8R_F)
+ =]SEs=9,%9B%-NCVgYfk.UKB1X3mL0ok:t"/_MqTWICC[G>\[""lP]_W%Z\R`=j#7?EBqNg
+ c>1B'i,10YQ.!tcd<I3F8ifn,i5_$Lp!NjA2gfqUnFpl6=K>FCkEm\5n=Zj=,:,/^$J5>;1
+ <7d,dK_I6-5,'!cV`>iT1:(rLt>USulsgY,&<"Uk&%sZ^Mh98O6bc"Ot.Qb7&!0RI2`bXO)
+ g,U_mf)ZKL)7g/,+F\jXYt_?+[&kFbCTbB71^@"hIAc7%(-lp`m_gVfQ(/#6[>PUJ'_=)nd
+ %243ZqFW^-WKiCI<?./]<7Q,J>poF4KUjJ>R-nG@"p?u?t0[_InDt-e\RR\kW,0ep?6t$*!
+ `VuCH#/g@rmNipRYcH<FXKSEJIWp*:*@iQe/:]uFY]#Z?,ep@+MAI`K2!W:/4j7eS$[,4DX
+ '-h*VW:!N!P!/Xmm;X]?E&:-k:&E9Z'$;ToD%XJbG=`o"=Pf$<t=Y$?nfL!FEd75,nj<W";
+ 6Cli6%bfjr7"_laj-AQ'+hKcEb0IU3,9d<`2ar!)a<?43q;Vhp>ZIPE^I-=@mN+N@FNZ<_K
+ >?UaRbo<t;q@OMT^q0"AiHPa&g_8WgK)!\5]J/-,\jIV5h"d&tce`FG.6,!,+smUJ2]XJDM
+ n!;q'%XZ6S!]2j+g02Zc]@O9!I6aN8OpfNj!!q=</UVbUH%cRpiHHr]K"kHaQ]Yd>K$4FWs
+ 8)KhR0W=;[Hgq*4!fpA[goOa0*Nr;4YQ.og[X4=VP]"k8`9WU2,!,+c*agq4\XUWH.p4@XY
+ Mc1[!*hPX`_;!sm9EkPV^J<cZBF4c]NZ!<M?OlO"lRPn>r)nDF-Fl\O%u_F@O4IbhAK#sHn
+ 7p!!I3W!ldH:iF$up_(FKC:lh4ua#eQ3NgbkLb'GM=5(Ja=eAh]Nh;-5!5XJFK(Tt2dZ3bJ
+ I]@0*lY@`Qtd#BHsg.it//`_$RF$QoRIDP**]-'')lSJ\<k\uYLh'Gh;)@3l)2"^oNqD$_"
+ :?]Puk$QM*J`r\EHDIe[^Rat<I7QPAe6"'f(Ebaq$7#/!&e]$R#(1+)<aPT1dp?KQDFUJ6X
+ $c&$+>:(XWJ^<TniakD+Y?"O8Z]]pW!c[9"f-Vh*Ia@9;i`X*'.WnP!O`cm/SX\=`.4?_I=
+ &*\*OQbUSd_j"UJpZ&3XF/bQ.8'?[Ci2esH_2];!*;p6)P?6C/_3;.CP)Fud-[p<Uc>u%9)
+ \G4>Y.4i!OuSmE_H$9nW_rtRWWqU*c*d@[oZI48thLa9b$EkT3nH[ql^\LJ?_7-=.&p1TcP
+ cA&[][Z8d\)-R0DU;o2J*%R``Ld-$s7bDSH&c4+Gr^b7k_BZ+WV\]q>U%(Bh!Kf3RuaYGA[
+ IE-@DHUi?<QC+c\<5;B?LkM8)I;cj$;logMK0P3!'!TNo?4R.$D6JF">cFl6GZ$*6o)-q88
+ =0%4ES3J\XDp&7OoV8Vjgt6ga\\X4Ve,@8?JN,OON++s"DIi@J9rtH&iC]!K`nDpRQ_0^jm
+ ]F!BEDos>DX9Hlb[A4mdf%,%JbVZU>+#UG.@b0=q.RdAZ#8s:P,3#&Y#8@%f&[lVC+rr:F^
+ OZR+Al.M]CdNT"El([,dO,HC=Ul47E+_a4T:_b!cTgU^_2:A;ka)EpAtP6Ff0UMg2:GlKK0
+ j*i^n"7gp$dtL#l)($aqFq$^AL0Y\X:<`<MOhMXDc3$^gHn,t6eYP@[fn3</Bkj_Do/).tk
+ \r9'fY!8Q5!ZcHPF>h)!V9*&PI3rQ]4A2gf>>:/.8\D5]ZeR*gLVJp1b%u6m`!.8`nha2t;
+ ;lP,"B*'\kdClV/WVk8Y87[@N@K"`cCiX-q/;<R-Ka$DS_7^3b-;Top/fk;.kK->R"1'4HQ
+ 6R>_hJi@4p!gWq]AraR%>=rs":+\F2jc_ZF>r_N^l%>JMNN)"FRIDQcBY:(YVt%61]*IIK`
+ r"jLYeM50R91(+7V2'Q6r/"ik\4okGluVXHAKb<N@Up/;S?r"GI!j=GP`j.;ZE;'(5lQMWh
+ E?\t@e@j_bud21JK9Q`KfFDHp:mnaN']XYQ6D'Gc4ALYmBu@.Mh2cJh]OFWRZ\Lbq?cA(fF
+ Og#OLZOCAP)+Q/9m4kF*4eETfg3Iho.>"R+@\*c2a/N%CD0$CuB'OliOV%==V5[YUXCQ?6l
+ \Z%ZJ[f@I`d-]-CiHA."Y]E_8V<ls'X7$5aEg=KCYjU_EZOJ6WSaIUA!dMbDPn[[!ceUfS@
+ 0%3,\pKaZjD!!.`@Cql<Tr<!NUpMK*om0A;5Ll`lHEJg'nlg!m+<75%AqM>i?LMciI`)\Mc
+ 1VclM9A>*a,ZCf'(BkSbAO;!=B4MKk%`,(lY4:SKU2pi=@SB;?TCj*`oFu?I?IO*ct`iKa$
+ DOn^gL,T%U3o`@!lt'4"0a6p-:6jQk20omlA>C[@BBKa$DOnX\,;B%1bt9c?o[o5FQ4mNWc
+ C2i@73$JkT1Q`KjL@0*lW@Y[N``<SRpGd3FK;pOLi"[2?V[k/Kq,?%\E/tC^gnFkHr4[shK
+ 6])[1.BGV37lXT*GMfi>JAMA,iT.Er#1j%<Euoq'lbOc.9K]*Er8KZD<"Cn%>eMct$c%Hp>
+ :1dZapRbhnKAEGcF)U74]&ek/XNO4q3I!l'Gc3,Vm[k`)n&S('VFk#Jh+F!86'a(fFg?cq/
+ <J2HLhGs"lR8pY>$j`C(R$)#`*5djkEB7k?#8Nh*8&\g"8W"!P!)pF2EV_d_j$/4+bA\OE>
+ s9nh.!jhm0M@_?+]8oVR9`G[7D+M,/6sMY35K/`3kEVRm0'2PI>T>W@rBUVr[*.R)8m4;Ls
+ q[!^%D5YiK!YQ.Aa3OZe4%$p1pntQ_HFBs)`F<sAI*b54S2RZFa!2fH02(@m;4\1%o+fK;D
+ :'GrCbZ@[#m:Y,EoBKO<[SU'+VY2(Q2CRo\4]$V"+i#/+P+q&B2O1jdUM_IY6cOhs!\h%Vn
+ 8a/uQK\G@UCY3'>a\pJ"'R?ZrVi+B/JJtNWI9Pf)&2e-mO(6q&-b(8(XJF7G`'?>0U6]uWB
+ jt9F^Osl#P`hd!\fm\@#70X0C(gmUCZhpK`tk!s8KtAKrr_8j[UEN!t#FOeG^GO_HtX'j@:
+ <M!t#FOe:E%+$jp4<HchC5^]tPhJ>n0J(E+!r8p?UDI=MhV/-&TRpFJ!Hlo!TnIe3Y)TWeD
+ i*I;Y<6"-t_Y4A=un,\4H@-Z+No*btQ6[+Y7I=MhV/-&V(hg*;9YVeI7Ie3Y)TWeDi*I3"7
+ 6"+POBDKp[pjg3_0QlHY3<k2NWB87`5/7C;(4`BZDpC6hCn9u:5Pb@%:fm^pNp`KM:tT_O/
+ HpqPK`tlLf^>.3Kl%+=j0JcQ-nG?sa**jb[S^\[O$:>a'Gc4uj0-c_-u?4bQQLX+%k&t/;o
+ `7;DG\uA:6QcJJ.F%Z!)k7g:,=<"r7hDP6"'e]3k8a8U3Y#6A*)kpi"$/&^spKn;\CZu^)e
+ L`pAf\>=9,3nD+hCC"a[QRg]RI?@0)`V[<6MaKe:e!jl[]C%gi][eGEGs-<f'.HA)4O"8kG
+ n!LPqH>/GtVVh`IA4hr-R(4`C5RjTIbfHNIPIdR79TWeDiS_5h`dD?[bU%378n-=XN?n0R:
+ 3_K'Qq7-b*K>I[DZKLEYK>O53KB22:i"$/&^rY@Wf`m/DTEiWc4hr-R(4_O\LR/qop7`TN5
+ PG/-:fm]ep)R=K:tRPZ?[hDDrh-IqN8ts8.;ZQ_.mNb.Io'5I7WJti2(7g&h+)q^!WJar!0
+ $:%^VleL'tUmPg]RI?@0%4uT<m1CYZGp"q7-b*K>I[DEa[cKK>LsF4ltiJi"$/&^rV)?$Om
+ GIWI)_44hr-R(4_O"HPRG4[Rtb1T6l)Q-nG?CLYeM=:tY*FX8L`Tr'=`l(ch6V4G1N-cs>_
+ q*oIP9$c'iWG98RgCphQOT6l)Q-nG?CLYnM8:tQ]B=aoc>rh-IqN#[+YN^7)hFXR!q#P`qg
+ !\eZEW3CEUKDaL,r'=`l(chHTCkKUUlh9mj&+BgX"^pEDmTBaJD:%\2k8rOl$4FYjYr?85'
+ XgkOb0N5d*u#K@OlO&c(_jDMB?8C^^`O7+J<Alq:H'\<7&S5S4hr-R(4_M\4/Wr1p6JI;T6
+ l)Q-nG?C-[&0X1p=H9XSgEIHp_<C(cf-T8tq$adpqHo*oItE$c'j*D$_":?]UKpH@To^/-$
+ om[SBT)YQ]?;r6POn6X^!43ONErU4kb28&u3(^c)rCJ<BS5S![ktcfsEZ6P&YqK`tlFo]?I
+ :(:HaEP95+$D^[.:,H!J[:UI@FHDpbg"+3IE!>nBbYDs5Y7"4>#^7hb2'"@&f$A63]lG<$Y
+ &+C$^"d$7-7YSOK#K[K>^c)rC5iVJYaYkR676NnbkQ/DkYQ,<Fp[E3U_NsHcnj<rW%>=tI\
+ :'-u)&2e-mO(6k!8gUT!/\lZ\C@1UjgqYnci\Y_@0%W+kJ.^__H,kanj<rW%>=tI\Gc:@)\
+ i"'mNk*i!8gUT!/]I&ZcJfD=*7=7Ta1@I_?*9`JUIn8$<sKVa:CGL2\[#miXYB^?r5K*h-G
+ Kn!PY83!"sKpc[:A^?IR`kLE>@m$4@trLYag9luh,aIcpgHl)t$^hl\s<mQ^:N8ll*[4Rru
+ O'Xh1XaU^PM2r3#d?_&^VDJ]N,G[/KP\X0fqGEs=sJ8ZheJ-8l=AJa^#A5Lo^h+<(Z!PY83
+ !92R/\*h+af`l;1EDq(G+*rHU-nKoD>UrsG^MT]b?6+1"KDa'uHp_<C`6qJgVI\!]2f3EZT
+ \IX#4hruj(-if_[r6$(HYnB3]K-sRVYndCHp_<C4@%`1<CLh%ZdRD?9fbXjkQ/DkYQ4+=gp
+ FfTb_"Wpj"9EDdB(gYLE>@m$4Ge$eR!Orll/mi[^AOlVo]H4^c)rC^ooUSXtN!o06`WA9@m
+ 58'+aRGK`r$1j5TY-mB40^e$_A%7Xj=tI\O&3b%n"]GOb/Bn(R%dVh`m/D??el_?-^]r>pg
+ (jm'*PFK\nr!fIa\@=_BhCQCk&MY5>B[S.fT;-4qE75e0^0$\miX#laiq0G`"mOg``!'q;7
+ 828]i8DL7F%q^5'5[f5n&S^+[`?'jt($JLRFBA:["p=s\"]BtsHR)d#e-"?]#lFm\"d5G.H
+ R'SbH=W$'/3hL35bYDI;S,f7]Q!%En(O52a-Gt>X@!-YK>I[]MEMT&0FA15UDk#8ZUY'.7U
+ ^l/dSR9IFRMn!04!6U!Q9]/!_FWJ7j;lp"Rk'e*XNqi'Ga4Hm#_8LLVa*9kK=p'dF[%r!@3
+ Q>!b'(i#p?`+/.bSCOT9un_?(l[I4<p;4VI#M1C=C@$4DNE5C4YnZ:=t8O2:tWiPQ^n$Jh0
+ dLk$K[('k$98-%U9@=_gE6oJ!Z/b*qD6lp\#!&%/N+Md9GY@`gu&69J\#c>\_X8.\+,6<H7
+ Ka!'`4VC"8TWg\CYX\ac*Y]@q%/*3l8VP"_!ROrXQ9>jGZD"LQ2"jH?abdk9EnDT;,Voc88
+ FU8Q7&B<#5@f;/7:?5SYt"jd#o"0EP:q8.Y3BipZNU>_"oNE4lH41[Vg<[RarUj35U\2A^#
+ l3bjep`U!MgP>$[@Un&jV>KY^gAT)qL>rSa5NI$aW>)#VHGlG_8-l5cUT`J8G`QCJ+<GnFY
+ .o4VC!c:ftLSp.,2Y%13JF;La+;nN52_]l[hH":umo(3%Us4VISWOT7_'_?00#YXd])*loB
+ /+FGRg@#@)[!89bJ!@(q>O`6QLrlsel!5aZ2!XW=s"G"!Y=<J4BOT5qG$4FeMn`JQsqe(2#
+ l,k%R+K81>+:db#!0AlA$abaR&c`>;=PuKt,6>Rh$4GW"HR&U$7:Cc)`Z>EdG#6gn)B(M=]
+ XXu7@)[s9b75Loin1YS_2]Q-[r.c2lDq635PicdJ,"e5NdpuIBeB@S?i4Ano,hr<fsAcqcQ
+ >9Z`;)V^GOFE/2qEldj.L6HjXU[]:@e&*Ps#aFW^uNo2E,P&.9ZK?gC&N-e(NXUhUFdioC:
+ -h1\.fkL9+b>Dr/,JUITOreh'g'4WQSZ%iWr;lI;u[QhULXh]MXHcOPHp`NfI?Io$'1J!mQ
+ eo0Z9kCMMFWs08@Hp.,1f/7f08p$*IYEmhM6r78>(54QRqN>qc@T%uO[/&Q6iXdktGgGqLQ
+ UQ5n4qWX-f:[@r&c;2aYYGZd#,B6l\@?1PQ>*?7sV55]OCmk%bDbp;p7gDhZeu`H&FQmVQ4
+ _\VFaH2Etf(=CY\^CHGs33ZHA8-JjHLX5_,etH-!+&6S_E+eZEFRU^IPg)=(IgJ&Vu?L:OR
+ Aea-SmPqe>U[<5OZcKO)qIFSNhZr7/l(4\?JI)J;6Hqf^n9-3a$b)l&gl>okN)29fdKL38M
+ /WY$BZ#$1<foo5ELH$F_SreS@e6S)NV..siOl<#;.C2E#I*^OO@L^,,RP*uZt\E(B/]"oWQ
+ 0R-sn?nbi!RJ,.9uQS5NXs48&0m#_;+O'id4WG08A4c>hgZL$r7_7V9I/8)hQ/rr4\oZ'1f
+ =](=0[B7.=Yh+4I0'WK(OT9?)+:DC[^A?A]B0Yq)YQ4i(iY,<V:O7?@m+L3%G[^6)*V13h2
+ i.LGp'l?o!;J-"Klo"L%ie^BX`$p=[&B<i!U1/l`9H*irM,D`r.fchG5hD3nH+#o4V@_t8/
+ J_GHJMAG1<Z*W4aOK!%Sl:1k?"*4F*`5o)roe)J%9e5mIFhL`!H'-!4WQ5_VVr9rkJJ[mUM
+ :el-dQAdNjjr1MKb=QX:eXqK+Kgp;VgJ"ku2QFCgL9J@>5W==:17s7r/qIs=dL_O@/t=/Ga
+ B2+":!8ki?$G18lO/WO#.eB4^#7'6F\s0s?Ns1@dPs7tp,s4aeN%9BL,;BbjPH.K(bhu<>f
+ _SR&ITet+m];B]-9M[<IfcpM?#%%_ld'1?s@f=s"#$YI+lh4jXX68P^O](M0H:4`n.J5&\4
+ epM.P^:;)?W65mV4LTMO-TRZBUuK#Ua\/nWF!uX#O+AC:liBfX%=T`iV9K4'%p*,G-Udg,^
+ RPHGl\&<a+_d_c$oZfdIKqrS7W4d?AC.hQLp64Fo/2*%<^]BV7rVP-^FO9[(J2i(XZBOs%!
+ bc)&[/SfD#0qcfR4%08X?660bLP(V_obdn<d98-"I42heB7>Q<uhZWF<rP9tENE$qK:.5`a
+ sW@*pV8NN;@]V*d7dD(U2*W;lL\i"<>J.=6F0KLj?>J-lK-*m&5mR",O86jbPjkmBE_SUJ)
+ :S(hbbVLJjpMTHoh!Z2qelrshHe0(0;s9:G8$Bl2+SqMp.&Hk?,BnT\>-XgJ:OY(Bq/&_dY
+ ;`L_(;2TMdnA:#H6@P[!$oQ^@;k"-s+;Ce?b!><Ij7-UWSu77HpjssV.1;E:2iE2r>fC*;V
+ _u]PB;#=hkn-WKi(bB*rE?8,"qp;$6XFq(\[F]e_g/A\:4C(q4ol@W<l2!<pEW2RtY1bK7-
+ %bhiHXf4M\McN8r78h+:C<qnNI5n(tancb.h04C#C3ZDX%lm#_9Uc:)15o2e'QoZ5=K4hgM
+ F?)^-;CXpJ#[fF*n`&ZHEO%)@ImFoQ%[%0!oB(mDGBSP.9(n2GpYt"j\cl!Sl^k!em4Z4Q1
+ _ok]JXQZ!a5+")IWqtc;k^?egUS$:eq4#cf&Y6]0Xbi=.CV7`(F]in(%e`En#eWP4gXNUhe
+ Q6D0S(GQqd6(C-LSLIhEk1JCkG*5m9on.TmEOnCUYQ;2N%Q]ip-m]4\om31cgp_V[7Cg<Ns
+ #GEi[_+ZefXm1`otg@]pco-PZeb!kjT>^mVC3'#Wo$Yk+TESgS[:+`3A7\LL'&MWb@70HR-
+ U;dCjF;kmG4l=G:U0^4"fuIXDr-L2UFqQ7MW@dYU8JQ_HEN:\SD'@nUIZY%R^e,LK9cr<OO
+ 64LhVXaN"8VY;oOYG]7`JJEU>D5:mli4NNH.?&hkM0=C#n.H?=n*-LMoWd++Rfm#B(Ok4q*
+ WcWI\G9VRuo74^GfJ.mLm-^1[CWGZ+XIkX##NGKCkg&6tb3mBob'<dIO_&)Y3a6fFe[`/dH
+ @"hu*XMff\u2de[`u..0@R/h?,80dpB&@)hi$W#rh-](\'11G*`l@Lp-,*CGeZbC5``LM]?
+ %_f!@4p3$@DXAo(D>Gfpn`f(TgJ*f)`=m'`DA*-tVr@lIG;ahjJlTpLe9FYX\a[;MZ*sU8$
+ XCs7B6XRJ+AP3[4DpOQ>''O*a$de9m`E6qs6+Fm%'G54"CkX%1\)cLUEc!3)X'qX/RBgT+C
+ /4+f:OH@*jLddnR[pi:J/V7hrU="ijIiu^d;e5l96!:ZJ[n^p&%W46K9B0Q7fEqDFW6^3k"
+ R@3d=&+hjNmpk_H27c;PKDaI55Y?$'fIkS?['JEtO"3(_=ZJXVB"[mdI8Bgi6<i+f!C2U5_
+ Y.'r&Y<uphtC%A6+o1B*`!j+n`SdMmO#.(OT6NGXK[,JhnENA0HT9_dG)akQ-5AREVhDKO8
+ Lin^GKP`"9<_((:Ijt"i8fe#<5'/)*=B03hY:>rd797H"E$>J3\q*Yha"DN;RC3f<Ms5VPU
+ 9Lc0i%/a.qC,.siNA"@tY/mAAB:^>''>Sr,`$fA5]?,NINc)"=32Z:=s]Yd@]g:=4j>d8no
+ jKMo^QbHl5<Qa_H$lHZi0eK\h],68p#*`Ue!khk_i-U!QRPWf`Y5i9QFR[*m(cOFEG"FrbL
+ Ddc/A9GeiJ>4eQpGC(,Jaj-p].=3<?"P$e3m1ipo/j=`H,/+c)g"=8-lk03G49%\E9`tIje
+ QUW!dFCp<j9koMiT_#^R2P;OO/Sp_9#$Vki%?U8SYjoCdUO&lm,dF@PKq>G/=ETbrNl..#l
+ sDS'_\<MdhQQd>kF@Ge'gHQrV/D;2\Z8!l7P5!J\*nVo(>i!kHSi'qd7ci.fdp1(4l+P;N&
+ fO5g:4LcLNp6!5q`:"P.4M)Q:kTT5JE@T1cr0!2MR]M]hnc;V?fN_TU/"s5T:mo&%l60`WY
+ \_BScHE%XbJg%h1aWp8K<hdldc'88k']Vs=BATl'&<[[lAqMb\%"]8BTa<)=LB=;#Woui\t
+ heN3i%>@6\4KHt6AT0EQeT[XII_,VO.23Pko5aK&!L_*g4T,<S.g#2JKgeO\<iOrui;E"*<
+ V)j2mlLNKJANMC^7s+&AT4N-<2DgDId7#*H5%-gp9IRH9t!7qm]DNIJ(!dL!6L5;+piW`W@
+ X=ul.S^GGt%Q=#U"t$=<r[JS#f_.T?6,<!-j78,&+]ce1?mK8P`:boOlTj6NB]7_BR+':\\
+ GQZo#KH$buiY(<Pb"8p+*iSeqAkqTT9ga\Z(bV5KPu?J>c-+q)15$B&Hf-'N\JSm8IfH/K\
+ C.jus3fN4=VV\2-7b>sV;OFC[@8-0J@amJCdbu'=hO"=jj0`]fR/5j;fV:&=0kHSk&\eorZ
+ 'G`.WObCKgo@/($+gM*,;-9WSO!OYI]4ZRoqAfnc]I5=/asWEgY7`nRpo*hS'L52d=JVB].
+ b-g2FM@^oddL*7@74OdJt>f=Z\ndYV5`oH.K*9N&3:L@WTbk.\uPOd'A<EW[.LsF5e'WmUo
+ _lkeP4#41,IFWY=lW=!JC`474NXFYq+MQ*Rqg<Q@@-]#R^ut'_bR9T?.T0V2G8Uo\1-H-r=
+ %(ZV#@<nXiB?<TUtg^N0DY+,'k<T1\HsZHK/GqO^EqGl\$fK`tY-4L'lAO0=^qo)]1W$4F6
+ ;Lf,*?rq"CTUu/oeJrIPJGPY,;%gY^!;ucDWl*i?s^nS'K>rJYV:PX+@Ig<ak:fuX^$W[+;
+ BHMB]?_uZ$!!7tn!r%"DWMqBHP<DVtr^-Ip$m943/9"rL3`i4/rq"niddL`I5so(3#C0XTh
+ C?g7QDXZA"mNKPJ3]g45T?:q.SIa+\o^n!r_E;1%3RVY/C:h$-Er>9ggF.$6:Cl+-nGqY,:
+ `]aa?gX;Ds@!RJ0:Mh5Wf\Trm(Ok>l&jX+:'AH&<F"F2-RtEg1@:Ss!@Wp"sArC!c`N]WGX
+ 321NB@nq+p7!2ZQtp(5UkG'=dAman=*ND?-sj/<GsQ.fT%_Q_`Ns!!H/$+AoIES>T.AFl>j
+ =mr8>*-nLIYQ]rDii]O8So&Ul7<Dcl6^b\T*;K8;sr/((#*.S;%$^c3=`HaMQ=3(;0qOJ,3
+ IhWYP2FGH/VN4[c5F6i*"7R1^"cRjeJVW]Aqi@T2li;E&@0&=d7&^S0cfrqcjo(tHq]13Z-
+ nO<)EY[b?5*c#&JQ+sE!LI+nC4qK9r:ACcDg;>sTs0&oT3&%FMW]*-;Wl_!!Mfu.$Wupb($
+ BjaZ1PXLJ3PG5@*<d0kP=q3dVjL55+o-1/^Giq?H$sipK#oF6i+J,^hXW"Y\:.ro/un9l1[
+ @AY>JE;U6h,`Wo?&+1Y%o^9>CE+G`'?>G!-`3%Z9/3-iN`Di$f!@=LV8KH+&/hH3+*9%YY(
+ fHV(3N.\8G1W+u6aoFM#\&IJnjiKGb:o63u&%flYOTs0&;VQ4r:YHFnba3o#/J/9Ubi/q[4
+ ?)@>g#GgM_!&%/N^cN(-GYYkEFm=>%UB#ge'Gg2`91nkTnXL+@aGf=>q1/O]+pn^&l9d%mP
+ Er(pp:l?Hl2J*%PPeM_?j25f]J`*qF-b.s3hs_U$MDK`\kdrr00^NY\3XO(YC0HNF;<-tO1
+ uUa'%n$hH<@$3fq^Fb#6b)0'Gg2SC_b9!1o'Z5Kl'haLKr<a,!'"Y@K;3+K`ukn>eJtLq:E
+ A$HJce)o(8<-H9en+_ECa]3)E_m+3RVGB@b!r%k;Cr!^3PgpZ%hn+Qt4)i/]NAeOd`$IdlM
+ f@RdfF=ZrKGpMS@Fj<k7aHVmn\2_.iiBBn-0VuJi]_R:rF6b+QO#TO0ZpuX)!F]S%&s7=Ck
+ KF6HX=-"r`K<OUDHr(1R!eggUZqA;Yk"iVh9uV+pf:t"a!B%Hpr.G)K)2/7loMqa!W-HV6^
+ ,+P=A6\MZ\ubeYZb->N6"'edX]=n]%kR23^9Dh<'6Qb<C=J[B1C$Gk'Gg2k91j>*0i(8.g)
+ L'9Pf:_Ph@inc]/R(/!q65E"oPi9`H*+#?r-*BD7q=^Jf`#($G]j[(@^4>ESXTaeL>qH^Q7
+ n5Z7YY?BHH@>G_O!9,2uhso\:hkH<Sr_5H&.="^i4%"[kJN<'(ido^A>rioahMM/3P@kQ=p
+ S@0%hI4`KS6IW9n1Kl,5C]H[2;+$QV,9MnnWb3,!];_cV*E?5!2Co`j,qQRU60L9+J6/ts-
+ k83$`Y^OBbQ3[99kmm8_$O@+I$OHT=(!l^.`ZF,nD8eY85eJKr+DZdjH+IUa+8J]>mrsWob
+ :A)[-nHM,%ob[Sn,I.hff,da=7ol^cj+e<$4FrORSm9<k#o:&f<&6rl`[KE5;fWPq]t^9hM
+ ^J1^tgCi+E!m1kF=T@gki*W'%p*.jl*M?F7Zh6&.e*?$MDK`\k`D=oDp?k+R[QT4QkC18%0
+ 2@<HHXc)]+g4c^AofPC-L$`UXA33XQ5t_/L.\o]ai=Y&=2DO_rKF^TOSM8b'FA`N+;l-$i+
+ (Gm3q1+iR8[+:'#cnXNZfSUKD4[]7*I]&LqcZb$9b:D=?36#**t5S1T(<$rFPCu![XmD?<3
+ X'/MP@UB&BM;hsW=3L.AmYO:$UTd!KbYp5l]67miY,mI9ZFB:ZC>McmQ@8;mS@mI*GY]$s/
+ !/g?/.`@gWd)f"IP<qa_Z&\hW5eM^8sZ'pR?bBu]QdQI<NLsr:DAs'Ka!&QNr;`=f(CgICo
+ bRFCF]c/(VM\#]W1^8Gm0??Ka!'<K8Os.[7dR!=R@H@^UbfXqX&X($=BDV*nG]"o0=BC#7:
+ hAa)akLX-i(Brb7hu_VPX;nqmK3lE]hVcghEr'SQ\lAX&'t<itY&X0?g('6X:>5aPQBeK_6
+ \?B%!EIV&ZOUTd"VHpGY5)&[.T0!2E#fbR<TlRL45/p]UH+4Q<$eUtE.7;,M"+AeYR$E@R,
+ ^]421*Yl+`A*r!Nq25T("n%#s$Jep8EWYd(YX$Qo;)(jiG&OPlgT"h17;s=3;lkbs:JN#R'
+ G^tChc2C,7Y[HqMA$QS=EAc,`PZb;?PEE.D6=r#A)@k.'G^tC0?<`%`BX1?/7/e^CTS->&R
+ 8X>Xg@)D-P-rF"TiA-MKqDbnKB*1D:Pr)6KE\]jgqYnn-!3pYWr3eSr.gip[?Ja]KnH]/aB
+ LBnp,N"3B^VrGQKTs=<N-c="6q`49lBL&^P6ngWM)sr**LD&n?RpKa!'BOG\=tq9AHh@D,H
+ OJ%`K=GI*&He6^a4jl\#<2@9fpdkV[0/7.+)]VfU[DHL7gDM^k*e<]@uo(*N.)heHIku0(o
+ aiI$>ENrC[C=cE#/jB$l</ZG^e0F)<-dXaY$O"r1Q0PG[Sl]9i/KW:6eR)ZhI`gC8$6J#`T
+ VJ_V?qjKQcf:&s(%5b/Cd$.C8p?UDTg0PF/.bp'HtR*]i/Zg91tK7N_X)h;(\Bug&IPmr_?
+ ,;]XGZ0T'W_jaV+[*:()mrG?]UKpTg0PF/.c=o<Qt%fs"Zr(OY/N@X>0U1i]>m]IYn1;6=G
+ Ef>HD;*c[9?7GTts">7#FPpQV;E^k5Hc+NQaH@\,#I\r-70]s=DS%16=>#Kon?i'AbB&7f.
+ .]fn/&9nhS2Z#.+%I[NK1oCU[k:6Qcq^jT'^+Q3*qQtQ$QLVskj;H*NTZcD%Tl;$o&'Y#e'
+ $O#6)(%ZhGX(0']l(6SVV[D6aIZOX*7:Catqk/glQ7_DShFI=_:X1aECp>$.5=gA&U9HnuI
+ 9slTSN:?3f<5udUeB7)Z4./1X2d*`FST&Hgl'#N_:M]+4T"h((/L`!OerS"&%nso=n8m5\/
+ 0i&Jo?g0H2crEVMHVJ4gRZ7Q0Z0TZP9ncKa!'HNJ`"QG[d%b+5NtWZbW6<kO]&U=UNrB_?.
+ RJBbg6`TANS[O7(P)H.\doN^fQg5>HdA:fnk8?AuRA+tG1=\TrgP,2J,m1jc%SFkp&HS-!o
+ .YWsW/<\[C!IcqlZj!`_Be"EpVH@YoQ"n"]cJP_0_AuD:"ClW`^h7HjpaIobkHBe=e"n"]c
+ JP]IO@\.LcA"7cch5e/rb3YnU#2N%'U&oP5YWsW7To+-pi"$$LpX+kMm?7qc>G\sT:K67I'
+ G^t.VcCqsDHrKP*3H_qBT>Ae0Bb^m',A"jK`sg%AYn+][SM3q>'9L!nNONc<OB];Yk`UPr'
+ U_V6t(YN(ntK2S=Isp:38>DXu6))[SD%.+0%RV$4DO+C_cR>gV:uIn"UVOY>7&)lkS>LI]*
+ =M-nGoFlVu+"c#8UYO/!GVN]goLfMXk#I]*=M-nGoFJ#$PF:ObDC[M(`?>Zbl?fK;;TU=82
+ d.aWda!b(Vs.csmEZB'5MX_=_AQb1B#8Tlu&Zi"&3>#8<D/7):1cficI`T(W)d9G'.$<s5$
+ aNmkZ"bd+H,YPOng9jRhC't"?s/=,=jr5#C8.UoVX$@%"$\V%j?e?e[ITS^m<n*A;0IUb?*
+ nRLBj[Ui<WENrbkg"=^@tS[VUbghUXA<8U&=K8%Si>P3-ltl/R[_mkDDq;=p4huZ5,]&_=*
+ ;gor7X';5`[t,7hk9_-$i+]*dgfq.+eDIFRMiXo:#)j?$EF%?#"M[HJP:bc7W$t+@I!GR-0
+ 5hS08qD>>!bA@8PTL\tAc!7d)g[F@p<;HFpAngJK$jrU-;?*uY.=Zr+e)YIp#,:Y9B$-$Q'
+ !E-&0>`5aaPV+G;\=/iY!g?L?'Ip%56!/T*6.d$QF-\dGDd?^8'=.2jl7\(tlZk4&cSQ;hI
+ >0e^Q"[_@t<6V[\9jdEG=qfSV[q10-A@n_NM9g$l7ChjP7:%Do&2ITm1!c@M4lje"chX)6#
+ *\GK*[!YU&ILI=(Q\,Z@=do^VqO<Z7O6=.#tJRc@Y;d'TLXj"D?O=!DP*iI:fm_4dS7,t\o
+ u=4c_<l$f2nFN[sHk:I"3H_,!,\(*oUC2.4c#o"'!KC=1;MKN#(MnT4fq6.Wl9l)3,iOW!@
+ X;<sf=gcU\fB>e%"14Gst\3&KAt\=M6(`+M%2bP;=clb$+t+FGN:R-3,,A`?k"kZ[LSKAi-
+ "a5#ZGG.=06\td7+&el*P*ck?*o>V^W`3X,Sj3=uDo?A/Kikla%e"ec,ogg1q8n!;?=fPr&
+ TDir@GS]Zrgbmbm^0T."`90.4VGn)r-a+4"#bSLSk5##NVi!F\Y-MPY%H^lO*[3eo&L`ug+
+ 5cW]@=don7)_:Gr8r7o-JEV&ic9\IQE/';IJOeD8@=f-,q(=+$pL\#T!*NQN-H$'C2-k;]?
+ 0,*Fu?k;pg?T'2!B,YH8L<Zd$B`nR-.+L)C=6.ld!1$eR6=$epYJL42aR0>*0loF'^:H&3c
+ /UR-/O_)^-7jlcN#J1Dk+#HC4JPLU`7gJBM!UD4TTUoIj)j/.aRdU10]ASerdq7-eS?Y:uH
+ *brS4>_F%.bmd;j6"ktC^hH\h+3c&\=n,0rdDlgZU\^GathRKf&WbBN)JjgDe(6kG]=*A]R
+ q#drqpWb==BDT0p;u_%#?cZ"O!:frfMKtBL&[AG^bo:[^RsYos6['+anrQbI41JNJiEWimQ
+ Y2f%DQDhG#(ZJs:;o=;rpV'#+F9_lcO>F4GtI`DTCWWACh(#oebhig?F1C^#aN`:R-0T%#)
+ LmX$Wlm7)GJ5YHA)'lLPShkfO\C8kKIu@U8>(ZW-hI]4<$3N'<e)GDH<nsS!/gm;gd<:B,/
+ "ebIeVYRbZ]^nhF,Y('p2g<6V[`<F>87H6TXLDsIOY;0bl['-I#=;iU5319Yu,9t4?6de/N
+ a_?-G%'c$<k?.qrcL>Fo)J!RpX]Vl'kc[>Mb;.mII/oe"(WJst1K`sg),o-$qTANR0Kc[3U
+ ]@^Lp(?eWT@it]+/sdF;19[%)5@j!n94<BJq3m1dk0E?;SjJLfhcO_R1[B11n_k!#Pj/0"o
+ 31e[Zq0cm$4EZ(YH!t&n`$AC>@L+=N_'FbZVpN/+9(c`2;QMg?LtX#CoCWLn)[/MJP^Vu`B
+ 4Yqgdf#0hRRlUNksd=Y6Tp1Is@tgGK=j=>b\*CL"2M?K`sg)9.K(,[k9\UXjc95a\mcMRA\
+ *p,eG>QXOGuYQ`.u2@/K%X@=_7#7`@L)pNt#mEM>n^c*_0To;:q(*WgTI]H6,hYGM,.7O#9
+ )I\j863;o8&Gs:kojP63VlKfHg$qBFqh+Kj5n:L7Men&.qG"ueP?g#7@Q,3Trnt_.g*d]FE
+ 2uhe"dd`)$VJ(f-.c(5/kC0s0mcO$tQ:k%V+T.g-1!ca^SV0eB]@2atb=r2?*ZNMki^JAdN
+ B4D]ol>nhU*ZDE<[fR2.\BTncVmUci=jl4eC:]Td:ZmsE3=E'UB"LB3iuKpFd^jeUN=+rX\
+ r7D<'I!bShGJ"P^!>E<qQ)BK5l3?pCZiqCq6A=\]jBM2lAPm\_QRJ@=]]p0bs;mnA8l/!da
+ 8)qst7DC69Pah?@#$?$CFZf=\4!)JUfUQ_:QJpcYWdSl]59-XK'AeQcEh2HQAiL3O`s;S?t
+ ?VoQ:um#?qH]ml_R=<LSoX-i'graq%4!PK7_hN7K#acl(/kcWorJ/c`orN%(!19VF*X=op5
+ YX%tpX-i(j5Fs<15Tc6WR:.dQ0BmrFNIVraoK\agF^@);,6#5I.4c#[o2Nr?IJZ.sr;#8Ch
+ ElkZl<O=E2Zh"%lCKX">.AXZ2p3Kq/.d8/<QoORTQf;^$]qs"<G"%q6+7L\"UroG::]e9qk
+ )OumcIN1Q:k%V+T06XR&?nH@Ia9k+gNh@X3!l:f5u6,=<LI8\!KaHEd7[iH#Bn%Y>P]8KZY
+ DPo2Jfgp6T1(cL7B:0P3!'!TLdX6ED`)\9M\Imk7NF$\V+_eb87[0jQnh^8X6,fO3.ild=_
+ 65XNV9?^H%$]6fFUb9\Pf&:PX!gLp$6NoK$h9geRlZ[#cQ4,?r?EOqjU\BJaFQ3m4C<_L(n
+ .4c#O?W/]*o]b2M_rmqLSWaIODPcbM*Y--+c>naL4tN%nYX$9X;8kZNftGA*kGPU1Et(TVW
+ ?*VI%`B'dZ!?;Zqt&oUT+i%HO2k8EPOsoC^WZ;uAL;nDW5h;JRWq-F[YoH^bIj.RkM("`$4
+ DOB#?B">r:A%FpA[l^FmI7`DlrFQ?4aK;;YP-uf7NCRS<o#pl]]0ei=X,Q`:&+mWd+2aG&I
+ %RKCO:WpD/eq*os3dk^L'1m1o.EL/V1s4D(U\B?QU*=IFE3ne:U'-?-42.c(cm=-LpK^Kfc
+ 5i?Ien-A;W?A;#hchLAnt[Ri4(A_@8$fXXD-k]H?`i*N7+Ls*G;3&k.?AP$<]OGUA_+tZO>
+ e>15RVcM'%ld'B&RD[Q99=uU]b@TMMAR<]s0u\E0LqbJi/.a.8="7L`I0L2J+?`V5'YcH[?
+ kesn^>_GpSXeS_>imn^bCmImX2JA,'G^tBc`<FG_1J9,,=P)@klh:?pm`BXhA!>3d-^C00:
+ 01tI[Tu3Ohq1Yo*H0'r6ZSqQ6%J1C+C>`)ot`76bFt[(#A7aUkcS+C\c)Fh<%49JWTY4(%Z
+ hGX(.qqMMt*tL>:D#Rp>OJY2XH;/K;kZMVjii]F;_cMSG.f![4g6`B=1m:Xc+0MO9R?1CV9
+ DRI2^L(KBYe0(W8NI8]+M[GijVHVdfe,Y"i%kOmF1=0N%DR_QpO1)K5<idFC<Fh34KjV#+n
+ `n+I%+oVh]*srC/7$`^U/mSh0?'KfM=gc[o4u_\Kig!)<9fg2f--,*Nm$7L&i\/%OTWgZu4
+ KDq_Pl7UWMb%AGrjIXfo=7J;(Cs6_.b0/Wk4O,I/Fh?KK2G_n$m&>%cg'&/V<<]>o*8kFN9
+ CXqTQk-J;haZ,19ZDD-3n.n`-kKb+:M;OR-3.8;sUDDpSp?bfO.>7ld=^o8O55FZcg#D]f+
+ h]eOo@5?(35n&-apibk%SOc&eME_Wn1'S'C4Zh*J\5.A]mS3ks=W/F9d-V;a/===>^3"p7l
+ LF80_D#EIsS?_;-gX0:f[imC=<dH)"Y>it_-nV>%iCiQ,P5i@&rWHP</PF7h^o4F(KORbur
+ dCBJA\l91UmnW-re,<S7[HR!l_-8O3?PF7]][*REB@<TYd_d=AY*CUsR+Q\1N,rDRSt8o#9
+ m:ZHf=\4!)J[iZ^XD+/@=c5k4VdR9pqq`[LIiUFeu^g!gU4VVG^Y`]SaMU&I^qjl0UGGf(P
+ N"oDjH9+ro,RX8.UoW34jNp_<A&K]u:)<gmWAsqrJG*\I6>_@#:P\5iD<81!aX=L#U)_5Eq
+ e1B/$oTohb/_-+EJlONY;.c^>N)K];%D<YQ;T[MP0#;f/0!oX)c`Y^A4f\`ui@-@buma#Or
+ =iYb?h=*-*^GqIZ/WMt?;po>o2'G^t_5>j5^Zt\?l-V8hF2m,BOa^g--lR_Q=,pi</-#tr*
+ YH&#jc><mr"P[B#o/uj0E9Tq,/B.'Q2]1T:]!$@;P)l0q'#c$mXuD%<Jo-m4H2dJDB1bs_@
+ &Z,l^gN+ne&0.OP[kt!X]Q*VRFea>F2_K`VC3u,?sei*CVi)Ec\CZ\$\#%fo!$e$G:pDDX-
+ ?:1l$b8VHLB%Wbt[/&V*\UA!;i"<Gm4`C-p:$$-doXNDp&99H%'%C'sCN`E;hOsju$,]-16
+ ].!Wa(W,BY&UFJ/7C$)5jr1JRj8;YLt<;f[[HQPea0YIU.B='s)cIj)?i@=\CtULKh`IWr*
+ i(^Zq;"cEedHD)B/hPMRFXOGM\&M>_E/'`f`\\rnn=<OPKN9`j`!sb,_O-(Qb]=,juYW(ch
+ ie7`O$a,ghPjFfX.jG!CEfIp;61n(io5s5[g"7.E/N<YXfKRDb[TD_]ie7`IX0.d^'^hJ2_
+ W;F)7:Cb?R\sVdN>qafc]I(rE,`8%Ge]mVMFT//C3?FZQb32TO2"i.;Z+LD$&&P7WgATgrq
+ :9.8C*CUW[_1]0]DWrB^niO<0PTYd%aiP^fI%2/H3esLVa+DoQ_1taj9.>YRf5cC\T0e3o*
+ *7<hX#7\\s1hZc-90<`)sAOOgJ"BAC!JklDE=Ks+91R.FC%:[qhH::]fY19Y>7:C6W5M>O$
+ O5Tl%d1!f#Is4FGXVpL/W^Abg\SbT+>dM"iaCSF/n$7:cgpiF0u5L0)AeUi"a?SJoGa\NNL
+ GBlcZp+#4XZ=V32:7LFb^Roa>]oo>/:bO`+bZd[_*Yk^L?`L:Hr&OdHg+0*#l]h%"`KMN29
+ E<l0@k-X*e>ZA`D6<I.#l:O&W;T3^2dT('k+>Qs`6/l&or@uJWM^1(K^Z;>.m'S7.:+%j@0
+ 5@>ZW,/TG3rJti=V+Ar>FLB-1RD<94a%%.hmH05%sH+H`%rts00&==o)^hebT4G@3)gQ~>
+Q
+Q Q
+showpage
+%%Trailer
+end
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-area-drawing_expected.pdf b/testfiles/cli_tests/testcases/export-area-drawing_expected.pdf
new file mode 100644
index 0000000..17e19ec
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-area-drawing_expected.pdf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-area-drawing_expected.png b/testfiles/cli_tests/testcases/export-area-drawing_expected.png
new file mode 100644
index 0000000..8c8ef62
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-area-drawing_expected.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-area-drawing_expected.ps b/testfiles/cli_tests/testcases/export-area-drawing_expected.ps
new file mode 100644
index 0000000..130815e
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-area-drawing_expected.ps
@@ -0,0 +1,480 @@
+%!PS-Adobe-3.0
+%%Creator: cairo 1.16.0 (https://cairographics.org)
+%%CreationDate: Thu Feb 27 23:52:48 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 3
+%%DocumentMedia: 87x72mm 248 206 0 () ()
+%%BoundingBox: 0 0 248 206
+%%EndComments
+%%BeginProlog
+/languagelevel where
+{ pop languagelevel } { 1 } ifelse
+3 lt { /Helvetica findfont 12 scalefont setfont 50 500 moveto
+ (This print job requires a PostScript Language Level 3 printer.) show
+ showpage quit } if
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+/cairo_set_page_size {
+ % Change paper size, but only if different from previous paper size otherwise
+ % duplex fails. PLRM specifies a tolerance of 5 pts when matching paper size
+ % so we use the same when checking if the size changes.
+ /setpagedevice where {
+ pop currentpagedevice
+ /PageSize known {
+ 2 copy
+ currentpagedevice /PageSize get aload pop
+ exch 4 1 roll
+ sub abs 5 gt
+ 3 1 roll
+ sub abs 5 gt
+ or
+ } {
+ true
+ } ifelse
+ {
+ 2 array astore
+ 2 dict begin
+ /PageSize exch def
+ /ImagingBBox null def
+ currentdict end
+ setpagedevice
+ } {
+ pop pop
+ } ifelse
+ } {
+ pop
+ } ifelse
+} def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageMedia: 87x72mm
+%%PageBoundingBox: 0 0 248 206
+248 206 cairo_set_page_size
+%%EndPageSetup
+q 0 0 248 206 rectclip
+1 0 0 -1 0 206 cm q
+0.254902 0.411765 0.882353 rg
+14.172 0 m 127.559 0 l 135.41 0 141.73 6.32 141.73 14.172 c 141.73 70.867
+ l 141.73 78.719 135.41 85.039 127.559 85.039 c 14.172 85.039 l 6.32 85.039
+ 0 78.719 0 70.867 c 0 14.172 l 0 6.32 6.32 0 14.172 0 c h
+14.172 0 m f
+1 0 0 rg
+243.668 152.23 m 184.707 148.898 l 148.539 195.559 l 133.488 138.473 l
+77.918 118.504 l 127.578 86.559 l 129.402 27.555 l 175.141 64.895 l 231.84
+ 48.395 l 210.449 103.418 l h
+243.668 152.23 m f
+0.501961 0 0.501961 rg
+4.251969 w
+0 J
+0 j
+[] 0.0 d
+4 M q 1 0 0 1 0 0 cm
+243.668 152.23 m 184.707 148.898 l 148.539 195.559 l 133.488 138.473 l
+77.918 118.504 l 127.578 86.559 l 129.402 27.555 l 175.141 64.895 l 231.84
+ 48.395 l 210.449 103.418 l h
+243.668 152.23 m S Q
+0 0.501961 0 rg
+116.219 153.07 m 116.219 181.25 93.375 204.094 65.195 204.094 c 37.016
+204.094 14.172 181.25 14.172 153.07 c 14.172 124.891 37.016 102.047 65.195
+ 102.047 c 93.375 102.047 116.219 124.891 116.219 153.07 c h
+116.219 153.07 m f
+0 g
+2.834646 w
+q 1 0 0 1 0 0 cm
+116.219 153.07 m 116.219 181.25 93.375 204.094 65.195 204.094 c 37.016
+204.094 14.172 181.25 14.172 153.07 c 14.172 124.891 37.016 102.047 65.195
+ 102.047 c 93.375 102.047 116.219 124.891 116.219 153.07 c h
+116.219 153.07 m S Q
+Q q
+39 17 151 151 re W n
+q
+39 17 151 151 re W n
+% Fallback Image: x=39 y=17 w=151 h=151 res=300ppi size=1190700
+[ 151.2 0 0 -151.2 39 168.2 ] concat
+/cairo_ascii85_file currentfile /ASCII85Decode filter def
+/DeviceRGB setcolorspace
+<<
+ /ImageType 1
+ /Width 630
+ /Height 630
+ /Interpolate false
+ /BitsPerComponent 8
+ /Decode [ 0 1 0 1 0 1 ]
+ /DataSource cairo_ascii85_file /FlateDecode filter
+ /ImageMatrix [ 630 0 0 -630 0 630 ]
+>>
+cairo_image
+ Gb"0WGFVoNIH^]DMs@>:hKuG,0.X(r,f,<D":W*XK,nD0\,\G>]L#qUH]rg8Q\:I]joW(",
+ UT5GOfW;#Y*)i!>1t<?OgJ;oZn#j7[<;+'RY=2/m*%TT3Tkb7bia8bs1$/s0@o^<pTmA2k'
+ OD+/BWFIk]aa_D"kaT!0Cru<WE'#>E8h,\<brKfOp8ZGO/`<82]"7f&+'H&b&%#Ka!R\-nI
+ JJ@0*/\$4G0V=9'e0_SS)C(-kKeYj`'T"d%.\/3H:.!MhOZ$dVXO!%EoE_D;lA5Y@fUn-k!
+ SOO>O.4@<NXUbiDdcVR^D.?23]1DCTi8u&W[%#"kHdO6Y/6X^"k1^XW::fm]HNP`Og'G`r+
+ UTj4rKa!Rd-nIJJ@0*/\$4G0V=9'e0_SS)C(-kKeYj`'T"d%.\/3H:.!MhOZ$dVXO!%EoE_
+ D;lAJ2M\6XUtLM&k-6f)%$beR`"SVj:M>G&jg$c)%$beRNlaq3MXV8gH@>\(UFekStW@gX?
+ $.>Co=LtPM<#'4hP%tkD9FtKkDeb!,IV_T%-_@a8<&7bW2mqr'uX$_SS)C(4\9_G0atVltr
+ 63+5F1fr249KKnX.f/-'IGmJ?4e7NHbiNqa.9"ptaa:fm_K*.NCV]nLU!fK!g;d=C*N6t,$
+ l$4FY<j0)7nDdaZa[SG\'BUSWkGV9C3cVR^Dei;9]%k=[@o]?JWEu'W4OE[D&n-k!S@*Qe6
+ ;qi^:MkI2JTWmnoK`tl/amE#9(=mJQf8MkDq,is"_D;lAJ2O75`DtRS8r>%u=\1nfi>Dt1^
+ m\2+-c]BBi2i`IYj`'T"^oUP>:.?sfTqd:L\ArYrL#)c4@<NX=EF>H":H/kFSG,bE^)@6Kk
+ Deb!,G>JSmYCI?`g+`OtH5\;cn:!_?-t(bbjoBKtY0_@`tR;K*!PP-nIJJ@0'J?SC\OS2iU
+ &+DkYKA1PLNq-nG?[p)IAsZEPp.?:dFki>Dt1^lH)uZE4i7N5$kl-nIJJ@0,#cp[E3-Klrp
+ @k0lp0$4G0V=9*O241*4@@F%>C;XE;Uf]sWK_SS)C(4[Dc#;$bc[S>cm^$"cT$470e:fm^@
+ j;_9:^T^SUG0f_s,mB!VB.,;248/,Mi_a,)-['aBP(LQf1DCUT*W'mnHWSiU^KO46eG>5#@
+ Gr(2$c%c)q\FH\fJC!<;G9'SfCDN@$4G0V=9*M(mIRon/WRJ$a5W.S@Gr(2$c%dTHPZBT=N
+ Bs`Xf\ClYj`'T"^oBEpZD?R=N@CXPP6*'n>$$k$dVXO!-T?.GFkF\Fe%<)@3l)2KkDeb!':
+ \(qiK-X]ku5CVsJ;MC^LQr:fm^@*.DP'5[gGCXJDMnn-k!S@)nD=k"$6<EH:qUZ]u4=(7q5
+ (!:2Zp>$/iB>:/JP=H_m/9juuu$4FY0UMnGD/aSnh8rA6-Y,%_pn-k!S@)oP.ZOE:I;%Knf
+ fctF]NW7%rYj`'T"^oBU2eEuXYYf%>f3S"?(qYnn^2nUj.k)kt4@<NXfPu;j%"f6Pl&8hMe
+ `dQtHDKB<`jq7a_SS)C(4[FG?_;5__2(j-$L;Y/>G?o*NU5oS'DCV-=@keei>Dt1^lB'iV\
+ qnleuoJIE,&U[qq6tqR8)]P^[k8@5;n4LB.,;24&5.0RV;\@p"7j.[]7d3Sc?CZc5t_'F3h
+ ,gIk46$i>Dt1^lFW5V^*1p4?_i`_E)FSVgLY]DIairP(LQf1DCUT*N-=l>Z`AKpFWJA;.g[
+ /@<NlBhj"li(7q5(!:2\QF`O4u/'t%4<7pETZK^QF+M3b!ZYB?R4U^]UYph4d6X^#JEGE[m
+ 7!j,YO(b;m/[";DQ6WHN*I2Eql]4bKi>Dt1^lJ.rS![mRcKX:B$gXbB=k6YPSCfi=Dfd9/K
+ nX.f/-%b,Dk<&(k)/f8GREce8rBa[=&MjsG_X':YiGAQnVK0,=:^CtYhlLX\6Q=EH4D0&;-
+ 3gA(^/,5B)I@"=\3U9pTY[jgeHh4jtDl=`CNT8(4ZR8Nik(7>H&[6:gq%A8TE_9m,fYo\eo
+ Tf=9'-'2(Iqr4ij;#/Lr7WCp0W_IV3t*h)aegN+>7`!EcT#8@Ch60'WILL%T+*7DY!U2_.s
+ %O(BRG$4FY$I>=+-'Wk('2_Jpia0O8<i1@Q+r3_]adZYQjJ\hJm@,IYh#!';kqme.l$SPOY
+ Pb4=e.j@0L&U"j(J-@q<Q^WS$pN9iq>LA]Mq6j@&PjFsgeA0,1E.c"/YTW,E&qc'AkWW#Sl
+ _rG&cfu*c,?qZnErD!JdSh(C7:?4!Y^c[^K7mOX]keG7dB(eiojGCoc.b"I\h%'3:a;ba/-
+ $(Ok:ACPf2^YU@UZ$68V,fmYQ-46Y]$4$]IJ%7gP*?.]'a<_-,i/N;BSgN(4ZPnpF\/@<gE
+ +@7:C+'T+k+p8(il<d9G'<C=bueD1i1K"^o0_4nB,OF\R)OW3EbD=\4ajI:DToLSa/l_LfW
+ +Yi*lTo*g4n?$GtEYd_8L8Q!]($H.S6!jLV0]-`66eleR[[RKg[!B)1g-p'JVPn]%._?-t"
+ n2h-uFK.fSp7\^NRbK>A/3bSVFRRH9#4?n+)^m6;$]_k_f-!u!\s#@4FYr<*p/b/)5.UYR.
+ k0s1'Gc3&8)B\E:oKkpUMkk"'jXCN?4X=\ft8mMDHpF!4in:0'Gc3&8)KhROJb.q_9^!,W6
+ i6F7b1FcC7b&O7fZCg!#?R14j+sA%D'7QKtS4,8Wh%c$@%TJ!jLV8)`%r'e>P/l$@=;7P+k
+ d6Zl@\>Dbb/J@3l(\;-3f6*8<?g+A#9g6`R@#kJ+]4&%*A0r,!*_.p,Ao().O7"ijuBDU>.
+ jSM"$s>LA]MA_'p\C`6Z+G-U[U7hAL!!#?R33R&[9:>fXjgEO4l_XbaQXJ>iM"^o<+*@?IT
+ DelCWh9#LcMW/iX2qG*r*qBk1@0'JLEr4?S"-f_(LkkXu%D(@UlROE-:;0l=;L6VU$AAXk_
+ 9$Om'!749bpZoK]<IG"<`)s!(7a)>3RRI5kY^>2]rrrtQef`SM6<Y16oIRHI/&Z;!jLV4Q:
+ VD_%B:Hu*n(@NY+unE2mVUfAi2NQOs6`!=9',Ls4YCZMfVp[&2.`:=@iPF-nG?+:2c_cBPr
+ _"E:JmqX\!/ikTP*oS%ifLc/f\gP99dsYQ-46]d_pW(@,Dg%?2g2dk9m6FsEKgC&HNZ7FK'
+ q5+o+[/3'tMI%m<S/e>o')CLSH;FHtapF3XM=9*7+3OZdI0r93#GWOmJd7T:n\olF9J9NCm
+ !4VYA2(@m;4\35T$#.nW7=(?Wa;4-0<*-J"3X"pXKa$DchBU\XEOi"QMFTI"&J%o?jDH#g]
+ l]Za$fD0q$c&mXLR/sEV+_C20;]Uf;BS.;(4]Wd%P9JMasGk>]TpL:FdD]c'W(\=ZB?EB:f
+ m^8jW%N?En.&aMMKUh031sbm[@C1A$pCp?jhYli)\-S,mH6;XS[fTdNpPI;WM\c^e0Jp6"'
+ f([=A3cK9T@!hVsB60;?pc?PVosQB`0+K#.S.COaVRK8s3a]u9e]M\dlm/f^>9gr.7=TgiG
+ k\922O8RX,l:[p2DjN1#OG?@EKC(qG6>6jmT1rtGnh'\d_]0'7gEO0\^F=CUP_?+]Bi1:)V
+ mC9%*M)WhnLebR\#M>#PmcO&k8sW)i!X:&(!4NHO2r>_tf6(0b+F:tiPnYV.h27"9!st[*!
+ 8KX-mQ?,4gsB?F;s55T<`51KS^Gs&a0HjT@0*l\pmm0KH;UHD`&m1^HJSS%*aq"5XJ?+:$4
+ FYO,B(mcLdeH6/07*\21)Tk@`sGiZ>AcK+<D44J6PP#RU]0OS%>A>!I_dVMNgP<!P!:Ec.N
+ ;hW7)c_gZP3p\uYdp'F.c4&TAF"J6PNfcS>bo]l#JE5!M'Zlih^"h8gAF26EQqNH@\[+;>T
+ A>K:p51oNcoDC8aRT<XP'`DBY:DB7JC;HP;*ilUea=">_W=\02j'Gc4'a'YHId"Q)&0&AZ=
+ d3f+MN3&f@kZh.d":Ttfi7@GMV]->d\XF0bSY0_gQ#D!q7HrZsbmiJ*'H@f&J6OCE:,BtBG
+ iQ_"k]YNY;OlQ#V>`M+.0>+<!c[JdNi@ki+BOU=%pTZ&lh'dpYhdC_D4,A'ic=:n?mC@/i8
+ 2TC;\H2t^)eM*\)=43^$S;?;/p3!FC.:A!P!8e>Jc(Gd%eRl(+@dWf0!)3,T@'u^o^O>-RU
+ tQ$h['\W!,ndmSISD-,mDRV`*,/Pn$O2U9FXQH@O,V6mr";4[Qep!S!o$MY0fXOj2cAZ*rY
+ #l.2XW$4FYOe#t'&fQ2AKGg:miU=77I=@l@iZpZr6.>YhBE9m/Y$&Qt*,!AW4AO.);8R_F)
+ =]SEs=9,%9B%-NCVgYfk.UKB1X3mL0ok:t"/_MqTWICC[G>\[""lP]_W%Z\R`=j#7?EBqNg
+ c>1B'i,10YQ.!tcd<I3F8ifn,i5_$Lp!NjA2gfqUnFpl6=K>FCkEm\5n=Zj=,:,/^$J5>;1
+ <7d,dK_I6-5,'!cV`>iT1:(rLt>USulsgY,&<"Uk&%sZ^Mh98O6bc"Ot.Qb7&!0RI2`bXO)
+ g,U_mf)ZKL)7g/,+F\jXYt_?+[&kFbCTbB71^@"hIAc7%(-lp`m_gVfQ(/#6[>PUJ'_=)nd
+ %243ZqFW^-WKiCI<?./]<7Q,J>poF4KUjJ>R-nG@"p?u?t0[_InDt-e\RR\kW,0ep?6t$*!
+ `VuCH#/g@rmNipRYcH<FXKSEJIWp*:*@iQe/:]uFY]#Z?,ep@+MAI`K2!W:/4j7eS$[,4DX
+ '-h*VW:!N!P!/Xmm;X]?E&:-k:&E9Z'$;ToD%XJbG=`o"=Pf$<t=Y$?nfL!FEd75,nj<W";
+ 6Cli6%bfjr7"_laj-AQ'+hKcEb0IU3,9d<`2ar!)a<?43q;Vhp>ZIPE^I-=@mN+N@FNZ<_K
+ >?UaRbo<t;q@OMT^q0"AiHPa&g_8WgK)!\5]J/-,\jIV5h"d&tce`FG.6,!,+smUJ2]XJDM
+ n!;q'%XZ6S!]2j+g02Zc]@O9!I6aN8OpfNj!!q=</UVbUH%cRpiHHr]K"kHaQ]Yd>K$4FWs
+ 8)KhR0W=;[Hgq*4!fpA[goOa0*Nr;4YQ.og[X4=VP]"k8`9WU2,!,+c*agq4\XUWH.p4@XY
+ Mc1[!*hPX`_;!sm9EkPV^J<cZBF4c]NZ!<M?OlO"lRPn>r)nDF-Fl\O%u_F@O4IbhAK#sHn
+ 7p!!I3W!ldH:iF$up_(FKC:lh4ua#eQ3NgbkLb'GM=5(Ja=eAh]Nh;-5!5XJFK(Tt2dZ3bJ
+ I]@0*lY@`Qtd#BHsg.it//`_$RF$QoRIDP**]-'')lSJ\<k\uYLh'Gh;)@3l)2"^oNqD$_"
+ :?]Puk$QM*J`r\EHDIe[^Rat<I7QPAe6"'f(Ebaq$7#/!&e]$R#(1+)<aPT1dp?KQDFUJ6X
+ $c&$+>:(XWJ^<TniakD+Y?"O8Z]]pW!c[9"f-Vh*Ia@9;i`X*'.WnP!O`cm/SX\=`.4?_I=
+ &*\*OQbUSd_j"UJpZ&3XF/bQ.8'?[Ci2esH_2];!*;p6)P?6C/_3;.CP)Fud-[p<Uc>u%9)
+ \G4>Y.4i!OuSmE_H$9nW_rtRWWqU*c*d@[oZI48thLa9b$EkT3nH[ql^\LJ?_7-=.&p1TcP
+ cA&[][Z8d\)-R0DU;o2J*%R``Ld-$s7bDSH&c4+Gr^b7k_BZ+WV\]q>U%(Bh!Kf3RuaYGA[
+ IE-@DHUi?<QC+c\<5;B?LkM8)I;cj$;logMK0P3!'!TNo?4R.$D6JF">cFl6GZ$*6o)-q88
+ =0%4ES3J\XDp&7OoV8Vjgt6ga\\X4Ve,@8?JN,OON++s"DIi@J9rtH&iC]!K`nDpRQ_0^jm
+ ]F!BEDos>DX9Hlb[A4mdf%,%JbVZU>+#UG.@b0=q.RdAZ#8s:P,3#&Y#8@%f&[lVC+rr:F^
+ OZR+Al.M]CdNT"El([,dO,HC=Ul47E+_a4T:_b!cTgU^_2:A;ka)EpAtP6Ff0UMg2:GlKK0
+ j*i^n"7gp$dtL#l)($aqFq$^AL0Y\X:<`<MOhMXDc3$^gHn,t6eYP@[fn3</Bkj_Do/).tk
+ \r9'fY!8Q5!ZcHPF>h)!V9*&PI3rQ]4A2gf>>:/.8\D5]ZeR*gLVJp1b%u6m`!.8`nha2t;
+ ;lP,"B*'\kdClV/WVk8Y87[@N@K"`cCiX-q/;<R-Ka$DS_7^3b-;Top/fk;.kK->R"1'4HQ
+ 6R>_hJi@4p!gWq]AraR%>=rs":+\F2jc_ZF>r_N^l%>JMNN)"FRIDQcBY:(YVt%61]*IIK`
+ r"jLYeM50R91(+7V2'Q6r/"ik\4okGluVXHAKb<N@Up/;S?r"GI!j=GP`j.;ZE;'(5lQMWh
+ E?\t@e@j_bud21JK9Q`KfFDHp:mnaN']XYQ6D'Gc4ALYmBu@.Mh2cJh]OFWRZ\Lbq?cA(fF
+ Og#OLZOCAP)+Q/9m4kF*4eETfg3Iho.>"R+@\*c2a/N%CD0$CuB'OliOV%==V5[YUXCQ?6l
+ \Z%ZJ[f@I`d-]-CiHA."Y]E_8V<ls'X7$5aEg=KCYjU_EZOJ6WSaIUA!dMbDPn[[!ceUfS@
+ 0%3,\pKaZjD!!.`@Cql<Tr<!NUpMK*om0A;5Ll`lHEJg'nlg!m+<75%AqM>i?LMciI`)\Mc
+ 1VclM9A>*a,ZCf'(BkSbAO;!=B4MKk%`,(lY4:SKU2pi=@SB;?TCj*`oFu?I?IO*ct`iKa$
+ DOn^gL,T%U3o`@!lt'4"0a6p-:6jQk20omlA>C[@BBKa$DOnX\,;B%1bt9c?o[o5FQ4mNWc
+ C2i@73$JkT1Q`KjL@0*lW@Y[N``<SRpGd3FK;pOLi"[2?V[k/Kq,?%\E/tC^gnFkHr4[shK
+ 6])[1.BGV37lXT*GMfi>JAMA,iT.Er#1j%<Euoq'lbOc.9K]*Er8KZD<"Cn%>eMct$c%Hp>
+ :1dZapRbhnKAEGcF)U74]&ek/XNO4q3I!l'Gc3,Vm[k`)n&S('VFk#Jh+F!86'a(fFg?cq/
+ <J2HLhGs"lR8pY>$j`C(R$)#`*5djkEB7k?#8Nh*8&\g"8W"!P!)pF2EV_d_j$/4+bA\OE>
+ s9nh.!jhm0M@_?+]8oVR9`G[7D+M,/6sMY35K/`3kEVRm0'2PI>T>W@rBUVr[*.R)8m4;Ls
+ q[!^%D5YiK!YQ.Aa3OZe4%$p1pntQ_HFBs)`F<sAI*b54S2RZFa!2fH02(@m;4\1%o+fK;D
+ :'GrCbZ@[#m:Y,EoBKO<[SU'+VY2(Q2CRo\4]$V"+i#/+P+q&B2O1jdUM_IY6cOhs!\h%Vn
+ 8a/uQK\G@UCY3'>a\pJ"'R?ZrVi+B/JJtNWI9Pf)&2e-mO(6q&-b(8(XJF7G`'?>0U6]uWB
+ jt9F^Osl#P`hd!\fm\@#70X0C(gmUCZhpK`tk!s8KtAKrr_8j[UEN!t#FOeG^GO_HtX'j@:
+ <M!t#FOe:E%+$jp4<HchC5^]tPhJ>n0J(E+!r8p?UDI=MhV/-&TRpFJ!Hlo!TnIe3Y)TWeD
+ i*I;Y<6"-t_Y4A=un,\4H@-Z+No*btQ6[+Y7I=MhV/-&V(hg*;9YVeI7Ie3Y)TWeDi*I3"7
+ 6"+POBDKp[pjg3_0QlHY3<k2NWB87`5/7C;(4`BZDpC6hCn9u:5Pb@%:fm^pNp`KM:tT_O/
+ HpqPK`tlLf^>.3Kl%+=j0JcQ-nG?sa**jb[S^\[O$:>a'Gc4uj0-c_-u?4bQQLX+%k&t/;o
+ `7;DG\uA:6QcJJ.F%Z!)k7g:,=<"r7hDP6"'e]3k8a8U3Y#6A*)kpi"$/&^spKn;\CZu^)e
+ L`pAf\>=9,3nD+hCC"a[QRg]RI?@0)`V[<6MaKe:e!jl[]C%gi][eGEGs-<f'.HA)4O"8kG
+ n!LPqH>/GtVVh`IA4hr-R(4`C5RjTIbfHNIPIdR79TWeDiS_5h`dD?[bU%378n-=XN?n0R:
+ 3_K'Qq7-b*K>I[DZKLEYK>O53KB22:i"$/&^rY@Wf`m/DTEiWc4hr-R(4_O\LR/qop7`TN5
+ PG/-:fm]ep)R=K:tRPZ?[hDDrh-IqN8ts8.;ZQ_.mNb.Io'5I7WJti2(7g&h+)q^!WJar!0
+ $:%^VleL'tUmPg]RI?@0%4uT<m1CYZGp"q7-b*K>I[DEa[cKK>LsF4ltiJi"$/&^rV)?$Om
+ GIWI)_44hr-R(4_O"HPRG4[Rtb1T6l)Q-nG?CLYeM=:tY*FX8L`Tr'=`l(ch6V4G1N-cs>_
+ q*oIP9$c'iWG98RgCphQOT6l)Q-nG?CLYnM8:tQ]B=aoc>rh-IqN#[+YN^7)hFXR!q#P`qg
+ !\eZEW3CEUKDaL,r'=`l(chHTCkKUUlh9mj&+BgX"^pEDmTBaJD:%\2k8rOl$4FYjYr?85'
+ XgkOb0N5d*u#K@OlO&c(_jDMB?8C^^`O7+J<Alq:H'\<7&S5S4hr-R(4_M\4/Wr1p6JI;T6
+ l)Q-nG?C-[&0X1p=H9XSgEIHp_<C(cf-T8tq$adpqHo*oItE$c'j*D$_":?]UKpH@To^/-$
+ om[SBT)YQ]?;r6POn6X^!43ONErU4kb28&u3(^c)rCJ<BS5S![ktcfsEZ6P&YqK`tlFo]?I
+ :(:HaEP95+$D^[.:,H!J[:UI@FHDpbg"+3IE!>nBbYDs5Y7"4>#^7hb2'"@&f$A63]lG<$Y
+ &+C$^"d$7-7YSOK#K[K>^c)rC5iVJYaYkR676NnbkQ/DkYQ,<Fp[E3U_NsHcnj<rW%>=tI\
+ :'-u)&2e-mO(6k!8gUT!/\lZ\C@1UjgqYnci\Y_@0%W+kJ.^__H,kanj<rW%>=tI\Gc:@)\
+ i"'mNk*i!8gUT!/]I&ZcJfD=*7=7Ta1@I_?*9`JUIn8$<sKVa:CGL2\[#miXYB^?r5K*h-G
+ Kn!PY83!"sKpc[:A^?IR`kLE>@m$4@trLYag9luh,aIcpgHl)t$^hl\s<mQ^:N8ll*[4Rru
+ O'Xh1XaU^PM2r3#d?_&^VDJ]N,G[/KP\X0fqGEs=sJ8ZheJ-8l=AJa^#A5Lo^h+<(Z!PY83
+ !92R/\*h+af`l;1EDq(G+*rHU-nKoD>UrsG^MT]b?6+1"KDa'uHp_<C`6qJgVI\!]2f3EZT
+ \IX#4hruj(-if_[r6$(HYnB3]K-sRVYndCHp_<C4@%`1<CLh%ZdRD?9fbXjkQ/DkYQ4+=gp
+ FfTb_"Wpj"9EDdB(gYLE>@m$4Ge$eR!Orll/mi[^AOlVo]H4^c)rC^ooUSXtN!o06`WA9@m
+ 58'+aRGK`r$1j5TY-mB40^e$_A%7Xj=tI\O&3b%n"]GOb/Bn(R%dVh`m/D??el_?-^]r>pg
+ (jm'*PFK\nr!fIa\@=_BhCQCk&MY5>B[S.fT;-4qE75e0^0$\miX#laiq0G`"mOg``!'q;7
+ 828]i8DL7F%q^5'5[f5n&S^+[`?'jt($JLRFBA:["p=s\"]BtsHR)d#e-"?]#lFm\"d5G.H
+ R'SbH=W$'/3hL35bYDI;S,f7]Q!%En(O52a-Gt>X@!-YK>I[]MEMT&0FA15UDk#8ZUY'.7U
+ ^l/dSR9IFRMn!04!6U!Q9]/!_FWJ7j;lp"Rk'e*XNqi'Ga4Hm#_8LLVa*9kK=p'dF[%r!@3
+ Q>!b'(i#p?`+/.bSCOT9un_?(l[I4<p;4VI#M1C=C@$4DNE5C4YnZ:=t8O2:tWiPQ^n$Jh0
+ dLk$K[('k$98-%U9@=_gE6oJ!Z/b*qD6lp\#!&%/N+Md9GY@`gu&69J\#c>\_X8.\+,6<H7
+ Ka!'`4VC"8TWg\CYX\ac*Y]@q%/*3l8VP"_!ROrXQ9>jGZD"LQ2"jH?abdk9EnDT;,Voc88
+ FU8Q7&B<#5@f;/7:?5SYt"jd#o"0EP:q8.Y3BipZNU>_"oNE4lH41[Vg<[RarUj35U\2A^#
+ l3bjep`U!MgP>$[@Un&jV>KY^gAT)qL>rSa5NI$aW>)#VHGlG_8-l5cUT`J8G`QCJ+<GnFY
+ .o4VC!c:ftLSp.,2Y%13JF;La+;nN52_]l[hH":umo(3%Us4VISWOT7_'_?00#YXd])*loB
+ /+FGRg@#@)[!89bJ!@(q>O`6QLrlsel!5aZ2!XW=s"G"!Y=<J4BOT5qG$4FeMn`JQsqe(2#
+ l,k%R+K81>+:db#!0AlA$abaR&c`>;=PuKt,6>Rh$4GW"HR&U$7:Cc)`Z>EdG#6gn)B(M=]
+ XXu7@)[s9b75Loin1YS_2]Q-[r.c2lDq635PicdJ,"e5NdpuIBeB@S?i4Ano,hr<fsAcqcQ
+ >9Z`;)V^GOFE/2qEldj.L6HjXU[]:@e&*Ps#aFW^uNo2E,P&.9ZK?gC&N-e(NXUhUFdioC:
+ -h1\.fkL9+b>Dr/,JUITOreh'g'4WQSZ%iWr;lI;u[QhULXh]MXHcOPHp`NfI?Io$'1J!mQ
+ eo0Z9kCMMFWs08@Hp.,1f/7f08p$*IYEmhM6r78>(54QRqN>qc@T%uO[/&Q6iXdktGgGqLQ
+ UQ5n4qWX-f:[@r&c;2aYYGZd#,B6l\@?1PQ>*?7sV55]OCmk%bDbp;p7gDhZeu`H&FQmVQ4
+ _\VFaH2Etf(=CY\^CHGs33ZHA8-JjHLX5_,etH-!+&6S_E+eZEFRU^IPg)=(IgJ&Vu?L:OR
+ Aea-SmPqe>U[<5OZcKO)qIFSNhZr7/l(4\?JI)J;6Hqf^n9-3a$b)l&gl>okN)29fdKL38M
+ /WY$BZ#$1<foo5ELH$F_SreS@e6S)NV..siOl<#;.C2E#I*^OO@L^,,RP*uZt\E(B/]"oWQ
+ 0R-sn?nbi!RJ,.9uQS5NXs48&0m#_;+O'id4WG08A4c>hgZL$r7_7V9I/8)hQ/rr4\oZ'1f
+ =](=0[B7.=Yh+4I0'WK(OT9?)+:DC[^A?A]B0Yq)YQ4i(iY,<V:O7?@m+L3%G[^6)*V13h2
+ i.LGp'l?o!;J-"Klo"L%ie^BX`$p=[&B<i!U1/l`9H*irM,D`r.fchG5hD3nH+#o4V@_t8/
+ J_GHJMAG1<Z*W4aOK!%Sl:1k?"*4F*`5o)roe)J%9e5mIFhL`!H'-!4WQ5_VVr9rkJJ[mUM
+ :el-dQAdNjjr1MKb=QX:eXqK+Kgp;VgJ"ku2QFCgL9J@>5W==:17s7r/qIs=dL_O@/t=/Ga
+ B2+":!8ki?$G18lO/WO#.eB4^#7'6F\s0s?Ns1@dPs7tp,s4aeN%9BL,;BbjPH.K(bhu<>f
+ _SR&ITet+m];B]-9M[<IfcpM?#%%_ld'1?s@f=s"#$YI+lh4jXX68P^O](M0H:4`n.J5&\4
+ epM.P^:;)?W65mV4LTMO-TRZBUuK#Ua\/nWF!uX#O+AC:liBfX%=T`iV9K4'%p*,G-Udg,^
+ RPHGl\&<a+_d_c$oZfdIKqrS7W4d?AC.hQLp64Fo/2*%<^]BV7rVP-^FO9[(J2i(XZBOs%!
+ bc)&[/SfD#0qcfR4%08X?660bLP(V_obdn<d98-"I42heB7>Q<uhZWF<rP9tENE$qK:.5`a
+ sW@*pV8NN;@]V*d7dD(U2*W;lL\i"<>J.=6F0KLj?>J-lK-*m&5mR",O86jbPjkmBE_SUJ)
+ :S(hbbVLJjpMTHoh!Z2qelrshHe0(0;s9:G8$Bl2+SqMp.&Hk?,BnT\>-XgJ:OY(Bq/&_dY
+ ;`L_(;2TMdnA:#H6@P[!$oQ^@;k"-s+;Ce?b!><Ij7-UWSu77HpjssV.1;E:2iE2r>fC*;V
+ _u]PB;#=hkn-WKi(bB*rE?8,"qp;$6XFq(\[F]e_g/A\:4C(q4ol@W<l2!<pEW2RtY1bK7-
+ %bhiHXf4M\McN8r78h+:C<qnNI5n(tancb.h04C#C3ZDX%lm#_9Uc:)15o2e'QoZ5=K4hgM
+ F?)^-;CXpJ#[fF*n`&ZHEO%)@ImFoQ%[%0!oB(mDGBSP.9(n2GpYt"j\cl!Sl^k!em4Z4Q1
+ _ok]JXQZ!a5+")IWqtc;k^?egUS$:eq4#cf&Y6]0Xbi=.CV7`(F]in(%e`En#eWP4gXNUhe
+ Q6D0S(GQqd6(C-LSLIhEk1JCkG*5m9on.TmEOnCUYQ;2N%Q]ip-m]4\om31cgp_V[7Cg<Ns
+ #GEi[_+ZefXm1`otg@]pco-PZeb!kjT>^mVC3'#Wo$Yk+TESgS[:+`3A7\LL'&MWb@70HR-
+ U;dCjF;kmG4l=G:U0^4"fuIXDr-L2UFqQ7MW@dYU8JQ_HEN:\SD'@nUIZY%R^e,LK9cr<OO
+ 64LhVXaN"8VY;oOYG]7`JJEU>D5:mli4NNH.?&hkM0=C#n.H?=n*-LMoWd++Rfm#B(Ok4q*
+ WcWI\G9VRuo74^GfJ.mLm-^1[CWGZ+XIkX##NGKCkg&6tb3mBob'<dIO_&)Y3a6fFe[`/dH
+ @"hu*XMff\u2de[`u..0@R/h?,80dpB&@)hi$W#rh-](\'11G*`l@Lp-,*CGeZbC5``LM]?
+ %_f!@4p3$@DXAo(D>Gfpn`f(TgJ*f)`=m'`DA*-tVr@lIG;ahjJlTpLe9FYX\a[;MZ*sU8$
+ XCs7B6XRJ+AP3[4DpOQ>''O*a$de9m`E6qs6+Fm%'G54"CkX%1\)cLUEc!3)X'qX/RBgT+C
+ /4+f:OH@*jLddnR[pi:J/V7hrU="ijIiu^d;e5l96!:ZJ[n^p&%W46K9B0Q7fEqDFW6^3k"
+ R@3d=&+hjNmpk_H27c;PKDaI55Y?$'fIkS?['JEtO"3(_=ZJXVB"[mdI8Bgi6<i+f!C2U5_
+ Y.'r&Y<uphtC%A6+o1B*`!j+n`SdMmO#.(OT6NGXK[,JhnENA0HT9_dG)akQ-5AREVhDKO8
+ Lin^GKP`"9<_((:Ijt"i8fe#<5'/)*=B03hY:>rd797H"E$>J3\q*Yha"DN;RC3f<Ms5VPU
+ 9Lc0i%/a.qC,.siNA"@tY/mAAB:^>''>Sr,`$fA5]?,NINc)"=32Z:=s]Yd@]g:=4j>d8no
+ jKMo^QbHl5<Qa_H$lHZi0eK\h],68p#*`Ue!khk_i-U!QRPWf`Y5i9QFR[*m(cOFEG"FrbL
+ Ddc/A9GeiJ>4eQpGC(,Jaj-p].=3<?"P$e3m1ipo/j=`H,/+c)g"=8-lk03G49%\E9`tIje
+ QUW!dFCp<j9koMiT_#^R2P;OO/Sp_9#$Vki%?U8SYjoCdUO&lm,dF@PKq>G/=ETbrNl..#l
+ sDS'_\<MdhQQd>kF@Ge'gHQrV/D;2\Z8!l7P5!J\*nVo(>i!kHSi'qd7ci.fdp1(4l+P;N&
+ fO5g:4LcLNp6!5q`:"P.4M)Q:kTT5JE@T1cr0!2MR]M]hnc;V?fN_TU/"s5T:mo&%l60`WY
+ \_BScHE%XbJg%h1aWp8K<hdldc'88k']Vs=BATl'&<[[lAqMb\%"]8BTa<)=LB=;#Woui\t
+ heN3i%>@6\4KHt6AT0EQeT[XII_,VO.23Pko5aK&!L_*g4T,<S.g#2JKgeO\<iOrui;E"*<
+ V)j2mlLNKJANMC^7s+&AT4N-<2DgDId7#*H5%-gp9IRH9t!7qm]DNIJ(!dL!6L5;+piW`W@
+ X=ul.S^GGt%Q=#U"t$=<r[JS#f_.T?6,<!-j78,&+]ce1?mK8P`:boOlTj6NB]7_BR+':\\
+ GQZo#KH$buiY(<Pb"8p+*iSeqAkqTT9ga\Z(bV5KPu?J>c-+q)15$B&Hf-'N\JSm8IfH/K\
+ C.jus3fN4=VV\2-7b>sV;OFC[@8-0J@amJCdbu'=hO"=jj0`]fR/5j;fV:&=0kHSk&\eorZ
+ 'G`.WObCKgo@/($+gM*,;-9WSO!OYI]4ZRoqAfnc]I5=/asWEgY7`nRpo*hS'L52d=JVB].
+ b-g2FM@^oddL*7@74OdJt>f=Z\ndYV5`oH.K*9N&3:L@WTbk.\uPOd'A<EW[.LsF5e'WmUo
+ _lkeP4#41,IFWY=lW=!JC`474NXFYq+MQ*Rqg<Q@@-]#R^ut'_bR9T?.T0V2G8Uo\1-H-r=
+ %(ZV#@<nXiB?<TUtg^N0DY+,'k<T1\HsZHK/GqO^EqGl\$fK`tY-4L'lAO0=^qo)]1W$4F6
+ ;Lf,*?rq"CTUu/oeJrIPJGPY,;%gY^!;ucDWl*i?s^nS'K>rJYV:PX+@Ig<ak:fuX^$W[+;
+ BHMB]?_uZ$!!7tn!r%"DWMqBHP<DVtr^-Ip$m943/9"rL3`i4/rq"niddL`I5so(3#C0XTh
+ C?g7QDXZA"mNKPJ3]g45T?:q.SIa+\o^n!r_E;1%3RVY/C:h$-Er>9ggF.$6:Cl+-nGqY,:
+ `]aa?gX;Ds@!RJ0:Mh5Wf\Trm(Ok>l&jX+:'AH&<F"F2-RtEg1@:Ss!@Wp"sArC!c`N]WGX
+ 321NB@nq+p7!2ZQtp(5UkG'=dAman=*ND?-sj/<GsQ.fT%_Q_`Ns!!H/$+AoIES>T.AFl>j
+ =mr8>*-nLIYQ]rDii]O8So&Ul7<Dcl6^b\T*;K8;sr/((#*.S;%$^c3=`HaMQ=3(;0qOJ,3
+ IhWYP2FGH/VN4[c5F6i*"7R1^"cRjeJVW]Aqi@T2li;E&@0&=d7&^S0cfrqcjo(tHq]13Z-
+ nO<)EY[b?5*c#&JQ+sE!LI+nC4qK9r:ACcDg;>sTs0&oT3&%FMW]*-;Wl_!!Mfu.$Wupb($
+ BjaZ1PXLJ3PG5@*<d0kP=q3dVjL55+o-1/^Giq?H$sipK#oF6i+J,^hXW"Y\:.ro/un9l1[
+ @AY>JE;U6h,`Wo?&+1Y%o^9>CE+G`'?>G!-`3%Z9/3-iN`Di$f!@=LV8KH+&/hH3+*9%YY(
+ fHV(3N.\8G1W+u6aoFM#\&IJnjiKGb:o63u&%flYOTs0&;VQ4r:YHFnba3o#/J/9Ubi/q[4
+ ?)@>g#GgM_!&%/N^cN(-GYYkEFm=>%UB#ge'Gg2`91nkTnXL+@aGf=>q1/O]+pn^&l9d%mP
+ Er(pp:l?Hl2J*%PPeM_?j25f]J`*qF-b.s3hs_U$MDK`\kdrr00^NY\3XO(YC0HNF;<-tO1
+ uUa'%n$hH<@$3fq^Fb#6b)0'Gg2SC_b9!1o'Z5Kl'haLKr<a,!'"Y@K;3+K`ukn>eJtLq:E
+ A$HJce)o(8<-H9en+_ECa]3)E_m+3RVGB@b!r%k;Cr!^3PgpZ%hn+Qt4)i/]NAeOd`$IdlM
+ f@RdfF=ZrKGpMS@Fj<k7aHVmn\2_.iiBBn-0VuJi]_R:rF6b+QO#TO0ZpuX)!F]S%&s7=Ck
+ KF6HX=-"r`K<OUDHr(1R!eggUZqA;Yk"iVh9uV+pf:t"a!B%Hpr.G)K)2/7loMqa!W-HV6^
+ ,+P=A6\MZ\ubeYZb->N6"'edX]=n]%kR23^9Dh<'6Qb<C=J[B1C$Gk'Gg2k91j>*0i(8.g)
+ L'9Pf:_Ph@inc]/R(/!q65E"oPi9`H*+#?r-*BD7q=^Jf`#($G]j[(@^4>ESXTaeL>qH^Q7
+ n5Z7YY?BHH@>G_O!9,2uhso\:hkH<Sr_5H&.="^i4%"[kJN<'(ido^A>rioahMM/3P@kQ=p
+ S@0%hI4`KS6IW9n1Kl,5C]H[2;+$QV,9MnnWb3,!];_cV*E?5!2Co`j,qQRU60L9+J6/ts-
+ k83$`Y^OBbQ3[99kmm8_$O@+I$OHT=(!l^.`ZF,nD8eY85eJKr+DZdjH+IUa+8J]>mrsWob
+ :A)[-nHM,%ob[Sn,I.hff,da=7ol^cj+e<$4FrORSm9<k#o:&f<&6rl`[KE5;fWPq]t^9hM
+ ^J1^tgCi+E!m1kF=T@gki*W'%p*.jl*M?F7Zh6&.e*?$MDK`\k`D=oDp?k+R[QT4QkC18%0
+ 2@<HHXc)]+g4c^AofPC-L$`UXA33XQ5t_/L.\o]ai=Y&=2DO_rKF^TOSM8b'FA`N+;l-$i+
+ (Gm3q1+iR8[+:'#cnXNZfSUKD4[]7*I]&LqcZb$9b:D=?36#**t5S1T(<$rFPCu![XmD?<3
+ X'/MP@UB&BM;hsW=3L.AmYO:$UTd!KbYp5l]67miY,mI9ZFB:ZC>McmQ@8;mS@mI*GY]$s/
+ !/g?/.`@gWd)f"IP<qa_Z&\hW5eM^8sZ'pR?bBu]QdQI<NLsr:DAs'Ka!&QNr;`=f(CgICo
+ bRFCF]c/(VM\#]W1^8Gm0??Ka!'<K8Os.[7dR!=R@H@^UbfXqX&X($=BDV*nG]"o0=BC#7:
+ hAa)akLX-i(Brb7hu_VPX;nqmK3lE]hVcghEr'SQ\lAX&'t<itY&X0?g('6X:>5aPQBeK_6
+ \?B%!EIV&ZOUTd"VHpGY5)&[.T0!2E#fbR<TlRL45/p]UH+4Q<$eUtE.7;,M"+AeYR$E@R,
+ ^]421*Yl+`A*r!Nq25T("n%#s$Jep8EWYd(YX$Qo;)(jiG&OPlgT"h17;s=3;lkbs:JN#R'
+ G^tChc2C,7Y[HqMA$QS=EAc,`PZb;?PEE.D6=r#A)@k.'G^tC0?<`%`BX1?/7/e^CTS->&R
+ 8X>Xg@)D-P-rF"TiA-MKqDbnKB*1D:Pr)6KE\]jgqYnn-!3pYWr3eSr.gip[?Ja]KnH]/aB
+ LBnp,N"3B^VrGQKTs=<N-c="6q`49lBL&^P6ngWM)sr**LD&n?RpKa!'BOG\=tq9AHh@D,H
+ OJ%`K=GI*&He6^a4jl\#<2@9fpdkV[0/7.+)]VfU[DHL7gDM^k*e<]@uo(*N.)heHIku0(o
+ aiI$>ENrC[C=cE#/jB$l</ZG^e0F)<-dXaY$O"r1Q0PG[Sl]9i/KW:6eR)ZhI`gC8$6J#`T
+ VJ_V?qjKQcf:&s(%5b/Cd$.C8p?UDTg0PF/.bp'HtR*]i/Zg91tK7N_X)h;(\Bug&IPmr_?
+ ,;]XGZ0T'W_jaV+[*:()mrG?]UKpTg0PF/.c=o<Qt%fs"Zr(OY/N@X>0U1i]>m]IYn1;6=G
+ Ef>HD;*c[9?7GTts">7#FPpQV;E^k5Hc+NQaH@\,#I\r-70]s=DS%16=>#Kon?i'AbB&7f.
+ .]fn/&9nhS2Z#.+%I[NK1oCU[k:6Qcq^jT'^+Q3*qQtQ$QLVskj;H*NTZcD%Tl;$o&'Y#e'
+ $O#6)(%ZhGX(0']l(6SVV[D6aIZOX*7:Catqk/glQ7_DShFI=_:X1aECp>$.5=gA&U9HnuI
+ 9slTSN:?3f<5udUeB7)Z4./1X2d*`FST&Hgl'#N_:M]+4T"h((/L`!OerS"&%nso=n8m5\/
+ 0i&Jo?g0H2crEVMHVJ4gRZ7Q0Z0TZP9ncKa!'HNJ`"QG[d%b+5NtWZbW6<kO]&U=UNrB_?.
+ RJBbg6`TANS[O7(P)H.\doN^fQg5>HdA:fnk8?AuRA+tG1=\TrgP,2J,m1jc%SFkp&HS-!o
+ .YWsW/<\[C!IcqlZj!`_Be"EpVH@YoQ"n"]cJP_0_AuD:"ClW`^h7HjpaIobkHBe=e"n"]c
+ JP]IO@\.LcA"7cch5e/rb3YnU#2N%'U&oP5YWsW7To+-pi"$$LpX+kMm?7qc>G\sT:K67I'
+ G^t.VcCqsDHrKP*3H_qBT>Ae0Bb^m',A"jK`sg%AYn+][SM3q>'9L!nNONc<OB];Yk`UPr'
+ U_V6t(YN(ntK2S=Isp:38>DXu6))[SD%.+0%RV$4DO+C_cR>gV:uIn"UVOY>7&)lkS>LI]*
+ =M-nGoFlVu+"c#8UYO/!GVN]goLfMXk#I]*=M-nGoFJ#$PF:ObDC[M(`?>Zbl?fK;;TU=82
+ d.aWda!b(Vs.csmEZB'5MX_=_AQb1B#8Tlu&Zi"&3>#8<D/7):1cficI`T(W)d9G'.$<s5$
+ aNmkZ"bd+H,YPOng9jRhC't"?s/=,=jr5#C8.UoVX$@%"$\V%j?e?e[ITS^m<n*A;0IUb?*
+ nRLBj[Ui<WENrbkg"=^@tS[VUbghUXA<8U&=K8%Si>P3-ltl/R[_mkDDq;=p4huZ5,]&_=*
+ ;gor7X';5`[t,7hk9_-$i+]*dgfq.+eDIFRMiXo:#)j?$EF%?#"M[HJP:bc7W$t+@I!GR-0
+ 5hS08qD>>!bA@8PTL\tAc!7d)g[F@p<;HFpAngJK$jrU-;?*uY.=Zr+e)YIp#,:Y9B$-$Q'
+ !E-&0>`5aaPV+G;\=/iY!g?L?'Ip%56!/T*6.d$QF-\dGDd?^8'=.2jl7\(tlZk4&cSQ;hI
+ >0e^Q"[_@t<6V[\9jdEG=qfSV[q10-A@n_NM9g$l7ChjP7:%Do&2ITm1!c@M4lje"chX)6#
+ *\GK*[!YU&ILI=(Q\,Z@=do^VqO<Z7O6=.#tJRc@Y;d'TLXj"D?O=!DP*iI:fm_4dS7,t\o
+ u=4c_<l$f2nFN[sHk:I"3H_,!,\(*oUC2.4c#o"'!KC=1;MKN#(MnT4fq6.Wl9l)3,iOW!@
+ X;<sf=gcU\fB>e%"14Gst\3&KAt\=M6(`+M%2bP;=clb$+t+FGN:R-3,,A`?k"kZ[LSKAi-
+ "a5#ZGG.=06\td7+&el*P*ck?*o>V^W`3X,Sj3=uDo?A/Kikla%e"ec,ogg1q8n!;?=fPr&
+ TDir@GS]Zrgbmbm^0T."`90.4VGn)r-a+4"#bSLSk5##NVi!F\Y-MPY%H^lO*[3eo&L`ug+
+ 5cW]@=don7)_:Gr8r7o-JEV&ic9\IQE/';IJOeD8@=f-,q(=+$pL\#T!*NQN-H$'C2-k;]?
+ 0,*Fu?k;pg?T'2!B,YH8L<Zd$B`nR-.+L)C=6.ld!1$eR6=$epYJL42aR0>*0loF'^:H&3c
+ /UR-/O_)^-7jlcN#J1Dk+#HC4JPLU`7gJBM!UD4TTUoIj)j/.aRdU10]ASerdq7-eS?Y:uH
+ *brS4>_F%.bmd;j6"ktC^hH\h+3c&\=n,0rdDlgZU\^GathRKf&WbBN)JjgDe(6kG]=*A]R
+ q#drqpWb==BDT0p;u_%#?cZ"O!:frfMKtBL&[AG^bo:[^RsYos6['+anrQbI41JNJiEWimQ
+ Y2f%DQDhG#(ZJs:;o=;rpV'#+F9_lcO>F4GtI`DTCWWACh(#oebhig?F1C^#aN`:R-0T%#)
+ LmX$Wlm7)GJ5YHA)'lLPShkfO\C8kKIu@U8>(ZW-hI]4<$3N'<e)GDH<nsS!/gm;gd<:B,/
+ "ebIeVYRbZ]^nhF,Y('p2g<6V[`<F>87H6TXLDsIOY;0bl['-I#=;iU5319Yu,9t4?6de/N
+ a_?-G%'c$<k?.qrcL>Fo)J!RpX]Vl'kc[>Mb;.mII/oe"(WJst1K`sg),o-$qTANR0Kc[3U
+ ]@^Lp(?eWT@it]+/sdF;19[%)5@j!n94<BJq3m1dk0E?;SjJLfhcO_R1[B11n_k!#Pj/0"o
+ 31e[Zq0cm$4EZ(YH!t&n`$AC>@L+=N_'FbZVpN/+9(c`2;QMg?LtX#CoCWLn)[/MJP^Vu`B
+ 4Yqgdf#0hRRlUNksd=Y6Tp1Is@tgGK=j=>b\*CL"2M?K`sg)9.K(,[k9\UXjc95a\mcMRA\
+ *p,eG>QXOGuYQ`.u2@/K%X@=_7#7`@L)pNt#mEM>n^c*_0To;:q(*WgTI]H6,hYGM,.7O#9
+ )I\j863;o8&Gs:kojP63VlKfHg$qBFqh+Kj5n:L7Men&.qG"ueP?g#7@Q,3Trnt_.g*d]FE
+ 2uhe"dd`)$VJ(f-.c(5/kC0s0mcO$tQ:k%V+T.g-1!ca^SV0eB]@2atb=r2?*ZNMki^JAdN
+ B4D]ol>nhU*ZDE<[fR2.\BTncVmUci=jl4eC:]Td:ZmsE3=E'UB"LB3iuKpFd^jeUN=+rX\
+ r7D<'I!bShGJ"P^!>E<qQ)BK5l3?pCZiqCq6A=\]jBM2lAPm\_QRJ@=]]p0bs;mnA8l/!da
+ 8)qst7DC69Pah?@#$?$CFZf=\4!)JUfUQ_:QJpcYWdSl]59-XK'AeQcEh2HQAiL3O`s;S?t
+ ?VoQ:um#?qH]ml_R=<LSoX-i'graq%4!PK7_hN7K#acl(/kcWorJ/c`orN%(!19VF*X=op5
+ YX%tpX-i(j5Fs<15Tc6WR:.dQ0BmrFNIVraoK\agF^@);,6#5I.4c#[o2Nr?IJZ.sr;#8Ch
+ ElkZl<O=E2Zh"%lCKX">.AXZ2p3Kq/.d8/<QoORTQf;^$]qs"<G"%q6+7L\"UroG::]e9qk
+ )OumcIN1Q:k%V+T06XR&?nH@Ia9k+gNh@X3!l:f5u6,=<LI8\!KaHEd7[iH#Bn%Y>P]8KZY
+ DPo2Jfgp6T1(cL7B:0P3!'!TLdX6ED`)\9M\Imk7NF$\V+_eb87[0jQnh^8X6,fO3.ild=_
+ 65XNV9?^H%$]6fFUb9\Pf&:PX!gLp$6NoK$h9geRlZ[#cQ4,?r?EOqjU\BJaFQ3m4C<_L(n
+ .4c#O?W/]*o]b2M_rmqLSWaIODPcbM*Y--+c>naL4tN%nYX$9X;8kZNftGA*kGPU1Et(TVW
+ ?*VI%`B'dZ!?;Zqt&oUT+i%HO2k8EPOsoC^WZ;uAL;nDW5h;JRWq-F[YoH^bIj.RkM("`$4
+ DOB#?B">r:A%FpA[l^FmI7`DlrFQ?4aK;;YP-uf7NCRS<o#pl]]0ei=X,Q`:&+mWd+2aG&I
+ %RKCO:WpD/eq*os3dk^L'1m1o.EL/V1s4D(U\B?QU*=IFE3ne:U'-?-42.c(cm=-LpK^Kfc
+ 5i?Ien-A;W?A;#hchLAnt[Ri4(A_@8$fXXD-k]H?`i*N7+Ls*G;3&k.?AP$<]OGUA_+tZO>
+ e>15RVcM'%ld'B&RD[Q99=uU]b@TMMAR<]s0u\E0LqbJi/.a.8="7L`I0L2J+?`V5'YcH[?
+ kesn^>_GpSXeS_>imn^bCmImX2JA,'G^tBc`<FG_1J9,,=P)@klh:?pm`BXhA!>3d-^C00:
+ 01tI[Tu3Ohq1Yo*H0'r6ZSqQ6%J1C+C>`)ot`76bFt[(#A7aUkcS+C\c)Fh<%49JWTY4(%Z
+ hGX(.qqMMt*tL>:D#Rp>OJY2XH;/K;kZMVjii]F;_cMSG.f![4g6`B=1m:Xc+0MO9R?1CV9
+ DRI2^L(KBYe0(W8NI8]+M[GijVHVdfe,Y"i%kOmF1=0N%DR_QpO1)K5<idFC<Fh34KjV#+n
+ `n+I%+oVh]*srC/7$`^U/mSh0?'KfM=gc[o4u_\Kig!)<9fg2f--,*Nm$7L&i\/%OTWgZu4
+ KDq_Pl7UWMb%AGrjIXfo=7J;(Cs6_.b0/Wk4O,I/Fh?KK2G_n$m&>%cg'&/V<<]>o*8kFN9
+ CXqTQk-J;haZ,19ZDD-3n.n`-kKb+:M;OR-3.8;sUDDpSp?bfO.>7ld=^o8O55FZcg#D]f+
+ h]eOo@5?(35n&-apibk%SOc&eME_Wn1'S'C4Zh*J\5.A]mS3ks=W/F9d-V;a/===>^3"p7l
+ LF80_D#EIsS?_;-gX0:f[imC=<dH)"Y>it_-nV>%iCiQ,P5i@&rWHP</PF7h^o4F(KORbur
+ dCBJA\l91UmnW-re,<S7[HR!l_-8O3?PF7]][*REB@<TYd_d=AY*CUsR+Q\1N,rDRSt8o#9
+ m:ZHf=\4!)J[iZ^XD+/@=c5k4VdR9pqq`[LIiUFeu^g!gU4VVG^Y`]SaMU&I^qjl0UGGf(P
+ N"oDjH9+ro,RX8.UoW34jNp_<A&K]u:)<gmWAsqrJG*\I6>_@#:P\5iD<81!aX=L#U)_5Eq
+ e1B/$oTohb/_-+EJlONY;.c^>N)K];%D<YQ;T[MP0#;f/0!oX)c`Y^A4f\`ui@-@buma#Or
+ =iYb?h=*-*^GqIZ/WMt?;po>o2'G^t_5>j5^Zt\?l-V8hF2m,BOa^g--lR_Q=,pi</-#tr*
+ YH&#jc><mr"P[B#o/uj0E9Tq,/B.'Q2]1T:]!$@;P)l0q'#c$mXuD%<Jo-m4H2dJDB1bs_@
+ &Z,l^gN+ne&0.OP[kt!X]Q*VRFea>F2_K`VC3u,?sei*CVi)Ec\CZ\$\#%fo!$e$G:pDDX-
+ ?:1l$b8VHLB%Wbt[/&V*\UA!;i"<Gm4`C-p:$$-doXNDp&99H%'%C'sCN`E;hOsju$,]-16
+ ].!Wa(W,BY&UFJ/7C$)5jr1JRj8;YLt<;f[[HQPea0YIU.B='s)cIj)?i@=\CtULKh`IWr*
+ i(^Zq;"cEedHD)B/hPMRFXOGM\&M>_E/'`f`\\rnn=<OPKN9`j`!sb,_O-(Qb]=,juYW(ch
+ ie7`O$a,ghPjFfX.jG!CEfIp;61n(io5s5[g"7.E/N<YXfKRDb[TD_]ie7`IX0.d^'^hJ2_
+ W;F)7:Cb?R\sVdN>qafc]I(rE,`8%Ge]mVMFT//C3?FZQb32TO2"i.;Z+LD$&&P7WgATgrq
+ :9.8C*CUW[_1]0]DWrB^niO<0PTYd%aiP^fI%2/H3esLVa+DoQ_1taj9.>YRf5cC\T0e3o*
+ *7<hX#7\\s1hZc-90<`)sAOOgJ"BAC!JklDE=Ks+91R.FC%:[qhH::]fY19Y>7:C6W5M>O$
+ O5Tl%d1!f#Is4FGXVpL/W^Abg\SbT+>dM"iaCSF/n$7:cgpiF0u5L0)AeUi"a?SJoGa\NNL
+ GBlcZp+#4XZ=V32:7LFb^Roa>]oo>/:bO`+bZd[_*Yk^L?`L:Hr&OdHg+0*#l]h%"`KMN29
+ E<l0@k-X*e>ZA`D6<I.#l:O&W;T3^2dT('k+>Qs`6/l&or@uJWM^1(K^Z;>.m'S7.:+%j@0
+ 5@>ZW,/TG3rJti=V+Ar>FLB-1RD<94a%%.hmH05%sH+H`%rts00&==o)^hebT4G@3)gQ~>
+Q
+Q Q
+showpage
+%%Trailer
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-area-drawing_expected.svg b/testfiles/cli_tests/testcases/export-area-drawing_expected.svg
new file mode 100644
index 0000000..639e72b
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-area-drawing_expected.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="87.435127mm"
+ height="72.5mm"
+ version="1.1"
+ viewBox="0 0 87.435127 72.5"
+ id="svg10"
+ sodipodi:docname="areas.svg">
+ <metadata
+ id="metadata16">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs14" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="640"
+ inkscape:window-height="480"
+ id="namedview12" />
+ <rect
+ x="-1.7763568e-15"
+ y="-1.7763568e-15"
+ width="50"
+ height="30"
+ rx="5"
+ fill="#4169e1"
+ id="rect2" />
+ <path
+ id="MyStar"
+ d="m 85.96,53.703 -20.799,-1.1756 -12.76,16.461 -5.3089,-20.138 -19.604,-7.0445 17.518,-11.27 0.64404,-20.815 16.136,13.172 20.002,-5.8198 -7.5458,19.411 z"
+ fill="#ff0000"
+ stroke-width="1.5"
+ stroke="#800080" />
+ <rect
+ x="14"
+ y="6"
+ width="53"
+ height="53"
+ fill="#ffff00"
+ fill-opacity="0.7"
+ id="rect5" />
+ <path
+ d="M 41,54 A 18,18 0 0 1 23,72 18,18 0 0 1 5,54 18,18 0 0 1 23,36 18,18 0 0 1 41,54 Z"
+ fill="#008000"
+ stroke-width="1"
+ stroke="#000000"
+ id="path7" />
+ <rect
+ id="MyRect"
+ x="9.5"
+ y="3.5"
+ width="70"
+ height="60"
+ fill="none"
+ inkscape:export-ydpi="600"
+ inkscape:export-xdpi="600"
+ inkscape:export-filename="export-use-hints.png" />
+</svg>
diff --git a/testfiles/cli_tests/testcases/export-area-drawing_expected.wmf b/testfiles/cli_tests/testcases/export-area-drawing_expected.wmf
new file mode 100644
index 0000000..07f7e13
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-area-drawing_expected.wmf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-area-page_expected.emf b/testfiles/cli_tests/testcases/export-area-page_expected.emf
new file mode 100644
index 0000000..1a8b98c
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-area-page_expected.emf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-area-page_expected.eps b/testfiles/cli_tests/testcases/export-area-page_expected.eps
new file mode 100644
index 0000000..acb7604
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-area-page_expected.eps
@@ -0,0 +1,443 @@
+%!PS-Adobe-3.0 EPSF-3.0
+%%Creator: cairo 1.15.10 (http://cairographics.org)
+%%CreationDate: Mon Mar 2 08:39:48 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 3
+%%BoundingBox: 0 0 340 298
+%%EndComments
+%%BeginProlog
+50 dict begin
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageBoundingBox: 0 0 340 298
+%%EndPageSetup
+q 42 49 249 207 rectclip
+1 0 0 -1 0 298 cm q
+0.254902 0.411765 0.882353 rg
+56.691 42.52 m 170.078 42.52 l 177.93 42.52 184.254 48.84 184.254 56.691
+ c 184.254 113.387 l 184.254 121.238 177.93 127.559 170.078 127.559 c 56.691
+ 127.559 l 48.84 127.559 42.52 121.238 42.52 113.387 c 42.52 56.691 l 42.52
+ 48.84 48.84 42.52 56.691 42.52 c h
+56.691 42.52 m f
+1 0 0 rg
+286.188 194.75 m 227.227 191.418 l 191.059 238.078 l 176.008 180.992 l
+120.438 161.023 l 170.098 129.078 l 171.922 70.074 l 217.66 107.414 l 274.359
+ 90.914 l 252.969 145.938 l h
+286.188 194.75 m f
+0.501961 0 0.501961 rg
+4.251969 w
+0 J
+0 j
+[] 0.0 d
+4 M q 1 0 0 1 0 0 cm
+286.188 194.75 m 227.227 191.418 l 191.059 238.078 l 176.008 180.992 l
+120.438 161.023 l 170.098 129.078 l 171.922 70.074 l 217.66 107.414 l 274.359
+ 90.914 l 252.969 145.938 l h
+286.188 194.75 m S Q
+0 0.501961 0 rg
+158.738 195.59 m 158.738 223.77 135.895 246.613 107.715 246.613 c 79.535
+ 246.613 56.691 223.77 56.691 195.59 c 56.691 167.41 79.535 144.566 107.715
+ 144.566 c 135.895 144.566 158.738 167.41 158.738 195.59 c f
+0 g
+2.834646 w
+q 1 0 0 1 0 0 cm
+158.738 195.59 m 158.738 223.77 135.895 246.613 107.715 246.613 c 79.535
+ 246.613 56.691 223.77 56.691 195.59 c 56.691 167.41 79.535 144.566 107.715
+ 144.566 c 135.895 144.566 158.738 167.41 158.738 195.59 c S Q
+Q q
+82 59 151 151 re W n
+q
+82 59 151 151 re W n
+% Fallback Image: x=82 y=59 w=151 h=151 res=300ppi size=1190700
+[ 151.2 0 0 -151.2 82 210.2 ] concat
+/cairo_ascii85_file currentfile /ASCII85Decode filter def
+/DeviceRGB setcolorspace
+<<
+ /ImageType 1
+ /Width 630
+ /Height 630
+ /Interpolate false
+ /BitsPerComponent 8
+ /Decode [ 0 1 0 1 0 1 ]
+ /DataSource cairo_ascii85_file /FlateDecode filter
+ /ImageMatrix [ 630 0 0 -630 0 630 ]
+>>
+cairo_image
+ Gb"0W#BX56H/SW;NF2j8'mgT%fW-Fo?>[`Kjj-sYf3!(&ekM#QR6&W*m:?0DCN%d!log@ng
+ IYG3j4uA7=i;XaU*TF6OePBG!['c?"A'.-5h*V/*(Vb8Js_u#^@shccMZSk4#tuX3"&?"T0
+ Fu@k.*"V<4i84P0\\XFfcXDRn*BF%KI\$/B@.ak%je-g\s&182_86Sn`+j#&qE/[_f3IqRJ
+ QB=p4oJk'H^?f%/)r$d'kKJ7M,Xi>N%2+K84?\;%j%888hSY+`'"OrB%.CFC`aUdeeO)@?i
+ pWDQd-L;F!_C(qGdTs+N[)MNl\-nKnE7Ub6o$4@uU;-:>__?*8C'GdjFYQ0h)KnX:j/:aNc
+ @Gr+3$QuEG=La+&!_@&?(9X@8!0S7SKki(fJ>>YCi>N%2+K84?\;%j%888hSY+`'"OrB%.C
+ FC`aUdeeO)@?ipWDQd-L;F!_C(qGdTs+N[)MNl\-nG@6NZD5HjR?XK+!WBP(9X@8!3A3NqY
+ YD=-\hZ%peMMa$4GH^=9-pBfK\hp^-CtdIBsEr[8QqcM$8TeCFC_6CJMT%\#llCDss-<nAK
+ A;+"8ZH$eJ3W!7p5D3iBZq@IJ$)I/)n<nXlh&'GdjFYQ1cHff/fR_VV_DiirRn`IG*e:ft5
+ ^_?-t(X]K,J^T\:t?^iSFZeZ"CKki(f!,LG;\`:P]S_c1$@#c<nqa=lO)@?kfRT"QGhQ*kh
+ B3r_EFF9MO&,AC'(9X@8!3A2gB1=8&%6M1=dZMG*`.-&o;-:>__?-t(o^:t7(Uf=,<`2ar\
+ ;%j%Yib=YdpabBfVt3M*q75)Z4n(WTs+NuH$e/m(^h9c8MSa+.k)kt>XMp#=EA(eG3jqUa[
+ umI$eJ3W!7m[n0dX!>[]Q>broO<75;S"ICFC_6CYm`!3UXbuqkSbq;GRXmY](&*"^oS:gC4
+ PfmRiA#/%knM.4e^k@0'JOG3_QV_M4W71$-ceV&oZ\'GdjFYQ1abkH(oN+tG+&*qFD!"bhY
+ G-nG?[%YP5U/?eHRHr9ruKki(f!':\OXL)![EuqK[TWiAEK`tl)_36B#(A@IVZ9,a%8O6eD
+ )@?kfNc5Hp(A;!D-,kukMd?:d.4e^k@0,##kkHq!KtYamPnXoJY+`'"lo9*'W@N[^LodCkj
+ i=7&E".L)@)q6$/f$A3oPR`2;G^K*L;F"rj2d?/Xqp_9A0o3'gFF^$/6kPN!SDDQn7&j7)g
+ 6DjUn\/:[.n-0=La+&!jL_KTg^":[Z/7k'GM=cTs+O`#iAg3<nOWTHgRo1TWiAEK`tl)Ylb
+ BI/F9q%.k.b7*FIsua/6([%>=sna72)1n@5^cDEfRXnr^hM<`2ar\;%j%YiGKt$bS.ZD-JB
+ Bs1^JkYph5/6X^#JET6Ze151]A?:dFki>N%2^lB**k6Vn^m-S*-$s$8&=La+&!jL_GCNF%p
+ )tqR;abPp0M<i3ZY](&*"^m*g@F+7:Fs@sp8dYn5f2,QLG3$:s`up0I]$"+0chbn&lmjM5/
+ 6kPN!SEO@Dmhq=)g+WN<n05]:QMt(fi*6pa0Lk[_SS/E(4[ED]e[d(K2"Bi>hH9,SQVo3fS
+ s\u.k)kt>XMp#fQ$3<R0F7>d:mHO%;XO/V0]Z-aI_3GWUlAh'GdjFYQ4#L[<I?k!CJoE]s(
+ r(s(@$,A2ggW[SAsTqYY^=fG#+(Ts+O`35&t962p;rCV)(P4',4^gY'(0brY>p#B.d`=La+
+ &!jL_WmV22l0:^E"$QE-E]B/?uFsD?\-,k`&n,'@=/6kPN!SHr9Y,Uq<02IYD=(0"8FF(25
+ BfE?dCk)IW$470m:fm^@SCnd(@Dd;gSJ4!Q[VT]H^<^\-cY]4tm(gL3+.L-a2&$gV*G8^$N
+ ]2-8H>MbuD<'nR$470m:fm^@]X)*?]U::uULP`h=H2`WI3lks4jsr(XJE(>%>=sna#i$'a4
+ n,qH;oiW%;X<r_F:mPg$jFaQ1EJREu9ak_?-t%m']LU#-M'hN4lGJXea$bBlZqe$L(EHKLB
+ 5LfPtfp`-CTKU.<.%fSGbBH@Xhmk&-`7/UroT1e-JuYQ4#D8D=;24T<4SG!l#0^67L(n[U6
+ r]I+)F-nG?+%cj]J.dSjq_R?SB.'UCJU?(tbc1N$Oa"e83K`tl#_36AXMH6U0)Afj4SWslm
+ ];OGqc=`bW"bd+f_8-f.9n"gtLH$`!CZJ%fiVt-G,Fc;^@TCM_l/@R(5[a[qE9Of'@E\=OL
+ P9Sd?2(`Gfs$@_$X,pW,8u,&@0'JLkkHocMI"ecX;&*#ZB?Ehp.C4jK/8u9H!;'(-nG?+%N
+ eniEhB98g+$o@UC/S$MI)n.E-&/RTLM1D=9'*V3O6Sr?^84;`PRA;kgTudR,,jGm?gjOc:S
+ E\TWeDI3-&Q76+jAcIWc)<@=KTBmCs(">:;Y;&NCI?J-AFR0/CW"2;Ql<_VS=qZ`L@+-p'>
+ BPn]#ZK`tl#d=VrJ2!_a@j&0='l?-^4*c!^?`NN!SXBR&8[h0;g(4ZS/%PBRD<gE+??=@'o
+ =\4ajF)p*e<RRW]JO!WE=E+2R!sZ05f"G<?Co_>%.;kA$Q(Hk%57.e6K&^c%0M'_gS`P$'g
+ OsL2F0.niX?:,:.k/go$4FY$'5s6P[RU#\VQaL:[oZJ[IT!s#1R]7b8V)5^YQ-46nbRWc6I
+ kAhSe\^,p.C2NlROCkpLZ?`Z]YA1U9FVK34XO16([@`2_PQn)HfXO;;Z"FZ?\MYh7U+"HC2
+ U*-nG?+:;/X<TM;n;=-!aDVGd/t)qhHQ1CL#A3W+WK!jEg1eN!h6Wn3X_Kl)gNUr]mUP2bK
+ KfrV!b*@NH=$Zbsc2Fp`@cB'MllanYr`n6:;q,aIOf!:G?D($q8$f62@Q9gS#FLkjg_jY^O
+ M&p(aZF<n5<3hYgRjflg.o*c?di_-=^r[A#26rC>>S"7^g"`\CY88dV*q'Y1@0'JLC@;R`'
+ "-q&n\V_kXJE)u[O`pXOrf;\@);(jdg[30^4-i8CV)X`c&Iu&:%dhKk<,P:=9',,hJH.mnZ
+ '<!UWti;I3lk(m/!_>d\@j--nG?+hVL+AhXomnn`!-RRp.Blb#h5tKIAAO$dS+/)K0ijof2
+ K5hTJpTcYd:>793<>6CXc`5.j9l!Ea='V<iGb;kV0gQa@EgPn`9G7eYj#fe#q"UduO@(4ZR
+ $G"d`O2hcR:GXadX`_),_I8a9?OYeI;!"7Z:4ur&^kgAB!NN--ZQ0R=O,FcGhEp]-Wd_d.J
+ TWeDI34j$*]V-e*,K0a'Mi^]5d"C[S2L"K*C\`mak<I`k@0'JLG3d(k.oW04Lt=G+0!1oT[
+ IDkI"@cf#=CBLB;E\9tRrNF^,t2nR#3c]7,nq,F;[86gHU//UO(Q%nFF(25BfE=$];Mlr,!
+ f(.!)=QY=_U]uMu#X-@N#O:e2#c/.<;ml]MZ7>3X;mL_?+]Ai5l]<=h1>`M!*2'.k)#3$4F
+ YK6RS0="Jn6%Kak*14fki_\8Z*s.k)):Ka$DaU#X<)C3!Gf'!J)uDQKW^Ho\gtk$%"+^^:D
+ V^o>5lQC@_L<"B[=UiUFTUr[ms;GG+;krQU?$4FYK_ag%1FQA<#+PoU=696FjB<`CY<`11G
+ $4FYK,=`Lg7&3`T(QY=[Y,nEYa\%FUUTa`g3c]J-5sDI:Ic=M;X%mjhYeH:[F]MBC(PN%Ne
+ BpVo)RSP9mN^RVlWdUbEU3I9kub_$Ka$DaZ%FBWXjXo%KBS&O::[0UfSqF4.>!Dn!c[GWJV
+ =<Y\=n8+f?c\4F^MbC%BDJ%TJ\!$%E+:D!c[H"Rh$A.U0;2dGL0>I\jsL%Pn^&"B/V^>[VS
+ c:=9)):Iq+$q/8jaEiXp9:KC2X58)'"S.k-(&_?+]A`9["(8l;Y$M\j_B/NWQV*bI@:GrmA
+ 'Pd?Ois!TDuVbZTNB97t]j="gI.k-VpKa$Daak,Yn1uahh.NL]K<U24%.k/W$c"UnS8HHOo
+ @0&?QoBI<J3n3Ihn`kBNr0N9B4k=L]Mt^"c]!Yj*-0Pno"lNA*^=`^)B[C&'PNP(f<`8#IS
+ 6U8\Q#o`>2[Tr.WOMTG1?_&gc4KH8#e*i4Pd$=fD^[.:VquaQUp/*)>s*:^,#8QM);27bS9
+ _33n.Q-:^o:7ZB,]j(7Al#U:6lhL.4uU1e^DNOT6u7-!c[GShAHML#jE&n`eeK\)>7-0m*0
+ FHAgN-Q:fm]mS_cNc+9^\Ho82tMr'c%+h8naGQuqrJEp:;g_?+]Am9S3#9gGkQ+J(JuWRUn
+ 67Q.@-bQ@%I,WQ2>^o=ee9oXAlkNd-I;=g8lSX%5fMM+<)!B>46B6LqO^OX1EUg@[Ll7mH,
+ HiDQ0NOA)J>EbR*/-)Tnm$JFSfFN0o4D4ZCXOBf6#eQ54]oo+QPnXoJ5k)Fm@rf4<^IXD$D
+ jbNtF@s(bCcAZ1>:3`1=tEsLA4'c+3-BCkgk@np7u<#FkH=,7elPaS4uJst'GM<JJ=PFR.G
+ dp/X@J-qbl?sM[sU?aDIcDsR*emZP6*-`J7(@GF^i")^/U.)moLVglAY>c=&SD,^/_RE\j"
+ 5d_?+]!@)u^%_o1RM'Q1%K-t=P,Ka$CM#BEi`9_`Q^`6/lH=*6a>OZaL`8J);HP0Ql*UTa_
+ <3I#Ru_/(dR6cGN030BI?QCek1naPn2-<=#@=9*elnAK@"_1"rd&J%nR\Zq<lB\uci;G^Hq
+ 5'6#`14uQT=Uu:O<BrG-Zr0'BDIi?_:9M:9;S#ATLVa+3jNt0."s^fJ%hqf>E.rC+KcXb`Z
+ FO0e?HZ\$SYQBU@0&?.pui?CH%&0m&FWTF<U&AN[\"71Shi%L_IoJh/-'$:El=cXQ+F_TTb
+ ;lK.k-qQ2b:D,0i1sPNZ:'lfO0YFec!GXPSfj2HEeN*2mNV!VpneMOj6l,TWeD93I5A$_$n
+ )Uqhhj3@>%lf8WgW%@eGFddY/#h$4FY3UB_kt(uSE*O*I$)$nEe)LVa+3jOf$;#,D'MmOb$
+ +`)EFX=C4X>iQs,V;G^Hq,*'G6JAL$Delguq8k<j$UaUUN]A_mUkun6&Ka$DUnM_4#%.0Oq
+ Vg+.5PoD5&nm7ba;mAYM=FihJ!B>,Zp%\e*]12N+02VcYl'*]EJDJ;PP(LOPnCWTro*iW]X
+ mRg5TnMUK8rBq7F'>#X>F*,-nA<<!jr+Zn>Ip;KJ<E3hmT*_D-,o2OUnFpl]O4dmHCZKkT-
+ WCD8r?j)=%i;2s!TDu7rhq,@kaVrF;OK>,`e$^\YP#5NGS$kM)S%n8ghjG-nG?gEq[a9R*U
+ aQ6bGM&k.9tr2mQj,V\c^p`'`Zk'Gc3DDnRpL?ns\*FRe?9Tc2`3htB["dXs?F)gPYR_?-t
+ 1eB9WW`cAC\'@0BnC=$7DoOtk!AL.M6"lP,6HY42>jH*+KDb\YpdA[rS0Y<IlgG,\)S<Xd&
+ (4],WpImL_aK6.W(b7$`/-*FH]mEr<UKC<RmL*p5elh]J;B00m"bd+fa4n5'`r-iF4^L6'B
+ N))j$hRQ5OY)$!H=Pbll+6ZkKa$DUR@S=62IDLY0+6LEZ'$;\fAsMYX/#hFNktj:jNL9MnW
+ )Kn4]XZq\<C,I.8#bN,mAu+i5duAVSkQCMtC]QVVl?S-O&.]Cfku.]HgfT6"'dRo$6sNnP7
+ p3HB`YKclk>K;Hu1@X/.O25PoQ7Yg!X%'Gc4eZeHhG4*$8P8F_r&?5ds@rKS(DZQ#p.CF)D
+ JrVFtrm!dpP(Ok$6LD1MSR<)R[`]J*3oEauITG&n<oqK1HDIi?^9Y,L`\TmmS'aefXY?H!!
+ Io4<,\(@k^Gj)W.;pU.p=9)qAL2En2/fkTm<N5lMNGM_9R0DRj['$rJ_p,LKjZ2o>G5^*,&
+ h#:2-P#4=1`3P8oJ#f_+;WKg/!#r0pWuaM2LN,?H=5mGXW@F5.4bHH%N6(j9FhkV#@^Z91C
+ Njm]9,8VlEnRb5d$mgs4s@Ab0#Uj$OV&Irs1cA"lR>8P:I%Glm4e_4<0/_ZVE$u,O@lXJ*5
+ n(b.t*S#lf=GWd)Y)_m=En`UTo0$4FY+6cYN49\4;J=M,Q.91YtF>@0t,FlN(g`T$R'QVbl
+ &gUF4r()4FppXV),$c';?a-RZ;T$)GAGVWYLZBE)qgTl948^UeXiC<([MaqFCm>1XTBG;54
+ 7>HpU24-K@HD#L6@&?\CbLbe@7^hB;),9ei-,UYT>[On9o%_+RYJpI5-jg?t,tEtH0pk(g]
+ "`J=@'!4<S@?4q@Sc^"WmtG5Y"$-:F6UWu]U,/FY#8@%ef?cVfO48Tec!EB!@QZ+8Q"Wh8r
+ C&qfn=:6F5pVn6=Bn>k31bm#*\5=mOR-"3#DR`P4^;)S66C1D6dKu;"LQQneJX#6H&@F)[Z
+ tg%uq]YcsZELf6tU'!%T6D.4d\4i8W_K/PUCh;Dct-]3`"WoYA\\^1BZ?!VjL9K*r,8:R1?
+ "Sd*j,`_),_I8c,,;Z(5X`#eAb=",[#l]*!1^gQ`gd(RB0!/m:g>,`Q\2c'62-\k&,fBqkj
+ V%>Q!AOBl,pqcJZ(Doi;C7QXl"kXR<.2@/jh*U4@STl`9;.j;HD6CL^WoD5(]AKofYQ4UZp
+ _tQ6XjK;MMZ>]G[#uW_nn4SCC2U8a'S=1'n?!A-cSq4R[CWtp$fDZ,<`7^VJ*3S."+e;jE8
+ osXGm9H[4G:[@!;Al^;N.nc/%<:*lA#=ckVrNEI;Zf'9ORr=3;M95K?81-G87:]>iistMP(
+ 74_I(_YWc_A@d&sKCKi(qP#'+%@@k`N"Ff'BbhuPc;;YX:T*dpHfW7B]k3p#1I!^d]V"^il
+ ohe[pJe>P1ALHiFKUO-*M8`:cM5M?9(X@i5)dcheMnL6!h#Msc<QGHhkGT7km\Zq;a<qq_S
+ Xp<SBSq"=Wm1=sTi4u=YB,V2WAt:84p>I;V<`4/n=&0,%&[(DJ%D#36IrG`&^k#F29ti[9U
+ /T$D5!0Uph7KBdLkd`lD7?Rr3m6SQ947i\E,*^@K;fUIGAB`SDQKXQ6a7\8WVb:/r6RNZ"l
+ R2\h'RgjTCWUnVn3\r?:eQo^)g@QLYgCkXSn05_?+]7m9O`#K?_jY`.pHc2<jaBgFF]mhQF
+ H\*Gl,k^k&t=:,TO[&>/G'(Xab!oFS"%7er@0/JJtN"(tr_Nd%uJkcs.-*BrHP8r>quVEtg
+ iZ_\59U9FV;33sjtLKoV4SG6KIJ%31TU?u/Cf6(GODdcRH!"QZJI<)t6Y=O]iTBbp2#2LU'
+ Wj$ra)UhRi=&E'"%\2>jpB*I7ognV8%_l`*W>G1\a1&,DQfo#_F[)e]ZE],#!c[(6r6H'-T
+ s3aJiC]"$BA*&tP6lpHi./X+/ChZ"7j%XY*6K+";V5+Zi@50B^siC[_HtU,j@:;>XBoNTkc
+ sln>qOMm?2p4M/JJtNW<5.2%17GZh^:Ybi,=aC'I2@>1EAT(2%V/e!)j+uXtV8?p`;hPoYL
+ :USeMa'gXQd2=9,3.Slfl,fQ',CIe3X^TWeDi*BLpYTWjdU+SjCiG_!X40Qm0C;M2hek^i<
+ "&+BRQ"^pWbd3T!N]qPbQO$:>]'Gc4u,=`Lg'Xgp6QQLX+#:M,';b%<N1+;Khh+rLl!?Ro<
+ !)+CroUnKYYNnCCUCZhoK`tlLfKk_<<pAK3UCZhoK`tlLfR\_\KtX1,acAX$!u)N$WT.n\?
+ r5KjSL$Ms!"Vn.!2bdkZ,!$:2g%pld0FoH_?)E6iq<*OKrrlgj@:<M!=B4MeB.[\$jp4:]?
+ ?7!^]P8dJ>na9br$p9/O)HT)mG.8!\fo2l5YGnf'?$*d0FoH_?)EVZZ!9<@2j;)q9]FlJAM
+ @Ap"$i[#)-pB)=jO5n,S.G@-Xk\9a]XVe)a(AhuO/m^so?s1CX]GU/U/D+5d/,$c(8k]e^'
+ ()q$2+UCZhoK`tlLo^:rJKpA9WJ2\l-!)mh)RMKgJF7f\3\HdA3_?)Fap9(Cb(,fN\Q_/VT
+ 2^g3W;eL9n?.]3A:4DU3hOq(Q9V$GX45Rgil7qjN&+BdW"^pVd*L#M8Clobj5PG.r:fm^pc
+ >J]TYik[-q7-`TK>I[DF`uN8GRI`W8'9.@^`*t'J>mTo-cTBC;E[?qmK)1;YQ.A9S(FkPHC
+ 4Wc"2$p.!>mhYZdPMN(\N4IEt@aEK`tlFi5l]<Ke:b@jl[]C%13KYaMAP5%17GR&!<K2J2\
+ l-!$d8Z0/G"m\K3:dpAf,.=9(f#+#>?^KcVX,pAf,.=9(f#+'iVXfIM8ur7hCe6"'d23HAY
+ e6")9dJ,-V[G_<j70QQ7D@Y;O(ec"Rl*oIJ7$c'j26]2?[HVO7"T6l)I-nG?C7h7ds:tR8R
+ 7t0k,hOq(QN#Xp`d%IU%FDpuc#C(m<!\eZmbm+Xa>HP6mk8rOj$4FYjnM_4#'_\El.6mP,D
+ bsO97S^T[hk722\qg4="2$p.!>mieqE0DIf';\oEt@aEK`tlFjD/fG$<sF?aj3,c)\a'<OW
+ :.5f'?$*\HdA3_?)ESoBI<J_D^U1noG?2#)*4gjK3j:%1:jg1Z[^?i!g#$^rT``bVCS_P>7
+ I:H[n%'/-$om?Ckk-fOg[$5PG.r:fm]e:@4T\:tUk)Oe6uN2^g3W,H(902^iJmKgkR*YQ0W
+ PhE<II]]SJcnj<rW%#"jmjR$5Oj"5)=*\TbM!7+JD!(P9#N4Z=iHCXo["$AqZ!>mig;Kf^,
+ /^S"hLE>@k$4FYjR2o5c/SpOj/O/k-cVR^D7c)f^3SqWEf;%D^*oInC$c'j:D3Ns2Cp>$15
+ P+s%:fm]ec>K0iDdc<2#l1/!G_X':0G8;1_b0T@q4S%<L;F"Jhgq;_#)0a`LWL&i^bZZ?5l
+ ;*1't-dHPok=&B.,<]p`2K-_P"-"oFUhl#C)!?!f5a-j3%l=>HS4S%m@]a'G`t3%NSng:tW
+ s;OSmH-4@<NX`:*3#V$;XUci`Bb*oInC$QsnRO"N.8Co&0q5P+s%:fm_f4*"l:6"-t^Ie#i
+ :n-k!S&AKHF(E!q\RdM[joDm36=9.kQHK[YI_HtiBjG+f7)%$`oI`Dqe'XgX.Q61F'BAP0*
+ =9!&5:>QlhLrJ,h9F?,6pQ?VEJ7m^hChmflC[5R4Ldb=Ka_-I2FdDsP"$AqZ!Q:t:DLKVG1
+ COUpUO1,o4hrif(-k"!GHZ9RidUTIRp+WRIcpg(Ts+N+do`rZoZ`e9D/E4L]?(RH^bZZ?5Z
+ FH%fCBa>DO]i8Y@4n^=8ANpG_X':kgZ_MX3^7(k-1t)l?0!%ciZBt@0,.N9@Ln<dgL3*3k]
+ ZQf;Q=&%m@]a'G`r(C+E=#m$V=`X_jMK])9nW4@<NXr4/-5NTE1lm'a;rZVH]+H@TWV/:]h
+ ;/6e^\F0Za1%;X@jnj<rW%#"ird<'1&`?V]pC29<#:6?WEJ7g8]ORtl5:"P:=)t/oF7@kRH
+ K6<8t-nLJ8)J[hag`mE]"[.gj3$B/:=<M<\g*>"%Hp*MQkob7K=<K'KW`i^+g2pD7?-Dk:5
+ !4e"7U^ine'bn6if),F&pGkU@qp,sq=t69U9Hm<WN09(pi<gD\$"h=0]1"RDPOF$Ec>rX7(
+ `&:1qW^;5G>;H'"D)Q]O7)f'o7(VeJ#+L!IW]<pbNU"88-9@)r09p;5-Q=YgOW')heG.=&n
+ c'QhO$E\>oG6EpF'SLr'4MW1dg?adY(k]ldnI":t`j"VQbAHR,V5Du&o,p`+tk&dWh0!$@o
+ =5XOsJO$nO?HW0['T3=KV:fnk8fHUFl&VUC?XLW>'gNt3'fHUEaTs-dieWd6cfZbHD^oo.;
+ &3q<'OXRrW7j:l\7KL=o@=b)^;7\UQ-2'k0^.2U_i+T2t=*e8OX1,6o4VAY'UBsHk'G^rWp
+ .,176X^#QX1tq%r+jbd]ldnI"CN=*"al7l&jUtE=MV-*Y-LP!#Vl_ppl@@\,\hIGq%3`T0F
+ A3_%!s7M"@6Gshe91M:j-r@X1,7uOT<$]@0(jcNs(PX-nHL#4VC"hU9K0@d,sYJMfZA67fl
+ 4L'G_7s*XNt*;-:VTm#_:2!egfj<HiRcd48.$]ldnI"Q0Y%(@[2/HR-V87fr/f$4D*14VC$
+ .Ts0'j4P1lu?B;+!Ns+B"'GbX$HR+.&7:?4@Gd']47#u$2?0'$3J.>FY^cF.B8;q2B"Q4J<
+ (9iZZ&cgcW@0'#)RQ"9=>Bk%dMf!$d":ugm(9i?Q&cgdL_?/#i1i9<RmHt<Lp.,176t$*$g
+ ts>r5C4Z+TVAZ@OllKL(QZjbp".(IOT9um_?08>;7\T6k@?Mi?.gG'"<W=$io/-'M$EZE!"
+ "1m!\n/F"G"!Y=<Ih@2qA]3TC2!HlNKGQ8Z0bX"9>l,=J/TdQ;*Y-(?TLUn^`sg&jTp#Ye[
+ qtYXd]9*l87P+>H_qTHbNEOXOb$"H_7a/9$rFjic=%!Pg,k)o#3_o<iB"_TL'R'GRVWbK3n
+ 5o]VIEB:"DRqng48D?&)=g(!%+.cSu`^Z!Ut\ZYp833l&KX8K@9^hjdoF`Tpf`mW;c-VlAN
+ ]D$m>T:c(gT2#:#%jlbc)e2)-a^H,H%%9Dqc^6HCqtA8?bK*4OnXlnao^I?RnDMM>;mM]?l
+ 1Z!D1$%qu+8ODbj+%!e.DPlTN#,ikpVn/1_KES=Fe/:lMBmDPCcQhf!*GFqKalpCL2StWG[
+ \mYIPgl*a%!4<4^dX/mV-A^h/q[fAu:<4T(#Mjrn5.Q@JK!os0NRqXqq'#QLNNO!PlFf/Sn
+ G;qYg:5r;"[:4oY5TjidO^*o/tBh(L(6p[mmE5%@'c%tAiF1XA`#B/b4UgYBG5r1@q[M!U8
+ FgG-g`[^&C)85b:BfPkg;$<Fn9ik-jImXP#jTi7h!V5=I)Vm2bNN0Nh(n^0j^LMq=QJ*K[n
+ eqj>V#U8t5!,tes$[_mNWdji2=^V1!kW*^1GO:hK*Bi@;FD0gVNJ%5mD^Oo,[u_,'7HN&@M
+ (JI<*.X8='t1)UdubkJIe&]hO+$B/@DfZK2pK)bI"se5I\/!Z)"WMIbUG(&p@eI;,6>Rf^+
+ \iT>8qeR>4Cmb'3;3uhkN]1FLqU"`f00]kbH&qnK4[jk,o9q(FlJ^Bk)G64BL9@Uds&$DXs
+ ,P!-*H>Cgl;F?i4C0_[d:FeLOod:^M9aNpZhu@j>?*@EsiOlc&lUHgGA?o"mQCm^4oRhd<&
+ ^Klj?mFUc+sr)D=[\dc8q5Od`tj6"SM/KLS(5O%D]M\c!_d>oUr4h=&Z?bP4,fctKp4V@b_
+ jFb=8PcX`G:UAZV+5!\7^BBi_UITP.iYP+%gGZg?@AA&VXn-.c"=k#a]UrG;Y?Y6-MdK-gh
+ aSpcf4on[qXhmSo[2VN^,W=$0bM-<gK;)U_SKBXhR^!_!l-sE?J+i>2*^uke>5e?("RK'ZB
+ XXT<.Cb4M\rGCfSCtKALfW4WEGA`-LCa+r^XjMs-lA"s1tpWdXR.!:B1<!*d__aSimSeFgX
+ YOo<j0=H.uW\,;G3A&cauQ<FNdhXT.ogdoNUki1Ca<n"0$0djn>?E$aZt[9T[?8j0!"-^B!
+ OW@*pV>,g[9fqQu?Pkp88T2Y%88ps702+fHXe9RcdK9aUOm#N1Q))Nc5b?l=(Dr8EtQGB<)
+ rl1DuY>'*2f!S3/`uYNe;7Kl`n/[VAo02rc_7nQ((qpM$&LlrWV*:R%UIor/WFtT%e;E]ab
+ 5O,lejbN=^*=\p</(e\1u^X8L37q<fa1)9/&_VE#uC7cN2pQO"FsUa<nIcr<r_ARP=L]%/s
+ _17a=h7?.(*B\:IWG!>`a@anu#S3q%MR#q8Kb[8PMo^]m@I%0?itFB;.N@,>kTK\+!rK!&:
+ cbfZ(p*J,J?^p$2hi]ZiK&WMrLl;X0CcDg.gHB[9(aFoOe-Kou+W4^W.cBMdi8^,,343FAk
+ lBPH1&EI'q=<Yo[[$h]ljh-L#3"V;gM$CdBk@`t)77uZs,"$L@\]=A);4WJ`so=<rU%Ac7i
+ e/Y:h`q9kil'X?*pFgtM?#UHtc/f],OT=<02@@'Gs+:hH4Og-e0_L7727Yj?/(8.,UZPe<<
+ pC>j/WP41.m92JXA7)l!F;:EEh(12Z=T("!+0eDfVYN?s7UB;]\'u`U"&kY>EE3W/lrU40C
+ l[;#9poOGPQ@JRekZH,L;remQk/BfHX7mqm/coJ2sCA<`cL)a\T34NH4h2_L3ZDo6aZ>^Qs
+ mZ_Y?U]6e'fWIOuro7BKlZi5HMq\^X[R8,rpM3"&qo])"MG):uKE\m*WDI(MF*WJsMu>rkH
+ 3m?u&!fcP>1:"ssbqY^?eZRtUH#_b0:'ed\`iSko84?`\TmONcq!_<;G(H*pb`MYmW4<qb7
+ 2d=2=SugF3Xfck5N4opM?e`!<NZR$T+9->Lo5F@[<3In15Majof*cmAHR&Sn'162dMcW(8f
+ LT(E7\iO<[e+!W&*sg6Nhk6s[qeKom.Q?E9Q+Z$\t9FK^6ZNi4V@_rVUNDJQEL5`f>i"#RV
+ I-%VFEc0\j#mn+;0rTW=@b2L-$@+mu;6k>OD,*M+`.cZVH_!(]bnLHPH2%V)>E_F`?D)egI
+ :%Jf&nZnmO?/+,cRC-0p7$k!WKs*,=>0EJ?Uh!!;Jh_H'^?h6Q/AXK6N5,;hXTq:/84pB"r
+ uM0u%EXKm2YZYt%G'^@Z]m#_8ZG]skqeuar3q*.[Afe_IH(`aCfH3eGq-AF0B*_'O[6f\+?
+ IGT?md@G,kWaF'n#loh?]u%MQ_S=aYdN9OIo9B*GF:mGcmmTF12H&3h.4OPE7u!:phVJLV*
+ XMh'j1W<54'@[VaZLkT!I3O\OU(i\.4_<CpTuT'C@I'EN-?qYm#_;+.>rm#'"PF?gZ)>n[V
+ nV6:1\.SfAQYmJ+oRiS:/e*>M15%5_,-:NfECnIh#':Y9RDhZF$BQg4ZWTs4<CAo#`![!&C
+ &Y2EgV2%^to&h6T_%7&cm&q+4:X&$>8BkhjB?BktF(G=2+f^WHO%-aU[e7G@Sil$3crqGcP
+ ']oGnWXL''sA[#<DbOeX)8-"_cDdh*=+)^n/oDhB(I*0b5m'=/b?MWchZ+#u]&cd;&p;3[p
+ [RTc-)oQ;\A6XcJ"cV7m;ni4p!GDBV8mC'D8@5Tk!e;B2;V8FdnpW*J[<8pmEd21jlFQY$U
+ [OILp.,2QQ`,V/'\NRA31Q*bSlP'.O`*oT3#Us0?[JUKl2NQucYm+B,6>T03[sE'2@78):F
+ igunj.@&kPiksqB&237fn4EF]d:6Omb2W?[<D2JUK?FOZ<XoZV/Gb52H8(MsQQ+i)f@tCtm
+ Q\G\enCnQ/<l=&.'+RHidMUb<nlkg5lS*QqZinD'>NZH\u`p^HMkro4m\lNdLYem@!)8l&V
+ 9Z_s$kW)TcP4"2WD9,gmu^Iqp.!-"k0_MZZAi/<UR5tr9#Nr;BBJc^IG'GeFRXAZ+C5AAn!
+ ?2_\$dao05[%X_nTs$sM'ju>/=f,s5+FRf%!/6+k[=&+V].uH(`i/fB8mbg'OrT10T7'A3a
+ i`uBmA[O2OhSO0,Voc8s*H`2"XX/3P+nQGW+pTTqC2T/S1#oii<c>M.C6@#Y.F>AUb=P)8V
+ k5-.))n7El7@iNZY8rr=A..%7%4mYR$OJ.+M*2S=X$:f'c7=D?,h,KgjqtF_#iBr$O'rrVT
+ =:<ucbO6"(@%+'#>A1,r'Z9SN9.'*&P>=9R7-,#9qRs*SpNWr&R`V$A.%;-3tTdn3>7#%um
+ ?<Uh`j=!)t27U_DZ4qN"HEL^[s:j)K(Ub=t5$&l_F0mlHi*\-#&YdZ[gA*Ib?5bb!W8/U5`
+ C4qK=o]W;us,HY^#Tt9f=<s&B9YSTsXc(p4SFi'P+Rr91,bF[$V5Un!QZVnUVkbt8!""Cs+
+ sDA>W=0Nn8P<'uIR<mW"HZ/&//$JLPj57-3[lNC^HX&n8HMF"'G_#A$)Ij+\5##h5Q72F<=
+ 'd&:fo!O-^>UPi]TO$9Bcft5Q[U_89iH12+,?0rq94RCD/t[;-5)r/P7TlnXcBDSh'jh-nL
+ P%C),>l8Q!7tMY%Vj+LigNP00Zs^+,V@UAji?dMF?_J>08=2BN6\;oO46;Ul.N8meIrJr%8
+ f$%4YEBCU(8B<<A.peb-=ValtD=0\CcR;1UiV6<o"_*VRmZlT\6SZ:=<YO:]N:Sf^H!!,Dp
+ Oj$!&htl@0qPfWb94,"*JqCi`aZUR"YD]_WWp@u(?a]q9J\hI1O71Q:/#KA%s!)sR!q8D]$
+ To7$$;WeAPPs8TkpmR#s%umkc%f8'kJ)C:qVh\:EtfC'[m@u^;Gu$5^+"L/!q7uQ$f#HMMZ
+ %Wg="jTUoHap][jWfs4ptBWVgk!^pn,u90`Z]tYe^-P;/3CorCe<8CB%T963c@8'Ge45pQQ
+ Si@R#o)PJ3+Zp^i,_3qQ^SqQ&8ps7i>\[1'ih-nK<j?WX8>MXb5QX4MeGI5qE!'ab=jij3X
+ k<#j=f?2T]S'b.rC-nN`UO/gjYK9KH:#iIi[(5Q_%MZ#[-+mFn'J59!D+K@"c9i80fkJ&Nq
+ >m'Qu$4Ff3Br)$5(f0TAWp!O4QSnh`'GcMPf9GR#@Q]PqX60GD^:F?dU9HoL5+uFMJ+H7rW
+ uHR?$4Fd`M2Li!Vl9b_QG1-8#bnOg>dc%<?H%0/%T1b0871BWUek/9?;E'$=2fDJ]3>%L#9
+ \`;!X9^(A(T0;:J,!V!(9Xc^s"hSj6>#[(&tW4Pn(TV'G`C/7B(8V`j99S$G@j9YljJ4Ka!
+ E`>H+2D`q03o+apTtn/-i_SG9UOnat"&jhI"nh>qkX_?(4l;)+Y</=!s+!I0I<!Y0WSW<"K
+ go,l:L?h+1Z6X^#Y*`jgc`H(D4VlH>T^?P`DK>I[jXB%'IYATZ?l2c-^_?*bc5CA*`@QCD:
+ CB*0_?haU`5[a[t^(fo%MC/m4\,Ie(m/q;HK`s#bq8k]_r3Z+(&&<#r(9m!?V$<r%:=$)Cl
+ o3@Z56+b^=MV)cW#-&+h#S-.Ka"Q9D$NLEME:\885B3cm\-k8F\aI:9>toe:?%*15iSNBJ/
+ ^\L@Q_td=*b,PE!_4%>]RT:jbt1XlM0KOOH:kAi0(8oQ*+RV5PRn93<6i('GbZ665@].ejU
+ >RZh,XG?laq)h"-b.4]Euqe#QF/VZ?e0-nO;Vbpo$CQE"9eVkI,%GDdfU5bapUJ?-,sqF#i
+ E/FR^Vhk-qs>gUBgF]M&2aNk(k:/G'.D4a\1SCA8Gi]iA/8%).+*`oKLfBI2D;Z_Ie$4D)d
+ ZARbF>EmZEfQMspbS<:6;0S-E(BEG]@0$md,GoDIUnjshn#)rKNbk1o4n?2PgT8lck6?H=:
+ ftLP$WJZijas)u.Vu^p4oC'&(#h#HJBq(p^`203.RV0q?MO'3W0(ro9N7E$>,-H8R4,kK-n
+ O;E#?<<fadaEW'Xh:u*a><CnX1f@G:5$#RI;QS7E';=s8*CJ7CGpQ*aPcB2sLhI_#cl#=MS
+ -6<%MZ^%.D2ZU-t&uHmU=;I_l*,N5>X&H:k0>.dBSs;;6Rag.>gX?3/-BY^jc[X'!ZffCsG
+ jfQR(:r$=M%<d0lP3'^B8Y^jd2\$=ageC,]gjc//X[2%@V58.&#1Z]tpO:3Ngi4W,?QLiX&
+ oXA&W4$OC$<VL9<Gm3+U$4D+IY@Dl;[Wbd+aZk`..C/-nqb.\7oE!`6@0$tF4e2J]0+W*2Y
+ \dKK!P!`u![>@X<18\j7bu\XSJPQbCc`^%"5$"M!@cJs/E^[']?dh#[UXRd?ibrbUcbM`3V
+ 0.kpV*m!hKtn+b:A,<;-6*1(_t56^4!kl[cJ6d#Q!pZ/J']F$OD&H`H(tC,CO,2gFb.b]V7
+ miCaHJS<"p)ZZpgsF.dH9Q/+(jdSfjWV40cF"LlT=RZ9G3r3PYL(Z+8U9MMLkBHmT31-OL7
+ &qHcig'\BH+f\F!q7d8@L"#Iosg*)b`!:NEd!HI^2e<?>i;H1$/[<6OmA)4`^Ko=9l3_d8e
+ GN46<^n4G*5hq]*/Eb]!-aXVs>sn;=MDGVIG"iPZlePZL!Dln6(P\ofB@sGkc-;a7(YQ#hB
+ N*a;<TQ74nod\>&*r$Yp"1d@5_JJK5OJN4;-:UE5+l@F>0jgmYjGV_2r6)iIdupoF'IbDHF
+ L<fqD@e/+!_@($i%Qe8p+!.cU/3PmA-t98o<V.Wo><NDp/crC55FSn/JqTT8>noUTf8EbZ7
+ S:f<2)3DQJmFZL>M)HZO&c\V3I*:DL(uh0>S'&g)f2I,YM`QLh<[3\0p@[]?T8;fR+Lrm&X
+ F@D,n"nd7PoK`jeP!WO"2U7MmEE3MW:h\FjMHY)<8'6EcRqscI>/?_=5Ts0&sXB/7pAnGdX
+ <?/b%meDdqVN!N,QX2d:4*R_IpI(;1&NnDFM1EGErp_S:;H20;S);G3=P.dk\(4n>>[73\F
+ RcBfN<f;&n0fjG/#-(nZY@l2>D<%)B;2DEIjV17Dmjmmn!5&@TMM]X/jD%@\56;C=#R(W-R
+ 0ktlIC%.,Pu+leG=94`L'7\TS[U:+t+iji%*\c.TJo#^\ZIh<snhr:s5`)neDt%cM$3!I4B
+ Ahq8ir]K#.S<1IaerldcMe[BT%G\@eP(O-$!(5V+<7i&@-*e<?JqEp];W^F6gO`k$<EneW%
+ T/H81VY+EEm-j0X+"oRMp1&[jApV3?fARX?hiIO0M\qg4Q"JVWpJ.IW<;9rl>4$oaImh5Rm
+ <.uAA5<OLq-nO:D+&l+`d%Ql$OBc5Oe,DTS0:,b1f;%D^-P.#H"aoOg.[G/HjAPNb[VNI^r
+ `[.4o<1p\-F.H863'ha^n6@]W98q#S0T0,]@BudnR-W2[Ear-T0VQf/Ds;s<7J6j=b#t5hF
+ f9(WNL8AH-X)#QX"g/:UqMt5<j_E;-:W+5,Y-Ho^ij\:B>Dt[?9PcefX*SW5m!*:S;bC(@\
+ 6j/(I?_4b*Ci>k*QgW4?ocNRFZ"#A2\I/C1kZhbdLk\#-+&k6kW6.A"h02:enA?F\Y`B;2D
+ JDtstPFFq@2^@`Waoo'a89tZk5g6Ph$mkT9c[S.3s+/(qI-nO:\hbp_LJqB9IX`Phq\anNB
+ K>Qd:EUk=gkQHSCr+rTpH@G`qCCasBdTF-,lo):YB?F"Ii*If_i+IsJeFWIqUVmi1SD!XLY
+ N`dl;\Z_X$4HX^?`L&;lI7BT4uuc:1XD_u(\BEW.14i<Ka%D@.A]pAk9hQE?+oOfPO.(Dj2
+ +]H_WL+Q+e9fA8:_7J=MX)(WMjIa[e\p>FJu0f^"HVpFh4(?+9aeo@0*!iX.l2`T,oYW8E\
+ 42+^:rCb7@LD6PL=*\IYa>a3P-N@or3o!e\@UkRmB<'^-*^"ao_W.dH9L/Zk^TUNdt'Wk#d
+ ZTWl!'rYD%S5>-R>;-:X6Z;f=pkg3f/6oriG[.2pPPIG":F:\1M$J^WX!ra"RK&WF#,[3Bn
+ cBZ$s_k=4KHD(0e"n"ig!WQB%U7T\\5'W+g\=;1J`:J;>r%&$>7UZ>tqgs]Ok0<3:hFHboY
+ -'PWc;'QuQQN-@qbUD1s.3(;<HWc%F\?LfhB\[`Va?mEG*kCj]io'jO&Otb-nGo8"'!ckmb
+ A0F6ooMMcn'gdQKuBXitQ_[$4?u'[YfD1n:s$`I<=m\_'4NK(@&0qitQ_[$4@!R8eq#Hs82
+ PKpX-PJ)f*[SMJ^i\ne2bJ'86SkiSuEQq='/pM"f;SJmD:`$<s?Rb>2*q:)"HLKGZU/N>pJ
+ T4H<I,*LgP8fJ5ThI\HmfU9Hm*Cf)Z\Fad4d<-@k[nNK!8<P68CYk`U8r'U_V6t(Y>(nm[q
+ !s]TupX-OK3l<>uea#p)TaJ0%YWu%'n//cEs*BEE+0?sHRr)!SCV$/9:^,QM=<K"MWMjIQQ
+ :1L-EEIM34G1LWd)4q".aX3m!^Z@TML%Jbl/CWA[%dEV15!I+km8N(,!T.[2A?o4MQa(ZBB
+ dinic2?"\R@kneqjTnXju7hk:4i5_?*$nXAULIo.siD2t8l]ErOU'#)02/"6AVpd"]C=!Ra
+ V`ISs*C)B(Mka3fbpB-&`TCp>$15?ECo'G^t9;(CkQcX/+B7k8=)<Z2Z1=5Vo0;rpM"JuMn
+ NfmN`p=<Kk@W2OA'mK^\jBLPSc`nYDXDoLS_4iIX`JuDFgbLI9_qlAG(!^3t_O"T!.9D"#J
+ HIsN,Ud>TleGZT:^/_c=%Ge'SpC.'l)K]ST:2eNo*W4fjYX!^i;c1joIlUdsNjb1d3RaY*i
+ @u`EWt@>M8qrOEn9V_,pEpXFosd=O-62,F4oD%gDu7ljgXO5+C6e_\!"d\9<7LO3e)SEjBM
+ 7HtKa*/^I,?)2Xq<3.SJ&Y6Fct>drHRP8Sl<shac?o3;11o&4c_]\9Y?=mgq'rJCpJc`8&U
+ 0@!i/-n[%:>+$O"Ze.dC0FXM8utXU/jigG-fXZJEm@`.p7&ihl/.7U@Mp&2<!C;,;@YQ@'S
+ ;\TiA-_.:0RLPX8n"@@b/P,H?!=9,gQ<Loa*6ScWeB[QLi`<MO.5`ZX%h=/1@:+anh?-4[O
+ "bNYElMP1'UkD)*A[J;-HEGk>$f@G0%rh=!,sB2[]M_iSG_W8tVlD3YT#9D;J(J4`iDufPL
+ _"J"flc,ke\"d^+F*<U;,9JDJuj*0F6WI)KAguNh^:V*iENd5fCn;='GeKr@T_r<kg3f//6
+ [.jA?!CabLbcYH'01/d]Cn;/si685[moqU7Uc,kDO!#U;jceB(IU%T-?JCiBnFRE"DM3@=d
+ ?>V4sR=rcY/`(@<T!3SRrU?.JnlLH5:pMB:\M?7@E:4I]@#oABOO[WiJQ]):-,KDAQJLPsK
+ "pe$P6iE9J9/.aoc;c1jscV+mgU0T_$Q5e&W>Wj7K>HrEKUX$r)D7Ke'"VSYL<RgWJYDj<9
+ U0T@1)frKW4NXa,G8D@fn:U5f[>OW*0CqeEJ`->S(%j]gpaGYZ>'KtRBa,gMMB3m(gYiBT0
+ :/?95U(GX76R:Alj3,(>'K)P!sU!Q=H=/gm1<?qH[DAd$4@!_=&d\58.Vq?=&ZB@Cs'0L<:
+ L8*6-bfijDLMj;nf!2AmKi8RbB8BDdHUR<O1[hiM+KML#KfAQ06@A4#X#/i67A9B:Rj<X^K
+ J>]U'sB=<N^>Y+hMC*Z?LL7-e$F2s,!6Gq8TcmO:?\+f/>>($[L`Dagkk0/@gl2Jp*0Q<Wm
+ &7HoA$q7-MK(B6rp4WjI7@.>ANU#aH9#356Z*[Er4+m":sjtNtT&'oFgU+L/?*;R,G+;Wj3
+ ;:!t("3#'>L<A;*"r":5Nu"AZ&5fG(43VL!bLtiY;$m[VMl0P(Z[@%JnJ]!4mb0.s3SC4W`
+ MYj]k'X^-hY'MMeSC_hBIPJD.,Rs#cbbBa`G=[pd,t7m9V,d=6h_38q!_2Vmo9+/SAW5#-s
+ c-578%'?5hVm+SiLk<bLb)](QZcu(mUH[&Fl/!cU='lN-(H=bGVF(5(g]R6'J<05?"IlM5S
+ ]Hf8CC`9ZmK#<da8O1Z`qe_n6cRT,qb#6t(XWPcd&3P:#r::SCGHX4N``bQcllI.G/W2fp^
+ .H-L\M;If)T9<VG)$Ns1]lMO>JhFftbmO?dO`F*Lgf6tSm5MT;C1G(/EW*!Ksj[rs45l&-O
+ HKNN?,Mk`\>WOUk?ufT'pPI4G\>huq3aXL:RCgi2bE]pGJb$1*H58*t-f./`gaHHC0KJatp
+ f<tsQdEU!M/o!8Mhbr#6<;rlV6E3=4fP0I'e9pgqljq+DQPjeT=8oDnI!&X=*6`_GqIZ)V7
+ C#em?]>W"Tmf?.dC0O4u%7B[4?Q7WkerM%q4u>^^[]_]>M;DDOXa8[DmpP.YS$--&0+O+8X
+ =qnHc_knu;Eh;m]L7(N0>UZa3o2`;+E[$4@!of2U8knA>S-Cn3ZD]X6JT5"/6QnQ'I8$3(A
+ bd3BXn[6/)nCTg_F$O"V=<RgW^:F>#k@IM_$\06'sacl(+Fa*@(^nT"uOMe`&Is"pfZ]j5E
+ 'Ga4^O/o5BF.XBp*^R7Mn"4JO:9Wj^i_b4XeN1l^?EGiHTWg]NI9o?*?i&2!nH-;hau^+N9
+ [mN7;S3CAU0_Kpl1%O>H@*3@AW+_X-nGpWO/f/AM'p<!(XR$,=&td7eRk@!?3`I>U/R!BBS
+ SONQ07KdQ_:QJn3*SASfdl<U3H^p(2DNe[saFU>eR+`M?a1mPrW/';0lV^CWM,E.YS$-AV5
+ kdIeL+IiW\s>GXra3f#rB$GTbq_293ld$MS)CYZuds!l;?N76Ss]5?g7"*k7[5TYY_%d\Fc
+ #!Fp8`S($3mgEtdg^=oVh.>7p,A\O%Jp?[&i4F\CXcUkG\gS?XD9*#.>3k[e>Fa+"8rVU+8
+ ?_nGP#aD;<l8`k$V$/_?'AAuNP0$q52fp_C=<LpNQ:kID51eOj.>7p,AXnX(8kV>E-((op7
+ qS6L=Zo0c(Pl.H*p>;]7Jisn\Qja'!l6fp76Pd9hL(2JhY!V^#*UrPNt^scic7ulFI:[m*K
+ qCZY4_]=8n!99l)d!2h7KSALX.d8R;"Qt)g%s%2%ZMeh8S>nM6NJ9Q:nkY.4c#Y>u^!"jN)
+ 5uiU]t+Sns%)HX6@X%gQPUZE[Zoau`LnrM-S^E-tGM4YpaE'!Imn/tJAF0QJi3@H-Z>/2$0
+ 1%eElpjpY%O92J_5i4c4cT%0kHLf_nq2bKDT9\hfoSgW4ehf[c,@9s=b9kgaHr)a#S"bPbf
+ <Rd3@&(\9iG^c++hHRR\GX9]5>IplQjV$abhHtJLQ0@Ot_?.QY8OYeaYa"p2MI)]c0Rg4jI
+ c:sU]eo%3$d0iR]Nd'T@=`r5UnXI<rGIqn<:S](;tCI<DlFd$h@\O18uWm"0X#'.k(pc-.4
+ c#Ufdc:?^]-UaWT(=TQ^/Rai*8DgUf4*gJdu&5"!$I2k`r,!9cbsSTWg\sh5@!=$Q"t;Ti8
+ Sm/C!!3d9mI9l5nqB_>Da7=>!HX]ghp28R[1#l)m'3?+Qi?Zttns.KYM%:H'-0'mcNt:iN=
+ %SWWAg\O;k,EX<'\"PI._W+Wu#*mu9%7ci<W#,A\1NtQ@<g4aXkGs0;Y?7Xl=+T?,OYX%]*
+ 8OYdXf++#d.5L0<=.'NYAt:8T(::?N>iPDo+b0G#LVa)nnHfs'4SIDul&d/74BHU5S%KJ?X
+ g>shK!6`\a'i9^G"l\^[9id-pua%V&.kmBeFUR":6HYI@P/XjAMBFnojI="_"i#-goV7U.V
+ 4r>FGRd8!ARkK4jQ>W>C**$\X("O?nrP_1#dUQEm6YI:lfU@"+..XOn^H2,8:n!LqtS+3H]
+ ))oLKQm?a[mi3&g+2n]E,`$[S2X]O!_[gV6ig=<Im@<@U^cI_'VmD.rFV\*R[Z6C[Oi:,\K
+ c<P#YaAWg)jmdDj5KeI*9A%>!_-nGo40mo(-`f/u)DVa-kNm9?rd:IX&G\E7=XmYU2YIWC8
+ 2H6jpJ)T3&Ka!(d,:_u(o[2XGo0c^2q'?5D3a[p]DbUjZ]"M?XSAW5#-s\>%Wp:@Q"bL-eM
+ L,:$mjCcS'4HkXlc]QU^!7<F:/VD_P[Zf(@9tUZRP4Z'Mh](M5qkSn4Zdi^C7C3A_S+V,Zh
+ g-Y.it7W;Q^6db\QbJkrr<(d3/i)"@4[Uoj#.sF?BNk<do(\_774PLP\MKH/-c[*9K,.kN'
+ Y:FlG'_(f4>MpRY>]:VV>rd_j!>W\5nZR+Q\1Hq:/kV^EA3lc_K3b9^mQ<f"N=@RFQ)hg8<
+ k4j02dQQ@Kfcs4?%Ci7khDW:=\GV(h>93f^LckDKi/Dtlu<RgW"DiBf"-@Tr`h_]tr?)d^Z
+ #:SP:eFtuIlc$cU>qXMJ$4HXc3#2,al;od;LP]AC5!@e2FI22'iW2Yf=*6`_GqIg@h;#5#q
+ umiH$4HX`pQ`m#;:!q%rd!e9YaY+t#1GY8:MA.TiYb?f(7%U0Rs:t`E1c6eY^jJOY+hKg"1
+ j.Pikr\f>+.@@gM/L]CNL*W&ga>#SShlH7#B6V96?!,!rfVo76Mahck,G5i\)U]cS1aF'mc
+ NtN"SoWVj-BW%eEkq1Jdp;AV@o%r\dbF4`#1cT.=Or0Grj5odK>O-(`;+OMe`&Is"pfZYt'
+ bGE0NArYEDSM)X;\kJK$ne5`Y,)s5C+>s(]8UfqCkb\Q'.P6/$pU-fV1n?F??e<?;gHL"Ts
+ s+^!.:s5`km1TrkH+4.><D"*jjV$UEjRr4eM9A1f0$tgL$i&@A.dC0L\=`C1nY=Nb1CZg"\
+ K3:CVSpiU;iC9]LGuH&k8&riOIW#KJ<.rP;9sI$4&1*\psIXi#7X.KTcPH8VSqD?\O>\5RB
+ ;&?KKOqlI&sf7H<ttRTHCL^f<)('AqRcoB8G_"kU\9OfFtdV&fm^NJ7!_-76N(KWhNt)oGI
+ +tr10#\Ap]g2/RGA@X4?$IS`TP<Ef'+5>;";H$4HXZ)AVqhR*eQQLA4j'YrN$K]hAqOO&U'
+ Ul1>$?f&7'S<Ug^l(QTjV(@\@Z.dD<EWU\Cm`4s0Ag0hSVlZ(pT$7IFJ--D7o&V&op6X^"&
+ H<R:i(%pqJ(\A5B\u;lWS_9+.f9tX+$DIkjcs^75]Nh0u'Gg27O/o3,.kA<<EKg#'n[66If
+ `lNsRdM\4X2&_h>U'3Ea['5cd3/i)psNC^omHPP$X-d7*M#1\o;@';d)Y/=g-\M:+&[o#og
+ ;m^qk9&fr1e0(_FOX0$O;/2rdqDWL;F"@e5o(%^\m1H></7G;uc15F6^;\rN1CW=f&SWKig
+ 11/jE9A1&0pZVnGmpQ%$!JGlE122TA8~>
+Q
+Q Q
+showpage
+%%Trailer
+end
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-area-page_expected.pdf b/testfiles/cli_tests/testcases/export-area-page_expected.pdf
new file mode 100644
index 0000000..4a510aa
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-area-page_expected.pdf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-area-page_expected.png b/testfiles/cli_tests/testcases/export-area-page_expected.png
new file mode 100644
index 0000000..8bdc9e9
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-area-page_expected.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-area-page_expected.ps b/testfiles/cli_tests/testcases/export-area-page_expected.ps
new file mode 100644
index 0000000..ce348be
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-area-page_expected.ps
@@ -0,0 +1,482 @@
+%!PS-Adobe-3.0
+%%Creator: cairo 1.16.0 (https://cairographics.org)
+%%CreationDate: Thu Feb 27 23:59:28 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 3
+%%DocumentMedia: 120x105mm 340 298 0 () ()
+%%BoundingBox: 42 49 291 256
+%%EndComments
+%%BeginProlog
+/languagelevel where
+{ pop languagelevel } { 1 } ifelse
+3 lt { /Helvetica findfont 12 scalefont setfont 50 500 moveto
+ (This print job requires a PostScript Language Level 3 printer.) show
+ showpage quit } if
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+/cairo_set_page_size {
+ % Change paper size, but only if different from previous paper size otherwise
+ % duplex fails. PLRM specifies a tolerance of 5 pts when matching paper size
+ % so we use the same when checking if the size changes.
+ /setpagedevice where {
+ pop currentpagedevice
+ /PageSize known {
+ 2 copy
+ currentpagedevice /PageSize get aload pop
+ exch 4 1 roll
+ sub abs 5 gt
+ 3 1 roll
+ sub abs 5 gt
+ or
+ } {
+ true
+ } ifelse
+ {
+ 2 array astore
+ 2 dict begin
+ /PageSize exch def
+ /ImagingBBox null def
+ currentdict end
+ setpagedevice
+ } {
+ pop pop
+ } ifelse
+ } {
+ pop
+ } ifelse
+} def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageMedia: 120x105mm
+%%PageBoundingBox: 42 49 291 256
+341 298 cairo_set_page_size
+%%EndPageSetup
+q 42 49 249 207 rectclip
+1 0 0 -1 0 298 cm q
+0.254902 0.411765 0.882353 rg
+56.691 42.52 m 170.078 42.52 l 177.93 42.52 184.254 48.84 184.254 56.691
+ c 184.254 113.387 l 184.254 121.238 177.93 127.559 170.078 127.559 c 56.691
+ 127.559 l 48.84 127.559 42.52 121.238 42.52 113.387 c 42.52 56.691 l 42.52
+ 48.84 48.84 42.52 56.691 42.52 c h
+56.691 42.52 m f
+1 0 0 rg
+286.188 194.75 m 227.227 191.418 l 191.059 238.078 l 176.008 180.992 l
+120.438 161.023 l 170.098 129.078 l 171.922 70.074 l 217.66 107.414 l 274.359
+ 90.914 l 252.969 145.938 l h
+286.188 194.75 m f
+0.501961 0 0.501961 rg
+4.251969 w
+0 J
+0 j
+[] 0.0 d
+4 M q 1 0 0 1 0 0 cm
+286.188 194.75 m 227.227 191.418 l 191.059 238.078 l 176.008 180.992 l
+120.438 161.023 l 170.098 129.078 l 171.922 70.074 l 217.66 107.414 l 274.359
+ 90.914 l 252.969 145.938 l h
+286.188 194.75 m S Q
+0 0.501961 0 rg
+158.738 195.59 m 158.738 223.77 135.895 246.613 107.715 246.613 c 79.535
+ 246.613 56.691 223.77 56.691 195.59 c 56.691 167.41 79.535 144.566 107.715
+ 144.566 c 135.895 144.566 158.738 167.41 158.738 195.59 c h
+158.738 195.59 m f
+0 g
+2.834646 w
+q 1 0 0 1 0 0 cm
+158.738 195.59 m 158.738 223.77 135.895 246.613 107.715 246.613 c 79.535
+ 246.613 56.691 223.77 56.691 195.59 c 56.691 167.41 79.535 144.566 107.715
+ 144.566 c 135.895 144.566 158.738 167.41 158.738 195.59 c h
+158.738 195.59 m S Q
+Q q
+82 59 151 151 re W n
+q
+82 59 151 151 re W n
+% Fallback Image: x=82 y=59 w=151 h=151 res=300ppi size=1190700
+[ 151.2 0 0 -151.2 82 210.2 ] concat
+/cairo_ascii85_file currentfile /ASCII85Decode filter def
+/DeviceRGB setcolorspace
+<<
+ /ImageType 1
+ /Width 630
+ /Height 630
+ /Interpolate false
+ /BitsPerComponent 8
+ /Decode [ 0 1 0 1 0 1 ]
+ /DataSource cairo_ascii85_file /FlateDecode filter
+ /ImageMatrix [ 630 0 0 -630 0 630 ]
+>>
+cairo_image
+ Gb"0W#BX56H/SW;NF2j8'mgT%fW-Fo?>[`Kjj-sYf3!(&ekM#QR6&W*m:?0DCN%d!log@ng
+ IYG3j4uA7=i;XaU*TF6OePBG!['c?"A'.-5h*V/*(Vb8Js_u#^@shccMZSk4#tuX3"&?"T0
+ Fu@k.*"V<4i84P0\\XFfcXDRn*BF%KI\$/B@.ak%je-g\s&182_86Sn`+j#&qE/[_f3IqRJ
+ QB=p4oJk'H^?f%/)r$d'kKJ7M,Xi>N%2+K84?\;%j%888hSY+`'"OrB%.CFC`aUdeeO)@?i
+ pWDQd-L;F!_C(qGdTs+N[)MNl\-nKnE7Ub6o$4@uU;-:>__?*8C'GdjFYQ0h)KnX:j/:aNc
+ @Gr+3$QuEG=La+&!_@&?(9X@8!0S7SKki(fJ>>YCi>N%2+K84?\;%j%888hSY+`'"OrB%.C
+ FC`aUdeeO)@?ipWDQd-L;F!_C(qGdTs+N[)MNl\-nG@6NZD5HjR?XK+!WBP(9X@8!3A3NqY
+ YD=-\hZ%peMMa$4GH^=9-pBfK\hp^-CtdIBsEr[8QqcM$8TeCFC_6CJMT%\#llCDss-<nAK
+ A;+"8ZH$eJ3W!7p5D3iBZq@IJ$)I/)n<nXlh&'GdjFYQ1cHff/fR_VV_DiirRn`IG*e:ft5
+ ^_?-t(X]K,J^T\:t?^iSFZeZ"CKki(f!,LG;\`:P]S_c1$@#c<nqa=lO)@?kfRT"QGhQ*kh
+ B3r_EFF9MO&,AC'(9X@8!3A2gB1=8&%6M1=dZMG*`.-&o;-:>__?-t(o^:t7(Uf=,<`2ar\
+ ;%j%Yib=YdpabBfVt3M*q75)Z4n(WTs+NuH$e/m(^h9c8MSa+.k)kt>XMp#=EA(eG3jqUa[
+ umI$eJ3W!7m[n0dX!>[]Q>broO<75;S"ICFC_6CYm`!3UXbuqkSbq;GRXmY](&*"^oS:gC4
+ PfmRiA#/%knM.4e^k@0'JOG3_QV_M4W71$-ceV&oZ\'GdjFYQ1abkH(oN+tG+&*qFD!"bhY
+ G-nG?[%YP5U/?eHRHr9ruKki(f!':\OXL)![EuqK[TWiAEK`tl)_36B#(A@IVZ9,a%8O6eD
+ )@?kfNc5Hp(A;!D-,kukMd?:d.4e^k@0,##kkHq!KtYamPnXoJY+`'"lo9*'W@N[^LodCkj
+ i=7&E".L)@)q6$/f$A3oPR`2;G^K*L;F"rj2d?/Xqp_9A0o3'gFF^$/6kPN!SDDQn7&j7)g
+ 6DjUn\/:[.n-0=La+&!jL_KTg^":[Z/7k'GM=cTs+O`#iAg3<nOWTHgRo1TWiAEK`tl)Ylb
+ BI/F9q%.k.b7*FIsua/6([%>=sna72)1n@5^cDEfRXnr^hM<`2ar\;%j%YiGKt$bS.ZD-JB
+ Bs1^JkYph5/6X^#JET6Ze151]A?:dFki>N%2^lB**k6Vn^m-S*-$s$8&=La+&!jL_GCNF%p
+ )tqR;abPp0M<i3ZY](&*"^m*g@F+7:Fs@sp8dYn5f2,QLG3$:s`up0I]$"+0chbn&lmjM5/
+ 6kPN!SEO@Dmhq=)g+WN<n05]:QMt(fi*6pa0Lk[_SS/E(4[ED]e[d(K2"Bi>hH9,SQVo3fS
+ s\u.k)kt>XMp#fQ$3<R0F7>d:mHO%;XO/V0]Z-aI_3GWUlAh'GdjFYQ4#L[<I?k!CJoE]s(
+ r(s(@$,A2ggW[SAsTqYY^=fG#+(Ts+O`35&t962p;rCV)(P4',4^gY'(0brY>p#B.d`=La+
+ &!jL_WmV22l0:^E"$QE-E]B/?uFsD?\-,k`&n,'@=/6kPN!SHr9Y,Uq<02IYD=(0"8FF(25
+ BfE?dCk)IW$470m:fm^@SCnd(@Dd;gSJ4!Q[VT]H^<^\-cY]4tm(gL3+.L-a2&$gV*G8^$N
+ ]2-8H>MbuD<'nR$470m:fm^@]X)*?]U::uULP`h=H2`WI3lks4jsr(XJE(>%>=sna#i$'a4
+ n,qH;oiW%;X<r_F:mPg$jFaQ1EJREu9ak_?-t%m']LU#-M'hN4lGJXea$bBlZqe$L(EHKLB
+ 5LfPtfp`-CTKU.<.%fSGbBH@Xhmk&-`7/UroT1e-JuYQ4#D8D=;24T<4SG!l#0^67L(n[U6
+ r]I+)F-nG?+%cj]J.dSjq_R?SB.'UCJU?(tbc1N$Oa"e83K`tl#_36AXMH6U0)Afj4SWslm
+ ];OGqc=`bW"bd+f_8-f.9n"gtLH$`!CZJ%fiVt-G,Fc;^@TCM_l/@R(5[a[qE9Of'@E\=OL
+ P9Sd?2(`Gfs$@_$X,pW,8u,&@0'JLkkHocMI"ecX;&*#ZB?Ehp.C4jK/8u9H!;'(-nG?+%N
+ eniEhB98g+$o@UC/S$MI)n.E-&/RTLM1D=9'*V3O6Sr?^84;`PRA;kgTudR,,jGm?gjOc:S
+ E\TWeDI3-&Q76+jAcIWc)<@=KTBmCs(">:;Y;&NCI?J-AFR0/CW"2;Ql<_VS=qZ`L@+-p'>
+ BPn]#ZK`tl#d=VrJ2!_a@j&0='l?-^4*c!^?`NN!SXBR&8[h0;g(4ZS/%PBRD<gE+??=@'o
+ =\4ajF)p*e<RRW]JO!WE=E+2R!sZ05f"G<?Co_>%.;kA$Q(Hk%57.e6K&^c%0M'_gS`P$'g
+ OsL2F0.niX?:,:.k/go$4FY$'5s6P[RU#\VQaL:[oZJ[IT!s#1R]7b8V)5^YQ-46nbRWc6I
+ kAhSe\^,p.C2NlROCkpLZ?`Z]YA1U9FVK34XO16([@`2_PQn)HfXO;;Z"FZ?\MYh7U+"HC2
+ U*-nG?+:;/X<TM;n;=-!aDVGd/t)qhHQ1CL#A3W+WK!jEg1eN!h6Wn3X_Kl)gNUr]mUP2bK
+ KfrV!b*@NH=$Zbsc2Fp`@cB'MllanYr`n6:;q,aIOf!:G?D($q8$f62@Q9gS#FLkjg_jY^O
+ M&p(aZF<n5<3hYgRjflg.o*c?di_-=^r[A#26rC>>S"7^g"`\CY88dV*q'Y1@0'JLC@;R`'
+ "-q&n\V_kXJE)u[O`pXOrf;\@);(jdg[30^4-i8CV)X`c&Iu&:%dhKk<,P:=9',,hJH.mnZ
+ '<!UWti;I3lk(m/!_>d\@j--nG?+hVL+AhXomnn`!-RRp.Blb#h5tKIAAO$dS+/)K0ijof2
+ K5hTJpTcYd:>793<>6CXc`5.j9l!Ea='V<iGb;kV0gQa@EgPn`9G7eYj#fe#q"UduO@(4ZR
+ $G"d`O2hcR:GXadX`_),_I8a9?OYeI;!"7Z:4ur&^kgAB!NN--ZQ0R=O,FcGhEp]-Wd_d.J
+ TWeDI34j$*]V-e*,K0a'Mi^]5d"C[S2L"K*C\`mak<I`k@0'JLG3d(k.oW04Lt=G+0!1oT[
+ IDkI"@cf#=CBLB;E\9tRrNF^,t2nR#3c]7,nq,F;[86gHU//UO(Q%nFF(25BfE=$];Mlr,!
+ f(.!)=QY=_U]uMu#X-@N#O:e2#c/.<;ml]MZ7>3X;mL_?+]Ai5l]<=h1>`M!*2'.k)#3$4F
+ YK6RS0="Jn6%Kak*14fki_\8Z*s.k)):Ka$DaU#X<)C3!Gf'!J)uDQKW^Ho\gtk$%"+^^:D
+ V^o>5lQC@_L<"B[=UiUFTUr[ms;GG+;krQU?$4FYK_ag%1FQA<#+PoU=696FjB<`CY<`11G
+ $4FYK,=`Lg7&3`T(QY=[Y,nEYa\%FUUTa`g3c]J-5sDI:Ic=M;X%mjhYeH:[F]MBC(PN%Ne
+ BpVo)RSP9mN^RVlWdUbEU3I9kub_$Ka$DaZ%FBWXjXo%KBS&O::[0UfSqF4.>!Dn!c[GWJV
+ =<Y\=n8+f?c\4F^MbC%BDJ%TJ\!$%E+:D!c[H"Rh$A.U0;2dGL0>I\jsL%Pn^&"B/V^>[VS
+ c:=9)):Iq+$q/8jaEiXp9:KC2X58)'"S.k-(&_?+]A`9["(8l;Y$M\j_B/NWQV*bI@:GrmA
+ 'Pd?Ois!TDuVbZTNB97t]j="gI.k-VpKa$Daak,Yn1uahh.NL]K<U24%.k/W$c"UnS8HHOo
+ @0&?QoBI<J3n3Ihn`kBNr0N9B4k=L]Mt^"c]!Yj*-0Pno"lNA*^=`^)B[C&'PNP(f<`8#IS
+ 6U8\Q#o`>2[Tr.WOMTG1?_&gc4KH8#e*i4Pd$=fD^[.:VquaQUp/*)>s*:^,#8QM);27bS9
+ _33n.Q-:^o:7ZB,]j(7Al#U:6lhL.4uU1e^DNOT6u7-!c[GShAHML#jE&n`eeK\)>7-0m*0
+ FHAgN-Q:fm]mS_cNc+9^\Ho82tMr'c%+h8naGQuqrJEp:;g_?+]Am9S3#9gGkQ+J(JuWRUn
+ 67Q.@-bQ@%I,WQ2>^o=ee9oXAlkNd-I;=g8lSX%5fMM+<)!B>46B6LqO^OX1EUg@[Ll7mH,
+ HiDQ0NOA)J>EbR*/-)Tnm$JFSfFN0o4D4ZCXOBf6#eQ54]oo+QPnXoJ5k)Fm@rf4<^IXD$D
+ jbNtF@s(bCcAZ1>:3`1=tEsLA4'c+3-BCkgk@np7u<#FkH=,7elPaS4uJst'GM<JJ=PFR.G
+ dp/X@J-qbl?sM[sU?aDIcDsR*emZP6*-`J7(@GF^i")^/U.)moLVglAY>c=&SD,^/_RE\j"
+ 5d_?+]!@)u^%_o1RM'Q1%K-t=P,Ka$CM#BEi`9_`Q^`6/lH=*6a>OZaL`8J);HP0Ql*UTa_
+ <3I#Ru_/(dR6cGN030BI?QCek1naPn2-<=#@=9*elnAK@"_1"rd&J%nR\Zq<lB\uci;G^Hq
+ 5'6#`14uQT=Uu:O<BrG-Zr0'BDIi?_:9M:9;S#ATLVa+3jNt0."s^fJ%hqf>E.rC+KcXb`Z
+ FO0e?HZ\$SYQBU@0&?.pui?CH%&0m&FWTF<U&AN[\"71Shi%L_IoJh/-'$:El=cXQ+F_TTb
+ ;lK.k-qQ2b:D,0i1sPNZ:'lfO0YFec!GXPSfj2HEeN*2mNV!VpneMOj6l,TWeD93I5A$_$n
+ )Uqhhj3@>%lf8WgW%@eGFddY/#h$4FY3UB_kt(uSE*O*I$)$nEe)LVa+3jOf$;#,D'MmOb$
+ +`)EFX=C4X>iQs,V;G^Hq,*'G6JAL$Delguq8k<j$UaUUN]A_mUkun6&Ka$DUnM_4#%.0Oq
+ Vg+.5PoD5&nm7ba;mAYM=FihJ!B>,Zp%\e*]12N+02VcYl'*]EJDJ;PP(LOPnCWTro*iW]X
+ mRg5TnMUK8rBq7F'>#X>F*,-nA<<!jr+Zn>Ip;KJ<E3hmT*_D-,o2OUnFpl]O4dmHCZKkT-
+ WCD8r?j)=%i;2s!TDu7rhq,@kaVrF;OK>,`e$^\YP#5NGS$kM)S%n8ghjG-nG?gEq[a9R*U
+ aQ6bGM&k.9tr2mQj,V\c^p`'`Zk'Gc3DDnRpL?ns\*FRe?9Tc2`3htB["dXs?F)gPYR_?-t
+ 1eB9WW`cAC\'@0BnC=$7DoOtk!AL.M6"lP,6HY42>jH*+KDb\YpdA[rS0Y<IlgG,\)S<Xd&
+ (4],WpImL_aK6.W(b7$`/-*FH]mEr<UKC<RmL*p5elh]J;B00m"bd+fa4n5'`r-iF4^L6'B
+ N))j$hRQ5OY)$!H=Pbll+6ZkKa$DUR@S=62IDLY0+6LEZ'$;\fAsMYX/#hFNktj:jNL9MnW
+ )Kn4]XZq\<C,I.8#bN,mAu+i5duAVSkQCMtC]QVVl?S-O&.]Cfku.]HgfT6"'dRo$6sNnP7
+ p3HB`YKclk>K;Hu1@X/.O25PoQ7Yg!X%'Gc4eZeHhG4*$8P8F_r&?5ds@rKS(DZQ#p.CF)D
+ JrVFtrm!dpP(Ok$6LD1MSR<)R[`]J*3oEauITG&n<oqK1HDIi?^9Y,L`\TmmS'aefXY?H!!
+ Io4<,\(@k^Gj)W.;pU.p=9)qAL2En2/fkTm<N5lMNGM_9R0DRj['$rJ_p,LKjZ2o>G5^*,&
+ h#:2-P#4=1`3P8oJ#f_+;WKg/!#r0pWuaM2LN,?H=5mGXW@F5.4bHH%N6(j9FhkV#@^Z91C
+ Njm]9,8VlEnRb5d$mgs4s@Ab0#Uj$OV&Irs1cA"lR>8P:I%Glm4e_4<0/_ZVE$u,O@lXJ*5
+ n(b.t*S#lf=GWd)Y)_m=En`UTo0$4FY+6cYN49\4;J=M,Q.91YtF>@0t,FlN(g`T$R'QVbl
+ &gUF4r()4FppXV),$c';?a-RZ;T$)GAGVWYLZBE)qgTl948^UeXiC<([MaqFCm>1XTBG;54
+ 7>HpU24-K@HD#L6@&?\CbLbe@7^hB;),9ei-,UYT>[On9o%_+RYJpI5-jg?t,tEtH0pk(g]
+ "`J=@'!4<S@?4q@Sc^"WmtG5Y"$-:F6UWu]U,/FY#8@%ef?cVfO48Tec!EB!@QZ+8Q"Wh8r
+ C&qfn=:6F5pVn6=Bn>k31bm#*\5=mOR-"3#DR`P4^;)S66C1D6dKu;"LQQneJX#6H&@F)[Z
+ tg%uq]YcsZELf6tU'!%T6D.4d\4i8W_K/PUCh;Dct-]3`"WoYA\\^1BZ?!VjL9K*r,8:R1?
+ "Sd*j,`_),_I8c,,;Z(5X`#eAb=",[#l]*!1^gQ`gd(RB0!/m:g>,`Q\2c'62-\k&,fBqkj
+ V%>Q!AOBl,pqcJZ(Doi;C7QXl"kXR<.2@/jh*U4@STl`9;.j;HD6CL^WoD5(]AKofYQ4UZp
+ _tQ6XjK;MMZ>]G[#uW_nn4SCC2U8a'S=1'n?!A-cSq4R[CWtp$fDZ,<`7^VJ*3S."+e;jE8
+ osXGm9H[4G:[@!;Al^;N.nc/%<:*lA#=ckVrNEI;Zf'9ORr=3;M95K?81-G87:]>iistMP(
+ 74_I(_YWc_A@d&sKCKi(qP#'+%@@k`N"Ff'BbhuPc;;YX:T*dpHfW7B]k3p#1I!^d]V"^il
+ ohe[pJe>P1ALHiFKUO-*M8`:cM5M?9(X@i5)dcheMnL6!h#Msc<QGHhkGT7km\Zq;a<qq_S
+ Xp<SBSq"=Wm1=sTi4u=YB,V2WAt:84p>I;V<`4/n=&0,%&[(DJ%D#36IrG`&^k#F29ti[9U
+ /T$D5!0Uph7KBdLkd`lD7?Rr3m6SQ947i\E,*^@K;fUIGAB`SDQKXQ6a7\8WVb:/r6RNZ"l
+ R2\h'RgjTCWUnVn3\r?:eQo^)g@QLYgCkXSn05_?+]7m9O`#K?_jY`.pHc2<jaBgFF]mhQF
+ H\*Gl,k^k&t=:,TO[&>/G'(Xab!oFS"%7er@0/JJtN"(tr_Nd%uJkcs.-*BrHP8r>quVEtg
+ iZ_\59U9FV;33sjtLKoV4SG6KIJ%31TU?u/Cf6(GODdcRH!"QZJI<)t6Y=O]iTBbp2#2LU'
+ Wj$ra)UhRi=&E'"%\2>jpB*I7ognV8%_l`*W>G1\a1&,DQfo#_F[)e]ZE],#!c[(6r6H'-T
+ s3aJiC]"$BA*&tP6lpHi./X+/ChZ"7j%XY*6K+";V5+Zi@50B^siC[_HtU,j@:;>XBoNTkc
+ sln>qOMm?2p4M/JJtNW<5.2%17GZh^:Ybi,=aC'I2@>1EAT(2%V/e!)j+uXtV8?p`;hPoYL
+ :USeMa'gXQd2=9,3.Slfl,fQ',CIe3X^TWeDi*BLpYTWjdU+SjCiG_!X40Qm0C;M2hek^i<
+ "&+BRQ"^pWbd3T!N]qPbQO$:>]'Gc4u,=`Lg'Xgp6QQLX+#:M,';b%<N1+;Khh+rLl!?Ro<
+ !)+CroUnKYYNnCCUCZhoK`tlLfKk_<<pAK3UCZhoK`tlLfR\_\KtX1,acAX$!u)N$WT.n\?
+ r5KjSL$Ms!"Vn.!2bdkZ,!$:2g%pld0FoH_?)E6iq<*OKrrlgj@:<M!=B4MeB.[\$jp4:]?
+ ?7!^]P8dJ>na9br$p9/O)HT)mG.8!\fo2l5YGnf'?$*d0FoH_?)EVZZ!9<@2j;)q9]FlJAM
+ @Ap"$i[#)-pB)=jO5n,S.G@-Xk\9a]XVe)a(AhuO/m^so?s1CX]GU/U/D+5d/,$c(8k]e^'
+ ()q$2+UCZhoK`tlLo^:rJKpA9WJ2\l-!)mh)RMKgJF7f\3\HdA3_?)Fap9(Cb(,fN\Q_/VT
+ 2^g3W;eL9n?.]3A:4DU3hOq(Q9V$GX45Rgil7qjN&+BdW"^pVd*L#M8Clobj5PG.r:fm^pc
+ >J]TYik[-q7-`TK>I[DF`uN8GRI`W8'9.@^`*t'J>mTo-cTBC;E[?qmK)1;YQ.A9S(FkPHC
+ 4Wc"2$p.!>mhYZdPMN(\N4IEt@aEK`tlFi5l]<Ke:b@jl[]C%13KYaMAP5%17GR&!<K2J2\
+ l-!$d8Z0/G"m\K3:dpAf,.=9(f#+#>?^KcVX,pAf,.=9(f#+'iVXfIM8ur7hCe6"'d23HAY
+ e6")9dJ,-V[G_<j70QQ7D@Y;O(ec"Rl*oIJ7$c'j26]2?[HVO7"T6l)I-nG?C7h7ds:tR8R
+ 7t0k,hOq(QN#Xp`d%IU%FDpuc#C(m<!\eZmbm+Xa>HP6mk8rOj$4FYjnM_4#'_\El.6mP,D
+ bsO97S^T[hk722\qg4="2$p.!>mieqE0DIf';\oEt@aEK`tlFjD/fG$<sF?aj3,c)\a'<OW
+ :.5f'?$*\HdA3_?)ESoBI<J_D^U1noG?2#)*4gjK3j:%1:jg1Z[^?i!g#$^rT``bVCS_P>7
+ I:H[n%'/-$om?Ckk-fOg[$5PG.r:fm]e:@4T\:tUk)Oe6uN2^g3W,H(902^iJmKgkR*YQ0W
+ PhE<II]]SJcnj<rW%#"jmjR$5Oj"5)=*\TbM!7+JD!(P9#N4Z=iHCXo["$AqZ!>mig;Kf^,
+ /^S"hLE>@k$4FYjR2o5c/SpOj/O/k-cVR^D7c)f^3SqWEf;%D^*oInC$c'j:D3Ns2Cp>$15
+ P+s%:fm]ec>K0iDdc<2#l1/!G_X':0G8;1_b0T@q4S%<L;F"Jhgq;_#)0a`LWL&i^bZZ?5l
+ ;*1't-dHPok=&B.,<]p`2K-_P"-"oFUhl#C)!?!f5a-j3%l=>HS4S%m@]a'G`t3%NSng:tW
+ s;OSmH-4@<NX`:*3#V$;XUci`Bb*oInC$QsnRO"N.8Co&0q5P+s%:fm_f4*"l:6"-t^Ie#i
+ :n-k!S&AKHF(E!q\RdM[joDm36=9.kQHK[YI_HtiBjG+f7)%$`oI`Dqe'XgX.Q61F'BAP0*
+ =9!&5:>QlhLrJ,h9F?,6pQ?VEJ7m^hChmflC[5R4Ldb=Ka_-I2FdDsP"$AqZ!Q:t:DLKVG1
+ COUpUO1,o4hrif(-k"!GHZ9RidUTIRp+WRIcpg(Ts+N+do`rZoZ`e9D/E4L]?(RH^bZZ?5Z
+ FH%fCBa>DO]i8Y@4n^=8ANpG_X':kgZ_MX3^7(k-1t)l?0!%ciZBt@0,.N9@Ln<dgL3*3k]
+ ZQf;Q=&%m@]a'G`r(C+E=#m$V=`X_jMK])9nW4@<NXr4/-5NTE1lm'a;rZVH]+H@TWV/:]h
+ ;/6e^\F0Za1%;X@jnj<rW%#"ird<'1&`?V]pC29<#:6?WEJ7g8]ORtl5:"P:=)t/oF7@kRH
+ K6<8t-nLJ8)J[hag`mE]"[.gj3$B/:=<M<\g*>"%Hp*MQkob7K=<K'KW`i^+g2pD7?-Dk:5
+ !4e"7U^ine'bn6if),F&pGkU@qp,sq=t69U9Hm<WN09(pi<gD\$"h=0]1"RDPOF$Ec>rX7(
+ `&:1qW^;5G>;H'"D)Q]O7)f'o7(VeJ#+L!IW]<pbNU"88-9@)r09p;5-Q=YgOW')heG.=&n
+ c'QhO$E\>oG6EpF'SLr'4MW1dg?adY(k]ldnI":t`j"VQbAHR,V5Du&o,p`+tk&dWh0!$@o
+ =5XOsJO$nO?HW0['T3=KV:fnk8fHUFl&VUC?XLW>'gNt3'fHUEaTs-dieWd6cfZbHD^oo.;
+ &3q<'OXRrW7j:l\7KL=o@=b)^;7\UQ-2'k0^.2U_i+T2t=*e8OX1,6o4VAY'UBsHk'G^rWp
+ .,176X^#QX1tq%r+jbd]ldnI"CN=*"al7l&jUtE=MV-*Y-LP!#Vl_ppl@@\,\hIGq%3`T0F
+ A3_%!s7M"@6Gshe91M:j-r@X1,7uOT<$]@0(jcNs(PX-nHL#4VC"hU9K0@d,sYJMfZA67fl
+ 4L'G_7s*XNt*;-:VTm#_:2!egfj<HiRcd48.$]ldnI"Q0Y%(@[2/HR-V87fr/f$4D*14VC$
+ .Ts0'j4P1lu?B;+!Ns+B"'GbX$HR+.&7:?4@Gd']47#u$2?0'$3J.>FY^cF.B8;q2B"Q4J<
+ (9iZZ&cgcW@0'#)RQ"9=>Bk%dMf!$d":ugm(9i?Q&cgdL_?/#i1i9<RmHt<Lp.,176t$*$g
+ ts>r5C4Z+TVAZ@OllKL(QZjbp".(IOT9um_?08>;7\T6k@?Mi?.gG'"<W=$io/-'M$EZE!"
+ "1m!\n/F"G"!Y=<Ih@2qA]3TC2!HlNKGQ8Z0bX"9>l,=J/TdQ;*Y-(?TLUn^`sg&jTp#Ye[
+ qtYXd]9*l87P+>H_qTHbNEOXOb$"H_7a/9$rFjic=%!Pg,k)o#3_o<iB"_TL'R'GRVWbK3n
+ 5o]VIEB:"DRqng48D?&)=g(!%+.cSu`^Z!Ut\ZYp833l&KX8K@9^hjdoF`Tpf`mW;c-VlAN
+ ]D$m>T:c(gT2#:#%jlbc)e2)-a^H,H%%9Dqc^6HCqtA8?bK*4OnXlnao^I?RnDMM>;mM]?l
+ 1Z!D1$%qu+8ODbj+%!e.DPlTN#,ikpVn/1_KES=Fe/:lMBmDPCcQhf!*GFqKalpCL2StWG[
+ \mYIPgl*a%!4<4^dX/mV-A^h/q[fAu:<4T(#Mjrn5.Q@JK!os0NRqXqq'#QLNNO!PlFf/Sn
+ G;qYg:5r;"[:4oY5TjidO^*o/tBh(L(6p[mmE5%@'c%tAiF1XA`#B/b4UgYBG5r1@q[M!U8
+ FgG-g`[^&C)85b:BfPkg;$<Fn9ik-jImXP#jTi7h!V5=I)Vm2bNN0Nh(n^0j^LMq=QJ*K[n
+ eqj>V#U8t5!,tes$[_mNWdji2=^V1!kW*^1GO:hK*Bi@;FD0gVNJ%5mD^Oo,[u_,'7HN&@M
+ (JI<*.X8='t1)UdubkJIe&]hO+$B/@DfZK2pK)bI"se5I\/!Z)"WMIbUG(&p@eI;,6>Rf^+
+ \iT>8qeR>4Cmb'3;3uhkN]1FLqU"`f00]kbH&qnK4[jk,o9q(FlJ^Bk)G64BL9@Uds&$DXs
+ ,P!-*H>Cgl;F?i4C0_[d:FeLOod:^M9aNpZhu@j>?*@EsiOlc&lUHgGA?o"mQCm^4oRhd<&
+ ^Klj?mFUc+sr)D=[\dc8q5Od`tj6"SM/KLS(5O%D]M\c!_d>oUr4h=&Z?bP4,fctKp4V@b_
+ jFb=8PcX`G:UAZV+5!\7^BBi_UITP.iYP+%gGZg?@AA&VXn-.c"=k#a]UrG;Y?Y6-MdK-gh
+ aSpcf4on[qXhmSo[2VN^,W=$0bM-<gK;)U_SKBXhR^!_!l-sE?J+i>2*^uke>5e?("RK'ZB
+ XXT<.Cb4M\rGCfSCtKALfW4WEGA`-LCa+r^XjMs-lA"s1tpWdXR.!:B1<!*d__aSimSeFgX
+ YOo<j0=H.uW\,;G3A&cauQ<FNdhXT.ogdoNUki1Ca<n"0$0djn>?E$aZt[9T[?8j0!"-^B!
+ OW@*pV>,g[9fqQu?Pkp88T2Y%88ps702+fHXe9RcdK9aUOm#N1Q))Nc5b?l=(Dr8EtQGB<)
+ rl1DuY>'*2f!S3/`uYNe;7Kl`n/[VAo02rc_7nQ((qpM$&LlrWV*:R%UIor/WFtT%e;E]ab
+ 5O,lejbN=^*=\p</(e\1u^X8L37q<fa1)9/&_VE#uC7cN2pQO"FsUa<nIcr<r_ARP=L]%/s
+ _17a=h7?.(*B\:IWG!>`a@anu#S3q%MR#q8Kb[8PMo^]m@I%0?itFB;.N@,>kTK\+!rK!&:
+ cbfZ(p*J,J?^p$2hi]ZiK&WMrLl;X0CcDg.gHB[9(aFoOe-Kou+W4^W.cBMdi8^,,343FAk
+ lBPH1&EI'q=<Yo[[$h]ljh-L#3"V;gM$CdBk@`t)77uZs,"$L@\]=A);4WJ`so=<rU%Ac7i
+ e/Y:h`q9kil'X?*pFgtM?#UHtc/f],OT=<02@@'Gs+:hH4Og-e0_L7727Yj?/(8.,UZPe<<
+ pC>j/WP41.m92JXA7)l!F;:EEh(12Z=T("!+0eDfVYN?s7UB;]\'u`U"&kY>EE3W/lrU40C
+ l[;#9poOGPQ@JRekZH,L;remQk/BfHX7mqm/coJ2sCA<`cL)a\T34NH4h2_L3ZDo6aZ>^Qs
+ mZ_Y?U]6e'fWIOuro7BKlZi5HMq\^X[R8,rpM3"&qo])"MG):uKE\m*WDI(MF*WJsMu>rkH
+ 3m?u&!fcP>1:"ssbqY^?eZRtUH#_b0:'ed\`iSko84?`\TmONcq!_<;G(H*pb`MYmW4<qb7
+ 2d=2=SugF3Xfck5N4opM?e`!<NZR$T+9->Lo5F@[<3In15Majof*cmAHR&Sn'162dMcW(8f
+ LT(E7\iO<[e+!W&*sg6Nhk6s[qeKom.Q?E9Q+Z$\t9FK^6ZNi4V@_rVUNDJQEL5`f>i"#RV
+ I-%VFEc0\j#mn+;0rTW=@b2L-$@+mu;6k>OD,*M+`.cZVH_!(]bnLHPH2%V)>E_F`?D)egI
+ :%Jf&nZnmO?/+,cRC-0p7$k!WKs*,=>0EJ?Uh!!;Jh_H'^?h6Q/AXK6N5,;hXTq:/84pB"r
+ uM0u%EXKm2YZYt%G'^@Z]m#_8ZG]skqeuar3q*.[Afe_IH(`aCfH3eGq-AF0B*_'O[6f\+?
+ IGT?md@G,kWaF'n#loh?]u%MQ_S=aYdN9OIo9B*GF:mGcmmTF12H&3h.4OPE7u!:phVJLV*
+ XMh'j1W<54'@[VaZLkT!I3O\OU(i\.4_<CpTuT'C@I'EN-?qYm#_;+.>rm#'"PF?gZ)>n[V
+ nV6:1\.SfAQYmJ+oRiS:/e*>M15%5_,-:NfECnIh#':Y9RDhZF$BQg4ZWTs4<CAo#`![!&C
+ &Y2EgV2%^to&h6T_%7&cm&q+4:X&$>8BkhjB?BktF(G=2+f^WHO%-aU[e7G@Sil$3crqGcP
+ ']oGnWXL''sA[#<DbOeX)8-"_cDdh*=+)^n/oDhB(I*0b5m'=/b?MWchZ+#u]&cd;&p;3[p
+ [RTc-)oQ;\A6XcJ"cV7m;ni4p!GDBV8mC'D8@5Tk!e;B2;V8FdnpW*J[<8pmEd21jlFQY$U
+ [OILp.,2QQ`,V/'\NRA31Q*bSlP'.O`*oT3#Us0?[JUKl2NQucYm+B,6>T03[sE'2@78):F
+ igunj.@&kPiksqB&237fn4EF]d:6Omb2W?[<D2JUK?FOZ<XoZV/Gb52H8(MsQQ+i)f@tCtm
+ Q\G\enCnQ/<l=&.'+RHidMUb<nlkg5lS*QqZinD'>NZH\u`p^HMkro4m\lNdLYem@!)8l&V
+ 9Z_s$kW)TcP4"2WD9,gmu^Iqp.!-"k0_MZZAi/<UR5tr9#Nr;BBJc^IG'GeFRXAZ+C5AAn!
+ ?2_\$dao05[%X_nTs$sM'ju>/=f,s5+FRf%!/6+k[=&+V].uH(`i/fB8mbg'OrT10T7'A3a
+ i`uBmA[O2OhSO0,Voc8s*H`2"XX/3P+nQGW+pTTqC2T/S1#oii<c>M.C6@#Y.F>AUb=P)8V
+ k5-.))n7El7@iNZY8rr=A..%7%4mYR$OJ.+M*2S=X$:f'c7=D?,h,KgjqtF_#iBr$O'rrVT
+ =:<ucbO6"(@%+'#>A1,r'Z9SN9.'*&P>=9R7-,#9qRs*SpNWr&R`V$A.%;-3tTdn3>7#%um
+ ?<Uh`j=!)t27U_DZ4qN"HEL^[s:j)K(Ub=t5$&l_F0mlHi*\-#&YdZ[gA*Ib?5bb!W8/U5`
+ C4qK=o]W;us,HY^#Tt9f=<s&B9YSTsXc(p4SFi'P+Rr91,bF[$V5Un!QZVnUVkbt8!""Cs+
+ sDA>W=0Nn8P<'uIR<mW"HZ/&//$JLPj57-3[lNC^HX&n8HMF"'G_#A$)Ij+\5##h5Q72F<=
+ 'd&:fo!O-^>UPi]TO$9Bcft5Q[U_89iH12+,?0rq94RCD/t[;-5)r/P7TlnXcBDSh'jh-nL
+ P%C),>l8Q!7tMY%Vj+LigNP00Zs^+,V@UAji?dMF?_J>08=2BN6\;oO46;Ul.N8meIrJr%8
+ f$%4YEBCU(8B<<A.peb-=ValtD=0\CcR;1UiV6<o"_*VRmZlT\6SZ:=<YO:]N:Sf^H!!,Dp
+ Oj$!&htl@0qPfWb94,"*JqCi`aZUR"YD]_WWp@u(?a]q9J\hI1O71Q:/#KA%s!)sR!q8D]$
+ To7$$;WeAPPs8TkpmR#s%umkc%f8'kJ)C:qVh\:EtfC'[m@u^;Gu$5^+"L/!q7uQ$f#HMMZ
+ %Wg="jTUoHap][jWfs4ptBWVgk!^pn,u90`Z]tYe^-P;/3CorCe<8CB%T963c@8'Ge45pQQ
+ Si@R#o)PJ3+Zp^i,_3qQ^SqQ&8ps7i>\[1'ih-nK<j?WX8>MXb5QX4MeGI5qE!'ab=jij3X
+ k<#j=f?2T]S'b.rC-nN`UO/gjYK9KH:#iIi[(5Q_%MZ#[-+mFn'J59!D+K@"c9i80fkJ&Nq
+ >m'Qu$4Ff3Br)$5(f0TAWp!O4QSnh`'GcMPf9GR#@Q]PqX60GD^:F?dU9HoL5+uFMJ+H7rW
+ uHR?$4Fd`M2Li!Vl9b_QG1-8#bnOg>dc%<?H%0/%T1b0871BWUek/9?;E'$=2fDJ]3>%L#9
+ \`;!X9^(A(T0;:J,!V!(9Xc^s"hSj6>#[(&tW4Pn(TV'G`C/7B(8V`j99S$G@j9YljJ4Ka!
+ E`>H+2D`q03o+apTtn/-i_SG9UOnat"&jhI"nh>qkX_?(4l;)+Y</=!s+!I0I<!Y0WSW<"K
+ go,l:L?h+1Z6X^#Y*`jgc`H(D4VlH>T^?P`DK>I[jXB%'IYATZ?l2c-^_?*bc5CA*`@QCD:
+ CB*0_?haU`5[a[t^(fo%MC/m4\,Ie(m/q;HK`s#bq8k]_r3Z+(&&<#r(9m!?V$<r%:=$)Cl
+ o3@Z56+b^=MV)cW#-&+h#S-.Ka"Q9D$NLEME:\885B3cm\-k8F\aI:9>toe:?%*15iSNBJ/
+ ^\L@Q_td=*b,PE!_4%>]RT:jbt1XlM0KOOH:kAi0(8oQ*+RV5PRn93<6i('GbZ665@].ejU
+ >RZh,XG?laq)h"-b.4]Euqe#QF/VZ?e0-nO;Vbpo$CQE"9eVkI,%GDdfU5bapUJ?-,sqF#i
+ E/FR^Vhk-qs>gUBgF]M&2aNk(k:/G'.D4a\1SCA8Gi]iA/8%).+*`oKLfBI2D;Z_Ie$4D)d
+ ZARbF>EmZEfQMspbS<:6;0S-E(BEG]@0$md,GoDIUnjshn#)rKNbk1o4n?2PgT8lck6?H=:
+ ftLP$WJZijas)u.Vu^p4oC'&(#h#HJBq(p^`203.RV0q?MO'3W0(ro9N7E$>,-H8R4,kK-n
+ O;E#?<<fadaEW'Xh:u*a><CnX1f@G:5$#RI;QS7E';=s8*CJ7CGpQ*aPcB2sLhI_#cl#=MS
+ -6<%MZ^%.D2ZU-t&uHmU=;I_l*,N5>X&H:k0>.dBSs;;6Rag.>gX?3/-BY^jc[X'!ZffCsG
+ jfQR(:r$=M%<d0lP3'^B8Y^jd2\$=ageC,]gjc//X[2%@V58.&#1Z]tpO:3Ngi4W,?QLiX&
+ oXA&W4$OC$<VL9<Gm3+U$4D+IY@Dl;[Wbd+aZk`..C/-nqb.\7oE!`6@0$tF4e2J]0+W*2Y
+ \dKK!P!`u![>@X<18\j7bu\XSJPQbCc`^%"5$"M!@cJs/E^[']?dh#[UXRd?ibrbUcbM`3V
+ 0.kpV*m!hKtn+b:A,<;-6*1(_t56^4!kl[cJ6d#Q!pZ/J']F$OD&H`H(tC,CO,2gFb.b]V7
+ miCaHJS<"p)ZZpgsF.dH9Q/+(jdSfjWV40cF"LlT=RZ9G3r3PYL(Z+8U9MMLkBHmT31-OL7
+ &qHcig'\BH+f\F!q7d8@L"#Iosg*)b`!:NEd!HI^2e<?>i;H1$/[<6OmA)4`^Ko=9l3_d8e
+ GN46<^n4G*5hq]*/Eb]!-aXVs>sn;=MDGVIG"iPZlePZL!Dln6(P\ofB@sGkc-;a7(YQ#hB
+ N*a;<TQ74nod\>&*r$Yp"1d@5_JJK5OJN4;-:UE5+l@F>0jgmYjGV_2r6)iIdupoF'IbDHF
+ L<fqD@e/+!_@($i%Qe8p+!.cU/3PmA-t98o<V.Wo><NDp/crC55FSn/JqTT8>noUTf8EbZ7
+ S:f<2)3DQJmFZL>M)HZO&c\V3I*:DL(uh0>S'&g)f2I,YM`QLh<[3\0p@[]?T8;fR+Lrm&X
+ F@D,n"nd7PoK`jeP!WO"2U7MmEE3MW:h\FjMHY)<8'6EcRqscI>/?_=5Ts0&sXB/7pAnGdX
+ <?/b%meDdqVN!N,QX2d:4*R_IpI(;1&NnDFM1EGErp_S:;H20;S);G3=P.dk\(4n>>[73\F
+ RcBfN<f;&n0fjG/#-(nZY@l2>D<%)B;2DEIjV17Dmjmmn!5&@TMM]X/jD%@\56;C=#R(W-R
+ 0ktlIC%.,Pu+leG=94`L'7\TS[U:+t+iji%*\c.TJo#^\ZIh<snhr:s5`)neDt%cM$3!I4B
+ Ahq8ir]K#.S<1IaerldcMe[BT%G\@eP(O-$!(5V+<7i&@-*e<?JqEp];W^F6gO`k$<EneW%
+ T/H81VY+EEm-j0X+"oRMp1&[jApV3?fARX?hiIO0M\qg4Q"JVWpJ.IW<;9rl>4$oaImh5Rm
+ <.uAA5<OLq-nO:D+&l+`d%Ql$OBc5Oe,DTS0:,b1f;%D^-P.#H"aoOg.[G/HjAPNb[VNI^r
+ `[.4o<1p\-F.H863'ha^n6@]W98q#S0T0,]@BudnR-W2[Ear-T0VQf/Ds;s<7J6j=b#t5hF
+ f9(WNL8AH-X)#QX"g/:UqMt5<j_E;-:W+5,Y-Ho^ij\:B>Dt[?9PcefX*SW5m!*:S;bC(@\
+ 6j/(I?_4b*Ci>k*QgW4?ocNRFZ"#A2\I/C1kZhbdLk\#-+&k6kW6.A"h02:enA?F\Y`B;2D
+ JDtstPFFq@2^@`Waoo'a89tZk5g6Ph$mkT9c[S.3s+/(qI-nO:\hbp_LJqB9IX`Phq\anNB
+ K>Qd:EUk=gkQHSCr+rTpH@G`qCCasBdTF-,lo):YB?F"Ii*If_i+IsJeFWIqUVmi1SD!XLY
+ N`dl;\Z_X$4HX^?`L&;lI7BT4uuc:1XD_u(\BEW.14i<Ka%D@.A]pAk9hQE?+oOfPO.(Dj2
+ +]H_WL+Q+e9fA8:_7J=MX)(WMjIa[e\p>FJu0f^"HVpFh4(?+9aeo@0*!iX.l2`T,oYW8E\
+ 42+^:rCb7@LD6PL=*\IYa>a3P-N@or3o!e\@UkRmB<'^-*^"ao_W.dH9L/Zk^TUNdt'Wk#d
+ ZTWl!'rYD%S5>-R>;-:X6Z;f=pkg3f/6oriG[.2pPPIG":F:\1M$J^WX!ra"RK&WF#,[3Bn
+ cBZ$s_k=4KHD(0e"n"ig!WQB%U7T\\5'W+g\=;1J`:J;>r%&$>7UZ>tqgs]Ok0<3:hFHboY
+ -'PWc;'QuQQN-@qbUD1s.3(;<HWc%F\?LfhB\[`Va?mEG*kCj]io'jO&Otb-nGo8"'!ckmb
+ A0F6ooMMcn'gdQKuBXitQ_[$4?u'[YfD1n:s$`I<=m\_'4NK(@&0qitQ_[$4@!R8eq#Hs82
+ PKpX-PJ)f*[SMJ^i\ne2bJ'86SkiSuEQq='/pM"f;SJmD:`$<s?Rb>2*q:)"HLKGZU/N>pJ
+ T4H<I,*LgP8fJ5ThI\HmfU9Hm*Cf)Z\Fad4d<-@k[nNK!8<P68CYk`U8r'U_V6t(Y>(nm[q
+ !s]TupX-OK3l<>uea#p)TaJ0%YWu%'n//cEs*BEE+0?sHRr)!SCV$/9:^,QM=<K"MWMjIQQ
+ :1L-EEIM34G1LWd)4q".aX3m!^Z@TML%Jbl/CWA[%dEV15!I+km8N(,!T.[2A?o4MQa(ZBB
+ dinic2?"\R@kneqjTnXju7hk:4i5_?*$nXAULIo.siD2t8l]ErOU'#)02/"6AVpd"]C=!Ra
+ V`ISs*C)B(Mka3fbpB-&`TCp>$15?ECo'G^t9;(CkQcX/+B7k8=)<Z2Z1=5Vo0;rpM"JuMn
+ NfmN`p=<Kk@W2OA'mK^\jBLPSc`nYDXDoLS_4iIX`JuDFgbLI9_qlAG(!^3t_O"T!.9D"#J
+ HIsN,Ud>TleGZT:^/_c=%Ge'SpC.'l)K]ST:2eNo*W4fjYX!^i;c1joIlUdsNjb1d3RaY*i
+ @u`EWt@>M8qrOEn9V_,pEpXFosd=O-62,F4oD%gDu7ljgXO5+C6e_\!"d\9<7LO3e)SEjBM
+ 7HtKa*/^I,?)2Xq<3.SJ&Y6Fct>drHRP8Sl<shac?o3;11o&4c_]\9Y?=mgq'rJCpJc`8&U
+ 0@!i/-n[%:>+$O"Ze.dC0FXM8utXU/jigG-fXZJEm@`.p7&ihl/.7U@Mp&2<!C;,;@YQ@'S
+ ;\TiA-_.:0RLPX8n"@@b/P,H?!=9,gQ<Loa*6ScWeB[QLi`<MO.5`ZX%h=/1@:+anh?-4[O
+ "bNYElMP1'UkD)*A[J;-HEGk>$f@G0%rh=!,sB2[]M_iSG_W8tVlD3YT#9D;J(J4`iDufPL
+ _"J"flc,ke\"d^+F*<U;,9JDJuj*0F6WI)KAguNh^:V*iENd5fCn;='GeKr@T_r<kg3f//6
+ [.jA?!CabLbcYH'01/d]Cn;/si685[moqU7Uc,kDO!#U;jceB(IU%T-?JCiBnFRE"DM3@=d
+ ?>V4sR=rcY/`(@<T!3SRrU?.JnlLH5:pMB:\M?7@E:4I]@#oABOO[WiJQ]):-,KDAQJLPsK
+ "pe$P6iE9J9/.aoc;c1jscV+mgU0T_$Q5e&W>Wj7K>HrEKUX$r)D7Ke'"VSYL<RgWJYDj<9
+ U0T@1)frKW4NXa,G8D@fn:U5f[>OW*0CqeEJ`->S(%j]gpaGYZ>'KtRBa,gMMB3m(gYiBT0
+ :/?95U(GX76R:Alj3,(>'K)P!sU!Q=H=/gm1<?qH[DAd$4@!_=&d\58.Vq?=&ZB@Cs'0L<:
+ L8*6-bfijDLMj;nf!2AmKi8RbB8BDdHUR<O1[hiM+KML#KfAQ06@A4#X#/i67A9B:Rj<X^K
+ J>]U'sB=<N^>Y+hMC*Z?LL7-e$F2s,!6Gq8TcmO:?\+f/>>($[L`Dagkk0/@gl2Jp*0Q<Wm
+ &7HoA$q7-MK(B6rp4WjI7@.>ANU#aH9#356Z*[Er4+m":sjtNtT&'oFgU+L/?*;R,G+;Wj3
+ ;:!t("3#'>L<A;*"r":5Nu"AZ&5fG(43VL!bLtiY;$m[VMl0P(Z[@%JnJ]!4mb0.s3SC4W`
+ MYj]k'X^-hY'MMeSC_hBIPJD.,Rs#cbbBa`G=[pd,t7m9V,d=6h_38q!_2Vmo9+/SAW5#-s
+ c-578%'?5hVm+SiLk<bLb)](QZcu(mUH[&Fl/!cU='lN-(H=bGVF(5(g]R6'J<05?"IlM5S
+ ]Hf8CC`9ZmK#<da8O1Z`qe_n6cRT,qb#6t(XWPcd&3P:#r::SCGHX4N``bQcllI.G/W2fp^
+ .H-L\M;If)T9<VG)$Ns1]lMO>JhFftbmO?dO`F*Lgf6tSm5MT;C1G(/EW*!Ksj[rs45l&-O
+ HKNN?,Mk`\>WOUk?ufT'pPI4G\>huq3aXL:RCgi2bE]pGJb$1*H58*t-f./`gaHHC0KJatp
+ f<tsQdEU!M/o!8Mhbr#6<;rlV6E3=4fP0I'e9pgqljq+DQPjeT=8oDnI!&X=*6`_GqIZ)V7
+ C#em?]>W"Tmf?.dC0O4u%7B[4?Q7WkerM%q4u>^^[]_]>M;DDOXa8[DmpP.YS$--&0+O+8X
+ =qnHc_knu;Eh;m]L7(N0>UZa3o2`;+E[$4@!of2U8knA>S-Cn3ZD]X6JT5"/6QnQ'I8$3(A
+ bd3BXn[6/)nCTg_F$O"V=<RgW^:F>#k@IM_$\06'sacl(+Fa*@(^nT"uOMe`&Is"pfZ]j5E
+ 'Ga4^O/o5BF.XBp*^R7Mn"4JO:9Wj^i_b4XeN1l^?EGiHTWg]NI9o?*?i&2!nH-;hau^+N9
+ [mN7;S3CAU0_Kpl1%O>H@*3@AW+_X-nGpWO/f/AM'p<!(XR$,=&td7eRk@!?3`I>U/R!BBS
+ SONQ07KdQ_:QJn3*SASfdl<U3H^p(2DNe[saFU>eR+`M?a1mPrW/';0lV^CWM,E.YS$-AV5
+ kdIeL+IiW\s>GXra3f#rB$GTbq_293ld$MS)CYZuds!l;?N76Ss]5?g7"*k7[5TYY_%d\Fc
+ #!Fp8`S($3mgEtdg^=oVh.>7p,A\O%Jp?[&i4F\CXcUkG\gS?XD9*#.>3k[e>Fa+"8rVU+8
+ ?_nGP#aD;<l8`k$V$/_?'AAuNP0$q52fp_C=<LpNQ:kID51eOj.>7p,AXnX(8kV>E-((op7
+ qS6L=Zo0c(Pl.H*p>;]7Jisn\Qja'!l6fp76Pd9hL(2JhY!V^#*UrPNt^scic7ulFI:[m*K
+ qCZY4_]=8n!99l)d!2h7KSALX.d8R;"Qt)g%s%2%ZMeh8S>nM6NJ9Q:nkY.4c#Y>u^!"jN)
+ 5uiU]t+Sns%)HX6@X%gQPUZE[Zoau`LnrM-S^E-tGM4YpaE'!Imn/tJAF0QJi3@H-Z>/2$0
+ 1%eElpjpY%O92J_5i4c4cT%0kHLf_nq2bKDT9\hfoSgW4ehf[c,@9s=b9kgaHr)a#S"bPbf
+ <Rd3@&(\9iG^c++hHRR\GX9]5>IplQjV$abhHtJLQ0@Ot_?.QY8OYeaYa"p2MI)]c0Rg4jI
+ c:sU]eo%3$d0iR]Nd'T@=`r5UnXI<rGIqn<:S](;tCI<DlFd$h@\O18uWm"0X#'.k(pc-.4
+ c#Ufdc:?^]-UaWT(=TQ^/Rai*8DgUf4*gJdu&5"!$I2k`r,!9cbsSTWg\sh5@!=$Q"t;Ti8
+ Sm/C!!3d9mI9l5nqB_>Da7=>!HX]ghp28R[1#l)m'3?+Qi?Zttns.KYM%:H'-0'mcNt:iN=
+ %SWWAg\O;k,EX<'\"PI._W+Wu#*mu9%7ci<W#,A\1NtQ@<g4aXkGs0;Y?7Xl=+T?,OYX%]*
+ 8OYdXf++#d.5L0<=.'NYAt:8T(::?N>iPDo+b0G#LVa)nnHfs'4SIDul&d/74BHU5S%KJ?X
+ g>shK!6`\a'i9^G"l\^[9id-pua%V&.kmBeFUR":6HYI@P/XjAMBFnojI="_"i#-goV7U.V
+ 4r>FGRd8!ARkK4jQ>W>C**$\X("O?nrP_1#dUQEm6YI:lfU@"+..XOn^H2,8:n!LqtS+3H]
+ ))oLKQm?a[mi3&g+2n]E,`$[S2X]O!_[gV6ig=<Im@<@U^cI_'VmD.rFV\*R[Z6C[Oi:,\K
+ c<P#YaAWg)jmdDj5KeI*9A%>!_-nGo40mo(-`f/u)DVa-kNm9?rd:IX&G\E7=XmYU2YIWC8
+ 2H6jpJ)T3&Ka!(d,:_u(o[2XGo0c^2q'?5D3a[p]DbUjZ]"M?XSAW5#-s\>%Wp:@Q"bL-eM
+ L,:$mjCcS'4HkXlc]QU^!7<F:/VD_P[Zf(@9tUZRP4Z'Mh](M5qkSn4Zdi^C7C3A_S+V,Zh
+ g-Y.it7W;Q^6db\QbJkrr<(d3/i)"@4[Uoj#.sF?BNk<do(\_774PLP\MKH/-c[*9K,.kN'
+ Y:FlG'_(f4>MpRY>]:VV>rd_j!>W\5nZR+Q\1Hq:/kV^EA3lc_K3b9^mQ<f"N=@RFQ)hg8<
+ k4j02dQQ@Kfcs4?%Ci7khDW:=\GV(h>93f^LckDKi/Dtlu<RgW"DiBf"-@Tr`h_]tr?)d^Z
+ #:SP:eFtuIlc$cU>qXMJ$4HXc3#2,al;od;LP]AC5!@e2FI22'iW2Yf=*6`_GqIg@h;#5#q
+ umiH$4HX`pQ`m#;:!q%rd!e9YaY+t#1GY8:MA.TiYb?f(7%U0Rs:t`E1c6eY^jJOY+hKg"1
+ j.Pikr\f>+.@@gM/L]CNL*W&ga>#SShlH7#B6V96?!,!rfVo76Mahck,G5i\)U]cS1aF'mc
+ NtN"SoWVj-BW%eEkq1Jdp;AV@o%r\dbF4`#1cT.=Or0Grj5odK>O-(`;+OMe`&Is"pfZYt'
+ bGE0NArYEDSM)X;\kJK$ne5`Y,)s5C+>s(]8UfqCkb\Q'.P6/$pU-fV1n?F??e<?;gHL"Ts
+ s+^!.:s5`km1TrkH+4.><D"*jjV$UEjRr4eM9A1f0$tgL$i&@A.dC0L\=`C1nY=Nb1CZg"\
+ K3:CVSpiU;iC9]LGuH&k8&riOIW#KJ<.rP;9sI$4&1*\psIXi#7X.KTcPH8VSqD?\O>\5RB
+ ;&?KKOqlI&sf7H<ttRTHCL^f<)('AqRcoB8G_"kU\9OfFtdV&fm^NJ7!_-76N(KWhNt)oGI
+ +tr10#\Ap]g2/RGA@X4?$IS`TP<Ef'+5>;";H$4HXZ)AVqhR*eQQLA4j'YrN$K]hAqOO&U'
+ Ul1>$?f&7'S<Ug^l(QTjV(@\@Z.dD<EWU\Cm`4s0Ag0hSVlZ(pT$7IFJ--D7o&V&op6X^"&
+ H<R:i(%pqJ(\A5B\u;lWS_9+.f9tX+$DIkjcs^75]Nh0u'Gg27O/o3,.kA<<EKg#'n[66If
+ `lNsRdM\4X2&_h>U'3Ea['5cd3/i)psNC^omHPP$X-d7*M#1\o;@';d)Y/=g-\M:+&[o#og
+ ;m^qk9&fr1e0(_FOX0$O;/2rdqDWL;F"@e5o(%^\m1H></7G;uc15F6^;\rN1CW=f&SWKig
+ 11/jE9A1&0pZVnGmpQ%$!JGlE122TA8~>
+Q
+Q Q
+showpage
+%%Trailer
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-area-page_expected.svg b/testfiles/cli_tests/testcases/export-area-page_expected.svg
new file mode 100644
index 0000000..ecdc717
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-area-page_expected.svg
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ width="120mm"
+ height="105mm"
+ version="1.1"
+ viewBox="0 0 120 105"
+ id="svg10">
+ <metadata
+ id="metadata16">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs14" />
+ <rect
+ x="15"
+ y="15"
+ width="50"
+ height="30"
+ rx="5"
+ fill="royalblue"
+ id="rect2" />
+ <path
+ id="MyStar"
+ d="m100.96 68.703-20.799-1.1756-12.76 16.461-5.3089-20.138-19.604-7.0445 17.518-11.27 0.64404-20.815 16.136 13.172 20.002-5.8198-7.5458 19.411z"
+ fill="red"
+ stroke-width="1.5"
+ stroke="purple" />
+ <rect
+ x="29"
+ y="21"
+ width="53"
+ height="53"
+ fill="yellow"
+ fill-opacity="0.7"
+ id="rect5" />
+ <path
+ d="M 56,69 A 18,18 0 0 1 38,87 18,18 0 0 1 20,69 18,18 0 0 1 38,51 18,18 0 0 1 56,69 Z"
+ fill="green"
+ stroke-width="1"
+ stroke="black"
+ id="path7" />
+ <rect
+ id="MyRect"
+ x="24.5"
+ y="18.5"
+ width="70"
+ height="60"
+ fill="none" />
+</svg>
diff --git a/testfiles/cli_tests/testcases/export-area-page_expected.wmf b/testfiles/cli_tests/testcases/export-area-page_expected.wmf
new file mode 100644
index 0000000..92421b3
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-area-page_expected.wmf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-area-page_export-id.pdf b/testfiles/cli_tests/testcases/export-area-page_export-id.pdf
new file mode 100644
index 0000000..c567930
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-area-page_export-id.pdf
@@ -0,0 +1,69 @@
+%PDF-1.5
+%µí®û
+4 0 obj
+<< /Length 5 0 R
+ /Filter /FlateDecode
+>>
+stream
+xœe=NA …{Ÿâ] ÆöüxÜÒDB¢XZDˆ”%ÅBÁõã4hdëé³ý<VH¾ƒf²pîÅ}8>n´’ÎÒ×ï‚ó7 7ÑèÿÊ—#*[Kø!ÁSÆ'½¾e‡àDÏX±í9mtÖ1 QÙn0sÎH \uà:•´€•Áâ“xg‘–„Ô„kIÔ³ÙÊlʵ‘ÄâoL9Ì°¼&0Í#;T<WMà•K B¤ñNšñvÖÆQ6—Ëÿ??b¡…îšÒ?a
+endstream
+endobj
+5 0 obj
+ 197
+endobj
+3 0 obj
+<<
+ /ExtGState <<
+ /a0 << /CA 1 /ca 1 >>
+ >>
+>>
+endobj
+2 0 obj
+<< /Type /Page % 1
+ /Parent 1 0 R
+ /MediaBox [ 0 0 340.157471 297.637787 ]
+ /Contents 4 0 R
+ /Group <<
+ /Type /Group
+ /S /Transparency
+ /I true
+ /CS /DeviceRGB
+ >>
+ /Resources 3 0 R
+>>
+endobj
+1 0 obj
+<< /Type /Pages
+ /Kids [ 2 0 R ]
+ /Count 1
+>>
+endobj
+6 0 obj
+<< /Producer (cairo 1.16.0 (https://cairographics.org))
+ /Creator <FEFF0049006E006B0073006300610070006500200031002E0031002D0064006500760020002800680074007400700073003A002F002F0069006E006B00730063006100700065002E006F007200670029>
+ /CreationDate (D:20200404202009+02'00)
+>>
+endobj
+7 0 obj
+<< /Type /Catalog
+ /Pages 1 0 R
+>>
+endobj
+xref
+0 8
+0000000000 65535 f
+0000000615 00000 n
+0000000383 00000 n
+0000000311 00000 n
+0000000015 00000 n
+0000000289 00000 n
+0000000680 00000 n
+0000000971 00000 n
+trailer
+<< /Size 8
+ /Root 7 0 R
+ /Info 6 0 R
+>>
+startxref
+1023
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-area-page_export-id.png b/testfiles/cli_tests/testcases/export-area-page_export-id.png
new file mode 100644
index 0000000..6bf9b04
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-area-page_export-id.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-area-page_export-id.ps b/testfiles/cli_tests/testcases/export-area-page_export-id.ps
new file mode 100644
index 0000000..6de42c9
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-area-page_export-id.ps
@@ -0,0 +1,130 @@
+%!PS-Adobe-3.0
+%%Creator: cairo 1.16.0 (https://cairographics.org)
+%%CreationDate: Sat Apr 04 20:20:10 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%DocumentMedia: 120x105mm 340 298 0 () ()
+%%BoundingBox: 115 55 291 233
+%%EndComments
+%%BeginProlog
+/languagelevel where
+{ pop languagelevel } { 1 } ifelse
+2 lt { /Helvetica findfont 12 scalefont setfont 50 500 moveto
+ (This print job requires a PostScript Language Level 2 printer.) show
+ showpage quit } if
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+/cairo_set_page_size {
+ % Change paper size, but only if different from previous paper size otherwise
+ % duplex fails. PLRM specifies a tolerance of 5 pts when matching paper size
+ % so we use the same when checking if the size changes.
+ /setpagedevice where {
+ pop currentpagedevice
+ /PageSize known {
+ 2 copy
+ currentpagedevice /PageSize get aload pop
+ exch 4 1 roll
+ sub abs 5 gt
+ 3 1 roll
+ sub abs 5 gt
+ or
+ } {
+ true
+ } ifelse
+ {
+ 2 array astore
+ 2 dict begin
+ /PageSize exch def
+ /ImagingBBox null def
+ currentdict end
+ setpagedevice
+ } {
+ pop pop
+ } ifelse
+ } {
+ pop
+ } ifelse
+} def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageMedia: 120x105mm
+%%PageBoundingBox: 115 55 291 233
+341 298 cairo_set_page_size
+%%EndPageSetup
+q 115 55 176 178 rectclip
+1 0 0 -1 0 298 cm q
+1 0 0 rg
+286.188 194.75 m 227.227 191.418 l 191.059 238.078 l 176.008 180.992 l
+120.438 161.023 l 170.098 129.078 l 171.922 70.074 l 217.66 107.414 l 274.359
+ 90.914 l 252.969 145.938 l h
+286.188 194.75 m f
+0.501961 0 0.501961 rg
+4.251969 w
+0 J
+0 j
+[] 0.0 d
+4 M q 1 0 0 1 0 0 cm
+286.188 194.75 m 227.227 191.418 l 191.059 238.078 l 176.008 180.992 l
+120.438 161.023 l 170.098 129.078 l 171.922 70.074 l 217.66 107.414 l 274.359
+ 90.914 l 252.969 145.938 l h
+286.188 194.75 m S Q
+Q Q
+showpage
+%%Trailer
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-area-page_export-id.svg b/testfiles/cli_tests/testcases/export-area-page_export-id.svg
new file mode 100644
index 0000000..9fb1575
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-area-page_export-id.svg
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ sodipodi:docname="areas.svg"
+ id="svg12"
+ viewBox="0 0 120 105"
+ version="1.1"
+ height="105mm"
+ width="120mm">
+ <metadata
+ id="metadata18">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs16" />
+ <sodipodi:namedview
+ id="namedview14"
+ inkscape:window-height="480"
+ inkscape:window-width="640"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0"
+ guidetolerance="10"
+ gridtolerance="10"
+ objecttolerance="10"
+ borderopacity="1"
+ bordercolor="#666666"
+ pagecolor="#ffffff" />
+ <g
+ id="g10"
+ transform="scale(2)">
+ <path
+ stroke="purple"
+ stroke-width="1.5"
+ fill="red"
+ d="m100.96 68.703-20.799-1.1756-12.76 16.461-5.3089-20.138-19.604-7.0445 17.518-11.27 0.64404-20.815 16.136 13.172 20.002-5.8198-7.5458 19.411z"
+ id="MyStar"
+ transform="scale(0.5)" />
+ </g>
+</svg>
diff --git a/testfiles/cli_tests/testcases/export-area-snap_expected.png b/testfiles/cli_tests/testcases/export-area-snap_expected.png
new file mode 100644
index 0000000..f35894b
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-area-snap_expected.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-area_expected.png b/testfiles/cli_tests/testcases/export-area_expected.png
new file mode 100644
index 0000000..c9ded26
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-area_expected.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-dpi_expected.eps b/testfiles/cli_tests/testcases/export-dpi_expected.eps
new file mode 100644
index 0000000..b662237
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-dpi_expected.eps
@@ -0,0 +1,261 @@
+%!PS-Adobe-3.0 EPSF-3.0
+%%Creator: cairo 1.15.10 (http://cairographics.org)
+%%CreationDate: Thu Mar 5 09:46:17 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 3
+%%BoundingBox: 0 0 173 90
+%%EndComments
+%%BeginProlog
+50 dict begin
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageBoundingBox: 0 0 173 90
+%%EndPageSetup
+q 0 0 173 90 rectclip
+1 0 0 -1 0 90 cm q
+0.9 0.950196 0.9 rg
+90 45 m 90 69.852 69.852 90 45 90 c 20.148 90 0 69.852 0 45 c 0 20.148
+20.148 0 45 0 c 69.852 0 90 20.148 90 45 c f
+0 0.501961 0 rg
+82.5 45 m 82.5 65.711 65.711 82.5 45 82.5 c 24.289 82.5 7.5 65.711 7.5
+45 c 7.5 24.289 24.289 7.5 45 7.5 c 65.711 7.5 82.5 24.289 82.5 45 c f
+Q q
+82 0 91 90 re W n
+q
+82 0 91 90 re W n
+% Fallback Image: x=82 y=0 w=91 h=90 res=300ppi size=427500
+[ 91.2 0 0 -90 82 90 ] concat
+/cairo_ascii85_file currentfile /ASCII85Decode filter def
+/DeviceRGB setcolorspace
+<<
+ /ImageType 1
+ /Width 380
+ /Height 375
+ /Interpolate false
+ /BitsPerComponent 8
+ /Decode [ 0 1 0 1 0 1 ]
+ /DataSource cairo_ascii85_file /FlateDecode filter
+ /ImageMatrix [ 380 0 0 -375 0 375 ]
+>>
+cairo_image
+ Gb"0WH'"9PS\T5"b>sR!5geEA>#*o-O@iO4X\%*-qcMm@A1J8n(%L39mSUBAm&DJ3@:ip8)
+ &VLH0Le;2,\jHDCQ=u(:dd_5R$mA&Hg^cQIr2eK@?;Q,q=WJOchCMul8"XcoqhP25Q/l$&'
+ n<a=Y]l$e'lpth:o&06UsLLHX2%i]jYpmGE]YOWEc85od<KkkZ$,8VKGqr2Qt7:-')?F?!=
+ G)ijoKk-,M'kinDa_Y#9\[mj_.*Y"H7?<B=H9>cZ;-;Q[:r[X5ERX3R;3b[Eh$MJE<8#+sH
+ ql'fM<%cb>QXprUk*6_gmNoJd.*6c5=-K$;T%EuX.b[CR5inD0fbeY8Gi$b`;beY:C@02JK
+ g;"]P_&Of[.'=$'_&U[-!fl9o^`:S7*%'SlXprUk*6_gmNoJd.*6c5=-K$;T%EuX.b[CR5i
+ nD0fbeY8Gi$b`;beY:C@02JKg;"]P_&Of[.'=$'_&U[-!fl9o^`:S7*%'SlXprUk*6_gmNo
+ Jd.*6c5=-K$;T%EuX.b[CR5inD0fbeY8Gi$bafT&-'15+NTQ)LQn)Zn(3Op67M%]:7T1a[&
+ Z[SCC0sPiP]C:jJe2Y+@8J=&<mBL&2KkPc(_":TKNDNfG0OW`uMS>:NYLZL5o^]q68;7GSd
+ NphA!N>+I^Oq2&*.=&cS.WB=7!h/-=K@W,VWf")4_-,TGS\%bupH$Akg.GUH:5tWV"$7cHh
+ 'e[B24b`3[f-_7Z,e=W5qmTl2p=qaUrU-66RJ4Y.-Q_1=\4TD;/l=VT.CL?Wad&[m>jX'!=
+ &`=5rpn1+]PI<1SMYNdlgF)i>4(:_E%g-H1oIM$a+\fHXgLS47JWPQ!`+AUq<iX@Lt:[kMp
+ 7;?:V_7!qm-[>0/RUmQ8t"DH1]KCbkh/A3iB@&pDCS.3j'T#jgj)D[R]13b[3ZXB246-2ru
+ _@&gd\)OaQ<$Fj*E08sW+8/g^,M:-^GU76;9Q2g<GM@7MX&6KOH-R(Sg]_K_@/b%H_>F-s9
+ Bk$S\]@#gG'3LGFZR,d78%d%./ju>AgK/o<O3DL(H?k2=,Ng#)UR"IDdj5aSXk$S\]3'!@Y
+ 3LGFZ:(HDo%d%/ZPCkl_K/o=ZjBrJ[?k2<Y3LGFZR"ICo%d%./k$X45K/o<O3LJjf?k2=,%
+ d#`2R"IDdK/l[:k$S\]@#gG'3LGFZR,d78%d%./ju>AgK/o<O3DN?,=++8U_DaPg[Md\'kM
+ 'dlUU/QG8l:IN7gXkUL;atI5JQN0\M+ig=DZj,Xn1[mSLa8uVfbV[g:XgC:R!uAlo!h,VQI
+ kI0quZ_Im*`n=pmOL([_Gh^9&%F'p<bPhsPX3PmXoub)6/%oL"npjfeR[^>H-I\&PfRCS'A
+ ]m"2\^4SADHk08?bp2Bs*S):l]d$&B3Bmr8F5M)nuX.o;XR6OR>ptkQ8G*hVtGm3$KDHU.C
+ /QLqUp3(1&Gh[BF<W%HIWjU!um&=Zt0WMZM%[)/e2uQ!f`k`(Y%V3XH*e+(H)7_P8k0>etf
+ &g-+pk.3@>ShJ;okU`imr)P2RqlBEp=e%(?+'7'X&eJ\F\:))CNZ3sBkkrC=h1jI?U=^MQr
+ KT?I4.2Kj%T')f,p><2-I*EFnKo\Fj$RoNndUDS(o3B@Ie#prDE:biq47J2jfh1OsUFEJ+N
+ W&Z6i;fR:9.(mnp6_d8?[Z7#`oQHHaI\,CPpS8qi#%(j>$3.ok"Y,t[EZP/ho*mhL">?brq
+ (.u[+cR%AIDc+IP/a"!N-4A?mg`#Pmp0\i0,C37]T#KLIPPL#a.,sF-o'7#&sg+Odc:R4Fb
+ T"O7/,qq6'XEc$lNP'SfeLHD9>%sOj,[@91;:<CaM:4@W;gAQ,?1AdSASaK=d6(]U\Bhu2b
+ eY:C$.3,jk$S\]3'!@Y3LGFZ:(HDo%d%/ZPCkl_K/o=ZjBrJ[?k2<Y3LGFZR"ICo%d%./k$
+ X45K/o<O3LJjf?k2=,%d#`2R"IDdK/l[:k$S\]@#gG'3LGFZR,d78%d%./ju>AgK/o<O3DL
+ (H?k2=,Ng#)UR"IDdj5aSXk$S\]3'!@Y3LGFZ:(HDo%d%/ZPG;fOia+F152IPoCb47Q;Ykr
+ d3QM<+4YS1/MC_rKr8qZu8(XiV7dK=\^2eI/RPQR.S<c1a2bc5\"f6mHKZoK4dgr=Fa'LPf
+ O)JAZQ25jMS'"MbS1JM.2=+l68[0JeQ'gW)1/&051O%u0=ng%L2/dldpebfrNK+ooSm7OaR
+ o3s;1a@u^3C<&57RcQ(;:=8<G>HAMV"+j^k=;EaV^*'+S!6m:,`eSJITG#r<n^Ab?>"2SE1
+ q:iL2M@PI_BIZBW,^MX7)uGbhM:)4d>9I].b\o@"oZ^^5Vr,:VH_)l[N[%*N=5k?SVKL="a
+ OPZL=:WSX7'3kt@@LqP$;D?6"!*Vt)D7]6p$lb9u,9I.PZBr60Oc8auuSfcOd<*69h[h;n:
+ sBP_hmqKGg*Q.mU]$(X*6ljRd%\)"*p&`0'mGM\R+dk/\NR$McN]R=/\b%6pQXH7t-F\,p1
+ _Bg"BML9jHjmRZeQ1(]bJj2j)afR#$RP-.)?/@X$,-<iL4S]&ps42+[D<Bo,S7DPXhi.k!b
+ !GGf]1_"DA9jR*1[!lCAC6j$3FOX4hD/&:53[ft)fA<^9k@VLk/W?>L>R%DkAGHQ%d%./k$
+ S\]@*Soik$0b*q#=L_Yl0rA3LGFZB:Di^1j(]b%d$0.QugZB;pejtg!cpCfRD&\*6c6hZd%
+ as<4>p&p%N/@)07J:F"h7bS8OA:f[6X?AB_k0be]g#F+u]H-BH_Z=I=_$R"IC$B:q98/_,8
+ t$=@4M%d%./jp[c2k"*afc/qO9*6c5=bS`BQ2+RY>]=6PaaI%T?R"IC$kH*f:2(^\?8&cOm
+ _&UZ:38\>.eCrH13LCIo@Eq;X:1\LCahJ+&0F)YQkIf$$9KoV33LGFZQug"(83q_])07J:F
+ "esIF1eV]be]gj$$._FF/4ia%a;bZ#RPQg0W2LY)KRS[6Ga]]b`Q'i>;-;'+sG18`15!4b4
+ GRI/l_hSrk2pXfa.&^7[*`hiSD3;mTWS,Ol^!gk$ZJAR$2d4D(,gM8r>-Tb<1lS?BT3UfD@
+ )opR8WXbrOZg_WpIQPOkkmX#7)XX[\nq,ZdmL/(p__U(fW/5Ge>-NCc(OXc?gK-/E[^Z7FR
+ Ck(5K]ENbcP-iR4Z;+XX^U=L-_:N<3J<TL3fYuL$Em-3mDOZDMtjkmKo?s,j"B2</aDtdZ)
+ lKKdt/1L'&'Y',PoG$GQkPa\R?VS_62C!!l8Md\c:=#@m@`8VTS^`D,E:I;n1s!TfXmPp2V
+ GE_I1WsaFqoYN#m]gto.Z+Y<6%?"BXhr@+0E6*;L,ih)kM!;MY,uU2k/\W&c^gEkc8TQhq>
+ LV?+.SQZBhHAZPGVqeSX_E[f^`HSGLSP+<6ucZh_H)3^Lau<mI"[kdgT=TPY>Gr^>(%'g?.
+ "[]^LX!SAF+JHt,p^WB#!s,j%4rq'"]`cnJ."Mr?1@@h,K9poU6:2Vo[&od["Jb]-Xmbfu9
+ eAWGhgo3r3lcl$7ekff1h5t?,(k$S[i*p/.$S3c#s?k-<Sk(M5(q;2>QXq'TU<FJWnbo+_9
+ YV:j6$?JON:3g>p=)So;KRKguKS2@Z2FhLqDnqmI06N/60ZO38gl63q$2[Vu#RPPl0LomGC
+ rRHh9/+C8F8Xd#@1En33+IsoB7`-3R,aPoeLf==6\II[R3S9q=!98QX9s$=1oJ($*coEn0Z
+ Qb+cMBnLbTX%(*%0!n=uokALMRHA*&a^l[0C]njtOhaEsDW@3TcE'@?/7fPRdK]/'K0;_Aq
+ a`nMGMFLTCr+Ji;d3Hd)\pXfLi2F,DK+rFd&00$&+s4d<!TcEo.[lf^,pBd5aaTi:3E1HfN
+ b\N/*1^AO2<j"e7O,PF;kWbnjQC*YfUB2<06Vf6S-hL/u49Tkm84f*KHaF.H*qF"c4YXo^r
+ [.]q#\1i\5V7>.$?"nN)/W.D[:S5#cMk._=iAKi&V5qRb04E]Tk'ZUqr6q*1O453WU8-S?o
+ c<_>g3jq>VNmsr(WIg:-CC%]8rP9Vqbd9P`^Z"0+#DcXEREg$^,5K1(GAc;m-C/c-`of`NK
+ +pZZIG^NHWR$XVoV;;SpU^kf\-&E"`@.BHek['4T4C^XZ\/r"C5S3V13^Lg:#_bU==>]lU2
+ 0!j<,+F-ccZ=lkhW-C<Y&$6\i<."ER*m7<AHWc/5QQF_U[>jhc_aopb$W%*P*^['i0M2<SN
+ 18NEY]k&'jq04#`)&8MQ3r/nsg+3+7U`>f2*-kn,!1Fd.h\<q7cm&\jG,,m!4cl@NICX-S#
+ T4&44)!6Lp*<2@,Qp\<93FOc-7)K>T+ksl(0Ua4n?==>s(?U:t*3$NO)KX!"0IONqaQ[+C=
+ V^(Pf:4M,?XO@H($:2$S0PF3C>*DVL[5=g_JYCGfo8"&:RDf=F,ncm_]5s0bR(mA''>9=@8
+ >J"3F$Vbfp.LVA4`.%$0K<S@LcNRk!Xe]C\-.e%TsG,7"RF7p8u.NZ6[YN&9D=O@PERR#]h
+ 3.S2L2q?k1j$k!Y"cX7ra9*3pa3M%F`omp;#7A4)^t+m(?1^!,uL@E0A!+MsYAc116[ULqf
+ ,bG+`$)jsX_qiHlA3Eg2Cm-C/cV[,+LdPRjVK_IK^Wt--rm!M_0afBbq?2E`\X7rc_)jR'0
+ E+8BI-H&Uq[:YYQq[B6ofkZ=/P+ju9]k15E]6p:as7Jm'bq!W^p$Z*thAPGqW]jDC.5J!W;
+ sa(b(;7'<Yr<b/Y^O?N>t[kSj8@a%s),c4&Qc0Ig9@VY?A0+.iJgr"@cXPO,?GiG?h![ERf
+ E;gkF[+a3Q%0L>U"A(SgKNRm?lN*eR+^8ERi)^\'*9kMW-sHiNkV<SbM9Za5;2JcnJ%O,N]
+ DA0jMXN%s'/9'6DCT?N'>sO2(M#Lp;3f@ZqSp=<noe3ojubPrT3G3N&8i[lOoS=_M9)r805
+ alq,A?8];Z63u=2Ccg6#`F,1#<k*VY0e;gF\LM2cXNk%/G^AT>>&.LD=0fS9P\":ATU-XMo
+ k2o4%6Suk<8&ZJDB?VM;9UMSacI4kH`Z1_hKK1OER/H.TF3(BJ0rM[`4ET^:fY.?,L^'&+k
+ 3P:B4*9W/IsXBA&.Kt5bXjEZ%\5U,5;'trZ7:4Fq9Sg81N_cHr2(k(OB'tDcKm-h6YDGJ2a
+ )?hq4!tC6WRcZ3Hq@V),bOIfb*:`H=@@ePs+8gN>W[5F+XCI%d&jQfji=SV*j'5ma.@8oLF
+ !WLTJW>F"Vb.'!WX&jokVp*R#\-ZHA]>8%+/tS=omO$d9cpZHA\["m\Kf:=('j`#Q'4X3CD
+ .&,nY4jokXf5g1CPZHA\+3u:>S3K&)5`Z2:a#W,$YOZBbWbZ<M7k*[79bhY1$hR7_/r#Z;/
+ h\NKK$sAnU?8IXZHYggL-2u.uN8!O.cZ(nSe]jMR1]:_C_1M4Z_[8P\H"D9m[a+d#++EnKf
+ 7/4K;uW:gF10<'G4i7EY./N)MIGWVH!s7r(DY=X\mkIM_cI=L=m^7W#s5t/GGgnAFfohZnp
+ k\!U>^cNVjhbJqE)Wcr3C\jF10?jj_MUnh&Oh[J,\W]S?6k8I2-hEQGac.V(TlZ^6fSP7<d
+ lO\7L]HS0iZ*ILu5Uc24"3jug6:;9U1EG?1+VQT!t;Kf?'Vq<Ol,NB'Dcbpme1!u2&-B?kG
+ QpatV>SNGTMEHte41hR3Y&-^-5F7tme_]5C)At(f154N:0+UV_fRIf;*pFq@NN7hggp<U1&
+ cCER+bf1WsF(^t&gQ$*f*25-!RlRYoIN<Kc_O,3&Cd#'=F,7]#D<C?d*2;q7RlTpYHT-'/n
+ 8g+SJD"P[#:D^N*25-!RlPggpL4V0(phJW6?aU8&;;jnb`O-*cl@M<S1%37R58sL;#^G6U@
+ ;c[3S1@H#'5U;RoSVKA*cEh%T.JWb+.5<hp/qH7,cB?F/EMEMn&_f[^*X_fp,S7><VPs,F\
+ LS^AZ:2/T^)lRHO%DXcG'h^@6MSZt%LigY'W2/BXu@4;[EjW4?:2p1NJ>7B#bN`3q%Fpqn`
+ @\Tm'Cp+%MKZ>./Fs8&AfMDTB"bTX$E5]c!A%$<QtqkHMkcMTWa@h)AOX>8u3_lnj@S1!3A
+ &p&j1AsG@[jTV>6B>Id[0S_&ap6bGQ9E.W`A*D-^lKtK'LF?R(QnpdLF@C7>kfe>KmAD[o-
+ t)b9oCK>]LX_i,hL5ZML]V1L3WJZjIgN)763lWa"jA$/ro;i7#cTi#JV,sn>P=GJi(=9G3C
+ /-;&9Gq$lnR[U'-DnQS=i(XLcK!Mp6\e4=;j5AK)T^!k)aH5@uc'Q3cfoPE";#lD./H^3C/
+ -S#BRtp0Ls)%q.Inp)GHNn@SY;.?k1j*k*VWJnq1u,+ksjRF)us4>:_I`/.?UDbq]$f($:T
+ p@?.lYcYO-_[lZ="/.?UDbq]$f($:Tp=_NiH?8I%O*oH\$#rmjqCPFB5EN^5pF8VA5kfc%d
+ I7W<\a&D?q\E#dNh`tMRF,<J/*&2*E$AB8tF"$SmF%3;N3HDN)rWPOdXng<3H=#5Y9J44YW
+ ^$#1KuBLV[7:a/+#`K7RSW#R?etVEdQ]Bj[:e%$r,k:A>7_a;(Gd4P_Pf>e`^Ge@/UGXfV*
+ g$N0LXtV0C4U\+t&Wjj_n,JW,XQP`^Z"H/S=A?&!nXX,fJfbR9/*E>2NZTD=a4Jhu=MTI.$
+ r<`%^OKm?,:DLY(lsZY]BAkl&nK"`ZgE?P"mWr%RPoUSC:/]F*%r`>l"f&b^?BOcuM"USC>
+ ??TVTH0lQ1AQ-"a%_N&n^J'u]k:tK#dhAPIsI?(HjEj&q_:1:dT6.[+EF%AIMq]$>lA@;<Y
+ ES4pKo%hJ`^qh#%:r8_.0k]%n<L?-3KK0WRcC4]I0SaHMF!APmR/@fF+WJ:g#VjBh)p"C6(
+ J:&.$0K1e`P`P1@Lh'&k!Xe]+pR1k-"0OoR$6T'_Aoj,bR(mAde_trQp[0m3FOl0bIT>]L[
+ 5=g_JW+oF#iBE4^h+<c)GT_`Z293bR(I5?dsM_D>.KLfb<M$f:9.D1<^oB_OU+a;q5'*3Fl
+ -W'1R%,X8T-6UE4KtW\>fZk+$*EJN77FQkMr1j<0gW[Y6a#1.T'_P%.r2fbW_'b8g>p$?O;
+ *0IL,f<45d&%pRdhRp)%krqGSHEf^U4'3*0n'?+96'U!R_;#s0jn_oA$_B_Kciq)3,d$`qO
+ CW?BIRciF&mnpX5nb)Q=3,'[QEgMqcQs`gd.oFsp;>cL9#ok$ChQV"Ij+b:d?8JCLU,c_Xa
+ '?+I3lCk'IeB;_o%#J\Qri-D0'7P&JK$Gm*7:jpTes8o>ALis\tncNm`qD!=+tEsq7cghqb
+ >4SkNskUqqu6#:<K(`-#8BHP;=Qs>;>KM="aC,/)N/LZBm3`o&>b806N/6^AIJF<>LjA:\m
+ 6'ZDL@@RVLK>bUL0<gf<PebU_!qYGJ9gY&"%@DV/%j2h#g9Z_5QWm:qomng?p[W@B@d'GOR
+ ^)W`ElpZ+R:g;>,S[-VKR:!]4[O2G$-Q^?6MGt=;iVc%dbo64Q]I5>qV:=#MLhL!<"/$::d
+ GCPh;)O0=Jmcj+!Fb-$(L,hQWkI;JeV]Ss`.BY5IGNT>q-4)bSX7Pg&'[2[BJ"J_nb6)hL<
+ 4_59aIZfMNnc2!HgG%K%/jiV(4kVt\(>+"=W?K_Db`fH#Nh,U@?/7f<4>i!EELF.X25j3c"
+ ZT^`Z29Eb]2($o#C6OLW(7=.a2n\S@Z-ibTY0I*%0"qkiol&Z96?Nbab]!Wj<`^F3(@8a"_
+ X=3a4:n#]lLja4',F[_lPa=V^'Y@norm'BTj3JiSjYj)/o7It\k`LbQjMF3aOa/'K0+_jjP
+ m;q=drg!bd<@:/:6+^n-Ec1/5fc[Ap>+fiJM:n)OWcN.Jb=W?LVe=>7Dg44st*3q'<$)ooc
+ DfROeC^Y;NB?jn&E\N8b$ZeXFNr6A!r-a*_6Z`XsKZos:5H[&r*07'ZP*kjfCCa.bS8>_.L
+ 6s3)%9p!Af*pfgkpc$XF)Uog1NI)`(e/rP-EC]QJ\*%Hk+#qbRDIeAprt7e/FdWoA!3n0,r
+ \K3If7mXp<9%p-I(7$94#aQ.n3dXm.0Tuqn@>C?GIQW-4rUV;+^?PH@PA]ZZ9ZG[V==ia5<
+ 6,d,f88(HQV"9qH:(Z(nRRd*pUukMJSX]UH9tN/o`WMj%(s;bTe88X4q`\adt7P&Iolq.>h
+ .8\%82Y?5t%gbS]_Rgcq$V+]pQ8YrXuIA'F%M3@cq-Ru=XNt[$0?PV]lmuQ=%]HO[2eYlqa
+ APHb/*VJ94YBdg0]n=)tcTQn;VkU0MI/g#m],UX=Yr6Og-b10,DG:'Yh'@PE%WYAA4h%<FH
+ MdQn%D+4b[O[\OWO4l2k(L\n`G`WKQuqBUW="PUMD'q73gc1P+N(.1^-'Z3`m.'F%=]\ec"
+ ZT^52RU:\(@\IW%Q)-qIEOY_))bA,[j1QEqdScjo*:#iYmP3R.L@?,i<hR%E%04i\D$VVB8
+ >fcdQp.RT%a7-!r02%d$/GQt(b/mG7.q*#WfO#BMRH#]j`bCP+](k$S\]?qq9qFS0`a%d$/
+ SQud8:e'Qq_oT:j5F"di?cHD_#IQicPR"IDd'=9bO>qIBn`Z34=*6];53Ro_3L.B6'beY:C
+ )*$Mc:1mA#iK5i"F"di?#0pAr3NlelqR43[beY:C))q`CnU9]4s1CR=4E)tZF"di?#0oJT3
+ k@CPNf5ucK/o;TE6^.-T[5_Tk$S\]@*ZPJh&Q6j9(BuiK/o<O3<SGdF+X9pc>OZG_&U\8S:
+ Pb(S;*%.H.4lk5NZ>`k$ZKfk*.n)?hIn=k!XZcF4$BHNBDN>3LGFZQnr\8#BMN!0F)[5D(l
+ 390F)Y'c37WOcCpa;#<&@u82EiiOb;GsnZJ@FDn\*j<msrbopN^(1Dd>WML3V'OE?+o@D8\
+ 9f_ei+B4K%QoA%T.X3C,&2.QW@osT-)J$M*5@!^tUR7;Uh.0l9.3Ur.&GPl9pcOB^8qO`dM
+ 3C7L#[I:d#SP*>&<$fLV1,@=)U85t>\bq?H=/`c`@`?[BIVum&UdS9ncGqqJ+(Z<:E;B6sC
+ $9@e06H>):/>5X:Am>mF_P?"=]:_sV+me,\!'d]P['_pQlKX;m];7J'.7?c1@Mf^f8Ye/4X
+ PK6Guq2\qPmOsW?aEC>)CR/B/M_RkD>Aic+J6mp6<\PPKf$-O%Cou2;Im[\]D7"jO)\Vjc/
+ A5NFh6polg$$pB_YuKanI/l0[riWk,=DZF/,fQHG)NMJord4.'oKIs\m7s7"srm&AGm9fHl
+ lcEr('pCH:n`i4()l>)%nQ%lI^5&A1lQ*a:X)B1JUr7^Zjc]qTe1O%u?g_o6#(+%,*KmtI3
+ _bN%YW+r;pj_A+GDg24_rPFS?iT=ZIUM*D&CR&jIT%n)C*6L#9G-mKW+ROa3*ftS;&]6^cB
+ 5I3BQ0W,EWf^Id[h^sgk%OQAGF67$q7DBGi(EKgnS[4RqW!jcQ2]Zj1^nFZ#BMR(F$_gQK/
+ o<O3DL(H?k2=,Ng#)UR"IDdj5aSXk$S\]3'!@Y3LGFZ:(HDo%d%/ZPCkl_K/o=ZjBrJ[?k2
+ <Y3LGFZR"ICo%d%./k$X45K/o<O3LJjf?k2=,%d#`2R"IDdK/l[:k$S\]@#gG'3LGFZR,d7
+ 8%d%./ju>AgK/o<O3DL(H?k2=,Ng#)UR"IDdj5fEm,?uXae)F6[T'mJbIk`s!o$j2/7V2H<
+ Y?uMPR-69ANbL4X8$3otgQQ3F6=PZ"?8Gc_,\]D=ni/@>U2Vi(dkp[U$fa#QAo'T6d6(]U<
+ 5>ccSEdOt&OU-5RE_@?cM/g*I!\%;a=gp#jilMJZZ9]HoZs$,=WSs/9t5Z>ol@NOhV\>3Dm
+ +T^g1sb?jL`Es2Pnh_-0`"Kf;Ul0P(7WRk546d0JBsr3W%k'XH0^3A$;:k)#qm-k&pE@f<j
+ )L?'DpuVR9Il/)U)0l%b#\0&%t=Ym%CJp90>,+(\V^eA<46T6gC6Vmem-QlKZ1)m^W3j,lk
+ 2W6g.*(9!ad4KdPQ3]\qJr/$TWpQ*)(Vf^8QdbX'-/[R(2lIK_A])'"F(48Y3c+LQan#JXS
+ >Gd.8#2b`bmDa:uQW07bH4!pm0#Q`mWPthCY(T2EU1"@^$30k"IpK^.nWKG9b^40\A+R.RS
+ $uB@X](D6jf_U.U?$<Dq>TBrihD;So4m#qICo,p1`5oY0"k_Jeq+G,KmNi]ig4Ar79t829!
+ Zn9h>%5G!lO(A.E*`U"`a'dn`E!LlhNt2(<JFOSld^klWf\jmF4-BBl3iQnQFtag0;#;jme
+ aWT%rB4(V[@7j5b)_CUJrp_S(I)F"di?0ZV;O*6c5=b].YW#BMR(F.=5o_&U\8*%(u40F)Y
+ Qa2u.;beY:CEH3`<F"di?S/fbh*6c5=-O4]H#BMT>ah\t@_&U[=E\I`>0F)Zh*6c5=beY8H
+ #BMR(F"kYV_&U\8*6`DC0F)YQ#BQCTbeY:C_&Og-F"di?0ZV;O*6c5=b].YW#BMR(F.=5o_
+ &U\8*%**8c1/5fIHGcrbXjEZN\-&%G0Kj>Y2:hdYu'(-:TTqiFj?L/Apo(1SQ"8,H]6()&M
+ @%S/"\g:R)>G*'[:@UBFloB(PWpJ\(=<-c<`:G8i@s+&sJ39%$Q>Aqk*jnfTEcdoB*eJs+r
+ \,;!GN7Sl7Wq.BY@"Lc0ac,aH&TjA&8fIHLIbAhcU7?WoVk4d^:FNRh.1;Fbq$7upEqXjno
+ u2G2$TO*sn"`L$7))C>\L;L:e^X](D'MWTer<h4([gUc.H?Gt^7?E886AHkBtHIL?tj4eKL
+ ,IO*Ag?UoH*d<fP*Y"<cPW"hBV0,E$kdsR>GbV":Sh@-H:iTcpq\!>%7r`BfpNC>Kpml8GZ
+ b*lL>br)C-a_;_--=p0eU"C0HFpOj6,ds]mRr41*lYO<3LGFZ9YaM1)jl7<R)9%H`O17WR"
+ ICm^en>UR"IDd_ZUsu[=L?*K/lW@;-Y'-K/o=:"<>CgJN9*M3D@4c>T9&_3LI\e*6#=:3LG
+ FZ9YaM1)jl7<R)9%H`O17WR"ICm^en>UR"IDd_ZUsu[=L?*K/lW@;-Y'-K/o=:"<>CgJN9*
+ M3D@4c>T9&_3LI\e*6#=:3LGFZ9YaM1)jl7<R)9%H`O17WR"ICm^en>UR"IDd_ZUsu[=L>j
+ YH4u[!mTurl,4Pge>'N3HMVrp<ik:UJ%e'KRb]HaCcH^G;(&V8l`@a9?Blm1*kBT'n,+gn0
+ Tp>~>
+Q
+Q Q
+showpage
+%%Trailer
+end
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-dpi_expected.pdf b/testfiles/cli_tests/testcases/export-dpi_expected.pdf
new file mode 100644
index 0000000..9a2bb9f
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-dpi_expected.pdf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-dpi_expected.png b/testfiles/cli_tests/testcases/export-dpi_expected.png
new file mode 100644
index 0000000..cf1781e
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-dpi_expected.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-dpi_expected.ps b/testfiles/cli_tests/testcases/export-dpi_expected.ps
new file mode 100644
index 0000000..a2b5a45
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-dpi_expected.ps
@@ -0,0 +1,298 @@
+%!PS-Adobe-3.0
+%%Creator: cairo 1.15.10 (http://cairographics.org)
+%%CreationDate: Thu Mar 5 09:46:34 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 3
+%%DocumentMedia: 61x32mm 173 90 0 () ()
+%%BoundingBox: 0 0 173 90
+%%EndComments
+%%BeginProlog
+/languagelevel where
+{ pop languagelevel } { 1 } ifelse
+3 lt { /Helvetica findfont 12 scalefont setfont 50 500 moveto
+ (This print job requires a PostScript Language Level 3 printer.) show
+ showpage quit } if
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+/cairo_set_page_size {
+ % Change paper size, but only if different from previous paper size otherwise
+ % duplex fails. PLRM specifies a tolerance of 5 pts when matching paper size
+ % so we use the same when checking if the size changes.
+ /setpagedevice where {
+ pop currentpagedevice
+ /PageSize known {
+ 2 copy
+ currentpagedevice /PageSize get aload pop
+ exch 4 1 roll
+ sub abs 5 gt
+ 3 1 roll
+ sub abs 5 gt
+ or
+ } {
+ true
+ } ifelse
+ {
+ 2 array astore
+ 2 dict begin
+ /PageSize exch def
+ /ImagingBBox null def
+ currentdict end
+ setpagedevice
+ } {
+ pop pop
+ } ifelse
+ } {
+ pop
+ } ifelse
+} def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageMedia: 61x32mm
+%%PageBoundingBox: 0 0 173 90
+173 90 cairo_set_page_size
+%%EndPageSetup
+q 0 0 173 90 rectclip
+1 0 0 -1 0 90 cm q
+0.9 0.950196 0.9 rg
+90 45 m 90 69.852 69.852 90 45 90 c 20.148 90 0 69.852 0 45 c 0 20.148
+20.148 0 45 0 c 69.852 0 90 20.148 90 45 c f
+0 0.501961 0 rg
+82.5 45 m 82.5 65.711 65.711 82.5 45 82.5 c 24.289 82.5 7.5 65.711 7.5
+45 c 7.5 24.289 24.289 7.5 45 7.5 c 65.711 7.5 82.5 24.289 82.5 45 c f
+Q q
+82 0 91 90 re W n
+q
+82 0 91 90 re W n
+% Fallback Image: x=82 y=0 w=91 h=90 res=300ppi size=427500
+[ 91.2 0 0 -90 82 90 ] concat
+/cairo_ascii85_file currentfile /ASCII85Decode filter def
+/DeviceRGB setcolorspace
+<<
+ /ImageType 1
+ /Width 380
+ /Height 375
+ /Interpolate false
+ /BitsPerComponent 8
+ /Decode [ 0 1 0 1 0 1 ]
+ /DataSource cairo_ascii85_file /FlateDecode filter
+ /ImageMatrix [ 380 0 0 -375 0 375 ]
+>>
+cairo_image
+ Gb"0WH'"9PS\T5"b>sR!5geEA>#*o-O@iO4X\%*-qcMm@A1J8n(%L39mSUBAm&DJ3@:ip8)
+ &VLH0Le;2,\jHDCQ=u(:dd_5R$mA&Hg^cQIr2eK@?;Q,q=WJOchCMul8"XcoqhP25Q/l$&'
+ n<a=Y]l$e'lpth:o&06UsLLHX2%i]jYpmGE]YOWEc85od<KkkZ$,8VKGqr2Qt7:-')?F?!=
+ G)ijoKk-,M'kinDa_Y#9\[mj_.*Y"H7?<B=H9>cZ;-;Q[:r[X5ERX3R;3b[Eh$MJE<8#+sH
+ ql'fM<%cb>QXprUk*6_gmNoJd.*6c5=-K$;T%EuX.b[CR5inD0fbeY8Gi$b`;beY:C@02JK
+ g;"]P_&Of[.'=$'_&U[-!fl9o^`:S7*%'SlXprUk*6_gmNoJd.*6c5=-K$;T%EuX.b[CR5i
+ nD0fbeY8Gi$b`;beY:C@02JKg;"]P_&Of[.'=$'_&U[-!fl9o^`:S7*%'SlXprUk*6_gmNo
+ Jd.*6c5=-K$;T%EuX.b[CR5inD0fbeY8Gi$bafT&-'15+NTQ)LQn)Zn(3Op67M%]:7T1a[&
+ Z[SCC0sPiP]C:jJe2Y+@8J=&<mBL&2KkPc(_":TKNDNfG0OW`uMS>:NYLZL5o^]q68;7GSd
+ NphA!N>+I^Oq2&*.=&cS.WB=7!h/-=K@W,VWf")4_-,TGS\%bupH$Akg.GUH:5tWV"$7cHh
+ 'e[B24b`3[f-_7Z,e=W5qmTl2p=qaUrU-66RJ4Y.-Q_1=\4TD;/l=VT.CL?Wad&[m>jX'!=
+ &`=5rpn1+]PI<1SMYNdlgF)i>4(:_E%g-H1oIM$a+\fHXgLS47JWPQ!`+AUq<iX@Lt:[kMp
+ 7;?:V_7!qm-[>0/RUmQ8t"DH1]KCbkh/A3iB@&pDCS.3j'T#jgj)D[R]13b[3ZXB246-2ru
+ _@&gd\)OaQ<$Fj*E08sW+8/g^,M:-^GU76;9Q2g<GM@7MX&6KOH-R(Sg]_K_@/b%H_>F-s9
+ Bk$S\]@#gG'3LGFZR,d78%d%./ju>AgK/o<O3DL(H?k2=,Ng#)UR"IDdj5aSXk$S\]3'!@Y
+ 3LGFZ:(HDo%d%/ZPCkl_K/o=ZjBrJ[?k2<Y3LGFZR"ICo%d%./k$X45K/o<O3LJjf?k2=,%
+ d#`2R"IDdK/l[:k$S\]@#gG'3LGFZR,d78%d%./ju>AgK/o<O3DN?,=++8U_DaPg[Md\'kM
+ 'dlUU/QG8l:IN7gXkUL;atI5JQN0\M+ig=DZj,Xn1[mSLa8uVfbV[g:XgC:R!uAlo!h,VQI
+ kI0quZ_Im*`n=pmOL([_Gh^9&%F'p<bPhsPX3PmXoub)6/%oL"npjfeR[^>H-I\&PfRCS'A
+ ]m"2\^4SADHk08?bp2Bs*S):l]d$&B3Bmr8F5M)nuX.o;XR6OR>ptkQ8G*hVtGm3$KDHU.C
+ /QLqUp3(1&Gh[BF<W%HIWjU!um&=Zt0WMZM%[)/e2uQ!f`k`(Y%V3XH*e+(H)7_P8k0>etf
+ &g-+pk.3@>ShJ;okU`imr)P2RqlBEp=e%(?+'7'X&eJ\F\:))CNZ3sBkkrC=h1jI?U=^MQr
+ KT?I4.2Kj%T')f,p><2-I*EFnKo\Fj$RoNndUDS(o3B@Ie#prDE:biq47J2jfh1OsUFEJ+N
+ W&Z6i;fR:9.(mnp6_d8?[Z7#`oQHHaI\,CPpS8qi#%(j>$3.ok"Y,t[EZP/ho*mhL">?brq
+ (.u[+cR%AIDc+IP/a"!N-4A?mg`#Pmp0\i0,C37]T#KLIPPL#a.,sF-o'7#&sg+Odc:R4Fb
+ T"O7/,qq6'XEc$lNP'SfeLHD9>%sOj,[@91;:<CaM:4@W;gAQ,?1AdSASaK=d6(]U\Bhu2b
+ eY:C$.3,jk$S\]3'!@Y3LGFZ:(HDo%d%/ZPCkl_K/o=ZjBrJ[?k2<Y3LGFZR"ICo%d%./k$
+ X45K/o<O3LJjf?k2=,%d#`2R"IDdK/l[:k$S\]@#gG'3LGFZR,d78%d%./ju>AgK/o<O3DL
+ (H?k2=,Ng#)UR"IDdj5aSXk$S\]3'!@Y3LGFZ:(HDo%d%/ZPG;fOia+F152IPoCb47Q;Ykr
+ d3QM<+4YS1/MC_rKr8qZu8(XiV7dK=\^2eI/RPQR.S<c1a2bc5\"f6mHKZoK4dgr=Fa'LPf
+ O)JAZQ25jMS'"MbS1JM.2=+l68[0JeQ'gW)1/&051O%u0=ng%L2/dldpebfrNK+ooSm7OaR
+ o3s;1a@u^3C<&57RcQ(;:=8<G>HAMV"+j^k=;EaV^*'+S!6m:,`eSJITG#r<n^Ab?>"2SE1
+ q:iL2M@PI_BIZBW,^MX7)uGbhM:)4d>9I].b\o@"oZ^^5Vr,:VH_)l[N[%*N=5k?SVKL="a
+ OPZL=:WSX7'3kt@@LqP$;D?6"!*Vt)D7]6p$lb9u,9I.PZBr60Oc8auuSfcOd<*69h[h;n:
+ sBP_hmqKGg*Q.mU]$(X*6ljRd%\)"*p&`0'mGM\R+dk/\NR$McN]R=/\b%6pQXH7t-F\,p1
+ _Bg"BML9jHjmRZeQ1(]bJj2j)afR#$RP-.)?/@X$,-<iL4S]&ps42+[D<Bo,S7DPXhi.k!b
+ !GGf]1_"DA9jR*1[!lCAC6j$3FOX4hD/&:53[ft)fA<^9k@VLk/W?>L>R%DkAGHQ%d%./k$
+ S\]@*Soik$0b*q#=L_Yl0rA3LGFZB:Di^1j(]b%d$0.QugZB;pejtg!cpCfRD&\*6c6hZd%
+ as<4>p&p%N/@)07J:F"h7bS8OA:f[6X?AB_k0be]g#F+u]H-BH_Z=I=_$R"IC$B:q98/_,8
+ t$=@4M%d%./jp[c2k"*afc/qO9*6c5=bS`BQ2+RY>]=6PaaI%T?R"IC$kH*f:2(^\?8&cOm
+ _&UZ:38\>.eCrH13LCIo@Eq;X:1\LCahJ+&0F)YQkIf$$9KoV33LGFZQug"(83q_])07J:F
+ "esIF1eV]be]gj$$._FF/4ia%a;bZ#RPQg0W2LY)KRS[6Ga]]b`Q'i>;-;'+sG18`15!4b4
+ GRI/l_hSrk2pXfa.&^7[*`hiSD3;mTWS,Ol^!gk$ZJAR$2d4D(,gM8r>-Tb<1lS?BT3UfD@
+ )opR8WXbrOZg_WpIQPOkkmX#7)XX[\nq,ZdmL/(p__U(fW/5Ge>-NCc(OXc?gK-/E[^Z7FR
+ Ck(5K]ENbcP-iR4Z;+XX^U=L-_:N<3J<TL3fYuL$Em-3mDOZDMtjkmKo?s,j"B2</aDtdZ)
+ lKKdt/1L'&'Y',PoG$GQkPa\R?VS_62C!!l8Md\c:=#@m@`8VTS^`D,E:I;n1s!TfXmPp2V
+ GE_I1WsaFqoYN#m]gto.Z+Y<6%?"BXhr@+0E6*;L,ih)kM!;MY,uU2k/\W&c^gEkc8TQhq>
+ LV?+.SQZBhHAZPGVqeSX_E[f^`HSGLSP+<6ucZh_H)3^Lau<mI"[kdgT=TPY>Gr^>(%'g?.
+ "[]^LX!SAF+JHt,p^WB#!s,j%4rq'"]`cnJ."Mr?1@@h,K9poU6:2Vo[&od["Jb]-Xmbfu9
+ eAWGhgo3r3lcl$7ekff1h5t?,(k$S[i*p/.$S3c#s?k-<Sk(M5(q;2>QXq'TU<FJWnbo+_9
+ YV:j6$?JON:3g>p=)So;KRKguKS2@Z2FhLqDnqmI06N/60ZO38gl63q$2[Vu#RPPl0LomGC
+ rRHh9/+C8F8Xd#@1En33+IsoB7`-3R,aPoeLf==6\II[R3S9q=!98QX9s$=1oJ($*coEn0Z
+ Qb+cMBnLbTX%(*%0!n=uokALMRHA*&a^l[0C]njtOhaEsDW@3TcE'@?/7fPRdK]/'K0;_Aq
+ a`nMGMFLTCr+Ji;d3Hd)\pXfLi2F,DK+rFd&00$&+s4d<!TcEo.[lf^,pBd5aaTi:3E1HfN
+ b\N/*1^AO2<j"e7O,PF;kWbnjQC*YfUB2<06Vf6S-hL/u49Tkm84f*KHaF.H*qF"c4YXo^r
+ [.]q#\1i\5V7>.$?"nN)/W.D[:S5#cMk._=iAKi&V5qRb04E]Tk'ZUqr6q*1O453WU8-S?o
+ c<_>g3jq>VNmsr(WIg:-CC%]8rP9Vqbd9P`^Z"0+#DcXEREg$^,5K1(GAc;m-C/c-`of`NK
+ +pZZIG^NHWR$XVoV;;SpU^kf\-&E"`@.BHek['4T4C^XZ\/r"C5S3V13^Lg:#_bU==>]lU2
+ 0!j<,+F-ccZ=lkhW-C<Y&$6\i<."ER*m7<AHWc/5QQF_U[>jhc_aopb$W%*P*^['i0M2<SN
+ 18NEY]k&'jq04#`)&8MQ3r/nsg+3+7U`>f2*-kn,!1Fd.h\<q7cm&\jG,,m!4cl@NICX-S#
+ T4&44)!6Lp*<2@,Qp\<93FOc-7)K>T+ksl(0Ua4n?==>s(?U:t*3$NO)KX!"0IONqaQ[+C=
+ V^(Pf:4M,?XO@H($:2$S0PF3C>*DVL[5=g_JYCGfo8"&:RDf=F,ncm_]5s0bR(mA''>9=@8
+ >J"3F$Vbfp.LVA4`.%$0K<S@LcNRk!Xe]C\-.e%TsG,7"RF7p8u.NZ6[YN&9D=O@PERR#]h
+ 3.S2L2q?k1j$k!Y"cX7ra9*3pa3M%F`omp;#7A4)^t+m(?1^!,uL@E0A!+MsYAc116[ULqf
+ ,bG+`$)jsX_qiHlA3Eg2Cm-C/cV[,+LdPRjVK_IK^Wt--rm!M_0afBbq?2E`\X7rc_)jR'0
+ E+8BI-H&Uq[:YYQq[B6ofkZ=/P+ju9]k15E]6p:as7Jm'bq!W^p$Z*thAPGqW]jDC.5J!W;
+ sa(b(;7'<Yr<b/Y^O?N>t[kSj8@a%s),c4&Qc0Ig9@VY?A0+.iJgr"@cXPO,?GiG?h![ERf
+ E;gkF[+a3Q%0L>U"A(SgKNRm?lN*eR+^8ERi)^\'*9kMW-sHiNkV<SbM9Za5;2JcnJ%O,N]
+ DA0jMXN%s'/9'6DCT?N'>sO2(M#Lp;3f@ZqSp=<noe3ojubPrT3G3N&8i[lOoS=_M9)r805
+ alq,A?8];Z63u=2Ccg6#`F,1#<k*VY0e;gF\LM2cXNk%/G^AT>>&.LD=0fS9P\":ATU-XMo
+ k2o4%6Suk<8&ZJDB?VM;9UMSacI4kH`Z1_hKK1OER/H.TF3(BJ0rM[`4ET^:fY.?,L^'&+k
+ 3P:B4*9W/IsXBA&.Kt5bXjEZ%\5U,5;'trZ7:4Fq9Sg81N_cHr2(k(OB'tDcKm-h6YDGJ2a
+ )?hq4!tC6WRcZ3Hq@V),bOIfb*:`H=@@ePs+8gN>W[5F+XCI%d&jQfji=SV*j'5ma.@8oLF
+ !WLTJW>F"Vb.'!WX&jokVp*R#\-ZHA]>8%+/tS=omO$d9cpZHA\["m\Kf:=('j`#Q'4X3CD
+ .&,nY4jokXf5g1CPZHA\+3u:>S3K&)5`Z2:a#W,$YOZBbWbZ<M7k*[79bhY1$hR7_/r#Z;/
+ h\NKK$sAnU?8IXZHYggL-2u.uN8!O.cZ(nSe]jMR1]:_C_1M4Z_[8P\H"D9m[a+d#++EnKf
+ 7/4K;uW:gF10<'G4i7EY./N)MIGWVH!s7r(DY=X\mkIM_cI=L=m^7W#s5t/GGgnAFfohZnp
+ k\!U>^cNVjhbJqE)Wcr3C\jF10?jj_MUnh&Oh[J,\W]S?6k8I2-hEQGac.V(TlZ^6fSP7<d
+ lO\7L]HS0iZ*ILu5Uc24"3jug6:;9U1EG?1+VQT!t;Kf?'Vq<Ol,NB'Dcbpme1!u2&-B?kG
+ QpatV>SNGTMEHte41hR3Y&-^-5F7tme_]5C)At(f154N:0+UV_fRIf;*pFq@NN7hggp<U1&
+ cCER+bf1WsF(^t&gQ$*f*25-!RlRYoIN<Kc_O,3&Cd#'=F,7]#D<C?d*2;q7RlTpYHT-'/n
+ 8g+SJD"P[#:D^N*25-!RlPggpL4V0(phJW6?aU8&;;jnb`O-*cl@M<S1%37R58sL;#^G6U@
+ ;c[3S1@H#'5U;RoSVKA*cEh%T.JWb+.5<hp/qH7,cB?F/EMEMn&_f[^*X_fp,S7><VPs,F\
+ LS^AZ:2/T^)lRHO%DXcG'h^@6MSZt%LigY'W2/BXu@4;[EjW4?:2p1NJ>7B#bN`3q%Fpqn`
+ @\Tm'Cp+%MKZ>./Fs8&AfMDTB"bTX$E5]c!A%$<QtqkHMkcMTWa@h)AOX>8u3_lnj@S1!3A
+ &p&j1AsG@[jTV>6B>Id[0S_&ap6bGQ9E.W`A*D-^lKtK'LF?R(QnpdLF@C7>kfe>KmAD[o-
+ t)b9oCK>]LX_i,hL5ZML]V1L3WJZjIgN)763lWa"jA$/ro;i7#cTi#JV,sn>P=GJi(=9G3C
+ /-;&9Gq$lnR[U'-DnQS=i(XLcK!Mp6\e4=;j5AK)T^!k)aH5@uc'Q3cfoPE";#lD./H^3C/
+ -S#BRtp0Ls)%q.Inp)GHNn@SY;.?k1j*k*VWJnq1u,+ksjRF)us4>:_I`/.?UDbq]$f($:T
+ p@?.lYcYO-_[lZ="/.?UDbq]$f($:Tp=_NiH?8I%O*oH\$#rmjqCPFB5EN^5pF8VA5kfc%d
+ I7W<\a&D?q\E#dNh`tMRF,<J/*&2*E$AB8tF"$SmF%3;N3HDN)rWPOdXng<3H=#5Y9J44YW
+ ^$#1KuBLV[7:a/+#`K7RSW#R?etVEdQ]Bj[:e%$r,k:A>7_a;(Gd4P_Pf>e`^Ge@/UGXfV*
+ g$N0LXtV0C4U\+t&Wjj_n,JW,XQP`^Z"H/S=A?&!nXX,fJfbR9/*E>2NZTD=a4Jhu=MTI.$
+ r<`%^OKm?,:DLY(lsZY]BAkl&nK"`ZgE?P"mWr%RPoUSC:/]F*%r`>l"f&b^?BOcuM"USC>
+ ??TVTH0lQ1AQ-"a%_N&n^J'u]k:tK#dhAPIsI?(HjEj&q_:1:dT6.[+EF%AIMq]$>lA@;<Y
+ ES4pKo%hJ`^qh#%:r8_.0k]%n<L?-3KK0WRcC4]I0SaHMF!APmR/@fF+WJ:g#VjBh)p"C6(
+ J:&.$0K1e`P`P1@Lh'&k!Xe]+pR1k-"0OoR$6T'_Aoj,bR(mAde_trQp[0m3FOl0bIT>]L[
+ 5=g_JW+oF#iBE4^h+<c)GT_`Z293bR(I5?dsM_D>.KLfb<M$f:9.D1<^oB_OU+a;q5'*3Fl
+ -W'1R%,X8T-6UE4KtW\>fZk+$*EJN77FQkMr1j<0gW[Y6a#1.T'_P%.r2fbW_'b8g>p$?O;
+ *0IL,f<45d&%pRdhRp)%krqGSHEf^U4'3*0n'?+96'U!R_;#s0jn_oA$_B_Kciq)3,d$`qO
+ CW?BIRciF&mnpX5nb)Q=3,'[QEgMqcQs`gd.oFsp;>cL9#ok$ChQV"Ij+b:d?8JCLU,c_Xa
+ '?+I3lCk'IeB;_o%#J\Qri-D0'7P&JK$Gm*7:jpTes8o>ALis\tncNm`qD!=+tEsq7cghqb
+ >4SkNskUqqu6#:<K(`-#8BHP;=Qs>;>KM="aC,/)N/LZBm3`o&>b806N/6^AIJF<>LjA:\m
+ 6'ZDL@@RVLK>bUL0<gf<PebU_!qYGJ9gY&"%@DV/%j2h#g9Z_5QWm:qomng?p[W@B@d'GOR
+ ^)W`ElpZ+R:g;>,S[-VKR:!]4[O2G$-Q^?6MGt=;iVc%dbo64Q]I5>qV:=#MLhL!<"/$::d
+ GCPh;)O0=Jmcj+!Fb-$(L,hQWkI;JeV]Ss`.BY5IGNT>q-4)bSX7Pg&'[2[BJ"J_nb6)hL<
+ 4_59aIZfMNnc2!HgG%K%/jiV(4kVt\(>+"=W?K_Db`fH#Nh,U@?/7f<4>i!EELF.X25j3c"
+ ZT^`Z29Eb]2($o#C6OLW(7=.a2n\S@Z-ibTY0I*%0"qkiol&Z96?Nbab]!Wj<`^F3(@8a"_
+ X=3a4:n#]lLja4',F[_lPa=V^'Y@norm'BTj3JiSjYj)/o7It\k`LbQjMF3aOa/'K0+_jjP
+ m;q=drg!bd<@:/:6+^n-Ec1/5fc[Ap>+fiJM:n)OWcN.Jb=W?LVe=>7Dg44st*3q'<$)ooc
+ DfROeC^Y;NB?jn&E\N8b$ZeXFNr6A!r-a*_6Z`XsKZos:5H[&r*07'ZP*kjfCCa.bS8>_.L
+ 6s3)%9p!Af*pfgkpc$XF)Uog1NI)`(e/rP-EC]QJ\*%Hk+#qbRDIeAprt7e/FdWoA!3n0,r
+ \K3If7mXp<9%p-I(7$94#aQ.n3dXm.0Tuqn@>C?GIQW-4rUV;+^?PH@PA]ZZ9ZG[V==ia5<
+ 6,d,f88(HQV"9qH:(Z(nRRd*pUukMJSX]UH9tN/o`WMj%(s;bTe88X4q`\adt7P&Iolq.>h
+ .8\%82Y?5t%gbS]_Rgcq$V+]pQ8YrXuIA'F%M3@cq-Ru=XNt[$0?PV]lmuQ=%]HO[2eYlqa
+ APHb/*VJ94YBdg0]n=)tcTQn;VkU0MI/g#m],UX=Yr6Og-b10,DG:'Yh'@PE%WYAA4h%<FH
+ MdQn%D+4b[O[\OWO4l2k(L\n`G`WKQuqBUW="PUMD'q73gc1P+N(.1^-'Z3`m.'F%=]\ec"
+ ZT^52RU:\(@\IW%Q)-qIEOY_))bA,[j1QEqdScjo*:#iYmP3R.L@?,i<hR%E%04i\D$VVB8
+ >fcdQp.RT%a7-!r02%d$/GQt(b/mG7.q*#WfO#BMRH#]j`bCP+](k$S\]?qq9qFS0`a%d$/
+ SQud8:e'Qq_oT:j5F"di?cHD_#IQicPR"IDd'=9bO>qIBn`Z34=*6];53Ro_3L.B6'beY:C
+ )*$Mc:1mA#iK5i"F"di?#0pAr3NlelqR43[beY:C))q`CnU9]4s1CR=4E)tZF"di?#0oJT3
+ k@CPNf5ucK/o;TE6^.-T[5_Tk$S\]@*ZPJh&Q6j9(BuiK/o<O3<SGdF+X9pc>OZG_&U\8S:
+ Pb(S;*%.H.4lk5NZ>`k$ZKfk*.n)?hIn=k!XZcF4$BHNBDN>3LGFZQnr\8#BMN!0F)[5D(l
+ 390F)Y'c37WOcCpa;#<&@u82EiiOb;GsnZJ@FDn\*j<msrbopN^(1Dd>WML3V'OE?+o@D8\
+ 9f_ei+B4K%QoA%T.X3C,&2.QW@osT-)J$M*5@!^tUR7;Uh.0l9.3Ur.&GPl9pcOB^8qO`dM
+ 3C7L#[I:d#SP*>&<$fLV1,@=)U85t>\bq?H=/`c`@`?[BIVum&UdS9ncGqqJ+(Z<:E;B6sC
+ $9@e06H>):/>5X:Am>mF_P?"=]:_sV+me,\!'d]P['_pQlKX;m];7J'.7?c1@Mf^f8Ye/4X
+ PK6Guq2\qPmOsW?aEC>)CR/B/M_RkD>Aic+J6mp6<\PPKf$-O%Cou2;Im[\]D7"jO)\Vjc/
+ A5NFh6polg$$pB_YuKanI/l0[riWk,=DZF/,fQHG)NMJord4.'oKIs\m7s7"srm&AGm9fHl
+ lcEr('pCH:n`i4()l>)%nQ%lI^5&A1lQ*a:X)B1JUr7^Zjc]qTe1O%u?g_o6#(+%,*KmtI3
+ _bN%YW+r;pj_A+GDg24_rPFS?iT=ZIUM*D&CR&jIT%n)C*6L#9G-mKW+ROa3*ftS;&]6^cB
+ 5I3BQ0W,EWf^Id[h^sgk%OQAGF67$q7DBGi(EKgnS[4RqW!jcQ2]Zj1^nFZ#BMR(F$_gQK/
+ o<O3DL(H?k2=,Ng#)UR"IDdj5aSXk$S\]3'!@Y3LGFZ:(HDo%d%/ZPCkl_K/o=ZjBrJ[?k2
+ <Y3LGFZR"ICo%d%./k$X45K/o<O3LJjf?k2=,%d#`2R"IDdK/l[:k$S\]@#gG'3LGFZR,d7
+ 8%d%./ju>AgK/o<O3DL(H?k2=,Ng#)UR"IDdj5fEm,?uXae)F6[T'mJbIk`s!o$j2/7V2H<
+ Y?uMPR-69ANbL4X8$3otgQQ3F6=PZ"?8Gc_,\]D=ni/@>U2Vi(dkp[U$fa#QAo'T6d6(]U<
+ 5>ccSEdOt&OU-5RE_@?cM/g*I!\%;a=gp#jilMJZZ9]HoZs$,=WSs/9t5Z>ol@NOhV\>3Dm
+ +T^g1sb?jL`Es2Pnh_-0`"Kf;Ul0P(7WRk546d0JBsr3W%k'XH0^3A$;:k)#qm-k&pE@f<j
+ )L?'DpuVR9Il/)U)0l%b#\0&%t=Ym%CJp90>,+(\V^eA<46T6gC6Vmem-QlKZ1)m^W3j,lk
+ 2W6g.*(9!ad4KdPQ3]\qJr/$TWpQ*)(Vf^8QdbX'-/[R(2lIK_A])'"F(48Y3c+LQan#JXS
+ >Gd.8#2b`bmDa:uQW07bH4!pm0#Q`mWPthCY(T2EU1"@^$30k"IpK^.nWKG9b^40\A+R.RS
+ $uB@X](D6jf_U.U?$<Dq>TBrihD;So4m#qICo,p1`5oY0"k_Jeq+G,KmNi]ig4Ar79t829!
+ Zn9h>%5G!lO(A.E*`U"`a'dn`E!LlhNt2(<JFOSld^klWf\jmF4-BBl3iQnQFtag0;#;jme
+ aWT%rB4(V[@7j5b)_CUJrp_S(I)F"di?0ZV;O*6c5=b].YW#BMR(F.=5o_&U\8*%(u40F)Y
+ Qa2u.;beY:CEH3`<F"di?S/fbh*6c5=-O4]H#BMT>ah\t@_&U[=E\I`>0F)Zh*6c5=beY8H
+ #BMR(F"kYV_&U\8*6`DC0F)YQ#BQCTbeY:C_&Og-F"di?0ZV;O*6c5=b].YW#BMR(F.=5o_
+ &U\8*%**8c1/5fIHGcrbXjEZN\-&%G0Kj>Y2:hdYu'(-:TTqiFj?L/Apo(1SQ"8,H]6()&M
+ @%S/"\g:R)>G*'[:@UBFloB(PWpJ\(=<-c<`:G8i@s+&sJ39%$Q>Aqk*jnfTEcdoB*eJs+r
+ \,;!GN7Sl7Wq.BY@"Lc0ac,aH&TjA&8fIHLIbAhcU7?WoVk4d^:FNRh.1;Fbq$7upEqXjno
+ u2G2$TO*sn"`L$7))C>\L;L:e^X](D'MWTer<h4([gUc.H?Gt^7?E886AHkBtHIL?tj4eKL
+ ,IO*Ag?UoH*d<fP*Y"<cPW"hBV0,E$kdsR>GbV":Sh@-H:iTcpq\!>%7r`BfpNC>Kpml8GZ
+ b*lL>br)C-a_;_--=p0eU"C0HFpOj6,ds]mRr41*lYO<3LGFZ9YaM1)jl7<R)9%H`O17WR"
+ ICm^en>UR"IDd_ZUsu[=L?*K/lW@;-Y'-K/o=:"<>CgJN9*M3D@4c>T9&_3LI\e*6#=:3LG
+ FZ9YaM1)jl7<R)9%H`O17WR"ICm^en>UR"IDd_ZUsu[=L?*K/lW@;-Y'-K/o=:"<>CgJN9*
+ M3D@4c>T9&_3LI\e*6#=:3LGFZ9YaM1)jl7<R)9%H`O17WR"ICm^en>UR"IDd_ZUsu[=L>j
+ YH4u[!mTurl,4Pge>'N3HMVrp<ik:UJ%e'KRb]HaCcH^G;(&V8l`@a9?Blm1*kBT'n,+gn0
+ Tp>~>
+Q
+Q Q
+showpage
+%%Trailer
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-height_expected.png b/testfiles/cli_tests/testcases/export-height_expected.png
new file mode 100644
index 0000000..e6ab7ed
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-height_expected.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_drawing_expected.emf b/testfiles/cli_tests/testcases/export-margin_drawing_expected.emf
new file mode 100644
index 0000000..9e23657
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_drawing_expected.emf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_drawing_expected.eps b/testfiles/cli_tests/testcases/export-margin_drawing_expected.eps
new file mode 100644
index 0000000..3029a52
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_drawing_expected.eps
@@ -0,0 +1,82 @@
+%!PS-Adobe-3.0 EPSF-3.0
+%%Creator: cairo 1.15.10 (http://cairographics.org)
+%%CreationDate: Fri Mar 20 19:25:06 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%BoundingBox: 0 0 150 150
+%%EndComments
+%%BeginProlog
+50 dict begin
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageBoundingBox: 0 0 150 150
+%%EndPageSetup
+q 37 37 76 76 rectclip
+1 0 0 -1 0 150 cm q
+0 0 1 rg
+37.5 37.5 37.5 75 re f
+1 0 0 rg
+75 37.5 37.5 75 re f
+Q Q
+showpage
+%%Trailer
+end
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-margin_drawing_expected.pdf b/testfiles/cli_tests/testcases/export-margin_drawing_expected.pdf
new file mode 100644
index 0000000..0535214
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_drawing_expected.pdf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_drawing_expected.png b/testfiles/cli_tests/testcases/export-margin_drawing_expected.png
new file mode 100644
index 0000000..de4aeba
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_drawing_expected.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_drawing_expected.ps b/testfiles/cli_tests/testcases/export-margin_drawing_expected.ps
new file mode 100644
index 0000000..025c45c
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_drawing_expected.ps
@@ -0,0 +1,119 @@
+%!PS-Adobe-3.0
+%%Creator: cairo 1.15.10 (http://cairographics.org)
+%%CreationDate: Fri Mar 20 19:25:05 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%DocumentMedia: 53x53mm 150 150 0 () ()
+%%BoundingBox: 37 37 113 113
+%%EndComments
+%%BeginProlog
+/languagelevel where
+{ pop languagelevel } { 1 } ifelse
+2 lt { /Helvetica findfont 12 scalefont setfont 50 500 moveto
+ (This print job requires a PostScript Language Level 2 printer.) show
+ showpage quit } if
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+/cairo_set_page_size {
+ % Change paper size, but only if different from previous paper size otherwise
+ % duplex fails. PLRM specifies a tolerance of 5 pts when matching paper size
+ % so we use the same when checking if the size changes.
+ /setpagedevice where {
+ pop currentpagedevice
+ /PageSize known {
+ 2 copy
+ currentpagedevice /PageSize get aload pop
+ exch 4 1 roll
+ sub abs 5 gt
+ 3 1 roll
+ sub abs 5 gt
+ or
+ } {
+ true
+ } ifelse
+ {
+ 2 array astore
+ 2 dict begin
+ /PageSize exch def
+ /ImagingBBox null def
+ currentdict end
+ setpagedevice
+ } {
+ pop pop
+ } ifelse
+ } {
+ pop
+ } ifelse
+} def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageMedia: 53x53mm
+%%PageBoundingBox: 37 37 113 113
+150 150 cairo_set_page_size
+%%EndPageSetup
+q 37 37 76 76 rectclip
+1 0 0 -1 0 150 cm q
+0 0 1 rg
+37.5 37.5 37.5 75 re f
+1 0 0 rg
+75 37.5 37.5 75 re f
+Q Q
+showpage
+%%Trailer
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-margin_drawing_expected.svg b/testfiles/cli_tests/testcases/export-margin_drawing_expected.svg
new file mode 100644
index 0000000..e22c4ef
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_drawing_expected.svg
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ sodipodi:docname="square_px.svg"
+ id="svg4"
+ version="1.1"
+ height="200"
+ width="200">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs8" />
+ <sodipodi:namedview
+ fit-margin-bottom="50"
+ fit-margin-right="50"
+ fit-margin-left="50"
+ fit-margin-top="50"
+ id="namedview6"
+ inkscape:window-height="480"
+ inkscape:window-width="640"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0"
+ guidetolerance="10"
+ gridtolerance="10"
+ objecttolerance="10"
+ borderopacity="1"
+ bordercolor="#666666"
+ pagecolor="#ffffff" />
+ <rect
+ id="square-blue"
+ fill="#0000ff"
+ height="100"
+ width="50"
+ y="50"
+ x="50" />
+ <rect
+ id="square-red"
+ fill="#ff0000"
+ height="100"
+ width="50"
+ y="50"
+ x="100" />
+</svg>
diff --git a/testfiles/cli_tests/testcases/export-margin_drawing_expected.wmf b/testfiles/cli_tests/testcases/export-margin_drawing_expected.wmf
new file mode 100644
index 0000000..d37195f
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_drawing_expected.wmf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_export-area_expected.png b/testfiles/cli_tests/testcases/export-margin_export-area_expected.png
new file mode 100644
index 0000000..2e53613
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_export-area_expected.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_export-id_expected.emf b/testfiles/cli_tests/testcases/export-margin_export-id_expected.emf
new file mode 100644
index 0000000..3802d6d
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_export-id_expected.emf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_export-id_expected.eps b/testfiles/cli_tests/testcases/export-margin_export-id_expected.eps
new file mode 100644
index 0000000..6f5f368
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_export-id_expected.eps
@@ -0,0 +1,82 @@
+%!PS-Adobe-3.0 EPSF-3.0
+%%Creator: cairo 1.16.0 (https://cairographics.org)
+%%CreationDate: Fri Mar 20 20:03:34 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%BoundingBox: 0 0 113 150
+%%EndComments
+%%BeginProlog
+50 dict begin
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageBoundingBox: 0 0 113 150
+%%EndPageSetup
+q 0 37 75 76 rectclip
+1 0 0 -1 0 150 cm q
+0 0 1 rg
+0 37.5 37.5 75 re f
+1 0 0 rg
+37.5 37.5 37.5 75 re f
+Q Q
+showpage
+%%Trailer
+end
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-margin_export-id_expected.pdf b/testfiles/cli_tests/testcases/export-margin_export-id_expected.pdf
new file mode 100644
index 0000000..3f2adf7
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_export-id_expected.pdf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_export-id_expected.png b/testfiles/cli_tests/testcases/export-margin_export-id_expected.png
new file mode 100644
index 0000000..d044884
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_export-id_expected.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_export-id_expected.ps b/testfiles/cli_tests/testcases/export-margin_export-id_expected.ps
new file mode 100644
index 0000000..47be99f
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_export-id_expected.ps
@@ -0,0 +1,119 @@
+%!PS-Adobe-3.0
+%%Creator: cairo 1.16.0 (https://cairographics.org)
+%%CreationDate: Fri Mar 20 20:03:12 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%DocumentMedia: 40x53mm 113 150 0 () ()
+%%BoundingBox: 0 37 75 113
+%%EndComments
+%%BeginProlog
+/languagelevel where
+{ pop languagelevel } { 1 } ifelse
+2 lt { /Helvetica findfont 12 scalefont setfont 50 500 moveto
+ (This print job requires a PostScript Language Level 2 printer.) show
+ showpage quit } if
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+/cairo_set_page_size {
+ % Change paper size, but only if different from previous paper size otherwise
+ % duplex fails. PLRM specifies a tolerance of 5 pts when matching paper size
+ % so we use the same when checking if the size changes.
+ /setpagedevice where {
+ pop currentpagedevice
+ /PageSize known {
+ 2 copy
+ currentpagedevice /PageSize get aload pop
+ exch 4 1 roll
+ sub abs 5 gt
+ 3 1 roll
+ sub abs 5 gt
+ or
+ } {
+ true
+ } ifelse
+ {
+ 2 array astore
+ 2 dict begin
+ /PageSize exch def
+ /ImagingBBox null def
+ currentdict end
+ setpagedevice
+ } {
+ pop pop
+ } ifelse
+ } {
+ pop
+ } ifelse
+} def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageMedia: 40x53mm
+%%PageBoundingBox: 0 37 75 113
+113 150 cairo_set_page_size
+%%EndPageSetup
+q 0 37 75 76 rectclip
+1 0 0 -1 0 150 cm q
+0 0 1 rg
+0 37.5 37.5 75 re f
+1 0 0 rg
+37.5 37.5 37.5 75 re f
+Q Q
+showpage
+%%Trailer
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-margin_export-id_expected.svg b/testfiles/cli_tests/testcases/export-margin_export-id_expected.svg
new file mode 100644
index 0000000..76e4eb2
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_export-id_expected.svg
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ sodipodi:docname="square_px.svg"
+ id="svg4"
+ version="1.1"
+ height="200"
+ width="150">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs8" />
+ <sodipodi:namedview
+ fit-margin-bottom="50"
+ fit-margin-right="50"
+ fit-margin-left="50"
+ fit-margin-top="50"
+ id="namedview6"
+ inkscape:window-height="480"
+ inkscape:window-width="640"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0"
+ guidetolerance="10"
+ gridtolerance="10"
+ objecttolerance="10"
+ borderopacity="1"
+ bordercolor="#666666"
+ pagecolor="#ffffff" />
+ <rect
+ id="square-blue"
+ fill="#0000ff"
+ height="100"
+ width="50"
+ y="50"
+ x="0" />
+ <rect
+ id="square-red"
+ fill="#ff0000"
+ height="100"
+ width="50"
+ y="50"
+ x="50" />
+</svg>
diff --git a/testfiles/cli_tests/testcases/export-margin_export-id_expected.wmf b/testfiles/cli_tests/testcases/export-margin_export-id_expected.wmf
new file mode 100644
index 0000000..407efb7
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_export-id_expected.wmf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.emf b/testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.emf
new file mode 100644
index 0000000..5dbda47
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.emf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.eps b/testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.eps
new file mode 100644
index 0000000..b9ac1df
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.eps
@@ -0,0 +1,80 @@
+%!PS-Adobe-3.0 EPSF-3.0
+%%Creator: cairo 1.15.10 (http://cairographics.org)
+%%CreationDate: Fri Mar 20 19:25:02 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%BoundingBox: 0 0 113 150
+%%EndComments
+%%BeginProlog
+50 dict begin
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageBoundingBox: 0 0 113 150
+%%EndPageSetup
+q 37 37 38 76 rectclip
+1 0 0 -1 0 150 cm q
+1 0 0 rg
+37.5 37.5 37.5 75 re f
+Q Q
+showpage
+%%Trailer
+end
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.pdf b/testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.pdf
new file mode 100644
index 0000000..736e623
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.pdf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.png b/testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.png
new file mode 100644
index 0000000..a63f9c4
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.ps b/testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.ps
new file mode 100644
index 0000000..1512864
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.ps
@@ -0,0 +1,117 @@
+%!PS-Adobe-3.0
+%%Creator: cairo 1.15.10 (http://cairographics.org)
+%%CreationDate: Fri Mar 20 19:25:01 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%DocumentMedia: 40x53mm 113 150 0 () ()
+%%BoundingBox: 37 37 75 113
+%%EndComments
+%%BeginProlog
+/languagelevel where
+{ pop languagelevel } { 1 } ifelse
+2 lt { /Helvetica findfont 12 scalefont setfont 50 500 moveto
+ (This print job requires a PostScript Language Level 2 printer.) show
+ showpage quit } if
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+/cairo_set_page_size {
+ % Change paper size, but only if different from previous paper size otherwise
+ % duplex fails. PLRM specifies a tolerance of 5 pts when matching paper size
+ % so we use the same when checking if the size changes.
+ /setpagedevice where {
+ pop currentpagedevice
+ /PageSize known {
+ 2 copy
+ currentpagedevice /PageSize get aload pop
+ exch 4 1 roll
+ sub abs 5 gt
+ 3 1 roll
+ sub abs 5 gt
+ or
+ } {
+ true
+ } ifelse
+ {
+ 2 array astore
+ 2 dict begin
+ /PageSize exch def
+ /ImagingBBox null def
+ currentdict end
+ setpagedevice
+ } {
+ pop pop
+ } ifelse
+ } {
+ pop
+ } ifelse
+} def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageMedia: 40x53mm
+%%PageBoundingBox: 37 37 75 113
+113 150 cairo_set_page_size
+%%EndPageSetup
+q 37 37 38 76 rectclip
+1 0 0 -1 0 150 cm q
+1 0 0 rg
+37.5 37.5 37.5 75 re f
+Q Q
+showpage
+%%Trailer
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.svg b/testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.svg
new file mode 100644
index 0000000..0d1c2ac
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.svg
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ sodipodi:docname="square_px.svg"
+ id="svg4"
+ version="1.1"
+ height="200"
+ width="150">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs8" />
+ <sodipodi:namedview
+ fit-margin-bottom="50"
+ fit-margin-right="50"
+ fit-margin-left="50"
+ fit-margin-top="50"
+ id="namedview6"
+ inkscape:window-height="480"
+ inkscape:window-width="640"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0"
+ guidetolerance="10"
+ gridtolerance="10"
+ objecttolerance="10"
+ borderopacity="1"
+ bordercolor="#666666"
+ pagecolor="#ffffff" />
+ <rect
+ id="square-red"
+ fill="#ff0000"
+ height="100"
+ width="50"
+ y="50"
+ x="50" />
+</svg>
diff --git a/testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.wmf b/testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.wmf
new file mode 100644
index 0000000..169b86d
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_export-id_export-id-only_expected.wmf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_expected.emf b/testfiles/cli_tests/testcases/export-margin_mm_expected.emf
new file mode 100644
index 0000000..78c3182
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_expected.emf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_expected.eps b/testfiles/cli_tests/testcases/export-margin_mm_expected.eps
new file mode 100644
index 0000000..fba673e
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_expected.eps
@@ -0,0 +1,82 @@
+%!PS-Adobe-3.0 EPSF-3.0
+%%Creator: cairo 1.15.10 (http://cairographics.org)
+%%CreationDate: Fri Mar 20 19:24:54 2020
+%%Pages: 1
+%%BoundingBox: 0 0 851 851
+%%HiResBoundingBox: 0 0 851 851
+%%LanguageLevel: 3
+%%EndComments
+%%BeginProlog
+50 dict begin
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageBoundingBox: 0 0 851 851
+%%EndPageSetup
+q 283 284 284 284 rectclip
+1 0 0 -1 0 851 cm q
+0 0 1 rg
+283.465 283.465 141.73 283.465 re f
+1 0 0 rg
+425.195 283.465 141.734 283.465 re f
+Q Q
+showpage
+%%Trailer
+end
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_expected.pdf b/testfiles/cli_tests/testcases/export-margin_mm_expected.pdf
new file mode 100644
index 0000000..f4c472c
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_expected.pdf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_expected.png b/testfiles/cli_tests/testcases/export-margin_mm_expected.png
new file mode 100644
index 0000000..9c22341
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_expected.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_expected.ps b/testfiles/cli_tests/testcases/export-margin_mm_expected.ps
new file mode 100644
index 0000000..abb8818
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_expected.ps
@@ -0,0 +1,119 @@
+%!PS-Adobe-3.0
+%%Creator: cairo 1.15.10 (http://cairographics.org)
+%%CreationDate: Fri Mar 20 19:24:53 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%DocumentMedia: 300x300mm 850 850 0 () ()
+%%BoundingBox: 283 284 567 568
+%%EndComments
+%%BeginProlog
+/languagelevel where
+{ pop languagelevel } { 1 } ifelse
+2 lt { /Helvetica findfont 12 scalefont setfont 50 500 moveto
+ (This print job requires a PostScript Language Level 2 printer.) show
+ showpage quit } if
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+/cairo_set_page_size {
+ % Change paper size, but only if different from previous paper size otherwise
+ % duplex fails. PLRM specifies a tolerance of 5 pts when matching paper size
+ % so we use the same when checking if the size changes.
+ /setpagedevice where {
+ pop currentpagedevice
+ /PageSize known {
+ 2 copy
+ currentpagedevice /PageSize get aload pop
+ exch 4 1 roll
+ sub abs 5 gt
+ 3 1 roll
+ sub abs 5 gt
+ or
+ } {
+ true
+ } ifelse
+ {
+ 2 array astore
+ 2 dict begin
+ /PageSize exch def
+ /ImagingBBox null def
+ currentdict end
+ setpagedevice
+ } {
+ pop pop
+ } ifelse
+ } {
+ pop
+ } ifelse
+} def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageMedia: 300x300mm
+%%PageBoundingBox: 283 284 567 568
+851 851 cairo_set_page_size
+%%EndPageSetup
+q 283 284 284 284 rectclip
+1 0 0 -1 0 851 cm q
+0 0 1 rg
+283.465 283.465 141.73 283.465 re f
+1 0 0 rg
+425.195 283.465 141.734 283.465 re f
+Q Q
+showpage
+%%Trailer
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_expected.svg b/testfiles/cli_tests/testcases/export-margin_mm_expected.svg
new file mode 100644
index 0000000..424e8f8
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_expected.svg
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ sodipodi:docname="square_mm.svg"
+ id="svg4"
+ version="1.1"
+ height="300mm"
+ width="300mm">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs8" />
+ <sodipodi:namedview
+ fit-margin-bottom="50"
+ fit-margin-right="50"
+ fit-margin-left="50"
+ fit-margin-top="50"
+ id="namedview6"
+ inkscape:window-height="480"
+ inkscape:window-width="640"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0"
+ guidetolerance="10"
+ gridtolerance="10"
+ objecttolerance="10"
+ borderopacity="1"
+ bordercolor="#666666"
+ pagecolor="#ffffff" />
+ <rect
+ id="square-blue"
+ fill="#0000ff"
+ height="100mm"
+ width="50mm"
+ y="377.95276"
+ x="377.95276" />
+ <rect
+ id="square-red"
+ fill="#ff0000"
+ height="100mm"
+ width="50mm"
+ y="377.95276"
+ x="566.92914" />
+</svg>
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_expected.wmf b/testfiles/cli_tests/testcases/export-margin_mm_expected.wmf
new file mode 100644
index 0000000..0131399
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_expected.wmf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.emf b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.emf
new file mode 100644
index 0000000..e0130bf
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.emf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.eps b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.eps
new file mode 100644
index 0000000..7975c1d
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.eps
@@ -0,0 +1,82 @@
+%!PS-Adobe-3.0 EPSF-3.0
+%%Creator: cairo 1.16.0 (https://cairographics.org)
+%%CreationDate: Sat Mar 21 23:14:25 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%BoundingBox: 0 0 567 567
+%%EndComments
+%%BeginProlog
+50 dict begin
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageBoundingBox: 0 0 567 567
+%%EndPageSetup
+q 141 141 285 285 rectclip
+1 0 0 -1 0 567 cm q
+0 0 1 rg
+141.73 141.73 141.734 283.465 re f
+1 0 0 rg
+283.465 141.73 141.73 283.465 re f
+Q Q
+showpage
+%%Trailer
+end
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.pdf b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.pdf
new file mode 100644
index 0000000..a463c55
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.pdf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.png b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.png
new file mode 100644
index 0000000..84fd7bc
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.ps b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.ps
new file mode 100644
index 0000000..d43e2b3
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.ps
@@ -0,0 +1,119 @@
+%!PS-Adobe-3.0
+%%Creator: cairo 1.16.0 (https://cairographics.org)
+%%CreationDate: Sat Mar 21 23:14:19 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%DocumentMedia: 200x200mm 567 567 0 () ()
+%%BoundingBox: 141 141 426 426
+%%EndComments
+%%BeginProlog
+/languagelevel where
+{ pop languagelevel } { 1 } ifelse
+2 lt { /Helvetica findfont 12 scalefont setfont 50 500 moveto
+ (This print job requires a PostScript Language Level 2 printer.) show
+ showpage quit } if
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+/cairo_set_page_size {
+ % Change paper size, but only if different from previous paper size otherwise
+ % duplex fails. PLRM specifies a tolerance of 5 pts when matching paper size
+ % so we use the same when checking if the size changes.
+ /setpagedevice where {
+ pop currentpagedevice
+ /PageSize known {
+ 2 copy
+ currentpagedevice /PageSize get aload pop
+ exch 4 1 roll
+ sub abs 5 gt
+ 3 1 roll
+ sub abs 5 gt
+ or
+ } {
+ true
+ } ifelse
+ {
+ 2 array astore
+ 2 dict begin
+ /PageSize exch def
+ /ImagingBBox null def
+ currentdict end
+ setpagedevice
+ } {
+ pop pop
+ } ifelse
+ } {
+ pop
+ } ifelse
+} def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageMedia: 200x200mm
+%%PageBoundingBox: 141 141 426 426
+567 567 cairo_set_page_size
+%%EndPageSetup
+q 141 141 285 285 rectclip
+1 0 0 -1 0 567 cm q
+0 0 1 rg
+141.73 141.73 141.734 283.465 re f
+1 0 0 rg
+283.465 141.73 141.73 283.465 re f
+Q Q
+showpage
+%%Trailer
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.svg b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.svg
new file mode 100644
index 0000000..e7773cd
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.svg
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ sodipodi:docname="square_mm_viewbox.svg"
+ id="svg4"
+ version="1.1"
+ viewBox="0 0 200 200"
+ height="200mm"
+ width="200mm">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs8" />
+ <sodipodi:namedview
+ fit-margin-bottom="50"
+ fit-margin-right="50"
+ fit-margin-left="50"
+ fit-margin-top="50"
+ id="namedview6"
+ inkscape:window-height="480"
+ inkscape:window-width="640"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0"
+ guidetolerance="10"
+ gridtolerance="10"
+ objecttolerance="10"
+ borderopacity="1"
+ bordercolor="#666666"
+ pagecolor="#ffffff" />
+ <!-- Consider that usually 1 user unit = 1 px = 1/96 inch, but viewBox scale the the drawing here,
+ so that 1 (unitless) user unit is equivalent to 1 mm after scaling.
+ The first square (square-mm) doesn't actually have a size of 100 mm.
+ The second and third square together has the size of 100 mm × 100 mm.
+ <rect x="0" y="0" width="100mm" height="100mm" fill="yellow" id="square-mm" />-->
+ <rect
+ id="square-blue"
+ fill="#0000ff"
+ height="100"
+ width="50"
+ y="50"
+ x="50" />
+ <rect
+ id="square-red"
+ fill="#ff0000"
+ height="100"
+ width="50"
+ y="50"
+ x="100" />
+</svg>
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.wmf b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.wmf
new file mode 100644
index 0000000..f8c842d
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_drawing_expected.wmf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.emf b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.emf
new file mode 100644
index 0000000..6315da5
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.emf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.eps b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.eps
new file mode 100644
index 0000000..3d0d510
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.eps
@@ -0,0 +1,82 @@
+%!PS-Adobe-3.0 EPSF-3.0
+%%Creator: cairo 1.16.0 (https://cairographics.org)
+%%CreationDate: Sat Mar 21 23:11:11 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%BoundingBox: 0 0 426 567
+%%EndComments
+%%BeginProlog
+50 dict begin
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageBoundingBox: 0 0 426 567
+%%EndPageSetup
+q 0 141 284 285 rectclip
+1 0 0 -1 0 567 cm q
+0 0 1 rg
+0 141.73 141.73 283.465 re f
+1 0 0 rg
+141.73 141.73 141.734 283.465 re f
+Q Q
+showpage
+%%Trailer
+end
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.pdf b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.pdf
new file mode 100644
index 0000000..f9914b4
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.pdf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.png b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.png
new file mode 100644
index 0000000..51a98e1
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.ps b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.ps
new file mode 100644
index 0000000..08192c1
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.ps
@@ -0,0 +1,119 @@
+%!PS-Adobe-3.0
+%%Creator: cairo 1.16.0 (https://cairographics.org)
+%%CreationDate: Sat Mar 21 23:11:23 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%DocumentMedia: 150x200mm 425 567 0 () ()
+%%BoundingBox: 0 141 284 426
+%%EndComments
+%%BeginProlog
+/languagelevel where
+{ pop languagelevel } { 1 } ifelse
+2 lt { /Helvetica findfont 12 scalefont setfont 50 500 moveto
+ (This print job requires a PostScript Language Level 2 printer.) show
+ showpage quit } if
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+/cairo_set_page_size {
+ % Change paper size, but only if different from previous paper size otherwise
+ % duplex fails. PLRM specifies a tolerance of 5 pts when matching paper size
+ % so we use the same when checking if the size changes.
+ /setpagedevice where {
+ pop currentpagedevice
+ /PageSize known {
+ 2 copy
+ currentpagedevice /PageSize get aload pop
+ exch 4 1 roll
+ sub abs 5 gt
+ 3 1 roll
+ sub abs 5 gt
+ or
+ } {
+ true
+ } ifelse
+ {
+ 2 array astore
+ 2 dict begin
+ /PageSize exch def
+ /ImagingBBox null def
+ currentdict end
+ setpagedevice
+ } {
+ pop pop
+ } ifelse
+ } {
+ pop
+ } ifelse
+} def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageMedia: 150x200mm
+%%PageBoundingBox: 0 141 284 426
+426 567 cairo_set_page_size
+%%EndPageSetup
+q 0 141 284 285 rectclip
+1 0 0 -1 0 567 cm q
+0 0 1 rg
+0 141.73 141.73 283.465 re f
+1 0 0 rg
+141.73 141.73 141.734 283.465 re f
+Q Q
+showpage
+%%Trailer
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.svg b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.svg
new file mode 100644
index 0000000..460de99
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.svg
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ sodipodi:docname="square_mm_viewbox.svg"
+ id="svg4"
+ version="1.1"
+ viewBox="0 0 150 200"
+ height="200mm"
+ width="150mm">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs8" />
+ <sodipodi:namedview
+ fit-margin-bottom="50"
+ fit-margin-right="50"
+ fit-margin-left="50"
+ fit-margin-top="50"
+ id="namedview6"
+ inkscape:window-height="480"
+ inkscape:window-width="640"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0"
+ guidetolerance="10"
+ gridtolerance="10"
+ objecttolerance="10"
+ borderopacity="1"
+ bordercolor="#666666"
+ pagecolor="#ffffff" />
+ <!-- Consider that usually 1 user unit = 1 px = 1/96 inch, but viewBox scale the the drawing here,
+ so that 1 (unitless) user unit is equivalent to 1 mm after scaling.
+ The first square (square-mm) doesn't actually have a size of 100 mm.
+ The second and third square together has the size of 100 mm × 100 mm.
+ <rect x="0" y="0" width="100mm" height="100mm" fill="yellow" id="square-mm" />-->
+ <rect
+ id="square-blue"
+ fill="#0000ff"
+ height="100"
+ width="50"
+ y="50"
+ x="-3.814697e-07" />
+ <rect
+ id="square-red"
+ fill="#ff0000"
+ height="100"
+ width="50"
+ y="50"
+ x="50" />
+</svg>
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.wmf b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.wmf
new file mode 100644
index 0000000..18179f4
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_id_expected.wmf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.emf b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.emf
new file mode 100644
index 0000000..78c3182
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.emf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.eps b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.eps
new file mode 100644
index 0000000..fba673e
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.eps
@@ -0,0 +1,82 @@
+%!PS-Adobe-3.0 EPSF-3.0
+%%Creator: cairo 1.15.10 (http://cairographics.org)
+%%CreationDate: Fri Mar 20 19:24:54 2020
+%%Pages: 1
+%%BoundingBox: 0 0 851 851
+%%HiResBoundingBox: 0 0 851 851
+%%LanguageLevel: 3
+%%EndComments
+%%BeginProlog
+50 dict begin
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageBoundingBox: 0 0 851 851
+%%EndPageSetup
+q 283 284 284 284 rectclip
+1 0 0 -1 0 851 cm q
+0 0 1 rg
+283.465 283.465 141.73 283.465 re f
+1 0 0 rg
+425.195 283.465 141.734 283.465 re f
+Q Q
+showpage
+%%Trailer
+end
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.pdf b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.pdf
new file mode 100644
index 0000000..f4c472c
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.pdf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.png b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.png
new file mode 100644
index 0000000..9c22341
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.ps b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.ps
new file mode 100644
index 0000000..abb8818
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.ps
@@ -0,0 +1,119 @@
+%!PS-Adobe-3.0
+%%Creator: cairo 1.15.10 (http://cairographics.org)
+%%CreationDate: Fri Mar 20 19:24:53 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%DocumentMedia: 300x300mm 850 850 0 () ()
+%%BoundingBox: 283 284 567 568
+%%EndComments
+%%BeginProlog
+/languagelevel where
+{ pop languagelevel } { 1 } ifelse
+2 lt { /Helvetica findfont 12 scalefont setfont 50 500 moveto
+ (This print job requires a PostScript Language Level 2 printer.) show
+ showpage quit } if
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+/cairo_set_page_size {
+ % Change paper size, but only if different from previous paper size otherwise
+ % duplex fails. PLRM specifies a tolerance of 5 pts when matching paper size
+ % so we use the same when checking if the size changes.
+ /setpagedevice where {
+ pop currentpagedevice
+ /PageSize known {
+ 2 copy
+ currentpagedevice /PageSize get aload pop
+ exch 4 1 roll
+ sub abs 5 gt
+ 3 1 roll
+ sub abs 5 gt
+ or
+ } {
+ true
+ } ifelse
+ {
+ 2 array astore
+ 2 dict begin
+ /PageSize exch def
+ /ImagingBBox null def
+ currentdict end
+ setpagedevice
+ } {
+ pop pop
+ } ifelse
+ } {
+ pop
+ } ifelse
+} def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageMedia: 300x300mm
+%%PageBoundingBox: 283 284 567 568
+851 851 cairo_set_page_size
+%%EndPageSetup
+q 283 284 284 284 rectclip
+1 0 0 -1 0 851 cm q
+0 0 1 rg
+283.465 283.465 141.73 283.465 re f
+1 0 0 rg
+425.195 283.465 141.734 283.465 re f
+Q Q
+showpage
+%%Trailer
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.svg b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.svg
new file mode 100644
index 0000000..8274392
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.svg
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ sodipodi:docname="square_mm_viewbox.svg"
+ id="svg4"
+ version="1.1"
+ viewBox="0 0 300 300"
+ height="300mm"
+ width="300mm">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs8" />
+ <sodipodi:namedview
+ fit-margin-bottom="50"
+ fit-margin-right="50"
+ fit-margin-left="50"
+ fit-margin-top="50"
+ id="namedview6"
+ inkscape:window-height="480"
+ inkscape:window-width="640"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0"
+ guidetolerance="10"
+ gridtolerance="10"
+ objecttolerance="10"
+ borderopacity="1"
+ bordercolor="#666666"
+ pagecolor="#ffffff" />
+ <!-- Consider that usually 1 user unit = 1 px = 1/96 inch, but viewBox scale the the drawing here,
+ so that 1 (unitless) user unit is equivalent to 1 mm after scaling.
+ The first square (square-mm) doesn't actually have a size of 100 mm.
+ The second and third square together has the size of 100 mm × 100 mm.
+ <rect x="0" y="0" width="100mm" height="100mm" fill="yellow" id="square-mm" />-->
+ <rect
+ id="square-blue"
+ fill="#0000ff"
+ height="100"
+ width="50"
+ y="100"
+ x="100" />
+ <rect
+ id="square-red"
+ fill="#ff0000"
+ height="100"
+ width="50"
+ y="100"
+ x="150" />
+</svg>
diff --git a/testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.wmf b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.wmf
new file mode 100644
index 0000000..0131399
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_mm_viewbox_page_expected.wmf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_px_expected.emf b/testfiles/cli_tests/testcases/export-margin_px_expected.emf
new file mode 100644
index 0000000..0d52e4d
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_px_expected.emf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_px_expected.eps b/testfiles/cli_tests/testcases/export-margin_px_expected.eps
new file mode 100644
index 0000000..dcc1bc0
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_px_expected.eps
@@ -0,0 +1,82 @@
+%!PS-Adobe-3.0 EPSF-3.0
+%%Creator: cairo 1.15.10 (http://cairographics.org)
+%%CreationDate: Fri Mar 20 19:24:50 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%BoundingBox: 0 0 225 225
+%%EndComments
+%%BeginProlog
+50 dict begin
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageBoundingBox: 0 0 225 225
+%%EndPageSetup
+q 75 75 75 75 rectclip
+1 0 0 -1 0 225 cm q
+0 0 1 rg
+75 75 37.5 75 re f
+1 0 0 rg
+112.5 75 37.5 75 re f
+Q Q
+showpage
+%%Trailer
+end
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-margin_px_expected.pdf b/testfiles/cli_tests/testcases/export-margin_px_expected.pdf
new file mode 100644
index 0000000..a467503
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_px_expected.pdf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_px_expected.png b/testfiles/cli_tests/testcases/export-margin_px_expected.png
new file mode 100644
index 0000000..a9ce6f9
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_px_expected.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-margin_px_expected.ps b/testfiles/cli_tests/testcases/export-margin_px_expected.ps
new file mode 100644
index 0000000..c2fa5e8
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_px_expected.ps
@@ -0,0 +1,119 @@
+%!PS-Adobe-3.0
+%%Creator: cairo 1.15.10 (http://cairographics.org)
+%%CreationDate: Fri Mar 20 19:24:49 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%DocumentMedia: 79x79mm 225 225 0 () ()
+%%BoundingBox: 75 75 150 150
+%%EndComments
+%%BeginProlog
+/languagelevel where
+{ pop languagelevel } { 1 } ifelse
+2 lt { /Helvetica findfont 12 scalefont setfont 50 500 moveto
+ (This print job requires a PostScript Language Level 2 printer.) show
+ showpage quit } if
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+/cairo_set_page_size {
+ % Change paper size, but only if different from previous paper size otherwise
+ % duplex fails. PLRM specifies a tolerance of 5 pts when matching paper size
+ % so we use the same when checking if the size changes.
+ /setpagedevice where {
+ pop currentpagedevice
+ /PageSize known {
+ 2 copy
+ currentpagedevice /PageSize get aload pop
+ exch 4 1 roll
+ sub abs 5 gt
+ 3 1 roll
+ sub abs 5 gt
+ or
+ } {
+ true
+ } ifelse
+ {
+ 2 array astore
+ 2 dict begin
+ /PageSize exch def
+ /ImagingBBox null def
+ currentdict end
+ setpagedevice
+ } {
+ pop pop
+ } ifelse
+ } {
+ pop
+ } ifelse
+} def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageMedia: 79x79mm
+%%PageBoundingBox: 75 75 150 150
+225 225 cairo_set_page_size
+%%EndPageSetup
+q 75 75 75 75 rectclip
+1 0 0 -1 0 225 cm q
+0 0 1 rg
+75 75 37.5 75 re f
+1 0 0 rg
+112.5 75 37.5 75 re f
+Q Q
+showpage
+%%Trailer
+%%EOF
diff --git a/testfiles/cli_tests/testcases/export-margin_px_expected.svg b/testfiles/cli_tests/testcases/export-margin_px_expected.svg
new file mode 100644
index 0000000..b4ec04f
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_px_expected.svg
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ sodipodi:docname="square_px.svg"
+ id="svg4"
+ version="1.1"
+ height="300"
+ width="300">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs8" />
+ <sodipodi:namedview
+ fit-margin-bottom="50"
+ fit-margin-right="50"
+ fit-margin-left="50"
+ fit-margin-top="50"
+ id="namedview6"
+ inkscape:window-height="480"
+ inkscape:window-width="640"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0"
+ guidetolerance="10"
+ gridtolerance="10"
+ objecttolerance="10"
+ borderopacity="1"
+ bordercolor="#666666"
+ pagecolor="#ffffff" />
+ <rect
+ id="square-blue"
+ fill="#0000ff"
+ height="100"
+ width="50"
+ y="100"
+ x="100" />
+ <rect
+ id="square-red"
+ fill="#ff0000"
+ height="100"
+ width="50"
+ y="100"
+ x="150" />
+</svg>
diff --git a/testfiles/cli_tests/testcases/export-margin_px_expected.wmf b/testfiles/cli_tests/testcases/export-margin_px_expected.wmf
new file mode 100644
index 0000000..57bd577
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-margin_px_expected.wmf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export-width_expected.png b/testfiles/cli_tests/testcases/export-width_expected.png
new file mode 100644
index 0000000..f05cecd
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export-width_expected.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/export_hints.svg b/testfiles/cli_tests/testcases/export_hints.svg
new file mode 100644
index 0000000..87c06f5
--- /dev/null
+++ b/testfiles/cli_tests/testcases/export_hints.svg
@@ -0,0 +1,7 @@
+<!-- Note: inkscape:export-ydpi is actually ignored, see src/ui/dialog/export.cpp and src/io/file-export-cmd.cpp -->
+
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="190" height="80"
+ inkscape:export-filename="export_hints_drawing.png" inkscape:export-xdpi="111" inkscape:export-ydpi="222">
+ <rect id="rect1" x="10" y="10" width="80" height="60" fill="#00f" inkscape:export-filename="export_hints_rectangle.png" inkscape:export-xdpi="123" inkscape:export-ydpi="456"/>
+ <rect id="rect2" x="100" y="10" width="80" height="60" fill="#f00"/>
+</svg>
diff --git a/testfiles/cli_tests/testcases/filter.svg b/testfiles/cli_tests/testcases/filter.svg
new file mode 100644
index 0000000..6111da3
--- /dev/null
+++ b/testfiles/cli_tests/testcases/filter.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="230" height="120" viewBox="0 0 230 120" xmlns="http://www.w3.org/2000/svg">
+ <filter id="blurMe">
+ <feGaussianBlur stdDeviation="5"/>
+ </filter>
+ <circle cx="60" cy="60" r="60" fill="green" fill-opacity="0.1" />
+ <circle cx="60" cy="60" r="50" fill="green" />
+ <circle cx="170" cy="60" r="50" fill="green" filter="url(#blurMe)" />
+</svg>
diff --git a/testfiles/cli_tests/testcases/librevenge_formats/corel_draw.cdr b/testfiles/cli_tests/testcases/librevenge_formats/corel_draw.cdr
new file mode 100644
index 0000000..ccdb02a
--- /dev/null
+++ b/testfiles/cli_tests/testcases/librevenge_formats/corel_draw.cdr
Binary files differ
diff --git a/testfiles/cli_tests/testcases/librevenge_formats/corel_draw2.cdr b/testfiles/cli_tests/testcases/librevenge_formats/corel_draw2.cdr
new file mode 100644
index 0000000..4a74dbc
--- /dev/null
+++ b/testfiles/cli_tests/testcases/librevenge_formats/corel_draw2.cdr
Binary files differ
diff --git a/testfiles/cli_tests/testcases/librevenge_formats/corel_draw2_expected.png b/testfiles/cli_tests/testcases/librevenge_formats/corel_draw2_expected.png
new file mode 100644
index 0000000..79a0966
--- /dev/null
+++ b/testfiles/cli_tests/testcases/librevenge_formats/corel_draw2_expected.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/librevenge_formats/corel_draw_expected.png b/testfiles/cli_tests/testcases/librevenge_formats/corel_draw_expected.png
new file mode 100644
index 0000000..b3ee3e4
--- /dev/null
+++ b/testfiles/cli_tests/testcases/librevenge_formats/corel_draw_expected.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/librevenge_formats/visio.vsd b/testfiles/cli_tests/testcases/librevenge_formats/visio.vsd
new file mode 100644
index 0000000..a526a70
--- /dev/null
+++ b/testfiles/cli_tests/testcases/librevenge_formats/visio.vsd
Binary files differ
diff --git a/testfiles/cli_tests/testcases/librevenge_formats/visio.vsd_expected.png b/testfiles/cli_tests/testcases/librevenge_formats/visio.vsd_expected.png
new file mode 100644
index 0000000..43ba022
--- /dev/null
+++ b/testfiles/cli_tests/testcases/librevenge_formats/visio.vsd_expected.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/librevenge_formats/visio.vsdx b/testfiles/cli_tests/testcases/librevenge_formats/visio.vsdx
new file mode 100644
index 0000000..ef8f6a1
--- /dev/null
+++ b/testfiles/cli_tests/testcases/librevenge_formats/visio.vsdx
Binary files differ
diff --git a/testfiles/cli_tests/testcases/librevenge_formats/visio.vsdx_expected.png b/testfiles/cli_tests/testcases/librevenge_formats/visio.vsdx_expected.png
new file mode 100644
index 0000000..5f1bdfa
--- /dev/null
+++ b/testfiles/cli_tests/testcases/librevenge_formats/visio.vsdx_expected.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/librevenge_formats/word_perfect.wpg b/testfiles/cli_tests/testcases/librevenge_formats/word_perfect.wpg
new file mode 100644
index 0000000..02e1b82
--- /dev/null
+++ b/testfiles/cli_tests/testcases/librevenge_formats/word_perfect.wpg
Binary files differ
diff --git a/testfiles/cli_tests/testcases/librevenge_formats/word_perfect_expected.png b/testfiles/cli_tests/testcases/librevenge_formats/word_perfect_expected.png
new file mode 100644
index 0000000..aa5ac9e
--- /dev/null
+++ b/testfiles/cli_tests/testcases/librevenge_formats/word_perfect_expected.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/pdf-mesh.pdf b/testfiles/cli_tests/testcases/pdf-mesh.pdf
new file mode 100644
index 0000000..8d4c29c
--- /dev/null
+++ b/testfiles/cli_tests/testcases/pdf-mesh.pdf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/pyramids.svg b/testfiles/cli_tests/testcases/pyramids.svg
new file mode 100644
index 0000000..30ac82a
--- /dev/null
+++ b/testfiles/cli_tests/testcases/pyramids.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="185px" height="100px">
+ <rect x="4.888" y="5.333" width="175" height="90" fill="#fff" stroke="#000" id="rect_misaligned" />
+
+ <rect x="10" y="10" width="80" height="80" fill="none" stroke="#000" id="rect_1" />
+ <rect x="15" y="15" width="70" height="70" fill="none" stroke="#000" id="rect_2" />
+ <rect x="20" y="20" width="60" height="60" fill="none" stroke="#000" id="rect_3" />
+ <rect x="25" y="25" width="50" height="50" fill="none" stroke="#000" id="rect_4" />
+ <rect x="30.5" y="30.5" width="40" height="40" fill="red" fill-opacity="0.2" id="quad_1" />
+ <rect x="35.5" y="35.5" width="30" height="30" fill="red" fill-opacity="0.2" id="quad_2" />
+ <rect x="40.5" y="40.5" width="20" height="20" fill="red" fill-opacity="0.2" id="quad_3" />
+ <rect x="45.5" y="45.5" width="10" height="10" fill="red" fill-opacity="0.2" id="quad_4" />
+
+ <rect x="95.5" y="10.5" width="80" height="80" fill="none" stroke="#000" id="rect_snapped_1" />
+ <rect x="100.5" y="15.5" width="70" height="70" fill="none" stroke="#000" id="rect_snapped_2" />
+ <rect x="105.5" y="20.5" width="60" height="60" fill="none" stroke="#000" id="rect_snapped_3" />
+ <rect x="110.5" y="25.5" width="50" height="50" fill="none" stroke="#000" id="rect_snapped_4" />
+ <rect x="115" y="30" width="40" height="40" fill="green" fill-opacity="0.2" id="quad_snapped_1" />
+ <rect x="120" y="35" width="30" height="30" fill="green" fill-opacity="0.2" id="quad_snapped_2" />
+ <rect x="125" y="40" width="20" height="20" fill="green" fill-opacity="0.2" id="quad_snapped_3" />
+ <rect x="130" y="45" width="10" height="10" fill="green" fill-opacity="0.2" id="quad_snapped_4" />
+</svg>
diff --git a/testfiles/cli_tests/testcases/rects.svg b/testfiles/cli_tests/testcases/rects.svg
new file mode 100644
index 0000000..3d90ef1
--- /dev/null
+++ b/testfiles/cli_tests/testcases/rects.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="300px" height="100px">
+ <rect x="10" y="10" width="80" height="80" fill="#f00" id="rect1" />
+ <rect x="110" y="20" width="80" height="70" fill="#0f0" id="rect2" />
+ <rect x="210" y="30" width="80" height="60" fill="#00f" id="rect3" />
+</svg>
diff --git a/testfiles/cli_tests/testcases/shapes.svg b/testfiles/cli_tests/testcases/shapes.svg
new file mode 100644
index 0000000..61ad01d
--- /dev/null
+++ b/testfiles/cli_tests/testcases/shapes.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="400" height="300" version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <g stroke="#000">
+ <rect x="10" y="5" width="160" height="130" fill="#00f" stroke-width="2"/>
+ <ellipse cx="275" cy="130" rx="120" ry="100" fill="#f00" stroke-width="4"/>
+ <path d="m95 290 9.9517-87.343-74.468-46.716 86.143-17.526 21.418-85.259 43.288 76.511 87.704-5.9771-59.39 64.812 32.787 81.565-79.993-36.455z" fill="#ff0" stroke-width="6"/>
+ </g>
+</svg>
diff --git a/testfiles/cli_tests/testcases/shapes_expected.emf b/testfiles/cli_tests/testcases/shapes_expected.emf
new file mode 100644
index 0000000..4bf1faa
--- /dev/null
+++ b/testfiles/cli_tests/testcases/shapes_expected.emf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/shapes_expected.eps b/testfiles/cli_tests/testcases/shapes_expected.eps
new file mode 100644
index 0000000..fe91e4d
--- /dev/null
+++ b/testfiles/cli_tests/testcases/shapes_expected.eps
@@ -0,0 +1,109 @@
+%!PS-Adobe-3.0 EPSF-3.0
+%%Creator: cairo 1.16.0 (https://cairographics.org)
+%%CreationDate: Sat Feb 22 20:44:27 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%BoundingBox: 0 0 291 220
+%%EndComments
+%%BeginProlog
+50 dict begin
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageBoundingBox: 0 0 291 220
+%%EndPageSetup
+q 0 0 291 220 rectclip
+1 0 0 -1 0 220 cm q
+0 0 1 rg
+0.75 0.75 120 97.5 re f
+0 g
+1.5 w
+0 J
+0 j
+[] 0.0 d
+4 M q 1 0 0 1 0 0 cm
+0.75 0.75 120 97.5 re S Q
+1 0 0 rg
+289.5 94.5 m 289.5 135.922 249.207 169.5 199.5 169.5 c 149.793 169.5 109.5
+ 135.922 109.5 94.5 c 109.5 53.078 149.793 19.5 199.5 19.5 c 249.207 19.5
+ 289.5 53.078 289.5 94.5 c f
+0 g
+3 w
+q 1 0 0 1 0 0 cm
+289.5 94.5 m 289.5 135.922 249.207 169.5 199.5 169.5 c 149.793 169.5 109.5
+ 135.922 109.5 94.5 c 109.5 53.078 149.793 19.5 199.5 19.5 c 249.207 19.5
+ 289.5 53.078 289.5 94.5 c S Q
+1 1 0 rg
+64.5 214.5 m 71.965 148.992 l 16.113 113.957 l 80.719 100.813 l 96.785
+36.867 l 129.25 94.25 l 195.027 89.766 l 150.484 138.375 l 175.074 199.551
+ l 115.082 172.207 l h
+64.5 214.5 m f
+0 g
+4.5 w
+q 1 0 0 1 0 0 cm
+64.5 214.5 m 71.965 148.992 l 16.113 113.957 l 80.719 100.813 l 96.785
+36.867 l 129.25 94.25 l 195.027 89.766 l 150.484 138.375 l 175.074 199.551
+ l 115.082 172.207 l h
+64.5 214.5 m S Q
+Q Q
+showpage
+%%Trailer
+end
+%%EOF
diff --git a/testfiles/cli_tests/testcases/shapes_expected.pdf b/testfiles/cli_tests/testcases/shapes_expected.pdf
new file mode 100644
index 0000000..bff07b6
--- /dev/null
+++ b/testfiles/cli_tests/testcases/shapes_expected.pdf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/shapes_expected.png b/testfiles/cli_tests/testcases/shapes_expected.png
new file mode 100644
index 0000000..3e81da4
--- /dev/null
+++ b/testfiles/cli_tests/testcases/shapes_expected.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/shapes_expected.ps b/testfiles/cli_tests/testcases/shapes_expected.ps
new file mode 100644
index 0000000..9037026
--- /dev/null
+++ b/testfiles/cli_tests/testcases/shapes_expected.ps
@@ -0,0 +1,146 @@
+%!PS-Adobe-3.0
+%%Creator: cairo 1.16.0 (https://cairographics.org)
+%%CreationDate: Sat Feb 22 20:44:25 2020
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%DocumentMedia: 106x79mm 300 225 0 () ()
+%%BoundingBox: 6 2 298 222
+%%EndComments
+%%BeginProlog
+/languagelevel where
+{ pop languagelevel } { 1 } ifelse
+2 lt { /Helvetica findfont 12 scalefont setfont 50 500 moveto
+ (This print job requires a PostScript Language Level 2 printer.) show
+ showpage quit } if
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+ 0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+ {
+ dup
+ type /stringtype eq
+ { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+ } forall
+ currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+ cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+ { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+ /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+ /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+ cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+ CairoDataIndex CairoData length lt
+ { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+ { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+/cairo_set_page_size {
+ % Change paper size, but only if different from previous paper size otherwise
+ % duplex fails. PLRM specifies a tolerance of 5 pts when matching paper size
+ % so we use the same when checking if the size changes.
+ /setpagedevice where {
+ pop currentpagedevice
+ /PageSize known {
+ 2 copy
+ currentpagedevice /PageSize get aload pop
+ exch 4 1 roll
+ sub abs 5 gt
+ 3 1 roll
+ sub abs 5 gt
+ or
+ } {
+ true
+ } ifelse
+ {
+ 2 array astore
+ 2 dict begin
+ /PageSize exch def
+ /ImagingBBox null def
+ currentdict end
+ setpagedevice
+ } {
+ pop pop
+ } ifelse
+ } {
+ pop
+ } ifelse
+} def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageMedia: 106x79mm
+%%PageBoundingBox: 6 2 298 222
+300 225 cairo_set_page_size
+%%EndPageSetup
+q 6 2 292 220 rectclip
+1 0 0 -1 0 225 cm q
+0 0 1 rg
+7.5 3.75 120 97.5 re f
+0 g
+1.5 w
+0 J
+0 j
+[] 0.0 d
+4 M q 1 0 0 1 0 0 cm
+7.5 3.75 120 97.5 re S Q
+1 0 0 rg
+296.25 97.5 m 296.25 138.922 255.957 172.5 206.25 172.5 c 156.543 172.5
+ 116.25 138.922 116.25 97.5 c 116.25 56.078 156.543 22.5 206.25 22.5 c 255.957
+ 22.5 296.25 56.078 296.25 97.5 c f
+0 g
+3 w
+q 1 0 0 1 0 0 cm
+296.25 97.5 m 296.25 138.922 255.957 172.5 206.25 172.5 c 156.543 172.5
+ 116.25 138.922 116.25 97.5 c 116.25 56.078 156.543 22.5 206.25 22.5 c 255.957
+ 22.5 296.25 56.078 296.25 97.5 c S Q
+1 1 0 rg
+71.25 217.5 m 78.715 151.992 l 22.863 116.957 l 87.469 103.813 l 103.535
+ 39.867 l 136 97.25 l 201.777 92.766 l 157.234 141.375 l 181.824 202.551
+ l 121.832 175.207 l h
+71.25 217.5 m f
+0 g
+4.5 w
+q 1 0 0 1 0 0 cm
+71.25 217.5 m 78.715 151.992 l 22.863 116.957 l 87.469 103.813 l 103.535
+ 39.867 l 136 97.25 l 201.777 92.766 l 157.234 141.375 l 181.824 202.551
+ l 121.832 175.207 l h
+71.25 217.5 m S Q
+Q Q
+showpage
+%%Trailer
+%%EOF
diff --git a/testfiles/cli_tests/testcases/shapes_expected.wmf b/testfiles/cli_tests/testcases/shapes_expected.wmf
new file mode 100644
index 0000000..1b3fdcf
--- /dev/null
+++ b/testfiles/cli_tests/testcases/shapes_expected.wmf
Binary files differ
diff --git a/testfiles/cli_tests/testcases/shapes_expected.xaml b/testfiles/cli_tests/testcases/shapes_expected.xaml
new file mode 100644
index 0000000..025f668
--- /dev/null
+++ b/testfiles/cli_tests/testcases/shapes_expected.xaml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--This file is NOT compatible with Silverlight-->
+<Viewbox xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Stretch="Uniform">
+ <Canvas Name="svg10" Width="400" Height="300">
+ <Canvas.Resources/>
+ <!--Unknown tag: metadata-->
+ <!--Unknown tag: sodipodi:namedview-->
+ <Canvas Name="g8">
+ <Rectangle xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Canvas.Left="10" Canvas.Top="5" Width="160" Height="130" Name="rect2" Fill="#FF0000FF" StrokeThickness="2" Stroke="#FF000000"/>
+ <Ellipse xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Canvas.Left="155" Width="240" Canvas.Top="30" Height="200" Name="ellipse4" Fill="#FFFF0000" StrokeThickness="4" Stroke="#FF000000"/>
+ <Path xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Name="path6" Fill="#FFFFFF00" StrokeThickness="6" Stroke="#FF000000">
+ <Path.Data>
+ <PathGeometry Figures="m95 290 9.9517-87.343-74.468-46.716 86.143-17.526 21.418-85.259 43.288 76.511 87.704-5.9771-59.39 64.812 32.787 81.565-79.993-36.455z" FillRule="NonZero"/>
+ </Path.Data>
+ </Path>
+ </Canvas>
+ </Canvas>
+</Viewbox>
diff --git a/testfiles/cli_tests/testcases/square_mm.svg b/testfiles/cli_tests/testcases/square_mm.svg
new file mode 100644
index 0000000..b52aec6
--- /dev/null
+++ b/testfiles/cli_tests/testcases/square_mm.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="200mm" height="200mm">
+ <rect x="50mm" y="50mm" width="50mm" height="100mm" fill="blue" id="square-blue" />
+ <rect x="100mm" y="50mm" width="50mm" height="100mm" fill="red" id="square-red" />
+</svg>
diff --git a/testfiles/cli_tests/testcases/square_mm_viewbox.svg b/testfiles/cli_tests/testcases/square_mm_viewbox.svg
new file mode 100644
index 0000000..394b205
--- /dev/null
+++ b/testfiles/cli_tests/testcases/square_mm_viewbox.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="200mm" height="200mm" viewBox="0 0 200 200">
+ <!-- Consider that usually 1 user unit = 1 px = 1/96 inch, but viewBox scale the the drawing here,
+ so that 1 (unitless) user unit is equivalent to 1 mm after scaling.
+ The first square (square-mm) doesn't actually have a size of 100 mm.
+ The second and third square together has the size of 100 mm × 100 mm.
+ <rect x="0" y="0" width="100mm" height="100mm" fill="yellow" id="square-mm" />-->
+ <rect x="50" y="50" width="50" height="100" fill="blue" id="square-blue" />
+ <rect x="100" y="50" width="50" height="100" fill="red" id="square-red" />
+</svg>
diff --git a/testfiles/cli_tests/testcases/square_px.svg b/testfiles/cli_tests/testcases/square_px.svg
new file mode 100644
index 0000000..7467f7d
--- /dev/null
+++ b/testfiles/cli_tests/testcases/square_px.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="200px" height="200px">
+ <rect x="50" y="50" width="50" height="100" fill="blue" id="square-blue" />
+ <rect x="100" y="50" width="50" height="100" fill="red" id="square-red" />
+</svg>
diff --git a/testfiles/cli_tests/testcases/systemLanguage.svg b/testfiles/cli_tests/testcases/systemLanguage.svg
new file mode 100644
index 0000000..c650dc9
--- /dev/null
+++ b/testfiles/cli_tests/testcases/systemLanguage.svg
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="100">
+ <switch id="switch">
+ <!-- some fuzz to probe for crashing issues -->
+ <rect systemLanguage="" width="1" height="1"/>
+ <rect systemLanguage=" " width="1" height="1"/>
+ <rect systemLanguage="_" width="1" height="1"/>
+ <rect systemLanguage="-" width="1" height="1"/>
+ <rect systemLanguage="#" width="1" height="1"/>
+ <rect systemLanguage="-CH" width="1" height="1"/>
+
+ <!-- use rects instead of text to avoid any issues with font rendering -->
+ <!-- note: we don't support the 'allowReorder' attribute yet -->
+ <rect systemLanguage="en" x="10" y="10" width="80" height="80" fill="#00f"/>
+ <rect systemLanguage="fr" x="110" y="10" width="80" height="80" fill="#00f"/>
+ <rect systemLanguage="de-CH" x="210" y="10" width="80" height="80" fill="#00f"/>
+ <rect systemLanguage="es, pt" x="310" y="10" width="80" height="80" fill="#00f"/>
+ <rect x="410" y="10" width="80" height="80" fill="#00f"/>
+ </switch>
+</svg>
diff --git a/testfiles/cli_tests/testcases/systemLanguage_RDF.svg b/testfiles/cli_tests/testcases/systemLanguage_RDF.svg
new file mode 100644
index 0000000..7e1223e
--- /dev/null
+++ b/testfiles/cli_tests/testcases/systemLanguage_RDF.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="100"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <metadata>
+ <rdf:RDF>
+ <cc:Work>
+ <dc:language>fr</dc:language>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <use xlink:href="systemLanguage.svg#switch" />
+</svg>
diff --git a/testfiles/cli_tests/testcases/systemLanguage_de.png b/testfiles/cli_tests/testcases/systemLanguage_de.png
new file mode 100644
index 0000000..1dbfe36
--- /dev/null
+++ b/testfiles/cli_tests/testcases/systemLanguage_de.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/systemLanguage_default.png b/testfiles/cli_tests/testcases/systemLanguage_default.png
new file mode 100644
index 0000000..67cb9f0
--- /dev/null
+++ b/testfiles/cli_tests/testcases/systemLanguage_default.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/systemLanguage_en.png b/testfiles/cli_tests/testcases/systemLanguage_en.png
new file mode 100644
index 0000000..31679aa
--- /dev/null
+++ b/testfiles/cli_tests/testcases/systemLanguage_en.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/systemLanguage_fr.png b/testfiles/cli_tests/testcases/systemLanguage_fr.png
new file mode 100644
index 0000000..deeb4bf
--- /dev/null
+++ b/testfiles/cli_tests/testcases/systemLanguage_fr.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/systemLanguage_pt.png b/testfiles/cli_tests/testcases/systemLanguage_pt.png
new file mode 100644
index 0000000..91e5b4b
--- /dev/null
+++ b/testfiles/cli_tests/testcases/systemLanguage_pt.png
Binary files differ
diff --git a/testfiles/cli_tests/testcases/text.svg b/testfiles/cli_tests/testcases/text.svg
new file mode 100644
index 0000000..5dd1c7f
--- /dev/null
+++ b/testfiles/cli_tests/testcases/text.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="100px" height="50px">
+ <text x="10" y="25">some text</text>
+</svg>
diff --git a/testfiles/doc-per-case-test.cpp b/testfiles/doc-per-case-test.cpp
new file mode 100644
index 0000000..0f3721c
--- /dev/null
+++ b/testfiles/doc-per-case-test.cpp
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Test fixture with SPDocument per entire test case.
+ *
+ * Author:
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2015 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "doc-per-case-test.h"
+
+#include "inkscape.h"
+
+SPDocument *DocPerCaseTest::_doc = 0;
+
+DocPerCaseTest::DocPerCaseTest() :
+ ::testing::Test()
+{
+}
+
+void DocPerCaseTest::SetUpTestCase()
+{
+ if ( !Inkscape::Application::exists() )
+ {
+ // Create the global inkscape object.
+ Inkscape::Application::create(false);
+ }
+
+ _doc = SPDocument::createNewDoc( NULL, TRUE, true );
+ ASSERT_TRUE( _doc != NULL );
+}
+
+void DocPerCaseTest::TearDownTestCase()
+{
+ if (_doc) {
+ _doc->doUnref();
+ _doc = NULL;
+ }
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/testfiles/doc-per-case-test.h b/testfiles/doc-per-case-test.h
new file mode 100644
index 0000000..fbd6ff3
--- /dev/null
+++ b/testfiles/doc-per-case-test.h
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Test fixture with SPDocument per entire test case.
+ *
+ * Author:
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2015 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "gtest/gtest.h"
+
+#include "document.h"
+
+
+/**
+ * Simple fixture that creates a single SPDocument to be shared between all tests
+ * in this test case.
+ */
+class DocPerCaseTest : public ::testing::Test
+{
+public:
+ DocPerCaseTest();
+
+protected:
+ static void SetUpTestCase();
+
+ static void TearDownTestCase();
+
+ static SPDocument *_doc;
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/testfiles/fuzzer.cpp b/testfiles/fuzzer.cpp
new file mode 100644
index 0000000..450d98c
--- /dev/null
+++ b/testfiles/fuzzer.cpp
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2017 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include "xml/repr.h"
+#include "inkscape.h"
+#include "document.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ g_type_init();
+ Inkscape::GC::init();
+ if ( !Inkscape::Application::exists() )
+ Inkscape::Application::create(false);
+ //void* a= sp_repr_read_mem((const char*)data, size, 0);
+ SPDocument *doc = SPDocument::createNewDocFromMem( (const char*)data, size, 0);
+ if(doc)
+ doc->doUnref();
+ return 0;
+}
diff --git a/testfiles/fuzzer.dict b/testfiles/fuzzer.dict
new file mode 100644
index 0000000..c746484
--- /dev/null
+++ b/testfiles/fuzzer.dict
@@ -0,0 +1,526 @@
+# Dictionary for the fuzzer to "guess" faster important words.
+# Contains xml keywords and svg element names and attributes.
+# It might be useful to remove some of them, maybe.
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+"100"
+"200"
+"300"
+"400"
+"500"
+"600"
+"700"
+"800"
+"900"
+"a"
+"accent-height"
+"accumulate"
+"additive"
+"after-edge"
+"alignment-baseline"
+"all"
+"alphabetic"
+"altGlyph"
+"altGlyphDef"
+"altGlyphItem"
+"amplitude"
+"animate"
+"animateColor"
+"animateMotion"
+"animateTransform"
+"arabic-form"
+"ascent"
+attr_encoding=" encoding=\"1\""
+attr_generic=" a=\"1\""
+attr_href=" href=\"1\""
+"attributeName"
+"attributeType"
+attr_standalone=" standalone=\"no\""
+attr_version=" version=\"1\""
+attr_xml_base=" xml:base=\"1\""
+attr_xml_id=" xml:id=\"1\""
+attr_xml_lang=" xml:lang=\"1\""
+attr_xmlns=" xmlns=\"1\""
+attr_xml_space=" xml:space=\"1\""
+"auto"
+"azimuth"
+"baseFrequency"
+"baseline"
+"baseline-shift"
+"baseProfile"
+"bbox"
+"before-edge"
+"begin"
+"bevel"
+"bias"
+"bidi-override"
+"blink"
+"block"
+"bold"
+"bolder"
+"butt"
+"by"
+"calcMode"
+"cap-height"
+"caption"
+"central"
+"circle"
+"class"
+"clip"
+"clip-path"
+"clipPath"
+"clipPathUnits"
+"clip-rule"
+"collapse"
+"color"
+"color-interpolation"
+"color-interpolation-filters"
+"color-profile"
+"color-rendering"
+"compact"
+"condensed"
+"contentScriptType"
+"contentStyleType"
+"crispEdges"
+"crosshair"
+"currentColor"
+"cursor"
+"cx"
+"cy"
+"d"
+"default"
+"defs"
+"desc"
+"descent"
+"diffuseConstant"
+"direction"
+"display"
+"divisor"
+"dominant-baseline"
+"dur"
+"dx"
+"dy"
+"edgeMode"
+"elevation"
+"ellipse"
+"embed"
+"enable-background"
+"end"
+entity_builtin="&lt;"
+entity_decimal="&#1;"
+entity_external="&a;"
+entity_hex="&#x1;"
+"e-resize"
+"evenodd"
+"expanded"
+"exponent"
+"externalResourcesRequired"
+"extra-condensed"
+"extra-expanded"
+"feBlend"
+"feColorMatrix"
+"feComponentTransfer"
+"feComposite"
+"feConvolveMatrix"
+"feDiffuseLighting"
+"feDisplacementMap"
+"feDistantLight"
+"feFlood"
+"feFuncA"
+"feFuncB"
+"feFuncG"
+"feFuncR"
+"feGaussianBlur"
+"feImage"
+"feMerge"
+"feMergeNode"
+"feMorphology"
+"feOffset"
+"fePointLight"
+"feSpecularLighting"
+"feSpotLight"
+"feTile"
+"feTurbulence"
+"fill"
+"fill-opacity"
+"fill-rule"
+"filter"
+"filterRes"
+"filterUnits"
+"flood-color"
+"flood-opacity"
+"font"
+"font-face"
+"font-face-format"
+"font-face-name"
+"font-face-src"
+"font-face-uri"
+"font-family"
+"font-size"
+"font-size-adjust"
+"font-stretch"
+"font-style"
+"font-variant"
+"font-weight"
+"foreignObject"
+"format"
+"from"
+"fx"
+"fy"
+"g"
+"g1"
+"g2"
+"geometricPrecision"
+"glyph"
+"glyph-name"
+"glyph-orientation-horizontal"
+"glyph-orientation-vertical"
+"glyphRef"
+"gradientTransform"
+"gradientUnits"
+"hanging"
+"height"
+"help"
+"hidden"
+"hkern"
+"horiz-adv-x"
+"horiz-origin-x"
+"horiz-origin-y"
+"icon"
+"id"
+"ideographic"
+"image"
+"image-rendering"
+"in"
+"in2"
+"individual"
+"inherit"
+"inline"
+"inline-table"
+"intercept"
+"italic"
+"k"
+"k1"
+"k2"
+"k3"
+"k4"
+"kernelMatrix"
+"kernelUnitLength"
+"kerning"
+"keyPoints"
+"keySplines"
+"keyTimes"
+"lang"
+"lengthAdjust"
+"letter-spacing"
+"lighter"
+"lighting-color"
+"limitingConeAngle"
+"line"
+"linearGradient"
+"linearRGB"
+"'line-height'"
+"line-through"
+"list-item"
+"local"
+"lr"
+"lr-tb"
+"ltr"
+"marker"
+"marker-end"
+"markerHeight"
+"marker-mid"
+"marker-start"
+"markerUnits"
+"markerWidth"
+"mask"
+"maskContentUnits"
+"maskUnits"
+"mathematical"
+"max"
+"media"
+"menu"
+"message-box"
+"metadata"
+"method"
+"middle"
+"min"
+"missing-glyph"
+"miter"
+"mode"
+"move"
+"mpath"
+"name"
+"narrower"
+"ne-resize"
+"new"
+"no-change"
+"none"
+"nonzero"
+"normal"
+"n-resize"
+"numOctaves"
+"nw-resize"
+"oblique"
+"offset"
+"onabort"
+"onactivate"
+"onbegin"
+"onclick"
+"onend"
+"onerror"
+"onfocusin"
+"onfocusout"
+"onload"
+"onmousedown"
+"onmousemove"
+"onmouseout"
+"onmouseover"
+"onmouseup"
+"onrepeat"
+"onresize"
+"onscroll"
+"onunload"
+"onzoom"
+"opacity"
+"operator"
+"optimizeLegibility"
+"optimizeQuality"
+"optimizeSpeed"
+"order"
+"orient"
+"orientation"
+"origin"
+"overflow"
+"overline"
+"overline-position"
+"overline-thickness"
+"paint"
+"painted"
+"panose-1"
+"path"
+"pathLength"
+"pattern"
+"patternContentUnits"
+"patternTransform"
+"patternUnits"
+"pointer"
+"pointer-events"
+"points"
+"pointsAtX"
+"pointsAtY"
+"pointsAtZ"
+"polygon"
+"polyline"
+"preserveAlpha"
+"preserveAspectRatio"
+"primitiveUnits"
+"properties"
+"r"
+"radialGradient"
+"radius"
+"rect"
+"refX"
+"refY"
+"rendering-intent"
+"repeatCount"
+"repeatDur"
+"requiredExtensions"
+"requiredFeatures"
+"reset-size"
+"restart"
+"result"
+"rl"
+"rl-tb"
+"rotate"
+"round"
+"rtl"
+"run-in"
+"rx"
+"ry"
+"scale"
+"script"
+"scroll"
+"see"
+"seed"
+"semi-condensed"
+"semi-expanded"
+"se-resize"
+"set"
+"shape-rendering"
+"slope"
+"small-caps"
+"small-caption"
+"spacing"
+"Specifying"
+"specularConstant"
+"specularExponent"
+"spreadMethod"
+"square"
+"s-resize"
+"sRGB"
+"start"
+"startOffset"
+"status-bar"
+"stdDeviation"
+"stemh"
+"stemv"
+"stitchTiles"
+"stop"
+"stop-color"
+"stop-opacity"
+"strikethrough-position"
+"strikethrough-thickness"
+"string"
+string_any="ANY"
+string_brackets="[]"
+string_cdata="CDATA"
+string_col_fallback=":fallback"
+string_col_generic=":a"
+string_col_include=":include"
+string_dashes="--"
+string_empty_dblquotes="\"\""
+string_empty="EMPTY"
+string_empty_quotes="''"
+string_entities="ENTITIES"
+string_entity="ENTITY"
+string_fixed="#FIXED"
+string_id="ID"
+string_idref="IDREF"
+string_idrefs="IDREFS"
+string_implied="#IMPLIED"
+string_nmtoken="NMTOKEN"
+string_nmtokens="NMTOKENS"
+string_notation="NOTATION"
+string_parentheses="()"
+string_pcdata="#PCDATA"
+string_percent="%a"
+string_public="PUBLIC"
+string_required="#REQUIRED"
+string_schema=":schema"
+string_system="SYSTEM"
+string_ucs4="UCS-4"
+string_utf16="UTF-16"
+string_utf8="UTF-8"
+string_xmlns="xmlns:"
+"stroke"
+"stroke-dasharray"
+"stroke-dashoffset"
+"stroke-linecap"
+"stroke-linejoin"
+"stroke-miterlimit"
+"stroke-opacity"
+"stroke-width"
+"style"
+"sub"
+"super"
+"surfaceScale"
+"svg"
+"switch"
+"sw-resize"
+"symbol"
+"systemLanguage"
+"table"
+"table-caption"
+"table-cell"
+"table-column"
+"table-column-group"
+"table-footer-group"
+"table-header-group"
+"table-row"
+"table-row-group"
+"tableValues"
+tag_attlist="<!ATTLIST"
+tag_cdata="<![CDATA["
+tag_close="</a>"
+tag_doctype="<!DOCTYPE"
+tag_element="<!ELEMENT"
+tag_entity="<!ENTITY"
+tag_ignore="<![IGNORE["
+tag_include="<![INCLUDE["
+tag_notation="<!NOTATION"
+tag_open="<a>"
+tag_open_close="<a />"
+tag_open_exclamation="<!"
+tag_open_q="<?"
+tag_sq2_close="]]>"
+tag_xml_q="<?xml?>"
+"target"
+"targetX"
+"targetY"
+"tb"
+"tb-rl"
+"text"
+"text-after-edge"
+"text-anchor"
+"text-before-edge"
+"text-decoration"
+"textLength"
+"textPath"
+"text-rendering"
+"title"
+"to"
+"transform"
+"tref"
+"tspan"
+"type"
+"u1"
+"u2"
+"ultra-condensed"
+"ultra-expanded"
+"underline"
+"underline-position"
+"underline-thickness"
+"unicode"
+"unicode-bidi"
+"unicode-range"
+"units-per-em"
+"use"
+"use-script"
+"v-alphabetic"
+"values"
+"version"
+"vert-adv-y"
+"vert-origin-x"
+"vert-origin-y"
+"v-hanging"
+"v-ideographic"
+"view"
+"viewBox"
+"viewTarget"
+"visibility"
+"visible"
+"visibleFill"
+"visiblePainted"
+"visibleStroke"
+"vkern"
+"v-mathematical"
+"wait"
+"wider"
+"width"
+"widths"
+"word-spacing"
+"w-resize"
+"writing-mode"
+"x"
+"x1"
+"x2"
+"xChannelSelector"
+"x-height"
+"xlink:actuate"
+"xlink:arcrole"
+"xlink:href"
+"xlink:role"
+"xlink:show"
+"xlink:title"
+"xlink:type"
+#XML
+"xml:base"
+"xml:lang"
+"xml:space"
+"y"
+"y1"
+"y2"
+"yChannelSelector"
+"z"
+"zoomAndPan"
diff --git a/testfiles/rendering_tests/CMakeLists.txt b/testfiles/rendering_tests/CMakeLists.txt
new file mode 100644
index 0000000..f3bef01
--- /dev/null
+++ b/testfiles/rendering_tests/CMakeLists.txt
@@ -0,0 +1,52 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+#add your test here (do not put the .svg extension)
+set(RENDERING_TESTS
+ # -- Generic tests --
+ test-empty
+ test-dont-crash
+
+ # -- Selector tests --
+ selector-important-002
+ selector-important-003
+
+ multi-style
+
+ # -- Text tests --
+ ## Many (if not all) of these tests are sensitive to the text rendering stack: FreeType, HarfBuzz, Pango.
+
+ # test-baseline-shift
+ ## Small differences with code adapted for Pango 1.44.
+
+ # test-glyph-y-pos
+ ## to be fixed since an update happened between harfbuzz 1.5.1(OK) and 1.6.0(FAIL).
+ ## If you re-enable the test, you may have to *slightly* fix the expected rendering (hoping the fix happens upstream).
+ ## Please also check that the rendering with harfbuzz <=1.5.1 is not *too* wrong (for older systems)
+ ## cf Tav's post : https://www.patreon.com/posts/into-sinkhole-19021727
+ ## and bug https://bugzilla.gnome.org/show_bug.cgi?id=787526
+
+ # test-rtl-vertical
+
+ # text-shaping
+ ## Expected rendering generated with Pango 1.44. Currently fails with
+ ## CI as CI uses Pango 1.40. Enable after updating CI to Ubuntu 20.04.
+
+ # text-glyphs-combining.svg
+ ## Expected rendering generated with Pango 1.44.
+
+ # text-glyphs-vertical.svg
+ ## Expected rendering generated with Pango 1.44.
+
+ # -- LPE tests --
+ test-powerstroke-join
+)
+
+
+foreach(rendering_test ${RENDERING_TESTS})
+ set(testname "render_${rendering_test}")
+ add_test(NAME ${testname}
+ COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/test.sh ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/inkscape ${CMAKE_CURRENT_SOURCE_DIR}/${rendering_test}
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/testfiles/rendering_tests)
+ set_tests_properties(${testname} PROPERTIES ENVIRONMENT "${INKSCAPE_TEST_PROFILE_DIR_ENV}/${testname};${CMAKE_CTEST_ENV}")
+endforeach()
+
diff --git a/testfiles/rendering_tests/README b/testfiles/rendering_tests/README
new file mode 100644
index 0000000..6ebcb4c
--- /dev/null
+++ b/testfiles/rendering_tests/README
@@ -0,0 +1,26 @@
+HOWTO
+
+# Add a rendering test:
+ - create the svg file
+ - 0.92:
+ - inkscape <yourfile>.svg -d 96 -e expected_rendering/<yourfile>.png
+ - inkscape <yourfile>.svg -d 384 -e expected_rendering/<yourfile>-large.png
+ - 1.0:
+ - inkscape -d 96 --export-filename=expected_rendering/<yourfile>.png <yourfile>.svg
+ - inkscape -d 384 --export-filename=expected_rendering/<yourfile>-large.png <yourfile>.svg
+ - add the test in CMakeLists.txt
+ - use stable if possible to generate the reference png files
+ - git add <yourfile>.svg expected_rendering/<yourfile>-large.png expected_rendering/<yourfile>.png
+
+# Fix a failing test (due to a change in code):
+ - DO *NOT* MODIFY the expected rendering (or the svg) before getting advice from inkscape-devel@
+ - fix your code if possible
+ - IF you change introduces a greater compatibility with css or browsers
+ - AND you cannot reasonably "update" files from older versions to match the appearance
+ - AND inkscape-devel@ has a consensus that it's the only way
+ -> do as you must
+ - manually double check the changes
+
+# Fix a failing test (due to a change in pixman or cairo):
+ - update renderings. Use a *stable* version to generate the renderings, NOT TRUNK
+ - manually check appearances
diff --git a/testfiles/rendering_tests/expected_rendering/multi-style.png b/testfiles/rendering_tests/expected_rendering/multi-style.png
new file mode 100644
index 0000000..7c7c2fb
--- /dev/null
+++ b/testfiles/rendering_tests/expected_rendering/multi-style.png
Binary files differ
diff --git a/testfiles/rendering_tests/expected_rendering/selector-important-002-large.png b/testfiles/rendering_tests/expected_rendering/selector-important-002-large.png
new file mode 100644
index 0000000..e92eef0
--- /dev/null
+++ b/testfiles/rendering_tests/expected_rendering/selector-important-002-large.png
Binary files differ
diff --git a/testfiles/rendering_tests/expected_rendering/selector-important-002.png b/testfiles/rendering_tests/expected_rendering/selector-important-002.png
new file mode 100644
index 0000000..b0af9bd
--- /dev/null
+++ b/testfiles/rendering_tests/expected_rendering/selector-important-002.png
Binary files differ
diff --git a/testfiles/rendering_tests/expected_rendering/selector-important-003-large.png b/testfiles/rendering_tests/expected_rendering/selector-important-003-large.png
new file mode 100644
index 0000000..91cb3af
--- /dev/null
+++ b/testfiles/rendering_tests/expected_rendering/selector-important-003-large.png
Binary files differ
diff --git a/testfiles/rendering_tests/expected_rendering/selector-important-003.png b/testfiles/rendering_tests/expected_rendering/selector-important-003.png
new file mode 100644
index 0000000..dfe3dbc
--- /dev/null
+++ b/testfiles/rendering_tests/expected_rendering/selector-important-003.png
Binary files differ
diff --git a/testfiles/rendering_tests/expected_rendering/test-baseline-shift-large.png b/testfiles/rendering_tests/expected_rendering/test-baseline-shift-large.png
new file mode 100644
index 0000000..29369a8
--- /dev/null
+++ b/testfiles/rendering_tests/expected_rendering/test-baseline-shift-large.png
Binary files differ
diff --git a/testfiles/rendering_tests/expected_rendering/test-baseline-shift.png b/testfiles/rendering_tests/expected_rendering/test-baseline-shift.png
new file mode 100644
index 0000000..45aed90
--- /dev/null
+++ b/testfiles/rendering_tests/expected_rendering/test-baseline-shift.png
Binary files differ
diff --git a/testfiles/rendering_tests/expected_rendering/test-dont-crash.png b/testfiles/rendering_tests/expected_rendering/test-dont-crash.png
new file mode 100644
index 0000000..a2d005e
--- /dev/null
+++ b/testfiles/rendering_tests/expected_rendering/test-dont-crash.png
Binary files differ
diff --git a/testfiles/rendering_tests/expected_rendering/test-empty-large.png b/testfiles/rendering_tests/expected_rendering/test-empty-large.png
new file mode 100644
index 0000000..34acf1f
--- /dev/null
+++ b/testfiles/rendering_tests/expected_rendering/test-empty-large.png
Binary files differ
diff --git a/testfiles/rendering_tests/expected_rendering/test-empty.png b/testfiles/rendering_tests/expected_rendering/test-empty.png
new file mode 100644
index 0000000..2e0a5fe
--- /dev/null
+++ b/testfiles/rendering_tests/expected_rendering/test-empty.png
Binary files differ
diff --git a/testfiles/rendering_tests/expected_rendering/test-glyph-y-pos-large.png b/testfiles/rendering_tests/expected_rendering/test-glyph-y-pos-large.png
new file mode 100644
index 0000000..e60f477
--- /dev/null
+++ b/testfiles/rendering_tests/expected_rendering/test-glyph-y-pos-large.png
Binary files differ
diff --git a/testfiles/rendering_tests/expected_rendering/test-glyph-y-pos.png b/testfiles/rendering_tests/expected_rendering/test-glyph-y-pos.png
new file mode 100644
index 0000000..85a3050
--- /dev/null
+++ b/testfiles/rendering_tests/expected_rendering/test-glyph-y-pos.png
Binary files differ
diff --git a/testfiles/rendering_tests/expected_rendering/test-powerstroke-join-large.png b/testfiles/rendering_tests/expected_rendering/test-powerstroke-join-large.png
new file mode 100644
index 0000000..72d8821
--- /dev/null
+++ b/testfiles/rendering_tests/expected_rendering/test-powerstroke-join-large.png
Binary files differ
diff --git a/testfiles/rendering_tests/expected_rendering/test-powerstroke-join.png b/testfiles/rendering_tests/expected_rendering/test-powerstroke-join.png
new file mode 100644
index 0000000..1eeec07
--- /dev/null
+++ b/testfiles/rendering_tests/expected_rendering/test-powerstroke-join.png
Binary files differ
diff --git a/testfiles/rendering_tests/expected_rendering/test-rtl-vertical-large.png b/testfiles/rendering_tests/expected_rendering/test-rtl-vertical-large.png
new file mode 100644
index 0000000..84abe78
--- /dev/null
+++ b/testfiles/rendering_tests/expected_rendering/test-rtl-vertical-large.png
Binary files differ
diff --git a/testfiles/rendering_tests/expected_rendering/test-rtl-vertical.png b/testfiles/rendering_tests/expected_rendering/test-rtl-vertical.png
new file mode 100644
index 0000000..ea8836f
--- /dev/null
+++ b/testfiles/rendering_tests/expected_rendering/test-rtl-vertical.png
Binary files differ
diff --git a/testfiles/rendering_tests/expected_rendering/text-glyphs-combining-large.png b/testfiles/rendering_tests/expected_rendering/text-glyphs-combining-large.png
new file mode 100644
index 0000000..6952968
--- /dev/null
+++ b/testfiles/rendering_tests/expected_rendering/text-glyphs-combining-large.png
Binary files differ
diff --git a/testfiles/rendering_tests/expected_rendering/text-glyphs-combining.png b/testfiles/rendering_tests/expected_rendering/text-glyphs-combining.png
new file mode 100644
index 0000000..98e06a7
--- /dev/null
+++ b/testfiles/rendering_tests/expected_rendering/text-glyphs-combining.png
Binary files differ
diff --git a/testfiles/rendering_tests/expected_rendering/text-glyphs-vertical-large.png b/testfiles/rendering_tests/expected_rendering/text-glyphs-vertical-large.png
new file mode 100644
index 0000000..d7091db
--- /dev/null
+++ b/testfiles/rendering_tests/expected_rendering/text-glyphs-vertical-large.png
Binary files differ
diff --git a/testfiles/rendering_tests/expected_rendering/text-glyphs-vertical.png b/testfiles/rendering_tests/expected_rendering/text-glyphs-vertical.png
new file mode 100644
index 0000000..eb46075
--- /dev/null
+++ b/testfiles/rendering_tests/expected_rendering/text-glyphs-vertical.png
Binary files differ
diff --git a/testfiles/rendering_tests/expected_rendering/text-shaping-large.png b/testfiles/rendering_tests/expected_rendering/text-shaping-large.png
new file mode 100644
index 0000000..e84ebf5
--- /dev/null
+++ b/testfiles/rendering_tests/expected_rendering/text-shaping-large.png
Binary files differ
diff --git a/testfiles/rendering_tests/expected_rendering/text-shaping.png b/testfiles/rendering_tests/expected_rendering/text-shaping.png
new file mode 100644
index 0000000..346ab77
--- /dev/null
+++ b/testfiles/rendering_tests/expected_rendering/text-shaping.png
Binary files differ
diff --git a/testfiles/rendering_tests/fonts/Estedad-Medium.ttf b/testfiles/rendering_tests/fonts/Estedad-Medium.ttf
new file mode 100644
index 0000000..d4844c2
--- /dev/null
+++ b/testfiles/rendering_tests/fonts/Estedad-Medium.ttf
Binary files differ
diff --git a/testfiles/rendering_tests/fonts/GeomTest-Regular.otf b/testfiles/rendering_tests/fonts/GeomTest-Regular.otf
new file mode 100755
index 0000000..f11fd09
--- /dev/null
+++ b/testfiles/rendering_tests/fonts/GeomTest-Regular.otf
Binary files differ
diff --git a/testfiles/rendering_tests/fonts/LICENSES b/testfiles/rendering_tests/fonts/LICENSES
new file mode 100644
index 0000000..71bfc0e
--- /dev/null
+++ b/testfiles/rendering_tests/fonts/LICENSES
@@ -0,0 +1,10 @@
+
+All fonts in this directory are licensed under open licenses.
+
+Lohit (https://pagure.io/lohit SIL Open Font 1.1)
+NotoSans (https://www.google.com/get/noto/ Open Font License 1.1)
+Estedad (https://github.com/aminabedi68/Estedad/ SIL Open Font 1.1)
+
+GeomTest Released under Open Font Licens 1.1. Copyright Tavmjong Bah 2015,2019
+
+
diff --git a/testfiles/rendering_tests/fonts/Lohit-Telugu.ttf b/testfiles/rendering_tests/fonts/Lohit-Telugu.ttf
new file mode 100644
index 0000000..3869703
--- /dev/null
+++ b/testfiles/rendering_tests/fonts/Lohit-Telugu.ttf
Binary files differ
diff --git a/testfiles/rendering_tests/fonts/NotoSans-Regular.ttf b/testfiles/rendering_tests/fonts/NotoSans-Regular.ttf
new file mode 100644
index 0000000..b031a49
--- /dev/null
+++ b/testfiles/rendering_tests/fonts/NotoSans-Regular.ttf
Binary files differ
diff --git a/testfiles/rendering_tests/fonts/NotoSansCJKjp-Regular.otf b/testfiles/rendering_tests/fonts/NotoSansCJKjp-Regular.otf
new file mode 100644
index 0000000..296fbeb
--- /dev/null
+++ b/testfiles/rendering_tests/fonts/NotoSansCJKjp-Regular.otf
Binary files differ
diff --git a/testfiles/rendering_tests/fonts/NotoSansHebrew-Regular.ttf b/testfiles/rendering_tests/fonts/NotoSansHebrew-Regular.ttf
new file mode 100644
index 0000000..9bf03ab
--- /dev/null
+++ b/testfiles/rendering_tests/fonts/NotoSansHebrew-Regular.ttf
Binary files differ
diff --git a/testfiles/rendering_tests/multi-style.svg b/testfiles/rendering_tests/multi-style.svg
new file mode 100644
index 0000000..473192e
--- /dev/null
+++ b/testfiles/rendering_tests/multi-style.svg
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="80"
+ height="80"
+ viewBox="0 0 80 80"
+ version="1.1">
+ <style
+ id="first">
+rect { fill: #800000; }
+/* class "c1" is redefined several times, only the last one counts */
+.c1 { fill: #ff0000; }
+.c2 { fill: #ff9900; }
+</style>
+ <defs>
+ <style
+ id="insidedefs">
+rect { fill: #ffff00; }
+.c1 { fill: #008000; }
+.c3 { fill: #00ff00; }
+</style>
+ </defs>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <rect
+ id="background"
+ width="80"
+ height="80" />
+ <rect
+ width="20"
+ height="20" />
+ <rect
+ width="20"
+ height="20"
+ x="30"
+ class="c1" />
+ <rect
+ width="20"
+ height="20"
+ x="60"
+ class="c2" />
+ <rect
+ width="20"
+ height="20"
+ class="c3"
+ y="30" />
+ <rect
+ class="c4"
+ width="20"
+ height="20"
+ x="30"
+ y="30" />
+ <rect
+ class="c5"
+ width="20"
+ height="20"
+ x="60"
+ y="30" />
+ <style
+ id="insidegroup">
+#background { fill: white; }
+.c5 { fill: #00ccff; }
+/* nested CSS selector (with XML entity because not using CDATA) */
+g.g1 &gt; rect { fill: #00ffcc; }
+</style>
+ <rect
+ class="c5"
+ width="20"
+ height="20"
+ y="60" />
+ <rect
+ width="20"
+ height="20"
+ x="30"
+ y="60" />
+ <g
+ class="g1">
+ <rect
+ width="20"
+ height="20"
+ x="60"
+ y="60" />
+ </g>
+ </g>
+ <style
+ id="last"><!-- using CDATA --><![CDATA[
+rect { fill: #0000ff; }
+.c1 { fill: #990099; }
+.c4 { fill: #ff00ff; }
+]]></style>
+</svg>
diff --git a/testfiles/rendering_tests/selector-important-002.svg b/testfiles/rendering_tests/selector-important-002.svg
new file mode 100644
index 0000000..e5a66b6
--- /dev/null
+++ b/testfiles/rendering_tests/selector-important-002.svg
@@ -0,0 +1,58 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="480" height="360"
+ style="fill:orange !important">
+
+ <title>Style "!important" — 002</title>
+
+ <style type="text/css">
+
+ <!-- Later rule overrides same specificity previous rule. -->
+ #groupA use { fill: red !important; }
+ #groupA use { fill: blue !important; }
+
+ #groupB .classB { fill: red !important; }
+ #groupB .classB { fill: blue !important; }
+
+ #groupC #MyRectC { fill: red !important; }
+ #groupC #MyRectC { fill: blue !important; }
+
+ #groupD { fill: blue !important; }
+ #classD { fill: red !important; }
+
+ #groupE use { fill: blue !important; }
+ #groupE { fill: red !important; }
+ </style>
+
+ <defs>
+ <rect id="MyRect" width="40" height="40"/>
+ </defs>
+
+ <!--
+ <text id="title" x="240" y="50" style="fill:black; font-size:24px; text-anchor:middle;">Style "!important" — 002</text>
+ <a href="https://svgwg.org/svg2-draft/stylling.html">
+ <text id="source" x="240" y="70" style="fill:black; font-size:12px; text-anchor:middle;">https://svgwg.org/svg2-draft/styling.html</text>
+ </a>
+ -->
+
+ <g id="groupA">
+ <use id="MyRectA" class="classA" x="20" y="100" xlink:href="#MyRect" />
+ </g>
+
+ <g id="groupB">
+ <use id="MyRectB" class="classB" x="120" y="100" xlink:href="#MyRect" />
+ </g>
+
+ <g id="groupC">
+ <use id="MyRectC" class="classC" x="220" y="100" xlink:href="#MyRect" />
+ </g>
+
+ <g id="groupD">
+ <use id="MyRectD" class="classD" x="320" y="100" xlink:href="#MyRect" />
+ </g>
+
+ <g id="groupE">
+ <use id="MyRectE" class="classE" x="420" y="100" xlink:href="#MyRect" />
+ </g>
+
+</svg>
diff --git a/testfiles/rendering_tests/selector-important-003.svg b/testfiles/rendering_tests/selector-important-003.svg
new file mode 100644
index 0000000..831319f
--- /dev/null
+++ b/testfiles/rendering_tests/selector-important-003.svg
@@ -0,0 +1,57 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="480" height="360">
+
+ <title>Style "!important" — 003</title>
+
+ <style type="text/css">
+
+ /* !important is not inherited. */
+ g #groupA { fill: red !important; }
+ use { fill: blue; }
+
+ /* Attributes cannot have !important. */
+ #MyRectB { fill: blue; }
+
+ /* Inline can have !important. */
+ #MyRectC { fill: red !important; }
+
+ /* Bad property shouldn't set !important. */
+ #MyRectD { fill: XXX !important; }
+
+ /* Bad inline property shouldn't set !important. */
+ #MyRectE { fill: blue; }
+ </style>
+
+ <defs>
+ <rect id="MyRect" width="40" height="40"/>
+ </defs>
+
+ <!--
+ <text id="title" x="240" y="50" style="fill:black; font-size:24px; text-anchor:middle;">Style "!important" — 003</text>
+ <a href="https://svgwg.org/svg2-draft/stylling.html">
+ <text id="source" x="240" y="70" style="fill:black; font-size:12px; text-anchor:middle;">https://svgwg.org/svg2-draft/styling.html</text>
+ </a>
+ -->
+
+ <g id="groupA">
+ <use id="MyRectA" class="classA" x="20" y="100" xlink:href="#MyRect" />
+ </g>
+
+ <g id="groupB">
+ <use id="MyRectB" class="classB" x="120" y="100" xlink:href="#MyRect" fill="red !important"/>
+ </g>
+
+ <g id="groupC">
+ <use id="MyRectC" class="classC" x="220" y="100" xlink:href="#MyRect" style="fill: blue !important"/>
+ </g>
+
+ <g id="groupD">
+ <use id="MyRectD" class="classD" x="320" y="100" xlink:href="#MyRect" style="fill: blue"/>
+ </g>
+
+ <g id="groupE">
+ <use id="MyRectE" class="classE" x="420" y="100" xlink:href="#MyRect" style="fill: XXX !important"/>
+ </g>
+
+</svg>
diff --git a/testfiles/rendering_tests/test-baseline-shift.svg b/testfiles/rendering_tests/test-baseline-shift.svg
new file mode 100644
index 0000000..534ffab
--- /dev/null
+++ b/testfiles/rendering_tests/test-baseline-shift.svg
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<svg width="600" height="600"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.1"
+ viewBox="0 0 600 600">
+ <style type="text/css">
+ text {
+ font-family: DejaVu Sans;
+ font-size: 36px;
+ }
+ .title {
+ text-anchor: middle;
+ }
+ </style>
+
+ <g>
+ <text x="50" y="200">subscript: H<tspan style="font-size:65%;baseline-shift:sub">2</tspan>O</text>
+ <text x="50" y="300">superscript: m<tspan style="font-size:65%;baseline-shift:super">2</tspan></text>
+ <text x="530" y="200" style="writing-mode:tb-rl">subscript: H<tspan style="font-size:65%;baseline-shift:sub">2</tspan>O</text>
+ <text x="430" y="200" style="writing-mode:tb-rl">superscript: m<tspan style="font-size:65%;baseline-shift:super">2</tspan></text>
+ </g>
+
+ <text class="title" x="50%" y="120">Sub- and Superscript</text>
+
+</svg>
diff --git a/testfiles/rendering_tests/test-dont-crash.svg b/testfiles/rendering_tests/test-dont-crash.svg
new file mode 100644
index 0000000..d01c0ea
--- /dev/null
+++ b/testfiles/rendering_tests/test-dont-crash.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ viewBox="0 0 210 297"
+ height="297mm"
+ width="210mm">
+ <g
+ id="layer1"
+ inkscape:groupmode="layer"
+ inkscape:label="Layer 1">
+ <!-- missing xlink:href attribute - don't care how it's rendered (place it
+ off-page), but Inkscape should not crash -->
+ <image
+ y="-100"
+ x="-100"
+ height="50"
+ width="50" />
+ </g>
+</svg>
diff --git a/testfiles/rendering_tests/test-empty.svg b/testfiles/rendering_tests/test-empty.svg
new file mode 100644
index 0000000..3b5ee5a
--- /dev/null
+++ b/testfiles/rendering_tests/test-empty.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="210mm"
+ height="297mm"
+ viewBox="0 0 210 297"
+ version="1.1"
+ id="svg8"
+ inkscape:version="0.92.1 r15371"
+ sodipodi:docname="test-empty.svg">
+ <defs
+ id="defs2" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.7"
+ inkscape:cx="132.86611"
+ inkscape:cy="438.52092"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1055"
+ inkscape:window-x="1920"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <rect
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:2.64583325;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98039216"
+ id="rect7"
+ width="92.226196"
+ height="75.595238"
+ x="52.160713"
+ y="46.023808"
+ rx="6.6565199"
+ ry="6.6565199" />
+ </g>
+</svg>
diff --git a/testfiles/rendering_tests/test-glyph-y-pos.svg b/testfiles/rendering_tests/test-glyph-y-pos.svg
new file mode 100644
index 0000000..28200c7
--- /dev/null
+++ b/testfiles/rendering_tests/test-glyph-y-pos.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<svg width="600" height="600"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.1"
+ viewBox="0 0 600 600">
+ <style type="text/css">
+ text {
+ font-family: DejaVu Sans;
+ font-size: 36px;
+ }
+ .title {
+ text-anchor: middle;
+ }
+ </style>
+
+ <g>
+ <text x="50" y="200">G̃g̃X̃x̃</text>
+ <text x="300" y="200" style="writing-mode:vertical-lr;">G̃g̃X̃x̃</text>
+ <text x="500" y="200" style="writing-mode:vertical-lr;text-orientation:upright">G̃g̃X̃x̃</text>
+ </g>
+
+ <text class="title" x="50%" y="120">Composed Glyphs</text>
+
+</svg>
diff --git a/testfiles/rendering_tests/test-powerstroke-join.svg b/testfiles/rendering_tests/test-powerstroke-join.svg
new file mode 100644
index 0000000..2c05fb3
--- /dev/null
+++ b/testfiles/rendering_tests/test-powerstroke-join.svg
@@ -0,0 +1,6 @@
+<svg xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns="http://www.w3.org/2000/svg" height="103.10469" width="114.42079">
+ <defs id="defs8">
+ <inkscape:path-effect end_linecap_type="zerowidth" scale_width="1" miter_limit="4" linejoin_type="extrp_arc" start_linecap_type="zerowidth" interpolator_beta="0.2" interpolator_type="CubicBezierSmooth" sort_points="true" offset_points="0.2,6 | 1,6 | 1.8,6" lpeversion="1" is_visible="true" id="path-effect12" effect="powerstroke" />
+ </defs>
+ <path inkscape:original-d="M 37.025152,63.80944 C 62.629473,85.82239 121.42349,99.11534 92.9073,25.53973 73.516303,45.478852 29.421533,5.937486 9.304592,36.285332" inkscape:path-effect="#path-effect12"/>
+</svg>
diff --git a/testfiles/rendering_tests/test-rtl-vertical.svg b/testfiles/rendering_tests/test-rtl-vertical.svg
new file mode 100644
index 0000000..a959c18
--- /dev/null
+++ b/testfiles/rendering_tests/test-rtl-vertical.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<svg width="600" height="600"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.1"
+ viewBox="0 0 600 600">
+ <style type="text/css">
+ text {
+ font-family: DejaVu Sans;
+ font-size: 36px;
+ }
+ .title {
+ text-anchor: middle;
+ }
+ </style>
+
+ <g>
+ <text x="50" y="200">أبجد</text>
+ <text x="300" y="200" style="writing-mode:vertical-lr;">بجد</text>
+ <text x="500" y="200" style="writing-mode:vertical-lr;text-orientation:upright">أبجد</text>
+ </g>
+
+ <text class="title" x="50%" y="120">RTL text in vertical mode</text>
+
+</svg>
diff --git a/testfiles/rendering_tests/test.sh b/testfiles/rendering_tests/test.sh
new file mode 100755
index 0000000..11167dd
--- /dev/null
+++ b/testfiles/rendering_tests/test.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+if [ "$#" -lt 2 ]; then
+ echo "pass the path of the inkscape executable as parameter then the name of the test" $#
+ exit 1
+fi
+
+command -v compare >/dev/null 2>&1 || { echo >&2 "I require ImageMagick's 'compare' but it's not installed. Aborting."; exit 1; }
+
+INKSCAPE_EXE=$1
+exit_status=0
+test=$2
+EXPECTED=$(dirname $test)"/expected_rendering/"$(basename $test)
+testname=$(basename $test)
+
+
+ ${INKSCAPE_EXE} --export-filename=${testname}.png -d 96 ${test}.svg #2>/dev/null >/dev/null
+ compare -metric AE ${testname}.png ${EXPECTED}.png ${testname}-compare.png 2> ${testname}-result.txt
+ test1=`cat ${testname}-result.txt`
+ echo $test1
+ if [ "$test1" = 0 ]; then
+ echo ${testname} "PASSED"
+ rm ${testname}.png ${testname}-compare.png
+ else
+ echo ${testname} "FAILED"
+ exit_status=1
+ fi
+
+if [ -f "${EXPECTED}-large.png" ]; then
+ ${INKSCAPE_EXE} --export-filename=${testname}-large.png -d 384 ${test}.svg #2>/dev/null >/dev/null
+ compare -metric AE ${testname}-large.png ${EXPECTED}-large.png ${testname}-compare-large.png 2> ${testname}-result.txt
+ test2=`cat ${testname}-result.txt`
+ if [ "$test2" = 0 ]; then
+ echo ${testname}-large "PASSED"
+ rm ${testname}-large.png ${testname}-compare-large.png
+ else
+ echo ${testname}-large "FAILED"
+ exit_status=1
+ fi
+else
+ echo ${testname}-large "SKIPPED"
+fi
+
+rm ${testname}-result.txt
+exit $exit_status
diff --git a/testfiles/rendering_tests/text-glyphs-combining.svg b/testfiles/rendering_tests/text-glyphs-combining.svg
new file mode 100644
index 0000000..29be00e
--- /dev/null
+++ b/testfiles/rendering_tests/text-glyphs-combining.svg
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ width="100%"
+ height="100%"
+ viewBox="0 0 600 600">
+
+ <style type="text/css">
+
+ @font-face {
+ font-family: "Noto Sans";
+ src: url("fonts/NotoSans-Regular.ttf");
+ }
+
+ text {
+ font-family: "Noto Sans";
+ font-size: 30px;
+ }
+
+ </style>
+
+ <text x="25%" y="10%">õőo̓ơọo̫o̳o̻o̓o͋o͗o͡</text>
+ <text x="25%" y="25%" style="writing-mode:vertical-lr">õőo̓ơọo̫o̳o̻o̓o͋o͗o͡</text>
+ <text x="50%" y="25%" style="writing-mode:vertical-lr;text-orientation:upright">õőo̓ơọo̫o̳o̻o̓o͋o͗o͡</text>
+ <text x="75%" y="25%" style="writing-mode:vertical-lr;text-orientation:sideways">õőo̓ơọo̫o̳o̻o̓o͋o͗o͡</text>
+
+ <!-- Show reference point -->
+ <circle cx="25%" cy="10%" r="2" style="fill:lightblue"/>
+ <circle cx="25%" cy="25%" r="2" style="fill:lightblue"/>
+ <circle cx="50%" cy="25%" r="2" style="fill:lightblue"/>
+ <circle cx="75%" cy="25%" r="2" style="fill:lightblue"/>
+
+</svg>
diff --git a/testfiles/rendering_tests/text-glyphs-vertical.svg b/testfiles/rendering_tests/text-glyphs-vertical.svg
new file mode 100644
index 0000000..f1264fd
--- /dev/null
+++ b/testfiles/rendering_tests/text-glyphs-vertical.svg
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ width="100%"
+ height="100%"
+ viewBox="0 0 600 600">
+
+ <style type="text/css">
+
+ @font-face {
+ font-family: "Noto Sans";
+ src: url("fonts/NotoSans-Regular.ttf");
+ }
+
+ @font-face {
+ font-family: "Noto Sans CJK JP";
+ src: url("fonts/NotoSansCJKjp-Regular.otf");
+ }
+
+ @font-face {
+ font-family: "GeomTest";
+ src: url("fonts/GeomTest.otf");
+ }
+
+ text {
+ font-family: "Noto Sans";
+ font-size: 30px;
+ }
+
+ .geomtest {
+ font-family: GeomTest;
+ }
+
+ </style>
+
+ <text x="100" y="100">㆕㆖㆘<tspan class="geomtest">A回ーऄ</tspan>G̃g̃X̃x̃</text>
+ <text x="115" y="200" style="writing-mode:vertical-lr">㆕㆖㆘<tspan class="geomtest">A回ーऄ</tspan>G̃g̃X̃x̃</text>
+ <text x="305" y="200" style="writing-mode:vertical-lr;text-orientation:upright">㆕㆖㆘<tspan class="geomtest">A回ーऄ</tspan>G̃g̃X̃x̃</text>
+ <text x="495" y="200" style="writing-mode:vertical-lr;text-orientation:sideways">㆕㆖㆘<tspan class="geomtest">A回ーऄ</tspan>G̃g̃X̃x̃</text>
+
+ <!-- Show reference point -->
+ <circle cx="100" cy="100" r="2" style="fill:lightblue"/>
+ <circle cx="115" cy="200" r="2" style="fill:lightblue"/>
+ <circle cx="305" cy="200" r="2" style="fill:lightblue"/>
+ <circle cx="495" cy="200" r="2" style="fill:lightblue"/>
+
+</svg>
diff --git a/testfiles/rendering_tests/text-shaping.svg b/testfiles/rendering_tests/text-shaping.svg
new file mode 100644
index 0000000..80aa95a
--- /dev/null
+++ b/testfiles/rendering_tests/text-shaping.svg
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ width="100%"
+ height="100%"
+ viewBox="0 0 600 600">
+
+ <style type="text/css">
+
+ @font-face {
+ font-family: "Estedad";
+ src: url("fonts/Estedad-Medium.ttf");
+ }
+
+ @font-face {
+ font-family: "Noto Sans Hebrew";
+ src: url("fonts/NotoSansHebrew-Regular.ttf");
+ }
+
+ @font-face {
+ font-family: "Noto Sans";
+ src: url("fonts/NotoSans-Regular.ttf");
+ }
+
+ @font-face {
+ font-family: "Noto Sans CJK JP";
+ src: url("fonts/NotoSansCJKjp-Regular.otf");
+ }
+
+ @font-face {
+ font-family: "Lohit Telugu";
+ src: url("fonts/Lohit-Telugu.ttf");
+ }
+
+ </style>
+
+ <g style="fill:none;stroke:black;stroke-width:0.5px;font-size:42px;font-family:serif;text-anchor:middle">
+
+ <!-- bug https://gitlab.com/inkscape/inkscape/-/issues/469 -->
+ <text xml:space="preserve"
+ x="300"
+ y="50"
+ style="font-family:Estedad;direction:rtl">نیرو</text>
+
+ <text xml:space="preserve"
+ x="300"
+ y="100"
+ style="font-family:Estedad;direction:rtl">بÙسْم٠اللَّه٠الرَّحْمَن٠الرَّحÙيمÙ</text>
+
+ <text xml:space="preserve"
+ x="300"
+ y="150"
+ style="font-family:'Noto Sans Hebrew';direction:rtl">ש×ָלוֹ×</text>
+
+ <text xml:space="preserve"
+ x="300"
+ y="200"
+ style="font-family:'Noto Sans Hebrew';direction:rtl">חִירִיק</text>
+
+ <text xml:space="preserve"
+ x="300"
+ y="250"
+ style="font-family:'Noto Sans'">â â̂ â â̂</text>
+
+ <text xml:space="preserve"
+ x="300"
+ y="300"
+ style="font-family:'Noto Sans'">a ḁ ą ą</text>
+
+ <text xml:space="preserve"
+ x="300"
+ y="350"
+ style="font-family:'Noto Sans CJK JP'">ヘ ペ ペ</text>
+
+ <text xml:space="preserve"
+ x="300"
+ y="400"
+ style="font-family:'Lohit Telugu'">తెలà±à°—à±à°²à±‹</text>
+
+ <!-- Teluga bug https://gitlab.com/inkscape/inkscape/-/issues/394 -->
+ <text xml:space="preserve"
+ x="300"
+ y="450"
+ style="font-family:'Lohit Telugu'">à°—à±à°°à°‚థాలయం</text>
+
+ <!-- Teluga bug https://launchpadlibrarian.net/167162208/inkscape-telugu-text.svg -->
+ <text xml:space="preserve"
+ x="300"
+ y="500"
+ style="font-family:'Lohit Telugu'">ఇంకà±â€Œà°¸à±à°•à±‡à°ªà±</text>
+ </g>
+</svg>
diff --git a/testfiles/src/2geom-characterization-test.cpp b/testfiles/src/2geom-characterization-test.cpp
new file mode 100644
index 0000000..d3f099b
--- /dev/null
+++ b/testfiles/src/2geom-characterization-test.cpp
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * 2Geom Lib characterization tests
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2020 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include <gtest/gtest.h>
+
+#include <2geom/path.h>
+
+TEST(Characterization2Geom, retrievingBackElementOfAnEmptyClosedPathFails)
+{
+ Geom::Path path(Geom::Point(3, 5));
+ path.close();
+ ASSERT_TRUE(path.closed());
+ ASSERT_EQ(path.size_closed(), 0u);
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/attributes-test.cpp b/testfiles/src/attributes-test.cpp
new file mode 100644
index 0000000..791f140
--- /dev/null
+++ b/testfiles/src/attributes-test.cpp
@@ -0,0 +1,646 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/*
+ * Unit tests for attributes.
+ *
+ * Author:
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2015 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+#include "attributes.h"
+
+namespace {
+
+static const unsigned int FIRST_VALID_ID = 1;
+
+class AttributeInfo
+{
+public:
+ AttributeInfo(std::string attr, bool supported) :
+ attr(std::move(attr)),
+ supported(supported)
+ {
+ }
+
+ std::string attr;
+ bool supported;
+};
+
+typedef std::vector<AttributeInfo>::iterator AttrItr;
+
+std::vector<AttributeInfo> getKnownAttrs()
+{
+/* Originally extracted mechanically from
+ http://www.w3.org/TR/SVG11/attindex.html:
+
+ tidy -wrap 999 -asxml < attindex.html 2>/dev/null |
+ tr -d \\n |
+ sed 's,<tr>,@,g' |
+ tr @ \\n |
+ sed 's,</td>.*,,;s,^<td>,,;1,/^%/d;/^%/d;s,^, {",;s/$/", false},/' |
+ uniq
+
+ attindex.html lacks attributeName, begin, additive, font, marker;
+ I've added these manually.
+
+ SVG 2: white-space, shape-inside, shape-subtrace, shape-padding, shape-margin
+*/
+ AttributeInfo all_attrs[] = {
+ AttributeInfo("attributeName", true),
+ AttributeInfo("begin", true),
+ AttributeInfo("additive", true),
+ AttributeInfo("font", true),
+ AttributeInfo("-inkscape-font-specification", true), // TODO look into this attribute's name
+ AttributeInfo("marker", true),
+ AttributeInfo("line-height", true),
+
+ AttributeInfo("accent-height", true),
+ AttributeInfo("accumulate", true),
+ AttributeInfo("alignment-baseline", true),
+ AttributeInfo("alphabetic", true),
+ AttributeInfo("amplitude", true),
+ AttributeInfo("animate", false),
+ AttributeInfo("arabic-form", true),
+ AttributeInfo("ascent", true),
+ AttributeInfo("attributeType", true),
+ AttributeInfo("azimuth", true),
+ AttributeInfo("baseFrequency", true),
+ AttributeInfo("baseline-shift", true),
+ AttributeInfo("baseProfile", false),
+ AttributeInfo("bbox", true),
+ AttributeInfo("bias", true),
+ AttributeInfo("by", true),
+ AttributeInfo("calcMode", true),
+ AttributeInfo("cap-height", true),
+ AttributeInfo("class", false),
+ AttributeInfo("clip", true),
+ AttributeInfo("clip-path", true),
+ AttributeInfo("clip-rule", true),
+ AttributeInfo("clipPathUnits", true),
+ AttributeInfo("color", true),
+ AttributeInfo("color-interpolation", true),
+ AttributeInfo("color-interpolation-filters", true),
+ AttributeInfo("color-profile", true),
+ AttributeInfo("color-rendering", true),
+ AttributeInfo("contentScriptType", false),
+ AttributeInfo("contentStyleType", false),
+ AttributeInfo("cursor", true),
+ AttributeInfo("cx", true),
+ AttributeInfo("cy", true),
+ AttributeInfo("d", true),
+ AttributeInfo("descent", true),
+ AttributeInfo("diffuseConstant", true),
+ AttributeInfo("direction", true),
+ AttributeInfo("display", true),
+ AttributeInfo("divisor", true),
+ AttributeInfo("dominant-baseline", true),
+ AttributeInfo("dur", true),
+ AttributeInfo("dx", true),
+ AttributeInfo("dy", true),
+ AttributeInfo("edgeMode", true),
+ AttributeInfo("elevation", true),
+ AttributeInfo("enable-background", true),
+ AttributeInfo("end", true),
+ AttributeInfo("exponent", true),
+ AttributeInfo("externalResourcesRequired", false),
+ AttributeInfo("feBlend", false),
+ AttributeInfo("feColorMatrix", false),
+ AttributeInfo("feComponentTransfer", false),
+ AttributeInfo("feComposite", false),
+ AttributeInfo("feConvolveMatrix", false),
+ AttributeInfo("feDiffuseLighting", false),
+ AttributeInfo("feDisplacementMap", false),
+ AttributeInfo("feFlood", false),
+ AttributeInfo("feGaussianBlur", false),
+ AttributeInfo("feImage", false),
+ AttributeInfo("feMerge", false),
+ AttributeInfo("feMorphology", false),
+ AttributeInfo("feOffset", false),
+ AttributeInfo("feSpecularLighting", false),
+ AttributeInfo("feTile", false),
+ AttributeInfo("fill", true),
+ AttributeInfo("fill-opacity", true),
+ AttributeInfo("fill-rule", true),
+ AttributeInfo("filter", true),
+ AttributeInfo("filterRes", true),
+ AttributeInfo("filterUnits", true),
+ AttributeInfo("flood-color", true),
+ AttributeInfo("flood-opacity", true),
+ AttributeInfo("font-family", true),
+ AttributeInfo("font-feature-settings", true),
+ AttributeInfo("font-size", true),
+ AttributeInfo("font-size-adjust", true),
+ AttributeInfo("font-stretch", true),
+ AttributeInfo("font-style", true),
+ AttributeInfo("font-variant", true),
+ AttributeInfo("font-variant-ligatures", true),
+ AttributeInfo("font-variant-position", true),
+ AttributeInfo("font-variant-caps", true),
+ AttributeInfo("font-variant-numeric", true),
+ AttributeInfo("font-variant-east-asian", true),
+ AttributeInfo("font-variant-alternates", true),
+ AttributeInfo("font-variation-settings", true),
+ AttributeInfo("font-weight", true),
+ AttributeInfo("format", false),
+ AttributeInfo("from", true),
+ AttributeInfo("fx", true),
+ AttributeInfo("fr", true),
+ AttributeInfo("fy", true),
+ AttributeInfo("g1", true),
+ AttributeInfo("g2", true),
+ AttributeInfo("glyph-name", true),
+ AttributeInfo("glyph-orientation-horizontal", true),
+ AttributeInfo("glyph-orientation-vertical", true),
+ AttributeInfo("glyphRef", false),
+ AttributeInfo("gradientTransform", true),
+ AttributeInfo("gradientUnits", true),
+ AttributeInfo("hanging", true),
+ AttributeInfo("hatchContentUnits", true), // SVG 2.0
+ AttributeInfo("hatchTransform", true), // SVG 2.0 TODO renamed to transform
+ AttributeInfo("hatchUnits", true), // SVG 2.0
+ AttributeInfo("height", true),
+ AttributeInfo("horiz-adv-x", true),
+ AttributeInfo("horiz-origin-x", true),
+ AttributeInfo("horiz-origin-y", true),
+ AttributeInfo("ideographic", true),
+ AttributeInfo("image-rendering", true),
+ AttributeInfo("in", true),
+ AttributeInfo("in2", true),
+ AttributeInfo("inline-size", true),
+ AttributeInfo("intercept", true),
+ AttributeInfo("isolation", true),
+ AttributeInfo("k", true),
+ AttributeInfo("k1", true),
+ AttributeInfo("k2", true),
+ AttributeInfo("k3", true),
+ AttributeInfo("k4", true),
+ AttributeInfo("kernelMatrix", true),
+ AttributeInfo("kernelUnitLength", true),
+ AttributeInfo("kerning", true),
+ AttributeInfo("keyPoints", false),
+ AttributeInfo("keySplines", true),
+ AttributeInfo("keyTimes", true),
+ AttributeInfo("lang", true),
+ AttributeInfo("lengthAdjust", true),
+ AttributeInfo("letter-spacing", true),
+ AttributeInfo("lighting-color", true),
+ AttributeInfo("limitingConeAngle", true),
+ AttributeInfo("local", true),
+ AttributeInfo("marker-end", true),
+ AttributeInfo("marker-mid", true),
+ AttributeInfo("marker-start", true),
+ AttributeInfo("markerHeight", true),
+ AttributeInfo("markerUnits", true),
+ AttributeInfo("markerWidth", true),
+ AttributeInfo("mask", true),
+ AttributeInfo("maskContentUnits", true),
+ AttributeInfo("maskUnits", true),
+ AttributeInfo("mathematical", true),
+ AttributeInfo("max", true),
+ AttributeInfo("media", false),
+ AttributeInfo("method", false),
+ AttributeInfo("min", true),
+ AttributeInfo("mix-blend-mode", true),
+ AttributeInfo("mode", true),
+ AttributeInfo("name", true),
+ AttributeInfo("numOctaves", true),
+ AttributeInfo("offset", true),
+ AttributeInfo("onabort", false),
+ AttributeInfo("onactivate", false),
+ AttributeInfo("onbegin", false),
+ AttributeInfo("onclick", false),
+ AttributeInfo("onend", false),
+ AttributeInfo("onerror", false),
+ AttributeInfo("onfocusin", false),
+ AttributeInfo("onfocusout", false),
+ AttributeInfo("onload", true),
+ AttributeInfo("onmousedown", false),
+ AttributeInfo("onmousemove", false),
+ AttributeInfo("onmouseout", false),
+ AttributeInfo("onmouseover", false),
+ AttributeInfo("onmouseup", false),
+ AttributeInfo("onrepeat", false),
+ AttributeInfo("onresize", false),
+ AttributeInfo("onscroll", false),
+ AttributeInfo("onunload", false),
+ AttributeInfo("onzoom", false),
+ AttributeInfo("opacity", true),
+ AttributeInfo("operator", true),
+ AttributeInfo("order", true),
+ AttributeInfo("orient", true),
+ AttributeInfo("orientation", true),
+ AttributeInfo("origin", false),
+ AttributeInfo("overflow", true),
+ AttributeInfo("overline-position", true),
+ AttributeInfo("overline-thickness", true),
+ AttributeInfo("paint-order", true),
+ AttributeInfo("panose-1", true),
+ AttributeInfo("path", true),
+ AttributeInfo("pathLength", false),
+ AttributeInfo("patternContentUnits", true),
+ AttributeInfo("patternTransform", true),
+ AttributeInfo("patternUnits", true),
+ AttributeInfo("pitch", true), // SVG 2.-
+ AttributeInfo("pointer-events", true),
+ AttributeInfo("points", true),
+ AttributeInfo("pointsAtX", true),
+ AttributeInfo("pointsAtY", true),
+ AttributeInfo("pointsAtZ", true),
+ AttributeInfo("preserveAlpha", true),
+ AttributeInfo("preserveAspectRatio", true),
+ AttributeInfo("primitiveUnits", true),
+ AttributeInfo("r", true),
+ AttributeInfo("radius", true),
+ AttributeInfo("refX", true),
+ AttributeInfo("refY", true),
+ AttributeInfo("rendering-intent", true),
+ AttributeInfo("repeatCount", true),
+ AttributeInfo("repeatDur", true),
+ AttributeInfo("requiredFeatures", true),
+ AttributeInfo("requiredExtensions", true),
+ AttributeInfo("restart", true),
+ AttributeInfo("result", true),
+ AttributeInfo("rotate", true),
+ AttributeInfo("rx", true),
+ AttributeInfo("ry", true),
+ AttributeInfo("scale", true),
+ AttributeInfo("seed", true),
+ AttributeInfo("shape-inside", true),
+ AttributeInfo("shape-margin", true),
+ AttributeInfo("shape-subtract", true),
+ AttributeInfo("shape-padding", true),
+ AttributeInfo("shape-rendering", true),
+ AttributeInfo("side", true),
+ AttributeInfo("slope", true),
+ AttributeInfo("solid-color", true), // SVG 2.0
+ AttributeInfo("solid-opacity", true), // SVG 2.0
+ AttributeInfo("spacing", false),
+ AttributeInfo("specularConstant", true),
+ AttributeInfo("specularExponent", true),
+ AttributeInfo("spreadMethod", true),
+ AttributeInfo("startOffset", true),
+ AttributeInfo("stdDeviation", true),
+ AttributeInfo("stemh", true),
+ AttributeInfo("stemv", true),
+ AttributeInfo("stitchTiles", true),
+ AttributeInfo("stop-color", true),
+ AttributeInfo("stop-opacity", true),
+ AttributeInfo("strikethrough-position", true),
+ AttributeInfo("strikethrough-thickness", true),
+ AttributeInfo("stroke", true),
+ AttributeInfo("stroke-dasharray", true),
+ AttributeInfo("stroke-dashoffset", true),
+ AttributeInfo("stroke-linecap", true),
+ AttributeInfo("stroke-linejoin", true),
+ AttributeInfo("stroke-miterlimit", true),
+ AttributeInfo("stroke-opacity", true),
+ AttributeInfo("stroke-width", true),
+ AttributeInfo("style", true),
+ AttributeInfo("surfaceScale", true),
+ AttributeInfo("systemLanguage", true),
+ AttributeInfo("tableValues", true),
+ AttributeInfo("target", true),
+ AttributeInfo("targetX", true),
+ AttributeInfo("targetY", true),
+ AttributeInfo("text-align", true),
+ AttributeInfo("text-anchor", true),
+ AttributeInfo("text-decoration", true),
+ AttributeInfo("text-decoration-color", true),
+ AttributeInfo("text-decoration-fill", true),
+ AttributeInfo("text-decoration-line", true),
+ AttributeInfo("text-decoration-stroke", true),
+ AttributeInfo("text-decoration-style", true),
+ AttributeInfo("text-indent", true),
+ AttributeInfo("text-orientation", true),
+ AttributeInfo("text-rendering", true),
+ AttributeInfo("text-transform", true),
+ AttributeInfo("textLength", true),
+ AttributeInfo("title", false),
+ AttributeInfo("to", true),
+ AttributeInfo("transform", true),
+ AttributeInfo("type", true),
+ AttributeInfo("u1", true),
+ AttributeInfo("u2", true),
+ AttributeInfo("underline-position", true),
+ AttributeInfo("underline-thickness", true),
+ AttributeInfo("unicode", true),
+ AttributeInfo("unicode-bidi", true),
+ AttributeInfo("unicode-range", true),
+ AttributeInfo("units-per-em", true),
+ AttributeInfo("v-alphabetic", true),
+ AttributeInfo("v-hanging", true),
+ AttributeInfo("v-ideographic", true),
+ AttributeInfo("v-mathematical", true),
+ AttributeInfo("values", true),
+ AttributeInfo("vector-effect", true),
+ AttributeInfo("version", true),
+ AttributeInfo("vert-adv-y", true),
+ AttributeInfo("vert-origin-x", true),
+ AttributeInfo("vert-origin-y", true),
+ AttributeInfo("viewBox", true),
+ AttributeInfo("viewTarget", false),
+ AttributeInfo("visibility", true),
+ AttributeInfo("white-space", true),
+ AttributeInfo("width", true),
+ AttributeInfo("widths", true),
+ AttributeInfo("word-spacing", true),
+ AttributeInfo("writing-mode", true),
+ AttributeInfo("x", true),
+ AttributeInfo("x-height", true),
+ AttributeInfo("x1", true),
+ AttributeInfo("x2", true),
+ AttributeInfo("xChannelSelector", true),
+ AttributeInfo("xlink:actuate", true),
+ AttributeInfo("xlink:arcrole", true),
+ AttributeInfo("xlink:href", true),
+ AttributeInfo("xlink:role", true),
+ AttributeInfo("xlink:show", true),
+ AttributeInfo("xlink:title", true),
+ AttributeInfo("xlink:type", true),
+ AttributeInfo("xml:base", false),
+ AttributeInfo("xml:lang", true),
+ AttributeInfo("xml:space", true),
+ AttributeInfo("xmlns", false),
+ AttributeInfo("xmlns:xlink", false),
+ AttributeInfo("y", true),
+ AttributeInfo("y1", true),
+ AttributeInfo("y2", true),
+ AttributeInfo("yChannelSelector", true),
+ AttributeInfo("z", true),
+ AttributeInfo("zoomAndPan", false),
+
+ // Extra attributes.
+ AttributeInfo("id", true),
+ AttributeInfo("inkscape:bbox-nodes", true),
+ AttributeInfo("inkscape:bbox-paths", true),
+ AttributeInfo("inkscape:box3dsidetype", true),
+ AttributeInfo("inkscape:collect", true),
+ AttributeInfo("inkscape:color", true),
+ AttributeInfo("inkscape:connection-end", true),
+ AttributeInfo("inkscape:connection-end-point", true),
+ AttributeInfo("inkscape:connection-points", true),
+ AttributeInfo("inkscape:connection-start", true),
+ AttributeInfo("inkscape:connection-start-point", true),
+ AttributeInfo("inkscape:connector-avoid", true),
+ AttributeInfo("inkscape:connector-curvature", true),
+ AttributeInfo("inkscape:connector-spacing", true),
+ AttributeInfo("inkscape:connector-type", true),
+ AttributeInfo("inkscape:corner0", true),
+ AttributeInfo("inkscape:corner7", true),
+ AttributeInfo("inkscape:current-layer", true),
+ AttributeInfo("inkscape:cx", true),
+ AttributeInfo("inkscape:cy", true),
+ AttributeInfo("inkscape:document-units", true),
+ AttributeInfo("inkscape:dstBox", true),
+ AttributeInfo("inkscape:dstColumn", true),
+ AttributeInfo("inkscape:dstPath", true),
+ AttributeInfo("inkscape:dstShape", true),
+ AttributeInfo("inkscape:excludeShape", true),
+ AttributeInfo("inkscape:expanded", true),
+ AttributeInfo("inkscape:flatsided", true),
+ AttributeInfo("inkscape:groupmode", true),
+ AttributeInfo("inkscape:highlight-color", true),
+ AttributeInfo("inkscape:href", true),
+ AttributeInfo("inkscape:label", true),
+ AttributeInfo("inkscape:layoutOptions", true),
+ AttributeInfo("inkscape:lockguides", true),
+ AttributeInfo("inkscape:locked", true),
+ AttributeInfo("inkscape:object-nodes", true),
+ AttributeInfo("inkscape:object-paths", true),
+ AttributeInfo("inkscape:original", true),
+ AttributeInfo("inkscape:original-d", true),
+ AttributeInfo("inkscape:pagecheckerboard", true),
+ AttributeInfo("inkscape:pageopacity", true),
+ AttributeInfo("inkscape:pageshadow", true),
+ AttributeInfo("inkscape:path-effect", true),
+ AttributeInfo("inkscape:persp3d", true),
+ AttributeInfo("inkscape:persp3d-origin", true),
+ AttributeInfo("inkscape:perspectiveID", true),
+ AttributeInfo("inkscape:radius", true),
+ AttributeInfo("inkscape:randomized", true),
+ AttributeInfo("inkscape:rounded", true),
+ AttributeInfo("inkscape:snap-bbox", true),
+ AttributeInfo("inkscape:snap-bbox-edge-midpoints", true),
+ AttributeInfo("inkscape:snap-bbox-midpoints", true),
+ AttributeInfo("inkscape:snap-center", true),
+ AttributeInfo("inkscape:snap-global", true),
+ AttributeInfo("inkscape:snap-grids", true),
+ AttributeInfo("inkscape:snap-intersection-paths", true),
+ AttributeInfo("inkscape:snap-midpoints", true),
+ AttributeInfo("inkscape:snap-nodes", true),
+ AttributeInfo("inkscape:snap-object-midpoints", true),
+ AttributeInfo("inkscape:snap-others", true),
+ AttributeInfo("inkscape:snap-from-guide", true),
+ AttributeInfo("inkscape:snap-page", true),
+ AttributeInfo("inkscape:snap-path-clip", true),
+ AttributeInfo("inkscape:snap-path-mask", true),
+ AttributeInfo("inkscape:snap-perpendicular", true),
+ AttributeInfo("inkscape:snap-smooth-nodes", true),
+ AttributeInfo("inkscape:snap-tangential", true),
+ AttributeInfo("inkscape:snap-text-baseline", true),
+ AttributeInfo("inkscape:snap-to-guides", true),
+ AttributeInfo("inkscape:spray-origin", true),
+ AttributeInfo("inkscape:srcNoMarkup", true),
+ AttributeInfo("inkscape:srcPango", true),
+ AttributeInfo("inkscape:transform-center-x", true),
+ AttributeInfo("inkscape:transform-center-y", true),
+ AttributeInfo("inkscape:version", true),
+ AttributeInfo("inkscape:vp_x", true),
+ AttributeInfo("inkscape:vp_y", true),
+ AttributeInfo("inkscape:vp_z", true),
+ AttributeInfo("inkscape:window-height", true),
+ AttributeInfo("inkscape:window-maximized", true),
+ AttributeInfo("inkscape:window-width", true),
+ AttributeInfo("inkscape:window-x", true),
+ AttributeInfo("inkscape:window-y", true),
+ AttributeInfo("inkscape:zoom", true),
+ AttributeInfo("inkscape:svg-dpi", true),
+ AttributeInfo("osb:paint", true),
+ AttributeInfo("sodipodi:arc-type", true),
+ AttributeInfo("sodipodi:arg1", true),
+ AttributeInfo("sodipodi:arg2", true),
+ AttributeInfo("sodipodi:argument", true),
+ AttributeInfo("sodipodi:cx", true),
+ AttributeInfo("sodipodi:cy", true),
+ AttributeInfo("sodipodi:docname", true),
+ AttributeInfo("sodipodi:end", true),
+ AttributeInfo("sodipodi:expansion", true),
+ AttributeInfo("sodipodi:insensitive", true),
+ AttributeInfo("sodipodi:linespacing", true),
+ AttributeInfo("sodipodi:open", true),
+ AttributeInfo("sodipodi:original", true),
+ AttributeInfo("sodipodi:r1", true),
+ AttributeInfo("sodipodi:r2", true),
+ AttributeInfo("sodipodi:radius", true),
+ AttributeInfo("sodipodi:revolution", true),
+ AttributeInfo("sodipodi:role", true),
+ AttributeInfo("sodipodi:rx", true),
+ AttributeInfo("sodipodi:ry", true),
+ AttributeInfo("sodipodi:sides", true),
+ AttributeInfo("sodipodi:start", true),
+ AttributeInfo("sodipodi:t0", true),
+ AttributeInfo("sodipodi:type", true),
+ AttributeInfo("sodipodi:version", false),
+
+ // SPMeshPatch
+ AttributeInfo("tensor", true),
+
+ // SPNamedView
+ AttributeInfo("fit-margin-top", true),
+ AttributeInfo("fit-margin-left", true),
+ AttributeInfo("fit-margin-right", true),
+ AttributeInfo("fit-margin-bottom", true),
+ AttributeInfo("units", true),
+ AttributeInfo("viewonly", true),
+ AttributeInfo("showgrid", true),
+// AttributeInfo("gridtype", true),
+ AttributeInfo("showguides", true),
+ AttributeInfo("gridtolerance", true),
+ AttributeInfo("guidetolerance", true),
+ AttributeInfo("objecttolerance", true),
+/* AttributeInfo("gridoriginx", true),
+ AttributeInfo("gridoriginy", true),
+ AttributeInfo("gridspacingx", true),
+ AttributeInfo("gridspacingy", true),
+ AttributeInfo("gridanglex", true),
+ AttributeInfo("gridanglez", true),
+ AttributeInfo("gridcolor", true),
+ AttributeInfo("gridopacity", true),
+ AttributeInfo("gridempcolor", true),
+ AttributeInfo("gridempopacity", true),
+ AttributeInfo("gridempspacing", true), */
+ AttributeInfo("guidecolor", true),
+ AttributeInfo("guideopacity", true),
+ AttributeInfo("guidehicolor", true),
+ AttributeInfo("guidehiopacity", true),
+ AttributeInfo("showborder", true),
+ AttributeInfo("inkscape:showpageshadow", true),
+ AttributeInfo("borderlayer", true),
+ AttributeInfo("bordercolor", true),
+ AttributeInfo("borderopacity", true),
+ AttributeInfo("pagecolor", true),
+
+ // SPGuide
+ AttributeInfo("position", true)
+ };
+
+ size_t count = sizeof(all_attrs) / sizeof(all_attrs[0]);
+ std::vector<AttributeInfo> vect(all_attrs, all_attrs + count);
+ EXPECT_GT(vect.size(), size_t(100)); // should be more than
+ return vect;
+}
+
+/**
+ * Returns a vector with counts for all IDs up to the highest known value.
+ *
+ * The index is the ID, and the value is the number of times that ID is seen.
+ */
+std::vector<size_t> getIdIds()
+{
+ std::vector<size_t> ids;
+ std::vector<AttributeInfo> all_attrs = getKnownAttrs();
+ ids.reserve(all_attrs.size()); // minimize memory thrashing
+ for (auto & all_attr : all_attrs) {
+ auto id = sp_attribute_lookup(all_attr.attr.c_str());
+ if (id >= ids.size()) {
+ ids.resize(id + 1);
+ }
+ ids[id]++;
+ }
+
+ return ids;
+}
+
+// Ensure 'supported' value for each known attribute is correct.
+TEST(AttributesTest, SupportedKnown)
+{
+ std::vector<AttributeInfo> all_attrs = getKnownAttrs();
+ for (AttrItr it(all_attrs.begin()); it != all_attrs.end(); ++it) {
+ auto id = sp_attribute_lookup(it->attr.c_str());
+ EXPECT_EQ(it->supported, id != 0u) << "Matching for attribute '" << it->attr << "'";
+ }
+}
+
+// Ensure names of known attributes are preserved when converted to id and back.
+TEST(AttributesTest, NameRoundTrip)
+{
+ std::vector<AttributeInfo> all_attrs = getKnownAttrs();
+ for (AttrItr it(all_attrs.begin()); it != all_attrs.end(); ++it) {
+ if (it->supported) {
+ auto id = sp_attribute_lookup(it->attr.c_str());
+ char const *redoneName = sp_attribute_name(id);
+ EXPECT_TRUE(redoneName != NULL) << "For attribute '" << it->attr << "'";
+ if (redoneName) {
+ EXPECT_EQ(it->attr, redoneName);
+ }
+ }
+ }
+}
+
+/* Test for any attributes that this test program doesn't know about.
+ *
+ * If any are found, then:
+ *
+ * If it is in the `inkscape:' namespace then simply add it to all_attrs with
+ * `true' as the second field (`supported').
+ *
+ * If it is in the `sodipodi:' namespace then check the spelling against sodipodi
+ * sources. If you don't have sodipodi sources, then don't add it: leave to someone
+ * else.
+ *
+ * Otherwise, it's probably a bug: ~all SVG 1.1 attributes should already be
+ * in the all_attrs table. However, the comment above all_attrs does mention
+ * some things missing from attindex.html, so there may be more. Check the SVG
+ * spec. Another possibility is that the attribute is new in SVG 1.2. In this case,
+ * check the spelling against the [draft] SVG 1.2 spec before adding to all_attrs.
+ * (If you can't be bothered checking the spec, then don't update all_attrs.)
+ *
+ * If the attribute isn't in either SVG 1.1 or 1.2 then it's probably a mistake
+ * for it not to be in the inkscape namespace. (Not sure about attributes used only
+ * on elements in the inkscape namespace though.)
+ *
+ * In any case, make sure that the attribute's source is documented accordingly.
+ */
+TEST(AttributesTest, ValuesAreKnown)
+{
+ std::vector<size_t> ids = getIdIds();
+ for (size_t i = FIRST_VALID_ID; i < ids.size(); ++i) {
+ if (!ids[i]) {
+ char const *name = sp_attribute_name((SPAttributeEnum)i);
+ EXPECT_TRUE(ids[i] > 0) << "Attribute string with enum " << i << " {" << name << "} not handled";
+ }
+ }
+}
+
+// Ensure two different names aren't mapped to the same enum value.
+TEST(AttributesTest, ValuesUnique)
+{
+ std::vector<size_t> ids = getIdIds();
+ for (size_t i = FIRST_VALID_ID; i < ids.size(); ++i) {
+ EXPECT_LE(ids[i], size_t(1)) << "Attribute enum " << i << " used for multiple strings"
+ << " including {" << sp_attribute_name((SPAttributeEnum)i) << "}";
+ }
+}
+
+} // namespace
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/testfiles/src/color-profile-test.cpp b/testfiles/src/color-profile-test.cpp
new file mode 100644
index 0000000..4804763
--- /dev/null
+++ b/testfiles/src/color-profile-test.cpp
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Unit tests for color profile.
+ *
+ * Author:
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2015 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "gtest/gtest.h"
+
+#include "attributes.h"
+#include "cms-system.h"
+#include "object/color-profile.h"
+#include "doc-per-case-test.h"
+
+namespace {
+
+/**
+ * Test fixture to inherit a shared doc and create a color profile instance per test.
+ */
+class ProfTest : public DocPerCaseTest
+{
+public:
+ ProfTest() :
+ DocPerCaseTest(),
+ _prof(0)
+ {
+ }
+
+protected:
+ void SetUp() override
+ {
+ DocPerCaseTest::SetUp();
+ _prof = new Inkscape::ColorProfile();
+ ASSERT_TRUE( _prof != NULL );
+ _prof->document = _doc;
+ }
+
+ void TearDown() override
+ {
+ if (_prof) {
+ delete _prof;
+ _prof = NULL;
+ }
+ DocPerCaseTest::TearDown();
+ }
+
+ Inkscape::ColorProfile *_prof;
+};
+
+typedef ProfTest ColorProfileTest;
+
+TEST_F(ColorProfileTest, SetRenderingIntent)
+{
+ struct {
+ gchar const *attr;
+ guint intVal;
+ }
+ const cases[] = {
+ {"auto", (guint)Inkscape::RENDERING_INTENT_AUTO},
+ {"perceptual", (guint)Inkscape::RENDERING_INTENT_PERCEPTUAL},
+ {"relative-colorimetric", (guint)Inkscape::RENDERING_INTENT_RELATIVE_COLORIMETRIC},
+ {"saturation", (guint)Inkscape::RENDERING_INTENT_SATURATION},
+ {"absolute-colorimetric", (guint)Inkscape::RENDERING_INTENT_ABSOLUTE_COLORIMETRIC},
+ {"something-else", (guint)Inkscape::RENDERING_INTENT_UNKNOWN},
+ {"auto2", (guint)Inkscape::RENDERING_INTENT_UNKNOWN},
+ };
+
+ for (auto i : cases) {
+ _prof->setKeyValue( SP_ATTR_RENDERING_INTENT, i.attr);
+ ASSERT_EQ( (guint)i.intVal, _prof->rendering_intent ) << i.attr;
+ }
+}
+
+TEST_F(ColorProfileTest, SetLocal)
+{
+ gchar const* cases[] = {
+ "local",
+ "something",
+ };
+
+ for (auto & i : cases) {
+ _prof->setKeyValue( SP_ATTR_LOCAL, i);
+ ASSERT_TRUE( _prof->local != NULL );
+ if ( _prof->local ) {
+ ASSERT_EQ( std::string(i), _prof->local );
+ }
+ }
+ _prof->setKeyValue( SP_ATTR_LOCAL, NULL);
+ ASSERT_EQ( (gchar*)0, _prof->local );
+}
+
+TEST_F(ColorProfileTest, SetName)
+{
+ gchar const* cases[] = {
+ "name",
+ "something",
+ };
+
+ for (auto & i : cases) {
+ _prof->setKeyValue( SP_ATTR_NAME, i);
+ ASSERT_TRUE( _prof->name != NULL );
+ if ( _prof->name ) {
+ ASSERT_EQ( std::string(i), _prof->name );
+ }
+ }
+ _prof->setKeyValue( SP_ATTR_NAME, NULL );
+ ASSERT_EQ( (gchar*)0, _prof->name );
+}
+
+
+} // namespace
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/testfiles/src/curve-test.cpp b/testfiles/src/curve-test.cpp
new file mode 100644
index 0000000..8c94b77
--- /dev/null
+++ b/testfiles/src/curve-test.cpp
@@ -0,0 +1,258 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Curve test
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2020 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include <gtest/gtest.h>
+
+#include "display/curve.h"
+#include <2geom/curves.h>
+#include <2geom/path.h>
+#include <2geom/pathvector.h>
+
+class CurveTest : public ::testing::Test {
+ public:
+ Geom::Path path1;
+ Geom::Path path2;
+ Geom::Path path3;
+ Geom::Path path4;
+
+ protected:
+ CurveTest()
+ : path4(Geom::Point(3, 5)) // Just a moveto
+ {
+ // Closed path
+ path1.append(Geom::LineSegment(Geom::Point(0, 0), Geom::Point(1, 0)));
+ path1.append(Geom::LineSegment(Geom::Point(1, 0), Geom::Point(1, 1)));
+ path1.close();
+ // Closed path (ClosingSegment is zero length)
+ path2.append(Geom::LineSegment(Geom::Point(2, 0), Geom::Point(3, 0)));
+ path2.append(Geom::CubicBezier(Geom::Point(3, 0), Geom::Point(2, 1), Geom::Point(1, 1), Geom::Point(2, 0)));
+ path2.close();
+ // Open path
+ path3.setStitching(true);
+ path3.append(Geom::EllipticalArc(Geom::Point(4, 0), 1, 2, M_PI, false, false, Geom::Point(5, 1)));
+ path3.append(Geom::LineSegment(Geom::Point(5, 1), Geom::Point(5, 2)));
+ path3.append(Geom::LineSegment(Geom::Point(6, 4), Geom::Point(2, 4)));
+ }
+};
+
+TEST_F(CurveTest, testGetSegmentCount)
+{
+ { // Zero segments
+ Geom::PathVector pv;
+ SPCurve curve(pv);
+ ASSERT_EQ(curve.get_segment_count(), 0u);
+ }
+ { // Zero segments
+ Geom::PathVector pv;
+ pv.push_back(Geom::Path());
+ SPCurve curve(pv);
+ ASSERT_EQ(curve.get_segment_count(), 0u);
+ }
+ { // Individual paths
+ Geom::PathVector pv((Geom::Path()));
+ pv[0] = path1;
+ ASSERT_EQ(SPCurve(pv).get_segment_count(), 3u);
+ pv[0] = path2;
+ ASSERT_EQ(SPCurve(pv).get_segment_count(), 2u);
+ pv[0] = path3;
+ ASSERT_EQ(SPCurve(pv).get_segment_count(), 4u);
+ pv[0] = path4;
+ ASSERT_EQ(SPCurve(pv).get_segment_count(), 0u);
+ pv[0].close();
+ ASSERT_EQ(SPCurve(pv).get_segment_count(), 0u);
+ }
+ { // Combination
+ Geom::PathVector pv;
+ pv.push_back(path1);
+ pv.push_back(path2);
+ pv.push_back(path3);
+ pv.push_back(path4);
+ SPCurve curve(pv);
+ ASSERT_EQ(curve.get_segment_count(), 9u);
+ }
+}
+
+TEST_F(CurveTest, testNodesInPathForZeroSegments)
+{
+ { // Zero segments
+ Geom::PathVector pv;
+ SPCurve curve(pv);
+ ASSERT_EQ(curve.nodes_in_path(), 0u);
+ }
+ { // Zero segments
+ Geom::PathVector pv;
+ pv.push_back(Geom::Path());
+ SPCurve curve(pv);
+ ASSERT_EQ(curve.nodes_in_path(), 1u);
+ }
+}
+
+TEST_F(CurveTest, testNodesInPathForIndividualPaths)
+{
+ Geom::PathVector pv((Geom::Path()));
+ pv[0] = path1;
+ ASSERT_EQ(SPCurve(pv).nodes_in_path(), 3u);
+ pv[0] = path2;
+ ASSERT_EQ(SPCurve(pv).nodes_in_path(), 2u); // zero length closing segments do not increase the nodecount.
+ pv[0] = path3;
+ ASSERT_EQ(SPCurve(pv).nodes_in_path(), 5u);
+ pv[0] = path4;
+ ASSERT_EQ(SPCurve(pv).nodes_in_path(), 1u);
+}
+
+TEST_F(CurveTest, testNodesInPathForNakedMoveToClosedPath)
+{
+ Geom::PathVector pv((Geom::Path()));
+ pv[0] = path4; // just a MoveTo
+ pv[0].close();
+ ASSERT_EQ(SPCurve(pv).nodes_in_path(), 1u);
+}
+
+/*
+TEST_F(CurveTest, testNodesInPathForPathsCombination)
+{
+ Geom::PathVector pv;
+ pv.push_back(path1);
+ pv.push_back(path2);
+ pv.push_back(path3);
+ pv.push_back(path4);
+ SPCurve curve(pv);
+ ASSERT_EQ(curve.nodes_in_path(), 12u);
+}
+*/
+
+TEST_F(CurveTest, testIsEmpty)
+{
+ ASSERT_TRUE(SPCurve(Geom::PathVector()).is_empty());
+ ASSERT_FALSE(SPCurve(path1).is_empty());
+ ASSERT_FALSE(SPCurve(path2).is_empty());
+ ASSERT_FALSE(SPCurve(path3).is_empty());
+ ASSERT_FALSE(SPCurve(path4).is_empty());
+}
+
+TEST_F(CurveTest, testIsClosed)
+{
+ ASSERT_FALSE(SPCurve(Geom::PathVector()).is_closed());
+ Geom::PathVector pv((Geom::Path()));
+ ASSERT_FALSE(SPCurve(pv).is_closed());
+ pv[0].close();
+ ASSERT_TRUE(SPCurve(pv).is_closed());
+ ASSERT_TRUE(SPCurve(path1).is_closed());
+ ASSERT_TRUE(SPCurve(path2).is_closed());
+ ASSERT_FALSE(SPCurve(path3).is_closed());
+ ASSERT_FALSE(SPCurve(path4).is_closed());
+}
+
+/*
+TEST_F(CurveTest, testLastFirstSegment)
+{
+ Geom::PathVector pv(path4);
+ ASSERT_EQ(SPCurve(pv).first_segment(), (void *)0);
+ ASSERT_EQ(SPCurve(pv).last_segment(), (void *)0);
+ pv[0].close();
+ ASSERT_NE(SPCurve(pv).first_segment(), (void *)0);
+ ASSERT_NE(SPCurve(pv).last_segment(), (void *)0);
+}
+*/
+
+TEST_F(CurveTest, testLastFirstPath)
+{
+ Geom::PathVector pv;
+ ASSERT_EQ(SPCurve(pv).first_path(), (void *)0);
+ ASSERT_EQ(SPCurve(pv).last_path(), (void *)0);
+ pv.push_back(path1);
+ ASSERT_EQ(*SPCurve(pv).first_path(), pv[0]);
+ ASSERT_EQ(*SPCurve(pv).last_path(), pv[0]);
+ pv.push_back(path2);
+ ASSERT_EQ(*SPCurve(pv).first_path(), pv[0]);
+ ASSERT_EQ(*SPCurve(pv).last_path(), pv[1]);
+ pv.push_back(path3);
+ ASSERT_EQ(*SPCurve(pv).first_path(), pv[0]);
+ ASSERT_EQ(*SPCurve(pv).last_path(), pv[2]);
+ pv.push_back(path4);
+ ASSERT_EQ(*SPCurve(pv).first_path(), pv[0]);
+ ASSERT_EQ(*SPCurve(pv).last_path(), pv[3]);
+}
+
+TEST_F(CurveTest, testFirstPoint)
+{
+ ASSERT_EQ(*(SPCurve(path1).first_point()), Geom::Point(0, 0));
+ ASSERT_EQ(*(SPCurve(path2).first_point()), Geom::Point(2, 0));
+ ASSERT_EQ(*(SPCurve(path3).first_point()), Geom::Point(4, 0));
+ ASSERT_EQ(*(SPCurve(path4).first_point()), Geom::Point(3, 5));
+ Geom::PathVector pv;
+ ASSERT_FALSE(SPCurve(pv).first_point());
+ pv.push_back(path1);
+ pv.push_back(path2);
+ pv.push_back(path3);
+ ASSERT_EQ(*(SPCurve(pv).first_point()), Geom::Point(0, 0));
+ pv.insert(pv.begin(), path4);
+ ASSERT_EQ(*(SPCurve(pv).first_point()), Geom::Point(3, 5));
+}
+
+/*
+TEST_F(CurveTest, testLastPoint)
+{
+ ASSERT_EQ(*(SPCurve(path1).last_point()), Geom::Point(0, 0));
+ ASSERT_EQ(*(SPCurve(path2).last_point()), Geom::Point(2, 0));
+ ASSERT_EQ(*(SPCurve(path3).last_point()), Geom::Point(8, 4));
+ ASSERT_EQ(*(SPCurve(path4).last_point()), Geom::Point(3, 5));
+ Geom::PathVector pv;
+ ASSERT_FALSE(SPCurve(pv).last_point());
+ pv.push_back(path1);
+ pv.push_back(path2);
+ pv.push_back(path3);
+ ASSERT_EQ(*(SPCurve(pv).last_point()), Geom::Point(8, 4));
+ pv.push_back(path4);
+ ASSERT_EQ(*(SPCurve(pv).last_point()), Geom::Point(3, 5));
+}
+*/
+
+TEST_F(CurveTest, testSecondPoint)
+{
+ ASSERT_EQ(*(SPCurve(path1).second_point()), Geom::Point(1, 0));
+ ASSERT_EQ(*(SPCurve(path2).second_point()), Geom::Point(3, 0));
+ ASSERT_EQ(*(SPCurve(path3).second_point()), Geom::Point(5, 1));
+ ASSERT_EQ(*(SPCurve(path4).second_point()), Geom::Point(3, 5));
+ Geom::PathVector pv;
+ pv.push_back(path1);
+ pv.push_back(path2);
+ pv.push_back(path3);
+ ASSERT_EQ(*(SPCurve(pv).second_point()), Geom::Point(1, 0));
+ pv.insert(pv.begin(), path4);
+ ASSERT_EQ(*SPCurve(pv).second_point(), Geom::Point(0, 0));
+}
+
+/*
+TEST_F(CurveTest, testPenultimatePoint)
+{
+ ASSERT_EQ(*(SPCurve(Geom::PathVector(path1)).penultimate_point()), Geom::Point(1, 1));
+ ASSERT_EQ(*(SPCurve(Geom::PathVector(path2)).penultimate_point()), Geom::Point(3, 0));
+ ASSERT_EQ(*(SPCurve(Geom::PathVector(path3)).penultimate_point()), Geom::Point(6, 4));
+ ASSERT_EQ(*(SPCurve(Geom::PathVector(path4)).penultimate_point()), Geom::Point(3, 5));
+ Geom::PathVector pv;
+ pv.push_back(path1);
+ pv.push_back(path2);
+ pv.push_back(path3);
+ ASSERT_EQ(*(SPCurve(pv).penultimate_point()), Geom::Point(6, 4));
+ pv.push_back(path4);
+ ASSERT_EQ(*(SPCurve(pv).penultimate_point()), Geom::Point(8, 4));
+}
+*/
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/cxxtests-to-migrate/marker-test.h b/testfiles/src/cxxtests-to-migrate/marker-test.h
new file mode 100644
index 0000000..1a77aff
--- /dev/null
+++ b/testfiles/src/cxxtests-to-migrate/marker-test.h
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * @brief Unit tests for SVG marker handling
+ *//*
+ * Authors:
+ * see git history
+ * Johan Engelen <goejendaagh@zonnet.nl>
+ *
+ * Copyright (C) 2016 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cxxtest/TestSuite.h>
+
+#include "sp-marker-loc.h"
+
+class MarkerTest : public CxxTest::TestSuite
+{
+public:
+
+ void testMarkerLoc()
+ {
+ // code depends on these *exact* values, so check them here.
+ TS_ASSERT_EQUALS(SP_MARKER_LOC, 0);
+ TS_ASSERT_EQUALS(SP_MARKER_LOC_START, 1);
+ TS_ASSERT_EQUALS(SP_MARKER_LOC_MID, 2);
+ TS_ASSERT_EQUALS(SP_MARKER_LOC_END, 3);
+ TS_ASSERT_EQUALS(SP_MARKER_LOC_QTY, 4);
+ }
+
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/cxxtests-to-migrate/mod360-test.h b/testfiles/src/cxxtests-to-migrate/mod360-test.h
new file mode 100644
index 0000000..12ee994
--- /dev/null
+++ b/testfiles/src/cxxtests-to-migrate/mod360-test.h
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2016 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_MOD_360_TEST_H
+#define SEEN_MOD_360_TEST_H
+
+#include <cxxtest/TestSuite.h>
+#include <2geom/math-utils.h>
+#include "mod360.h"
+
+
+class Mod360Test : public CxxTest::TestSuite
+{
+public:
+ static double inf() { return INFINITY; }
+ static double nan() { return ((double)INFINITY) - ((double)INFINITY); }
+
+ void testMod360()
+ {
+ double cases[][2] = {
+ {0, 0},
+ {10, 10},
+ {360, 0},
+ {361, 1},
+ {-1, 359},
+ {-359, 1},
+ {-360, -0},
+ {-361, 359},
+ {inf(), 0},
+ {-inf(), 0},
+ {nan(), 0},
+ {720, 0},
+ {-721, 359},
+ {-1000, 80}
+ };
+
+ for ( unsigned i = 0; i < G_N_ELEMENTS(cases); i++ ) {
+ double result = mod360( cases[i][0] );
+ TS_ASSERT_EQUALS( cases[i][1], result );
+ }
+ }
+
+};
+
+
+#endif // SEEN_MOD_360_TEST_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
+
diff --git a/testfiles/src/cxxtests-to-migrate/preferences-test.h b/testfiles/src/cxxtests-to-migrate/preferences-test.h
new file mode 100644
index 0000000..0dc04b8
--- /dev/null
+++ b/testfiles/src/cxxtests-to-migrate/preferences-test.h
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * @brief Unit tests for the Preferences object
+ *//*
+ * Authors:
+ * see git history
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2016 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cxxtest/TestSuite.h>
+#include "preferences.h"
+
+#include <glibmm/ustring.h>
+
+// test observer
+class TestObserver : public Inkscape::Preferences::Observer {
+public:
+ TestObserver(Glib::ustring const &path) :
+ Inkscape::Preferences::Observer(path),
+ value(0) {}
+
+ virtual void notify(Inkscape::Preferences::Entry const &val)
+ {
+ value = val.getInt();
+ }
+ int value;
+};
+
+class PreferencesTest : public CxxTest::TestSuite {
+public:
+ void setUp() {
+ prefs = Inkscape::Preferences::get();
+ }
+ void tearDown() {
+ prefs = NULL;
+ Inkscape::Preferences::unload();
+ }
+
+ void testStartingState()
+ {
+ TS_ASSERT_DIFFERS(prefs, static_cast<void*>(0));
+ TS_ASSERT_EQUALS(prefs->isWritable(), true);
+ }
+
+ void testOverwrite()
+ {
+ prefs->setInt("/test/intvalue", 123);
+ prefs->setInt("/test/intvalue", 321);
+ TS_ASSERT_EQUALS(prefs->getInt("/test/intvalue"), 321);
+ }
+
+ void testDefaultReturn()
+ {
+ TS_ASSERT_EQUALS(prefs->getInt("/this/path/does/not/exist", 123), 123);
+ }
+
+ void testLimitedReturn()
+ {
+ prefs->setInt("/test/intvalue", 1000);
+
+ // simple case
+ TS_ASSERT_EQUALS(prefs->getIntLimited("/test/intvalue", 123, 0, 500), 123);
+ // the below may seem quirky but this behaviour is intended
+ TS_ASSERT_EQUALS(prefs->getIntLimited("/test/intvalue", 123, 1001, 5000), 123);
+ // corner cases
+ TS_ASSERT_EQUALS(prefs->getIntLimited("/test/intvalue", 123, 0, 1000), 1000);
+ TS_ASSERT_EQUALS(prefs->getIntLimited("/test/intvalue", 123, 1000, 5000), 1000);
+ }
+
+ void testKeyObserverNotification()
+ {
+ Glib::ustring const path = "/some/random/path";
+ TestObserver obs("/some/random");
+ obs.value = 1;
+ prefs->setInt(path, 5);
+ TS_ASSERT_EQUALS(obs.value, 1); // no notifications sent before adding
+
+ prefs->addObserver(obs);
+ prefs->setInt(path, 10);
+ TS_ASSERT_EQUALS(obs.value, 10);
+ prefs->setInt("/some/other/random/path", 10);
+ TS_ASSERT_EQUALS(obs.value, 10); // value should not change
+
+ prefs->removeObserver(obs);
+ prefs->setInt(path, 15);
+ TS_ASSERT_EQUALS(obs.value, 10); // no notifications sent after removal
+ }
+
+ void testEntryObserverNotification()
+ {
+ Glib::ustring const path = "/some/random/path";
+ TestObserver obs(path);
+ obs.value = 1;
+ prefs->setInt(path, 5);
+ TS_ASSERT_EQUALS(obs.value, 1); // no notifications sent before adding
+
+ prefs->addObserver(obs);
+ prefs->setInt(path, 10);
+ TS_ASSERT_EQUALS(obs.value, 10);
+
+ // test that filtering works properly
+ prefs->setInt("/some/random/value", 1234);
+ TS_ASSERT_EQUALS(obs.value, 10);
+ prefs->setInt("/some/randomvalue", 1234);
+ TS_ASSERT_EQUALS(obs.value, 10);
+ prefs->setInt("/some/random/path2", 1234);
+ TS_ASSERT_EQUALS(obs.value, 10);
+
+ prefs->removeObserver(obs);
+ prefs->setInt(path, 15);
+ TS_ASSERT_EQUALS(obs.value, 10); // no notifications sent after removal
+ }
+
+ void testPreferencesEntryMethods()
+ {
+ prefs->setInt("/test/prefentry", 100);
+ Inkscape::Preferences::Entry val = prefs->getEntry("/test/prefentry");
+ TS_ASSERT(val.isValid());
+ TS_ASSERT_EQUALS(val.getPath(), "/test/prefentry");
+ TS_ASSERT_EQUALS(val.getEntryName(), "prefentry");
+ TS_ASSERT_EQUALS(val.getInt(), 100);
+ }
+private:
+ Inkscape::Preferences *prefs;
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/cxxtests-to-migrate/sp-style-elem-test.h b/testfiles/src/cxxtests-to-migrate/sp-style-elem-test.h
new file mode 100644
index 0000000..271f319
--- /dev/null
+++ b/testfiles/src/cxxtests-to-migrate/sp-style-elem-test.h
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2016 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_SP_STYLE_ELEM_TEST_H
+#define SEEN_SP_STYLE_ELEM_TEST_H
+
+#include <cxxtest/TestSuite.h>
+
+#include "test-helpers.h"
+
+#include "sp-style-elem.h"
+#include "xml/repr.h"
+
+class SPStyleElemTest : public CxxTest::TestSuite
+{
+public:
+ SPDocument* _doc;
+
+ SPStyleElemTest() :
+ _doc(0)
+ {
+ }
+
+ virtual ~SPStyleElemTest()
+ {
+ if ( _doc )
+ {
+ _doc->doUnref();
+ }
+ }
+
+ static void createSuiteSubclass( SPStyleElemTest *& dst )
+ {
+ SPStyleElem *style_elem = new SPStyleElem();
+
+ if ( style_elem ) {
+ TS_ASSERT(!style_elem->is_css);
+ TS_ASSERT(style_elem->media.print);
+ TS_ASSERT(style_elem->media.screen);
+ delete style_elem;
+
+ dst = new SPStyleElemTest();
+ }
+ }
+
+ static SPStyleElemTest *createSuite()
+ {
+ return Inkscape::createSuiteAndDocument<SPStyleElemTest>( createSuiteSubclass );
+ }
+
+ static void destroySuite( SPStyleElemTest *suite ) { delete suite; }
+
+// -------------------------------------------------------------------------
+// -------------------------------------------------------------------------
+
+
+ void testSetType()
+ {
+ SPStyleElem *style_elem = new SPStyleElem();
+ SP_OBJECT(style_elem)->document = _doc;
+
+ SP_OBJECT(style_elem)->setKeyValue( SP_ATTR_TYPE, "something unrecognized");
+ TS_ASSERT( !style_elem->is_css );
+
+ SP_OBJECT(style_elem)->setKeyValue( SP_ATTR_TYPE, "text/css");
+ TS_ASSERT( style_elem->is_css );
+
+ SP_OBJECT(style_elem)->setKeyValue( SP_ATTR_TYPE, "atext/css");
+ TS_ASSERT( !style_elem->is_css );
+
+ SP_OBJECT(style_elem)->setKeyValue( SP_ATTR_TYPE, "text/cssx");
+ TS_ASSERT( !style_elem->is_css );
+
+ delete style_elem;
+ }
+
+ void testWrite()
+ {
+ TS_ASSERT( _doc );
+ TS_ASSERT( _doc->getReprDoc() );
+ if ( !_doc->getReprDoc() ) {
+ return; // evil early return
+ }
+
+ SPStyleElem *style_elem = new SPStyleElem();
+ SP_OBJECT(style_elem)->document = _doc;
+
+ SP_OBJECT(style_elem)->setKeyValue( SP_ATTR_TYPE, "text/css");
+ Inkscape::XML::Node *repr = _doc->getReprDoc()->createElement("svg:style");
+ SP_OBJECT(style_elem)->updateRepr(_doc->getReprDoc(), repr, SP_OBJECT_WRITE_ALL);
+ {
+ gchar const *typ = repr->attribute("type");
+ TS_ASSERT( typ != NULL );
+ if ( typ )
+ {
+ TS_ASSERT_EQUALS( std::string(typ), std::string("text/css") );
+ }
+ }
+
+ delete style_elem;
+ }
+
+ void testBuild()
+ {
+ TS_ASSERT( _doc );
+ TS_ASSERT( _doc->getReprDoc() );
+ if ( !_doc->getReprDoc() ) {
+ return; // evil early return
+ }
+
+ SPStyleElem *style_elem = new SPStyleElem();
+ Inkscape::XML::Node *const repr = _doc->getReprDoc()->createElement("svg:style");
+ repr->setAttribute("type", "text/css");
+ style_elem->invoke_build( _doc, repr, false);
+ TS_ASSERT( style_elem->is_css );
+ TS_ASSERT( style_elem->media.print );
+ TS_ASSERT( style_elem->media.screen );
+
+ /* Some checks relevant to the read_content test below. */
+ {
+ g_assert(_doc->style_cascade);
+ CRStyleSheet const *const stylesheet = cr_cascade_get_sheet(_doc->style_cascade, ORIGIN_AUTHOR);
+ g_assert(stylesheet);
+ g_assert(stylesheet->statements == NULL);
+ }
+
+ delete style_elem;
+ Inkscape::GC::release(repr);
+ }
+
+ void testReadContent()
+ {
+ TS_ASSERT( _doc );
+ TS_ASSERT( _doc->getReprDoc() );
+ if ( !_doc->getReprDoc() ) {
+ return; // evil early return
+ }
+
+ SPStyleElem *style_elem = new SPStyleElem();
+ Inkscape::XML::Node *const repr = _doc->getReprDoc()->createElement("svg:style");
+ repr->setAttribute("type", "text/css");
+ Inkscape::XML::Node *const content_repr = _doc->getReprDoc()->createTextNode(".myclass { }");
+ repr->addChild(content_repr, NULL);
+ style_elem->invoke_build(_doc, repr, false);
+ TS_ASSERT( style_elem->is_css );
+ TS_ASSERT( _doc->style_cascade );
+ CRStyleSheet const *const stylesheet = cr_cascade_get_sheet(_doc->style_cascade, ORIGIN_AUTHOR);
+ TS_ASSERT(stylesheet != NULL);
+ TS_ASSERT(stylesheet->statements != NULL);
+
+ delete style_elem;
+ Inkscape::GC::release(repr);
+ }
+
+};
+
+
+#endif // SEEN_SP_STYLE_ELEM_TEST_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/cxxtests-to-migrate/test-helpers.h b/testfiles/src/cxxtests-to-migrate/test-helpers.h
new file mode 100644
index 0000000..96bf5b4
--- /dev/null
+++ b/testfiles/src/cxxtests-to-migrate/test-helpers.h
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2016 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_TEST_HELPERS_H
+#define SEEN_TEST_HELPERS_H
+
+
+#include <cxxtest/TestSuite.h>
+
+#include "document.h"
+#include "inkscape.h"
+
+
+// Dummy functions to keep linker happy
+#if !defined(DUMMY_MAIN_TEST_CALLS_SEEN)
+#define DUMMY_MAIN_TEST_CALLS_SEEN
+int sp_main_gui (int, char const**) { return 0; }
+int sp_main_console (int, char const**) { return 0; }
+#endif // DUMMY_MAIN_TEST_CALLS_SEEN
+
+namespace Inkscape
+{
+
+template <class T>
+T* createSuiteAndDocument( void (*fun)(T*&) )
+{
+ T* suite = 0;
+
+#if !GLIB_CHECK_VERSION(2,36,0)
+ g_type_init();
+#endif
+
+ Inkscape::GC::init();
+ if ( !Inkscape::Application::exists() )
+ {
+ // Create the global inkscape object.
+ Inkscape::Application::create(false);
+ }
+
+ SPDocument* tmp = SPDocument::createNewDoc( NULL, TRUE, true );
+ if ( tmp ) {
+ fun( suite );
+ if ( suite )
+ {
+ suite->_doc = tmp;
+ }
+ else
+ {
+ tmp->doUnref();
+ }
+ }
+
+ return suite;
+}
+
+} // namespace Inkscape
+
+#endif // SEEN_TEST_HELPERS_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/cxxtests-to-migrate/verbs-test.h b/testfiles/src/cxxtests-to-migrate/verbs-test.h
new file mode 100644
index 0000000..b8fd299
--- /dev/null
+++ b/testfiles/src/cxxtests-to-migrate/verbs-test.h
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2016 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#include <cxxtest/TestSuite.h>
+
+#include "verbs.h"
+
+class VerbsTest : public CxxTest::TestSuite
+{
+public:
+
+ class TestHook : public Inkscape::Verb {
+ public:
+ static int getInternalTableSize() { return _getBaseListSize(); }
+
+ private:
+ TestHook();
+ };
+
+ void testEnumLength()
+ {
+ TS_ASSERT_DIFFERS( 0, static_cast<int>(SP_VERB_LAST) );
+ TS_ASSERT_EQUALS( static_cast<int>(SP_VERB_LAST) + 1, TestHook::getInternalTableSize() );
+ }
+
+ void testEnumFixed()
+ {
+ TS_ASSERT_EQUALS( 0, static_cast<int>(SP_VERB_INVALID) );
+ TS_ASSERT_EQUALS( 1, static_cast<int>(SP_VERB_NONE) );
+
+ TS_ASSERT_DIFFERS( 0, static_cast<int>(SP_VERB_LAST) );
+ TS_ASSERT_DIFFERS( 1, static_cast<int>(SP_VERB_LAST) );
+ }
+
+ void testFetch()
+ {
+ for ( int i = 0; i < static_cast<int>(SP_VERB_LAST); i++ )
+ {
+ char tmp[16];
+ snprintf( tmp, sizeof(tmp), "Verb# %d", i );
+ tmp[sizeof(tmp)-1] = 0;
+ std::string descr(tmp);
+
+ Inkscape::Verb* verb = Inkscape::Verb::get(i);
+ TSM_ASSERT( descr, verb );
+ if ( verb )
+ {
+ TSM_ASSERT_EQUALS( descr, verb->get_code(), static_cast<unsigned int>(i) );
+
+ if ( i != static_cast<int>(SP_VERB_INVALID) )
+ {
+ TSM_ASSERT( descr, verb->get_id() );
+ TSM_ASSERT( descr, verb->get_name() );
+
+ Inkscape::Verb* bounced = verb->getbyid( verb->get_id() );
+ // TODO - put this back once verbs are fixed
+ //TSM_ASSERT( descr, bounced );
+ if ( bounced )
+ {
+ TSM_ASSERT_EQUALS( descr, bounced->get_code(), static_cast<unsigned int>(i) );
+ }
+ else
+ {
+ TS_FAIL( std::string("Unable to getbyid() for ") + descr + std::string(" ID: '") + std::string(verb->get_id()) + std::string("'") );
+ }
+ }
+ else
+ {
+ TSM_ASSERT( std::string("SP_VERB_INVALID"), !verb->get_id() );
+ TSM_ASSERT( std::string("SP_VERB_INVALID"), !verb->get_name() );
+ }
+ }
+ }
+ }
+
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/dir-util-test.cpp b/testfiles/src/dir-util-test.cpp
new file mode 100644
index 0000000..bac5e3c
--- /dev/null
+++ b/testfiles/src/dir-util-test.cpp
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Unit tests for dir utils.
+ *
+ * Author:
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2015 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "gtest/gtest.h"
+
+#include <glib.h>
+
+#include "io/dir-util.h"
+
+namespace {
+
+
+TEST(DirUtilTest, Base)
+{
+ char const* cases[][3] = {
+#if defined(WIN32) || defined(__WIN32__)
+ {"\\foo\\bar", "\\foo", "bar"},
+ {"\\foo\\barney", "\\foo\\bar", "\\foo\\barney"},
+ {"\\foo\\bar\\baz", "\\foo\\", "bar\\baz"},
+ {"\\foo\\bar\\baz", "\\", "foo\\bar\\baz"},
+ {"\\foo\\bar\\baz", "\\foo\\qux", "\\foo\\bar\\baz"},
+#else
+ {"/foo/bar", "/foo", "bar"},
+ {"/foo/barney", "/foo/bar", "/foo/barney"},
+ {"/foo/bar/baz", "/foo/", "bar/baz"},
+ {"/foo/bar/baz", "/", "foo/bar/baz"},
+ {"/foo/bar/baz", "/foo/qux", "/foo/bar/baz"},
+#endif
+ };
+
+ for (auto & i : cases)
+ {
+ if ( i[0] && i[1] ) { // std::string can't use null.
+ std::string result = sp_relative_path_from_path( i[0], i[1] );
+ ASSERT_FALSE( result.empty() );
+ if ( !result.empty() )
+ {
+ ASSERT_EQ( std::string(i[2]), result );
+ }
+ }
+ }
+}
+
+} // namespace
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/testfiles/src/drag-and-drop-svgz.cpp b/testfiles/src/drag-and-drop-svgz.cpp
new file mode 100644
index 0000000..5ea7c0b
--- /dev/null
+++ b/testfiles/src/drag-and-drop-svgz.cpp
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+/**
+ * @file
+ * Test that svgz (= compressed SVG) import/drag-and-drop
+ * is working: https://gitlab.com/inkscape/inkscape/issues/906 .
+ *
+ */
+/*
+ * Authors:
+ * Shlomi Fish
+ *
+ * Copyright (C) 2020 Authors
+ */
+
+#include "doc-per-case-test.h"
+#include <glibmm.h>
+
+#include "extension/db.h"
+#include "extension/find_extension_by_mime.h"
+#include "extension/internal/svgz.h"
+#include "path-prefix.h"
+#include "preferences.h"
+
+#include "gtest/gtest.h"
+
+
+class SvgzImportTest : public DocPerCaseTest {
+ public:
+ SvgzImportTest() {}
+ void TestBody()
+ {
+ ASSERT_TRUE(_doc != nullptr);
+ ASSERT_TRUE(_doc->getRoot() != nullptr);
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool("/dialogs/import/ask_svg", true);
+ prefs->setBool("/options/onimport", true);
+ auto ext = Inkscape::Extension::find_by_mime("image/svg+xml-compressed");
+ ext->set_gui(true);
+ auto fn = Glib::build_filename(INKSCAPE_EXAMPLESDIR, "tiger.svgz");
+ auto imod = dynamic_cast<Inkscape::Extension::Input *>(ext);
+ auto svg_mod = (new Inkscape::Extension::Internal::Svg);
+ ASSERT_TRUE(svg_mod->open(imod, fn.c_str()) != nullptr);
+ }
+ ~SvgzImportTest() override {}
+};
+
+TEST_F(SvgzImportTest, Eq)
+{
+ SvgzImportTest foo;
+ foo.TestBody();
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/extract-uri-test.cpp b/testfiles/src/extract-uri-test.cpp
new file mode 100644
index 0000000..acff966
--- /dev/null
+++ b/testfiles/src/extract-uri-test.cpp
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Test extract_uri
+ */
+/*
+ * Authors:
+ * Thomas Holder
+ *
+ * Copyright (C) 2018 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extract-uri.h"
+#include "gtest/gtest.h"
+
+TEST(ExtractUriTest, valid)
+{
+ ASSERT_EQ(extract_uri("url(#foo)"), "#foo");
+ ASSERT_EQ(extract_uri("url( \t #foo \t )"), "#foo");
+ ASSERT_EQ(extract_uri("url( '#foo' )"), "#foo");
+ ASSERT_EQ(extract_uri("url('url(foo)')"), "url(foo)");
+ ASSERT_EQ(extract_uri("url(\"foo(url)\")"), "foo(url)");
+ ASSERT_EQ(extract_uri("url()bar"), "");
+ ASSERT_EQ(extract_uri("url( )bar"), "");
+ ASSERT_EQ(extract_uri("url(a b)"), "a b");
+}
+
+TEST(ExtractUriTest, legacy)
+{
+ ASSERT_EQ(extract_uri("url (foo)"), "foo");
+}
+
+TEST(ExtractUriTest, invalid)
+{
+ ASSERT_EQ(extract_uri("#foo"), "");
+ ASSERT_EQ(extract_uri(" url(foo)"), "");
+ ASSERT_EQ(extract_uri("url(#foo"), "");
+ ASSERT_EQ(extract_uri("url('#foo'"), "");
+ ASSERT_EQ(extract_uri("url('#foo)"), "");
+ ASSERT_EQ(extract_uri("url #foo)"), "");
+}
+
+static char const *extract_end(char const *s)
+{
+ char const *end = nullptr;
+ extract_uri(s, &end);
+ return end;
+}
+
+TEST(ExtractUriTest, endptr)
+{
+ ASSERT_STREQ(extract_end(""), nullptr);
+ ASSERT_STREQ(extract_end("url(invalid"), nullptr);
+ ASSERT_STREQ(extract_end("url('invalid)"), nullptr);
+ ASSERT_STREQ(extract_end("url(valid)"), "");
+ ASSERT_STREQ(extract_end("url(valid)foo"), "foo");
+ ASSERT_STREQ(extract_end("url('valid')bar"), "bar");
+ ASSERT_STREQ(extract_end("url( 'valid' )bar"), "bar");
+ ASSERT_STREQ(extract_end("url( valid ) bar "), " bar ");
+ ASSERT_STREQ(extract_end("url()bar"), "bar");
+ ASSERT_STREQ(extract_end("url( )bar"), "bar");
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/lpe-bool-test.cpp b/testfiles/src/lpe-bool-test.cpp
new file mode 100644
index 0000000..31bc371
--- /dev/null
+++ b/testfiles/src/lpe-bool-test.cpp
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * LPE Boolean operation test
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2020 Authors
+ *
+ * Released under GNU GPL version 2 or later, read the file 'COPYING' for more information
+ */
+
+#include <gtest/gtest.h>
+#include <src/document.h>
+#include <src/inkscape.h>
+#include <src/live_effects/lpe-bool.h>
+#include <src/object/sp-ellipse.h>
+#include <src/object/sp-lpe-item.h>
+
+
+
+using namespace Inkscape;
+using namespace Inkscape::LivePathEffect;
+
+class LPEBoolTest : public ::testing::Test {
+ protected:
+ void SetUp() override
+ {
+ // setup hidden dependency
+ Application::create(false);
+ }
+};
+
+TEST_F(LPEBoolTest, canBeApplyedToNonSiblingPaths)
+{
+ std::string svg("\
+<svg width='100' height='100'\
+ xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd'\
+ xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape'>\
+ <defs>\
+ <inkscape:path-effect\
+ id='path-effect1'\
+ effect='bool_op'\
+ operation='diff'\
+ operand-path='#circle1'\
+ lpeversion='1'\
+ hide-linked='true' />\
+ </defs>\
+ <path id='rect1'\
+ inkscape:path-effect='#path-effect1'\
+ sodipodi:type='rect'\
+ width='100' height='100' fill='#ff0000' />\
+ <g id='group1'>\
+ <circle id='circle1'\
+ r='40' cy='50' cx='50' fill='#ffffff' style='display:inline'/>\
+ </g>\
+</svg>");
+
+ SPDocument *doc = SPDocument::createNewDocFromMem(svg.c_str(), svg.size(), true);
+ doc->ensureUpToDate();
+
+ auto lpe_item = dynamic_cast<SPLPEItem *>(doc->getObjectById("rect1"));
+ ASSERT_TRUE(lpe_item != nullptr);
+
+ auto lpe_bool_op_effect = dynamic_cast<LPEBool *>(lpe_item->getPathEffectOfType(EffectType::BOOL_OP));
+ ASSERT_TRUE(lpe_bool_op_effect != nullptr);
+
+ auto operand_path = lpe_bool_op_effect->getParameter("operand-path")->param_getSVGValue();
+ auto circle = dynamic_cast<SPGenericEllipse *>(doc->getObjectById(operand_path.substr(1)));
+ ASSERT_TRUE(circle != nullptr);
+} \ No newline at end of file
diff --git a/testfiles/src/object-set-test.cpp b/testfiles/src/object-set-test.cpp
new file mode 100644
index 0000000..5df9738
--- /dev/null
+++ b/testfiles/src/object-set-test.cpp
@@ -0,0 +1,636 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Multiindex container for selection
+ *
+ * Authors:
+ * Adrian Boguszewski
+ *
+ * Copyright (C) 2016 Adrian Boguszewski
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include <gtest/gtest.h>
+#include <doc-per-case-test.h>
+#include <src/object/sp-factory.h>
+#include <src/object/sp-rect.h>
+#include <src/object/sp-path.h>
+#include <src/object/sp-use.h>
+#include <src/object/sp-root.h>
+#include <src/object/object-set.h>
+#include <xml/node.h>
+#include <src/xml/text-node.h>
+#include <src/xml/simple-document.h>
+//#include <unistd.h>
+#include <2geom/transforms.h>
+using namespace Inkscape;
+using namespace Inkscape::XML;
+
+class ObjectSetTest: public DocPerCaseTest {
+public:
+ ObjectSetTest() {
+ A = new SPObject();
+ B = new SPObject();
+ C = new SPObject();
+ D = new SPObject();
+ E = new SPObject();
+ F = new SPObject();
+ G = new SPObject();
+ H = new SPObject();
+ X = new SPObject();
+ set = new ObjectSet(_doc);
+ set2 = new ObjectSet(_doc);
+ auto sd = _doc->getReprDoc();
+ auto xt = new TextNode(Util::share_string("x"), sd);
+ auto ht = new TextNode(Util::share_string("h"), sd);
+ auto gt = new TextNode(Util::share_string("g"), sd);
+ auto ft = new TextNode(Util::share_string("f"), sd);
+ auto et = new TextNode(Util::share_string("e"), sd);
+ auto dt = new TextNode(Util::share_string("d"), sd);
+ auto ct = new TextNode(Util::share_string("c"), sd);
+ auto bt = new TextNode(Util::share_string("b"), sd);
+ auto at = new TextNode(Util::share_string("a"), sd);
+ X->invoke_build(_doc, xt, 0);
+ H->invoke_build(_doc, ht, 0);
+ G->invoke_build(_doc, gt, 0);
+ F->invoke_build(_doc, ft, 0);
+ E->invoke_build(_doc, et, 0);
+ D->invoke_build(_doc, dt, 0);
+ C->invoke_build(_doc, ct, 0);
+ B->invoke_build(_doc, bt, 0);
+ A->invoke_build(_doc, at, 0);
+
+ //create 3 rects at root of document
+ Inkscape::XML::Node *repr = _doc->getReprDoc()->createElement("svg:rect");
+ _doc->getRoot()->appendChild(repr);
+ r1.reset(dynamic_cast<SPRect*>(_doc->getObjectByRepr(repr)));
+ repr = _doc->getReprDoc()->createElement("svg:rect");
+ _doc->getRoot()->appendChild(repr);
+ r2.reset(dynamic_cast<SPRect*>(_doc->getObjectByRepr(repr)));
+ repr = _doc->getReprDoc()->createElement("svg:rect");
+ _doc->getRoot()->appendChild(repr);
+ r3.reset(dynamic_cast<SPRect*>(_doc->getObjectByRepr(repr)));
+ EXPECT_EQ(6, _doc->getRoot()->children.size());//metadata, defs, namedview, and those three rects.
+ r1->x = r1->y = r2->x = r2->y = r3->x = r3->y = 0;
+ r1->width = r1->height = r2->width = r2->height = r3->width = r3->height = 10;
+ r1->set_shape();
+ r2->set_shape();
+ r3->set_shape();
+
+ }
+ ~ObjectSetTest() override {
+ delete set;
+ delete set2;
+ delete X;
+ delete H;
+ delete G;
+ delete F;
+ delete E;
+ delete D;
+ delete C;
+ delete B;
+ delete A;
+ }
+ SPObject* A;
+ SPObject* B;
+ SPObject* C;
+ SPObject* D;
+ SPObject* E;
+ SPObject* F;
+ SPObject* G;
+ SPObject* H;
+ SPObject* X;
+ std::unique_ptr<SPRect> r1;
+ std::unique_ptr<SPRect> r2;
+ std::unique_ptr<SPRect> r3;
+ ObjectSet* set;
+ ObjectSet* set2;
+};
+
+#define SP_IS_CLONE(obj) (dynamic_cast<const SPUse*>(obj) != NULL)
+
+bool containsClone(ObjectSet* set) {
+ for (auto it : set->items()) {
+ if (SP_IS_CLONE(it)) {
+ return true;
+ }
+ if (SP_IS_GROUP(it)) {
+ ObjectSet tmp_set(set->document());
+ std::vector<SPObject*> c = it->childList(false);
+ tmp_set.setList(c);
+ if (containsClone(&tmp_set)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+TEST_F(ObjectSetTest, Basics) {
+ EXPECT_EQ(0, set->size());
+ set->add(A);
+ EXPECT_EQ(1, set->size());
+ EXPECT_TRUE(set->includes(A));
+ set->add(B);
+ set->add(C);
+ EXPECT_EQ(3, set->size());
+ EXPECT_TRUE(set->includes(B));
+ EXPECT_TRUE(set->includes(C));
+ EXPECT_FALSE(set->includes(D));
+ EXPECT_FALSE(set->includes(X));
+ EXPECT_FALSE(set->includes(nullptr));
+ set->remove(A);
+ EXPECT_EQ(2, set->size());
+ EXPECT_FALSE(set->includes(A));
+ set->clear();
+ EXPECT_EQ(0, set->size());
+ bool resultNull = set->add((SPObject*)nullptr);
+ EXPECT_FALSE(resultNull);
+ EXPECT_EQ(0, set->size());
+ bool resultNull2 = set->remove(nullptr);
+ EXPECT_FALSE(resultNull2);
+}
+
+TEST_F(ObjectSetTest, Advanced) {
+ set->add(A);
+ set->add(B);
+ set->add(C);
+ EXPECT_TRUE(set->includes(C));
+ set->toggle(C);
+ EXPECT_EQ(2, set->size());
+ EXPECT_FALSE(set->includes(C));
+ set->toggle(D);
+ EXPECT_EQ(3, set->size());
+ EXPECT_TRUE(set->includes(D));
+ set->toggle(D);
+ EXPECT_EQ(2, set->size());
+ EXPECT_FALSE(set->includes(D));
+ EXPECT_EQ(nullptr, set->single());
+ set->set(X);
+ EXPECT_EQ(1, set->size());
+ EXPECT_TRUE(set->includes(X));
+ EXPECT_EQ(X, set->single());
+ EXPECT_FALSE(set->isEmpty());
+ set->clear();
+ EXPECT_TRUE(set->isEmpty());
+ std::vector<SPObject*> list1 {A, B, C, D};
+ std::vector<SPObject*> list2 {E, F};
+ set->addList(list1);
+ EXPECT_EQ(4, set->size());
+ set->addList(list2);
+ EXPECT_EQ(6, set->size());
+ EXPECT_TRUE(set->includes(A));
+ EXPECT_TRUE(set->includes(B));
+ EXPECT_TRUE(set->includes(C));
+ EXPECT_TRUE(set->includes(D));
+ EXPECT_TRUE(set->includes(E));
+ EXPECT_TRUE(set->includes(F));
+ set->setList(list2);
+ EXPECT_EQ(2, set->size());
+ EXPECT_TRUE(set->includes(E));
+ EXPECT_TRUE(set->includes(F));
+}
+
+TEST_F(ObjectSetTest, Items) {
+ // cannot test smallestItem and largestItem functions due to too many dependencies
+ // uncomment if the problem is fixed
+
+ SPRect* rect10x100 = &*r1;
+ rect10x100->x = rect10x100->x = 0;
+ rect10x100->width = 10;
+ rect10x100->height = 100;
+ rect10x100->set_shape();
+
+ SPRect* rect20x40 = &*r2;
+ rect20x40->x = rect20x40->x = 0;
+ rect20x40->width = 20;
+ rect20x40->height = 40;
+ rect20x40->set_shape();
+
+ SPRect* rect30x30 = &*r3;
+ rect30x30->x = rect30x30->x = 0;
+ rect30x30->width = 30;
+ rect30x30->height = 30;
+ rect30x30->set_shape();
+
+
+ set->add(rect10x100);
+ EXPECT_EQ(rect10x100, set->singleItem());
+ EXPECT_EQ(rect10x100->getRepr(), set->singleRepr());
+ set->add(rect20x40);
+ EXPECT_EQ(nullptr, set->singleItem());
+ EXPECT_EQ(nullptr, set->singleRepr());
+ set->add(rect30x30);
+ EXPECT_EQ(3, set->size());
+ EXPECT_EQ(rect10x100, set->smallestItem(ObjectSet::CompareSize::HORIZONTAL));
+ EXPECT_EQ(rect30x30, set->smallestItem(ObjectSet::CompareSize::VERTICAL));
+ EXPECT_EQ(rect20x40, set->smallestItem(ObjectSet::CompareSize::AREA));
+ EXPECT_EQ(rect30x30, set->largestItem(ObjectSet::CompareSize::HORIZONTAL));
+ EXPECT_EQ(rect10x100, set->largestItem(ObjectSet::CompareSize::VERTICAL));
+ EXPECT_EQ(rect10x100, set->largestItem(ObjectSet::CompareSize::AREA));
+}
+
+TEST_F(ObjectSetTest, Ranges) {
+ std::vector<SPObject*> objs {A, D, B, E, C, F};
+ set->add(objs.begin() + 1, objs.end() - 1);
+ EXPECT_EQ(4, set->size());
+ auto it = set->objects().begin();
+ EXPECT_EQ(D, *it++);
+ EXPECT_EQ(B, *it++);
+ EXPECT_EQ(E, *it++);
+ EXPECT_EQ(C, *it++);
+ EXPECT_EQ(set->objects().end(), it);
+ SPObject* rect1 = SPFactory::createObject("svg:rect");
+ SPObject* rect2 = SPFactory::createObject("svg:rect");
+ SPObject* rect3 = SPFactory::createObject("svg:rect");
+ set->add(rect1);
+ set->add(rect2);
+ set->add(rect3);
+ EXPECT_EQ(7, set->size());
+ auto xmlNode = set->xmlNodes().begin();
+ EXPECT_EQ(3, boost::distance(set->xmlNodes()));
+ EXPECT_EQ(rect1->getRepr(), *xmlNode++);
+ EXPECT_EQ(rect2->getRepr(), *xmlNode++);
+ EXPECT_EQ(rect3->getRepr(), *xmlNode++);
+ EXPECT_EQ(set->xmlNodes().end(), xmlNode);
+ auto item = set->items().begin();
+ EXPECT_EQ(3, boost::distance(set->items()));
+ EXPECT_EQ(rect1, *item++);
+ EXPECT_EQ(rect2, *item++);
+ EXPECT_EQ(rect3, *item++);
+ EXPECT_EQ(set->items().end(), item);
+}
+
+TEST_F(ObjectSetTest, Autoremoving) {
+ set->add(A);
+ EXPECT_TRUE(set->includes(A));
+ EXPECT_EQ(1, set->size());
+ A->releaseReferences();
+ EXPECT_EQ(0, set->size());
+}
+
+TEST_F(ObjectSetTest, BasicDescendants) {
+ A->attach(B, nullptr);
+ B->attach(C, nullptr);
+ A->attach(D, nullptr);
+ bool resultB = set->add(B);
+ bool resultB2 = set->add(B);
+ EXPECT_TRUE(resultB);
+ EXPECT_FALSE(resultB2);
+ EXPECT_TRUE(set->includes(B));
+ bool resultC = set->add(C);
+ EXPECT_FALSE(resultC);
+ EXPECT_FALSE(set->includes(C));
+ EXPECT_EQ(1, set->size());
+ bool resultA = set->add(A);
+ EXPECT_TRUE(resultA);
+ EXPECT_EQ(1, set->size());
+ EXPECT_TRUE(set->includes(A));
+ EXPECT_FALSE(set->includes(B));
+}
+
+TEST_F(ObjectSetTest, AdvancedDescendants) {
+ A->attach(B, nullptr);
+ A->attach(C, nullptr);
+ A->attach(X, nullptr);
+ B->attach(D, nullptr);
+ B->attach(E, nullptr);
+ C->attach(F, nullptr);
+ C->attach(G, nullptr);
+ C->attach(H, nullptr);
+ set->add(A);
+ bool resultF = set->remove(F);
+ EXPECT_TRUE(resultF);
+ EXPECT_EQ(4, set->size());
+ EXPECT_FALSE(set->includes(F));
+ EXPECT_TRUE(set->includes(B));
+ EXPECT_TRUE(set->includes(G));
+ EXPECT_TRUE(set->includes(H));
+ EXPECT_TRUE(set->includes(X));
+ bool resultF2 = set->add(F);
+ EXPECT_TRUE(resultF2);
+ EXPECT_EQ(5, set->size());
+ EXPECT_TRUE(set->includes(F));
+}
+
+TEST_F(ObjectSetTest, Removing) {
+ A->attach(B, nullptr);
+ A->attach(C, nullptr);
+ A->attach(X, nullptr);
+ B->attach(D, nullptr);
+ B->attach(E, nullptr);
+ C->attach(F, nullptr);
+ C->attach(G, nullptr);
+ C->attach(H, nullptr);
+ bool removeH = set->remove(H);
+ EXPECT_FALSE(removeH);
+ set->add(A);
+ bool removeX = set->remove(X);
+ EXPECT_TRUE(removeX);
+ EXPECT_EQ(2, set->size());
+ EXPECT_TRUE(set->includes(B));
+ EXPECT_TRUE(set->includes(C));
+ EXPECT_FALSE(set->includes(X));
+ EXPECT_FALSE(set->includes(A));
+ bool removeX2 = set->remove(X);
+ EXPECT_FALSE(removeX2);
+ EXPECT_EQ(2, set->size());
+ bool removeA = set->remove(A);
+ EXPECT_FALSE(removeA);
+ EXPECT_EQ(2, set->size());
+ bool removeC = set->remove(C);
+ EXPECT_TRUE(removeC);
+ EXPECT_EQ(1, set->size());
+ EXPECT_TRUE(set->includes(B));
+ EXPECT_FALSE(set->includes(C));
+}
+
+TEST_F(ObjectSetTest, TwoSets) {
+ A->attach(B, nullptr);
+ A->attach(C, nullptr);
+ set->add(A);
+ set2->add(A);
+ EXPECT_EQ(1, set->size());
+ EXPECT_EQ(1, set2->size());
+ set->remove(B);
+ EXPECT_EQ(1, set->size());
+ EXPECT_TRUE(set->includes(C));
+ EXPECT_EQ(1, set2->size());
+ EXPECT_TRUE(set2->includes(A));
+ C->releaseReferences();
+ EXPECT_EQ(0, set->size());
+ EXPECT_EQ(1, set2->size());
+ EXPECT_TRUE(set2->includes(A));
+}
+
+TEST_F(ObjectSetTest, SetRemoving) {
+ ObjectSet *objectSet = new ObjectSet(_doc);
+ A->attach(B, nullptr);
+ objectSet->add(A);
+ objectSet->add(C);
+ EXPECT_EQ(2, objectSet->size());
+ delete objectSet;
+ EXPECT_STREQ(nullptr, A->getId());
+ EXPECT_STREQ(nullptr, C->getId());
+}
+
+TEST_F(ObjectSetTest, Delete) {
+ //we cannot use the same item as in other tests since it will be freed at the test destructor
+
+ EXPECT_EQ(_doc->getRoot(), r1->parent);
+ set->add(r1.get());
+ set->deleteItems();
+ r1.release();
+ EXPECT_EQ(0, set->size());
+ //EXPECT_EQ(nullptr, r1->parent);
+}
+
+TEST_F(ObjectSetTest, Ops) {
+ set->add(r1.get());
+ set->add(r2.get());
+ set->add(r3.get());
+ set->duplicate();
+ EXPECT_EQ(9, _doc->getRoot()->children.size());//metadata, defs, namedview, and those 3x2 rects.
+ EXPECT_EQ(3, set->size());
+ EXPECT_FALSE(set->includes(r1.get()));
+ set->deleteItems();
+ EXPECT_TRUE(set->isEmpty());
+ set->add(r1.get());
+ set->add(r2.get());
+ set->add(r3.get());
+ set->group();//r1-3 are now invalid (grouping makes copies)
+ r1.release();
+ r2.release();
+ r3.release();
+ EXPECT_EQ(4, _doc->getRoot()->children.size());
+ EXPECT_EQ(1, set->size());
+ set->ungroup();
+ EXPECT_EQ(6, _doc->getRoot()->children.size());
+ EXPECT_EQ(3, set->size());
+ /* Uncomment this when toNextLayer is made desktop-independent
+ set->group();
+ set2->add(set->singleItem()->childList(false)[0]);
+ EXPECT_EQ(3, set->singleItem()->children.size());
+ EXPECT_EQ(4, _doc->getRoot()->children.size());
+ set2->popFromGroup();
+ EXPECT_EQ(2, set->singleItem()->children.size());
+ EXPECT_EQ(5, _doc->getRoot()->children.size());
+ set->ungroup();
+ set->add(set2->singleItem());
+ */
+ set->clone();
+ EXPECT_EQ(9, _doc->getRoot()->children.size());
+ EXPECT_EQ(3, set->size());
+ EXPECT_NE(nullptr,dynamic_cast<SPUse*>(*(set->items().begin())));
+ EXPECT_EQ(nullptr,dynamic_cast<SPRect*>(*(set->items().begin())));
+ set->unlink();
+ EXPECT_EQ(9, _doc->getRoot()->children.size());
+ EXPECT_EQ(3, set->size());
+ EXPECT_EQ(nullptr,dynamic_cast<SPUse*>(*(set->items().begin())));
+ EXPECT_NE(nullptr,dynamic_cast<SPRect*>(*(set->items().begin())));
+ set->clone(); //creates 3 clones
+ set->clone(); //creates 3 clones of clones
+ EXPECT_EQ(15, _doc->getRoot()->children.size());
+ EXPECT_EQ(3, set->size());
+ EXPECT_NE(nullptr,dynamic_cast<SPUse*>( ((SPUse*)(*(set->items().begin())))->get_original()));//"original is a Use"
+ set->unlink(); //clone of clone of rect -> rect
+ EXPECT_EQ(nullptr,dynamic_cast<SPUse*>(*(set->items().begin())));
+ EXPECT_NE(nullptr,dynamic_cast<SPRect*>(*(set->items().begin())));
+ set->clone();
+ set->set(*(set->items().begin()));
+ set->cloneOriginal();//get clone original
+ EXPECT_EQ(18, _doc->getRoot()->children.size());
+ EXPECT_EQ(1, set->size());
+ EXPECT_NE(nullptr,dynamic_cast<SPRect*>(*(set->items().begin())));
+ //let's stop here.
+ // TODO: write a hundred more tests to check clone (non-)displacement when grouping, ungrouping and unlinking...
+ TearDownTestCase();
+ SetUpTestCase();
+}
+
+TEST_F(ObjectSetTest, unlinkRecursiveBasic) {
+ // This is the same as the test (ObjectSetTest, Ops), but with unlinkRecursive instead of unlink.
+ set->set(r1.get());
+ set->add(r2.get());
+ set->add(r3.get());
+ EXPECT_FALSE(containsClone(set));
+ set->duplicate();
+ EXPECT_FALSE(containsClone(set));
+ EXPECT_EQ(9, _doc->getRoot()->children.size());//metadata, defs, namedview, and those 3x2 rects.
+ EXPECT_EQ(3, set->size());
+ EXPECT_FALSE(set->includes(r1.get()));
+ set->deleteItems();
+ EXPECT_FALSE(containsClone(set));
+ EXPECT_TRUE(set->isEmpty());
+ set->add(r1.get());
+ set->add(r2.get());
+ set->add(r3.get());
+ EXPECT_FALSE(containsClone(set));
+ set->group();//r1-3 are now invalid (grouping makes copies)
+ r1.release();
+ r2.release();
+ r3.release();
+ EXPECT_FALSE(containsClone(set));
+ EXPECT_EQ(4, _doc->getRoot()->children.size());
+ EXPECT_EQ(1, set->size());
+ set->ungroup();
+ EXPECT_FALSE(containsClone(set));
+ EXPECT_EQ(6, _doc->getRoot()->children.size());
+ EXPECT_EQ(3, set->size());
+ /* Uncomment this when toNextLayer is made desktop-independent
+ set->group();
+ set2->add(set->singleItem()->childList(false)[0]);
+ EXPECT_EQ(3, set->singleItem()->children.size());
+ EXPECT_EQ(4, _doc->getRoot()->children.size());
+ set2->popFromGroup();
+ EXPECT_EQ(2, set->singleItem()->children.size());
+ EXPECT_EQ(5, _doc->getRoot()->children.size());
+ set->ungroup();
+ set->add(set2->singleItem());
+ */
+ set->clone();
+ EXPECT_TRUE(containsClone(set));
+ EXPECT_EQ(9, _doc->getRoot()->children.size());
+ EXPECT_EQ(3, set->size());
+ EXPECT_NE(nullptr, dynamic_cast<SPUse*>(*(set->items().begin())));
+ EXPECT_EQ(nullptr, dynamic_cast<SPRect*>(*(set->items().begin())));
+ set->unlinkRecursive();
+ EXPECT_FALSE(containsClone(set));
+ EXPECT_EQ(9, _doc->getRoot()->children.size());
+ EXPECT_EQ(3, set->size());
+ EXPECT_EQ(nullptr, dynamic_cast<SPUse*>(*(set->items().begin())));
+ EXPECT_NE(nullptr, dynamic_cast<SPRect*>(*(set->items().begin())));
+ set->clone(); //creates 3 clones
+ EXPECT_TRUE(containsClone(set));
+ set->clone(); //creates 3 clones of clones
+ EXPECT_TRUE(containsClone(set));
+ EXPECT_EQ(15, _doc->getRoot()->children.size());
+ EXPECT_EQ(3, set->size());
+ EXPECT_NE(nullptr, dynamic_cast<SPUse*>( ((SPUse*)(*(set->items().begin())))->get_original()));//"original is a Use"
+ set->unlinkRecursive(); //clone of clone of rect -> rect
+ EXPECT_FALSE(containsClone(set));
+ EXPECT_EQ(nullptr, dynamic_cast<SPUse*>(*(set->items().begin())));
+ EXPECT_NE(nullptr, dynamic_cast<SPRect*>(*(set->items().begin())));
+ set->clone();
+ EXPECT_TRUE(containsClone(set));
+ set->set(*(set->items().begin()));
+ set->cloneOriginal();//get clone original
+ EXPECT_EQ(18, _doc->getRoot()->children.size());
+ EXPECT_EQ(1, set->size());
+ EXPECT_NE(nullptr, dynamic_cast<SPRect*>(*(set->items().begin())));
+ TearDownTestCase();
+ SetUpTestCase();
+}
+
+TEST_F(ObjectSetTest, unlinkRecursiveAdvanced) {
+ set->set(r1.get());
+ set->add(r2.get());
+ set->add(r3.get());
+ set->group();//r1-3 are now invalid (grouping makes copies)
+ r1.release();
+ r2.release();
+ r3.release();
+ EXPECT_FALSE(containsClone(set));
+ EXPECT_EQ(1, set->size());
+ SPItem* original = set->singleItem();
+ set->clone();
+ EXPECT_TRUE(containsClone(set));
+ EXPECT_EQ(1, set->size());
+ set->add(original);
+ EXPECT_TRUE(containsClone(set));
+ EXPECT_EQ(2, set->size());
+ set->group();
+ EXPECT_TRUE(containsClone(set));
+ EXPECT_EQ(1, set->size());
+ original = set->singleItem();
+ set->clone();
+ EXPECT_TRUE(containsClone(set));
+ EXPECT_EQ(1, set->size());
+ set->add(original);
+ EXPECT_TRUE(containsClone(set));
+ EXPECT_EQ(2, set->size());
+ set->group();
+ EXPECT_TRUE(containsClone(set));
+ EXPECT_EQ(1, set->size());
+ original = set->singleItem();
+ set->clone();
+ EXPECT_TRUE(containsClone(set));
+ EXPECT_EQ(1, set->size());
+ set->add(original);
+ EXPECT_TRUE(containsClone(set));
+ EXPECT_EQ(2, set->size());
+ set->unlinkRecursive();
+ EXPECT_FALSE(containsClone(set));
+ EXPECT_EQ(2, set->size());
+
+ TearDownTestCase();
+ SetUpTestCase();
+}
+
+TEST_F(ObjectSetTest, ZOrder) {
+ //sp_object_compare_position_bool == true iff "r1<r2" iff r1 is "before" r2 in the file, ie r1 is lower than r2
+ EXPECT_TRUE(sp_object_compare_position_bool(r1.get(),r2.get()));
+ EXPECT_TRUE(sp_object_compare_position_bool(r2.get(),r3.get()));
+ EXPECT_TRUE(sp_object_compare_position_bool(r1.get(),r3.get()));
+ EXPECT_FALSE(sp_object_compare_position_bool(r2.get(),r1.get()));
+ EXPECT_FALSE(sp_object_compare_position_bool(r3.get(),r1.get()));
+ EXPECT_FALSE(sp_object_compare_position_bool(r3.get(),r2.get()));
+ //1 2 3
+ set->set(r2.get());
+ set->raise();
+ //1 3 2
+ EXPECT_TRUE(sp_object_compare_position_bool(r1.get(),r3.get()));
+ EXPECT_TRUE(sp_object_compare_position_bool(r3.get(),r2.get()));//!
+ set->set(r3.get());
+ set->lower();
+ //3 1 2
+ EXPECT_TRUE(sp_object_compare_position_bool(r3.get(),r1.get()));
+ EXPECT_TRUE(sp_object_compare_position_bool(r1.get(),r2.get()));
+ set->raiseToTop();
+ //1 2 3
+ EXPECT_TRUE(sp_object_compare_position_bool(r1.get(),r2.get()));
+ EXPECT_TRUE(sp_object_compare_position_bool(r2.get(),r3.get()));
+ set->lowerToBottom();
+ //3 1 2
+ EXPECT_TRUE(sp_object_compare_position_bool(r3.get(),r1.get()));
+ EXPECT_TRUE(sp_object_compare_position_bool(r1.get(),r2.get()));
+}
+
+TEST_F(ObjectSetTest, Combine) {
+ set->add(r1.get());
+ set->add(r2.get());
+ set->combine();
+ r1.release();
+ r2.release();
+ EXPECT_EQ(1, set->size());
+ EXPECT_EQ(5, _doc->getRoot()->children.size());
+ set->breakApart();
+ EXPECT_EQ(2, set->size());
+ EXPECT_EQ(6, _doc->getRoot()->children.size());
+ set->deleteItems();
+ set->set(r3.get());
+ set->toCurves();
+ r3.release();
+ auto x = set->singleItem();
+ EXPECT_NE(nullptr,dynamic_cast<SPPath*>(x));
+ EXPECT_EQ(nullptr,dynamic_cast<SPRect*>(x));
+ set->deleteItems();
+}
+
+TEST_F(ObjectSetTest, Moves) {
+ set->add(r1.get());
+ set->moveRelative(15,15);
+ EXPECT_EQ(15,r1->x.value);
+ Geom::Point p(20,20);
+ Geom::Scale s(2);
+ set->setScaleRelative(p,s);
+ EXPECT_EQ(10,r1->x.value);
+ EXPECT_EQ(20,r1->width.value);
+ set->toCurves();
+ r1.release();
+ auto x = set->singleItem();
+ EXPECT_EQ(20,(*(x->documentVisualBounds()))[0].extent());
+ set->rotate90(true);
+ set->rotate90(true);
+ EXPECT_EQ(20,(*(x->documentVisualBounds()))[0].extent());
+ set->deleteItems();
+}
diff --git a/testfiles/src/object-style-test.cpp b/testfiles/src/object-style-test.cpp
new file mode 100644
index 0000000..25d9268
--- /dev/null
+++ b/testfiles/src/object-style-test.cpp
@@ -0,0 +1,197 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Combination style and object testing for cascading and flags.
+ *//*
+ *
+ * Authors:
+ * Martin Owens
+ *
+ * Copyright (C) 2018 Authors
+ *
+ * Released under GNU GPL version 2 or later, read the file 'COPYING' for more information
+ */
+
+#include <gtest/gtest.h>
+#include <doc-per-case-test.h>
+
+#include <src/style.h>
+#include <src/object/sp-root.h>
+#include <src/object/sp-rect.h>
+
+using namespace Inkscape;
+using namespace Inkscape::XML;
+
+class ObjectTest: public DocPerCaseTest {
+public:
+ ObjectTest() {
+ char const *docString = "\
+<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>\
+<style>\
+rect { fill: #808080; opacity:0.5; }\
+.extra { opacity:1.0; }\
+.overload { fill: #d0d0d0 !important; stroke: #c0c0c0 !important; }\
+.font { font: italic bold 12px/30px Georgia, serif; }\
+.exsize { stroke-width: 1ex; }\
+.fosize { font-size: 15px; }\
+</style>\
+<g style='fill:blue; stroke-width:2px;font-size: 14px;'>\
+ <rect id='one' style='fill:red; stroke:green;'/>\
+ <rect id='two' style='stroke:green; stroke-width:4px;'/>\
+ <rect id='three' class='extra' style='fill: #cccccc;'/>\
+ <rect id='four' class='overload' style='fill:green;stroke:red !important;'/>\
+ <rect id='five' class='font' style='font: 15px arial, sans-serif;'/>/\
+ <rect id='six' style='stroke-width:1em;'/>\
+ <rect id='seven' class='exsize'/>\
+ <rect id='eight' class='fosize' style='stroke-width: 50%;'/>\
+</g>\
+</svg>";
+ doc = SPDocument::createNewDocFromMem(docString, static_cast<int>(strlen(docString)), false);
+ }
+
+ ~ObjectTest() override {
+ doc->doUnref();
+ }
+
+ SPDocument *doc;
+};
+
+/*
+ * Test basic cascade values, that they are set correctly as we'd want to see them.
+ */
+TEST_F(ObjectTest, Styles) {
+ ASSERT_TRUE(doc != nullptr);
+ ASSERT_TRUE(doc->getRoot() != nullptr);
+
+ SPRoot *root = doc->getRoot();
+ ASSERT_TRUE(root->getRepr() != nullptr);
+ ASSERT_TRUE(root->hasChildren());
+
+ SPRect *one = dynamic_cast<SPRect *>(doc->getObjectById("one"));
+ ASSERT_TRUE(one != nullptr);
+
+ // TODO: Fix when Inkscape preserves colour names (i.e. 'red')
+ EXPECT_EQ(one->style->fill.get_value(), Glib::ustring("#ff0000"));
+ EXPECT_EQ(one->style->stroke.get_value(), Glib::ustring("#008000"));
+ EXPECT_EQ(one->style->opacity.get_value(), Glib::ustring("0.5"));
+ EXPECT_EQ(one->style->stroke_width.get_value(), Glib::ustring("2px"));
+
+ SPRect *two = dynamic_cast<SPRect *>(doc->getObjectById("two"));
+ ASSERT_TRUE(two != nullptr);
+
+ EXPECT_EQ(two->style->fill.get_value(), Glib::ustring("#808080"));
+ EXPECT_EQ(two->style->stroke.get_value(), Glib::ustring("#008000"));
+ EXPECT_EQ(two->style->opacity.get_value(), Glib::ustring("0.5"));
+ EXPECT_EQ(two->style->stroke_width.get_value(), Glib::ustring("4px"));
+
+ SPRect *three = dynamic_cast<SPRect *>(doc->getObjectById("three"));
+ ASSERT_TRUE(three != nullptr);
+
+ EXPECT_EQ(three->style->fill.get_value(), Glib::ustring("#cccccc"));
+ EXPECT_EQ(three->style->stroke.get_value(), Glib::ustring(""));
+ EXPECT_EQ(three->style->opacity.get_value(), Glib::ustring("1"));
+ EXPECT_EQ(three->style->stroke_width.get_value(), Glib::ustring("2px"));
+
+ SPRect *four = dynamic_cast<SPRect *>(doc->getObjectById("four"));
+ ASSERT_TRUE(four != nullptr);
+
+ EXPECT_EQ(four->style->fill.get_value(), Glib::ustring("#d0d0d0"));
+ EXPECT_EQ(four->style->stroke.get_value(), Glib::ustring("#ff0000"));
+ EXPECT_EQ(four->style->opacity.get_value(), Glib::ustring("0.5"));
+ EXPECT_EQ(four->style->stroke_width.get_value(), Glib::ustring("2px"));
+}
+
+/*
+ * Test the origin flag for each of the values, should indicate where it came from.
+ */
+TEST_F(ObjectTest, StyleSource) {
+ ASSERT_TRUE(doc != nullptr);
+ ASSERT_TRUE(doc->getRoot() != nullptr);
+
+ SPRoot *root = doc->getRoot();
+ ASSERT_TRUE(root->getRepr() != nullptr);
+ ASSERT_TRUE(root->hasChildren());
+
+ SPRect *one = dynamic_cast<SPRect *>(doc->getObjectById("one"));
+ ASSERT_TRUE(one != nullptr);
+
+ EXPECT_EQ(one->style->fill.style_src, SP_STYLE_SRC_STYLE_PROP);
+ EXPECT_EQ(one->style->stroke.style_src, SP_STYLE_SRC_STYLE_PROP);
+ EXPECT_EQ(one->style->opacity.style_src, SP_STYLE_SRC_STYLE_SHEET);
+ EXPECT_EQ(one->style->stroke_width.style_src, SP_STYLE_SRC_STYLE_PROP);
+
+ SPRect *two = dynamic_cast<SPRect *>(doc->getObjectById("two"));
+ ASSERT_TRUE(two != nullptr);
+
+ EXPECT_EQ(two->style->fill.style_src, SP_STYLE_SRC_STYLE_SHEET);
+ EXPECT_EQ(two->style->stroke.style_src, SP_STYLE_SRC_STYLE_PROP);
+ EXPECT_EQ(two->style->opacity.style_src, SP_STYLE_SRC_STYLE_SHEET);
+ EXPECT_EQ(two->style->stroke_width.style_src, SP_STYLE_SRC_STYLE_PROP);
+
+ SPRect *three = dynamic_cast<SPRect *>(doc->getObjectById("three"));
+ ASSERT_TRUE(three != nullptr);
+
+ EXPECT_EQ(three->style->fill.style_src, SP_STYLE_SRC_STYLE_PROP);
+ EXPECT_EQ(three->style->stroke.style_src, SP_STYLE_SRC_STYLE_PROP);
+ EXPECT_EQ(three->style->opacity.style_src, SP_STYLE_SRC_STYLE_SHEET);
+ EXPECT_EQ(three->style->stroke_width.style_src, SP_STYLE_SRC_STYLE_PROP);
+
+ SPRect *four = dynamic_cast<SPRect *>(doc->getObjectById("four"));
+ ASSERT_TRUE(four != nullptr);
+
+ EXPECT_EQ(four->style->fill.style_src, SP_STYLE_SRC_STYLE_SHEET);
+ EXPECT_EQ(four->style->stroke.style_src, SP_STYLE_SRC_STYLE_PROP);
+ EXPECT_EQ(four->style->opacity.style_src, SP_STYLE_SRC_STYLE_SHEET);
+ EXPECT_EQ(four->style->stroke_width.style_src, SP_STYLE_SRC_STYLE_PROP);
+}
+
+/*
+ * Test the breaking up of the font property and recreation into separate properties.
+ */
+TEST_F(ObjectTest, StyleFont) {
+ ASSERT_TRUE(doc != nullptr);
+ ASSERT_TRUE(doc->getRoot() != nullptr);
+
+ SPRoot *root = doc->getRoot();
+ ASSERT_TRUE(root->getRepr() != nullptr);
+ ASSERT_TRUE(root->hasChildren());
+
+ SPRect *five = dynamic_cast<SPRect *>(doc->getObjectById("five"));
+ ASSERT_TRUE(five != nullptr);
+
+ // Font property is ALWAYS unset as it's converted into specific font css properties
+ EXPECT_EQ(five->style->font.get_value(), Glib::ustring(""));
+ EXPECT_EQ(five->style->font_size.get_value(), Glib::ustring("12px"));
+ EXPECT_EQ(five->style->font_weight.get_value(), Glib::ustring("bold"));
+ EXPECT_EQ(five->style->font_style.get_value(), Glib::ustring("italic"));
+ EXPECT_EQ(five->style->font_family.get_value(), Glib::ustring("arial, sans-serif"));
+}
+
+/*
+ * Test the consumption of font dependent lengths in SPILength, e.g. EM, EX and % units
+ */
+TEST_F(ObjectTest, StyleFontSizes) {
+ ASSERT_TRUE(doc != nullptr);
+ ASSERT_TRUE(doc->getRoot() != nullptr);
+
+ SPRoot *root = doc->getRoot();
+ ASSERT_TRUE(root->getRepr() != nullptr);
+ ASSERT_TRUE(root->hasChildren());
+
+ SPRect *six = dynamic_cast<SPRect *>(doc->getObjectById("six"));
+ ASSERT_TRUE(six != nullptr);
+
+ EXPECT_EQ(six->style->stroke_width.get_value(), Glib::ustring("1em"));
+ EXPECT_EQ(six->style->stroke_width.computed, 14);
+
+ SPRect *seven = dynamic_cast<SPRect *>(doc->getObjectById("seven"));
+ ASSERT_TRUE(seven != nullptr);
+
+ EXPECT_EQ(seven->style->stroke_width.get_value(), Glib::ustring("1ex"));
+ EXPECT_EQ(seven->style->stroke_width.computed, 7);
+
+ SPRect *eight = dynamic_cast<SPRect *>(doc->getObjectById("eight"));
+ ASSERT_TRUE(eight != nullptr);
+
+ EXPECT_EQ(eight->style->stroke_width.get_value(), Glib::ustring("50%"));
+ EXPECT_EQ(eight->style->stroke_width.computed, 1); // Is this right?
+}
diff --git a/testfiles/src/object-test.cpp b/testfiles/src/object-test.cpp
new file mode 100644
index 0000000..ac42b30
--- /dev/null
+++ b/testfiles/src/object-test.cpp
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Unit tests migrated from cxxtest
+ *
+ * Authors:
+ * Adrian Boguszewski
+ *
+ * Copyright (C) 2018 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtest/gtest.h>
+#include <doc-per-case-test.h>
+#include <src/object/sp-root.h>
+#include <src/object/sp-path.h>
+
+using namespace Inkscape;
+using namespace Inkscape::XML;
+
+class ObjectTest: public DocPerCaseTest {
+public:
+ ObjectTest() {
+ // Sample document
+ // svg:svg
+ // svg:defs
+ // svg:path
+ // svg:linearGradient
+ // svg:stop
+ // svg:filter
+ // svg:feGaussianBlur (feel free to implement for other filters)
+ // svg:clipPath
+ // svg:rect
+ // svg:g
+ // svg:use
+ // svg:circle
+ // svg:ellipse
+ // svg:text
+ // svg:polygon
+ // svg:polyline
+ // svg:image
+ // svg:line
+ char const *docString = R"A(
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- just a comment -->
+ <title id="title">SVG test</title>
+ <defs>
+ <path id="P" d="M -21,-4 -5,0 -18,12 -3,4 -4,21 0,5 12,17 4,2 21,3 5,-1 17,-12 2,-4 3,-21 -1,-5 -12,-18 -4,-3z"/>
+ <linearGradient id="LG" x1="0%" y1="0%" x2="100%" y2="0%">
+ <stop offset="0%" style="stop-color:#ffff00;stop-opacity:1"/>
+ <stop offset="100%" style="stop-color:red;stop-opacity:1"/>
+ </linearGradient>
+ <clipPath id="clip" clipPathUnits="userSpaceOnUse">
+ <rect x="10" y="10" width="100" height="100"/>
+ </clipPath>
+ <filter style="color-interpolation-filters:sRGB" id="filter" x="-0.15" width="1.34" y="0" height="1">
+ <feGaussianBlur stdDeviation="4.26"/>
+ </filter>
+ </defs>
+
+ <g id="G" transform="skewX(10.5) translate(9,5)">
+ <use id="U" xlink:href="#P" opacity="0.5" fill="#1dace3" transform="rotate(4)"/>
+ <circle id="C" cx="45.5" cy="67" r="23" fill="#000"/>
+ <ellipse id="E" cx="200" cy="70" rx="85" ry="55" fill="url(#LG)"/>
+ <text id="T" fill="#fff" style="font-size:45;font-family:Verdana" x="150" y="86">TEST</text>
+ <polygon id="PG" points="60,20 100,40 100,80 60,100 20,80 20,40" clip-path="url(#clip)" filter="url(#filter)"/>
+ <polyline id="PL" points="0,40 40,40 40,80 80,80 80,120 120,120 120,160" style="fill:none;stroke:red;stroke-width:4"/>
+ <image id="I" xlink:href="data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE4MCIgd2lkdGg9IjUwMCI+PHBhdGggZD0iTTAsNDAgNDAsNDAgNDAsODAgODAsODAgODAsMTIwIDEyMCwxMjAgMTIwLDE2MCIgc3R5bGU9ImZpbGw6d2hpdGU7c3Ryb2tlOnJlZDtzdHJva2Utd2lkdGg6NCIvPjwvc3ZnPgo="/>
+ <line id="L" x1="20" y1="100" x2="100" y2="20" stroke="black" stroke-width="2"/>
+ </g>
+</svg>
+ )A";
+ doc = SPDocument::createNewDocFromMem(docString, static_cast<int>(strlen(docString)), false);
+ }
+
+ ~ObjectTest() override {
+ doc->doUnref();
+ }
+
+ SPDocument *doc;
+};
+
+TEST_F(ObjectTest, Clones) {
+ ASSERT_TRUE(doc != nullptr);
+ ASSERT_TRUE(doc->getRoot() != nullptr);
+
+ SPRoot *root = doc->getRoot();
+ ASSERT_TRUE(root->getRepr() != nullptr);
+ ASSERT_TRUE(root->hasChildren());
+
+ SPPath *path = dynamic_cast<SPPath *>(doc->getObjectById("P"));
+ ASSERT_TRUE(path != nullptr);
+
+ Node *node = path->getRepr();
+ ASSERT_TRUE(node != nullptr);
+
+ Document *xml_doc = node->document();
+ ASSERT_TRUE(xml_doc != nullptr);
+
+ Node *parent = node->parent();
+ ASSERT_TRUE(parent != nullptr);
+
+ const size_t num_clones = 1000;
+ std::string href = std::string("#") + std::string(path->getId());
+ std::vector<Node *> clones(num_clones, nullptr);
+
+ // Create num_clones clones of this path and stick them in the document
+ for (size_t i = 0; i < num_clones; ++i) {
+ Node *clone = xml_doc->createElement("svg:use");
+ Inkscape::GC::release(clone);
+ clone->setAttribute("xlink:href", href);
+ parent->addChild(clone, node);
+ clones[i] = clone;
+ }
+
+ // Remove those clones
+ for (size_t i = 0; i < num_clones; ++i) {
+ parent->removeChild(clones[i]);
+ }
+}
+
+TEST_F(ObjectTest, Grouping) {
+ ASSERT_TRUE(doc != nullptr);
+ ASSERT_TRUE(doc->getRoot() != nullptr);
+
+ SPRoot *root = doc->getRoot();
+ ASSERT_TRUE(root->getRepr() != nullptr);
+ ASSERT_TRUE(root->hasChildren());
+
+ SPGroup *group = dynamic_cast<SPGroup *>(doc->getObjectById("G"));
+
+ ASSERT_TRUE(group != nullptr);
+
+ Node *node = group->getRepr();
+ ASSERT_TRUE(node != nullptr);
+
+ Document *xml_doc = node->document();
+ ASSERT_TRUE(xml_doc != nullptr);
+
+ const size_t num_elements = 1000;
+
+ Node *new_group = xml_doc->createElement("svg:g");
+ Inkscape::GC::release(new_group);
+ node->addChild(new_group, nullptr);
+
+ std::vector<Node *> elements(num_elements, nullptr);
+
+ for (size_t i = 0; i < num_elements; ++i) {
+ Node *circle = xml_doc->createElement("svg:circle");
+ Inkscape::GC::release(circle);
+ circle->setAttribute("cx", "2048");
+ circle->setAttribute("cy", "1024");
+ circle->setAttribute("r", "1.5");
+ new_group->addChild(circle, nullptr);
+ elements[i] = circle;
+ }
+
+ SPGroup *n_group = dynamic_cast<SPGroup *>(group->get_child_by_repr(new_group));
+ ASSERT_TRUE(n_group != nullptr);
+
+ std::vector<SPItem*> ch;
+ sp_item_group_ungroup(n_group, ch, false);
+
+ // Remove those elements
+ for (size_t i = 0; i < num_elements; ++i) {
+ elements[i]->parent()->removeChild(elements[i]);
+ }
+
+}
+
+TEST_F(ObjectTest, Objects) {
+ ASSERT_TRUE(doc != nullptr);
+ ASSERT_TRUE(doc->getRoot() != nullptr);
+
+ SPRoot *root = doc->getRoot();
+ ASSERT_TRUE(root->getRepr() != nullptr);
+ ASSERT_TRUE(root->hasChildren());
+
+ SPPath *path = dynamic_cast<SPPath *>(doc->getObjectById("P"));
+ ASSERT_TRUE(path != nullptr);
+
+ // Test parent behavior
+ SPObject *child = root->firstChild();
+ ASSERT_TRUE(child != nullptr);
+
+ EXPECT_EQ(root, child->parent);
+ EXPECT_EQ(doc, child->document);
+ EXPECT_TRUE(root->isAncestorOf(child));
+
+ // Test list behavior
+ SPObject *next = child->getNext();
+ SPObject *prev = next;
+ EXPECT_EQ(child, next->getPrev());
+
+ prev = next;
+ next = next->getNext();
+ while (next != nullptr) {
+ // Walk the list
+ EXPECT_EQ(prev, next->getPrev());
+ prev = next;
+ next = next->getNext();
+ }
+
+ // Test hrefcount
+ EXPECT_TRUE(path->isReferenced());
+}
diff --git a/testfiles/src/sp-gradient-test.cpp b/testfiles/src/sp-gradient-test.cpp
new file mode 100644
index 0000000..d190f1c
--- /dev/null
+++ b/testfiles/src/sp-gradient-test.cpp
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Unit tests migrated from cxxtest
+ *
+ * Authors:
+ * Adrian Boguszewski
+ *
+ * Copyright (C) 2018 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtest/gtest.h>
+#include <doc-per-case-test.h>
+#include <src/object/sp-gradient.h>
+#include <src/attributes.h>
+#include <2geom/transforms.h>
+#include <src/xml/node.h>
+#include <src/xml/simple-document.h>
+#include <src/svg/svg.h>
+
+using namespace Inkscape;
+using namespace Inkscape::XML;
+
+class SPGradientTest: public DocPerCaseTest {
+public:
+ SPGradientTest() {
+ DocPerCaseTest::SetUpTestCase();
+ gr = new SPGradient();
+ }
+
+ ~SPGradientTest() override {
+ delete gr;
+ DocPerCaseTest::TearDownTestCase();
+ }
+
+ SPGradient *gr;
+};
+
+TEST_F(SPGradientTest, Init) {
+ ASSERT_TRUE(gr != nullptr);
+ EXPECT_TRUE(gr->gradientTransform.isIdentity());
+ EXPECT_TRUE(Geom::are_near(Geom::identity(), gr->gradientTransform));
+}
+
+TEST_F(SPGradientTest, SetGradientTransform) {
+ SP_OBJECT(gr)->document = _doc;
+
+ SP_OBJECT(gr)->setKeyValue(SP_ATTR_GRADIENTTRANSFORM, "translate(5, 8)");
+ EXPECT_TRUE(Geom::are_near(Geom::Affine(Geom::Translate(5.0, 8.0)), gr->gradientTransform));
+
+ SP_OBJECT(gr)->setKeyValue(SP_ATTR_GRADIENTTRANSFORM, "");
+ EXPECT_TRUE(Geom::are_near(Geom::identity(), gr->gradientTransform));
+
+ SP_OBJECT(gr)->setKeyValue(SP_ATTR_GRADIENTTRANSFORM, "rotate(90)");
+ EXPECT_TRUE(Geom::are_near(Geom::Affine(Geom::Rotate::from_degrees(90.0)), gr->gradientTransform));
+}
+
+TEST_F(SPGradientTest, Write) {
+ SP_OBJECT(gr)->document = _doc;
+
+ SP_OBJECT(gr)->setKeyValue(SP_ATTR_GRADIENTTRANSFORM, "matrix(0, 1, -1, 0, 0, 0)");
+ Document *xml_doc = _doc->getReprDoc();
+
+ ASSERT_TRUE(xml_doc != nullptr);
+
+ Node *repr = xml_doc->createElement("svg:radialGradient");
+ SP_OBJECT(gr)->updateRepr(xml_doc, repr, SP_OBJECT_WRITE_ALL);
+
+ gchar const *tr = repr->attribute("gradientTransform");
+ Geom::Affine svd;
+ bool const valid = sp_svg_transform_read(tr, &svd);
+
+ EXPECT_TRUE(valid);
+ EXPECT_TRUE(Geom::are_near(Geom::Affine(Geom::Rotate::from_degrees(90.0)), svd));
+}
+
+TEST_F(SPGradientTest, GetG2dGetGs2dSetGs2) {
+ SP_OBJECT(gr)->document = _doc;
+
+ Geom::Affine grXform(2, 1,
+ 1, 3,
+ 4, 6);
+ gr->gradientTransform = grXform;
+
+ Geom::Rect unit_rect(Geom::Point(0, 0), Geom::Point(1, 1));
+ {
+ Geom::Affine g2d(gr->get_g2d_matrix(Geom::identity(), unit_rect));
+ Geom::Affine gs2d(gr->get_gs2d_matrix(Geom::identity(), unit_rect));
+ EXPECT_TRUE(Geom::are_near(Geom::identity(), g2d));
+ EXPECT_TRUE(Geom::are_near(gs2d, gr->gradientTransform * g2d, 1e-12));
+
+ gr->set_gs2d_matrix(Geom::identity(), unit_rect, gs2d);
+ EXPECT_TRUE(Geom::are_near(gr->gradientTransform, grXform, 1e-12));
+ }
+
+ gr->gradientTransform = grXform;
+ Geom::Affine funny(2, 3,
+ 4, 5,
+ 6, 7);
+ {
+ Geom::Affine g2d(gr->get_g2d_matrix(funny, unit_rect));
+ Geom::Affine gs2d(gr->get_gs2d_matrix(funny, unit_rect));
+ EXPECT_TRUE(Geom::are_near(funny, g2d));
+ EXPECT_TRUE(Geom::are_near(gs2d, gr->gradientTransform * g2d, 1e-12));
+
+ gr->set_gs2d_matrix(funny, unit_rect, gs2d);
+ EXPECT_TRUE(Geom::are_near(gr->gradientTransform, grXform, 1e-12));
+ }
+
+ gr->gradientTransform = grXform;
+ Geom::Rect larger_rect(Geom::Point(5, 6), Geom::Point(8, 10));
+ {
+ Geom::Affine g2d(gr->get_g2d_matrix(funny, larger_rect));
+ Geom::Affine gs2d(gr->get_gs2d_matrix(funny, larger_rect));
+ EXPECT_TRUE(Geom::are_near(Geom::Affine(3, 0,
+ 0, 4,
+ 5, 6) * funny, g2d ));
+ EXPECT_TRUE(Geom::are_near(gs2d, gr->gradientTransform * g2d, 1e-12));
+
+ gr->set_gs2d_matrix(funny, larger_rect, gs2d);
+ EXPECT_TRUE(Geom::are_near(gr->gradientTransform, grXform, 1e-12));
+
+ SP_OBJECT(gr)->setKeyValue( SP_ATTR_GRADIENTUNITS, "userSpaceOnUse");
+ Geom::Affine user_g2d(gr->get_g2d_matrix(funny, larger_rect));
+ Geom::Affine user_gs2d(gr->get_gs2d_matrix(funny, larger_rect));
+ EXPECT_TRUE(Geom::are_near(funny, user_g2d));
+ EXPECT_TRUE(Geom::are_near(user_gs2d, gr->gradientTransform * user_g2d, 1e-12));
+ }
+}
diff --git a/testfiles/src/sp-item-group-test.cpp b/testfiles/src/sp-item-group-test.cpp
new file mode 100644
index 0000000..3439f54
--- /dev/null
+++ b/testfiles/src/sp-item-group-test.cpp
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * SPGroup test
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2020 Authors
+ *
+ * Released under GNU GPL version 2 or later, read the file 'COPYING' for more information
+ */
+
+#include <gtest/gtest.h>
+#include <src/document.h>
+#include <src/inkscape.h>
+#include <src/live_effects/effect.h>
+#include <src/object/sp-lpe-item.h>
+
+using namespace Inkscape;
+using namespace Inkscape::LivePathEffect;
+
+class SPGroupTest : public ::testing::Test {
+ protected:
+ void SetUp() override
+ {
+ // setup hidden dependency
+ Application::create(false);
+ }
+};
+
+TEST_F(SPGroupTest, applyingPowerClipEffectToGroupWithoutClipIsIgnored)
+{
+ std::string svg("\
+<svg width='100' height='100'>\
+ <g id='group1'>\
+ <rect id='rect1' width='100' height='50' />\
+ <rect id='rect2' y='50' width='100' height='50' />\
+ </g>\
+</svg>");
+
+ SPDocument *doc = SPDocument::createNewDocFromMem(svg.c_str(), svg.size(), true);
+
+ auto group = dynamic_cast<SPGroup *>(doc->getObjectById("group1"));
+ Effect::createAndApply(POWERCLIP, doc, group);
+
+ ASSERT_FALSE(group->hasPathEffect());
+}
diff --git a/testfiles/src/sp-object-test.cpp b/testfiles/src/sp-object-test.cpp
new file mode 100644
index 0000000..713b4b0
--- /dev/null
+++ b/testfiles/src/sp-object-test.cpp
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Multiindex container for selection
+ *
+ * Authors:
+ * Adrian Boguszewski
+ *
+ * Copyright (C) 2016 Adrian Boguszewski
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#include <gtest/gtest.h>
+#include <src/object/sp-object.h>
+#include <src/object/sp-item.h>
+#include <src/xml/node.h>
+#include <src/xml/text-node.h>
+#include <doc-per-case-test.h>
+#include <src/xml/simple-document.h>
+
+using namespace Inkscape;
+using namespace Inkscape::XML;
+
+class SPObjectTest: public DocPerCaseTest {
+public:
+ SPObjectTest() {
+ a = new SPItem();
+ b = new SPItem();
+ c = new SPItem();
+ d = new SPItem();
+ e = new SPItem();
+ auto sd = new SimpleDocument();
+ auto et = new TextNode(Util::share_string("e"), sd);
+ auto dt = new TextNode(Util::share_string("d"), sd);
+ auto ct = new TextNode(Util::share_string("c"), sd);
+ auto bt = new TextNode(Util::share_string("b"), sd);
+ auto at = new TextNode(Util::share_string("a"), sd);
+ e->invoke_build(_doc, et, 0);
+ d->invoke_build(_doc, dt, 0);
+ c->invoke_build(_doc, ct, 0);
+ b->invoke_build(_doc, bt, 0);
+ a->invoke_build(_doc, at, 0);
+ }
+ ~SPObjectTest() override {
+ delete e;
+ delete d;
+ delete c;
+ delete b;
+ delete a;
+ }
+ SPObject* a;
+ SPObject* b;
+ SPObject* c;
+ SPObject* d;
+ SPObject* e;
+};
+
+TEST_F(SPObjectTest, Basics) {
+ a->attach(c, a->lastChild());
+ a->attach(b, nullptr);
+ a->attach(d, c);
+ EXPECT_TRUE(a->hasChildren());
+ EXPECT_EQ(b, a->firstChild());
+ EXPECT_EQ(d, a->lastChild());
+ auto children = a->childList(false);
+ EXPECT_EQ(3, children.size());
+ EXPECT_EQ(b, children[0]);
+ EXPECT_EQ(c, children[1]);
+ EXPECT_EQ(d, children[2]);
+ a->attach(b, a->lastChild());
+ EXPECT_EQ(3, a->children.size());
+ a->reorder(b, b);
+ EXPECT_EQ(3, a->children.size());
+ EXPECT_EQ(b, &a->children.front());
+ EXPECT_EQ(d, &a->children.back());
+ a->reorder(b, d);
+ EXPECT_EQ(3, a->children.size());
+ EXPECT_EQ(c, &a->children.front());
+ EXPECT_EQ(b, &a->children.back());
+ a->reorder(d, nullptr);
+ EXPECT_EQ(3, a->children.size());
+ EXPECT_EQ(d, &a->children.front());
+ EXPECT_EQ(b, &a->children.back());
+ a->reorder(c, b);
+ EXPECT_EQ(3, a->children.size());
+ EXPECT_EQ(d, &a->children.front());
+ EXPECT_EQ(c, &a->children.back());
+ a->detach(b);
+ EXPECT_EQ(c, a->lastChild());
+ children = a->childList(false);
+ EXPECT_EQ(2, children.size());
+ EXPECT_EQ(d, children[0]);
+ EXPECT_EQ(c, children[1]);
+ a->detach(b);
+ EXPECT_EQ(2, a->childList(false).size());
+ a->releaseReferences();
+ EXPECT_FALSE(a->hasChildren());
+ EXPECT_EQ(nullptr, a->firstChild());
+ EXPECT_EQ(nullptr, a->lastChild());
+}
+
+TEST_F(SPObjectTest, Advanced) {
+ a->attach(b, a->lastChild());
+ a->attach(c, a->lastChild());
+ a->attach(d, a->lastChild());
+ a->attach(e, a->lastChild());
+ EXPECT_EQ(e, a->get_child_by_repr(e->getRepr()));
+ EXPECT_EQ(c, a->get_child_by_repr(c->getRepr()));
+ EXPECT_EQ(d, e->getPrev());
+ EXPECT_EQ(c, d->getPrev());
+ EXPECT_EQ(b, c->getPrev());
+ EXPECT_EQ(nullptr, b->getPrev());
+ EXPECT_EQ(nullptr, e->getNext());
+ EXPECT_EQ(e, d->getNext());
+ EXPECT_EQ(d, c->getNext());
+ EXPECT_EQ(c, b->getNext());
+ std::vector<SPObject*> tmp = {b, c, d, e};
+ int index = 0;
+ for(auto& child: a->children) {
+ EXPECT_EQ(tmp[index++], &child);
+ }
+}
diff --git a/testfiles/src/style-elem-test.cpp b/testfiles/src/style-elem-test.cpp
new file mode 100644
index 0000000..c003e7b
--- /dev/null
+++ b/testfiles/src/style-elem-test.cpp
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Test the API to the style element, access, read and write functions.
+ *//*
+ *
+ * Authors:
+ * Martin Owens
+ *
+ * Copyright (C) 2018 Authors
+ *
+ * Released under GNU GPL version 2 or later, read the file 'COPYING' for more information
+ */
+
+#include <gtest/gtest.h>
+#include <doc-per-case-test.h>
+
+#include <src/style.h>
+#include <src/object/sp-root.h>
+#include <src/object/sp-style-elem.h>
+
+using namespace Inkscape;
+using namespace Inkscape::XML;
+
+class ObjectTest: public DocPerCaseTest {
+public:
+ ObjectTest() {
+ char const *docString = "\
+<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>\
+<style id='style01'>\
+rect { fill: red; opacity:0.5; }\
+#id1, #id2 { fill: red; stroke: #c0c0c0; }\
+.cls1 { fill: red; opacity:1.0; }\
+</style>\
+<style id='style02'>\
+rect { fill: green; opacity:1.0; }\
+#id3, #id4 { fill: green; stroke: #606060; }\
+.cls2 { fill: green; opacity:0.5; }\
+</style>\
+</svg>";
+ doc = SPDocument::createNewDocFromMem(docString, static_cast<int>(strlen(docString)), false);
+ }
+
+ ~ObjectTest() override {
+ doc->doUnref();
+ }
+
+ SPDocument *doc;
+};
+
+/*
+ * Test sp-style-element objects created in document.
+ */
+TEST_F(ObjectTest, StyleElems) {
+ ASSERT_TRUE(doc != nullptr);
+ ASSERT_TRUE(doc->getRoot() != nullptr);
+
+ SPRoot *root = doc->getRoot();
+ ASSERT_TRUE(root->getRepr() != nullptr);
+
+ SPStyleElem *one = dynamic_cast<SPStyleElem *>(doc->getObjectById("style01"));
+ ASSERT_TRUE(one != nullptr);
+
+ for(auto style: one->styles) {
+ EXPECT_EQ(style->fill.get_value(), Glib::ustring("#ff0000"));
+ }
+
+ SPStyleElem *two = dynamic_cast<SPStyleElem *>(doc->getObjectById("style02"));
+ ASSERT_TRUE(one != nullptr);
+
+ for(auto style: two->styles) {
+ EXPECT_EQ(style->fill.get_value(), Glib::ustring("#008000"));
+ }
+}
diff --git a/testfiles/src/style-test.cpp b/testfiles/src/style-test.cpp
new file mode 100644
index 0000000..fea2901
--- /dev/null
+++ b/testfiles/src/style-test.cpp
@@ -0,0 +1,572 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/*
+ * Unit test for style properties.
+ *
+ * Author:
+ * Tavmjong Bah <tavjong@free.fr>
+ *
+ * Copyright (C) 2017 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+#include "style.h"
+
+namespace {
+
+class StyleRead {
+
+public:
+ StyleRead(std::string src, std::string dst, std::string uri) :
+ src(std::move(src)), dst(std::move(dst)), uri(std::move(uri))
+ {
+ }
+
+ StyleRead(std::string src, std::string dst) :
+ src(std::move(src)), dst(std::move(dst)), uri("")
+ {
+ }
+
+ StyleRead(std::string const &src) :
+ src(src), dst(src), uri("")
+ {
+ }
+
+ std::string src;
+ std::string dst;
+ std::string uri;
+
+};
+
+std::vector<StyleRead> getStyleData()
+{
+ StyleRead all_style_data[] = {
+
+ // Paint -----------------------------------------------
+ StyleRead("fill:none"), StyleRead("fill:currentColor"), StyleRead("fill:#ff00ff"),
+ StyleRead("fill:rgb(100%, 0%, 100%)", "fill:#ff00ff"), StyleRead("fill:rgb(255, 0, 255)", "fill:#ff00ff"),
+
+ // TODO - fix this to preserve the string
+ // StyleRead("fill:url(#painter) rgb(100%, 0%, 100%)",
+ // "fill:url(#painter) #ff00ff", "#painter" ),
+
+ // TODO - fix this to preserve the string
+ // StyleRead("fill:url(#painter) rgb(255, 0, 255)",
+ // "fill:url(#painter) #ff00ff", "#painter"),
+
+
+ StyleRead("fill:#ff00ff icc-color(colorChange, 0.1, 0.5, 0.1)"),
+
+ // StyleRead("fill:url(#painter)", "", "#painter"),
+ // StyleRead("fill:url(#painter) none", "", "#painter"),
+ // StyleRead("fill:url(#painter) currentColor", "", "#painter"),
+ // StyleRead("fill:url(#painter) #ff00ff", "", "#painter"),
+ // StyleRead("fill:url(#painter) rgb(100%, 0%, 100%)", "", "#painter"),
+ // StyleRead("fill:url(#painter) rgb(255, 0, 255)", "", "#painter"),
+
+ // StyleRead("fill:url(#painter) #ff00ff icc-color(colorChange, 0.1, 0.5, 0.1)", "", "#painter"),
+
+ // StyleRead("fill:url(#painter) inherit", "", "#painter"),
+
+ StyleRead("fill:inherit"),
+
+
+ // General tests (in general order of appearance in sp_style_read), SPIPaint tested above
+ StyleRead("visibility:hidden"), // SPIEnum
+ StyleRead("visibility:collapse"), StyleRead("visibility:visible"),
+ StyleRead("display:none"), // SPIEnum
+ StyleRead("overflow:visible"), // SPIEnum
+ StyleRead("overflow:auto"), // SPIEnum
+
+ StyleRead("color:#ff0000"), StyleRead("color:blue", "color:#0000ff"),
+ // StyleRead("color:currentColor"), SVG 1.1 does not allow color value 'currentColor'
+
+ // Font shorthand
+ StyleRead("font:bold 12px Arial", "font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;"
+ "font-size:12px;line-height:normal;font-family:Arial"),
+ StyleRead("font:bold 12px/24px 'Times New Roman'",
+ "font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12px;line-"
+ "height:24px;font-family:\'Times New Roman\'"),
+
+ // From CSS 3 Fonts (examples):
+ StyleRead("font: 12pt/15pt sans-serif", "font-style:normal;font-variant:normal;font-weight:normal;font-stretch:"
+ "normal;font-size:16px;line-height:15pt;font-family:sans-serif"),
+ // StyleRead("font: 80% sans-serif",
+ // "font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:80%;line-height:normal;font-family:sans-serif"),
+ // StyleRead("font: x-large/110% 'new century schoolbook', serif",
+ // "font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:x-large;line-height:110%;font-family:\'new
+ //century schoolbook\', serif"),
+ StyleRead("font: bold italic large Palatino, serif",
+ "font-style:italic;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:large;line-"
+ "height:normal;font-family:Palatino, serif"),
+ // StyleRead("font: normal small-caps 120%/120% fantasy",
+ // "font-style:normal;font-variant:small-caps;font-weight:normal;font-stretch:normal;font-size:120%;line-height:120%;font-family:fantasy"),
+ StyleRead("font: condensed oblique 12pt 'Helvetica Neue', serif;",
+ "font-style:oblique;font-variant:normal;font-weight:normal;font-stretch:condensed;font-size:16px;"
+ "line-height:normal;font-family:\'Helvetica Neue\', serif"),
+
+ StyleRead("font-family:sans-serif"), // SPIString, text_private
+ StyleRead("font-family:Arial"),
+ // StyleRead("font-variant:normal;font-stretch:normal;-inkscape-font-specification:Nimbus Roman No9 L Bold
+ // Italic"),
+
+ // Needs to be fixed (quotes should be around each font-family):
+ StyleRead("font-family:Georgia, 'Minion Web'", "font-family:Georgia, \'Minion Web\'"),
+ StyleRead("font-size:12", "font-size:12px"), // SPIFontSize
+ StyleRead("font-size:12px"), StyleRead("font-size:12pt", "font-size:16px"), StyleRead("font-size:medium"),
+ StyleRead("font-size:smaller"),
+ StyleRead("font-style:italic"), // SPIEnum
+ StyleRead("font-variant:small-caps"), // SPIEnum
+ StyleRead("font-weight:100"), // SPIEnum
+ StyleRead("font-weight:normal"), StyleRead("font-weight:bolder"),
+ StyleRead("font-stretch:condensed"), // SPIEnum
+
+ StyleRead("font-variant-ligatures:none"), // SPILigatures
+ StyleRead("font-variant-ligatures:normal"), StyleRead("font-variant-ligatures:no-common-ligatures"),
+ StyleRead("font-variant-ligatures:discretionary-ligatures"),
+ StyleRead("font-variant-ligatures:historical-ligatures"), StyleRead("font-variant-ligatures:no-contextual"),
+ StyleRead("font-variant-ligatures:common-ligatures", "font-variant-ligatures:normal"),
+ StyleRead("font-variant-ligatures:contextual", "font-variant-ligatures:normal"),
+ StyleRead("font-variant-ligatures:no-common-ligatures historical-ligatures"),
+ StyleRead("font-variant-ligatures:historical-ligatures no-contextual"),
+ StyleRead("font-variant-position:normal"), StyleRead("font-variant-position:sub"),
+ StyleRead("font-variant-position:super"), StyleRead("font-variant-caps:normal"),
+ StyleRead("font-variant-caps:small-caps"), StyleRead("font-variant-caps:all-small-caps"),
+ StyleRead("font-variant-numeric:normal"), StyleRead("font-variant-numeric:lining-nums"),
+ StyleRead("font-variant-numeric:oldstyle-nums"), StyleRead("font-variant-numeric:proportional-nums"),
+ StyleRead("font-variant-numeric:tabular-nums"), StyleRead("font-variant-numeric:diagonal-fractions"),
+ StyleRead("font-variant-numeric:stacked-fractions"), StyleRead("font-variant-numeric:ordinal"),
+ StyleRead("font-variant-numeric:slashed-zero"), StyleRead("font-variant-numeric:tabular-nums slashed-zero"),
+ StyleRead("font-variant-numeric:tabular-nums proportional-nums", "font-variant-numeric:proportional-nums"),
+
+ StyleRead("font-variation-settings:'wght' 400"),
+ StyleRead("font-variation-settings:'wght' 400", "font-variation-settings:'wght' 400"),
+ StyleRead("font-variation-settings:'wght' 400, 'slnt' 0.5", "font-variation-settings:'slnt' 0.5, 'wght' 400"),
+
+ // Should be moved down
+ StyleRead("text-indent:12em"), // SPILength?
+ StyleRead("text-align:center"), // SPIEnum
+
+ // SPITextDecoration
+ // The default value for 'text-decoration-color' is 'currentColor', but
+ // we cannot set the default to that value yet. (We need to switch
+ // SPIPaint to SPIColor and then add the ability to set default.)
+ // StyleRead("text-decoration: underline",
+ // "text-decoration: underline;text-decoration-line: underline;text-decoration-color:currentColor"),
+ // StyleRead("text-decoration: overline underline",
+ // "text-decoration: underline overline;text-decoration-line: underline
+ // overline;text-decoration-color:currentColor"),
+
+ StyleRead("text-decoration: underline wavy #0000ff",
+ "text-decoration: underline;text-decoration-line: "
+ "underline;text-decoration-style:wavy;text-decoration-color:#0000ff"),
+ StyleRead("text-decoration: double overline underline #ff0000",
+ "text-decoration: underline overline;text-decoration-line: underline "
+ "overline;text-decoration-style:double;text-decoration-color:#ff0000"),
+
+ // SPITextDecorationLine
+ StyleRead("text-decoration-line: underline", "text-decoration: underline;text-decoration-line: underline"),
+
+ // SPITextDecorationStyle
+ StyleRead("text-decoration-style:solid"), StyleRead("text-decoration-style:dotted"),
+
+ // SPITextDecorationColor
+ StyleRead("text-decoration-color:#ff00ff"),
+
+ // Should be moved up
+ StyleRead("line-height:24px"), // SPILengthOrNormal
+ StyleRead("line-height:1.5"),
+ StyleRead("letter-spacing:2px"), // SPILengthOrNormal
+ StyleRead("word-spacing:2px"), // SPILengthOrNormal
+ StyleRead("word-spacing:normal"),
+ StyleRead("text-transform:lowercase"), // SPIEnum
+ // ...
+ StyleRead("baseline-shift:baseline"), // SPIBaselineShift
+ StyleRead("baseline-shift:sub"), StyleRead("baseline-shift:12.5%"), StyleRead("baseline-shift:2px"),
+
+ StyleRead("opacity:0.1"), // SPIScale24
+ // ...
+ StyleRead("stroke-width:2px"), // SPILength
+ StyleRead("stroke-linecap:round"), // SPIEnum
+ StyleRead("stroke-linejoin:round"), // SPIEnum
+ StyleRead("stroke-miterlimit:4"), // SPIFloat
+ StyleRead("marker:url(#Arrow)"), // SPIString
+ StyleRead("marker-start:url(#Arrow)"), StyleRead("marker-mid:url(#Arrow)"), StyleRead("marker-end:url(#Arrow)"),
+ StyleRead("stroke-opacity:0.5"), // SPIScale24
+ // Currently inkscape handle unit conversion in dasharray but need
+ // a active document to do it, so we can't include in any test
+ StyleRead("stroke-dasharray:0, 1, 0, 1"), // SPIDashArray
+ StyleRead("stroke-dasharray:0 1 0 1", "stroke-dasharray:0, 1, 0, 1"),
+ StyleRead("stroke-dasharray:0 1 2 3", "stroke-dasharray:0, 1, 2, 3"),
+ StyleRead("stroke-dashoffset:13"), // SPILength
+ StyleRead("stroke-dashoffset:10px"),
+ // ...
+ // StyleRead("filter:url(#myfilter)"), // SPIFilter segfault in read
+ StyleRead("filter:inherit"),
+
+ StyleRead("opacity:0.1;fill:#ff0000;stroke:#0000ff;stroke-width:2px"),
+ StyleRead("opacity:0.1;fill:#ff0000;stroke:#0000ff;stroke-width:2px;stroke-dasharray:1, 2, 3, "
+ "4;stroke-dashoffset:15"),
+
+ StyleRead("paint-order:stroke"), // SPIPaintOrder
+ StyleRead("paint-order:normal"),
+ StyleRead("paint-order: markers stroke fill", "paint-order:markers stroke fill"),
+
+ // !important (in order of appearance in style-internal.h)
+ StyleRead("stroke-miterlimit:4 !important"), // SPIFloat
+ StyleRead("stroke-opacity:0.5 !important"), // SPIScale24
+ StyleRead("stroke-width:2px !important"), // SPILength
+ StyleRead("line-height:24px !important"), // SPILengthOrNormal
+ StyleRead("line-height:normal !important"),
+ StyleRead("font-stretch:condensed !important"), // SPIEnum
+ StyleRead("marker:url(#Arrow) !important"), // SPIString
+ StyleRead("color:#0000ff !important"), // SPIColor
+ StyleRead("fill:none !important"), // SPIPaint
+ StyleRead("fill:currentColor !important"), StyleRead("fill:#ff00ff !important"),
+ StyleRead("paint-order:stroke !important"), // SPIPaintOrder
+ StyleRead("paint-order:normal !important"),
+ StyleRead("stroke-dasharray:0, 1, 0, 1 !important"), // SPIDashArray
+ StyleRead("font-size:12px !important"), // SPIFontSize
+ StyleRead("baseline-shift:baseline !important"), // SPIBaselineShift
+ StyleRead("baseline-shift:sub !important"),
+ // StyleRead("text-decoration-line: underline !important"), // SPITextDecorationLine
+
+ };
+
+ size_t count = sizeof(all_style_data) / sizeof(all_style_data[0]);
+ std::vector<StyleRead> vect(all_style_data, all_style_data + count);
+ return vect;
+}
+
+TEST(StyleTest, Read) {
+ std::vector<StyleRead> all_style = getStyleData();
+ EXPECT_GT(all_style.size(), 0);
+ for (auto i : all_style) {
+
+ SPStyle style;
+ style.mergeString (i.src.c_str());
+
+ if (!i.uri.empty()) {
+ //EXPECT_EQ (style.fill.value.href->getURI()->toString(), i.uri);
+ }
+
+ std::string out = style.write();
+ if (i.dst.empty()) {
+ // std::cout << "out: " << out << std::endl;
+ // std::cout << "i.src: " << i.src << std::endl;
+ EXPECT_EQ (out, i.src);
+ } else {
+ // std::cout << "out: " << out << std::endl;
+ // std::cout << "i.dst: " << i.dst << std::endl;
+ EXPECT_EQ (out, i.dst);
+ }
+ }
+}
+
+
+// ------------------------------------------------------------------------------------
+
+class StyleMatch {
+
+public:
+ StyleMatch(std::string src, std::string dst, bool const &match) :
+ src(std::move(src)), dst(std::move(dst)), match(match)
+ {
+ }
+
+ std::string src;
+ std::string dst;
+ bool match;
+
+};
+
+std::vector<StyleMatch> getStyleMatchData()
+{
+ StyleMatch all_style_data[] = {
+
+ // SPIFloat
+ StyleMatch("stroke-miterlimit:4", "stroke-miterlimit:4", true ),
+ StyleMatch("stroke-miterlimit:4", "stroke-miterlimit:2", false),
+ StyleMatch("stroke-miterlimit:4", "", true ), // Default
+
+ // SPIScale24
+ StyleMatch("opacity:0.3", "opacity:0.3", true ),
+ StyleMatch("opacity:0.3", "opacity:0.6", false),
+ StyleMatch("opacity:1.0", "", true ), // Default
+
+ // SPILength
+ StyleMatch("text-indent:3", "text-indent:3", true ),
+ StyleMatch("text-indent:6", "text-indent:3", false),
+ StyleMatch("text-indent:6px", "text-indent:3", false),
+ StyleMatch("text-indent:1px", "text-indent:12pc", false),
+ StyleMatch("text-indent:2ex", "text-indent:2ex", false),
+
+ // SPILengthOrNormal
+ StyleMatch("letter-spacing:normal", "letter-spacing:normal", true ),
+ StyleMatch("letter-spacing:2", "letter-spacing:normal", false),
+ StyleMatch("letter-spacing:normal", "letter-spacing:2", false),
+ StyleMatch("letter-spacing:5px", "letter-spacing:5px", true ),
+ StyleMatch("letter-spacing:10px", "letter-spacing:5px", false),
+ StyleMatch("letter-spacing:10em", "letter-spacing:10em", false),
+
+ // SPIEnum
+ StyleMatch("text-anchor:start", "text-anchor:start", true ),
+ StyleMatch("text-anchor:start", "text-anchor:middle", false),
+ StyleMatch("text-anchor:start", "", true ), // Default
+ StyleMatch("text-anchor:start", "text-anchor:junk", true ), // Bad value
+
+ StyleMatch("font-weight:normal", "font-weight:400", true ),
+ StyleMatch("font-weight:bold", "font-weight:700", true ),
+
+
+ // SPIString and SPIFontString
+ StyleMatch("font-family:Arial", "font-family:Arial", true ),
+ StyleMatch("font-family:A B", "font-family:A B", true ),
+ StyleMatch("font-family:A B", "font-family:A C", false),
+ // Default is not set by class... value is NULL which cannot be compared
+ // StyleMatch("font-family:sans-serif", "", true ), // Default
+
+ // SPIColor
+ StyleMatch("color:blue", "color:blue", true ),
+ StyleMatch("color:blue", "color:red", false),
+ StyleMatch("color:red", "color:#ff0000", true ),
+
+ // SPIPaint
+ StyleMatch("fill:blue", "fill:blue", true ),
+ StyleMatch("fill:blue", "fill:red", false),
+ StyleMatch("fill:currentColor", "fill:currentColor", true ),
+ StyleMatch("fill:url(#xxx)", "fill:url(#xxx)", true ),
+ // Needs URL defined as in test 1
+ //StyleMatch("fill:url(#xxx)", "fill:url(#yyy)", false),
+
+ // SPIPaintOrder
+ StyleMatch("paint-order:markers", "paint-order:markers", true ),
+ StyleMatch("paint-order:markers", "paint-order:stroke", false),
+ //StyleMatch("paint-order:fill stroke markers", "", true ), // Default
+ StyleMatch("paint-order:normal", "paint-order:normal", true ),
+ //StyleMatch("paint-order:fill stroke markers", "paint-order:normal", true ),
+
+ // SPIDashArray
+ StyleMatch("stroke-dasharray:0 1 2 3","stroke-dasharray:0 1 2 3",true ),
+ StyleMatch("stroke-dasharray:0 1", "stroke-dasharray:0 2", false),
+
+ // SPIFilter
+
+ // SPIFontSize
+ StyleMatch("font-size:12px", "font-size:12px", true ),
+ StyleMatch("font-size:12px", "font-size:24px", false),
+ StyleMatch("font-size:12ex", "font-size:24ex", false),
+ StyleMatch("font-size:medium", "font-size:medium", true ),
+ StyleMatch("font-size:medium", "font-size:large", false),
+
+ // SPIBaselineShift
+ StyleMatch("baseline-shift:baseline", "baseline-shift:baseline", true ),
+ StyleMatch("baseline-shift:sub", "baseline-shift:sub", true ),
+ StyleMatch("baseline-shift:sub", "baseline-shift:super", false),
+ StyleMatch("baseline-shift:baseline", "baseline-shift:sub", false),
+ StyleMatch("baseline-shift:10px", "baseline-shift:10px", true ),
+ StyleMatch("baseline-shift:10px", "baseline-shift:12px", false),
+
+
+ // SPITextDecorationLine
+ StyleMatch("text-decoration-line:underline", "text-decoration-line:underline", true ),
+ StyleMatch("text-decoration-line:underline", "text-decoration-line:overline", false),
+ StyleMatch("text-decoration-line:underline overline", "text-decoration-line:underline overline", true ),
+ StyleMatch("text-decoration-line:none", "", true ), // Default
+
+
+ // SPITextDecorationStyle
+ StyleMatch("text-decoration-style:solid", "text-decoration-style:solid", true ),
+ StyleMatch("text-decoration-style:dotted", "text-decoration-style:solid", false),
+ StyleMatch("text-decoration-style:solid", "", true ), // Default
+
+ // SPITextDecoration
+ StyleMatch("text-decoration:underline", "text-decoration:underline", true ),
+ StyleMatch("text-decoration:underline", "text-decoration:overline", false),
+ StyleMatch("text-decoration:underline overline","text-decoration:underline overline",true ),
+ StyleMatch("text-decoration:overline underline","text-decoration:underline overline",true ),
+ // StyleMatch("text-decoration:none", "text-decoration-color:currentColor", true ), // Default
+
+ };
+
+ size_t count = sizeof(all_style_data) / sizeof(all_style_data[0]);
+ std::vector<StyleMatch> vect(all_style_data, all_style_data + count);
+ return vect;
+}
+
+TEST(StyleTest, Match) {
+ std::vector<StyleMatch> all_style = getStyleMatchData();
+ EXPECT_GT(all_style.size(), 0);
+ for (auto i : all_style) {
+
+ SPStyle style_src;
+ SPStyle style_dst;
+
+ style_src.mergeString( i.src.c_str() );
+ style_dst.mergeString( i.dst.c_str() );
+
+ // std::cout << "Test:" << std::endl;
+ // std::cout << " C: |" << i.src
+ // << "| |" << i.dst << "|" << std::endl;
+ // std::cout << " S: |" << style_src.write( SP_STYLE_FLAG_IFSET )
+ // << "| |" << style_dst.write( SP_STYLE_FLAG_IFSET ) << "|" <<std::endl;
+
+ EXPECT_TRUE( (style_src == style_dst) == i.match );
+ }
+}
+
+// ------------------------------------------------------------------------------------
+
+class StyleCascade {
+
+public:
+ StyleCascade(std::string parent, std::string child, std::string result) :
+ parent(std::move(parent)), child(std::move(child)), result(std::move(result))
+ {
+ }
+
+ std::string parent;
+ std::string child;
+ std::string result;
+
+};
+
+std::vector<StyleCascade> getStyleCascadeData()
+{
+
+ StyleCascade all_style_data[] = {
+
+ // SPIFloat
+ StyleCascade("stroke-miterlimit:6", "stroke-miterlimit:2", "stroke-miterlimit:2" ),
+ StyleCascade("stroke-miterlimit:6", "", "stroke-miterlimit:6" ),
+ StyleCascade("", "stroke-miterlimit:2", "stroke-miterlimit:2" ),
+
+ // SPIScale24
+ StyleCascade("opacity:0.3", "opacity:0.3", "opacity:0.3" ),
+ StyleCascade("opacity:0.3", "opacity:0.6", "opacity:0.6" ),
+ // 'opacity' does not inherit
+ StyleCascade("opacity:0.3", "", "opacity:1.0" ),
+ StyleCascade("", "opacity:0.3", "opacity:0.3" ),
+ StyleCascade("opacity:0.5", "opacity:inherit", "opacity:0.5" ),
+ StyleCascade("", "", "opacity:1.0" ),
+
+ // SPILength
+ StyleCascade("text-indent:3", "text-indent:3", "text-indent:3" ),
+ StyleCascade("text-indent:6", "text-indent:3", "text-indent:3" ),
+ StyleCascade("text-indent:6px", "text-indent:3", "text-indent:3" ),
+ StyleCascade("text-indent:1px", "text-indent:12pc", "text-indent:12pc" ),
+ // ex, em cannot be equal
+ //StyleCascade("text-indent:2ex", "text-indent:2ex", "text-indent:2ex" ),
+ StyleCascade("text-indent:3", "", "text-indent:3" ),
+ StyleCascade("text-indent:3", "text-indent:inherit", "text-indent:3" ),
+
+ // SPILengthOrNormal
+ StyleCascade("letter-spacing:normal", "letter-spacing:normal", "letter-spacing:normal" ),
+ StyleCascade("letter-spacing:2", "letter-spacing:normal", "letter-spacing:normal" ),
+ StyleCascade("letter-spacing:normal", "letter-spacing:2", "letter-spacing:2" ),
+ StyleCascade("letter-spacing:5px", "letter-spacing:5px", "letter-spacing:5px" ),
+ StyleCascade("letter-spacing:10px", "letter-spacing:5px", "letter-spacing:5px" ),
+ // ex, em cannot be equal
+ // StyleCascade("letter-spacing:10em", "letter-spacing:10em", "letter-spacing:10em" ),
+
+ // SPIEnum
+ StyleCascade("text-anchor:start", "text-anchor:start", "text-anchor:start" ),
+ StyleCascade("text-anchor:start", "text-anchor:middle", "text-anchor:middle" ),
+ StyleCascade("text-anchor:start", "", "text-anchor:start" ),
+ StyleCascade("text-anchor:start", "text-anchor:junk", "text-anchor:start" ),
+ StyleCascade("text-anchor:end", "text-anchor:inherit", "text-anchor:end" ),
+
+ StyleCascade("font-weight:400", "font-weight:400", "font-weight:400" ),
+ StyleCascade("font-weight:400", "font-weight:700", "font-weight:700" ),
+ StyleCascade("font-weight:400", "font-weight:bolder", "font-weight:700" ),
+ StyleCascade("font-weight:700", "font-weight:bolder", "font-weight:900" ),
+ StyleCascade("font-weight:400", "font-weight:lighter", "font-weight:100" ),
+ StyleCascade("font-weight:200", "font-weight:lighter", "font-weight:100" ),
+
+ StyleCascade("font-stretch:condensed","font-stretch:expanded", "font-stretch:expanded" ),
+ StyleCascade("font-stretch:condensed","font-stretch:wider", "font-stretch:semi-condensed" ),
+
+ // SPIString and SPIFontString
+
+ StyleCascade("font-variation-settings:'wght' 400", "", "font-variation-settings:'wght' 400"),
+ StyleCascade("font-variation-settings:'wght' 100",
+ "font-variation-settings:'wght' 400",
+ "font-variation-settings:'wght' 400"),
+
+ // SPIPaint
+
+ // SPIPaintOrder
+
+ // SPIDashArray
+
+ // SPIFilter
+
+ // SPIFontSize
+
+ // SPIBaselineShift
+
+
+ // SPITextDecorationLine
+ StyleCascade("text-decoration-line:overline", "text-decoration-line:underline",
+ "text-decoration-line:underline" ),
+
+ // SPITextDecorationStyle
+
+ // SPITextDecoration
+ };
+
+ size_t count = sizeof(all_style_data) / sizeof(all_style_data[0]);
+ std::vector<StyleCascade> vect(all_style_data, all_style_data + count);
+ return vect;
+
+}
+
+TEST(StyleTest, Cascade) {
+ std::vector<StyleCascade> all_style = getStyleCascadeData();
+ EXPECT_GT(all_style.size(), 0);
+ for (auto i : all_style) {
+
+ SPStyle style_parent;
+ SPStyle style_child;
+ SPStyle style_result;
+
+ style_parent.mergeString( i.parent.c_str() );
+ style_child.mergeString( i.child.c_str() );
+ style_result.mergeString( i.result.c_str() );
+
+ // std::cout << "Test:" << std::endl;
+ // std::cout << " Input: ";
+ // std::cout << " Parent: " << i.parent
+ // << " Child: " << i.child
+ // << " Result: " << i.result << std::endl;
+ // std::cout << " Write: ";
+ // std::cout << " Parent: " << style_parent.write( SP_STYLE_FLAG_IFSET )
+ // << " Child: " << style_child.write( SP_STYLE_FLAG_IFSET )
+ // << " Result: " << style_result.write( SP_STYLE_FLAG_IFSET ) << std::endl;
+
+ style_child.cascade( &style_parent );
+
+ EXPECT_TRUE(style_child == style_result );
+ }
+}
+
+
+} // namespace
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/testfiles/src/svg-stringstream-test.cpp b/testfiles/src/svg-stringstream-test.cpp
new file mode 100644
index 0000000..636b018
--- /dev/null
+++ b/testfiles/src/svg-stringstream-test.cpp
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Test CSSOStringStream and SVGOStringStream
+ */
+/*
+ * Authors:
+ * Thomas Holder
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "2geom/point.h"
+#include "svg/css-ostringstream.h"
+#include "svg/stringstream.h"
+
+#include "gtest/gtest.h"
+#include <glibmm/ustring.h>
+
+template <typename S, typename T>
+static void assert_tostring_eq(T value, const char *expected)
+{
+ S os;
+
+ // default of /options/svgoutput/numericprecision
+ os.precision(8);
+
+ os << value;
+ ASSERT_EQ(os.str(), expected);
+}
+
+#define TEST_STRING "Hello & <World>"
+
+template <typename S>
+void test_tostring()
+{
+ assert_tostring_eq<S, char>('A', "A");
+ assert_tostring_eq<S, signed char>('A', "A");
+ assert_tostring_eq<S, unsigned char>('A', "A");
+
+ assert_tostring_eq<S, short>(0x7FFF, "32767");
+ assert_tostring_eq<S, short>(-30000, "-30000");
+ assert_tostring_eq<S, unsigned short>(0xFFFFu, "65535");
+ assert_tostring_eq<S, int>(0x7FFFFFFF, "2147483647");
+ assert_tostring_eq<S, int>(-2000000000, "-2000000000");
+ assert_tostring_eq<S, unsigned int>(0xFFFFFFFFu, "4294967295");
+
+ // long is 32bit on Windows, 64bit on Linux
+ assert_tostring_eq<S, long>(0x7FFFFFFFL, "2147483647");
+ assert_tostring_eq<S, long>(-2000000000L, "-2000000000");
+ assert_tostring_eq<S, unsigned long>(0xFFFFFFFFuL, "4294967295");
+
+ assert_tostring_eq<S>((char const *)TEST_STRING, TEST_STRING);
+ assert_tostring_eq<S>((signed char const *)TEST_STRING, TEST_STRING);
+ assert_tostring_eq<S>((unsigned char const *)TEST_STRING, TEST_STRING);
+ assert_tostring_eq<S, std::string>(TEST_STRING, TEST_STRING);
+ assert_tostring_eq<S, Glib::ustring>(TEST_STRING, TEST_STRING);
+}
+
+TEST(CSSOStringStreamTest, tostring)
+{
+ using S = Inkscape::CSSOStringStream;
+
+ test_tostring<S>();
+
+ // float has 6 significant digits
+ assert_tostring_eq<S, float>(0.0, "0");
+ assert_tostring_eq<S, float>(4.5, "4.5");
+ assert_tostring_eq<S, float>(-4.0, "-4");
+ assert_tostring_eq<S, float>(0.001, "0.001");
+ assert_tostring_eq<S, float>(0.00123456, "0.00123456");
+ assert_tostring_eq<S, float>(-0.00123456, "-0.00123456");
+ assert_tostring_eq<S, float>(-1234560.0, "-1234560");
+
+ // double has 15 significant digits
+ assert_tostring_eq<S, double>(0.0, "0");
+ assert_tostring_eq<S, double>(4.5, "4.5");
+ assert_tostring_eq<S, double>(-4.0, "-4");
+ assert_tostring_eq<S, double>(0.001, "0.001");
+
+ // 9 significant digits
+ assert_tostring_eq<S, double>(1.23456789, "1.23456789");
+ assert_tostring_eq<S, double>(-1.23456789, "-1.23456789");
+ assert_tostring_eq<S, double>(12345678.9, "12345678.9");
+ assert_tostring_eq<S, double>(-12345678.9, "-12345678.9");
+
+ assert_tostring_eq<S, double>(1.234e-12, "0");
+ assert_tostring_eq<S, double>(3e9, "3000000000");
+ assert_tostring_eq<S, double>(-3.5e9, "-3500000000");
+}
+
+TEST(SVGOStringStreamTest, tostring)
+{
+ using S = Inkscape::SVGOStringStream;
+
+ test_tostring<S>();
+
+ assert_tostring_eq<S>(Geom::Point(12, 3.4), "12,3.4");
+
+ // float has 6 significant digits
+ assert_tostring_eq<S, float>(0.0, "0");
+ assert_tostring_eq<S, float>(4.5, "4.5");
+ assert_tostring_eq<S, float>(-4.0, "-4");
+ assert_tostring_eq<S, float>(0.001, "0.001");
+ assert_tostring_eq<S, float>(0.00123456, "0.00123456");
+ assert_tostring_eq<S, float>(-0.00123456, "-0.00123456");
+ assert_tostring_eq<S, float>(-1234560.0, "-1234560");
+
+ // double has 15 significant digits
+ assert_tostring_eq<S, double>(0.0, "0");
+ assert_tostring_eq<S, double>(4.5, "4.5");
+ assert_tostring_eq<S, double>(-4.0, "-4");
+ assert_tostring_eq<S, double>(0.001, "0.001");
+
+ // 8 significant digits
+ assert_tostring_eq<S, double>(1.23456789, "1.2345679");
+ assert_tostring_eq<S, double>(-1.23456789, "-1.2345679");
+ assert_tostring_eq<S, double>(12345678.9, "12345679");
+ assert_tostring_eq<S, double>(-12345678.9, "-12345679");
+
+ assert_tostring_eq<S, double>(1.234e-12, "1.234e-12");
+ assert_tostring_eq<S, double>(3e9, "3e+09");
+ assert_tostring_eq<S, double>(-3.5e9, "-3.5e+09");
+}
+
+template <typename S>
+void test_concat()
+{
+ S s;
+ s << "hello, ";
+ s << -53.5;
+ ASSERT_EQ(s.str(), std::string("hello, -53.5"));
+}
+
+TEST(CSSOStringStreamTest, concat)
+{ //
+ test_concat<Inkscape::CSSOStringStream>();
+}
+
+TEST(SVGOStringStreamTest, concat)
+{ //
+ test_concat<Inkscape::SVGOStringStream>();
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/src/uri-test.cpp b/testfiles/src/uri-test.cpp
new file mode 100644
index 0000000..91af2e7
--- /dev/null
+++ b/testfiles/src/uri-test.cpp
@@ -0,0 +1,304 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Test Inkscape::URI
+ */
+/*
+ * Authors:
+ * Thomas Holder
+ *
+ * Copyright (C) 2018 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "object/uri.h"
+#include "gtest/gtest.h"
+
+using Inkscape::URI;
+
+#define BASE64_HELLO_WORLD_P1 "SGVsbG8g"
+#define BASE64_HELLO_WORLD_P2 "V29ybGQ="
+#define DATA_BASE64_HEADER "data:text/plain;charset=utf-8;base64,"
+char const *DATA_BASE64_HELLO_WORLD = DATA_BASE64_HEADER BASE64_HELLO_WORLD_P1 BASE64_HELLO_WORLD_P2;
+char const *DATA_BASE64_HELLO_WORLD_WRAPPED = DATA_BASE64_HEADER BASE64_HELLO_WORLD_P1 "\n" BASE64_HELLO_WORLD_P2;
+
+char const *win_url_unc = "file://laptop/My%20Documents/FileSchemeURIs.doc";
+char const *win_url_local = "file:///C:/Documents%20and%20Settings/davris/FileSchemeURIs.doc";
+char const *win_filename_local = "C:\\Documents and Settings\\davris\\FileSchemeURIs.doc";
+
+TEST(UriTest, Malformed)
+{
+ ASSERT_ANY_THROW(URI(nullptr));
+ ASSERT_ANY_THROW(URI("nonhex-%XX"));
+}
+
+TEST(UriTest, GetPath)
+{
+ ASSERT_STREQ(URI().getPath(), nullptr);
+ ASSERT_STREQ(URI("foo.svg").getPath(), "foo.svg");
+ ASSERT_STREQ(URI("foo.svg#bar").getPath(), "foo.svg");
+ ASSERT_STREQ(URI("#bar").getPath(), nullptr);
+ ASSERT_STREQ(URI("scheme://host").getPath(), nullptr);
+ ASSERT_STREQ(URI("scheme://host/path").getPath(), "/path");
+ ASSERT_STREQ(URI("scheme://host/path?query").getPath(), "/path");
+ ASSERT_STREQ(URI("scheme:/path").getPath(), "/path");
+}
+
+TEST(UriTest, FromDir)
+{
+#ifdef _WIN32
+ ASSERT_EQ(URI::from_dirname("C:\\tmp").str(), "file:///C:/tmp/");
+ ASSERT_EQ(URI::from_dirname("C:\\").str(), "file:///C:/");
+ ASSERT_EQ(URI::from_href_and_basedir("uri.svg", "C:\\tmp").str(), "file:///C:/tmp/uri.svg");
+#else
+ ASSERT_EQ(URI::from_dirname("/").str(), "file:///");
+ ASSERT_EQ(URI::from_dirname("/tmp").str(), "file:///tmp/");
+ ASSERT_EQ(URI::from_href_and_basedir("uri.svg", "/tmp").str(), "file:///tmp/uri.svg");
+#endif
+}
+
+TEST(UriTest, Str)
+{
+ ASSERT_EQ(URI().str(), "");
+ ASSERT_EQ(URI("").str(), "");
+ ASSERT_EQ(URI("", "http://a/b").str(), "http://a/b");
+
+ ASSERT_EQ(URI("uri.svg").str(), "uri.svg");
+ ASSERT_EQ(URI("tmp/uri.svg").str(), "tmp/uri.svg");
+ ASSERT_EQ(URI("/tmp/uri.svg").str(), "/tmp/uri.svg");
+ ASSERT_EQ(URI("../uri.svg").str(), "../uri.svg");
+
+ ASSERT_EQ(URI("file:///tmp/uri.svg").str(), "file:///tmp/uri.svg");
+ ASSERT_EQ(URI("uri.svg", "file:///tmp/").str(), "file:///tmp/uri.svg");
+ ASSERT_EQ(URI("file:///tmp/uri.svg").str("file:///tmp/"), "uri.svg");
+ ASSERT_EQ(URI("file:///tmp/up/uri.svg").str("file:///tmp/"), "up/uri.svg");
+ ASSERT_EQ(URI("file:///tmp/uri.svg").str("file:///tmp/up/"), "../uri.svg");
+ ASSERT_EQ(URI("file:///tmp/uri.svg").str("http://web/url"), "file:///tmp/uri.svg");
+ ASSERT_EQ(URI("file:///tmp/uri.svg").str("http://web/url"), "file:///tmp/uri.svg");
+ ASSERT_EQ(URI("foo/uri.svg", "http://web/a/b/c").str(), "http://web/a/b/foo/uri.svg");
+ ASSERT_EQ(URI("foo/uri.svg", "http://web/a/b/c").str("http://web/a/"), "b/foo/uri.svg");
+ ASSERT_EQ(URI("foo/uri.svg", "http://web/a/b/c").str("http://other/a/"), "http://web/a/b/foo/uri.svg");
+
+ ASSERT_EQ(URI("http://web/").str("http://web/"), "");
+ ASSERT_EQ(URI("http://web/").str("http://web/url"), "./");
+
+ // special case: don't cross filesystem root
+ ASSERT_EQ(URI("file:///a").str("file:///"), "a");
+ ASSERT_EQ(URI("file:///ax/b").str("file:///ay/"), "file:///ax/b"); // special case
+ ASSERT_EQ(URI("file:///C:/b").str("file:///D:/"), "file:///C:/b"); // special case
+ ASSERT_EQ(URI("file:///C:/a/b").str("file:///C:/b/"), "../a/b");
+
+ ASSERT_EQ(URI(win_url_unc).str(), win_url_unc);
+ ASSERT_EQ(URI(win_url_unc).str("file://laptop/My%20Documents/"), "FileSchemeURIs.doc");
+ ASSERT_EQ(URI(win_url_local).str(), win_url_local);
+ ASSERT_EQ(URI(win_url_local).str("file:///C:/Documents%20and%20Settings/"), "davris/FileSchemeURIs.doc");
+ ASSERT_EQ(URI(win_url_local).str(win_url_unc), win_url_local);
+#ifdef _WIN32
+ ASSERT_EQ(URI(win_url_local).toNativeFilename(), win_filename_local);
+#else
+ ASSERT_EQ(URI("file:///tmp/uri.svg").toNativeFilename(), "/tmp/uri.svg");
+ ASSERT_EQ(URI("file:///tmp/x%20y.svg").toNativeFilename(), "/tmp/x y.svg");
+ ASSERT_EQ(URI("file:///a/b#hash").toNativeFilename(), "/a/b");
+#endif
+
+ ASSERT_ANY_THROW(URI("http://a/b").toNativeFilename());
+}
+
+TEST(UriTest, StrDataScheme)
+{
+ ASSERT_EQ(URI("data:,text").str(), "data:,text");
+ ASSERT_EQ(URI("data:,white%20space").str(), "data:,white%20space");
+ ASSERT_EQ(URI("data:,umlaut-%C3%96").str(), "data:,umlaut-%C3%96");
+ ASSERT_EQ(URI(DATA_BASE64_HELLO_WORLD).str(), DATA_BASE64_HELLO_WORLD);
+}
+
+TEST(UriTest, Escape)
+{
+ ASSERT_EQ(URI("data:,white space").str(), "data:,white%20space");
+ ASSERT_EQ(URI("data:,white\nspace").str(), "data:,white%0Aspace");
+ ASSERT_EQ(URI("data:,umlaut-\xC3\x96").str(), "data:,umlaut-%C3%96");
+}
+
+TEST(UriTest, GetContents)
+{
+ ASSERT_EQ(URI("data:,white space").getContents(), "white space");
+ ASSERT_EQ(URI("data:,white%20space").getContents(), "white space");
+ ASSERT_EQ(URI("data:,white\nspace").getContents(), "white\nspace");
+ ASSERT_EQ(URI("data:,white%0Aspace").getContents(), "white\nspace");
+ ASSERT_EQ(URI("data:,umlaut-%C3%96").getContents(), "umlaut-\xC3\x96");
+ ASSERT_EQ(URI(DATA_BASE64_HELLO_WORLD).getContents(), "Hello World");
+ ASSERT_EQ(URI(DATA_BASE64_HELLO_WORLD_WRAPPED).getContents(), "Hello World");
+
+ ASSERT_ANY_THROW(URI().getContents());
+}
+
+TEST(UriTest, CssStr)
+{
+ ASSERT_EQ(URI("file:///tmp/uri.svg").cssStr(), "url(file:///tmp/uri.svg)");
+ ASSERT_EQ(URI("uri.svg").cssStr(), "url(uri.svg)");
+}
+
+TEST(UriTest, GetMimeType)
+{
+ ASSERT_EQ(URI("data:image/png;base64,").getMimeType(), "image/png");
+ ASSERT_EQ(URI("data:text/plain,xxx").getMimeType(), "text/plain");
+ ASSERT_EQ(URI("file:///tmp/uri.png").getMimeType(), "image/png");
+ ASSERT_EQ(URI("uri.png").getMimeType(), "image/png");
+ ASSERT_EQ(URI("uri.svg").getMimeType(), "image/svg+xml");
+
+ // can be "text/plain" or "text/*"
+ ASSERT_EQ(URI("file:///tmp/uri.txt").getMimeType().substr(0, 5), "text/");
+}
+
+TEST(UriTest, HasScheme)
+{
+ ASSERT_FALSE(URI().hasScheme("file"));
+ ASSERT_FALSE(URI("uri.svg").hasScheme("file"));
+ ASSERT_FALSE(URI("uri.svg").hasScheme("data"));
+
+ ASSERT_TRUE(URI("file:///uri.svg").hasScheme("file"));
+ ASSERT_TRUE(URI("FILE:///uri.svg").hasScheme("file"));
+ ASSERT_FALSE(URI("file:///uri.svg").hasScheme("data"));
+
+ ASSERT_TRUE(URI("data:,").hasScheme("data"));
+ ASSERT_TRUE(URI("DaTa:,").hasScheme("data"));
+ ASSERT_FALSE(URI("data:,").hasScheme("file"));
+
+ ASSERT_TRUE(URI("http://web/").hasScheme("http"));
+ ASSERT_FALSE(URI("http://web/").hasScheme("file"));
+
+ ASSERT_TRUE(URI::from_href_and_basedir("data:,white\nspace", "/tmp").hasScheme("data"));
+}
+
+TEST(UriTest, isOpaque)
+{
+ ASSERT_FALSE(URI().isOpaque());
+ ASSERT_FALSE(URI("file:///uri.svg").isOpaque());
+ ASSERT_FALSE(URI("/uri.svg").isOpaque());
+ ASSERT_FALSE(URI("uri.svg").isOpaque());
+ ASSERT_FALSE(URI("foo://bar/baz").isOpaque());
+ ASSERT_FALSE(URI("foo://bar").isOpaque());
+ ASSERT_FALSE(URI("foo:/bar").isOpaque());
+
+ ASSERT_TRUE(URI("foo:bar").isOpaque());
+ ASSERT_TRUE(URI("mailto:user@host.xy").isOpaque());
+ ASSERT_TRUE(URI("news:comp.lang.java").isOpaque());
+}
+
+TEST(UriTest, isRelative)
+{
+ ASSERT_TRUE(URI().isRelative());
+
+ ASSERT_FALSE(URI("http://web/uri.svg").isRelative());
+ ASSERT_FALSE(URI("file:///uri.svg").isRelative());
+ ASSERT_FALSE(URI("mailto:user@host.xy").isRelative());
+ ASSERT_FALSE(URI("data:,").isRelative());
+
+ ASSERT_TRUE(URI("//web/uri.svg").isRelative());
+ ASSERT_TRUE(URI("/uri.svg").isRelative());
+ ASSERT_TRUE(URI("uri.svg").isRelative());
+ ASSERT_TRUE(URI("./uri.svg").isRelative());
+ ASSERT_TRUE(URI("../uri.svg").isRelative());
+}
+
+TEST(UriTest, isNetPath)
+{
+ ASSERT_FALSE(URI().isNetPath());
+ ASSERT_FALSE(URI("http://web/uri.svg").isNetPath());
+ ASSERT_FALSE(URI("file:///uri.svg").isNetPath());
+ ASSERT_FALSE(URI("/uri.svg").isNetPath());
+ ASSERT_FALSE(URI("uri.svg").isNetPath());
+
+ ASSERT_TRUE(URI("//web/uri.svg").isNetPath());
+}
+
+TEST(UriTest, isRelativePath)
+{
+ ASSERT_FALSE(URI("foo:bar").isRelativePath());
+ ASSERT_TRUE(URI("foo%3Abar").isRelativePath());
+
+ ASSERT_FALSE(URI("http://web/uri.svg").isRelativePath());
+ ASSERT_FALSE(URI("//web/uri.svg").isRelativePath());
+ ASSERT_FALSE(URI("/uri.svg").isRelativePath());
+
+ ASSERT_TRUE(URI("uri.svg").isRelativePath());
+ ASSERT_TRUE(URI("./uri.svg").isRelativePath());
+ ASSERT_TRUE(URI("../uri.svg").isRelativePath());
+}
+
+TEST(UriTest, isAbsolutePath)
+{
+ ASSERT_FALSE(URI().isAbsolutePath());
+ ASSERT_FALSE(URI("http://web/uri.svg").isAbsolutePath());
+ ASSERT_FALSE(URI("//web/uri.svg").isAbsolutePath());
+ ASSERT_FALSE(URI("uri.svg").isAbsolutePath());
+ ASSERT_FALSE(URI("../uri.svg").isAbsolutePath());
+
+ ASSERT_TRUE(URI("/uri.svg").isAbsolutePath());
+}
+
+TEST(UriTest, getScheme)
+{
+ ASSERT_STREQ(URI().getScheme(), nullptr);
+
+ ASSERT_STREQ(URI("https://web/uri.svg").getScheme(), "https");
+ ASSERT_STREQ(URI("file:///uri.svg").getScheme(), "file");
+ ASSERT_STREQ(URI("data:,").getScheme(), "data");
+
+ ASSERT_STREQ(URI("data").getScheme(), nullptr);
+}
+
+TEST(UriTest, getQuery)
+{
+ ASSERT_STREQ(URI().getQuery(), nullptr);
+ ASSERT_STREQ(URI("uri.svg?a=b&c=d").getQuery(), "a=b&c=d");
+ ASSERT_STREQ(URI("?a=b&c=d#hash").getQuery(), "a=b&c=d");
+}
+
+TEST(UriTest, getFragment)
+{
+ ASSERT_STREQ(URI().getFragment(), nullptr);
+ ASSERT_STREQ(URI("uri.svg").getFragment(), nullptr);
+ ASSERT_STREQ(URI("uri.svg#hash").getFragment(), "hash");
+ ASSERT_STREQ(URI("?a=b&c=d#hash").getFragment(), "hash");
+ ASSERT_STREQ(URI("urn:isbn:096139210x#hash").getFragment(), "hash");
+}
+
+TEST(UriTest, getOpaque)
+{
+ ASSERT_STREQ(URI().getOpaque(), nullptr);
+ ASSERT_STREQ(URI("urn:isbn:096139210x#hash").getOpaque(), "isbn:096139210x");
+ ASSERT_STREQ(URI("data:,foo").getOpaque(), ",foo");
+}
+
+TEST(UriTest, from_native_filename)
+{
+#ifdef _WIN32
+ ASSERT_EQ(URI::from_native_filename(win_filename_local).str(), win_url_local);
+#else
+ ASSERT_EQ(URI::from_native_filename("/tmp/uri.svg").str(), "file:///tmp/uri.svg");
+ ASSERT_EQ(URI::from_native_filename("/tmp/x y.svg").str(), "file:///tmp/x%20y.svg");
+#endif
+}
+
+TEST(UriTest, uri_to_iri)
+{
+ // unescape UTF-8 (U+00D6)
+ ASSERT_EQ(Inkscape::uri_to_iri("data:,umlaut-%C3%96"), "data:,umlaut-\xC3\x96");
+ // don't unescape ASCII (U+003A)
+ ASSERT_EQ(Inkscape::uri_to_iri("foo%3Abar"), "foo%3Abar");
+ // sequence (U+00D6 U+1F37A U+003A)
+ ASSERT_EQ(Inkscape::uri_to_iri("%C3%96%F0%9F%8D%BA%3A"), "\xC3\x96\xF0\x9F\x8D\xBA%3A");
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/testfiles/unittest.cpp b/testfiles/unittest.cpp
new file mode 100644
index 0000000..5002466
--- /dev/null
+++ b/testfiles/unittest.cpp
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Unit test main.
+ *
+ * Author:
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2015 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "gtest/gtest.h"
+
+#include <gtkmm.h>
+
+#include "inkgc/gc-core.h"
+#include "inkscape.h"
+
+int main(int argc, char **argv) {
+
+ // setup general environment
+#if !GLIB_CHECK_VERSION(2,36,0)
+ g_type_init();
+#endif
+
+ // If possible, unit tests shouldn't require a GUI session
+ // since this won't generally be available in auto-builders
+ // int tmpArgc = 1;
+ // char const *tmp[] = {"foo", ""};
+ // char **tmpArgv = const_cast<char **>(tmp);
+ // Gtk::Main(tmpArgc, tmpArgv);
+
+ Inkscape::GC::init();
+
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :