From 7c4f5b58be6e8548a9940a6125ba09d1ee2907a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Offenh=C3=A4user?= Date: Thu, 17 Nov 2022 23:18:00 +0100 Subject: LibPDF: Use Gfx::PathRasterizer for Adobe Type 1 font rendering This gives much better visual results than painting the path directly. It also has the nice side effect that Type 1 fonts will now look much more similar to TrueType fonts, which use the same class :^) In addition, we can now cache glyph bitmaps for repeated use. --- Userland/Libraries/LibPDF/Fonts/PS1FontProgram.cpp | 52 +++++++++++++++++++--- Userland/Libraries/LibPDF/Fonts/PS1FontProgram.h | 6 ++- Userland/Libraries/LibPDF/Fonts/Type1Font.cpp | 21 ++++++--- Userland/Libraries/LibPDF/Fonts/Type1Font.h | 1 + 4 files changed, 69 insertions(+), 11 deletions(-) (limited to 'Userland/Libraries/LibPDF') diff --git a/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.cpp b/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.cpp index b4d15f1291..cb266f44fe 100644 --- a/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.cpp +++ b/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -86,20 +87,61 @@ PDFErrorOr PS1FontProgram::parse(ReadonlyBytes const& bytes, size_t cleart return parse_encrypted_portion(decrypted); } -Gfx::Path PS1FontProgram::build_char(u32 code_point, Gfx::FloatPoint const& point, float width) +RefPtr PS1FontProgram::rasterize_glyph(u32 code_point, float width) { - if (!m_glyph_map.contains(code_point)) + auto path = build_char(code_point, width); + auto bounding_box = path.bounding_box().size(); + + u32 w = (u32)ceilf(bounding_box.width()) + 1; + u32 h = (u32)ceilf(bounding_box.height()) + 1; + + Gfx::PathRasterizer rasterizer(Gfx::IntSize(w, h)); + rasterizer.draw_path(path); + return rasterizer.accumulate(); +} + +Gfx::Path PS1FontProgram::build_char(u32 code_point, float width) +{ + auto maybe_glyph = m_glyph_map.get(code_point); + if (!maybe_glyph.has_value()) return {}; - auto glyph = m_glyph_map.get(code_point).value(); + auto& glyph = maybe_glyph.value(); + auto transform = glyph_transform_to_device_space(glyph, width); + + // Translate such that the top-left point is at [0, 0]. + auto bounding_box = glyph.path.bounding_box(); + Gfx::FloatPoint translation(-bounding_box.x(), -(bounding_box.y() + bounding_box.height())); + transform.translate(translation); + + return glyph.path.copy_transformed(transform); +} + +Gfx::FloatPoint PS1FontProgram::glyph_translation(u32 code_point, float width) const +{ + auto maybe_glyph = m_glyph_map.get(code_point); + if (!maybe_glyph.has_value()) + return {}; + + auto& glyph = maybe_glyph.value(); + auto transform = glyph_transform_to_device_space(glyph, width); + + // Undo the translation we applied earlier. + auto bounding_box = glyph.path.bounding_box(); + Gfx::FloatPoint translation(bounding_box.x(), bounding_box.y() + bounding_box.height()); + + return transform.map(translation); +} + +Gfx::AffineTransform PS1FontProgram::glyph_transform_to_device_space(Glyph const& glyph, float width) const +{ auto scale = width / (m_font_matrix.a() * glyph.width + m_font_matrix.e()); auto transform = m_font_matrix; // Convert character space to device space. transform.scale(scale, -scale); - transform.set_translation(point); - return glyph.path.copy_transformed(transform); + return transform; } PDFErrorOr PS1FontProgram::parse_glyph(ReadonlyBytes const& data, GlyphParserState& state) diff --git a/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.h b/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.h index b119f2f19b..0a8afa733d 100644 --- a/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.h +++ b/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.h @@ -20,9 +20,11 @@ class PS1FontProgram : public RefCounted { public: PDFErrorOr parse(ReadonlyBytes const&, size_t cleartext_length, size_t encrypted_length); - Gfx::Path build_char(u32 code_point, Gfx::FloatPoint const& point, float width); + RefPtr rasterize_glyph(u32 code_point, float width); + Gfx::Path build_char(u32 code_point, float width); RefPtr encoding() const { return m_encoding; } + Gfx::FloatPoint glyph_translation(u32 code_point, float width) const; private: struct Glyph { @@ -46,6 +48,8 @@ private: Array postscript_stack; }; + Gfx::AffineTransform glyph_transform_to_device_space(Glyph const&, float width) const; + PDFErrorOr parse_glyph(ReadonlyBytes const&, GlyphParserState&); PDFErrorOr parse_encrypted_portion(ByteBuffer const&); PDFErrorOr> parse_subroutines(Reader&); diff --git a/Userland/Libraries/LibPDF/Fonts/Type1Font.cpp b/Userland/Libraries/LibPDF/Fonts/Type1Font.cpp index ac15a8837f..e580fe8ada 100644 --- a/Userland/Libraries/LibPDF/Fonts/Type1Font.cpp +++ b/Userland/Libraries/LibPDF/Fonts/Type1Font.cpp @@ -122,11 +122,22 @@ float Type1Font::get_char_width(u16 char_code, float) const void Type1Font::draw_glyph(Gfx::Painter& painter, Gfx::IntPoint const& point, float width, u32 code_point, Color color) { - // FIXME: Make a glyph cache - if (m_data.font_program) { - auto path = m_data.font_program->build_char(code_point, { point.x(), point.y() }, width); - Gfx::AntiAliasingPainter aa_painter(painter); - aa_painter.fill_path(path, color, Gfx::Painter::WindingRule::EvenOdd); + if (!m_data.font_program) + return; + + RefPtr bitmap; + + auto maybe_bitmap = m_glyph_cache.get(code_point); + if (maybe_bitmap.has_value()) { + bitmap = maybe_bitmap.value(); + } else { + bitmap = m_data.font_program->rasterize_glyph(code_point, width); + m_glyph_cache.set(code_point, bitmap); } + + auto translation = m_data.font_program->glyph_translation(code_point, width); + painter.blit_filtered(point.translated(translation.to_rounded()), *bitmap, bitmap->rect(), [color](Color pixel) -> Color { + return pixel.multiply(color); + }); } } diff --git a/Userland/Libraries/LibPDF/Fonts/Type1Font.h b/Userland/Libraries/LibPDF/Fonts/Type1Font.h index cc44e3076e..ce875d27dc 100644 --- a/Userland/Libraries/LibPDF/Fonts/Type1Font.h +++ b/Userland/Libraries/LibPDF/Fonts/Type1Font.h @@ -40,6 +40,7 @@ public: private: Data m_data; + HashMap> m_glyph_cache; }; } -- cgit v1.2.3