Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 127 additions & 7 deletions Userland/Libraries/LibPDF/ColorSpace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -55,7 +55,7 @@ PDFErrorOr<NonnullRefPtr<ColorSpace>> ColorSpace::create(DeprecatedFlyString con
if (name == CommonNames::DeviceCMYK)
return TRY(DeviceCMYKColorSpace::the());
if (name == CommonNames::Pattern)
return Error::rendering_unsupported_error("Pattern color spaces not yet implemented");
return PatternColorSpace::create(renderer);
VERIFY_NOT_REACHED();
}

Expand Down Expand Up @@ -86,12 +86,12 @@ 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));

if (color_space_name == CommonNames::Pattern)
return PatternColorSpace::create(renderer);

dbgln("Unknown color space: {}", color_space_name);
return Error::rendering_unsupported_error("unknown color space");
}
Expand Down Expand Up @@ -720,13 +720,13 @@ PDFErrorOr<NonnullRefPtr<ColorSpace>> IndexedColorSpace::create(Document* docume
for (size_t i = 0; i < lookup.size(); ++i)
lookup_float[i] = mix(decode[2 * (i % n)], decode[2 * (i % n) + 1], lookup[i] / 255.0f);

auto color_space = adopt_ref(*new IndexedColorSpace(move(base)));
auto color_space = adopt_ref(*new IndexedColorSpace(move(verify_cast<ColorSpaceWithFloatArgs>(*base))));
color_space->m_hival = hival;
color_space->m_lookup = move(lookup_float);
return color_space;
}

IndexedColorSpace::IndexedColorSpace(NonnullRefPtr<ColorSpace> base)
IndexedColorSpace::IndexedColorSpace(NonnullRefPtr<ColorSpaceWithFloatArgs> base)
: m_base(move(base))
{
}
Expand Down Expand Up @@ -802,4 +802,124 @@ 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;
if (!resources->contains(CommonNames::Pattern))
return Error::malformed_error("Pattern resource dictionary missing");

auto pattern_resource = resources->get_value(CommonNames::Pattern);
auto* maybe_doc_pattern_dict = pattern_resource.get_pointer<NonnullRefPtr<Object>>();
if (!maybe_doc_pattern_dict || !(*maybe_doc_pattern_dict)->is<DictObject>())
return Error::malformed_error("Pattern resource dictionary not DictObject");

auto doc_pattern_dict = (*maybe_doc_pattern_dict)->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<Object>(doc_pattern_dict->get_value(pattern_name)));
NonnullRefPtr<DictObject> pattern_dict = [&] {
// Shading patterns do not have a content stream.
if (pattern->is<DictObject>())
return pattern->cast<DictObject>();
return pattern->cast<StreamObject>()->dict();
}();

// PatternType (Required) A code identifying the type of pattern that this dictionary describes;
// shall be 1 for a tiling pattern
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);

// Type (Optional) The type of PDF object that this dictionary describes;
// if present, shall be Pattern for a pattern dictionary
auto const type = pattern_dict->get(CommonNames::Type);
if (type.has_value()) {
auto type_name = type->get<NonnullRefPtr<Object>>()->cast<NameObject>();
if (type_name->name() != CommonNames::Pattern)
return Error::rendering_unsupported_error("Unsupported pattern type {}", type_name->name());
}

// PaintType (Required) A code that determines how the colour of the pattern cell shall be specified
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);

// Matrix (Optional) An array of six numbers specifying the pattern matrix
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->cast<StreamObject>()->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();
}
}
62 changes: 44 additions & 18 deletions Userland/Libraries/LibPDF/ColorSpace.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,27 @@ class ColorSpace : public RefCounted<ColorSpace> {

virtual ~ColorSpace() = default;

virtual PDFErrorOr<ColorOrStyle> style(ReadonlySpan<Value> arguments) const = 0;

virtual int number_of_components() const = 0;
virtual Vector<float> default_decode() const = 0; // "TABLE 4.40 Default Decode arrays"
virtual ColorSpaceFamily const& family() const = 0;
};

class ColorSpaceWithFloatArgs : public ColorSpace {
public:
virtual PDFErrorOr<ColorOrStyle> style(ReadonlySpan<float> arguments) const = 0;
virtual PDFErrorOr<ColorOrStyle> style(ReadonlySpan<Value> arguments) const

PDFErrorOr<ColorOrStyle> style(ReadonlySpan<Value> arguments) const override
{
Vector<float, 4> float_arguments;
for (auto& argument : arguments)
float_arguments.append(argument.to_float());
return style(float_arguments);
}

virtual int number_of_components() const = 0;
virtual Vector<float> default_decode() const = 0; // "TABLE 4.40 Default Decode arrays"
virtual ColorSpaceFamily const& family() const = 0;
};

