Skip to content

Commit

Permalink
Use Poppler library for better PDF rendering.
Browse files Browse the repository at this point in the history
The libgraphicsmagick attempts to invoke ghostscript with limited
resolution.

Issues #126
  • Loading branch information
hzeller committed Jan 13, 2024
1 parent fef3191 commit dad7fcc
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 0 deletions.
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ option(WITH_VIDEO_DEVICE "Enables reading videos from devices e.g. v4l2 (require
option(WITH_GRAPHICSMAGICK "Enable general image loading with Graphicsmagick. You typically want this." ON)
option(WITH_TURBOJPEG "Optimized JPEG loading. You typically want this." ON)
option(WITH_RSVG "Use librsvg to open SVG images." ON)
option(WITH_POPPLER "Use poppler to render PDFs" OFF)
option(WITH_STB_IMAGE "Use STB image, a self-contained albeit limited image loading and lower quality. Use if WITH_GRAPHICSMAGICK is not possible and want to limit dependencies. Default on to be used as fallback." ON)
option(WITH_QOI_IMAGE "QOI image format" ON)

Expand Down Expand Up @@ -60,6 +61,11 @@ if(WITH_RSVG)
pkg_check_modules(CAIRO REQUIRED IMPORTED_TARGET cairo)
endif()

if(WITH_POPPLER)
pkg_check_modules(POPPLER REQUIRED IMPORTED_TARGET poppler-glib)
pkg_check_modules(CAIRO REQUIRED IMPORTED_TARGET cairo)
endif()

if(WITH_OPENSLIDE_SUPPORT)
pkg_check_modules(OPENSLIDE IMPORTED_TARGET REQUIRED openslide)
pkg_check_modules(AVUTIL REQUIRED IMPORTED_TARGET libavutil)
Expand Down
1 change: 1 addition & 0 deletions shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pkgs.mkShell {
libexif
libsixel
librsvg cairo
poppler

# Don't include qoi and stb by default to see if the cmake
# fallback to third_party/ works.
Expand Down
6 changes: 6 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ if(WITH_RSVG)
target_link_libraries(timg PkgConfig::RSVG PkgConfig::CAIRO)
endif()

if(WITH_POPPLER)
target_sources(timg PUBLIC pdf-image-source.h pdf-image-source.cc)
target_compile_definitions(timg PUBLIC WITH_TIMG_POPPLER)
target_link_libraries(timg PkgConfig::POPPLER PkgConfig::CAIRO)
endif()

if(WITH_TURBOJPEG)
target_sources(timg PUBLIC jpeg-source.h jpeg-source.cc)
target_compile_definitions(timg PUBLIC WITH_TIMG_JPEG)
Expand Down
8 changes: 8 additions & 0 deletions src/image-source.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "image-display.h"
#include "jpeg-source.h"
#include "openslide-source.h"
#include "pdf-image-source.h"
#include "qoi-image-source.h"
#include "stb-image-source.h"
#include "svg-image-source.h"
Expand Down Expand Up @@ -184,6 +185,13 @@ ImageSource *ImageSource::Create(const std::string &filename,
}
#endif

#ifdef WITH_TIMG_POPPLER
result.reset(new PDFImageSource(filename));
if (result->LoadAndScale(options, frame_offset, frame_count)) {
return result.release();
}
#endif

#ifdef WITH_TIMG_GRPAPHICSMAGICK
result.reset(new ImageLoader(filename));
if (result->LoadAndScale(options, frame_offset, frame_count)) {
Expand Down
119 changes: 119 additions & 0 deletions src/pdf-image-source.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*-
// (c) 2023 Henner Zeller <[email protected]>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://gnu.org/licenses/gpl-2.0.txt>

#include "pdf-image-source.h"

#include <cairo.h>
#include <poppler.h>
#include <stdlib.h>

#include <algorithm>
#include <filesystem>

#include "framebuffer.h"

namespace fs = std::filesystem;

namespace timg {

std::string PDFImageSource::FormatTitle(
const std::string &format_string) const {
return FormatFromParameters(format_string, filename_, (int)orig_width_,
(int)orig_height_, "pdf");
}

bool PDFImageSource::LoadAndScale(const DisplayOptions &opts, int, int) {
options_ = opts;

GError *error = nullptr;

// Poppler wants a URI as input.
std::string uri = "file://" + fs::absolute(filename_).string();

PopplerDocument *document =
poppler_document_new_from_file(uri.c_str(), nullptr, &error);
if (!document) {
fprintf(stderr, "no dice %s\n", error->message);
return false;
}

const int page_count = poppler_document_get_n_pages(document);

for (int page_num = 0; page_num < page_count; ++page_num) {
PopplerPage *page = poppler_document_get_page(document, page_num);
if (page == nullptr) {
return false;
}

poppler_page_get_size(page, &orig_width_, &orig_height_);
int target_width;
int target_height;
CalcScaleToFitDisplay(orig_width_, orig_height_, opts, false,
&target_width, &target_height);

int render_width = target_width;
int render_height = target_height;

const auto kCairoFormat = CAIRO_FORMAT_ARGB32;
int stride = cairo_format_stride_for_width(kCairoFormat, render_width);
std::unique_ptr<timg::Framebuffer> image(
new timg::Framebuffer(stride / 4, render_height));

cairo_surface_t *surface = cairo_image_surface_create_for_data(
(uint8_t *)image->begin(), kCairoFormat, render_width,
render_height, stride);
cairo_t *cr = cairo_create(surface);
cairo_scale(cr, 1.0 * render_width / orig_width_,
1.0 * render_height / orig_height_);
cairo_save(cr);
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_paint(cr);

poppler_page_render(page, cr);
cairo_restore(cr);
g_object_unref(page);

// render
cairo_destroy(cr);
cairo_surface_destroy(surface);

// Cairo stores A (high-byte), R, G, B (low-byte). We need ABGR.
for (rgba_t &pixel : *image) {
std::swap(pixel.r, pixel.b);
}

// TODO: implement auto-crop and crop-border
pages_.emplace_back(std::move(image));
}

return true;
}

int PDFImageSource::IndentationIfCentered(
const timg::Framebuffer &image) const {
return options_.center_horizontally ? (options_.width - image.width()) / 2
: 0;
}

void PDFImageSource::SendFrames(const Duration &duration, int loops,
const volatile sig_atomic_t &interrupt_received,
const Renderer::WriteFramebufferFun &sink) {
for (const auto &page : pages_) {
const int dx = IndentationIfCentered(*page);
sink(dx, 0, *page, SeqType::FrameImmediate, {});
}
}

} // namespace timg
51 changes: 51 additions & 0 deletions src/pdf-image-source.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*-
// (c) 2023 Henner Zeller <[email protected]>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation version 2.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://gnu.org/licenses/gpl-2.0.txt>

#ifndef PDF_SOURCE_H_
#define PDF_SOURCE_H_

#include <memory>
#include <vector>

#include "display-options.h"
#include "image-source.h"
#include "terminal-canvas.h"

namespace timg {
class PDFImageSource final : public ImageSource {
public:
explicit PDFImageSource(const std::string &filename)
: ImageSource(filename) {}

bool LoadAndScale(const DisplayOptions &options, int frame_offset,
int frame_count) final;

void SendFrames(const Duration &duration, int loops,
const volatile sig_atomic_t &interrupt_received,
const Renderer::WriteFramebufferFun &sink) final;

std::string FormatTitle(const std::string &format_string) const final;

private:
int IndentationIfCentered(const timg::Framebuffer &image) const;

DisplayOptions options_;
double orig_width_, orig_height_;
std::vector<std::unique_ptr<timg::Framebuffer>> pages_;
};

} // namespace timg

#endif // QOI_SOURCE_H_
7 changes: 7 additions & 0 deletions src/timg.cc
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
#ifdef WITH_TIMG_RSVG
# include <librsvg/rsvg.h>
#endif
#ifdef WITH_TIMG_POPPLER
# include <poppler.h>
#endif

#include <errno.h>
#include <fcntl.h>
Expand Down Expand Up @@ -442,6 +445,10 @@ static int PrintVersion(FILE *stream) {
fprintf(stream, "librsvg %d.%d.%d\n", LIBRSVG_MAJOR_VERSION,
LIBRSVG_MINOR_VERSION, LIBRSVG_MICRO_VERSION);
#endif
#ifdef WITH_TIMG_POPPLER
fprintf(stream, "PDF rendering with poppler %s\n",
poppler_get_version());
#endif
#ifdef WITH_TIMG_QOI
fprintf(stream, "QOI image loading\n");
#endif
Expand Down

0 comments on commit dad7fcc

Please sign in to comment.