diff --git a/Userland/Libraries/LibPDF/ColorSpace.cpp b/Userland/Libraries/LibPDF/ColorSpace.cpp index 251f83d0a9..46a54892d2 100644 --- a/Userland/Libraries/LibPDF/ColorSpace.cpp +++ b/Userland/Libraries/LibPDF/ColorSpace.cpp @@ -45,7 +45,7 @@ PDFErrorOr> ColorSpace::create(Document* document, Non return Error { Error::Type::MalformedPDF, "Color space must be name or array" }; } -PDFErrorOr> ColorSpace::create(DeprecatedFlyString const& name, Renderer&) +PDFErrorOr> ColorSpace::create(DeprecatedFlyString const& name, Renderer& renderer) { // Simple color spaces with no parameters, which can be specified directly if (name == CommonNames::DeviceGray) @@ -55,7 +55,7 @@ PDFErrorOr> ColorSpace::create(DeprecatedFlyString con if (name == CommonNames::DeviceCMYK) return DeviceCMYKColorSpace::the(); if (name == CommonNames::Pattern) - return Error::rendering_unsupported_error("Pattern color spaces not yet implemented"); + return PatternColorSpace::create(renderer); VERIFY_NOT_REACHED(); } @@ -86,9 +86,6 @@ PDFErrorOr> ColorSpace::create(Document* document, Non if (color_space_name == CommonNames::Lab) return TRY(LabColorSpace::create(document, move(parameters))); - if (color_space_name == CommonNames::Pattern) - return Error::rendering_unsupported_error("Pattern color spaces not yet implemented"); - if (color_space_name == CommonNames::Separation) return TRY(SeparationColorSpace::create(document, move(parameters), renderer)); @@ -773,4 +770,104 @@ Vector SeparationColorSpace::default_decode() const { return { 0.0f, 1.0f }; } +NonnullRefPtr PatternColorSpace::create(Renderer& renderer) +{ + return adopt_ref(*new PatternColorSpace(renderer)); +} + +PDFErrorOr PatternColorSpace::style(ReadonlySpan arguments) const +{ + VERIFY(arguments.size() >= 1); + + auto resources = m_renderer.m_page.resources; + + auto pattern_resource = resources->get_value(CommonNames::Pattern); + auto doc_pattern_dict = pattern_resource.get>()->cast(); + + auto const& pattern_name = arguments.last().get>()->cast()->name(); + if (!doc_pattern_dict->contains(pattern_name)) + return Error::malformed_error("Pattern dictionary does not contain pattern {}", pattern_name); + + auto const pattern = TRY(m_renderer.m_document->resolve_to(doc_pattern_dict->get_value(pattern_name))); + + auto const type = pattern->dict()->get(CommonNames::Type)->get>()->cast(); + if (type->name() != CommonNames::Pattern) + return Error::rendering_unsupported_error("Unsupported pattern type {}", type->name()); + + auto const pattern_type = pattern->dict()->get(CommonNames::PatternType)->get_u16(); + if (pattern_type != 1) + return Error::rendering_unsupported_error("Unsupported pattern type {}", pattern_type); + + auto const pattern_paint_type = pattern->dict()->get("PaintType")->get_u16(); + if (pattern_paint_type != 1) + return Error::rendering_unsupported_error("Unsupported pattern paint type {}", pattern_paint_type); + + Vector pattern_matrix; + if (pattern->dict()->contains(CommonNames::Matrix)) { + pattern_matrix = pattern->dict()->get_array(m_renderer.m_document, CommonNames::Matrix).value()->elements(); + } else { + pattern_matrix = Vector { Value { 1 }, Value { 0 }, Value { 0 }, Value { 1 }, Value { 0 }, Value { 0 } }; + } + + auto pattern_bounding_box = pattern->dict()->get_array(m_renderer.m_document, "BBox").value()->elements(); + auto pattern_transform = Gfx::AffineTransform( + pattern_matrix[0].to_float(), + pattern_matrix[1].to_float(), + pattern_matrix[2].to_float(), + pattern_matrix[3].to_float(), + pattern_matrix[4].to_float(), + pattern_matrix[5].to_float()); + + // To get the device space size for the bitmap, apply the pattern transform to the pattern space bounding box, and then apply the initial ctm. + // NB: the pattern pattern_matrix maps pattern space to the default (initial) coordinate space of the page. (i.e cannot be updated via cm). + + auto initial_ctm = Gfx::AffineTransform(m_renderer.m_graphics_state_stack.first().ctm); + initial_ctm.set_translation(0, 0); + initial_ctm.set_scale(initial_ctm.x_scale(), initial_ctm.y_scale()); + + auto pattern_space_lower_left = Gfx::FloatPoint { pattern_bounding_box[0].to_int(), pattern_bounding_box[1].to_int() }; + auto pattern_space_upper_right = Gfx::FloatPoint { pattern_bounding_box[2].to_int(), pattern_bounding_box[3].to_int() }; + + auto device_space_lower_left = initial_ctm.map(pattern_transform.map(pattern_space_lower_left)); + auto device_space_upper_right = initial_ctm.map(pattern_transform.map(pattern_space_upper_right)); + + auto bitmap_width = (int)device_space_upper_right.x() - (int)device_space_lower_left.x(); + auto bitmap_height = (int)device_space_upper_right.y() - (int)device_space_lower_left.y(); + + auto pattern_cell = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { bitmap_width, bitmap_height })); + auto page = Page(m_renderer.m_page); + page.media_box = Rectangle { + pattern_space_lower_left.x(), pattern_space_lower_left.y(), + pattern_space_upper_right.x(), pattern_space_upper_right.y() + }; + page.crop_box = page.media_box; + + auto pattern_renderer = Renderer(m_renderer.m_document, page, pattern_cell, {}, m_renderer.m_rendering_preferences); + + auto operators = TRY(Parser::parse_operators(m_renderer.m_document, pattern->bytes())); + for (auto& op : operators) + TRY(pattern_renderer.handle_operator(op, resources)); + + auto x_steps = pattern->dict()->get("XStep").value_or(bitmap_width).to_int(); + auto y_steps = pattern->dict()->get("YStep").value_or(bitmap_height).to_int(); + + auto device_space_steps = initial_ctm.map(pattern_transform.map(Gfx::IntPoint { x_steps, y_steps })); + + NonnullRefPtr style = MUST(Gfx::RepeatingBitmapPaintStyle::create( + *pattern_cell, + device_space_steps, + {})); + + return style; +} +int PatternColorSpace::number_of_components() const +{ + // Not permitted + VERIFY_NOT_REACHED(); +} +Vector PatternColorSpace::default_decode() const +{ + // Not permitted + VERIFY_NOT_REACHED(); +} } diff --git a/Userland/Libraries/LibPDF/ColorSpace.h b/Userland/Libraries/LibPDF/ColorSpace.h index cb4bf1da66..4ba6dea891 100644 --- a/Userland/Libraries/LibPDF/ColorSpace.h +++ b/Userland/Libraries/LibPDF/ColorSpace.h @@ -252,4 +252,24 @@ private: NonnullRefPtr m_tint_transform; Vector mutable m_tint_output_values; }; + +class PatternColorSpace final : public ColorSpace { +public: + static NonnullRefPtr create(Renderer& renderer); + ~PatternColorSpace() override = default; + + PDFErrorOr style(ReadonlySpan arguments) const override; + int number_of_components() const override; + Vector default_decode() const override; + ColorSpaceFamily const& family() const override { return ColorSpaceFamily::Pattern; } + +private: + PatternColorSpace(Renderer& renderer) + : m_renderer(renderer) + { + } + + Renderer& m_renderer; +}; + } diff --git a/Userland/Libraries/LibPDF/CommonNames.h b/Userland/Libraries/LibPDF/CommonNames.h index 65b2b6f2bb..326a75c16b 100644 --- a/Userland/Libraries/LibPDF/CommonNames.h +++ b/Userland/Libraries/LibPDF/CommonNames.h @@ -138,8 +138,10 @@ X(Outlines) \ X(P) \ X(Pages) \ + X(PaintType) \ X(Parent) \ X(Pattern) \ + X(PatternType) \ X(Perms) \ X(Predictor) \ X(Prev) \ diff --git a/Userland/Libraries/LibPDF/Renderer.cpp b/Userland/Libraries/LibPDF/Renderer.cpp index 0ebc9ebdd2..13dbff0bc7 100644 --- a/Userland/Libraries/LibPDF/Renderer.cpp +++ b/Userland/Libraries/LibPDF/Renderer.cpp @@ -616,14 +616,8 @@ RENDERER_HANDLER(set_stroking_color) RENDERER_HANDLER(set_stroking_color_extended) { - // FIXME: Handle Pattern color spaces - auto last_arg = args.last(); - if (last_arg.has>() && last_arg.get>()->is()) { - dbgln("pattern space {}", last_arg.get>()->cast()->name()); - return Error::rendering_unsupported_error("Pattern color spaces not yet implemented"); - } - - state().stroke_style = TRY(state().stroke_color_space->style(args)); + // FIXME: Pattern color spaces might need extra resources + state().paint_style = TRY(state().paint_color_space->style(args)); return {}; } @@ -635,13 +629,7 @@ RENDERER_HANDLER(set_painting_color) RENDERER_HANDLER(set_painting_color_extended) { - // FIXME: Handle Pattern color spaces - auto last_arg = args.last(); - if (last_arg.has>() && last_arg.get>()->is()) { - dbgln("pattern space {}", last_arg.get>()->cast()->name()); - return Error::rendering_unsupported_error("Pattern color spaces not yet implemented"); - } - + // FIXME: Pattern color spaces might need extra resources state().paint_style = TRY(state().paint_color_space->style(args)); return {}; } diff --git a/Userland/Libraries/LibPDF/Renderer.h b/Userland/Libraries/LibPDF/Renderer.h index 2f302fe5d5..af0d64a4af 100644 --- a/Userland/Libraries/LibPDF/Renderer.h +++ b/Userland/Libraries/LibPDF/Renderer.h @@ -97,6 +97,8 @@ struct RenderingPreferences { }; class Renderer { + friend class PatternColorSpace; + public: static PDFErrorsOr render(Document&, Page const&, RefPtr, Color background_color, RenderingPreferences preferences);