class DeviceGrayColorSpace final : public ColorSpace {
class DeviceGrayColorSpace final : public ColorSpaceWithFloatArgs {
public:
static NonnullRefPtr<DeviceGrayColorSpace> the();

Expand All @@ -95,7 +101,7 @@ class DeviceGrayColorSpace final : public ColorSpace {
DeviceGrayColorSpace() = default;
};

class DeviceRGBColorSpace final : public ColorSpace {
class DeviceRGBColorSpace final : public ColorSpaceWithFloatArgs {
public:
static NonnullRefPtr<DeviceRGBColorSpace> the();

Expand All @@ -110,7 +116,7 @@ class DeviceRGBColorSpace final : public ColorSpace {
DeviceRGBColorSpace() = default;
};

class DeviceCMYKColorSpace final : public ColorSpace {
class DeviceCMYKColorSpace final : public ColorSpaceWithFloatArgs {
public:
static ErrorOr<NonnullRefPtr<DeviceCMYKColorSpace>> the();

Expand All @@ -125,7 +131,7 @@ class DeviceCMYKColorSpace final : public ColorSpace {
DeviceCMYKColorSpace() = default;
};

class DeviceNColorSpace final : public ColorSpace {
class DeviceNColorSpace final : public ColorSpaceWithFloatArgs {
public:
static PDFErrorOr<NonnullRefPtr<DeviceNColorSpace>> create(Document*, Vector<Value>&& parameters, Renderer&);

Expand All @@ -145,7 +151,7 @@ class DeviceNColorSpace final : public ColorSpace {
Vector<Value> mutable m_tint_output_values;
};

class CalGrayColorSpace final : public ColorSpace {
class CalGrayColorSpace final : public ColorSpaceWithFloatArgs {
public:
static PDFErrorOr<NonnullRefPtr<CalGrayColorSpace>> create(Document*, Vector<Value>&& parameters);

Expand All @@ -164,7 +170,7 @@ class CalGrayColorSpace final : public ColorSpace {
float m_gamma { 1 };
};

class CalRGBColorSpace final : public ColorSpace {
class CalRGBColorSpace final : public ColorSpaceWithFloatArgs {
public:
static PDFErrorOr<NonnullRefPtr<CalRGBColorSpace>> create(Document*, Vector<Value>&& parameters);

Expand All @@ -184,7 +190,7 @@ class CalRGBColorSpace final : public ColorSpace {
Array<float, 9> m_matrix { 1, 0, 0, 0, 1, 0, 0, 0, 1 };
};

class ICCBasedColorSpace final : public ColorSpace {
class ICCBasedColorSpace final : public ColorSpaceWithFloatArgs {
public:
static PDFErrorOr<NonnullRefPtr<ColorSpace>> create(Document*, Vector<Value>&& parameters, Renderer&);

Expand All @@ -207,7 +213,7 @@ class ICCBasedColorSpace final : public ColorSpace {
Optional<Gfx::ICC::MatrixMatrixConversion> m_map;
};

class LabColorSpace final : public ColorSpace {
class LabColorSpace final : public ColorSpaceWithFloatArgs {
public:
static PDFErrorOr<NonnullRefPtr<LabColorSpace>> create(Document*, Vector<Value>&& parameters);

Expand All @@ -226,7 +232,7 @@ class LabColorSpace final : public ColorSpace {
Array<float, 4> m_range { -100, 100, -100, 100 };
};

class IndexedColorSpace final : public ColorSpace {
class IndexedColorSpace final : public ColorSpaceWithFloatArgs {
public:
static PDFErrorOr<NonnullRefPtr<ColorSpace>> create(Document*, Vector<Value>&& parameters, Renderer&);

Expand All @@ -237,7 +243,7 @@ class IndexedColorSpace final : public ColorSpace {
Vector<float> default_decode() const override;
ColorSpaceFamily const& family() const override { return ColorSpaceFamily::Indexed; }

NonnullRefPtr<ColorSpace> base_color_space() const { return m_base; }
NonnullRefPtr<ColorSpaceWithFloatArgs> base_color_space() const { return m_base; }

PDFErrorOr<ReadonlySpan<float>> base_components(int index) const
{
Expand All @@ -248,14 +254,14 @@ class IndexedColorSpace final : public ColorSpace {
}

private:
IndexedColorSpace(NonnullRefPtr<ColorSpace>);
IndexedColorSpace(NonnullRefPtr<ColorSpaceWithFloatArgs>);

NonnullRefPtr<ColorSpace> m_base;
NonnullRefPtr<ColorSpaceWithFloatArgs> m_base;
int m_hival { 0 };
Vector<float> m_lookup;
};

class SeparationColorSpace final : public ColorSpace {
class SeparationColorSpace final : public ColorSpaceWithFloatArgs {
public:
static PDFErrorOr<NonnullRefPtr<SeparationColorSpace>> create(Document*, Vector<Value>&& parameters, Renderer&);

Expand All @@ -274,4 +280,24 @@ class SeparationColorSpace final : public ColorSpace {
NonnullRefPtr<Function> m_tint_transform;
Vector<Value> mutable m_tint_output_values;
};

class PatternColorSpace final : public ColorSpace {
public:
static NonnullRefPtr<PatternColorSpace> create(Renderer& renderer);
~PatternColorSpace() override = default;

PDFErrorOr<ColorOrStyle> style(ReadonlySpan<Value> arguments) const override;
int number_of_components() const override;
Vector<float> default_decode() const override;
ColorSpaceFamily const& family() const override { return ColorSpaceFamily::Pattern; }

private:
PatternColorSpace(Renderer& renderer)
: m_renderer(renderer)
{
}

Renderer& m_renderer;
};

}
2 changes: 2 additions & 0 deletions Userland/Libraries/LibPDF/CommonNames.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,10 @@
X(Overlay) \
X(P) \
X(Pages) \
X(PaintType) \
X(Parent) \
X(Pattern) \
X(PatternType) \
X(Perms) \
X(Predictor) \
X(Prev) \
Expand Down
Loading
Loading