diff --git a/src/extension/internal/cairo-renderer-pdf-out.cpp b/src/extension/internal/cairo-renderer-pdf-out.cpp index fd21241818b8e7289c51d14391acd67b9ab01e67..be5249f10f27dec097fae0446cc88ac65148328f 100644 --- a/src/extension/internal/cairo-renderer-pdf-out.cpp +++ b/src/extension/internal/cairo-renderer-pdf-out.cpp @@ -154,7 +154,7 @@ pdf_render_document_to_file(SPDocument *doc, gchar const *filename, unsigned int } // Render the page into the context in the new location. - renderer->renderItem(ctx, child); + renderer->renderItem(ctx, child, nullptr, page); ctx->popState(); } ret = ctx->finishPage(); diff --git a/src/extension/internal/cairo-renderer.cpp b/src/extension/internal/cairo-renderer.cpp index fde2e0e1465cdc1d39b7c99d565aae3a66481857..e06d3887a8a04f066e2fc9d0d9dc52688439a8e2 100644 --- a/src/extension/internal/cairo-renderer.cpp +++ b/src/extension/internal/cairo-renderer.cpp @@ -75,6 +75,7 @@ #include "object/sp-linear-gradient.h" #include "object/sp-marker.h" #include "object/sp-mask.h" +#include "object/sp-page.h" #include "object/sp-pattern.h" #include "object/sp-radial-gradient.h" #include "object/sp-root.h" @@ -137,7 +138,7 @@ Here comes the rendering part which could be put into the 'render' methods of SP */ /* The below functions are copy&pasted plus slightly modified from *_invoke_print functions. */ -static void sp_item_invoke_render(SPItem *item, CairoRenderContext *ctx, SPItem *origin = nullptr); +static void sp_item_invoke_render(SPItem *item, CairoRenderContext *ctx, SPItem *origin = nullptr, SPPage *page = nullptr); static void sp_group_render(SPGroup *group, CairoRenderContext *ctx); static void sp_anchor_render(SPAnchor *a, CairoRenderContext *ctx); static void sp_use_render(SPUse *use, CairoRenderContext *ctx); @@ -146,7 +147,7 @@ static void sp_text_render(SPText *text, CairoRenderContext *ctx); static void sp_flowtext_render(SPFlowtext *flowtext, CairoRenderContext *ctx); static void sp_image_render(SPImage *image, CairoRenderContext *ctx); static void sp_symbol_render(SPSymbol *symbol, CairoRenderContext *ctx); -static void sp_asbitmap_render(SPItem *item, CairoRenderContext *ctx); +static void sp_asbitmap_render(SPItem *item, CairoRenderContext *ctx, SPPage *page = nullptr); static void sp_shape_render_invoke_marker_rendering(SPMarker* marker, Geom::Affine tr, SPStyle* style, CairoRenderContext *ctx, SPItem *origin) { @@ -514,7 +515,7 @@ static void sp_root_render(SPRoot *root, CairoRenderContext *ctx) This function converts the item to a raster image and includes the image into the cairo renderer. It is only used for filters and then only when rendering filters as bitmaps is requested. */ -static void sp_asbitmap_render(SPItem *item, CairoRenderContext *ctx) +static void sp_asbitmap_render(SPItem *item, CairoRenderContext *ctx, SPPage *page) { // The code was adapted from sp_selection_create_bitmap_copy in selection-chemistry.cpp @@ -529,18 +530,12 @@ static void sp_asbitmap_render(SPItem *item, CairoRenderContext *ctx) } TRACE(("sp_asbitmap_render: resolution: %f\n", res )); - // Get the bounding box of the selection in desktop coordinates. + // Get the bounding box of the selection in document coordinates. Geom::OptRect bbox = item->documentVisualBounds(); - // no bbox, e.g. empty group - if (!bbox) { - return; - } - - Geom::Rect docrect(Geom::Rect(Geom::Point(0, 0), item->document->getDimensions())); - bbox &= docrect; + bbox &= (page ? page->getDocumentRect() : item->document->getViewBox()); - // no bbox, e.g. empty group + // no bbox, e.g. empty group or item not overlapping its page if (!bbox) { return; } @@ -591,7 +586,7 @@ static void sp_asbitmap_render(SPItem *item, CairoRenderContext *ctx) } -static void sp_item_invoke_render(SPItem *item, CairoRenderContext *ctx, SPItem *origin) +static void sp_item_invoke_render(SPItem *item, CairoRenderContext *ctx, SPItem *origin, SPPage *page) { // Check item's visibility if (item->isHidden()) { @@ -610,7 +605,7 @@ static void sp_item_invoke_render(SPItem *item, CairoRenderContext *ctx, SPItem // TODO: might apply to some degree to masks with filtered elements as well; // we need to figure out where in the stack it would be safe to rasterize if (ctx->getFilterToBitmap() && item->style->filter.set && !item->isInClipPath()) { - return sp_asbitmap_render(item, ctx); + return sp_asbitmap_render(item, ctx, page); } SPRoot *root = dynamic_cast(item); @@ -691,7 +686,7 @@ CairoRenderer::setStateForItem(CairoRenderContext *ctx, SPItem const *item) } // TODO change this to accept a const SPItem: -void CairoRenderer::renderItem(CairoRenderContext *ctx, SPItem *item, SPItem *origin) +void CairoRenderer::renderItem(CairoRenderContext *ctx, SPItem *item, SPItem *origin, SPPage *page) { ctx->pushState(); setStateForItem(ctx, item); @@ -711,7 +706,7 @@ void CairoRenderer::renderItem(CairoRenderContext *ctx, SPItem *item, SPItem *or ctx->pushLayer(); } ctx->transform(item->transform); - sp_item_invoke_render(item, ctx, origin); + sp_item_invoke_render(item, ctx, origin, page); if (state->need_layer) { if (blend) { diff --git a/src/extension/internal/cairo-renderer.h b/src/extension/internal/cairo-renderer.h index 258df7fb24c9eb8f49ee7727abf3fde1d4d4f6e7..16ca9df32d08b82e7d61c9a6b7da8233b5d7d47f 100644 --- a/src/extension/internal/cairo-renderer.h +++ b/src/extension/internal/cairo-renderer.h @@ -26,6 +26,7 @@ class SPItem; class SPClipPath; class SPMask; class SPHatchPath; +class SPPage; namespace Inkscape { namespace Extension { @@ -53,7 +54,7 @@ public: bool setupDocument(CairoRenderContext *ctx, SPDocument *doc, bool pageBoundingBox, double bleedmargin_px, SPItem *base); /** Traverses the object tree and invokes the render methods. */ - void renderItem(CairoRenderContext *ctx, SPItem *item, SPItem *clone = nullptr); + void renderItem(CairoRenderContext *ctx, SPItem *item, SPItem *clone = nullptr, SPPage *page = nullptr); void renderHatchPath(CairoRenderContext *ctx, SPHatchPath const &hatchPath, unsigned key); private: diff --git a/testfiles/cli_tests/CMakeLists.txt b/testfiles/cli_tests/CMakeLists.txt index e40a982efc6aad99b0cd4fd4061641a2798a907e..4f8e470b6cc873665918e894554bdb53222f0021 100644 --- a/testfiles/cli_tests/CMakeLists.txt +++ b/testfiles/cli_tests/CMakeLists.txt @@ -8,6 +8,7 @@ # Command line options: # INPUT_FILENAME - name of input file (optional) # OUTPUT_FILENAME - name of output file (optional) +# OUTPUT_PAGE - index of page in multipage output (optional), starts from 0 # PARAMETERS - additional command line parameters to pass to Inkscape # # Pass/fail criteria: @@ -17,6 +18,7 @@ # 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' +# for multipage output, use OUTPUT_PAGE to specify a single page for comparison # 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 # @@ -24,7 +26,7 @@ # 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(oneValueArgs INPUT_FILENAME OUTPUT_FILENAME OUTPUT_PAGE PASS_FOR_OUTPUT FAIL_FOR_OUTPUT REFERENCE_FILENAME) set(multiValueArgs PARAMETERS EXPECTED_FILES TEST_SCRIPT ENVIRONMENT) cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) @@ -78,7 +80,8 @@ function(add_cli_test name) 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}") + "${ARG_OUTPUT_FILENAME}" "${ARG_OUTPUT_PAGE}" "${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() @@ -441,6 +444,11 @@ add_cli_test(export-with-filters_png PARAMETERS --export-type=png INPUT_FILENAM add_cli_test(export-with-filters_ps PARAMETERS --export-type=ps INPUT_FILENAME offset.svg OUTPUT_FILENAME export-with-filters.ps REFERENCE_FILENAME export-with-filters_expected.ps ) add_cli_test(export-with-filters_eps PARAMETERS --export-type=eps INPUT_FILENAME offset.svg OUTPUT_FILENAME export-with-filters.eps REFERENCE_FILENAME export-with-filters_expected.eps) add_cli_test(export-with-filters_pdf PARAMETERS --export-type=pdf INPUT_FILENAME offset.svg OUTPUT_FILENAME export-with-filters.pdf REFERENCE_FILENAME export-with-filters_expected.pdf) +add_cli_test(export-with-filters-multipage PARAMETERS --export-type=pdf + INPUT_FILENAME export-with-filters-multipage.svg + OUTPUT_FILENAME export-with-filters-multipage.pdf + OUTPUT_PAGE 1 + REFERENCE_FILENAME export-with-filters-multipage_expected.png) # EMF, WMF: No support for exporting filters. Feature request: https://gitlab.com/inkscape/inbox/-/issues/2275 # add_cli_test(export-with-filters_emf PARAMETERS --export-type=emf INPUT_FILENAME offset.svg OUTPUT_FILENAME export-with-filters.emf REFERENCE_FILENAME export-with-filters_expected.emf) # add_cli_test(export-with-filters_wmf PARAMETERS --export-type=wmf INPUT_FILENAME offset.svg OUTPUT_FILENAME export-with-filters.wmf REFERENCE_FILENAME export-with-filters_expected.wmf) diff --git a/testfiles/cli_tests/check_output.sh b/testfiles/cli_tests/check_output.sh index 06da1fadbf4c90f5830ec8f417f0bf1eedcdf1d1..67ef176053db9c409a41c27cfd59c8e45affb6f5 100644 --- a/testfiles/cli_tests/check_output.sh +++ b/testfiles/cli_tests/check_output.sh @@ -5,9 +5,10 @@ command -v convert >/dev/null 2>&1 || { echo >&2 "I require ImageMagick's 'conve 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 +OUTPUT_PAGE=$2 +REFERENCE_FILENAME=$3 +EXPECTED_FILES=$4 +TEST_SCRIPT=$5 # check if expected files exist for file in ${EXPECTED_FILES}; do @@ -29,7 +30,14 @@ if [ -n "${REFERENCE_FILENAME}" ]; then # - 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 + + # extract a page from multipage PDF if requested and convert it to RGB + OUTFILE_SUFFIX="" + if [ -n "$OUTPUT_PAGE" ]; then + OUTFILE_SUFFIX="[${OUTPUT_PAGE}] -colorspace RGB" + fi + + if ! convert ${delegate1}${OUTPUT_FILENAME}${OUTFILE_SUFFIX} ${OUTPUT_FILENAME}.png; then echo "Warning: Failed to convert test file '${OUTPUT_FILENAME}' to PNG format. Skipping comparison test." exit 42 fi @@ -61,7 +69,7 @@ if [ -n "${TEST_SCRIPT}" ]; then interpreter=python3 ;; *) - interpreter=sh + interpreter=bash ;; esac diff --git a/testfiles/cli_tests/testcases/export-with-filters-multipage.svg b/testfiles/cli_tests/testcases/export-with-filters-multipage.svg new file mode 100644 index 0000000000000000000000000000000000000000..79e7eb459d952be3e22924a636f9c3d6bd8b09c1 --- /dev/null +++ b/testfiles/cli_tests/testcases/export-with-filters-multipage.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + This is a digit 2 placed on page 2 with a Gaussian blur filter applied to it. + As reported in https://gitlab.com/inkscape/inkscape/-/issues/3214, objects with filters + were ignored on PDF export when placed on second and subsequent pages. This test ensures + that the blurred digit 2 is exported correctly. + + + + diff --git a/testfiles/cli_tests/testcases/export-with-filters-multipage_expected.png b/testfiles/cli_tests/testcases/export-with-filters-multipage_expected.png new file mode 100644 index 0000000000000000000000000000000000000000..ffe1266498689fbad16354c84a20d5f52c078cf6 Binary files /dev/null and b/testfiles/cli_tests/testcases/export-with-filters-multipage_expected.png differ