Skip to content

Commit 8ff8791

Browse files
X-ylawesomekling
authored andcommitted
LibPDF: Add basic tiled, coloured pattern rendering
1 parent 8191f2b commit 8ff8791

File tree

5 files changed

+129
-20
lines changed

5 files changed

+129
-20
lines changed

Userland/Libraries/LibPDF/ColorSpace.cpp

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ PDFErrorOr<NonnullRefPtr<ColorSpace>> ColorSpace::create(Document* document, Non
4545
return Error { Error::Type::MalformedPDF, "Color space must be name or array" };
4646
}
4747

48-
PDFErrorOr<NonnullRefPtr<ColorSpace>> ColorSpace::create(DeprecatedFlyString const& name, Renderer&)
48+
PDFErrorOr<NonnullRefPtr<ColorSpace>> ColorSpace::create(DeprecatedFlyString const& name, Renderer& renderer)
4949
{
5050
// Simple color spaces with no parameters, which can be specified directly
5151
if (name == CommonNames::DeviceGray)
@@ -55,7 +55,7 @@ PDFErrorOr<NonnullRefPtr<ColorSpace>> ColorSpace::create(DeprecatedFlyString con
5555
if (name == CommonNames::DeviceCMYK)
5656
return DeviceCMYKColorSpace::the();
5757
if (name == CommonNames::Pattern)
58-
return Error::rendering_unsupported_error("Pattern color spaces not yet implemented");
58+
return PatternColorSpace::create(renderer);
5959
VERIFY_NOT_REACHED();
6060
}
6161

@@ -86,9 +86,6 @@ PDFErrorOr<NonnullRefPtr<ColorSpace>> ColorSpace::create(Document* document, Non
8686
if (color_space_name == CommonNames::Lab)
8787
return TRY(LabColorSpace::create(document, move(parameters)));
8888

89-
if (color_space_name == CommonNames::Pattern)
90-
return Error::rendering_unsupported_error("Pattern color spaces not yet implemented");
91-
9289
if (color_space_name == CommonNames::Separation)
9390
return TRY(SeparationColorSpace::create(document, move(parameters), renderer));
9491

@@ -773,4 +770,104 @@ Vector<float> SeparationColorSpace::default_decode() const
773770
{
774771
return { 0.0f, 1.0f };
775772
}
773+
NonnullRefPtr<PatternColorSpace> PatternColorSpace::create(Renderer& renderer)
774+
{
775+
return adopt_ref(*new PatternColorSpace(renderer));
776+
}
777+
778+
PDFErrorOr<ColorOrStyle> PatternColorSpace::style(ReadonlySpan<Value> arguments) const
779+
{
780+
VERIFY(arguments.size() >= 1);
781+
782+
auto resources = m_renderer.m_page.resources;
783+
784+
auto pattern_resource = resources->get_value(CommonNames::Pattern);
785+
auto doc_pattern_dict = pattern_resource.get<NonnullRefPtr<Object>>()->cast<DictObject>();
786+
787+
auto const& pattern_name = arguments.last().get<NonnullRefPtr<Object>>()->cast<NameObject>()->name();
788+
if (!doc_pattern_dict->contains(pattern_name))
789+
return Error::malformed_error("Pattern dictionary does not contain pattern {}", pattern_name);
790+
791+
auto const pattern = TRY(m_renderer.m_document->resolve_to<StreamObject>(doc_pattern_dict->get_value(pattern_name)));
792+
793+
auto const type = pattern->dict()->get(CommonNames::Type)->get<NonnullRefPtr<Object>>()->cast<NameObject>();
794+
if (type->name() != CommonNames::Pattern)
795+
return Error::rendering_unsupported_error("Unsupported pattern type {}", type->name());
796+
797+
auto const pattern_type = pattern->dict()->get(CommonNames::PatternType)->get_u16();
798+
if (pattern_type != 1)
799+
return Error::rendering_unsupported_error("Unsupported pattern type {}", pattern_type);
800+
801+
auto const pattern_paint_type = pattern->dict()->get("PaintType")->get_u16();
802+
if (pattern_paint_type != 1)
803+
return Error::rendering_unsupported_error("Unsupported pattern paint type {}", pattern_paint_type);
804+
805+
Vector<Value> pattern_matrix;
806+
if (pattern->dict()->contains(CommonNames::Matrix)) {
807+
pattern_matrix = pattern->dict()->get_array(m_renderer.m_document, CommonNames::Matrix).value()->elements();
808+
} else {
809+
pattern_matrix = Vector { Value { 1 }, Value { 0 }, Value { 0 }, Value { 1 }, Value { 0 }, Value { 0 } };
810+
}
811+
812+
auto pattern_bounding_box = pattern->dict()->get_array(m_renderer.m_document, "BBox").value()->elements();
813+
auto pattern_transform = Gfx::AffineTransform(
814+
pattern_matrix[0].to_float(),
815+
pattern_matrix[1].to_float(),
816+
pattern_matrix[2].to_float(),
817+
pattern_matrix[3].to_float(),
818+
pattern_matrix[4].to_float(),
819+
pattern_matrix[5].to_float());
820+
821+
// 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.
822+
// NB: the pattern pattern_matrix maps pattern space to the default (initial) coordinate space of the page. (i.e cannot be updated via cm).
823+
824+
auto initial_ctm = Gfx::AffineTransform(m_renderer.m_graphics_state_stack.first().ctm);
825+
initial_ctm.set_translation(0, 0);
826+
initial_ctm.set_scale(initial_ctm.x_scale(), initial_ctm.y_scale());
827+
828+
auto pattern_space_lower_left = Gfx::FloatPoint { pattern_bounding_box[0].to_int(), pattern_bounding_box[1].to_int() };
829+
auto pattern_space_upper_right = Gfx::FloatPoint { pattern_bounding_box[2].to_int(), pattern_bounding_box[3].to_int() };
830+
831+
auto device_space_lower_left = initial_ctm.map(pattern_transform.map(pattern_space_lower_left));
832+
auto device_space_upper_right = initial_ctm.map(pattern_transform.map(pattern_space_upper_right));
833+
834+
auto bitmap_width = (int)device_space_upper_right.x() - (int)device_space_lower_left.x();
835+
auto bitmap_height = (int)device_space_upper_right.y() - (int)device_space_lower_left.y();
836+
837+
auto pattern_cell = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { bitmap_width, bitmap_height }));
838+
auto page = Page(m_renderer.m_page);
839+
page.media_box = Rectangle {
840+
pattern_space_lower_left.x(), pattern_space_lower_left.y(),
841+
pattern_space_upper_right.x(), pattern_space_upper_right.y()
842+
};
843+
page.crop_box = page.media_box;
844+
845+
auto pattern_renderer = Renderer(m_renderer.m_document, page, pattern_cell, {}, m_renderer.m_rendering_preferences);
846+
847+
auto operators = TRY(Parser::parse_operators(m_renderer.m_document, pattern->bytes()));
848+
for (auto& op : operators)
849+
TRY(pattern_renderer.handle_operator(op, resources));
850+
851+
auto x_steps = pattern->dict()->get("XStep").value_or(bitmap_width).to_int();
852+
auto y_steps = pattern->dict()->get("YStep").value_or(bitmap_height).to_int();
853+
854+
auto device_space_steps = initial_ctm.map(pattern_transform.map(Gfx::IntPoint { x_steps, y_steps }));
855+
856+
NonnullRefPtr<Gfx::PaintStyle> style = MUST(Gfx::RepeatingBitmapPaintStyle::create(
857+
*pattern_cell,
858+
device_space_steps,
859+
{}));
860+
861+
return style;
862+
}
863+
int PatternColorSpace::number_of_components() const
864+
{
865+
// Not permitted
866+
VERIFY_NOT_REACHED();
867+
}
868+
Vector<float> PatternColorSpace::default_decode() const
869+
{
870+
// Not permitted
871+
VERIFY_NOT_REACHED();
872+
}
776873
}

