2021-05-10 20:50:39 +03:00
|
|
|
/*
|
2022-03-06 03:30:55 +03:00
|
|
|
* Copyright (c) 2021-2022, Matthew Olsson <mattco@serenityos.org>
|
2021-05-10 20:50:39 +03:00
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <AK/Utf8View.h>
|
2021-05-24 18:15:43 +03:00
|
|
|
#include <LibPDF/CommonNames.h>
|
2022-03-25 08:14:50 +03:00
|
|
|
#include <LibPDF/Fonts/PDFFont.h>
|
LibPDF: Add initial image display support
After adding support for XObject Form rendering, the next was to display
XObject images. This commit adds this initial support,
Images come in many shapes and forms: encodings: color spaces, bits per
component, width, height, etc. This initial support is constrained to
the color spaces we currently support, to images that use 8 bits per
component, to images that do *not* use the JPXDecode filter, and that
are not Masks. There are surely other constraints that aren't considered
in this initial support, so expect breakage here and there.
In addition to supporting images, we also support applying an alpha mask
(SMask) on them. Additionally, a new rendering preference allows to skip
image loading and rendering altogether, instead showing an empty
rectangle as a placeholder (useful for when actual images are not
supported). Since RenderingPreferences is becoming a bit more complex,
we add a hash option that will allow us to keep track of different
preferences (e.g., in a HashMap).
2022-11-24 21:01:53 +03:00
|
|
|
#include <LibPDF/Interpolation.h>
|
2021-05-10 20:50:39 +03:00
|
|
|
#include <LibPDF/Renderer.h>
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
#define RENDERER_HANDLER(name) \
|
2022-11-21 08:28:41 +03:00
|
|
|
PDFErrorOr<void> Renderer::handle_##name([[maybe_unused]] Vector<Value> const& args, [[maybe_unused]] Optional<NonnullRefPtr<DictObject>> extra_resources)
|
2021-05-23 07:09:33 +03:00
|
|
|
|
2022-12-14 18:10:26 +03:00
|
|
|
#define RENDERER_TODO(name) \
|
|
|
|
RENDERER_HANDLER(name) \
|
|
|
|
{ \
|
|
|
|
return Error(Error::Type::RenderingUnsupported, "draw operation: " #name); \
|
2021-05-23 07:09:33 +03:00
|
|
|
}
|
|
|
|
|
2021-05-10 20:50:39 +03:00
|
|
|
namespace PDF {
|
|
|
|
|
LibPDF: Switch to best-effort PDF rendering
The current rendering routine aborts as soon as an error is found during
rendering, which potentially severely limits the contents we show on
screen. Moreover, whenever an error happens the PDFViewer widget shows
an error dialog, and doesn't display the bitmap that has been painted so
far.
This commit improves the situation in both fronts, implementing
rendering now with a best-effort approach. Firstly, execution of
operations isn't halted after an operand results in an error, but
instead execution of all operations is always attempted, and all
collected errors are returned in bulk. Secondly, PDFViewer now always
displays the resulting bitmap, regardless of error being produced or
not. To communicate errors, an on_render_errors callback has been added
so clients can subscribe to these events and handle them as appropriate.
2022-12-14 18:00:40 +03:00
|
|
|
PDFErrorsOr<void> Renderer::render(Document& document, Page const& page, RefPtr<Gfx::Bitmap> bitmap, RenderingPreferences rendering_preferences)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2022-11-23 16:11:29 +03:00
|
|
|
return Renderer(document, page, bitmap, rendering_preferences).render();
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2022-11-23 16:03:26 +03:00
|
|
|
static void rect_path(Gfx::Path& path, float x, float y, float width, float height)
|
|
|
|
{
|
|
|
|
path.move_to({ x, y });
|
|
|
|
path.line_to({ x + width, y });
|
|
|
|
path.line_to({ x + width, y + height });
|
|
|
|
path.line_to({ x, y + height });
|
|
|
|
path.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
static Gfx::Path rect_path(float x, float y, float width, float height)
|
|
|
|
{
|
|
|
|
Gfx::Path path;
|
|
|
|
rect_path(path, x, y, width, height);
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
2022-11-26 07:53:32 +03:00
|
|
|
template<typename T>
|
|
|
|
static void rect_path(Gfx::Path& path, Gfx::Rect<T> rect)
|
|
|
|
{
|
|
|
|
return rect_path(path, rect.x(), rect.y(), rect.width(), rect.height());
|
|
|
|
}
|
|
|
|
|
2022-11-23 16:03:26 +03:00
|
|
|
template<typename T>
|
|
|
|
static Gfx::Path rect_path(Gfx::Rect<T> rect)
|
|
|
|
{
|
2022-11-26 07:53:32 +03:00
|
|
|
Gfx::Path path;
|
|
|
|
rect_path(path, rect);
|
|
|
|
return path;
|
2022-11-23 16:03:26 +03:00
|
|
|
}
|
|
|
|
|
2022-11-23 16:11:29 +03:00
|
|
|
Renderer::Renderer(RefPtr<Document> document, Page const& page, RefPtr<Gfx::Bitmap> bitmap, RenderingPreferences rendering_preferences)
|
2021-05-10 20:50:39 +03:00
|
|
|
: m_document(document)
|
|
|
|
, m_bitmap(bitmap)
|
|
|
|
, m_page(page)
|
|
|
|
, m_painter(*bitmap)
|
2022-03-24 21:50:37 +03:00
|
|
|
, m_anti_aliasing_painter(m_painter)
|
2022-11-23 16:11:29 +03:00
|
|
|
, m_rendering_preferences(rendering_preferences)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
auto media_box = m_page.media_box;
|
|
|
|
|
2021-05-28 21:55:51 +03:00
|
|
|
Gfx::AffineTransform userspace_matrix;
|
|
|
|
userspace_matrix.translate(media_box.lower_left_x, media_box.lower_left_y);
|
2021-05-10 20:50:39 +03:00
|
|
|
|
2022-03-30 07:27:17 +03:00
|
|
|
float width = media_box.width();
|
|
|
|
float height = media_box.height();
|
2021-05-10 20:50:39 +03:00
|
|
|
float scale_x = static_cast<float>(bitmap->width()) / width;
|
|
|
|
float scale_y = static_cast<float>(bitmap->height()) / height;
|
2021-05-28 21:55:51 +03:00
|
|
|
userspace_matrix.scale(scale_x, scale_y);
|
2021-05-10 20:50:39 +03:00
|
|
|
|
2021-05-28 22:18:11 +03:00
|
|
|
// PDF user-space coordinate y axis increases from bottom to top, so we have to
|
|
|
|
// insert a horizontal reflection about the vertical midpoint into our transformation
|
|
|
|
// matrix
|
|
|
|
|
|
|
|
static Gfx::AffineTransform horizontal_reflection_matrix = { 1, 0, 0, -1, 0, 0 };
|
|
|
|
|
|
|
|
userspace_matrix.multiply(horizontal_reflection_matrix);
|
|
|
|
userspace_matrix.translate(0.0f, -height);
|
|
|
|
|
2022-11-23 16:03:26 +03:00
|
|
|
auto initial_clipping_path = rect_path(0, 0, width, height);
|
|
|
|
m_graphics_state_stack.append(GraphicsState { userspace_matrix, { initial_clipping_path, initial_clipping_path } });
|
2021-05-10 20:50:39 +03:00
|
|
|
|
|
|
|
m_bitmap->fill(Gfx::Color::NamedColor::White);
|
|
|
|
}
|
|
|
|
|
LibPDF: Switch to best-effort PDF rendering
The current rendering routine aborts as soon as an error is found during
rendering, which potentially severely limits the contents we show on
screen. Moreover, whenever an error happens the PDFViewer widget shows
an error dialog, and doesn't display the bitmap that has been painted so
far.
This commit improves the situation in both fronts, implementing
rendering now with a best-effort approach. Firstly, execution of
operations isn't halted after an operand results in an error, but
instead execution of all operations is always attempted, and all
collected errors are returned in bulk. Secondly, PDFViewer now always
displays the resulting bitmap, regardless of error being produced or
not. To communicate errors, an on_render_errors callback has been added
so clients can subscribe to these events and handle them as appropriate.
2022-12-14 18:00:40 +03:00
|
|
|
PDFErrorsOr<void> Renderer::render()
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
// Use our own vector, as the /Content can be an array with multiple
|
|
|
|
// streams which gets concatenated
|
|
|
|
// FIXME: Text operators are supposed to only have effects on the current
|
|
|
|
// stream object. Do the text operators treat this concatenated stream
|
|
|
|
// as one stream or multiple?
|
|
|
|
ByteBuffer byte_buffer;
|
|
|
|
|
2022-03-06 03:30:55 +03:00
|
|
|
if (m_page.contents->is<ArrayObject>()) {
|
|
|
|
auto contents = m_page.contents->cast<ArrayObject>();
|
2021-05-10 20:50:39 +03:00
|
|
|
for (auto& ref : *contents) {
|
2022-03-06 04:12:58 +03:00
|
|
|
auto bytes = TRY(m_document->resolve_to<StreamObject>(ref))->bytes();
|
2021-05-10 20:50:39 +03:00
|
|
|
byte_buffer.append(bytes.data(), bytes.size());
|
|
|
|
}
|
|
|
|
} else {
|
2022-03-06 03:30:55 +03:00
|
|
|
auto bytes = m_page.contents->cast<StreamObject>()->bytes();
|
2021-05-10 20:50:39 +03:00
|
|
|
byte_buffer.append(bytes.data(), bytes.size());
|
|
|
|
}
|
|
|
|
|
2022-03-26 01:00:11 +03:00
|
|
|
auto operators = TRY(Parser::parse_operators(m_document, byte_buffer));
|
2021-05-10 20:50:39 +03:00
|
|
|
|
LibPDF: Switch to best-effort PDF rendering
The current rendering routine aborts as soon as an error is found during
rendering, which potentially severely limits the contents we show on
screen. Moreover, whenever an error happens the PDFViewer widget shows
an error dialog, and doesn't display the bitmap that has been painted so
far.
This commit improves the situation in both fronts, implementing
rendering now with a best-effort approach. Firstly, execution of
operations isn't halted after an operand results in an error, but
instead execution of all operations is always attempted, and all
collected errors are returned in bulk. Secondly, PDFViewer now always
displays the resulting bitmap, regardless of error being produced or
not. To communicate errors, an on_render_errors callback has been added
so clients can subscribe to these events and handle them as appropriate.
2022-12-14 18:00:40 +03:00
|
|
|
Errors errors;
|
|
|
|
for (auto& op : operators) {
|
|
|
|
auto maybe_error = handle_operator(op);
|
|
|
|
if (maybe_error.is_error()) {
|
|
|
|
errors.add_error(maybe_error.release_error());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!errors.errors().is_empty())
|
|
|
|
return errors;
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2022-11-21 08:28:41 +03:00
|
|
|
PDFErrorOr<void> Renderer::handle_operator(Operator const& op, Optional<NonnullRefPtr<DictObject>> extra_resources)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2022-03-26 01:00:11 +03:00
|
|
|
switch (op.type()) {
|
LibPDF: Switch to best-effort PDF rendering
The current rendering routine aborts as soon as an error is found during
rendering, which potentially severely limits the contents we show on
screen. Moreover, whenever an error happens the PDFViewer widget shows
an error dialog, and doesn't display the bitmap that has been painted so
far.
This commit improves the situation in both fronts, implementing
rendering now with a best-effort approach. Firstly, execution of
operations isn't halted after an operand results in an error, but
instead execution of all operations is always attempted, and all
collected errors are returned in bulk. Secondly, PDFViewer now always
displays the resulting bitmap, regardless of error being produced or
not. To communicate errors, an on_render_errors callback has been added
so clients can subscribe to these events and handle them as appropriate.
2022-12-14 18:00:40 +03:00
|
|
|
#define V(name, snake_name, symbol) \
|
|
|
|
case OperatorType::name: \
|
|
|
|
TRY(handle_##snake_name(op.arguments(), extra_resources)); \
|
2021-05-23 07:09:33 +03:00
|
|
|
break;
|
2022-03-26 01:00:11 +03:00
|
|
|
ENUMERATE_OPERATORS(V)
|
2021-05-10 20:50:39 +03:00
|
|
|
#undef V
|
2022-03-26 01:00:11 +03:00
|
|
|
case OperatorType::TextNextLineShowString:
|
LibPDF: Switch to best-effort PDF rendering
The current rendering routine aborts as soon as an error is found during
rendering, which potentially severely limits the contents we show on
screen. Moreover, whenever an error happens the PDFViewer widget shows
an error dialog, and doesn't display the bitmap that has been painted so
far.
This commit improves the situation in both fronts, implementing
rendering now with a best-effort approach. Firstly, execution of
operations isn't halted after an operand results in an error, but
instead execution of all operations is always attempted, and all
collected errors are returned in bulk. Secondly, PDFViewer now always
displays the resulting bitmap, regardless of error being produced or
not. To communicate errors, an on_render_errors callback has been added
so clients can subscribe to these events and handle them as appropriate.
2022-12-14 18:00:40 +03:00
|
|
|
TRY(handle_text_next_line_show_string(op.arguments()));
|
2021-05-23 07:09:33 +03:00
|
|
|
break;
|
2022-03-26 01:00:11 +03:00
|
|
|
case OperatorType::TextNextLineShowStringSetSpacing:
|
LibPDF: Switch to best-effort PDF rendering
The current rendering routine aborts as soon as an error is found during
rendering, which potentially severely limits the contents we show on
screen. Moreover, whenever an error happens the PDFViewer widget shows
an error dialog, and doesn't display the bitmap that has been painted so
far.
This commit improves the situation in both fronts, implementing
rendering now with a best-effort approach. Firstly, execution of
operations isn't halted after an operand results in an error, but
instead execution of all operations is always attempted, and all
collected errors are returned in bulk. Secondly, PDFViewer now always
displays the resulting bitmap, regardless of error being produced or
not. To communicate errors, an on_render_errors callback has been added
so clients can subscribe to these events and handle them as appropriate.
2022-12-14 18:00:40 +03:00
|
|
|
TRY(handle_text_next_line_show_string_set_spacing(op.arguments()));
|
2021-05-23 07:09:33 +03:00
|
|
|
break;
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
2022-03-06 04:12:58 +03:00
|
|
|
|
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(save_state)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
m_graphics_state_stack.append(state());
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(restore_state)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
m_graphics_state_stack.take_last();
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(concatenate_matrix)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
Gfx::AffineTransform new_transform(
|
|
|
|
args[0].to_float(),
|
|
|
|
args[1].to_float(),
|
|
|
|
args[2].to_float(),
|
|
|
|
args[3].to_float(),
|
|
|
|
args[4].to_float(),
|
|
|
|
args[5].to_float());
|
|
|
|
|
|
|
|
state().ctm.multiply(new_transform);
|
|
|
|
m_text_rendering_matrix_is_dirty = true;
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(set_line_width)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
state().line_width = args[0].to_float();
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(set_line_cap)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2021-09-19 21:56:05 +03:00
|
|
|
state().line_cap_style = static_cast<LineCapStyle>(args[0].get<int>());
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(set_line_join)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2021-09-19 21:56:05 +03:00
|
|
|
state().line_join_style = static_cast<LineJoinStyle>(args[0].get<int>());
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(set_miter_limit)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
state().miter_limit = args[0].to_float();
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(set_dash_pattern)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2022-03-06 03:30:55 +03:00
|
|
|
auto dash_array = MUST(m_document->resolve_to<ArrayObject>(args[0]));
|
2021-05-10 20:50:39 +03:00
|
|
|
Vector<int> pattern;
|
|
|
|
for (auto& element : *dash_array)
|
2022-11-22 21:38:24 +03:00
|
|
|
pattern.append(element.to_int());
|
2021-09-19 21:56:05 +03:00
|
|
|
state().line_dash_pattern = LineDashPattern { pattern, args[1].get<int>() };
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2022-03-06 04:12:58 +03:00
|
|
|
RENDERER_TODO(set_color_rendering_intent)
|
|
|
|
RENDERER_TODO(set_flatness_tolerance)
|
2021-05-28 00:22:24 +03:00
|
|
|
|
|
|
|
RENDERER_HANDLER(set_graphics_state_from_dict)
|
|
|
|
{
|
2022-11-21 08:28:41 +03:00
|
|
|
auto resources = extra_resources.value_or(m_page.resources);
|
2022-03-06 03:30:55 +03:00
|
|
|
auto dict_name = MUST(m_document->resolve_to<NameObject>(args[0]))->name();
|
2022-11-21 08:28:41 +03:00
|
|
|
auto ext_gstate_dict = MUST(resources->get_dict(m_document, CommonNames::ExtGState));
|
2022-03-06 03:30:55 +03:00
|
|
|
auto target_dict = MUST(ext_gstate_dict->get_dict(m_document, dict_name));
|
2022-03-06 04:12:58 +03:00
|
|
|
TRY(set_graphics_state_from_dict(target_dict));
|
|
|
|
return {};
|
2021-05-28 00:22:24 +03:00
|
|
|
}
|
2021-05-10 20:50:39 +03:00
|
|
|
|
2021-05-23 21:43:28 +03:00
|
|
|
RENDERER_HANDLER(path_move)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2021-05-23 21:43:28 +03:00
|
|
|
m_current_path.move_to(map(args[0].to_float(), args[1].to_float()));
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(path_line)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2021-05-23 21:43:28 +03:00
|
|
|
VERIFY(!m_current_path.segments().is_empty());
|
|
|
|
m_current_path.line_to(map(args[0].to_float(), args[1].to_float()));
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2022-11-21 08:19:26 +03:00
|
|
|
RENDERER_HANDLER(path_cubic_bezier_curve)
|
|
|
|
{
|
|
|
|
VERIFY(args.size() == 6);
|
|
|
|
m_current_path.cubic_bezier_curve_to(
|
|
|
|
map(args[0].to_float(), args[1].to_float()),
|
|
|
|
map(args[2].to_float(), args[3].to_float()),
|
|
|
|
map(args[4].to_float(), args[5].to_float()));
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(path_cubic_bezier_curve_no_first_control)
|
|
|
|
{
|
|
|
|
VERIFY(args.size() == 4);
|
|
|
|
VERIFY(!m_current_path.segments().is_empty());
|
|
|
|
auto current_point = m_current_path.segments().rbegin()->point();
|
|
|
|
m_current_path.cubic_bezier_curve_to(
|
|
|
|
current_point,
|
|
|
|
map(args[0].to_float(), args[1].to_float()),
|
|
|
|
map(args[2].to_float(), args[3].to_float()));
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(path_cubic_bezier_curve_no_second_control)
|
|
|
|
{
|
|
|
|
VERIFY(args.size() == 4);
|
|
|
|
VERIFY(!m_current_path.segments().is_empty());
|
|
|
|
auto first_control_point = map(args[0].to_float(), args[1].to_float());
|
|
|
|
auto second_control_point = map(args[2].to_float(), args[3].to_float());
|
|
|
|
m_current_path.cubic_bezier_curve_to(
|
|
|
|
first_control_point,
|
|
|
|
second_control_point,
|
|
|
|
second_control_point);
|
|
|
|
return {};
|
|
|
|
}
|
2021-05-23 07:09:33 +03:00
|
|
|
|
|
|
|
RENDERER_HANDLER(path_close)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2021-05-23 21:43:28 +03:00
|
|
|
m_current_path.close();
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(path_append_rect)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2022-11-26 07:53:32 +03:00
|
|
|
auto rect = Gfx::FloatRect(args[0].to_float(), args[1].to_float(), args[2].to_float(), args[3].to_float());
|
|
|
|
rect_path(m_current_path, map(rect));
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2022-11-23 16:03:26 +03:00
|
|
|
///
|
|
|
|
// Path painting operations
|
|
|
|
///
|
|
|
|
|
|
|
|
void Renderer::begin_path_paint()
|
|
|
|
{
|
|
|
|
auto bounding_box = map(state().clipping_paths.current.bounding_box());
|
|
|
|
m_painter.clear_clip_rect();
|
2022-11-23 16:11:29 +03:00
|
|
|
if (m_rendering_preferences.show_clipping_paths) {
|
|
|
|
m_painter.stroke_path(rect_path(bounding_box), Color::Black, 1);
|
|
|
|
}
|
2022-11-23 16:03:26 +03:00
|
|
|
m_painter.add_clip_rect(bounding_box.to_type<int>());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::end_path_paint()
|
|
|
|
{
|
|
|
|
m_current_path.clear();
|
|
|
|
m_painter.clear_clip_rect();
|
|
|
|
state().clipping_paths.current = state().clipping_paths.next;
|
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(path_stroke)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2022-11-23 16:03:26 +03:00
|
|
|
begin_path_paint();
|
2022-03-24 21:50:37 +03:00
|
|
|
m_anti_aliasing_painter.stroke_path(m_current_path, state().stroke_color, state().line_width);
|
2022-11-23 16:03:26 +03:00
|
|
|
end_path_paint();
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(path_close_and_stroke)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2021-05-23 21:43:28 +03:00
|
|
|
m_current_path.close();
|
2022-03-06 04:12:58 +03:00
|
|
|
TRY(handle_path_stroke(args));
|
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(path_fill_nonzero)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2022-11-23 16:03:26 +03:00
|
|
|
begin_path_paint();
|
2022-03-24 21:50:37 +03:00
|
|
|
m_anti_aliasing_painter.fill_path(m_current_path, state().paint_color, Gfx::Painter::WindingRule::Nonzero);
|
2022-11-23 16:03:26 +03:00
|
|
|
end_path_paint();
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(path_fill_nonzero_deprecated)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2022-11-24 04:38:23 +03:00
|
|
|
return handle_path_fill_nonzero(args);
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(path_fill_evenodd)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2022-11-23 16:03:26 +03:00
|
|
|
begin_path_paint();
|
2022-03-24 21:50:37 +03:00
|
|
|
m_anti_aliasing_painter.fill_path(m_current_path, state().paint_color, Gfx::Painter::WindingRule::EvenOdd);
|
2022-11-23 16:03:26 +03:00
|
|
|
end_path_paint();
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(path_fill_stroke_nonzero)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2022-03-24 21:50:37 +03:00
|
|
|
m_anti_aliasing_painter.stroke_path(m_current_path, state().stroke_color, state().line_width);
|
2022-11-24 04:38:23 +03:00
|
|
|
return handle_path_fill_nonzero(args);
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(path_fill_stroke_evenodd)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2022-03-24 21:50:37 +03:00
|
|
|
m_anti_aliasing_painter.stroke_path(m_current_path, state().stroke_color, state().line_width);
|
2022-11-24 04:38:23 +03:00
|
|
|
return handle_path_fill_evenodd(args);
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(path_close_fill_stroke_nonzero)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2021-05-23 21:43:28 +03:00
|
|
|
m_current_path.close();
|
2022-11-24 04:38:23 +03:00
|
|
|
return handle_path_fill_stroke_nonzero(args);
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(path_close_fill_stroke_evenodd)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2021-05-23 21:43:28 +03:00
|
|
|
m_current_path.close();
|
2022-11-24 04:38:23 +03:00
|
|
|
return handle_path_fill_stroke_evenodd(args);
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(path_end)
|
|
|
|
{
|
2022-11-23 16:03:26 +03:00
|
|
|
begin_path_paint();
|
|
|
|
end_path_paint();
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-23 07:09:33 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 21:43:28 +03:00
|
|
|
RENDERER_HANDLER(path_intersect_clip_nonzero)
|
|
|
|
{
|
2022-11-23 16:03:26 +03:00
|
|
|
// FIXME: Support arbitrary path clipping in Path and utilize that here
|
|
|
|
auto next_clipping_bbox = state().clipping_paths.next.bounding_box();
|
|
|
|
next_clipping_bbox.intersect(m_current_path.bounding_box());
|
|
|
|
state().clipping_paths.next = rect_path(next_clipping_bbox);
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-23 21:43:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(path_intersect_clip_evenodd)
|
|
|
|
{
|
2022-11-23 16:03:26 +03:00
|
|
|
// FIXME: Should have different behavior than path_intersect_clip_nonzero
|
|
|
|
return handle_path_intersect_clip_nonzero(args);
|
2021-05-23 21:43:28 +03:00
|
|
|
}
|
2021-05-23 07:09:33 +03:00
|
|
|
|
|
|
|
RENDERER_HANDLER(text_begin)
|
|
|
|
{
|
|
|
|
m_text_matrix = Gfx::AffineTransform();
|
|
|
|
m_text_line_matrix = Gfx::AffineTransform();
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-23 07:09:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(text_end)
|
|
|
|
{
|
|
|
|
// FIXME: Do we need to do anything here?
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-23 07:09:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(text_set_char_space)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
text_state().character_spacing = args[0].to_float();
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(text_set_word_space)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
text_state().word_spacing = args[0].to_float();
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(text_set_horizontal_scale)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
m_text_rendering_matrix_is_dirty = true;
|
|
|
|
text_state().horizontal_scaling = args[0].to_float() / 100.0f;
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(text_set_leading)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
text_state().leading = args[0].to_float();
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(text_set_font)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2022-11-21 08:28:41 +03:00
|
|
|
auto resources = extra_resources.value_or(m_page.resources);
|
2022-03-06 03:30:55 +03:00
|
|
|
auto target_font_name = MUST(m_document->resolve_to<NameObject>(args[0]))->name();
|
2022-11-21 08:28:41 +03:00
|
|
|
auto fonts_dictionary = MUST(resources->get_dict(m_document, CommonNames::Font));
|
2022-03-06 03:30:55 +03:00
|
|
|
auto font_dictionary = MUST(fonts_dictionary->get_dict(m_document, target_font_name));
|
2021-05-10 20:50:39 +03:00
|
|
|
|
2021-05-28 21:55:51 +03:00
|
|
|
text_state().font_size = args[1].to_float();
|
2022-11-22 21:46:29 +03:00
|
|
|
|
|
|
|
auto& text_rendering_matrix = calculate_text_rendering_matrix();
|
|
|
|
auto font_size = text_rendering_matrix.x_scale() * text_state().font_size;
|
|
|
|
auto font = TRY(PDFFont::create(m_document, font_dictionary, font_size));
|
|
|
|
text_state().font = font;
|
2021-05-10 20:50:39 +03:00
|
|
|
|
|
|
|
m_text_rendering_matrix_is_dirty = true;
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(text_set_rendering_mode)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2021-09-19 21:56:05 +03:00
|
|
|
text_state().rendering_mode = static_cast<TextRenderingMode>(args[0].get<int>());
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(text_set_rise)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
m_text_rendering_matrix_is_dirty = true;
|
|
|
|
text_state().rise = args[0].to_float();
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(text_next_line_offset)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
Gfx::AffineTransform transform(1.0f, 0.0f, 0.0f, 1.0f, args[0].to_float(), args[1].to_float());
|
2022-03-24 21:14:31 +03:00
|
|
|
m_text_line_matrix.multiply(transform);
|
|
|
|
m_text_matrix = m_text_line_matrix;
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(text_next_line_and_set_leading)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
text_state().leading = -args[1].to_float();
|
2022-03-06 04:12:58 +03:00
|
|
|
TRY(handle_text_next_line_offset(args));
|
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(text_set_matrix_and_line_matrix)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
Gfx::AffineTransform new_transform(
|
|
|
|
args[0].to_float(),
|
|
|
|
args[1].to_float(),
|
|
|
|
args[2].to_float(),
|
|
|
|
args[3].to_float(),
|
|
|
|
args[4].to_float(),
|
|
|
|
args[5].to_float());
|
|
|
|
m_text_line_matrix = new_transform;
|
|
|
|
m_text_matrix = new_transform;
|
|
|
|
m_text_rendering_matrix_is_dirty = true;
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(text_next_line)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2022-03-06 04:12:58 +03:00
|
|
|
TRY(handle_text_next_line_offset({ 0.0f, -text_state().leading }));
|
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(text_show_string)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2022-03-06 03:30:55 +03:00
|
|
|
auto text = MUST(m_document->resolve_to<StringObject>(args[0]))->string();
|
2021-05-10 20:50:39 +03:00
|
|
|
show_text(text);
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(text_next_line_show_string)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2022-03-06 04:12:58 +03:00
|
|
|
TRY(handle_text_next_line(args));
|
|
|
|
TRY(handle_text_show_string(args));
|
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2022-03-06 04:12:58 +03:00
|
|
|
RENDERER_TODO(text_next_line_show_string_set_spacing)
|
2021-05-28 00:53:10 +03:00
|
|
|
|
|
|
|
RENDERER_HANDLER(text_show_string_array)
|
|
|
|
{
|
2022-03-06 03:30:55 +03:00
|
|
|
auto elements = MUST(m_document->resolve_to<ArrayObject>(args[0]))->elements();
|
2021-05-28 00:53:10 +03:00
|
|
|
float next_shift = 0.0f;
|
|
|
|
|
|
|
|
for (auto& element : elements) {
|
2021-09-19 21:56:05 +03:00
|
|
|
if (element.has<int>()) {
|
|
|
|
next_shift = element.get<int>();
|
|
|
|
} else if (element.has<float>()) {
|
|
|
|
next_shift = element.get<float>();
|
2021-05-28 00:53:10 +03:00
|
|
|
} else {
|
2022-08-19 10:02:46 +03:00
|
|
|
auto shift = next_shift / 1000.0f;
|
|
|
|
m_text_matrix.translate(-shift * text_state().font_size * text_state().horizontal_scaling, 0.0f);
|
2022-03-06 03:30:55 +03:00
|
|
|
auto str = element.get<NonnullRefPtr<Object>>()->cast<StringObject>()->string();
|
2022-08-19 10:02:46 +03:00
|
|
|
show_text(str);
|
2021-05-28 00:53:10 +03:00
|
|
|
}
|
|
|
|
}
|
2022-03-06 04:12:58 +03:00
|
|
|
|
|
|
|
return {};
|
2021-05-28 00:53:10 +03:00
|
|
|
}
|
|
|
|
|
2022-03-06 04:12:58 +03:00
|
|
|
RENDERER_TODO(type3_font_set_glyph_width)
|
|
|
|
RENDERER_TODO(type3_font_set_glyph_width_and_bbox)
|
2021-05-23 22:53:38 +03:00
|
|
|
|
|
|
|
RENDERER_HANDLER(set_stroking_space)
|
|
|
|
{
|
LibPDF: Refactor parsing of ColorSpaces
ColorSpaces can be specified in two ways: with a stream as operands of
the color space operations (CS/cs), or as a separate PDF object, which
is then referred to by other means (e.g., from Image XObjects and other
entities). These two modes of addressing a ColorSpace are slightly
different and need to be addressed separately. However, the current
implementation embedded the full logic of the first case in the routine
that created ColorSpace objects.
This commit refactors the creation of ColorSpace to support both cases.
First, a new ColorSpaceFamily class encapsulates the static aspects of a
family, like its name or whether color space construction never requires
parameters. Then we define the supported ColorSpaceFamily objects.
On top of this also sit a breakage on how ColorSpaces are created. Two
methods are now offered: one only providing construction of no-argument
color spaces (and thus taking a simple name), and another taking an
ArrayObject, hence used to create ColorSpaces requiring arguments.
Finally, on top of *that* two ways to get a color space in the Renderer
are made available: the first creates a ColorSpace with a name and a
Resources dictionary, and another takes an Object. These model the two
addressing modes described above.
2022-11-24 07:40:24 +03:00
|
|
|
state().stroke_color_space = TRY(get_color_space_from_resources(args[0], extra_resources.value_or(m_page.resources)));
|
2021-05-28 00:01:37 +03:00
|
|
|
VERIFY(state().stroke_color_space);
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-23 22:53:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(set_painting_space)
|
|
|
|
{
|
LibPDF: Refactor parsing of ColorSpaces
ColorSpaces can be specified in two ways: with a stream as operands of
the color space operations (CS/cs), or as a separate PDF object, which
is then referred to by other means (e.g., from Image XObjects and other
entities). These two modes of addressing a ColorSpace are slightly
different and need to be addressed separately. However, the current
implementation embedded the full logic of the first case in the routine
that created ColorSpace objects.
This commit refactors the creation of ColorSpace to support both cases.
First, a new ColorSpaceFamily class encapsulates the static aspects of a
family, like its name or whether color space construction never requires
parameters. Then we define the supported ColorSpaceFamily objects.
On top of this also sit a breakage on how ColorSpaces are created. Two
methods are now offered: one only providing construction of no-argument
color spaces (and thus taking a simple name), and another taking an
ArrayObject, hence used to create ColorSpaces requiring arguments.
Finally, on top of *that* two ways to get a color space in the Renderer
are made available: the first creates a ColorSpace with a name and a
Resources dictionary, and another takes an Object. These model the two
addressing modes described above.
2022-11-24 07:40:24 +03:00
|
|
|
state().paint_color_space = TRY(get_color_space_from_resources(args[0], extra_resources.value_or(m_page.resources)));
|
2021-05-28 00:01:37 +03:00
|
|
|
VERIFY(state().paint_color_space);
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-23 22:53:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(set_stroking_color)
|
|
|
|
{
|
2021-05-28 00:01:37 +03:00
|
|
|
state().stroke_color = state().stroke_color_space->color(args);
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-23 22:53:38 +03:00
|
|
|
}
|
|
|
|
|
2022-03-24 20:24:00 +03:00
|
|
|
RENDERER_HANDLER(set_stroking_color_extended)
|
|
|
|
{
|
|
|
|
// FIXME: Handle Pattern color spaces
|
|
|
|
auto last_arg = args.last();
|
|
|
|
if (last_arg.has<NonnullRefPtr<Object>>() && last_arg.get<NonnullRefPtr<Object>>()->is<NameObject>())
|
|
|
|
TODO();
|
|
|
|
|
|
|
|
state().stroke_color = state().stroke_color_space->color(args);
|
|
|
|
return {};
|
|
|
|
}
|
2021-05-23 22:53:38 +03:00
|
|
|
|
|
|
|
RENDERER_HANDLER(set_painting_color)
|
|
|
|
{
|
2021-05-28 00:01:37 +03:00
|
|
|
state().paint_color = state().paint_color_space->color(args);
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-23 22:53:38 +03:00
|
|
|
}
|
|
|
|
|
2022-03-24 20:24:00 +03:00
|
|
|
RENDERER_HANDLER(set_painting_color_extended)
|
|
|
|
{
|
|
|
|
// FIXME: Handle Pattern color spaces
|
|
|
|
auto last_arg = args.last();
|
|
|
|
if (last_arg.has<NonnullRefPtr<Object>>() && last_arg.get<NonnullRefPtr<Object>>()->is<NameObject>())
|
|
|
|
TODO();
|
|
|
|
|
|
|
|
state().paint_color = state().paint_color_space->color(args);
|
|
|
|
return {};
|
|
|
|
}
|
2021-05-23 22:53:38 +03:00
|
|
|
|
|
|
|
RENDERER_HANDLER(set_stroking_color_and_space_to_gray)
|
|
|
|
{
|
2021-05-28 00:01:37 +03:00
|
|
|
state().stroke_color_space = DeviceGrayColorSpace::the();
|
|
|
|
state().stroke_color = state().stroke_color_space->color(args);
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-23 22:53:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(set_painting_color_and_space_to_gray)
|
|
|
|
{
|
2021-05-28 00:01:37 +03:00
|
|
|
state().paint_color_space = DeviceGrayColorSpace::the();
|
|
|
|
state().paint_color = state().paint_color_space->color(args);
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-23 22:53:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(set_stroking_color_and_space_to_rgb)
|
|
|
|
{
|
2021-05-28 00:01:37 +03:00
|
|
|
state().stroke_color_space = DeviceRGBColorSpace::the();
|
|
|
|
state().stroke_color = state().stroke_color_space->color(args);
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-23 22:53:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(set_painting_color_and_space_to_rgb)
|
|
|
|
{
|
2021-05-28 00:01:37 +03:00
|
|
|
state().paint_color_space = DeviceRGBColorSpace::the();
|
|
|
|
state().paint_color = state().paint_color_space->color(args);
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-23 22:53:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(set_stroking_color_and_space_to_cmyk)
|
|
|
|
{
|
2021-05-28 00:01:37 +03:00
|
|
|
state().stroke_color_space = DeviceCMYKColorSpace::the();
|
|
|
|
state().stroke_color = state().stroke_color_space->color(args);
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-23 22:53:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(set_painting_color_and_space_to_cmyk)
|
|
|
|
{
|
2021-05-28 00:01:37 +03:00
|
|
|
state().paint_color_space = DeviceCMYKColorSpace::the();
|
|
|
|
state().paint_color = state().paint_color_space->color(args);
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_TODO(shade)
|
|
|
|
RENDERER_TODO(inline_image_begin)
|
|
|
|
RENDERER_TODO(inline_image_begin_data)
|
|
|
|
RENDERER_TODO(inline_image_end)
|
2022-11-21 08:32:09 +03:00
|
|
|
RENDERER_HANDLER(paint_xobject)
|
|
|
|
{
|
|
|
|
VERIFY(args.size() > 0);
|
|
|
|
auto resources = extra_resources.value_or(m_page.resources);
|
|
|
|
auto xobject_name = args[0].get<NonnullRefPtr<Object>>()->cast<NameObject>()->name();
|
|
|
|
auto xobjects_dict = MUST(resources->get_dict(m_document, CommonNames::XObject));
|
|
|
|
auto xobject = MUST(xobjects_dict->get_stream(m_document, xobject_name));
|
|
|
|
|
LibPDF: Add initial image display support
After adding support for XObject Form rendering, the next was to display
XObject images. This commit adds this initial support,
Images come in many shapes and forms: encodings: color spaces, bits per
component, width, height, etc. This initial support is constrained to
the color spaces we currently support, to images that use 8 bits per
component, to images that do *not* use the JPXDecode filter, and that
are not Masks. There are surely other constraints that aren't considered
in this initial support, so expect breakage here and there.
In addition to supporting images, we also support applying an alpha mask
(SMask) on them. Additionally, a new rendering preference allows to skip
image loading and rendering altogether, instead showing an empty
rectangle as a placeholder (useful for when actual images are not
supported). Since RenderingPreferences is becoming a bit more complex,
we add a hash option that will allow us to keep track of different
preferences (e.g., in a HashMap).
2022-11-24 21:01:53 +03:00
|
|
|
Optional<NonnullRefPtr<DictObject>> xobject_resources {};
|
|
|
|
if (xobject->dict()->contains(CommonNames::Resources)) {
|
|
|
|
xobject_resources = xobject->dict()->get_dict(m_document, CommonNames::Resources).value();
|
|
|
|
}
|
|
|
|
|
2022-11-21 08:32:09 +03:00
|
|
|
auto subtype = MUST(xobject->dict()->get_name(m_document, CommonNames::Subtype))->name();
|
|
|
|
if (subtype == CommonNames::Image) {
|
LibPDF: Add initial image display support
After adding support for XObject Form rendering, the next was to display
XObject images. This commit adds this initial support,
Images come in many shapes and forms: encodings: color spaces, bits per
component, width, height, etc. This initial support is constrained to
the color spaces we currently support, to images that use 8 bits per
component, to images that do *not* use the JPXDecode filter, and that
are not Masks. There are surely other constraints that aren't considered
in this initial support, so expect breakage here and there.
In addition to supporting images, we also support applying an alpha mask
(SMask) on them. Additionally, a new rendering preference allows to skip
image loading and rendering altogether, instead showing an empty
rectangle as a placeholder (useful for when actual images are not
supported). Since RenderingPreferences is becoming a bit more complex,
we add a hash option that will allow us to keep track of different
preferences (e.g., in a HashMap).
2022-11-24 21:01:53 +03:00
|
|
|
TRY(show_image(xobject));
|
2022-11-21 08:32:09 +03:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
MUST(handle_save_state({}));
|
|
|
|
Vector<Value> matrix;
|
|
|
|
if (xobject->dict()->contains(CommonNames::Matrix)) {
|
|
|
|
matrix = xobject->dict()->get_array(m_document, CommonNames::Matrix).value()->elements();
|
|
|
|
} else {
|
|
|
|
matrix = Vector { Value { 1 }, Value { 0 }, Value { 0 }, Value { 1 }, Value { 0 }, Value { 0 } };
|
|
|
|
}
|
|
|
|
MUST(handle_concatenate_matrix(matrix));
|
|
|
|
auto operators = TRY(Parser::parse_operators(m_document, xobject->bytes()));
|
|
|
|
for (auto& op : operators)
|
|
|
|
TRY(handle_operator(op, xobject_resources));
|
|
|
|
MUST(handle_restore_state({}));
|
|
|
|
return {};
|
|
|
|
}
|
2022-03-06 08:35:33 +03:00
|
|
|
|
|
|
|
RENDERER_HANDLER(marked_content_point)
|
|
|
|
{
|
|
|
|
// nop
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(marked_content_designate)
|
|
|
|
{
|
|
|
|
// nop
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(marked_content_begin)
|
|
|
|
{
|
|
|
|
// nop
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(marked_content_begin_with_property_list)
|
|
|
|
{
|
|
|
|
// nop
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(marked_content_end)
|
|
|
|
{
|
|
|
|
// nop
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2022-03-06 04:12:58 +03:00
|
|
|
RENDERER_TODO(compatibility_begin)
|
|
|
|
RENDERER_TODO(compatibility_end)
|
2021-05-23 07:09:33 +03:00
|
|
|
|
2021-05-10 20:50:39 +03:00
|
|
|
template<typename T>
|
|
|
|
Gfx::Point<T> Renderer::map(T x, T y) const
|
|
|
|
{
|
2022-11-26 07:53:32 +03:00
|
|
|
return state().ctm.map(Gfx::Point<T> { x, y });
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
Gfx::Size<T> Renderer::map(Gfx::Size<T> size) const
|
|
|
|
{
|
|
|
|
return state().ctm.map(size);
|
|
|
|
}
|
|
|
|
|
2021-05-23 22:53:38 +03:00
|
|
|
template<typename T>
|
|
|
|
Gfx::Rect<T> Renderer::map(Gfx::Rect<T> rect) const
|
|
|
|
{
|
|
|
|
return state().ctm.map(rect);
|
|
|
|
}
|
|
|
|
|
2022-03-06 04:12:58 +03:00
|
|
|
PDFErrorOr<void> Renderer::set_graphics_state_from_dict(NonnullRefPtr<DictObject> dict)
|
2021-05-28 00:22:24 +03:00
|
|
|
{
|
|
|
|
if (dict->contains(CommonNames::LW))
|
2022-03-06 04:12:58 +03:00
|
|
|
TRY(handle_set_line_width({ dict->get_value(CommonNames::LW) }));
|
2021-05-28 00:22:24 +03:00
|
|
|
|
|
|
|
if (dict->contains(CommonNames::LC))
|
2022-03-06 04:12:58 +03:00
|
|
|
TRY(handle_set_line_cap({ dict->get_value(CommonNames::LC) }));
|
2021-05-28 00:22:24 +03:00
|
|
|
|
|
|
|
if (dict->contains(CommonNames::LJ))
|
2022-03-06 04:12:58 +03:00
|
|
|
TRY(handle_set_line_join({ dict->get_value(CommonNames::LJ) }));
|
2021-05-28 00:22:24 +03:00
|
|
|
|
|
|
|
if (dict->contains(CommonNames::ML))
|
2022-03-06 04:12:58 +03:00
|
|
|
TRY(handle_set_miter_limit({ dict->get_value(CommonNames::ML) }));
|
2021-05-28 00:22:24 +03:00
|
|
|
|
2022-03-06 04:12:58 +03:00
|
|
|
if (dict->contains(CommonNames::D)) {
|
|
|
|
auto array = MUST(dict->get_array(m_document, CommonNames::D));
|
|
|
|
TRY(handle_set_dash_pattern(array->elements()));
|
|
|
|
}
|
2021-05-28 00:22:24 +03:00
|
|
|
|
|
|
|
if (dict->contains(CommonNames::FL))
|
2022-03-06 04:12:58 +03:00
|
|
|
TRY(handle_set_flatness_tolerance({ dict->get_value(CommonNames::FL) }));
|
|
|
|
|
|
|
|
return {};
|
2021-05-28 00:22:24 +03:00
|
|
|
}
|
|
|
|
|
2022-12-04 21:02:33 +03:00
|
|
|
void Renderer::show_text(DeprecatedString const& string)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2021-05-28 21:55:51 +03:00
|
|
|
auto& text_rendering_matrix = calculate_text_rendering_matrix();
|
|
|
|
|
2022-03-25 07:56:14 +03:00
|
|
|
auto font_size = text_rendering_matrix.x_scale() * text_state().font_size;
|
2021-05-28 21:55:51 +03:00
|
|
|
|
|
|
|
auto glyph_position = text_rendering_matrix.map(Gfx::FloatPoint { 0.0f, 0.0f });
|
2022-08-25 12:06:21 +03:00
|
|
|
|
2021-05-28 21:55:51 +03:00
|
|
|
auto original_position = glyph_position;
|
2021-05-10 20:50:39 +03:00
|
|
|
|
2022-03-08 22:10:06 +03:00
|
|
|
for (auto char_code : string.bytes()) {
|
2022-11-22 21:46:29 +03:00
|
|
|
auto char_width = text_state().font->get_char_width(char_code);
|
2022-08-25 12:06:21 +03:00
|
|
|
auto glyph_width = char_width * font_size;
|
2023-01-23 17:07:04 +03:00
|
|
|
text_state().font->draw_glyph(m_painter, glyph_position, glyph_width, char_code, state().paint_color);
|
2022-08-19 10:02:46 +03:00
|
|
|
auto tx = glyph_width;
|
2021-05-10 20:50:39 +03:00
|
|
|
tx += text_state().character_spacing;
|
|
|
|
tx *= text_state().horizontal_scaling;
|
2021-05-28 21:55:51 +03:00
|
|
|
glyph_position += { tx, 0.0f };
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
2021-05-28 21:55:51 +03:00
|
|
|
|
|
|
|
// Update text matrix
|
|
|
|
auto delta_x = glyph_position.x() - original_position.x();
|
|
|
|
m_text_rendering_matrix_is_dirty = true;
|
2022-03-24 21:14:31 +03:00
|
|
|
m_text_matrix.translate(delta_x / text_rendering_matrix.x_scale(), 0.0f);
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
LibPDF: Add initial image display support
After adding support for XObject Form rendering, the next was to display
XObject images. This commit adds this initial support,
Images come in many shapes and forms: encodings: color spaces, bits per
component, width, height, etc. This initial support is constrained to
the color spaces we currently support, to images that use 8 bits per
component, to images that do *not* use the JPXDecode filter, and that
are not Masks. There are surely other constraints that aren't considered
in this initial support, so expect breakage here and there.
In addition to supporting images, we also support applying an alpha mask
(SMask) on them. Additionally, a new rendering preference allows to skip
image loading and rendering altogether, instead showing an empty
rectangle as a placeholder (useful for when actual images are not
supported). Since RenderingPreferences is becoming a bit more complex,
we add a hash option that will allow us to keep track of different
preferences (e.g., in a HashMap).
2022-11-24 21:01:53 +03:00
|
|
|
PDFErrorOr<NonnullRefPtr<Gfx::Bitmap>> Renderer::load_image(NonnullRefPtr<StreamObject> image)
|
|
|
|
{
|
|
|
|
auto image_dict = image->dict();
|
|
|
|
auto filter_object = TRY(image_dict->get_object(m_document, CommonNames::Filter));
|
|
|
|
auto width = image_dict->get_value(CommonNames::Width).get<int>();
|
|
|
|
auto height = image_dict->get_value(CommonNames::Height).get<int>();
|
|
|
|
|
2023-01-09 03:23:00 +03:00
|
|
|
auto is_filter = [&](DeprecatedFlyString const& name) {
|
LibPDF: Add initial image display support
After adding support for XObject Form rendering, the next was to display
XObject images. This commit adds this initial support,
Images come in many shapes and forms: encodings: color spaces, bits per
component, width, height, etc. This initial support is constrained to
the color spaces we currently support, to images that use 8 bits per
component, to images that do *not* use the JPXDecode filter, and that
are not Masks. There are surely other constraints that aren't considered
in this initial support, so expect breakage here and there.
In addition to supporting images, we also support applying an alpha mask
(SMask) on them. Additionally, a new rendering preference allows to skip
image loading and rendering altogether, instead showing an empty
rectangle as a placeholder (useful for when actual images are not
supported). Since RenderingPreferences is becoming a bit more complex,
we add a hash option that will allow us to keep track of different
preferences (e.g., in a HashMap).
2022-11-24 21:01:53 +03:00
|
|
|
if (filter_object->is<NameObject>())
|
|
|
|
return filter_object->cast<NameObject>()->name() == name;
|
|
|
|
auto filters = filter_object->cast<ArrayObject>();
|
|
|
|
return MUST(filters->get_name_at(m_document, 0))->name() == name;
|
|
|
|
};
|
|
|
|
if (is_filter(CommonNames::JPXDecode)) {
|
|
|
|
return Error(Error::Type::RenderingUnsupported, "JPXDecode filter");
|
|
|
|
}
|
|
|
|
if (image_dict->contains(CommonNames::ImageMask)) {
|
|
|
|
auto is_mask = image_dict->get_value(CommonNames::ImageMask).get<bool>();
|
|
|
|
if (is_mask) {
|
|
|
|
return Error(Error::Type::RenderingUnsupported, "Image masks");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto color_space_object = MUST(image_dict->get_object(m_document, CommonNames::ColorSpace));
|
|
|
|
auto color_space = TRY(get_color_space_from_document(color_space_object));
|
|
|
|
auto bits_per_component = image_dict->get_value(CommonNames::BitsPerComponent).get<int>();
|
|
|
|
if (bits_per_component != 8) {
|
|
|
|
return Error(Error::Type::RenderingUnsupported, "Image's bit per component != 8");
|
|
|
|
}
|
|
|
|
|
|
|
|
Vector<float> decode_array;
|
|
|
|
if (image_dict->contains(CommonNames::Decode)) {
|
|
|
|
decode_array = MUST(image_dict->get_array(m_document, CommonNames::Decode))->float_elements();
|
|
|
|
} else {
|
|
|
|
decode_array = color_space->default_decode();
|
|
|
|
}
|
|
|
|
Vector<LinearInterpolation1D> component_value_decoders;
|
|
|
|
component_value_decoders.ensure_capacity(decode_array.size());
|
|
|
|
for (size_t i = 0; i < decode_array.size(); i += 2) {
|
|
|
|
auto dmin = decode_array[i];
|
|
|
|
auto dmax = decode_array[i + 1];
|
|
|
|
component_value_decoders.empend(0.0f, 255.0f, dmin, dmax);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_filter(CommonNames::DCTDecode)) {
|
|
|
|
// TODO: stream objects could store Variant<bytes/Bitmap> to avoid seialisation/deserialisation here
|
2023-01-20 22:06:05 +03:00
|
|
|
return TRY(Gfx::Bitmap::create_from_serialized_bytes(image->bytes()));
|
LibPDF: Add initial image display support
After adding support for XObject Form rendering, the next was to display
XObject images. This commit adds this initial support,
Images come in many shapes and forms: encodings: color spaces, bits per
component, width, height, etc. This initial support is constrained to
the color spaces we currently support, to images that use 8 bits per
component, to images that do *not* use the JPXDecode filter, and that
are not Masks. There are surely other constraints that aren't considered
in this initial support, so expect breakage here and there.
In addition to supporting images, we also support applying an alpha mask
(SMask) on them. Additionally, a new rendering preference allows to skip
image loading and rendering altogether, instead showing an empty
rectangle as a placeholder (useful for when actual images are not
supported). Since RenderingPreferences is becoming a bit more complex,
we add a hash option that will allow us to keep track of different
preferences (e.g., in a HashMap).
2022-11-24 21:01:53 +03:00
|
|
|
}
|
|
|
|
|
2023-01-20 22:06:05 +03:00
|
|
|
auto bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { width, height }));
|
LibPDF: Add initial image display support
After adding support for XObject Form rendering, the next was to display
XObject images. This commit adds this initial support,
Images come in many shapes and forms: encodings: color spaces, bits per
component, width, height, etc. This initial support is constrained to
the color spaces we currently support, to images that use 8 bits per
component, to images that do *not* use the JPXDecode filter, and that
are not Masks. There are surely other constraints that aren't considered
in this initial support, so expect breakage here and there.
In addition to supporting images, we also support applying an alpha mask
(SMask) on them. Additionally, a new rendering preference allows to skip
image loading and rendering altogether, instead showing an empty
rectangle as a placeholder (useful for when actual images are not
supported). Since RenderingPreferences is becoming a bit more complex,
we add a hash option that will allow us to keep track of different
preferences (e.g., in a HashMap).
2022-11-24 21:01:53 +03:00
|
|
|
int x = 0;
|
|
|
|
int y = 0;
|
|
|
|
int const n_components = color_space->number_of_components();
|
|
|
|
auto const bytes_per_component = bits_per_component / 8;
|
|
|
|
Vector<Value> component_values;
|
|
|
|
component_values.resize(n_components);
|
|
|
|
auto content = image->bytes();
|
|
|
|
while (!content.is_empty() && y < height) {
|
|
|
|
auto sample = content.slice(0, bytes_per_component * n_components);
|
|
|
|
content = content.slice(bytes_per_component * n_components);
|
|
|
|
for (int i = 0; i < n_components; ++i) {
|
|
|
|
auto component = sample.slice(0, bytes_per_component);
|
|
|
|
sample = sample.slice(bytes_per_component);
|
|
|
|
component_values[i] = Value { component_value_decoders[i].interpolate(component[0]) };
|
|
|
|
}
|
|
|
|
auto color = color_space->color(component_values);
|
|
|
|
bitmap->set_pixel(x, y, color);
|
|
|
|
++x;
|
|
|
|
if (x == width) {
|
|
|
|
x = 0;
|
|
|
|
++y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return bitmap;
|
|
|
|
}
|
|
|
|
|
|
|
|
Gfx::AffineTransform Renderer::calculate_image_space_transformation(int width, int height)
|
|
|
|
{
|
|
|
|
// Image space maps to a 1x1 unit of user space and starts at the top-left
|
|
|
|
auto image_space = state().ctm;
|
|
|
|
image_space.multiply(Gfx::AffineTransform(
|
|
|
|
1.0f / width,
|
|
|
|
0.0f,
|
|
|
|
0.0f,
|
|
|
|
-1.0f / height,
|
|
|
|
0.0f,
|
|
|
|
1.0f));
|
|
|
|
return image_space;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::show_empty_image(int width, int height)
|
|
|
|
{
|
|
|
|
auto image_space_transofmation = calculate_image_space_transformation(width, height);
|
|
|
|
auto image_border = image_space_transofmation.map(Gfx::IntRect { 0, 0, width, height });
|
|
|
|
m_painter.stroke_path(rect_path(image_border), Color::Black, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
PDFErrorOr<void> Renderer::show_image(NonnullRefPtr<StreamObject> image)
|
|
|
|
{
|
|
|
|
auto image_dict = image->dict();
|
|
|
|
auto width = image_dict->get_value(CommonNames::Width).get<int>();
|
|
|
|
auto height = image_dict->get_value(CommonNames::Height).get<int>();
|
|
|
|
|
|
|
|
if (!m_rendering_preferences.show_images) {
|
|
|
|
show_empty_image(width, height);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
auto image_bitmap = TRY(load_image(image));
|
|
|
|
if (image_dict->contains(CommonNames::SMask)) {
|
|
|
|
auto smask_bitmap = TRY(load_image(TRY(image_dict->get_stream(m_document, CommonNames::SMask))));
|
|
|
|
VERIFY(smask_bitmap->rect() == image_bitmap->rect());
|
|
|
|
for (int j = 0; j < image_bitmap->height(); ++j) {
|
|
|
|
for (int i = 0; i < image_bitmap->width(); ++i) {
|
|
|
|
auto image_color = image_bitmap->get_pixel(i, j);
|
|
|
|
auto smask_color = smask_bitmap->get_pixel(i, j);
|
|
|
|
image_color = image_color.with_alpha(smask_color.luminosity());
|
|
|
|
image_bitmap->set_pixel(i, j, image_color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto image_space = calculate_image_space_transformation(width, height);
|
|
|
|
auto image_rect = Gfx::FloatRect { 0, 0, width, height };
|
|
|
|
m_painter.draw_scaled_bitmap_with_transform(image_bitmap->rect(), image_bitmap, image_rect, image_space);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
LibPDF: Refactor parsing of ColorSpaces
ColorSpaces can be specified in two ways: with a stream as operands of
the color space operations (CS/cs), or as a separate PDF object, which
is then referred to by other means (e.g., from Image XObjects and other
entities). These two modes of addressing a ColorSpace are slightly
different and need to be addressed separately. However, the current
implementation embedded the full logic of the first case in the routine
that created ColorSpace objects.
This commit refactors the creation of ColorSpace to support both cases.
First, a new ColorSpaceFamily class encapsulates the static aspects of a
family, like its name or whether color space construction never requires
parameters. Then we define the supported ColorSpaceFamily objects.
On top of this also sit a breakage on how ColorSpaces are created. Two
methods are now offered: one only providing construction of no-argument
color spaces (and thus taking a simple name), and another taking an
ArrayObject, hence used to create ColorSpaces requiring arguments.
Finally, on top of *that* two ways to get a color space in the Renderer
are made available: the first creates a ColorSpace with a name and a
Resources dictionary, and another takes an Object. These model the two
addressing modes described above.
2022-11-24 07:40:24 +03:00
|
|
|
PDFErrorOr<NonnullRefPtr<ColorSpace>> Renderer::get_color_space_from_resources(Value const& value, NonnullRefPtr<DictObject> resources)
|
2021-05-23 22:53:38 +03:00
|
|
|
{
|
LibPDF: Refactor parsing of ColorSpaces
ColorSpaces can be specified in two ways: with a stream as operands of
the color space operations (CS/cs), or as a separate PDF object, which
is then referred to by other means (e.g., from Image XObjects and other
entities). These two modes of addressing a ColorSpace are slightly
different and need to be addressed separately. However, the current
implementation embedded the full logic of the first case in the routine
that created ColorSpace objects.
This commit refactors the creation of ColorSpace to support both cases.
First, a new ColorSpaceFamily class encapsulates the static aspects of a
family, like its name or whether color space construction never requires
parameters. Then we define the supported ColorSpaceFamily objects.
On top of this also sit a breakage on how ColorSpaces are created. Two
methods are now offered: one only providing construction of no-argument
color spaces (and thus taking a simple name), and another taking an
ArrayObject, hence used to create ColorSpaces requiring arguments.
Finally, on top of *that* two ways to get a color space in the Renderer
are made available: the first creates a ColorSpace with a name and a
Resources dictionary, and another takes an Object. These model the two
addressing modes described above.
2022-11-24 07:40:24 +03:00
|
|
|
auto color_space_name = value.get<NonnullRefPtr<Object>>()->cast<NameObject>()->name();
|
|
|
|
auto maybe_color_space_family = ColorSpaceFamily::get(color_space_name);
|
|
|
|
if (!maybe_color_space_family.is_error()) {
|
|
|
|
auto color_space_family = maybe_color_space_family.release_value();
|
|
|
|
if (color_space_family.never_needs_parameters()) {
|
|
|
|
return ColorSpace::create(color_space_name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
auto color_space_resource_dict = TRY(resources->get_dict(m_document, CommonNames::ColorSpace));
|
|
|
|
auto color_space_array = TRY(color_space_resource_dict->get_array(m_document, color_space_name));
|
|
|
|
return ColorSpace::create(m_document, color_space_array);
|
|
|
|
}
|
|
|
|
|
|
|
|
PDFErrorOr<NonnullRefPtr<ColorSpace>> Renderer::get_color_space_from_document(NonnullRefPtr<Object> color_space_object)
|
|
|
|
{
|
|
|
|
// Pattern cannot be a name in these cases
|
|
|
|
if (color_space_object->is<NameObject>()) {
|
|
|
|
return ColorSpace::create(color_space_object->cast<NameObject>()->name());
|
|
|
|
}
|
|
|
|
return ColorSpace::create(m_document, color_space_object->cast<ArrayObject>());
|
2021-05-23 22:53:38 +03:00
|
|
|
}
|
|
|
|
|
2021-06-01 21:16:11 +03:00
|
|
|
Gfx::AffineTransform const& Renderer::calculate_text_rendering_matrix()
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
if (m_text_rendering_matrix_is_dirty) {
|
|
|
|
m_text_rendering_matrix = Gfx::AffineTransform(
|
|
|
|
text_state().horizontal_scaling,
|
|
|
|
0.0f,
|
|
|
|
0.0f,
|
|
|
|
1.0f,
|
|
|
|
0.0f,
|
|
|
|
text_state().rise);
|
|
|
|
m_text_rendering_matrix.multiply(state().ctm);
|
2022-03-07 00:27:04 +03:00
|
|
|
m_text_rendering_matrix.multiply(m_text_matrix);
|
2021-05-10 20:50:39 +03:00
|
|
|
m_text_rendering_matrix_is_dirty = false;
|
|
|
|
}
|
|
|
|
return m_text_rendering_matrix;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|