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 {
|
|
|
|
|
LibPDF: Switch to best-effort PDF rendering
The current rendering routine aborts as soon as an error is found during
rendering, which potentially severely limits the contents we show on
screen. Moreover, whenever an error happens the PDFViewer widget shows
an error dialog, and doesn't display the bitmap that has been painted so
far.
This commit improves the situation in both fronts, implementing
rendering now with a best-effort approach. Firstly, execution of
operations isn't halted after an operand results in an error, but
instead execution of all operations is always attempted, and all
collected errors are returned in bulk. Secondly, PDFViewer now always
displays the resulting bitmap, regardless of error being produced or
not. To communicate errors, an on_render_errors callback has been added
so clients can subscribe to these events and handle them as appropriate.
2022-12-14 18:00:40 +03:00
|
|
|
PDFErrorsOr<void> Renderer::render(Document& document, Page const& page, RefPtr<Gfx::Bitmap> bitmap, RenderingPreferences rendering_preferences)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2022-11-23 16:11:29 +03:00
|
|
|
return Renderer(document, page, bitmap, rendering_preferences).render();
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2022-11-23 16:03:26 +03:00
|
|
|
static void rect_path(Gfx::Path& path, float x, float y, float width, float height)
|
|
|
|
{
|
|
|
|
path.move_to({ x, y });
|
|
|
|
path.line_to({ x + width, y });
|
|
|
|
path.line_to({ x + width, y + height });
|
|
|
|
path.line_to({ x, y + height });
|
|
|
|
path.close();
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2022-11-23 16:11:29 +03:00
|
|
|
Renderer::Renderer(RefPtr<Document> document, Page const& page, RefPtr<Gfx::Bitmap> bitmap, RenderingPreferences rendering_preferences)
|
2021-05-10 20:50:39 +03:00
|
|
|
: m_document(document)
|
|
|
|
, m_bitmap(bitmap)
|
|
|
|
, m_page(page)
|
|
|
|
, m_painter(*bitmap)
|
2022-03-24 21:50:37 +03:00
|
|
|
, m_anti_aliasing_painter(m_painter)
|
2022-11-23 16:11:29 +03:00
|
|
|
, m_rendering_preferences(rendering_preferences)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
auto media_box = m_page.media_box;
|
|
|
|
|
2021-05-28 21:55:51 +03:00
|
|
|
Gfx::AffineTransform userspace_matrix;
|
|
|
|
userspace_matrix.translate(media_box.lower_left_x, media_box.lower_left_y);
|
2021-05-10 20:50:39 +03:00
|
|
|
|
2022-03-30 07:27:17 +03:00
|
|
|
float width = media_box.width();
|
|
|
|
float height = media_box.height();
|
2021-05-10 20:50:39 +03:00
|
|
|
float scale_x = static_cast<float>(bitmap->width()) / width;
|
|
|
|
float scale_y = static_cast<float>(bitmap->height()) / height;
|
2021-05-28 21:55:51 +03:00
|
|
|
userspace_matrix.scale(scale_x, scale_y);
|
2021-05-10 20:50:39 +03:00
|
|
|
|
2021-05-28 22:18:11 +03:00
|
|
|
// PDF user-space coordinate y axis increases from bottom to top, so we have to
|
|
|
|
// insert a horizontal reflection about the vertical midpoint into our transformation
|
|
|
|
// matrix
|
|
|
|
|
|
|
|
static Gfx::AffineTransform horizontal_reflection_matrix = { 1, 0, 0, -1, 0, 0 };
|
|
|
|
|
|
|
|
userspace_matrix.multiply(horizontal_reflection_matrix);
|
|
|
|
userspace_matrix.translate(0.0f, -height);
|
|
|
|
|
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
|
|
|
|
|
|
|
m_bitmap->fill(Gfx::Color::NamedColor::White);
|
|
|
|
}
|
|
|
|
|
LibPDF: Switch to best-effort PDF rendering
The current rendering routine aborts as soon as an error is found during
rendering, which potentially severely limits the contents we show on
screen. Moreover, whenever an error happens the PDFViewer widget shows
an error dialog, and doesn't display the bitmap that has been painted so
far.
This commit improves the situation in both fronts, implementing
rendering now with a best-effort approach. Firstly, execution of
operations isn't halted after an operand results in an error, but
instead execution of all operations is always attempted, and all
collected errors are returned in bulk. Secondly, PDFViewer now always
displays the resulting bitmap, regardless of error being produced or
not. To communicate errors, an on_render_errors callback has been added
so clients can subscribe to these events and handle them as appropriate.
2022-12-14 18:00:40 +03:00
|
|
|
PDFErrorsOr<void> Renderer::render()
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
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());
|
|
|
|
rect_path(m_current_path, map(rect));
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2022-11-23 16:03:26 +03:00
|
|
|
///
|
|
|
|
// Path painting operations
|
|
|
|
///
|
|
|
|
|
|
|
|
void Renderer::begin_path_paint()
|
|
|
|
{
|
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>());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::end_path_paint()
|
|
|
|
{
|
|
|
|
m_current_path.clear();
|
|
|
|
m_painter.clear_clip_rect();
|
|
|
|
state().clipping_paths.current = state().clipping_paths.next;
|
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(path_stroke)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2022-11-23 16:03:26 +03:00
|
|
|
begin_path_paint();
|
2023-03-24 23:57:01 +03:00
|
|
|
m_anti_aliasing_painter.stroke_path(m_current_path, state().stroke_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();
|
2022-03-24 21:50:37 +03:00
|
|
|
m_anti_aliasing_painter.fill_path(m_current_path, state().paint_color, Gfx::Painter::WindingRule::Nonzero);
|
2022-11-23 16:03:26 +03:00
|
|
|
end_path_paint();
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(path_fill_nonzero_deprecated)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2022-11-24 04:38:23 +03:00
|
|
|
return handle_path_fill_nonzero(args);
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(path_fill_evenodd)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2022-11-23 16:03:26 +03:00
|
|
|
begin_path_paint();
|
2023-07-24 21:02:36 +03:00
|
|
|
m_current_path.close_all_subpaths();
|
2022-03-24 21:50:37 +03:00
|
|
|
m_anti_aliasing_painter.fill_path(m_current_path, state().paint_color, Gfx::Painter::WindingRule::EvenOdd);
|
2022-11-23 16:03:26 +03:00
|
|
|
end_path_paint();
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(path_fill_stroke_nonzero)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2023-03-24 23:57:01 +03:00
|
|
|
m_anti_aliasing_painter.stroke_path(m_current_path, state().stroke_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-03-24 23:57:01 +03:00
|
|
|
m_anti_aliasing_painter.stroke_path(m_current_path, state().stroke_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();
|
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
|
|
|
}
|
|
|
|
|
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
|
|
|
PDFErrorOr<NonnullRefPtr<PDFFont>> Renderer::get_font(FontCacheKey const& key, Optional<NonnullRefPtr<DictObject>> extra_resources)
|
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);
|
|
|
|
if (it != m_font_cache.end())
|
|
|
|
return it->value;
|
|
|
|
|
2022-11-21 08:28:41 +03:00
|
|
|
auto resources = extra_resources.value_or(m_page.resources);
|
|
|
|
auto fonts_dictionary = MUST(resources->get_dict(m_document, CommonNames::Font));
|
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 font_dictionary = MUST(fonts_dictionary->get_dict(m_document, key.font_dictionary_key));
|
|
|
|
|
|
|
|
auto font = TRY(PDFFont::create(m_document, font_dictionary, key.font_size));
|
|
|
|
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();
|
|
|
|
auto font_size = text_rendering_matrix.x_scale() * text_state().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
|
|
|
|
|
|
|
FontCacheKey cache_key { target_font_name, font_size };
|
|
|
|
text_state().font = TRY(get_font(cache_key, extra_resources));
|
2021-05-10 20:50:39 +03:00
|
|
|
|
|
|
|
m_text_rendering_matrix_is_dirty = true;
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(text_set_rendering_mode)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
2021-09-19 21:56:05 +03:00
|
|
|
text_state().rendering_mode = static_cast<TextRenderingMode>(args[0].get<int>());
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(text_set_rise)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
m_text_rendering_matrix_is_dirty = true;
|
|
|
|
text_state().rise = args[0].to_float();
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(text_next_line_offset)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
Gfx::AffineTransform transform(1.0f, 0.0f, 0.0f, 1.0f, args[0].to_float(), args[1].to_float());
|
2022-03-24 21:14:31 +03:00
|
|
|
m_text_line_matrix.multiply(transform);
|
|
|
|
m_text_matrix = m_text_line_matrix;
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(text_next_line_and_set_leading)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
text_state().leading = -args[1].to_float();
|
2022-03-06 04:12:58 +03:00
|
|
|
TRY(handle_text_next_line_offset(args));
|
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
2021-05-23 07:09:33 +03:00
|
|
|
RENDERER_HANDLER(text_set_matrix_and_line_matrix)
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
Gfx::AffineTransform new_transform(
|
|
|
|
args[0].to_float(),
|
|
|
|
args[1].to_float(),
|
|
|
|
args[2].to_float(),
|
|
|
|
args[3].to_float(),
|
|
|
|
args[4].to_float(),
|
|
|
|
args[5].to_float());
|
|
|
|
m_text_line_matrix = new_transform;
|
|
|
|
m_text_matrix = new_transform;
|
|
|
|
m_text_rendering_matrix_is_dirty = true;
|
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();
|
|
|
|
text_state().font->set_font_size(text_state().font_size * new_text_rendering_matrix.x_scale());
|
|
|
|
}
|
|
|
|
|
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
|
|
|
float next_shift = 0.0f;
|
|
|
|
|
|
|
|
for (auto& element : elements) {
|
2021-09-19 21:56:05 +03:00
|
|
|
if (element.has<int>()) {
|
|
|
|
next_shift = element.get<int>();
|
|
|
|
} else if (element.has<float>()) {
|
|
|
|
next_shift = element.get<float>();
|
2021-05-28 00:53:10 +03:00
|
|
|
} else {
|
2022-08-19 10:02:46 +03:00
|
|
|
auto shift = next_shift / 1000.0f;
|
|
|
|
m_text_matrix.translate(-shift * text_state().font_size * text_state().horizontal_scaling, 0.0f);
|
2022-03-06 03:30:55 +03:00
|
|
|
auto str = element.get<NonnullRefPtr<Object>>()->cast<StringObject>()->string();
|
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
|
|
|
}
|
|
|
|
|
2022-03-06 04:12:58 +03:00
|
|
|
RENDERER_TODO(type3_font_set_glyph_width)
|
|
|
|
RENDERER_TODO(type3_font_set_glyph_width_and_bbox)
|
2021-05-23 22:53:38 +03:00
|
|
|
|
|
|
|
RENDERER_HANDLER(set_stroking_space)
|
|
|
|
{
|
LibPDF: Refactor parsing of ColorSpaces
ColorSpaces can be specified in two ways: with a stream as operands of
the color space operations (CS/cs), or as a separate PDF object, which
is then referred to by other means (e.g., from Image XObjects and other
entities). These two modes of addressing a ColorSpace are slightly
different and need to be addressed separately. However, the current
implementation embedded the full logic of the first case in the routine
that created ColorSpace objects.
This commit refactors the creation of ColorSpace to support both cases.
First, a new ColorSpaceFamily class encapsulates the static aspects of a
family, like its name or whether color space construction never requires
parameters. Then we define the supported ColorSpaceFamily objects.
On top of this also sit a breakage on how ColorSpaces are created. Two
methods are now offered: one only providing construction of no-argument
color spaces (and thus taking a simple name), and another taking an
ArrayObject, hence used to create ColorSpaces requiring arguments.
Finally, on top of *that* two ways to get a color space in the Renderer
are made available: the first creates a ColorSpace with a name and a
Resources dictionary, and another takes an Object. These model the two
addressing modes described above.
2022-11-24 07:40:24 +03:00
|
|
|
state().stroke_color_space = TRY(get_color_space_from_resources(args[0], extra_resources.value_or(m_page.resources)));
|
2021-05-28 00:01:37 +03:00
|
|
|
VERIFY(state().stroke_color_space);
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-23 22:53:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(set_painting_space)
|
|
|
|
{
|
LibPDF: Refactor parsing of ColorSpaces
ColorSpaces can be specified in two ways: with a stream as operands of
the color space operations (CS/cs), or as a separate PDF object, which
is then referred to by other means (e.g., from Image XObjects and other
entities). These two modes of addressing a ColorSpace are slightly
different and need to be addressed separately. However, the current
implementation embedded the full logic of the first case in the routine
that created ColorSpace objects.
This commit refactors the creation of ColorSpace to support both cases.
First, a new ColorSpaceFamily class encapsulates the static aspects of a
family, like its name or whether color space construction never requires
parameters. Then we define the supported ColorSpaceFamily objects.
On top of this also sit a breakage on how ColorSpaces are created. Two
methods are now offered: one only providing construction of no-argument
color spaces (and thus taking a simple name), and another taking an
ArrayObject, hence used to create ColorSpaces requiring arguments.
Finally, on top of *that* two ways to get a color space in the Renderer
are made available: the first creates a ColorSpace with a name and a
Resources dictionary, and another takes an Object. These model the two
addressing modes described above.
2022-11-24 07:40:24 +03:00
|
|
|
state().paint_color_space = TRY(get_color_space_from_resources(args[0], extra_resources.value_or(m_page.resources)));
|
2021-05-28 00:01:37 +03:00
|
|
|
VERIFY(state().paint_color_space);
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-23 22:53:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(set_stroking_color)
|
|
|
|
{
|
2023-07-19 04:35:51 +03:00
|
|
|
state().stroke_color = TRY(state().stroke_color_space->color(args));
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-23 22:53:38 +03:00
|
|
|
}
|
|
|
|
|
2022-03-24 20:24:00 +03:00
|
|
|
RENDERER_HANDLER(set_stroking_color_extended)
|
|
|
|
{
|
|
|
|
// FIXME: Handle Pattern color spaces
|
|
|
|
auto last_arg = args.last();
|
2023-10-21 06:52:13 +03:00
|
|
|
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");
|
|
|
|
}
|
2022-03-24 20:24:00 +03:00
|
|
|
|
2023-07-19 04:35:51 +03:00
|
|
|
state().stroke_color = TRY(state().stroke_color_space->color(args));
|
2022-03-24 20:24:00 +03:00
|
|
|
return {};
|
|
|
|
}
|
2021-05-23 22:53:38 +03:00
|
|
|
|
|
|
|
RENDERER_HANDLER(set_painting_color)
|
|
|
|
{
|
2023-07-19 04:35:51 +03:00
|
|
|
state().paint_color = TRY(state().paint_color_space->color(args));
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-23 22:53:38 +03:00
|
|
|
}
|
|
|
|
|
2022-03-24 20:24:00 +03:00
|
|
|
RENDERER_HANDLER(set_painting_color_extended)
|
|
|
|
{
|
|
|
|
// FIXME: Handle Pattern color spaces
|
|
|
|
auto last_arg = args.last();
|
2023-07-23 19:35:03 +03:00
|
|
|
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");
|
|
|
|
}
|
2022-03-24 20:24:00 +03:00
|
|
|
|
2023-07-19 04:35:51 +03:00
|
|
|
state().paint_color = TRY(state().paint_color_space->color(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-07-19 04:35:51 +03:00
|
|
|
state().stroke_color = TRY(state().stroke_color_space->color(args));
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-23 22:53:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(set_painting_color_and_space_to_gray)
|
|
|
|
{
|
2021-05-28 00:01:37 +03:00
|
|
|
state().paint_color_space = DeviceGrayColorSpace::the();
|
2023-07-19 04:35:51 +03:00
|
|
|
state().paint_color = TRY(state().paint_color_space->color(args));
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-23 22:53:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(set_stroking_color_and_space_to_rgb)
|
|
|
|
{
|
2021-05-28 00:01:37 +03:00
|
|
|
state().stroke_color_space = DeviceRGBColorSpace::the();
|
2023-07-19 04:35:51 +03:00
|
|
|
state().stroke_color = TRY(state().stroke_color_space->color(args));
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-23 22:53:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(set_painting_color_and_space_to_rgb)
|
|
|
|
{
|
2021-05-28 00:01:37 +03:00
|
|
|
state().paint_color_space = DeviceRGBColorSpace::the();
|
2023-07-19 04:35:51 +03:00
|
|
|
state().paint_color = TRY(state().paint_color_space->color(args));
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-23 22:53:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(set_stroking_color_and_space_to_cmyk)
|
|
|
|
{
|
2021-05-28 00:01:37 +03:00
|
|
|
state().stroke_color_space = DeviceCMYKColorSpace::the();
|
2023-07-19 04:35:51 +03:00
|
|
|
state().stroke_color = TRY(state().stroke_color_space->color(args));
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
2021-05-23 22:53:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_HANDLER(set_painting_color_and_space_to_cmyk)
|
|
|
|
{
|
2021-05-28 00:01:37 +03:00
|
|
|
state().paint_color_space = DeviceCMYKColorSpace::the();
|
2023-07-19 04:35:51 +03:00
|
|
|
state().paint_color = TRY(state().paint_color_space->color(args));
|
2022-03-06 04:12:58 +03:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
RENDERER_TODO(shade)
|
|
|
|
RENDERER_TODO(inline_image_begin)
|
|
|
|
RENDERER_TODO(inline_image_begin_data)
|
|
|
|
RENDERER_TODO(inline_image_end)
|
2022-11-21 08:32:09 +03:00
|
|
|
RENDERER_HANDLER(paint_xobject)
|
|
|
|
{
|
|
|
|
VERIFY(args.size() > 0);
|
|
|
|
auto resources = extra_resources.value_or(m_page.resources);
|
|
|
|
auto xobject_name = args[0].get<NonnullRefPtr<Object>>()->cast<NameObject>()->name();
|
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
|
|
|
// 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.
|
2023-10-19 14:54:55 +03:00
|
|
|
// 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.
|
2023-10-18 18:46:11 +03:00
|
|
|
class ScopedState {
|
|
|
|
public:
|
|
|
|
ScopedState(Renderer& renderer)
|
|
|
|
: m_renderer(renderer)
|
2023-10-19 14:54:55 +03:00
|
|
|
, m_starting_stack_depth(m_renderer.m_graphics_state_stack.size())
|
2023-10-18 18:46:11 +03:00
|
|
|
{
|
|
|
|
MUST(m_renderer.handle_save_state({}));
|
|
|
|
}
|
|
|
|
~ScopedState()
|
|
|
|
{
|
2023-10-19 14:54:55 +03:00
|
|
|
m_renderer.m_graphics_state_stack.shrink(m_starting_stack_depth);
|
2023-10-18 18:46:11 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
Renderer& m_renderer;
|
2023-10-19 14:54:55 +03:00
|
|
|
size_t m_starting_stack_depth;
|
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);
|
|
|
|
}
|
|
|
|
|
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-01-29 05:57:21 +03:00
|
|
|
PDFErrorOr<void> Renderer::show_text(DeprecatedString 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");
|
|
|
|
|
2021-05-28 21:55:51 +03:00
|
|
|
auto& text_rendering_matrix = calculate_text_rendering_matrix();
|
|
|
|
|
2022-03-25 07:56:14 +03:00
|
|
|
auto font_size = text_rendering_matrix.x_scale() * text_state().font_size;
|
2021-05-28 21:55:51 +03:00
|
|
|
|
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 start_position = text_rendering_matrix.map(Gfx::FloatPoint { 0.0f, 0.0f });
|
2023-07-22 18:32:53 +03:00
|
|
|
auto end_position = TRY(text_state().font->draw_string(m_painter, start_position, string, state().paint_color, font_size, text_state().character_spacing * text_rendering_matrix.x_scale(), text_state().word_spacing * text_rendering_matrix.x_scale(), text_state().horizontal_scaling));
|
2021-05-28 21:55:51 +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;
|
2022-03-24 21:14:31 +03:00
|
|
|
m_text_matrix.translate(delta_x / text_rendering_matrix.x_scale(), 0.0f);
|
2023-01-29 05:57:21 +03:00
|
|
|
return {};
|
2021-05-10 20:50:39 +03:00
|
|
|
}
|
|
|
|
|
LibPDF: Add initial image display support
After adding support for XObject Form rendering, the next was to display
XObject images. This commit adds this initial support,
Images come in many shapes and forms: encodings: color spaces, bits per
component, width, height, etc. This initial support is constrained to
the color spaces we currently support, to images that use 8 bits per
component, to images that do *not* use the JPXDecode filter, and that
are not Masks. There are surely other constraints that aren't considered
in this initial support, so expect breakage here and there.
In addition to supporting images, we also support applying an alpha mask
(SMask) on them. Additionally, a new rendering preference allows to skip
image loading and rendering altogether, instead showing an empty
rectangle as a placeholder (useful for when actual images are not
supported). Since RenderingPreferences is becoming a bit more complex,
we add a hash option that will allow us to keep track of different
preferences (e.g., in a HashMap).
2022-11-24 21:01:53 +03:00
|
|
|
PDFErrorOr<NonnullRefPtr<Gfx::Bitmap>> Renderer::load_image(NonnullRefPtr<StreamObject> image)
|
|
|
|
{
|
|
|
|
auto image_dict = image->dict();
|
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>();
|
|
|
|
return MUST(filters->get_name_at(m_document, 0))->name() == name;
|
|
|
|
};
|
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");
|
|
|
|
}
|
|
|
|
if (image_dict->contains(CommonNames::ImageMask)) {
|
2023-10-25 07:03:15 +03:00
|
|
|
auto is_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
|
|
|
if (is_mask) {
|
|
|
|
return Error(Error::Type::RenderingUnsupported, "Image masks");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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."
|
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 color_space_object = MUST(image_dict->get_object(m_document, CommonNames::ColorSpace));
|
|
|
|
auto 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-10-25 07:03:15 +03:00
|
|
|
auto bits_per_component = TRY(m_document->resolve_to<int>(image_dict->get_value(CommonNames::BitsPerComponent)));
|
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 (bits_per_component != 8) {
|
|
|
|
return Error(Error::Type::RenderingUnsupported, "Image's bit per component != 8");
|
|
|
|
}
|
|
|
|
|
|
|
|
Vector<float> decode_array;
|
|
|
|
if (image_dict->contains(CommonNames::Decode)) {
|
|
|
|
decode_array = MUST(image_dict->get_array(m_document, CommonNames::Decode))->float_elements();
|
|
|
|
} else {
|
|
|
|
decode_array = color_space->default_decode();
|
|
|
|
}
|
|
|
|
Vector<LinearInterpolation1D> component_value_decoders;
|
|
|
|
component_value_decoders.ensure_capacity(decode_array.size());
|
|
|
|
for (size_t i = 0; i < decode_array.size(); i += 2) {
|
|
|
|
auto dmin = decode_array[i];
|
|
|
|
auto dmax = decode_array[i + 1];
|
|
|
|
component_value_decoders.empend(0.0f, 255.0f, dmin, dmax);
|
|
|
|
}
|
|
|
|
|
2023-07-24 05:06:48 +03:00
|
|
|
if (TRY(is_filter(CommonNames::DCTDecode))) {
|
2023-10-20 02:53:35 +03:00
|
|
|
// TODO: stream objects could store Variant<bytes/Bitmap> to avoid serialisation/deserialisation here
|
2023-01-20 22:06:05 +03:00
|
|
|
return TRY(Gfx::Bitmap::create_from_serialized_bytes(image->bytes()));
|
LibPDF: Add initial image display support
After adding support for XObject Form rendering, the next was to display
XObject images. This commit adds this initial support,
Images come in many shapes and forms: encodings: color spaces, bits per
component, width, height, etc. This initial support is constrained to
the color spaces we currently support, to images that use 8 bits per
component, to images that do *not* use the JPXDecode filter, and that
are not Masks. There are surely other constraints that aren't considered
in this initial support, so expect breakage here and there.
In addition to supporting images, we also support applying an alpha mask
(SMask) on them. Additionally, a new rendering preference allows to skip
image loading and rendering altogether, instead showing an empty
rectangle as a placeholder (useful for when actual images are not
supported). Since RenderingPreferences is becoming a bit more complex,
we add a hash option that will allow us to keep track of different
preferences (e.g., in a HashMap).
2022-11-24 21:01:53 +03:00
|
|
|
}
|
|
|
|
|
2023-01-20 22:06:05 +03:00
|
|
|
auto bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { width, height }));
|
LibPDF: Add initial image display support
After adding support for XObject Form rendering, the next was to display
XObject images. This commit adds this initial support,
Images come in many shapes and forms: encodings: color spaces, bits per
component, width, height, etc. This initial support is constrained to
the color spaces we currently support, to images that use 8 bits per
component, to images that do *not* use the JPXDecode filter, and that
are not Masks. There are surely other constraints that aren't considered
in this initial support, so expect breakage here and there.
In addition to supporting images, we also support applying an alpha mask
(SMask) on them. Additionally, a new rendering preference allows to skip
image loading and rendering altogether, instead showing an empty
rectangle as a placeholder (useful for when actual images are not
supported). Since RenderingPreferences is becoming a bit more complex,
we add a hash option that will allow us to keep track of different
preferences (e.g., in a HashMap).
2022-11-24 21:01:53 +03:00
|
|
|
int x = 0;
|
|
|
|
int y = 0;
|
|
|
|
int const n_components = color_space->number_of_components();
|
|
|
|
auto const bytes_per_component = bits_per_component / 8;
|
|
|
|
Vector<Value> component_values;
|
|
|
|
component_values.resize(n_components);
|
|
|
|
auto content = image->bytes();
|
|
|
|
while (!content.is_empty() && y < height) {
|
|
|
|
auto sample = content.slice(0, bytes_per_component * n_components);
|
|
|
|
content = content.slice(bytes_per_component * n_components);
|
|
|
|
for (int i = 0; i < n_components; ++i) {
|
|
|
|
auto component = sample.slice(0, bytes_per_component);
|
|
|
|
sample = sample.slice(bytes_per_component);
|
|
|
|
component_values[i] = Value { component_value_decoders[i].interpolate(component[0]) };
|
|
|
|
}
|
2023-07-19 04:35:51 +03:00
|
|
|
auto color = TRY(color_space->color(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
|
|
|
bitmap->set_pixel(x, y, color);
|
|
|
|
++x;
|
|
|
|
if (x == width) {
|
|
|
|
x = 0;
|
|
|
|
++y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return bitmap;
|
|
|
|
}
|
|
|
|
|
|
|
|
Gfx::AffineTransform Renderer::calculate_image_space_transformation(int width, int height)
|
|
|
|
{
|
|
|
|
// Image space maps to a 1x1 unit of user space and starts at the top-left
|
|
|
|
auto image_space = state().ctm;
|
|
|
|
image_space.multiply(Gfx::AffineTransform(
|
|
|
|
1.0f / width,
|
|
|
|
0.0f,
|
|
|
|
0.0f,
|
|
|
|
-1.0f / height,
|
|
|
|
0.0f,
|
|
|
|
1.0f));
|
|
|
|
return image_space;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::show_empty_image(int width, int height)
|
|
|
|
{
|
|
|
|
auto image_space_transofmation = calculate_image_space_transformation(width, height);
|
|
|
|
auto image_border = image_space_transofmation.map(Gfx::IntRect { 0, 0, width, height });
|
|
|
|
m_painter.stroke_path(rect_path(image_border), Color::Black, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
PDFErrorOr<void> Renderer::show_image(NonnullRefPtr<StreamObject> image)
|
|
|
|
{
|
|
|
|
auto image_dict = image->dict();
|
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
|
|
|
|
|
|
|
if (!m_rendering_preferences.show_images) {
|
|
|
|
show_empty_image(width, height);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
auto image_bitmap = TRY(load_image(image));
|
|
|
|
if (image_dict->contains(CommonNames::SMask)) {
|
|
|
|
auto smask_bitmap = TRY(load_image(TRY(image_dict->get_stream(m_document, CommonNames::SMask))));
|
2023-10-18 17:57:41 +03:00
|
|
|
|
|
|
|
// Make softmask same size as image.
|
|
|
|
// FIXME: The smask code here is fairly ad-hoc and incomplete.
|
|
|
|
if (smask_bitmap->size() != image_bitmap->size())
|
|
|
|
smask_bitmap = TRY(smask_bitmap->scaled_to_size(image_bitmap->size()));
|
|
|
|
|
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
|
|
|
for (int j = 0; j < image_bitmap->height(); ++j) {
|
|
|
|
for (int i = 0; i < image_bitmap->width(); ++i) {
|
|
|
|
auto image_color = image_bitmap->get_pixel(i, j);
|
|
|
|
auto smask_color = smask_bitmap->get_pixel(i, j);
|
|
|
|
image_color = image_color.with_alpha(smask_color.luminosity());
|
|
|
|
image_bitmap->set_pixel(i, j, image_color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto image_space = calculate_image_space_transformation(width, height);
|
|
|
|
auto image_rect = Gfx::FloatRect { 0, 0, width, height };
|
|
|
|
m_painter.draw_scaled_bitmap_with_transform(image_bitmap->rect(), image_bitmap, image_rect, image_space);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
LibPDF: Refactor parsing of ColorSpaces
ColorSpaces can be specified in two ways: with a stream as operands of
the color space operations (CS/cs), or as a separate PDF object, which
is then referred to by other means (e.g., from Image XObjects and other
entities). These two modes of addressing a ColorSpace are slightly
different and need to be addressed separately. However, the current
implementation embedded the full logic of the first case in the routine
that created ColorSpace objects.
This commit refactors the creation of ColorSpace to support both cases.
First, a new ColorSpaceFamily class encapsulates the static aspects of a
family, like its name or whether color space construction never requires
parameters. Then we define the supported ColorSpaceFamily objects.
On top of this also sit a breakage on how ColorSpaces are created. Two
methods are now offered: one only providing construction of no-argument
color spaces (and thus taking a simple name), and another taking an
ArrayObject, hence used to create ColorSpaces requiring arguments.
Finally, on top of *that* two ways to get a color space in the Renderer
are made available: the first creates a ColorSpace with a name and a
Resources dictionary, and another takes an Object. These model the two
addressing modes described above.
2022-11-24 07:40:24 +03:00
|
|
|
PDFErrorOr<NonnullRefPtr<ColorSpace>> Renderer::get_color_space_from_resources(Value const& value, NonnullRefPtr<DictObject> resources)
|
2021-05-23 22:53:38 +03:00
|
|
|
{
|
LibPDF: Refactor parsing of ColorSpaces
ColorSpaces can be specified in two ways: with a stream as operands of
the color space operations (CS/cs), or as a separate PDF object, which
is then referred to by other means (e.g., from Image XObjects and other
entities). These two modes of addressing a ColorSpace are slightly
different and need to be addressed separately. However, the current
implementation embedded the full logic of the first case in the routine
that created ColorSpace objects.
This commit refactors the creation of ColorSpace to support both cases.
First, a new ColorSpaceFamily class encapsulates the static aspects of a
family, like its name or whether color space construction never requires
parameters. Then we define the supported ColorSpaceFamily objects.
On top of this also sit a breakage on how ColorSpaces are created. Two
methods are now offered: one only providing construction of no-argument
color spaces (and thus taking a simple name), and another taking an
ArrayObject, hence used to create ColorSpaces requiring arguments.
Finally, on top of *that* two ways to get a color space in the Renderer
are made available: the first creates a ColorSpace with a name and a
Resources dictionary, and another takes an Object. These model the two
addressing modes described above.
2022-11-24 07:40:24 +03:00
|
|
|
auto color_space_name = value.get<NonnullRefPtr<Object>>()->cast<NameObject>()->name();
|
|
|
|
auto maybe_color_space_family = ColorSpaceFamily::get(color_space_name);
|
|
|
|
if (!maybe_color_space_family.is_error()) {
|
|
|
|
auto color_space_family = maybe_color_space_family.release_value();
|
2023-10-20 18:11:03 +03:00
|
|
|
if (color_space_family.may_be_specified_directly()) {
|
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
|
|
|
return ColorSpace::create(color_space_name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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-11-03 08:44:18 +03:00
|
|
|
return ColorSpace::create(m_document, color_space_object);
|
2021-05-23 22:53:38 +03:00
|
|
|
}
|
|
|
|
|
2021-06-01 21:16:11 +03:00
|
|
|
Gfx::AffineTransform const& Renderer::calculate_text_rendering_matrix()
|
2021-05-10 20:50:39 +03:00
|
|
|
{
|
|
|
|
if (m_text_rendering_matrix_is_dirty) {
|
|
|
|
m_text_rendering_matrix = Gfx::AffineTransform(
|
|
|
|
text_state().horizontal_scaling,
|
|
|
|
0.0f,
|
|
|
|
0.0f,
|
|
|
|
1.0f,
|
|
|
|
0.0f,
|
|
|
|
text_state().rise);
|
|
|
|
m_text_rendering_matrix.multiply(state().ctm);
|
2022-03-07 00:27:04 +03:00
|
|
|
m_text_rendering_matrix.multiply(m_text_matrix);
|
2021-05-10 20:50:39 +03:00
|
|
|
m_text_rendering_matrix_is_dirty = false;
|
|
|
|
}
|
|
|
|
return m_text_rendering_matrix;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|