From c3f78d95611d8cc5a885aef1d606698e539fd2bc Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Tue, 11 Jul 2023 13:25:36 -0400 Subject: [PATCH] pdf: Add function to render a page of a PDF to a bitmap Use like so: Build/lagom/bin/pdf --render foo.png --page=50 path/to.pdf --- Meta/Lagom/CMakeLists.txt | 2 +- Userland/Utilities/CMakeLists.txt | 2 +- Userland/Utilities/pdf.cpp | 56 +++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/Meta/Lagom/CMakeLists.txt b/Meta/Lagom/CMakeLists.txt index e0de29926a..df3d88b386 100644 --- a/Meta/Lagom/CMakeLists.txt +++ b/Meta/Lagom/CMakeLists.txt @@ -561,7 +561,7 @@ if (BUILD_LAGOM) endif() add_executable(pdf ../../Userland/Utilities/pdf.cpp) - target_link_libraries(pdf LibCore LibPDF LibMain) + target_link_libraries(pdf LibCore LibGfx LibPDF LibMain) add_executable(sql ../../Userland/Utilities/sql.cpp) target_link_libraries(sql LibCore LibFileSystem LibIPC LibLine LibMain LibSQL) diff --git a/Userland/Utilities/CMakeLists.txt b/Userland/Utilities/CMakeLists.txt index bbfc94a9fa..6b0447f889 100644 --- a/Userland/Utilities/CMakeLists.txt +++ b/Userland/Utilities/CMakeLists.txt @@ -123,7 +123,7 @@ target_link_libraries(notify PRIVATE LibGfx LibGUI) target_link_libraries(open PRIVATE LibDesktop LibFileSystem) target_link_libraries(passwd PRIVATE LibCrypt) target_link_libraries(paste PRIVATE LibGUI) -target_link_libraries(pdf PRIVATE LibPDF) +target_link_libraries(pdf PRIVATE LibGfx LibPDF) target_link_libraries(pgrep PRIVATE LibRegex) target_link_libraries(pixelflut PRIVATE LibImageDecoderClient LibIPC LibGfx) target_link_libraries(pkill PRIVATE LibRegex) diff --git a/Userland/Utilities/pdf.cpp b/Userland/Utilities/pdf.cpp index 1a850b42c4..38b9bcb089 100644 --- a/Userland/Utilities/pdf.cpp +++ b/Userland/Utilities/pdf.cpp @@ -4,11 +4,14 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include +#include #include #include +#include static PDF::PDFErrorOr print_document_info_dict(PDF::Document& document) { @@ -41,6 +44,37 @@ static PDF::PDFErrorOr print_document_info(PDF::Document& document) return {}; } +static PDF::PDFErrorOr> render_page(PDF::Document& document, int page_index) +{ + auto page = TRY(document.get_page(page_index)); + + auto page_size = Gfx::IntSize { 800, round_to(800 * page.media_box.height() / page.media_box.width()) }; + + auto bitmap = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, page_size)); + + auto errors = PDF::Renderer::render(document, page, bitmap, PDF::RenderingPreferences {}); + if (errors.is_error()) { + for (auto const& error : errors.error().errors()) + warnln("warning: {}", error.message()); + } + return bitmap; +} + +static PDF::PDFErrorOr save_rendered_page(PDF::Document& document, int page_index, StringView out_path) +{ + auto bitmap = TRY(render_page(document, page_index)); + + if (!out_path.ends_with(".png"sv, CaseSensitivity::CaseInsensitive)) + return Error::from_string_view("can only save to .png files"sv); + + auto output_stream = TRY(Core::File::open(out_path, Core::File::OpenMode::Write)); + auto buffered_stream = TRY(Core::OutputBufferedFile::create(move(output_stream))); + ByteBuffer bytes = TRY(Gfx::PNGWriter::encode(*bitmap)); + TRY(buffered_stream->write_until_depleted(bytes)); + + return {}; +} + static PDF::PDFErrorOr pdf_main(Main::Arguments arguments) { Core::ArgsParser args_parser; @@ -51,6 +85,12 @@ static PDF::PDFErrorOr pdf_main(Main::Arguments arguments) StringView in_path; args_parser.add_positional_argument(in_path, "Path to input image file", "FILE"); + StringView render_path; + args_parser.add_option(render_path, "Path to render a PDF page to", "render", {}, "PNG FILE"); + + u32 page_number = 1; + args_parser.add_option(page_number, "Page number (1-based)", "page", {}, "PAGE"); + args_parser.parse(arguments); auto file = TRY(Core::MappedFile::map(in_path)); @@ -70,6 +110,22 @@ static PDF::PDFErrorOr pdf_main(Main::Arguments arguments) TRY(document->initialize()); + if (page_number < 1 || page_number > document->get_page_count()) { + warnln("--page {} out of bounds, must be between 1 and {}", page_number, document->get_page_count()); + return 1; + } + + if (!render_path.is_empty()) { +#if !defined(AK_OS_SERENITY) + // Get from Build/lagom/bin/pdf to Base/res/fonts. + auto source_root = LexicalPath(arguments.argv[0]).parent().parent().parent().parent().string(); + Gfx::FontDatabase::set_default_fonts_lookup_path(DeprecatedString::formatted("{}/Base/res/fonts", source_root)); +#endif + + TRY(save_rendered_page(document, page_number - 1, render_path)); + return 0; + } + TRY(print_document_info(*document)); return 0;