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) \
|
2023-10-20 18:55:38 +03:00
|
|
|
PDFErrorOr<void> Renderer::handle_##name([[maybe_unused]] ReadonlySpan<Value> 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 {
|
|
|
|
|
2023-11-14 18:35:40 +03:00
|
|
|
// Use a RAII object to restore the graphics state, to make sure it gets restored even if
|
|
|
|
// a TRY(handle_operator()) causes us to exit the operators loop early.
|
|
|
|
// Explicitly resize stack size at the end so that if the recursive document contains
|
|
|
|
// `q q unsupportedop Q Q`, we undo the stack pushes from the inner `q q` even if
|
|
|
|
// `unsupportedop` terminates processing the inner instruction stream before `Q Q`
|
|
|
|
// would normally pop state.
|
|
|
|
class Renderer::ScopedState {
|
|
|
|
public:
|
|
|
|
ScopedState(Renderer& renderer)
|
|
|
|
: m_renderer(renderer)
|
|
|
|
, m_starting_stack_depth(m_renderer.m_graphics_state_stack.size())
|
|
|
|
{
|
|
|
|
MUST(m_renderer.handle_save_state({}));
|
|
|
|
}
|
|
|
|
~ScopedState()
|
|
|
|
{
|
|
|
|
m_renderer.m_graphics_state_stack.shrink(m_starting_stack_depth);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
Renderer& m_renderer;
|
|
|
|
size_t m_starting_stack_depth;
|
|
|
|
};
|
|
|
|
|
2023-12-07 15:53:13 +03:00
|
|
|
PDFErrorsOr<void> Renderer::render(Document& document, Page const& page, RefPtr<Gfx::Bitmap> bitmap, Color background_color, RenderingPreferences rendering_preferences)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2023-12-07 15:53:13 +03:00
|
|
|
return Renderer(document, page, bitmap, background_color, 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();
|
|
|
|
}
|
|
|
|
|
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>
|
2023-02-03 02:11:27 +03:00
|
|
|
static Gfx::Path rect_path(Gfx::Rect<T> const& rect)
|
2022-11-23 16:03:26 +03:00
|
|
|
{
|
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
|
|
|
}
|
|
|
|
|
2023-12-07 15:53:13 +03:00
|
|
|
Renderer::Renderer(RefPtr<Document> document, Page const& page, RefPtr<Gfx::Bitmap> bitmap, Color background_color, 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);
|
|
|
|
|
2023-02-03 02:11:27 +03:00
|
|
|
auto initial_clipping_path = rect_path(userspace_matrix.map(Gfx::FloatRect(0, 0, width, height)));
|
2022-11-23 16:03:26 +03:00
|
|
|
m_graphics_state_stack.append(GraphicsState { userspace_matrix, { initial_clipping_path, initial_clipping_path } });
|
2021-05-10 20:50:39 +03:00
|
|
|
|
2023-12-07 15:53:13 +03:00
|
|
|
m_bitmap->fill(background_color);
|
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
|
|
|
PDFErrorsOr<void> Renderer::render()
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2023-07-12 16:27:26 +03:00
|
|
|
auto operators = TRY(Parser::parse_operators(m_document, TRY(m_page.page_contents(*m_document))));
|
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());
|
2023-03-24 23:56:09 +03:00
|
|
|
state().line_dash_pattern = LineDashPattern { pattern, args[1].to_int() };
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2023-10-19 14:58:22 +03:00
|
|
|
RENDERER_HANDLER(set_color_rendering_intent)
|
|
|
|
{
|
|
|
|
state().color_rendering_intent = MUST(m_document->resolve_to<NameObject>(args[0]))->name();
|
|
|
|
return {};
|
|
|
|
}
|
2023-07-12 21:25:22 +03:00
|
|
|
|
|
|
|
RENDERER_HANDLER(set_flatness_tolerance)
|
|
|
|
{
|
|
|
|
state().flatness_tolerance = args[0].to_float();
|
|
|
|
return {};
|
|
|
|
}
|
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());
|
2023-03-06 16:17:01 +03:00
|
|
|
auto current_point = (*m_current_path.segments().rbegin())->point();
|
2022-11-21 08:19:26 +03:00
|
|
|
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());
|
2024-01-16 23:32:26 +03:00
|
|
|
// Note: The path of the rectangle is mapped (rather than the rectangle).
|
|
|
|
// This is because negative width/heights are possible, and result in different
|
|
|
|
// winding orders, but this is lost by Gfx::AffineTransform::map().
|
|
|
|
m_current_path.append_path(map(rect_path(rect)));
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2024-01-17 04:38:13 +03:00
|
|
|
void Renderer::activate_clip()
|
2022-11-23 16:03:26 +03:00
|
|
|
{
|
2023-02-03 02:11:27 +03:00
|
|
|
auto bounding_box = state().clipping_paths.current.bounding_box();
|
2022-11-23 16:03:26 +03:00
|
|
|
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>());
|
|
|
|
}
|
|
|
|
|
2024-01-17 04:38:13 +03:00
|
|
|
void Renderer::deactivate_clip()
|
2022-11-23 16:03:26 +03:00
|
|
|
{
|
|
|
|
m_painter.clear_clip_rect();
|
|
|
|
state().clipping_paths.current = state().clipping_paths.next;
|
|
|
|
}
|
|
|
|
|
2024-01-17 04:38:13 +03:00
|
|
|
///
|
|
|
|
// Path painting operations
|
|
|
|
///
|
|
|
|
|
|
|
|
void Renderer::begin_path_paint()
|
|
|
|
{
|
2024-01-17 04:45:12 +03:00
|
|
|
if (m_rendering_preferences.clip_paths)
|
|
|
|
activate_clip();
|
2024-01-17 04:38:13 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::end_path_paint()
|
|
|
|
{
|
|
|
|
m_current_path.clear();
|
2024-01-17 04:45:12 +03:00
|
|
|
if (m_rendering_preferences.clip_paths)
|
|
|
|
deactivate_clip();
|
2024-01-17 04:38:13 +03:00
|
|
|
}
|
|
|
|
|
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();
|
2023-12-05 15:51:42 +03:00
|
|
|
if (state().stroke_style.has<NonnullRefPtr<Gfx::PaintStyle>>()) {
|
|
|
|
m_anti_aliasing_painter.stroke_path(m_current_path, state().stroke_style.get<NonnullRefPtr<Gfx::PaintStyle>>(), state().ctm.x_scale() * state().line_width);
|
|
|
|
} else {
|
|
|
|
m_anti_aliasing_painter.stroke_path(m_current_path, state().stroke_style.get<Color>(), state().ctm.x_scale() * 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();
|
2023-07-24 21:02:36 +03:00
|
|
|
m_current_path.close_all_subpaths();
|
2023-12-05 15:51:42 +03:00
|
|
|
if (state().paint_style.has<NonnullRefPtr<Gfx::PaintStyle>>()) {
|
|
|
|
m_anti_aliasing_painter.fill_path(m_current_path, state().paint_style.get<NonnullRefPtr<Gfx::PaintStyle>>(), 1.0, Gfx::Painter::WindingRule::Nonzero);
|
|
|
|
} else {
|
|
|
|
m_anti_aliasing_painter.fill_path(m_current_path, state().paint_style.get<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();
|
2023-07-24 21:02:36 +03:00
|
|
|
m_current_path.close_all_subpaths();
|
2023-12-05 15:51:42 +03:00
|
|
|
if (state().paint_style.has<NonnullRefPtr<Gfx::PaintStyle>>()) {
|
|
|
|
m_anti_aliasing_painter.fill_path(m_current_path, state().paint_style.get<NonnullRefPtr<Gfx::PaintStyle>>(), 1.0, Gfx::Painter::WindingRule::EvenOdd);
|
|
|
|
} else {
|
|
|
|
m_anti_aliasing_painter.fill_path(m_current_path, state().paint_style.get<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
|
|
|
{
|
2023-12-05 15:51:42 +03:00
|
|
|
if (state().stroke_style.has<NonnullRefPtr<Gfx::PaintStyle>>()) {
|
|
|
|
m_anti_aliasing_painter.stroke_path(m_current_path, state().stroke_style.get<NonnullRefPtr<Gfx::PaintStyle>>(), state().ctm.x_scale() * state().line_width);
|
|
|
|
} else {
|
|
|
|
m_anti_aliasing_painter.stroke_path(m_current_path, state().stroke_style.get<Color>(), state().ctm.x_scale() * 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
|
|
|
{
|
2023-12-05 15:51:42 +03:00
|
|
|
if (state().stroke_style.has<NonnullRefPtr<Gfx::PaintStyle>>()) {
|
|
|
|
m_anti_aliasing_painter.stroke_path(m_current_path, state().stroke_style.get<NonnullRefPtr<Gfx::PaintStyle>>(), state().ctm.x_scale() * state().line_width);
|
|
|
|
} else {
|
|
|
|
m_anti_aliasing_painter.stroke_path(m_current_path, state().stroke_style.get<Color>(), state().ctm.x_scale() * 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();
|
2024-01-10 17:45:23 +03:00
|
|
|
m_text_rendering_matrix_is_dirty = true;
|
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
|
|
|
}
|
|
|
|
|
2023-11-17 04:15:58 +03:00
|
|
|
PDFErrorOr<NonnullRefPtr<PDFFont>> Renderer::get_font(FontCacheKey const& key)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
LibPDF: Cache fonts per page
Previously, every time a page switched fonts, we'd completely
re-parse the font.
Now, we cache fonts in Renderer, effectively caching them per page.
It'd be nice to have an LRU cache across pages too, but that's a
bigger change, and this already helps a lot.
Font size is part of the cache key, which means we re-parse the same
font at different font sizes. That could be better too, but again,
it's a big help as-is already.
Takes rendering the 1310 pages of the PDF 1.7 reference with
Build/lagom/bin/pdf --debugging-stats \
~/Downloads/pdf_reference_1-7.pdf
from 71 s to 11s :^)
Going through pages especially in the index is noticeably snappier.
(On the PDF 2.0 spec, ISO_32000-2-2020_sponsored.pdf, it's less
dramatic: From 19s to 16s.)
2023-10-10 20:55:35 +03:00
|
|
|
auto it = m_font_cache.find(key);
|
LibPDF: Update font size after getting font from cache
Page 1 of 0000277.pdf does:
BT 22 0 0 22 59 28 Tm /TT2 1 Tf
(Presented at Photonics West OPTO, February 17, 2016) Tj ET
BT 32 0 0 32 269 426 Tm /TT1 1 Tf
(Robert W. Boyd) Tj ET
BT 22 0 0 22 253 357 Tm /TT2 1 Tf
(Department of Physics and) Tj ET
BT 22 0 0 22 105 326 Tm /TT2 1 Tf
(Max-Planck Centre for Extreme and Quantum Photonics) Tj ET
Every line begins a text operation, then updates the font matrix,
selects a font (TT2, TT1, TT2, TT1), draws some text and ends the text
operation.
`Tm` (which sets the font matrix) contains a scale, and uses that
to update the font size of the currently-active font (cf #20084).
But in this file, we `Tm` first and `Tf` (font selection) second,
so this updates the size of the old font. So when we pull it out
of the cache again on line 3, it would still have the old size
from the `Tm` on line 2.
(The whole text scaling logic in LibPDF imho needs a rethink; the
current approach also causes issues with zero-width glyphs which
currently lead to divisions by zero. But that's for another PR.)
Fixes another regression from c8510b58a366320 (which I've accidentally
referred to by 2340e834cd in another commit).
2023-11-27 02:07:45 +03:00
|
|
|
if (it != m_font_cache.end()) {
|
|
|
|
// Update the potentially-stale size set in text_set_matrix_and_line_matrix().
|
|
|
|
it->value->set_font_size(key.font_size);
|
LibPDF: Cache fonts per page
Previously, every time a page switched fonts, we'd completely
re-parse the font.
Now, we cache fonts in Renderer, effectively caching them per page.
It'd be nice to have an LRU cache across pages too, but that's a
bigger change, and this already helps a lot.
Font size is part of the cache key, which means we re-parse the same
font at different font sizes. That could be better too, but again,
it's a big help as-is already.
Takes rendering the 1310 pages of the PDF 1.7 reference with
Build/lagom/bin/pdf --debugging-stats \
~/Downloads/pdf_reference_1-7.pdf
from 71 s to 11s :^)
Going through pages especially in the index is noticeably snappier.
(On the PDF 2.0 spec, ISO_32000-2-2020_sponsored.pdf, it's less
dramatic: From 19s to 16s.)
2023-10-10 20:55:35 +03:00
|
|
|
return it->value;
|
LibPDF: Update font size after getting font from cache
Page 1 of 0000277.pdf does:
BT 22 0 0 22 59 28 Tm /TT2 1 Tf
(Presented at Photonics West OPTO, February 17, 2016) Tj ET
BT 32 0 0 32 269 426 Tm /TT1 1 Tf
(Robert W. Boyd) Tj ET
BT 22 0 0 22 253 357 Tm /TT2 1 Tf
(Department of Physics and) Tj ET
BT 22 0 0 22 105 326 Tm /TT2 1 Tf
(Max-Planck Centre for Extreme and Quantum Photonics) Tj ET
Every line begins a text operation, then updates the font matrix,
selects a font (TT2, TT1, TT2, TT1), draws some text and ends the text
operation.
`Tm` (which sets the font matrix) contains a scale, and uses that
to update the font size of the currently-active font (cf #20084).
But in this file, we `Tm` first and `Tf` (font selection) second,
so this updates the size of the old font. So when we pull it out
of the cache again on line 3, it would still have the old size
from the `Tm` on line 2.
(The whole text scaling logic in LibPDF imho needs a rethink; the
current approach also causes issues with zero-width glyphs which
currently lead to divisions by zero. But that's for another PR.)
Fixes another regression from c8510b58a366320 (which I've accidentally
referred to by 2340e834cd in another commit).
2023-11-27 02:07:45 +03:00
|
|
|
}
|
LibPDF: Cache fonts per page
Previously, every time a page switched fonts, we'd completely
re-parse the font.
Now, we cache fonts in Renderer, effectively caching them per page.
It'd be nice to have an LRU cache across pages too, but that's a
bigger change, and this already helps a lot.
Font size is part of the cache key, which means we re-parse the same
font at different font sizes. That could be better too, but again,
it's a big help as-is already.
Takes rendering the 1310 pages of the PDF 1.7 reference with
Build/lagom/bin/pdf --debugging-stats \
~/Downloads/pdf_reference_1-7.pdf
from 71 s to 11s :^)
Going through pages especially in the index is noticeably snappier.
(On the PDF 2.0 spec, ISO_32000-2-2020_sponsored.pdf, it's less
dramatic: From 19s to 16s.)
2023-10-10 20:55:35 +03:00
|
|
|
|
2023-11-17 04:15:58 +03:00
|
|
|
auto font = TRY(PDFFont::create(m_document, key.font_dictionary, key.font_size));
|
LibPDF: Cache fonts per page
Previously, every time a page switched fonts, we'd completely
re-parse the font.
Now, we cache fonts in Renderer, effectively caching them per page.
It'd be nice to have an LRU cache across pages too, but that's a
bigger change, and this already helps a lot.
Font size is part of the cache key, which means we re-parse the same
font at different font sizes. That could be better too, but again,
it's a big help as-is already.
Takes rendering the 1310 pages of the PDF 1.7 reference with
Build/lagom/bin/pdf --debugging-stats \
~/Downloads/pdf_reference_1-7.pdf
from 71 s to 11s :^)
Going through pages especially in the index is noticeably snappier.
(On the PDF 2.0 spec, ISO_32000-2-2020_sponsored.pdf, it's less
dramatic: From 19s to 16s.)
2023-10-10 20:55:35 +03:00
|
|
|
m_font_cache.set(key, font);
|
|
|
|
return font;
|
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(text_set_font)
|
|
|
|
{
|
|
|
|
auto target_font_name = MUST(m_document->resolve_to<NameObject>(args[0]))->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();
|
2024-01-17 02:10:36 +03:00
|
|
|
auto font_size = text_rendering_matrix.x_scale() * text_state().font_size / text_state().horizontal_scaling;
|
LibPDF: Cache fonts per page
Previously, every time a page switched fonts, we'd completely
re-parse the font.
Now, we cache fonts in Renderer, effectively caching them per page.
It'd be nice to have an LRU cache across pages too, but that's a
bigger change, and this already helps a lot.
Font size is part of the cache key, which means we re-parse the same
font at different font sizes. That could be better too, but again,
it's a big help as-is already.
Takes rendering the 1310 pages of the PDF 1.7 reference with
Build/lagom/bin/pdf --debugging-stats \
~/Downloads/pdf_reference_1-7.pdf
from 71 s to 11s :^)
Going through pages especially in the index is noticeably snappier.
(On the PDF 2.0 spec, ISO_32000-2-2020_sponsored.pdf, it's less
dramatic: From 19s to 16s.)
2023-10-10 20:55:35 +03:00
|
|
|
|
2023-11-17 04:15:58 +03:00
|
|
|
auto resources = extra_resources.value_or(m_page.resources);
|
|
|
|
auto fonts_dictionary = MUST(resources->get_dict(m_document, CommonNames::Font));
|
|
|
|
auto font_dictionary = MUST(fonts_dictionary->get_dict(m_document, target_font_name));
|
|
|
|
|
|
|
|
FontCacheKey cache_key { move(font_dictionary), font_size };
|
|
|
|
text_state().font = TRY(get_font(cache_key));
|
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;
|
2024-01-15 05:17:12 +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_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;
|
2023-07-19 04:35:53 +03:00
|
|
|
|
|
|
|
// Settings the text/line matrix retroactively affects fonts
|
|
|
|
if (text_state().font) {
|
|
|
|
auto new_text_rendering_matrix = calculate_text_rendering_matrix();
|
2024-01-17 02:10:36 +03:00
|
|
|
text_state().font->set_font_size(text_state().font_size * new_text_rendering_matrix.x_scale() / text_state().horizontal_scaling);
|
2023-07-19 04:35:53 +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(text_next_line)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2023-10-20 18:55:38 +03:00
|
|
|
TRY(handle_text_next_line_offset(Array<Value, 2> { 0.0f, -text_state().leading }));
|
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_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();
|
2023-01-29 05:57:21 +03:00
|
|
|
TRY(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
|
|
|
}
|
|
|
|
|
2023-10-20 18:57:37 +03:00
|
|
|
RENDERER_HANDLER(text_next_line_show_string_set_spacing)
|
|
|
|
{
|
|
|
|
TRY(handle_text_set_word_space(args.slice(0, 1)));
|
|
|
|
TRY(handle_text_set_char_space(args.slice(1, 1)));
|
|
|
|
TRY(handle_text_next_line_show_string(args.slice(2)));
|
|
|
|
return {};
|
|
|
|
}
|
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
|
|
|
|
|
|
|
for (auto& element : elements) {
|
2021-09-19 21:56:05 +03:00
|
|
|
if (element.has<int>()) {
|
2023-11-14 02:39:10 +03:00
|
|
|
float shift = (float)element.get<int>() / 1000.0f;
|
|
|
|
m_text_matrix.translate(-shift * text_state().font_size * text_state().horizontal_scaling, 0.0f);
|
2024-01-15 05:36:50 +03:00
|
|
|
m_text_rendering_matrix_is_dirty = true;
|
2021-09-19 21:56:05 +03:00
|
|
|
} else if (element.has<float>()) {
|
2023-11-14 02:39:10 +03:00
|
|
|
float shift = element.get<float>() / 1000.0f;
|
2022-08-19 10:02:46 +03:00
|
|
|
m_text_matrix.translate(-shift * text_state().font_size * text_state().horizontal_scaling, 0.0f);
|
2024-01-15 05:36:50 +03:00
|
|
|
m_text_rendering_matrix_is_dirty = true;
|
2023-11-14 02:39:10 +03:00
|
|
|
} else {
|
2022-03-06 03:30:55 +03:00
|
|
|
auto str = element.get<NonnullRefPtr<Object>>()->cast<StringObject>()->string();
|
2023-01-29 05:57:21 +03:00
|
|
|
TRY(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
|
|
|
}
|
|
|
|
|
2023-11-15 15:47:17 +03:00
|
|
|
RENDERER_HANDLER(type3_font_set_glyph_width)
|
|
|
|
{
|
|
|
|
// FIXME: Do something with this.
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(type3_font_set_glyph_width_and_bbox)
|
|
|
|
{
|
|
|
|
// FIXME: Do something with this.
|
|
|
|
return {};
|
|
|
|
}
|
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)
|
|
|
|
{
|
2023-12-05 15:51:42 +03:00
|
|
|
state().stroke_style = TRY(state().stroke_color_space->style(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)
|
|
|
|
{
|
2023-12-20 02:49:12 +03:00
|
|
|
// FIXME: Handle Pattern color spaces
|
|
|
|
auto last_arg = args.last();
|
|
|
|
if (last_arg.has<NonnullRefPtr<Object>>() && last_arg.get<NonnullRefPtr<Object>>()->is<NameObject>()) {
|
|
|
|
dbgln("pattern space {}", last_arg.get<NonnullRefPtr<Object>>()->cast<NameObject>()->name());
|
|
|
|
return Error::rendering_unsupported_error("Pattern color spaces not yet implemented");
|
|
|
|
}
|
|
|
|
|
|
|
|
state().stroke_style = TRY(state().stroke_color_space->style(args));
|
2022-03-24 20:24:00 +03:00
|
|
|
return {};
|
|
|
|
}
|
2021-05-23 22:53:38 +03:00
|
|
|
|
|
|
|
RENDERER_HANDLER(set_painting_color)
|
|
|
|
{
|
2023-12-05 15:51:42 +03:00
|
|
|
state().paint_style = TRY(state().paint_color_space->style(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)
|
|
|
|
{
|
2023-12-20 02:49:12 +03:00
|
|
|
// FIXME: Handle Pattern color spaces
|
|
|
|
auto last_arg = args.last();
|
|
|
|
if (last_arg.has<NonnullRefPtr<Object>>() && last_arg.get<NonnullRefPtr<Object>>()->is<NameObject>()) {
|
|
|
|
dbgln("pattern space {}", last_arg.get<NonnullRefPtr<Object>>()->cast<NameObject>()->name());
|
|
|
|
return Error::rendering_unsupported_error("Pattern color spaces not yet implemented");
|
|
|
|
}
|
|
|
|
|
2023-12-05 15:51:42 +03:00
|
|
|
state().paint_style = TRY(state().paint_color_space->style(args));
|
2022-03-24 20:24:00 +03:00
|
|
|
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();
|
2023-12-05 15:51:42 +03:00
|
|
|
state().stroke_style = TRY(state().stroke_color_space->style(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();
|
2023-12-05 15:51:42 +03:00
|
|
|
state().paint_style = TRY(state().paint_color_space->style(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();
|
2023-12-05 15:51:42 +03:00
|
|
|
state().stroke_style = TRY(state().stroke_color_space->style(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();
|
2023-12-05 15:51:42 +03:00
|
|
|
state().paint_style = TRY(state().paint_color_space->style(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();
|
2023-12-05 15:51:42 +03:00
|
|
|
state().stroke_style = TRY(state().stroke_color_space->style(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();
|
2023-12-05 15:51:42 +03:00
|
|
|
state().paint_style = TRY(state().paint_color_space->style(args));
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_TODO(shade)
|
2023-12-19 00:36:29 +03:00
|
|
|
|
|
|
|
RENDERER_HANDLER(inline_image_begin)
|
|
|
|
{
|
|
|
|
// The parser only calls the inline_image_end handler for inline images.
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(inline_image_begin_data)
|
|
|
|
{
|
|
|
|
// The parser only calls the inline_image_end handler for inline images.
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
}
|
|
|
|
|
2023-12-19 05:13:03 +03:00
|
|
|
static PDFErrorOr<Value> expand_inline_image_value(Value const& value, HashMap<DeprecatedFlyString, DeprecatedFlyString> const& value_expansions)
|
|
|
|
{
|
|
|
|
if (!value.has<NonnullRefPtr<Object>>())
|
|
|
|
return value;
|
|
|
|
|
|
|
|
auto const& object = value.get<NonnullRefPtr<Object>>();
|
|
|
|
if (object->is<NameObject>()) {
|
|
|
|
auto const& name = object->cast<NameObject>()->name();
|
|
|
|
auto expanded_name = value_expansions.get(name);
|
|
|
|
if (!expanded_name.has_value())
|
|
|
|
return value;
|
|
|
|
return Value { make_object<NameObject>(expanded_name.value()) };
|
|
|
|
}
|
|
|
|
|
|
|
|
// For the Filters array.
|
|
|
|
if (object->is<ArrayObject>()) {
|
|
|
|
auto const& array = object->cast<ArrayObject>()->elements();
|
|
|
|
Vector<Value> expanded_array;
|
|
|
|
for (auto const& element : array) {
|
|
|
|
auto expanded_element = TRY(expand_inline_image_value(element, value_expansions));
|
|
|
|
expanded_array.append(expanded_element);
|
|
|
|
}
|
|
|
|
return Value { make_object<ArrayObject>(move(expanded_array)) };
|
|
|
|
}
|
|
|
|
|
|
|
|
// For the DecodeParms dict. It might be fine to just `return value` here, I'm not sure if there can really be abbreviations in here.
|
|
|
|
if (object->is<DictObject>()) {
|
|
|
|
auto const& dict = object->cast<DictObject>()->map();
|
|
|
|
HashMap<DeprecatedFlyString, Value> expanded_dict;
|
|
|
|
for (auto const& [key, value] : dict) {
|
|
|
|
auto expanded_value = TRY(expand_inline_image_value(value, value_expansions));
|
|
|
|
expanded_dict.set(key, expanded_value);
|
|
|
|
}
|
|
|
|
return Value { make_object<DictObject>(move(expanded_dict)) };
|
|
|
|
}
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
}
|
|
|
|
|
|
|
|
static PDFErrorOr<Value> expand_inline_image_colorspace(Value color_space_value, NonnullRefPtr<DictObject> resources, RefPtr<Document> document)
|
|
|
|
{
|
|
|
|
// PDF 1.7 spec, 4.8.6 Inline Images:
|
|
|
|
// "Beginning with PDF 1.2, the value of the ColorSpace entry may also be the name
|
|
|
|
// of a color space in the ColorSpace subdictionary of the current resource dictionary."
|
|
|
|
|
|
|
|
// But PDF 1.7 spec, 4.5.2 Color Space Families:
|
|
|
|
// "Outside a content stream, certain objects, such as image XObjects,
|
|
|
|
// specify a color space as an explicit parameter, often associated with
|
|
|
|
// the key ColorSpace. In this case, the color space array or name is
|
|
|
|
// always defined directly as a PDF object, not by an entry in the
|
|
|
|
// ColorSpace resource subdictionary."
|
|
|
|
|
|
|
|
// This converts a named color space of an inline image to an explicit color space object,
|
|
|
|
// so that the regular image drawing code tolerates it.
|
|
|
|
|
|
|
|
if (!color_space_value.has<NonnullRefPtr<Object>>())
|
|
|
|
return color_space_value;
|
|
|
|
|
|
|
|
auto const& object = color_space_value.get<NonnullRefPtr<Object>>();
|
|
|
|
if (!object->is<NameObject>())
|
|
|
|
return color_space_value;
|
|
|
|
|
|
|
|
auto const& name = object->cast<NameObject>()->name();
|
|
|
|
if (name == "DeviceGray" || name == "DeviceRGB" || name == "DeviceCMYK")
|
|
|
|
return color_space_value;
|
|
|
|
|
|
|
|
auto color_space_resource_dict = TRY(resources->get_dict(document, CommonNames::ColorSpace));
|
|
|
|
return color_space_resource_dict->get_object(document, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static PDFErrorOr<NonnullRefPtr<StreamObject>> expand_inline_image_abbreviations(NonnullRefPtr<StreamObject> inline_stream, NonnullRefPtr<DictObject> resources, RefPtr<Document> document)
|
|
|
|
{
|
|
|
|
// TABLE 4.43 Entries in an inline image object
|
|
|
|
static HashMap<DeprecatedFlyString, DeprecatedFlyString> key_expansions {
|
|
|
|
{ "BPC", "BitsPerComponent" },
|
|
|
|
{ "CS", "ColorSpace" },
|
|
|
|
{ "D", "Decode" },
|
|
|
|
{ "DP", "DecodeParms" },
|
|
|
|
{ "F", "Filter" },
|
|
|
|
{ "H", "Height" },
|
|
|
|
{ "IM", "ImageMask" },
|
|
|
|
{ "I", "Interpolate" },
|
|
|
|
{ "Intent", "Intent" }, // "No abbreviation"
|
|
|
|
{ "L", "Length" }, // PDF 2.0; would make more sense to read in Parser.
|
|
|
|
{ "W", "Width" },
|
|
|
|
};
|
|
|
|
|
|
|
|
// TABLE 4.44 Additional abbreviations in an inline image object
|
|
|
|
// "Also note that JBIG2Decode and JPXDecode are not listed in Table 4.44
|
|
|
|
// because those filters can be applied only to image XObjects."
|
|
|
|
static HashMap<DeprecatedFlyString, DeprecatedFlyString> value_expansions {
|
|
|
|
{ "G", "DeviceGray" },
|
|
|
|
{ "RGB", "DeviceRGB" },
|
|
|
|
{ "CMYK", "DeviceCMYK" },
|
|
|
|
{ "I", "Indexed" },
|
|
|
|
{ "AHx", "ASCIIHexDecode" },
|
|
|
|
{ "A85", "ASCII85Decode" },
|
|
|
|
{ "LZW", "LZWDecode" },
|
|
|
|
{ "Fl", "FlateDecode" },
|
|
|
|
{ "RL", "RunLengthDecode" },
|
|
|
|
{ "CCF", "CCITTFaxDecode" },
|
|
|
|
{ "DCT", "DCTDecode" },
|
|
|
|
};
|
|
|
|
|
|
|
|
// The values in key_expansions, that is the final expansions, are the valid keys in an inline image dict.
|
|
|
|
HashTable<DeprecatedFlyString> valid_keys;
|
|
|
|
for (auto const& [key, value] : key_expansions)
|
|
|
|
valid_keys.set(value);
|
|
|
|
|
|
|
|
HashMap<DeprecatedFlyString, Value> expanded_dict;
|
|
|
|
for (auto const& [key, value] : inline_stream->dict()->map()) {
|
|
|
|
DeprecatedFlyString expanded_key = key_expansions.get(key).value_or(key);
|
|
|
|
|
|
|
|
// "Entries other than those listed are ignored"
|
|
|
|
if (!valid_keys.contains(expanded_key)) {
|
|
|
|
dbgln("PDF: Ignoring invalid inline image key '{}'", expanded_key);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
Value expanded_value = TRY(expand_inline_image_value(value, value_expansions));
|
|
|
|
if (expanded_key == "ColorSpace")
|
|
|
|
expanded_value = TRY(expand_inline_image_colorspace(expanded_value, resources, document));
|
|
|
|
|
|
|
|
expanded_dict.set(expanded_key, expanded_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto map_object = make_object<DictObject>(move(expanded_dict));
|
|
|
|
return make_object<StreamObject>(move(map_object), MUST(ByteBuffer::copy(inline_stream->bytes())));
|
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(inline_image_end)
|
|
|
|
{
|
|
|
|
VERIFY(args.size() == 1);
|
|
|
|
auto inline_stream = args[0].get<NonnullRefPtr<Object>>()->cast<StreamObject>();
|
|
|
|
|
|
|
|
auto resources = extra_resources.value_or(m_page.resources);
|
|
|
|
auto expanded_inline_stream = TRY(expand_inline_image_abbreviations(inline_stream, resources, m_document));
|
|
|
|
TRY(m_document->unfilter_stream(expanded_inline_stream));
|
|
|
|
|
|
|
|
TRY(show_image(expanded_inline_stream));
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
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();
|
2023-02-12 09:50:41 +03:00
|
|
|
auto xobjects_dict = TRY(resources->get_dict(m_document, CommonNames::XObject));
|
|
|
|
auto xobject = TRY(xobjects_dict->get_stream(m_document, xobject_name));
|
2022-11-21 08:32:09 +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
|
|
|
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 {};
|
|
|
|
}
|
|
|
|
|
2023-10-18 18:46:11 +03:00
|
|
|
ScopedState scoped_state { *this };
|
|
|
|
|
2022-11-21 08:32:09 +03:00
|
|
|
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));
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2024-01-16 23:32:26 +03:00
|
|
|
Gfx::Path Renderer::map(Gfx::Path const& path) const
|
|
|
|
{
|
|
|
|
return path.copy_transformed(state().ctm);
|
|
|
|
}
|
|
|
|
|
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
|
|
|
{
|
2023-07-21 02:38:01 +03:00
|
|
|
// ISO 32000 (PDF 2.0), 8.4.5 Graphics state parameter dictionaries
|
|
|
|
|
2021-05-28 00:22:24 +03:00
|
|
|
if (dict->contains(CommonNames::LW))
|
2023-10-20 18:55:38 +03:00
|
|
|
TRY(handle_set_line_width(Array { dict->get_value(CommonNames::LW) }));
|
2021-05-28 00:22:24 +03:00
|
|
|
|
|
|
|
if (dict->contains(CommonNames::LC))
|
2023-10-20 18:55:38 +03:00
|
|
|
TRY(handle_set_line_cap(Array { dict->get_value(CommonNames::LC) }));
|
2021-05-28 00:22:24 +03:00
|
|
|
|
|
|
|
if (dict->contains(CommonNames::LJ))
|
2023-10-20 18:55:38 +03:00
|
|
|
TRY(handle_set_line_join(Array { dict->get_value(CommonNames::LJ) }));
|
2021-05-28 00:22:24 +03:00
|
|
|
|
|
|
|
if (dict->contains(CommonNames::ML))
|
2023-10-20 18:55:38 +03:00
|
|
|
TRY(handle_set_miter_limit(Array { 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
|
|
|
|
2023-10-19 14:58:22 +03:00
|
|
|
if (dict->contains(CommonNames::RI))
|
2023-10-20 18:55:38 +03:00
|
|
|
TRY(handle_set_color_rendering_intent(Array { dict->get_value(CommonNames::RI) }));
|
2023-10-19 14:58:22 +03:00
|
|
|
|
2023-07-21 02:38:01 +03:00
|
|
|
// FIXME: OP
|
|
|
|
// FIXME: op
|
|
|
|
// FIXME: OPM
|
|
|
|
// FIXME: Font
|
|
|
|
// FIXME: BG
|
|
|
|
// FIXME: BG2
|
|
|
|
// FIXME: UCR
|
|
|
|
// FIXME: UCR2
|
|
|
|
// FIXME: TR
|
|
|
|
// FIXME: TR2
|
|
|
|
// FIXME: HT
|
|
|
|
|
2021-05-28 00:22:24 +03:00
|
|
|
if (dict->contains(CommonNames::FL))
|
2023-10-20 18:55:38 +03:00
|
|
|
TRY(handle_set_flatness_tolerance(Array { dict->get_value(CommonNames::FL) }));
|
2022-03-06 04:12:58 +03:00
|
|
|
|
2023-07-21 02:38:01 +03:00
|
|
|
// FIXME: SM
|
|
|
|
// FIXME: SA
|
|
|
|
// FIXME: BM
|
|
|
|
// FIXME: SMask
|
|
|
|
// FIXME: CA
|
|
|
|
// FIXME: ca
|
|
|
|
// FIXME: AIS
|
|
|
|
// FIXME: TK
|
|
|
|
// FIXME: UseBlackPtComp
|
|
|
|
// FIXME: HTO
|
|
|
|
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-28 00:22:24 +03:00
|
|
|
}
|
|
|
|
|
2023-12-16 17:19:34 +03:00
|
|
|
PDFErrorOr<void> Renderer::show_text(ByteString const& string)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2023-02-12 09:51:26 +03:00
|
|
|
if (!text_state().font)
|
|
|
|
return Error::rendering_unsupported_error("Can't draw text because an invalid font was in use");
|
|
|
|
|
2024-01-20 07:01:23 +03:00
|
|
|
OwnPtr<ClipRAII> clip_raii;
|
|
|
|
if (m_rendering_preferences.clip_text)
|
|
|
|
clip_raii = make<ClipRAII>(*this);
|
|
|
|
|
2024-01-17 02:50:02 +03:00
|
|
|
auto start_position = Gfx::FloatPoint { 0.0f, 0.0f };
|
2023-11-14 18:11:39 +03:00
|
|
|
auto end_position = TRY(text_state().font->draw_string(m_painter, start_position, string, *this));
|
2021-05-28 21:55:51 +03:00
|
|
|
|
2024-01-17 02:50:02 +03:00
|
|
|
// Update text matrix.
|
LibPDF: Refactor *Font classes
The PDFFont class hierarchy was very simple (a top-level PDFFont class,
followed by all the children classes that derived directly from it).
While this design was good enough for some things, it didn't correctly
model the actual organization of font types:
* PDF fonts are first divided between "simple" and "composite" fonts.
The latter is the Type0 font, while the rest are all simple.
* PDF fonts yield a glyph per "character code". Simple fonts char codes
are always 1 byte long, while Type0 char codes are of variable size.
To this effect, this commit changes the hierarchy of Font classes,
introducing a new SimpleFont class, deriving from PDFFont, and acting as
the parent of Type1Font and TrueTypeFont, while Type0 still derives from
PDFFont directly. This distinction allows us now to:
* Model string rendering differently from simple and composite fonts:
PDFFont now offers a generic draw_string method that takes a whole
string to be rendered instead of a single char code. SimpleFont
implements this as a loop over individual bytes of the string, with
T1 and TT implementing draw_glyph for drawing a single char code.
* Some common fields between T1 and TT fonts now live under SimpleFont
instead of under PDFfont, where they previously resided.
* Some other interfaces specific to SimpleFont have been cleaned up,
with u16/u32 not appearing on these classes (or in PDFFont) anymore.
* Type0Font's rendering still remains unimplemented.
As part of this exercise I also took the chance to perform the following
cleanups and restructurings:
* Refactored the creation and initialisation of fonts. They are all
centrally created at PDFFont::create, with a virtual "initialize"
method that allows them to initialise their inner members in the
correct order (parent first, child later) after creation.
* Removed duplicated code.
* Cleaned up some public interfaces: receive const refs, removed
unnecessary ctro/dtors, etc.
* Slightly changed how Type1 and TrueType fonts are implemented: if
there's an embedded font that takes priority, otherwise we always
look for a replacement.
* This means we don't do anything special for the standard fonts. The
only behavior previously associated to standard fonts was choosing an
encoding, and even that was under questioning.
2023-01-31 19:52:23 +03:00
|
|
|
auto delta_x = end_position.x() - start_position.x();
|
2021-05-28 21:55:51 +03:00
|
|
|
m_text_rendering_matrix_is_dirty = true;
|
2024-01-17 02:50:02 +03:00
|
|
|
m_text_matrix.translate(delta_x * text_state().horizontal_scaling, 0.0f);
|
2023-01-29 05:57:21 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2023-12-07 02:47:00 +03:00
|
|
|
enum UpsampleMode {
|
|
|
|
StoreValuesUnchanged,
|
|
|
|
UpsampleTo8Bit,
|
|
|
|
};
|
2023-12-07 02:53:19 +03:00
|
|
|
static Vector<u8> upsample_to_8_bit(ReadonlyBytes content, int samples_per_line, int bits_per_component, UpsampleMode mode)
|
2023-11-15 22:21:26 +03:00
|
|
|
{
|
|
|
|
VERIFY(bits_per_component == 1 || bits_per_component == 2 || bits_per_component == 4);
|
|
|
|
Vector<u8> upsampled_storage;
|
|
|
|
upsampled_storage.ensure_capacity(content.size() * 8 / bits_per_component);
|
|
|
|
u8 const mask = (1 << bits_per_component) - 1;
|
2023-12-07 02:53:19 +03:00
|
|
|
|
|
|
|
int x = 0;
|
2023-11-15 22:21:26 +03:00
|
|
|
for (auto byte : content) {
|
|
|
|
for (int i = 0; i < 8; i += bits_per_component) {
|
|
|
|
auto value = (byte >> (8 - bits_per_component - i)) & mask;
|
2023-12-07 02:47:00 +03:00
|
|
|
if (mode == UpsampleMode::UpsampleTo8Bit)
|
|
|
|
upsampled_storage.append(value * (255 / mask));
|
|
|
|
else
|
|
|
|
upsampled_storage.append(value);
|
2023-12-07 02:53:19 +03:00
|
|
|
++x;
|
|
|
|
|
|
|
|
// "Byte boundaries are ignored, except that each row of sample data must begin on a byte boundary."
|
|
|
|
if (x == samples_per_line) {
|
|
|
|
x = 0;
|
|
|
|
break;
|
|
|
|
}
|
2023-11-15 22:21:26 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return upsampled_storage;
|
|
|
|
}
|
|
|
|
|
2023-12-23 02:56:42 +03:00
|
|
|
PDFErrorOr<Renderer::LoadedImage> Renderer::load_image(NonnullRefPtr<StreamObject> 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
|
|
|
{
|
|
|
|
auto image_dict = image->dict();
|
2023-10-25 07:03:15 +03:00
|
|
|
auto width = TRY(m_document->resolve_to<int>(image_dict->get_value(CommonNames::Width)));
|
|
|
|
auto height = TRY(m_document->resolve_to<int>(image_dict->get_value(CommonNames::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
|
|
|
|
2023-07-24 05:06:48 +03:00
|
|
|
auto is_filter = [&](DeprecatedFlyString const& name) -> PDFErrorOr<bool> {
|
|
|
|
if (!image_dict->contains(CommonNames::Filter))
|
|
|
|
return false;
|
|
|
|
auto filter_object = TRY(image_dict->get_object(m_document, CommonNames::Filter));
|
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>();
|
2023-12-20 03:30:13 +03:00
|
|
|
if (filters->elements().is_empty())
|
|
|
|
return false;
|
2023-11-12 07:35:26 +03:00
|
|
|
auto last_filter_index = filters->elements().size() - 1;
|
|
|
|
return MUST(filters->get_name_at(m_document, last_filter_index))->name() == 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
|
|
|
};
|
2023-07-24 05:06:48 +03:00
|
|
|
if (TRY(is_filter(CommonNames::JPXDecode))) {
|
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
|
|
|
return Error(Error::Type::RenderingUnsupported, "JPXDecode filter");
|
|
|
|
}
|
2023-12-23 02:56:42 +03:00
|
|
|
|
|
|
|
bool is_image_mask = false;
|
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 (image_dict->contains(CommonNames::ImageMask)) {
|
2023-12-23 02:56:42 +03:00
|
|
|
is_image_mask = TRY(m_document->resolve_to<bool>(image_dict->get_value(CommonNames::ImageMask)));
|
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-10-20 04:54:18 +03:00
|
|
|
// "(Required for images, except those that use the JPXDecode filter; not allowed for image masks) [...]
|
|
|
|
// it can be any type of color space except Pattern."
|
2023-12-23 02:56:42 +03:00
|
|
|
NonnullRefPtr<ColorSpace> color_space = DeviceGrayColorSpace::the();
|
|
|
|
if (!is_image_mask) {
|
|
|
|
auto color_space_object = MUST(image_dict->get_object(m_document, CommonNames::ColorSpace));
|
|
|
|
color_space = TRY(get_color_space_from_document(color_space_object));
|
|
|
|
}
|
2023-10-20 02:53:08 +03:00
|
|
|
|
|
|
|
auto color_rendering_intent = state().color_rendering_intent;
|
|
|
|
if (image_dict->contains(CommonNames::Intent))
|
|
|
|
color_rendering_intent = TRY(image_dict->get_name(m_document, CommonNames::Intent))->name();
|
|
|
|
// FIXME: Do something with color_rendering_intent.
|
|
|
|
|
2023-10-20 02:53:55 +03:00
|
|
|
// "Valid values are 1, 2, 4, 8, and (in PDF 1.5) 16."
|
2023-12-23 02:56:42 +03:00
|
|
|
// Per spec, this is required even for /Mask images, but it's required to be 1 there.
|
|
|
|
// In practice, it's sometimes missing for /Mask images.
|
|
|
|
auto bits_per_component = 1;
|
|
|
|
if (!is_image_mask)
|
|
|
|
bits_per_component = TRY(m_document->resolve_to<int>(image_dict->get_value(CommonNames::BitsPerComponent)));
|
2023-11-15 22:21:26 +03:00
|
|
|
switch (bits_per_component) {
|
|
|
|
case 1:
|
|
|
|
case 2:
|
|
|
|
case 4:
|
|
|
|
case 8:
|
|
|
|
case 16:
|
|
|
|
// Ok!
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return Error(Error::Type::MalformedPDF, "Image's /BitsPerComponent invalid");
|
|
|
|
}
|
|
|
|
auto content = image->bytes();
|
|
|
|
|
2023-12-07 02:53:19 +03:00
|
|
|
int const n_components = color_space->number_of_components();
|
|
|
|
|
2024-01-12 17:02:50 +03:00
|
|
|
Vector<u8> resampled_storage;
|
2023-11-15 22:21:26 +03:00
|
|
|
if (bits_per_component < 8) {
|
2023-12-07 02:47:00 +03:00
|
|
|
UpsampleMode mode = color_space->family() == ColorSpaceFamily::Indexed ? UpsampleMode::StoreValuesUnchanged : UpsampleMode::UpsampleTo8Bit;
|
2024-01-12 17:02:50 +03:00
|
|
|
resampled_storage = upsample_to_8_bit(content, width * n_components, bits_per_component, mode);
|
|
|
|
content = resampled_storage;
|
2023-11-15 22:21:26 +03:00
|
|
|
bits_per_component = 8;
|
2023-12-23 02:56:42 +03:00
|
|
|
|
|
|
|
if (is_image_mask) {
|
|
|
|
// "a sample value of 0 marks the page with the current color, and a 1 leaves the previous contents unchanged."
|
|
|
|
// That's opposite of the normal alpha convention, and we're upsampling masks to 8 bit and use that as normal alpha.
|
2024-01-12 17:02:50 +03:00
|
|
|
for (u8& byte : resampled_storage)
|
2023-12-23 02:56:42 +03:00
|
|
|
byte = ~byte;
|
|
|
|
}
|
2024-01-12 17:02:50 +03:00
|
|
|
} else if (bits_per_component == 16) {
|
|
|
|
if (color_space->family() == ColorSpaceFamily::Indexed)
|
|
|
|
return Error(Error::Type::RenderingUnsupported, "16 bpp indexed images not yet supported");
|
|
|
|
|
|
|
|
// PDF 1.7 spec, 4.8.2 Sample Representation:
|
|
|
|
// "units of 16 bits are given with the most significant byte first"
|
|
|
|
// FIXME: Eventually use all 16 bits instead of throwing away the lower 8 bits.
|
|
|
|
resampled_storage.ensure_capacity(content.size() / 2);
|
|
|
|
for (size_t i = 0; i < content.size(); i += 2)
|
|
|
|
resampled_storage.append(content[i]);
|
|
|
|
|
|
|
|
content = resampled_storage;
|
|
|
|
bits_per_component = 8;
|
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
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
auto const bytes_per_component = bits_per_component / 8;
|
2024-01-09 05:36:21 +03:00
|
|
|
Vector<float> component_values;
|
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
|
|
|
component_values.resize(n_components);
|
|
|
|
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);
|
2024-01-09 05:36:21 +03:00
|
|
|
component_values[i] = component_value_decoders[i].interpolate(component[0]);
|
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
|
|
|
}
|
2024-01-09 06:26:40 +03:00
|
|
|
auto color = TRY(color_space->style(component_values)).get<Color>();
|
|
|
|
bitmap->set_pixel(x, y, color);
|
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
|
|
|
++x;
|
|
|
|
if (x == width) {
|
|
|
|
x = 0;
|
|
|
|
++y;
|
|
|
|
}
|
|
|
|
}
|
2023-12-23 02:56:42 +03:00
|
|
|
return LoadedImage { bitmap, is_image_mask };
|
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
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2023-12-23 06:30:57 +03:00
|
|
|
auto image_space_transformation = calculate_image_space_transformation(width, height);
|
|
|
|
auto image_border = image_space_transformation.map(Gfx::IntRect { 0, 0, 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
|
|
|
m_painter.stroke_path(rect_path(image_border), Color::Black, 1);
|
|
|
|
}
|
|
|
|
|
2023-12-23 02:31:20 +03:00
|
|
|
static ErrorOr<void> apply_alpha_channel(NonnullRefPtr<Gfx::Bitmap> image_bitmap, NonnullRefPtr<const Gfx::Bitmap> mask_bitmap)
|
|
|
|
{
|
|
|
|
// Make alpha mask same size as image.
|
|
|
|
if (mask_bitmap->size() != image_bitmap->size())
|
|
|
|
mask_bitmap = TRY(mask_bitmap->scaled_to_size(image_bitmap->size()));
|
|
|
|
|
|
|
|
image_bitmap->add_alpha_channel();
|
|
|
|
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 mask_color = mask_bitmap->get_pixel(i, j);
|
|
|
|
image_color = image_color.with_alpha(mask_color.luminosity());
|
|
|
|
image_bitmap->set_pixel(i, j, image_color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
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<void> Renderer::show_image(NonnullRefPtr<StreamObject> image)
|
|
|
|
{
|
|
|
|
auto image_dict = image->dict();
|
2023-10-25 07:03:15 +03:00
|
|
|
auto width = TRY(m_document->resolve_to<int>(image_dict->get_value(CommonNames::Width)));
|
|
|
|
auto height = TRY(m_document->resolve_to<int>(image_dict->get_value(CommonNames::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
|
|
|
|
2024-01-17 04:45:12 +03:00
|
|
|
OwnPtr<ClipRAII> clip_raii;
|
|
|
|
if (m_rendering_preferences.clip_images)
|
|
|
|
clip_raii = make<ClipRAII>(*this);
|
2024-01-17 04:38:41 +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
|
|
|
if (!m_rendering_preferences.show_images) {
|
|
|
|
show_empty_image(width, height);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
auto image_bitmap = TRY(load_image(image));
|
2024-01-10 03:17:02 +03:00
|
|
|
if (image_bitmap.is_image_mask) {
|
|
|
|
// PDF 1.7 spec, 4.8.5 Masked Images, Stencil Masking:
|
|
|
|
// "An image mask (an image XObject whose ImageMask entry is true) [...] is treated as a stencil mask [...].
|
|
|
|
// Sample values [...] designate places on the page that should either be marked with the current color or masked out (not marked at all)."
|
|
|
|
if (!state().paint_style.has<Gfx::Color>())
|
|
|
|
return Error(Error::Type::RenderingUnsupported, "Image masks with pattern fill not yet implemented");
|
|
|
|
|
|
|
|
// Move mask to alpha channel, and put current color in RGB.
|
|
|
|
auto current_color = state().paint_style.get<Gfx::Color>();
|
|
|
|
for (auto& pixel : *image_bitmap.bitmap) {
|
|
|
|
u8 mask_alpha = Color::from_argb(pixel).luminosity();
|
|
|
|
pixel = current_color.with_alpha(mask_alpha).value();
|
|
|
|
}
|
|
|
|
} else if (image_dict->contains(CommonNames::SMask)) {
|
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
|
|
|
auto smask_bitmap = TRY(load_image(TRY(image_dict->get_stream(m_document, CommonNames::SMask))));
|
2023-12-23 02:56:42 +03:00
|
|
|
TRY(apply_alpha_channel(image_bitmap.bitmap, smask_bitmap.bitmap));
|
2023-11-16 16:58:38 +03:00
|
|
|
} else if (image_dict->contains(CommonNames::Mask)) {
|
|
|
|
auto mask_object = TRY(image_dict->get_object(m_document, CommonNames::Mask));
|
|
|
|
if (mask_object->is<StreamObject>()) {
|
2023-12-23 02:34:17 +03:00
|
|
|
auto mask_bitmap = TRY(load_image(mask_object->cast<StreamObject>()));
|
|
|
|
TRY(apply_alpha_channel(image_bitmap.bitmap, mask_bitmap.bitmap));
|
2023-11-16 16:58:38 +03:00
|
|
|
} else if (mask_object->is<ArrayObject>()) {
|
|
|
|
return Error::rendering_unsupported_error("/Mask array objects not yet implemented");
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
|
|
|
auto image_space = calculate_image_space_transformation(width, height);
|
|
|
|
auto image_rect = Gfx::FloatRect { 0, 0, width, height };
|
2023-12-23 02:56:42 +03:00
|
|
|
m_painter.draw_scaled_bitmap_with_transform(image_bitmap.bitmap->rect(), image_bitmap.bitmap, image_rect, image_space);
|
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
|
|
|
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();
|
2023-10-20 18:11:03 +03:00
|
|
|
if (color_space_family.may_be_specified_directly()) {
|
2023-12-07 16:45:44 +03:00
|
|
|
return ColorSpace::create(color_space_name, *this);
|
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_resource_dict = TRY(resources->get_dict(m_document, CommonNames::ColorSpace));
|
2023-07-23 19:35:03 +03:00
|
|
|
if (!color_space_resource_dict->contains(color_space_name)) {
|
|
|
|
dbgln("missing key {}", color_space_name);
|
|
|
|
return Error::rendering_unsupported_error("Missing entry for color space name");
|
|
|
|
}
|
2023-10-21 17:42:16 +03:00
|
|
|
return get_color_space_from_document(TRY(color_space_resource_dict->get_object(m_document, color_space_name)));
|
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_document(NonnullRefPtr<Object> color_space_object)
|
|
|
|
{
|
2023-12-07 16:45:44 +03:00
|
|
|
return ColorSpace::create(m_document, color_space_object, *this);
|
2021-05-23 22:53:38 +03:00
|
|
|
}
|
|
|
|
|
2023-11-14 18:11:39 +03:00
|
|
|
Gfx::AffineTransform const& Renderer::calculate_text_rendering_matrix() const
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
if (m_text_rendering_matrix_is_dirty) {
|
2024-01-17 00:41:51 +03:00
|
|
|
// PDF 1.7, 5.3.3. Text Space Details
|
2024-01-17 01:14:56 +03:00
|
|
|
Gfx::AffineTransform parameter_matrix {
|
2021-05-10 20:50:39 +03:00
|
|
|
text_state().horizontal_scaling,
|
|
|
|
0.0f,
|
|
|
|
0.0f,
|
|
|
|
1.0f,
|
|
|
|
0.0f,
|
2024-01-17 01:14:56 +03:00
|
|
|
text_state().rise
|
|
|
|
};
|
|
|
|
m_text_rendering_matrix = state().ctm;
|
2022-03-07 00:27:04 +03:00
|
|
|
m_text_rendering_matrix.multiply(m_text_matrix);
|
2024-01-17 01:14:56 +03:00
|
|
|
m_text_rendering_matrix.multiply(parameter_matrix);
|
2021-05-10 20:50:39 +03:00
|
|
|
m_text_rendering_matrix_is_dirty = false;
|
|
|
|
}
|
|
|
|
return m_text_rendering_matrix;
|
|
|
|
}
|
|
|
|
|
2023-11-15 15:51:56 +03:00
|
|
|
PDFErrorOr<void> Renderer::render_type3_glyph(Gfx::FloatPoint point, StreamObject const& glyph_data, Gfx::AffineTransform const& font_matrix, Optional<NonnullRefPtr<DictObject>> resources)
|
|
|
|
{
|
|
|
|
ScopedState scoped_state { *this };
|
|
|
|
|
|
|
|
auto text_rendering_matrix = calculate_text_rendering_matrix();
|
|
|
|
text_rendering_matrix.set_translation(point);
|
|
|
|
state().ctm = text_rendering_matrix;
|
|
|
|
state().ctm.scale(text_state().font_size, text_state().font_size);
|
|
|
|
state().ctm.multiply(font_matrix);
|
|
|
|
m_text_rendering_matrix_is_dirty = true;
|
|
|
|
|
|
|
|
auto operators = TRY(Parser::parse_operators(m_document, glyph_data.bytes()));
|
|
|
|
for (auto& op : operators)
|
|
|
|
TRY(handle_operator(op, resources));
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|