LibPDF: Propagate errors in Renderer/PDFViewer

This commit is contained in:
Matthew Olsson 2022-03-05 18:12:58 -07:00 committed by Andreas Kling
parent d82bd885ce
commit b240d23a87
Notes: sideshowbarker 2024-07-17 17:50:53 +09:00
4 changed files with 140 additions and 72 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, Matthew Olsson <mattco@serenityos.org>
* Copyright (c) 2021-2022, Matthew Olsson <mattco@serenityos.org>
* Copyright (c) 2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
@ -8,6 +8,7 @@
#include "PDFViewer.h"
#include <AK/Array.h>
#include <LibGUI/Action.h>
#include <LibGUI/MessageBox.h>
#include <LibGUI/Painter.h>
#include <LibPDF/Renderer.h>
@ -56,15 +57,15 @@ void PDFViewer::set_document(RefPtr<PDF::Document> document)
update();
}
RefPtr<Gfx::Bitmap> PDFViewer::get_rendered_page(u32 index)
PDF::PDFErrorOr<NonnullRefPtr<Gfx::Bitmap>> PDFViewer::get_rendered_page(u32 index)
{
auto& rendered_page_map = m_rendered_page_list[index];
auto existing_rendered_page = rendered_page_map.get(m_zoom_level);
if (existing_rendered_page.has_value() && existing_rendered_page.value().rotation == m_rotations)
return existing_rendered_page.value().bitmap;
// FIXME: Propogate errors in the Renderer
auto rendered_page = render_page(MUST(m_document->get_page(index)));
auto page = TRY(m_document->get_page(index));
auto rendered_page = TRY(render_page(page));
rendered_page_map.set(m_zoom_level, { rendered_page, m_rotations });
return rendered_page;
}
@ -81,7 +82,14 @@ void PDFViewer::paint_event(GUI::PaintEvent& event)
if (!m_document)
return;
auto page = get_rendered_page(m_current_page_index);
auto maybe_page = get_rendered_page(m_current_page_index);
if (maybe_page.is_error()) {
auto error = maybe_page.release_error();
GUI::MessageBox::show_error(nullptr, String::formatted("Error rendering page:\n{}", error.message()));
return;
}
auto page = maybe_page.release_value();
set_content_size(page->size());
painter.translate(frame_thickness(), frame_thickness());
@ -196,7 +204,7 @@ void PDFViewer::rotate(int degrees)
update();
}
RefPtr<Gfx::Bitmap> PDFViewer::render_page(const PDF::Page& page)
PDF::PDFErrorOr<NonnullRefPtr<Gfx::Bitmap>> PDFViewer::render_page(const PDF::Page& page)
{
auto zoom_scale_factor = static_cast<float>(zoom_levels[m_zoom_level]) / 100.0f;
@ -208,7 +216,7 @@ RefPtr<Gfx::Bitmap> PDFViewer::render_page(const PDF::Page& page)
auto width = height / page_scale_factor;
auto bitmap = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRA8888, { width, height }).release_value_but_fixme_should_propagate_errors();
PDF::Renderer::render(*m_document, page, bitmap);
TRY(PDF::Renderer::render(*m_document, page, bitmap));
if (page.rotate + m_rotations != 0) {
int rotation_count = ((page.rotate + m_rotations) / 90) % 4;

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, Matthew Olsson <mattco@serenityos.org>
* Copyright (c) 2021-2022, Matthew Olsson <mattco@serenityos.org>
* Copyright (c) 2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
@ -45,12 +45,12 @@ protected:
private:
struct RenderedPage {
RefPtr<Gfx::Bitmap> bitmap;
NonnullRefPtr<Gfx::Bitmap> bitmap;
int rotation;
};
RefPtr<Gfx::Bitmap> get_rendered_page(u32 index);
RefPtr<Gfx::Bitmap> render_page(const PDF::Page&);
PDF::PDFErrorOr<NonnullRefPtr<Gfx::Bitmap>> get_rendered_page(u32 index);
PDF::PDFErrorOr<NonnullRefPtr<Gfx::Bitmap>> render_page(const PDF::Page&);
RefPtr<PDF::Document> m_document;
u32 m_current_page_index { 0 };

View file

@ -9,7 +9,7 @@
#include <LibPDF/Renderer.h>
#define RENDERER_HANDLER(name) \
void Renderer::handle_##name([[maybe_unused]] Vector<Value> const& args)
PDFErrorOr<void> Renderer::handle_##name([[maybe_unused]] Vector<Value> const& args)
#define RENDERER_TODO(name) \
RENDERER_HANDLER(name) \
@ -20,9 +20,9 @@
namespace PDF {
void Renderer::render(Document& document, Page const& page, RefPtr<Gfx::Bitmap> bitmap)
PDFErrorOr<void> Renderer::render(Document& document, Page const& page, RefPtr<Gfx::Bitmap> bitmap)
{
Renderer(document, page, bitmap).render();
return Renderer(document, page, bitmap).render();
}
Renderer::Renderer(RefPtr<Document> document, Page const& page, RefPtr<Gfx::Bitmap> bitmap)
@ -56,7 +56,7 @@ Renderer::Renderer(RefPtr<Document> document, Page const& page, RefPtr<Gfx::Bitm
m_bitmap->fill(Gfx::Color::NamedColor::White);
}
void Renderer::render()
PDFErrorOr<void> Renderer::render()
{
// Use our own vector, as the /Content can be an array with multiple
// streams which gets concatenated
@ -68,7 +68,7 @@ void Renderer::render()
if (m_page.contents->is<ArrayObject>()) {
auto contents = m_page.contents->cast<ArrayObject>();
for (auto& ref : *contents) {
auto bytes = MUST(m_document->resolve_to<StreamObject>(ref))->bytes();
auto bytes = TRY(m_document->resolve_to<StreamObject>(ref))->bytes();
byte_buffer.append(bytes.data(), bytes.size());
}
} else {
@ -76,38 +76,44 @@ void Renderer::render()
byte_buffer.append(bytes.data(), bytes.size());
}
auto commands = MUST(Parser::parse_graphics_commands(byte_buffer));
auto commands = TRY(Parser::parse_graphics_commands(byte_buffer));
for (auto& command : commands)
handle_command(command);
TRY(handle_command(command));
return {};
}
void Renderer::handle_command(Command const& command)
PDFErrorOr<void> Renderer::handle_command(Command const& command)
{
switch (command.command_type()) {
#define V(name, snake_name, symbol) \
case CommandType::name: \
handle_##snake_name(command.arguments()); \
TRY(handle_##snake_name(command.arguments())); \
break;
ENUMERATE_COMMANDS(V)
#undef V
case CommandType::TextNextLineShowString:
handle_text_next_line_show_string(command.arguments());
TRY(handle_text_next_line_show_string(command.arguments()));
break;
case CommandType::TextNextLineShowStringSetSpacing:
handle_text_next_line_show_string_set_spacing(command.arguments());
TRY(handle_text_next_line_show_string_set_spacing(command.arguments()));
break;
}
return {};
}
RENDERER_HANDLER(save_state)
{
m_graphics_state_stack.append(state());
return {};
}
RENDERER_HANDLER(restore_state)
{
m_graphics_state_stack.take_last();
return {};
}
RENDERER_HANDLER(concatenate_matrix)
@ -122,26 +128,31 @@ RENDERER_HANDLER(concatenate_matrix)
state().ctm.multiply(new_transform);
m_text_rendering_matrix_is_dirty = true;
return {};
}
RENDERER_HANDLER(set_line_width)
{
state().line_width = args[0].to_float();
return {};
}
RENDERER_HANDLER(set_line_cap)
{
state().line_cap_style = static_cast<LineCapStyle>(args[0].get<int>());
return {};
}
RENDERER_HANDLER(set_line_join)
{
state().line_join_style = static_cast<LineJoinStyle>(args[0].get<int>());
return {};
}
RENDERER_HANDLER(set_miter_limit)
{
state().miter_limit = args[0].to_float();
return {};
}
RENDERER_HANDLER(set_dash_pattern)
@ -151,10 +162,11 @@ RENDERER_HANDLER(set_dash_pattern)
for (auto& element : *dash_array)
pattern.append(element.get<int>());
state().line_dash_pattern = LineDashPattern { pattern, args[1].get<int>() };
return {};
}
RENDERER_TODO(set_color_rendering_intent);
RENDERER_TODO(set_flatness_tolerance);
RENDERER_TODO(set_color_rendering_intent)
RENDERER_TODO(set_flatness_tolerance)
RENDERER_HANDLER(set_graphics_state_from_dict)
{
@ -162,27 +174,31 @@ RENDERER_HANDLER(set_graphics_state_from_dict)
auto dict_name = MUST(m_document->resolve_to<NameObject>(args[0]))->name();
auto ext_gstate_dict = MUST(m_page.resources->get_dict(m_document, CommonNames::ExtGState));
auto target_dict = MUST(ext_gstate_dict->get_dict(m_document, dict_name));
set_graphics_state_from_dict(target_dict);
TRY(set_graphics_state_from_dict(target_dict));
return {};
}
RENDERER_HANDLER(path_move)
{
m_current_path.move_to(map(args[0].to_float(), args[1].to_float()));
return {};
}
RENDERER_HANDLER(path_line)
{
VERIFY(!m_current_path.segments().is_empty());
m_current_path.line_to(map(args[0].to_float(), args[1].to_float()));
return {};
}
RENDERER_TODO(path_cubic_bezier_curve);
RENDERER_TODO(path_cubic_bezier_curve_no_first_control);
RENDERER_TODO(path_cubic_bezier_curve_no_second_control);
RENDERER_TODO(path_cubic_bezier_curve)
RENDERER_TODO(path_cubic_bezier_curve_no_first_control)
RENDERER_TODO(path_cubic_bezier_curve_no_second_control)
RENDERER_HANDLER(path_close)
{
m_current_path.close();
return {};
}
RENDERER_HANDLER(path_append_rect)
@ -195,63 +211,74 @@ RENDERER_HANDLER(path_append_rect)
m_current_path.line_to({ pos.x() + size.width(), pos.y() + size.height() });
m_current_path.line_to({ pos.x(), pos.y() + size.height() });
m_current_path.close();
return {};
}
RENDERER_HANDLER(path_stroke)
{
m_painter.stroke_path(m_current_path, state().stroke_color, state().line_width);
m_current_path.clear();
return {};
}
RENDERER_HANDLER(path_close_and_stroke)
{
m_current_path.close();
handle_path_stroke(args);
TRY(handle_path_stroke(args));
return {};
}
RENDERER_HANDLER(path_fill_nonzero)
{
m_painter.fill_path(m_current_path, state().paint_color, Gfx::Painter::WindingRule::Nonzero);
m_current_path.clear();
return {};
}
RENDERER_HANDLER(path_fill_nonzero_deprecated)
{
handle_path_fill_nonzero(args);
TRY(handle_path_fill_nonzero(args));
return {};
}
RENDERER_HANDLER(path_fill_evenodd)
{
m_painter.fill_path(m_current_path, state().paint_color, Gfx::Painter::WindingRule::EvenOdd);
m_current_path.clear();
return {};
}
RENDERER_HANDLER(path_fill_stroke_nonzero)
{
m_painter.stroke_path(m_current_path, state().stroke_color, state().line_width);
handle_path_fill_nonzero(args);
TRY(handle_path_fill_nonzero(args));
return {};
}
RENDERER_HANDLER(path_fill_stroke_evenodd)
{
m_painter.stroke_path(m_current_path, state().stroke_color, state().line_width);
handle_path_fill_evenodd(args);
TRY(handle_path_fill_evenodd(args));
return {};
}
RENDERER_HANDLER(path_close_fill_stroke_nonzero)
{
m_current_path.close();
handle_path_fill_stroke_nonzero(args);
TRY(handle_path_fill_stroke_nonzero(args));
return {};
}
RENDERER_HANDLER(path_close_fill_stroke_evenodd)
{
m_current_path.close();
handle_path_fill_stroke_evenodd(args);
TRY(handle_path_fill_stroke_evenodd(args));
return {};
}
RENDERER_HANDLER(path_end)
{
return {};
}
RENDERER_HANDLER(path_intersect_clip_nonzero)
@ -259,6 +286,7 @@ RENDERER_HANDLER(path_intersect_clip_nonzero)
// FIXME: Support arbitrary path clipping in the painter and utilize that here
auto bounding_box = map(m_current_path.bounding_box());
m_painter.add_clip_rect(bounding_box.to_type<int>());
return {};
}
RENDERER_HANDLER(path_intersect_clip_evenodd)
@ -266,38 +294,45 @@ RENDERER_HANDLER(path_intersect_clip_evenodd)
// FIXME: Support arbitrary path clipping in the painter and utilize that here
auto bounding_box = map(m_current_path.bounding_box());
m_painter.add_clip_rect(bounding_box.to_type<int>());
return {};
}
RENDERER_HANDLER(text_begin)
{
m_text_matrix = Gfx::AffineTransform();
m_text_line_matrix = Gfx::AffineTransform();
return {};
}
RENDERER_HANDLER(text_end)
{
// FIXME: Do we need to do anything here?
return {};
}
RENDERER_HANDLER(text_set_char_space)
{
text_state().character_spacing = args[0].to_float();
return {};
}
RENDERER_HANDLER(text_set_word_space)
{
text_state().word_spacing = args[0].to_float();
return {};
}
RENDERER_HANDLER(text_set_horizontal_scale)
{
m_text_rendering_matrix_is_dirty = true;
text_state().horizontal_scaling = args[0].to_float() / 100.0f;
return {};
}
RENDERER_HANDLER(text_set_leading)
{
text_state().leading = args[0].to_float();
return {};
}
RENDERER_HANDLER(text_set_font)
@ -330,17 +365,20 @@ RENDERER_HANDLER(text_set_font)
text_state().font_variant = font_variant;
m_text_rendering_matrix_is_dirty = true;
return {};
}
RENDERER_HANDLER(text_set_rendering_mode)
{
text_state().rendering_mode = static_cast<TextRenderingMode>(args[0].get<int>());
return {};
}
RENDERER_HANDLER(text_set_rise)
{
m_text_rendering_matrix_is_dirty = true;
text_state().rise = args[0].to_float();
return {};
}
RENDERER_HANDLER(text_next_line_offset)
@ -350,12 +388,14 @@ RENDERER_HANDLER(text_next_line_offset)
m_text_matrix = transform;
m_text_line_matrix = transform;
m_text_rendering_matrix_is_dirty = true;
return {};
}
RENDERER_HANDLER(text_next_line_and_set_leading)
{
text_state().leading = -args[1].to_float();
handle_text_next_line_offset(args);
TRY(handle_text_next_line_offset(args));
return {};
}
RENDERER_HANDLER(text_set_matrix_and_line_matrix)
@ -370,26 +410,30 @@ RENDERER_HANDLER(text_set_matrix_and_line_matrix)
m_text_line_matrix = new_transform;
m_text_matrix = new_transform;
m_text_rendering_matrix_is_dirty = true;
return {};
}
RENDERER_HANDLER(text_next_line)
{
handle_text_next_line_offset({ 0.0f, -text_state().leading });
TRY(handle_text_next_line_offset({ 0.0f, -text_state().leading }));
return {};
}
RENDERER_HANDLER(text_show_string)
{
auto text = MUST(m_document->resolve_to<StringObject>(args[0]))->string();
show_text(text);
return {};
}
RENDERER_HANDLER(text_next_line_show_string)
{
handle_text_next_line(args);
handle_text_show_string(args);
TRY(handle_text_next_line(args));
TRY(handle_text_show_string(args));
return {};
}
RENDERER_TODO(text_next_line_show_string_set_spacing);
RENDERER_TODO(text_next_line_show_string_set_spacing)
RENDERER_HANDLER(text_show_string_array)
{
@ -406,85 +450,97 @@ RENDERER_HANDLER(text_show_string_array)
show_text(str, next_shift);
}
}
return {};
}
RENDERER_TODO(type3_font_set_glyph_width);
RENDERER_TODO(type3_font_set_glyph_width_and_bbox);
RENDERER_TODO(type3_font_set_glyph_width)
RENDERER_TODO(type3_font_set_glyph_width_and_bbox)
RENDERER_HANDLER(set_stroking_space)
{
state().stroke_color_space = MUST(get_color_space(args[0]));
VERIFY(state().stroke_color_space);
return {};
}
RENDERER_HANDLER(set_painting_space)
{
state().paint_color_space = MUST(get_color_space(args[0]));
VERIFY(state().paint_color_space);
return {};
}
RENDERER_HANDLER(set_stroking_color)
{
state().stroke_color = state().stroke_color_space->color(args);
return {};
}
RENDERER_TODO(set_stroking_color_extended);
RENDERER_TODO(set_stroking_color_extended)
RENDERER_HANDLER(set_painting_color)
{
state().paint_color = state().paint_color_space->color(args);
return {};
}
RENDERER_TODO(set_painting_color_extended);
RENDERER_TODO(set_painting_color_extended)
RENDERER_HANDLER(set_stroking_color_and_space_to_gray)
{
state().stroke_color_space = DeviceGrayColorSpace::the();
state().stroke_color = state().stroke_color_space->color(args);
return {};
}
RENDERER_HANDLER(set_painting_color_and_space_to_gray)
{
state().paint_color_space = DeviceGrayColorSpace::the();
state().paint_color = state().paint_color_space->color(args);
return {};
}
RENDERER_HANDLER(set_stroking_color_and_space_to_rgb)
{
state().stroke_color_space = DeviceRGBColorSpace::the();
state().stroke_color = state().stroke_color_space->color(args);
return {};
}
RENDERER_HANDLER(set_painting_color_and_space_to_rgb)
{
state().paint_color_space = DeviceRGBColorSpace::the();
state().paint_color = state().paint_color_space->color(args);
return {};
}
RENDERER_HANDLER(set_stroking_color_and_space_to_cmyk)
{
state().stroke_color_space = DeviceCMYKColorSpace::the();
state().stroke_color = state().stroke_color_space->color(args);
return {};
}
RENDERER_HANDLER(set_painting_color_and_space_to_cmyk)
{
state().paint_color_space = DeviceCMYKColorSpace::the();
state().paint_color = state().paint_color_space->color(args);
return {};
}
RENDERER_TODO(shade);
RENDERER_TODO(inline_image_begin);
RENDERER_TODO(inline_image_begin_data);
RENDERER_TODO(inline_image_end);
RENDERER_TODO(paint_xobject);
RENDERER_TODO(marked_content_point);
RENDERER_TODO(marked_content_designate);
RENDERER_TODO(marked_content_begin);
RENDERER_TODO(marked_content_begin_with_property_list);
RENDERER_TODO(marked_content_end);
RENDERER_TODO(compatibility_begin);
RENDERER_TODO(compatibility_end);
RENDERER_TODO(shade)
RENDERER_TODO(inline_image_begin)
RENDERER_TODO(inline_image_begin_data)
RENDERER_TODO(inline_image_end)
RENDERER_TODO(paint_xobject)
RENDERER_TODO(marked_content_point)
RENDERER_TODO(marked_content_designate)
RENDERER_TODO(marked_content_begin)
RENDERER_TODO(marked_content_begin_with_property_list)
RENDERER_TODO(marked_content_end)
RENDERER_TODO(compatibility_begin)
RENDERER_TODO(compatibility_end)
template<typename T>
Gfx::Point<T> Renderer::map(T x, T y) const
@ -505,25 +561,29 @@ Gfx::Rect<T> Renderer::map(Gfx::Rect<T> rect) const
return state().ctm.map(rect);
}
void Renderer::set_graphics_state_from_dict(NonnullRefPtr<DictObject> dict)
PDFErrorOr<void> Renderer::set_graphics_state_from_dict(NonnullRefPtr<DictObject> dict)
{
if (dict->contains(CommonNames::LW))
handle_set_line_width({ dict->get_value(CommonNames::LW) });
TRY(handle_set_line_width({ dict->get_value(CommonNames::LW) }));
if (dict->contains(CommonNames::LC))
handle_set_line_cap({ dict->get_value(CommonNames::LC) });
TRY(handle_set_line_cap({ dict->get_value(CommonNames::LC) }));
if (dict->contains(CommonNames::LJ))
handle_set_line_join({ dict->get_value(CommonNames::LJ) });
TRY(handle_set_line_join({ dict->get_value(CommonNames::LJ) }));
if (dict->contains(CommonNames::ML))
handle_set_miter_limit({ dict->get_value(CommonNames::ML) });
TRY(handle_set_miter_limit({ dict->get_value(CommonNames::ML) }));
if (dict->contains(CommonNames::D))
handle_set_dash_pattern(MUST(dict->get_array(m_document, CommonNames::D))->elements());
if (dict->contains(CommonNames::D)) {
auto array = MUST(dict->get_array(m_document, CommonNames::D));
TRY(handle_set_dash_pattern(array->elements()));
}
if (dict->contains(CommonNames::FL))
handle_set_flatness_tolerance({ dict->get_value(CommonNames::FL) });
TRY(handle_set_flatness_tolerance({ dict->get_value(CommonNames::FL) }));
return {};
}
void Renderer::show_text(String const& string, float shift)