Userland/Libraries/LibPDF/ColorSpace.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,4 +252,24 @@ class SeparationColorSpace final : public ColorSpace {
252252
NonnullRefPtr<Function> m_tint_transform;
253253
Vector<Value> mutable m_tint_output_values;
254254
};
255+
256+
class PatternColorSpace final : public ColorSpace {
257+
public:
258+
static NonnullRefPtr<PatternColorSpace> create(Renderer& renderer);
259+
~PatternColorSpace() override = default;
260+
261+
PDFErrorOr<ColorOrStyle> style(ReadonlySpan<Value> arguments) const override;
262+
int number_of_components() const override;
263+
Vector<float> default_decode() const override;
264+
ColorSpaceFamily const& family() const override { return ColorSpaceFamily::Pattern; }
265+
266+
private:
267+
PatternColorSpace(Renderer& renderer)
268+
: m_renderer(renderer)
269+
{
270+
}
271+
272+
Renderer& m_renderer;
273+
};
274+
255275
}

Userland/Libraries/LibPDF/CommonNames.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,10 @@
138138
X(Outlines) \
139139
X(P) \
140140
X(Pages) \
141+
X(PaintType) \
141142
X(Parent) \
142143
X(Pattern) \
144+
X(PatternType) \
143145
X(Perms) \
144146
X(Predictor) \
145147
X(Prev) \

Userland/Libraries/LibPDF/Renderer.cpp

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -616,14 +616,8 @@ RENDERER_HANDLER(set_stroking_color)
616616

617617
RENDERER_HANDLER(set_stroking_color_extended)
618618
{
619-
// FIXME: Handle Pattern color spaces
620-
auto last_arg = args.last();
621-
if (last_arg.has<NonnullRefPtr<Object>>() && last_arg.get<NonnullRefPtr<Object>>()->is<NameObject>()) {
622-
dbgln("pattern space {}", last_arg.get<NonnullRefPtr<Object>>()->cast<NameObject>()->name());
623-
return Error::rendering_unsupported_error("Pattern color spaces not yet implemented");
624-
}
625-
626-
state().stroke_style = TRY(state().stroke_color_space->style(args));
619+
// FIXME: Pattern color spaces might need extra resources
620+
state().paint_style = TRY(state().paint_color_space->style(args));
627621
return {};
628622
}
629623

@@ -635,13 +629,7 @@ RENDERER_HANDLER(set_painting_color)
635629

636630
RENDERER_HANDLER(set_painting_color_extended)
637631
{
638-
// FIXME: Handle Pattern color spaces
639-
auto last_arg = args.last();
640-
if (last_arg.has<NonnullRefPtr<Object>>() && last_arg.get<NonnullRefPtr<Object>>()->is<NameObject>()) {
641-
dbgln("pattern space {}", last_arg.get<NonnullRefPtr<Object>>()->cast<NameObject>()->name());
642-
return Error::rendering_unsupported_error("Pattern color spaces not yet implemented");
643-
}
644-
632+
// FIXME: Pattern color spaces might need extra resources
645633
state().paint_style = TRY(state().paint_color_space->style(args));
646634
return {};
647635
}

Userland/Libraries/LibPDF/Renderer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ struct RenderingPreferences {
9797
};
9898

9999
class Renderer {
100+
friend class PatternColorSpace;
101+
100102
public:
101103
static PDFErrorsOr<void> render(Document&, Page const&, RefPtr<Gfx::Bitmap>, Color background_color, RenderingPreferences preferences);
102104

0 commit comments

Comments
 (0)