|
@@ -45,7 +45,7 @@ PDFErrorOr<NonnullRefPtr<ColorSpace>> ColorSpace::create(Document* document, Non
|
|
|
return Error { Error::Type::MalformedPDF, "Color space must be name or array" };
|
|
|
}
|
|
|
|
|
|
-PDFErrorOr<NonnullRefPtr<ColorSpace>> ColorSpace::create(DeprecatedFlyString const& name, Renderer&)
|
|
|
+PDFErrorOr<NonnullRefPtr<ColorSpace>> 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<NonnullRefPtr<ColorSpace>> 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<NonnullRefPtr<ColorSpace>> 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<float> SeparationColorSpace::default_decode() const
|
|
|
{
|
|
|
return { 0.0f, 1.0f };
|
|
|
}
|
|
|
+NonnullRefPtr<PatternColorSpace> PatternColorSpace::create(Renderer& renderer)
|
|
|
+{
|
|
|
+ return adopt_ref(*new PatternColorSpace(renderer));
|
|
|
+}
|
|
|
+
|
|
|
+PDFErrorOr<ColorOrStyle> PatternColorSpace::style(ReadonlySpan<Value> 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<NonnullRefPtr<Object>>()->cast<DictObject>();
|
|
|
+
|
|
|
+ auto const& pattern_name = arguments.last().get<NonnullRefPtr<Object>>()->cast<NameObject>()->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<StreamObject>(doc_pattern_dict->get_value(pattern_name)));
|
|
|
+
|
|
|
+ auto const type = pattern->dict()->get(CommonNames::Type)->get<NonnullRefPtr<Object>>()->cast<NameObject>();
|
|
|
+ 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<Value> 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<Gfx::PaintStyle> style = MUST(Gfx::RepeatingBitmapPaintStyle::create(
|
|
|
+ *pattern_cell,
|
|
|
+ device_space_steps,
|
|
|
+ {}));
|
|
|
+
|
|
|
+ return style;
|
|
|
+}
|
|
|
+int PatternColorSpace::number_of_components() const
|
|
|
+{
|
|
|
+ // Not permitted
|
|
|
+ VERIFY_NOT_REACHED();
|
|
|
+}
|
|
|
+Vector<float> PatternColorSpace::default_decode() const
|
|
|
+{
|
|
|
+ // Not permitted
|
|
|
+ VERIFY_NOT_REACHED();
|
|
|
+}
|
|
|
}
|