View file

@ -79,22 +79,22 @@ struct GraphicsState {
class Renderer {
public:
static void render(Document&, Page const&, RefPtr<Gfx::Bitmap>);
static PDFErrorOr<void> render(Document&, Page const&, RefPtr<Gfx::Bitmap>);
private:
Renderer(RefPtr<Document>, Page const&, RefPtr<Gfx::Bitmap>);
void render();
PDFErrorOr<void> render();
void handle_command(Command const&);
PDFErrorOr<void> handle_command(Command const&);
#define V(name, snake_name, symbol) \
void handle_##snake_name(Vector<Value> const& args);
PDFErrorOr<void> handle_##snake_name(Vector<Value> const& args);
ENUMERATE_COMMANDS(V)
#undef V
void handle_text_next_line_show_string(Vector<Value> const& args);
void handle_text_next_line_show_string_set_spacing(Vector<Value> const& args);
PDFErrorOr<void> handle_text_next_line_show_string(Vector<Value> const& args);
PDFErrorOr<void> handle_text_next_line_show_string_set_spacing(Vector<Value> const& args);
void set_graphics_state_from_dict(NonnullRefPtr<DictObject>);
PDFErrorOr<void> set_graphics_state_from_dict(NonnullRefPtr<DictObject>);
// shift is the manual advance given in the TJ command array
void show_text(String const&, float shift = 0.0f);
PDFErrorOr<NonnullRefPtr<ColorSpace>> get_color_space(Value const&);