From 082a4197b62d18fb0b472fba6db4bf6b4f906f88 Mon Sep 17 00:00:00 2001 From: Kyle Pereira Date: Tue, 5 Dec 2023 12:51:42 +0000 Subject: [PATCH] LibPDF: Use Variant instead of Color for ColorSpaces This is in anticipation of Pattern color space support which does not yield a simple color. --- Userland/Libraries/LibPDF/ColorSpace.cpp | 27 ++++---- Userland/Libraries/LibPDF/ColorSpace.h | 27 ++++---- .../Libraries/LibPDF/Fonts/TrueTypeFont.cpp | 13 +++- Userland/Libraries/LibPDF/Fonts/Type1Font.cpp | 26 ++++++-- Userland/Libraries/LibPDF/Renderer.cpp | 62 ++++++++++++++----- Userland/Libraries/LibPDF/Renderer.h | 16 +++-- 6 files changed, 116 insertions(+), 55 deletions(-) diff --git a/Userland/Libraries/LibPDF/ColorSpace.cpp b/Userland/Libraries/LibPDF/ColorSpace.cpp index 3e576d7054..d2d18b426d 100644 --- a/Userland/Libraries/LibPDF/ColorSpace.cpp +++ b/Userland/Libraries/LibPDF/ColorSpace.cpp @@ -101,7 +101,7 @@ NonnullRefPtr DeviceGrayColorSpace::the() return instance; } -PDFErrorOr DeviceGrayColorSpace::color(ReadonlySpan arguments) const +PDFErrorOr DeviceGrayColorSpace::style(ReadonlySpan arguments) const { VERIFY(arguments.size() == 1); auto gray = static_cast(arguments[0].to_float() * 255.0f); @@ -119,7 +119,7 @@ NonnullRefPtr DeviceRGBColorSpace::the() return instance; } -PDFErrorOr DeviceRGBColorSpace::color(ReadonlySpan arguments) const +PDFErrorOr DeviceRGBColorSpace::style(ReadonlySpan arguments) const { VERIFY(arguments.size() == 3); auto r = static_cast(arguments[0].to_float() * 255.0f); @@ -139,7 +139,7 @@ NonnullRefPtr DeviceCMYKColorSpace::the() return instance; } -PDFErrorOr DeviceCMYKColorSpace::color(ReadonlySpan arguments) const +PDFErrorOr DeviceCMYKColorSpace::style(ReadonlySpan arguments) const { VERIFY(arguments.size() == 4); auto c = arguments[0].to_float(); @@ -197,7 +197,7 @@ DeviceNColorSpace::DeviceNColorSpace(NonnullRefPtr alternate_space, { } -PDFErrorOr DeviceNColorSpace::color(ReadonlySpan arguments) const +PDFErrorOr DeviceNColorSpace::style(ReadonlySpan arguments) const { // FIXME: Does this need handling for the special colorant name "None"? // FIXME: When drawing to a printer, do something else. @@ -211,7 +211,7 @@ PDFErrorOr DeviceNColorSpace::color(ReadonlySpan arguments) const for (size_t i = 0; i < tint_output.size(); ++i) m_tint_output_values[i] = tint_output[i]; - return m_alternate_space->color(m_tint_output_values); + return m_alternate_space->style(m_tint_output_values); } int DeviceNColorSpace::number_of_components() const @@ -347,7 +347,7 @@ PDFErrorOr> CalGrayColorSpace::create(Document* return color_space; } -PDFErrorOr CalGrayColorSpace::color(ReadonlySpan arguments) const +PDFErrorOr CalGrayColorSpace::style(ReadonlySpan arguments) const { VERIFY(arguments.size() == 1); auto a = clamp(arguments[0].to_float(), 0.0f, 1.0f); @@ -433,7 +433,7 @@ PDFErrorOr> CalRGBColorSpace::create(Document* d return color_space; } -PDFErrorOr CalRGBColorSpace::color(ReadonlySpan arguments) const +PDFErrorOr CalRGBColorSpace::style(ReadonlySpan arguments) const { VERIFY(arguments.size() == 3); auto a = clamp(arguments[0].to_float(), 0.0f, 1.0f); @@ -493,7 +493,7 @@ ICCBasedColorSpace::ICCBasedColorSpace(NonnullRefPtr profile) { } -PDFErrorOr ICCBasedColorSpace::color(ReadonlySpan arguments) const +PDFErrorOr ICCBasedColorSpace::style(ReadonlySpan arguments) const { if (!s_srgb_profile) s_srgb_profile = TRY(Gfx::ICC::sRGB()); @@ -593,7 +593,7 @@ PDFErrorOr> LabColorSpace::create(Document* documen return color_space; } -PDFErrorOr LabColorSpace::color(ReadonlySpan arguments) const +PDFErrorOr LabColorSpace::style(ReadonlySpan arguments) const { VERIFY(arguments.size() == 3); auto L_star = clamp(arguments[0].to_float(), 0.0f, 100.0f); @@ -692,7 +692,7 @@ IndexedColorSpace::IndexedColorSpace(NonnullRefPtr base) { } -PDFErrorOr IndexedColorSpace::color(ReadonlySpan arguments) const +PDFErrorOr IndexedColorSpace::style(ReadonlySpan arguments) const { VERIFY(arguments.size() == 1); @@ -705,7 +705,7 @@ PDFErrorOr IndexedColorSpace::color(ReadonlySpan arguments) const for (size_t i = 0; i < n; ++i) TRY(components.try_append(Value(m_lookup[index * n + i] / 255.0f))); - return m_base->color(components); + return m_base->style(components); } Vector IndexedColorSpace::default_decode() const @@ -748,7 +748,7 @@ SeparationColorSpace::SeparationColorSpace(NonnullRefPtr alternate_s { } -PDFErrorOr SeparationColorSpace::color(ReadonlySpan arguments) const +PDFErrorOr SeparationColorSpace::style(ReadonlySpan arguments) const { // "For an additive device such as a computer display, a Separation color space never applies a process colorant directly; // it always reverts to the alternate color space as described below." @@ -765,12 +765,11 @@ PDFErrorOr SeparationColorSpace::color(ReadonlySpan arguments) con for (size_t i = 0; i < tint_output.size(); ++i) m_tint_output_values[i] = tint_output[i]; - return m_alternate_space->color(m_tint_output_values); + return m_alternate_space->style(m_tint_output_values); } Vector SeparationColorSpace::default_decode() const { return { 0.0f, 1.0f }; } - } diff --git a/Userland/Libraries/LibPDF/ColorSpace.h b/Userland/Libraries/LibPDF/ColorSpace.h index 6efe6b1fc5..1e300108f0 100644 --- a/Userland/Libraries/LibPDF/ColorSpace.h +++ b/Userland/Libraries/LibPDF/ColorSpace.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -28,6 +29,9 @@ namespace PDF { +typedef Variant> ColorOrStyle; +class Renderer; + class ColorSpaceFamily { public: ColorSpaceFamily(DeprecatedFlyString name, bool may_be_specified_directly) @@ -62,7 +66,7 @@ public: virtual ~ColorSpace() = default; - virtual PDFErrorOr color(ReadonlySpan arguments) const = 0; + virtual PDFErrorOr style(ReadonlySpan arguments) const = 0; virtual int number_of_components() const = 0; virtual Vector default_decode() const = 0; // "TABLE 4.40 Default Decode arrays" virtual ColorSpaceFamily const& family() const = 0; @@ -74,7 +78,7 @@ public: ~DeviceGrayColorSpace() override = default; - PDFErrorOr color(ReadonlySpan arguments) const override; + PDFErrorOr style(ReadonlySpan arguments) const override; int number_of_components() const override { return 1; } Vector default_decode() const override; ColorSpaceFamily const& family() const override { return ColorSpaceFamily::DeviceGray; } @@ -89,7 +93,7 @@ public: ~DeviceRGBColorSpace() override = default; - PDFErrorOr color(ReadonlySpan arguments) const override; + PDFErrorOr style(ReadonlySpan arguments) const override; int number_of_components() const override { return 3; } Vector default_decode() const override; ColorSpaceFamily const& family() const override { return ColorSpaceFamily::DeviceRGB; } @@ -104,7 +108,7 @@ public: ~DeviceCMYKColorSpace() override = default; - PDFErrorOr color(ReadonlySpan arguments) const override; + PDFErrorOr style(ReadonlySpan arguments) const override; int number_of_components() const override { return 4; } Vector default_decode() const override; ColorSpaceFamily const& family() const override { return ColorSpaceFamily::DeviceCMYK; } @@ -119,7 +123,7 @@ public: ~DeviceNColorSpace() override = default; - PDFErrorOr color(ReadonlySpan arguments) const override; + PDFErrorOr style(ReadonlySpan arguments) const override; int number_of_components() const override; Vector default_decode() const override; ColorSpaceFamily const& family() const override { return ColorSpaceFamily::DeviceN; } @@ -140,7 +144,7 @@ public: ~CalGrayColorSpace() override = default; - PDFErrorOr color(ReadonlySpan arguments) const override; + PDFErrorOr style(ReadonlySpan arguments) const override; int number_of_components() const override { return 1; } Vector default_decode() const override; ColorSpaceFamily const& family() const override { return ColorSpaceFamily::CalGray; } @@ -159,7 +163,7 @@ public: ~CalRGBColorSpace() override = default; - PDFErrorOr color(ReadonlySpan arguments) const override; + PDFErrorOr style(ReadonlySpan arguments) const override; int number_of_components() const override { return 3; } Vector default_decode() const override; ColorSpaceFamily const& family() const override { return ColorSpaceFamily::CalRGB; } @@ -179,7 +183,7 @@ public: ~ICCBasedColorSpace() override = default; - PDFErrorOr color(ReadonlySpan arguments) const override; + PDFErrorOr style(ReadonlySpan arguments) const override; int number_of_components() const override; Vector default_decode() const override; ColorSpaceFamily const& family() const override { return ColorSpaceFamily::ICCBased; } @@ -197,7 +201,7 @@ public: ~LabColorSpace() override = default; - PDFErrorOr color(ReadonlySpan arguments) const override; + PDFErrorOr style(ReadonlySpan arguments) const override; int number_of_components() const override { return 3; } Vector default_decode() const override; ColorSpaceFamily const& family() const override { return ColorSpaceFamily::Lab; } @@ -216,7 +220,7 @@ public: ~IndexedColorSpace() override = default; - PDFErrorOr color(ReadonlySpan arguments) const override; + PDFErrorOr style(ReadonlySpan arguments) const override; int number_of_components() const override { return 1; } Vector default_decode() const override; ColorSpaceFamily const& family() const override { return ColorSpaceFamily::Indexed; } @@ -235,7 +239,7 @@ public: ~SeparationColorSpace() override = default; - PDFErrorOr color(ReadonlySpan arguments) const override; + PDFErrorOr style(ReadonlySpan arguments) const override; int number_of_components() const override { return 1; } Vector default_decode() const override; ColorSpaceFamily const& family() const override { return ColorSpaceFamily::Separation; } @@ -248,5 +252,4 @@ private: NonnullRefPtr m_tint_transform; Vector mutable m_tint_output_values; }; - } diff --git a/Userland/Libraries/LibPDF/Fonts/TrueTypeFont.cpp b/Userland/Libraries/LibPDF/Fonts/TrueTypeFont.cpp index 9e0b36aca1..ef23b9f04d 100644 --- a/Userland/Libraries/LibPDF/Fonts/TrueTypeFont.cpp +++ b/Userland/Libraries/LibPDF/Fonts/TrueTypeFont.cpp @@ -48,13 +48,20 @@ void TrueTypeFont::set_font_size(float font_size) m_font = m_font->with_size((font_size * POINTS_PER_INCH) / DEFAULT_DPI); } -PDFErrorOr TrueTypeFont::draw_glyph(Gfx::Painter& painter, Gfx::FloatPoint point, float, u8 char_code, Renderer const& renderer) +PDFErrorOr TrueTypeFont::draw_glyph(Gfx::Painter& painter, Gfx::FloatPoint point, float width, u8 char_code, Renderer const& renderer) { - auto color = renderer.state().paint_color; + auto style = renderer.state().paint_style; // Account for the reversed font baseline auto position = point.translated(0, -m_font->baseline()); - painter.draw_glyph(position, char_code, *m_font, color); + if (style.has()) { + painter.draw_glyph(position, char_code, *m_font, style.get()); + } else { + // FIXME: Bounding box and sample point look to be pretty wrong + style.get>()->paint(Gfx::IntRect(position.x(), position.y(), width, 0), [&](auto sample) { + painter.draw_glyph(position, char_code, *m_font, sample(Gfx::IntPoint(position.x(), position.y()))); + }); + } return {}; } diff --git a/Userland/Libraries/LibPDF/Fonts/Type1Font.cpp b/Userland/Libraries/LibPDF/Fonts/Type1Font.cpp index bd67c6ec12..01bfb2ca5c 100644 --- a/Userland/Libraries/LibPDF/Fonts/Type1Font.cpp +++ b/Userland/Libraries/LibPDF/Fonts/Type1Font.cpp @@ -67,12 +67,19 @@ void Type1Font::set_font_size(float font_size) PDFErrorOr Type1Font::draw_glyph(Gfx::Painter& painter, Gfx::FloatPoint point, float width, u8 char_code, Renderer const& renderer) { - auto color = renderer.state().paint_color; + auto style = renderer.state().paint_style; if (!m_font_program) { // Account for the reversed font baseline auto position = point.translated(0, -m_font->baseline()); - painter.draw_glyph(position, char_code, *m_font, color); + // FIXME: Bounding box and sample point look to be pretty wrong + if (style.has()) { + painter.draw_glyph(position, char_code, *m_font, style.get()); + } else { + style.get>()->paint(Gfx::IntRect(position.x(), position.y(), width, 0), [&](auto sample) { + painter.draw_glyph(position, char_code, *m_font, sample(Gfx::IntPoint(position.x(), position.y()))); + }); + } return {}; } @@ -97,9 +104,18 @@ PDFErrorOr Type1Font::draw_glyph(Gfx::Painter& painter, Gfx::FloatPoint po m_glyph_cache.set(index, bitmap); } - painter.blit_filtered(glyph_position.blit_position, *bitmap, bitmap->rect(), [color](Color pixel) -> Color { - return pixel.multiply(color); - }); + if (style.has()) { + painter.blit_filtered(glyph_position.blit_position, *bitmap, bitmap->rect(), [style](Color pixel) -> Color { + return pixel.multiply(style.get()); + }); + } else { + style.get>()->paint(bitmap->physical_rect(), [&](auto sample) { + painter.blit_filtered(glyph_position.blit_position, *bitmap, bitmap->rect(), [&](Color pixel) -> Color { + // FIXME: Presumably we need to sample at every point in the glyph, not just the top left? + return pixel.multiply(sample(glyph_position.blit_position)); + }); + }); + } return {}; } } diff --git a/Userland/Libraries/LibPDF/Renderer.cpp b/Userland/Libraries/LibPDF/Renderer.cpp index 152ee345c9..80618068e1 100644 --- a/Userland/Libraries/LibPDF/Renderer.cpp +++ b/Userland/Libraries/LibPDF/Renderer.cpp @@ -311,7 +311,11 @@ void Renderer::end_path_paint() RENDERER_HANDLER(path_stroke) { begin_path_paint(); - m_anti_aliasing_painter.stroke_path(m_current_path, state().stroke_color, state().ctm.x_scale() * state().line_width); + if (state().stroke_style.has>()) { + m_anti_aliasing_painter.stroke_path(m_current_path, state().stroke_style.get>(), state().ctm.x_scale() * state().line_width); + } else { + m_anti_aliasing_painter.stroke_path(m_current_path, state().stroke_style.get(), state().ctm.x_scale() * state().line_width); + } end_path_paint(); return {}; } @@ -327,7 +331,11 @@ RENDERER_HANDLER(path_fill_nonzero) { begin_path_paint(); m_current_path.close_all_subpaths(); - m_anti_aliasing_painter.fill_path(m_current_path, state().paint_color, Gfx::Painter::WindingRule::Nonzero); + if (state().paint_style.has>()) { + m_anti_aliasing_painter.fill_path(m_current_path, state().paint_style.get>(), 1.0, Gfx::Painter::WindingRule::Nonzero); + } else { + m_anti_aliasing_painter.fill_path(m_current_path, state().paint_style.get(), Gfx::Painter::WindingRule::Nonzero); + } end_path_paint(); return {}; } @@ -341,20 +349,32 @@ RENDERER_HANDLER(path_fill_evenodd) { begin_path_paint(); m_current_path.close_all_subpaths(); - m_anti_aliasing_painter.fill_path(m_current_path, state().paint_color, Gfx::Painter::WindingRule::EvenOdd); + if (state().paint_style.has>()) { + m_anti_aliasing_painter.fill_path(m_current_path, state().paint_style.get>(), 1.0, Gfx::Painter::WindingRule::EvenOdd); + } else { + m_anti_aliasing_painter.fill_path(m_current_path, state().paint_style.get(), Gfx::Painter::WindingRule::EvenOdd); + } end_path_paint(); return {}; } RENDERER_HANDLER(path_fill_stroke_nonzero) { - m_anti_aliasing_painter.stroke_path(m_current_path, state().stroke_color, state().ctm.x_scale() * state().line_width); + if (state().stroke_style.has>()) { + m_anti_aliasing_painter.stroke_path(m_current_path, state().stroke_style.get>(), state().ctm.x_scale() * state().line_width); + } else { + m_anti_aliasing_painter.stroke_path(m_current_path, state().stroke_style.get(), state().ctm.x_scale() * state().line_width); + } return handle_path_fill_nonzero(args); } RENDERER_HANDLER(path_fill_stroke_evenodd) { - m_anti_aliasing_painter.stroke_path(m_current_path, state().stroke_color, state().ctm.x_scale() * state().line_width); + if (state().stroke_style.has>()) { + m_anti_aliasing_painter.stroke_path(m_current_path, state().stroke_style.get>(), state().ctm.x_scale() * state().line_width); + } else { + m_anti_aliasing_painter.stroke_path(m_current_path, state().stroke_style.get(), state().ctm.x_scale() * state().line_width); + } return handle_path_fill_evenodd(args); } @@ -590,7 +610,7 @@ RENDERER_HANDLER(set_painting_space) RENDERER_HANDLER(set_stroking_color) { - state().stroke_color = TRY(state().stroke_color_space->color(args)); + state().stroke_style = TRY(state().stroke_color_space->style(args)); return {}; } @@ -603,13 +623,13 @@ RENDERER_HANDLER(set_stroking_color_extended) return Error::rendering_unsupported_error("Pattern color spaces not yet implemented"); } - state().stroke_color = TRY(state().stroke_color_space->color(args)); + state().stroke_style = TRY(state().stroke_color_space->style(args)); return {}; } RENDERER_HANDLER(set_painting_color) { - state().paint_color = TRY(state().paint_color_space->color(args)); + state().paint_style = TRY(state().paint_color_space->style(args)); return {}; } @@ -622,49 +642,49 @@ RENDERER_HANDLER(set_painting_color_extended) return Error::rendering_unsupported_error("Pattern color spaces not yet implemented"); } - state().paint_color = TRY(state().paint_color_space->color(args)); + state().paint_style = TRY(state().paint_color_space->style(args)); return {}; } RENDERER_HANDLER(set_stroking_color_and_space_to_gray) { state().stroke_color_space = DeviceGrayColorSpace::the(); - state().stroke_color = TRY(state().stroke_color_space->color(args)); + state().stroke_style = TRY(state().stroke_color_space->style(args)); return {}; } RENDERER_HANDLER(set_painting_color_and_space_to_gray) { state().paint_color_space = DeviceGrayColorSpace::the(); - state().paint_color = TRY(state().paint_color_space->color(args)); + state().paint_style = TRY(state().paint_color_space->style(args)); return {}; } RENDERER_HANDLER(set_stroking_color_and_space_to_rgb) { state().stroke_color_space = DeviceRGBColorSpace::the(); - state().stroke_color = TRY(state().stroke_color_space->color(args)); + state().stroke_style = TRY(state().stroke_color_space->style(args)); return {}; } RENDERER_HANDLER(set_painting_color_and_space_to_rgb) { state().paint_color_space = DeviceRGBColorSpace::the(); - state().paint_color = TRY(state().paint_color_space->color(args)); + state().paint_style = TRY(state().paint_color_space->style(args)); return {}; } RENDERER_HANDLER(set_stroking_color_and_space_to_cmyk) { state().stroke_color_space = DeviceCMYKColorSpace::the(); - state().stroke_color = TRY(state().stroke_color_space->color(args)); + state().stroke_style = TRY(state().stroke_color_space->style(args)); return {}; } RENDERER_HANDLER(set_painting_color_and_space_to_cmyk) { state().paint_color_space = DeviceCMYKColorSpace::the(); - state().paint_color = TRY(state().paint_color_space->color(args)); + state().paint_style = TRY(state().paint_color_space->style(args)); return {}; } @@ -956,8 +976,16 @@ PDFErrorOr> Renderer::load_image(NonnullRefPtrcolor(component_values)); - bitmap->set_pixel(x, y, color); + auto color = TRY(color_space->style(component_values)); + if (color.has()) { + auto c = color.get(); + bitmap->set_pixel(x, y, c); + } else { + auto paint_style = color.get>(); + paint_style->paint(bitmap->rect(), [&](auto sample) { + bitmap->set_pixel(x, y, sample(Gfx::IntPoint(x, y))); + }); + } ++x; if (x == width) { x = 0; diff --git a/Userland/Libraries/LibPDF/Renderer.h b/Userland/Libraries/LibPDF/Renderer.h index c135593b4a..757a5c8ca7 100644 --- a/Userland/Libraries/LibPDF/Renderer.h +++ b/Userland/Libraries/LibPDF/Renderer.h @@ -74,8 +74,8 @@ struct GraphicsState { ClippingPaths clipping_paths; RefPtr stroke_color_space { DeviceGrayColorSpace::the() }; RefPtr paint_color_space { DeviceGrayColorSpace::the() }; - Gfx::Color stroke_color { Gfx::Color::NamedColor::Black }; - Gfx::Color paint_color { Gfx::Color::NamedColor::Black }; + ColorOrStyle stroke_style { Color::Black }; + ColorOrStyle paint_style { Color::Black }; DeprecatedString color_rendering_intent { "RelativeColorimetric"sv }; float flatness_tolerance { 0.0f }; float line_width { 1.0f }; @@ -286,8 +286,16 @@ struct Formatter : Formatter { StringBuilder builder; builder.append("GraphicsState {\n"sv); builder.appendff(" ctm={}\n", state.ctm); - builder.appendff(" stroke_color={}\n", state.stroke_color); - builder.appendff(" paint_color={}\n", state.paint_color); + if (state.stroke_style.has()) { + builder.appendff(" stroke_style={}\n", state.stroke_style.get()); + } else { + builder.appendff(" stroke_style={}\n", state.stroke_style.get>()); + } + if (state.paint_style.has()) { + builder.appendff(" paint_style={}\n", state.paint_style.get()); + } else { + builder.appendff(" paint_style={}\n", state.paint_style.get>()); + } builder.appendff(" color_rendering_intent={}\n", state.color_rendering_intent); builder.appendff(" flatness_tolerance={}\n", state.flatness_tolerance); builder.appendff(" line_width={}\n", state.line_width);