mirror of
https://github.com/SerenityOS/serenity.git
synced 2026-11-07 04:29:30 -10:00
LibPDF: Add "slow path" for text rendering that can handle transforms
This adds a slow path for drawing text (that is not simply scaled or translated), which works by creating a Gfx::Path for the text, applying the transform, then filling it. This supports arbitrary (affine) transforms. Currently, the APIs needed to support this are only implemented for TrueType fonts. It should be possible to extend to other (non-bitmap) fonts too, but since not all PDF fonts are built on top of the existing Gfx::Font classes, it requires more work to implement.
This commit is contained in:
1 parent
e103b1ec36
commit
1892b09b9f
7 files changed
+101
-19
No files matched your search
@@ -36,6 +36,10 @@ public:
|
||||
|
||||
virtual void set_font_size(float font_size) = 0;
|
||||
virtual PDFErrorOr<Gfx::FloatPoint> draw_string(Gfx::Painter&, Gfx::FloatPoint, ByteString const&, Renderer const&) = 0;
|
||||
virtual PDFErrorOr<Gfx::FloatPoint> append_text_path(Gfx::Path&, Gfx::FloatPoint, ByteString const&, Renderer const&)
|
||||
{
|
||||
return Error { Error::Type::RenderingUnsupported, "append_text_path not implemented for font" };
|
||||
}
|
||||
|
||||
virtual WritingMode writing_mode() const { return WritingMode::Horizontal; }
|
||||
|
||||
|
||||
@@ -46,17 +46,18 @@ PDFErrorOr<void> SimpleFont::initialize(Document* document, NonnullRefPtr<DictOb
|
||||
return {};
|
||||
}
|
||||
|
||||
PDFErrorOr<Gfx::FloatPoint> SimpleFont::draw_string(Gfx::Painter& painter, Gfx::FloatPoint glyph_position, ByteString const& string, Renderer const& renderer)
|
||||
template<typename Callback>
|
||||
PDFErrorOr<Gfx::FloatPoint> SimpleFont::for_each_glyph_position(Gfx::FloatPoint glyph_position, ByteString const& string, Renderer const& renderer, Callback callback)
|
||||
{
|
||||
auto horizontal_scaling = renderer.text_state().horizontal_scaling;
|
||||
|
||||
auto const& text_rendering_matrix = renderer.calculate_text_rendering_matrix();
|
||||
|
||||
// TrueType fonts are prescaled to text_rendering_matrix.x_scale() * text_state().font_size / horizontal_scaling,
|
||||
// cf `Renderer::text_set_font()`. That's the width we get back from `get_glyph_width()` if we use a fallback
|
||||
// (or built-in) font. Scale the width size too, so the m_width.get() codepath is consistent.
|
||||
auto const font_size = text_rendering_matrix.x_scale() * renderer.text_state().font_size / horizontal_scaling;
|
||||
auto font_scale = 1 / text_rendering_matrix.x_scale() * horizontal_scaling;
|
||||
|
||||
auto const font_size = renderer.text_state().font_size;
|
||||
auto character_spacing = renderer.text_state().character_spacing;
|
||||
auto word_spacing = renderer.text_state().word_spacing;
|
||||
|
||||
@@ -67,20 +68,14 @@ PDFErrorOr<Gfx::FloatPoint> SimpleFont::draw_string(Gfx::Painter& painter, Gfx::
|
||||
if (auto width = m_widths.get(char_code); width.has_value())
|
||||
glyph_width = font_size * width.value() * m_font_matrix.x_scale();
|
||||
else if (auto width = get_glyph_width(char_code); width.has_value())
|
||||
glyph_width = width.value();
|
||||
glyph_width = font_scale * width.value();
|
||||
else
|
||||
glyph_width = font_size * m_missing_width * m_font_matrix.x_scale();
|
||||
|
||||
if (renderer.text_state().rendering_mode != TextRenderingMode::Invisible || renderer.show_hidden_text()) {
|
||||
Gfx::FloatPoint glyph_render_position = text_rendering_matrix.map(glyph_position);
|
||||
TRY(draw_glyph(painter, glyph_render_position, glyph_width, char_code, renderer));
|
||||
}
|
||||
if (renderer.text_state().rendering_mode != TextRenderingMode::Invisible || renderer.show_hidden_text())
|
||||
TRY(callback(glyph_position, glyph_width, char_code));
|
||||
|
||||
// glyph_width is scaled by `text_rendering_matrix.x_scale() * renderer.text_state().font_size / horizontal_scaling`,
|
||||
// but it should only be scaled by `renderer.text_state().font_size`.
|
||||
// FIXME: Having to divide here isn't pretty. Refactor things so that this isn't needed.
|
||||
auto tx = glyph_width / text_rendering_matrix.x_scale() * horizontal_scaling;
|
||||
tx += character_spacing;
|
||||
auto tx = glyph_width + character_spacing;
|
||||
|
||||
// ISO 32000 (PDF 2.0), 9.3.3 Wordspacing
|
||||
// "Word spacing shall be applied to every occurrence of the single-byte character code 32
|
||||
@@ -94,4 +89,26 @@ PDFErrorOr<Gfx::FloatPoint> SimpleFont::draw_string(Gfx::Painter& painter, Gfx::
|
||||
return glyph_position;
|
||||
}
|
||||
|
||||
PDFErrorOr<Gfx::FloatPoint> SimpleFont::append_text_path(Gfx::Path& path, Gfx::FloatPoint position, ByteString const& string, Renderer const& renderer)
|
||||
{
|
||||
auto horizontal_scaling = renderer.text_state().horizontal_scaling;
|
||||
auto const& text_rendering_matrix = renderer.calculate_text_rendering_matrix();
|
||||
return for_each_glyph_position(position, string, renderer, [&](Gfx::FloatPoint glyph_position, float, u8 char_code) {
|
||||
// The glyph position is in terms of the `renderer.text_state().font_size`, so we need to scale it to the requested font size.
|
||||
return append_glyph_path(path, glyph_position.scaled(text_rendering_matrix.x_scale() / horizontal_scaling, 1), char_code, renderer);
|
||||
});
|
||||
}
|
||||
|
||||
PDFErrorOr<Gfx::FloatPoint> SimpleFont::draw_string(Gfx::Painter& painter, Gfx::FloatPoint position, ByteString const& string, Renderer const& renderer)
|
||||
{
|
||||
auto horizontal_scaling = renderer.text_state().horizontal_scaling;
|
||||
auto const& text_rendering_matrix = renderer.calculate_text_rendering_matrix();
|
||||
return for_each_glyph_position(position, string, renderer, [&](Gfx::FloatPoint glyph_position, float glyph_width, u8 char_code) {
|
||||
Gfx::FloatPoint glyph_render_position = text_rendering_matrix.map(glyph_position);
|
||||
// Glyph width is in terms of the `renderer.text_state().font_size`, so we need to scale it to the render size.
|
||||
glyph_width *= text_rendering_matrix.x_scale() / horizontal_scaling;
|
||||
return draw_glyph(painter, glyph_render_position, glyph_width, char_code, renderer);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,17 +14,26 @@ namespace PDF {
|
||||
class SimpleFont : public PDFFont {
|
||||
public:
|
||||
PDFErrorOr<Gfx::FloatPoint> draw_string(Gfx::Painter&, Gfx::FloatPoint, ByteString const&, Renderer const&) override;
|
||||
PDFErrorOr<Gfx::FloatPoint> append_text_path(Gfx::Path&, Gfx::FloatPoint, ByteString const&, Renderer const&) override;
|
||||
|
||||
protected:
|
||||
PDFErrorOr<void> initialize(Document* document, NonnullRefPtr<DictObject> const& dict, float font_size) override;
|
||||
virtual Optional<float> get_glyph_width(u8 char_code) const = 0;
|
||||
virtual PDFErrorOr<void> draw_glyph(Gfx::Painter& painter, Gfx::FloatPoint point, float width, u8 char_code, Renderer const&) = 0;
|
||||
virtual PDFErrorOr<void> append_glyph_path(Gfx::Path&, Gfx::FloatPoint, u8, Renderer const&)
|
||||
{
|
||||
return Error { Error::Type::RenderingUnsupported, "append_glyph_path not implemented for font" };
|
||||
}
|
||||
|
||||
RefPtr<Encoding>& encoding() { return m_encoding; }
|
||||
RefPtr<Encoding> const& encoding() const { return m_encoding; }
|
||||
|
||||
Gfx::AffineTransform& font_matrix() { return m_font_matrix; }
|
||||
|
||||
private:
|
||||
template<typename Callback>
|
||||
PDFErrorOr<Gfx::FloatPoint> for_each_glyph_position(Gfx::FloatPoint, ByteString const&, Renderer const&, Callback callback);
|
||||
|
||||
RefPtr<Encoding> m_encoding;
|
||||
RefPtr<StreamObject> m_to_unicode;
|
||||
HashMap<u8, u16> m_widths;
|
||||
|
||||
@@ -148,6 +148,16 @@ PDFErrorOr<void> TrueTypePainter::draw_glyph(Gfx::Painter& painter, Gfx::FloatPo
|
||||
return {};
|
||||
}
|
||||
|
||||
PDFErrorOr<void> TrueTypePainter::append_glyph_path(Gfx::Path& path, Gfx::FloatPoint point, u8 char_code, Renderer const&)
|
||||
{
|
||||
auto glyph_id = TRY(resolve_glyph_id_for_char_code(char_code));
|
||||
if (glyph_id.has_value()) {
|
||||
path.move_to(point.translated(m_font->glyph_metrics(*glyph_id).left_side_bearing, -m_font->pixel_metrics().ascent));
|
||||
m_font->append_glyph_path_to(path, *glyph_id);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Optional<float> TrueTypePainter::get_glyph_width(u8 char_code) const
|
||||
{
|
||||
// FIXME: Make this use the full char_code lookup method used in draw_glyph() once that's complete.
|
||||
@@ -208,4 +218,9 @@ PDFErrorOr<void> TrueTypeFont::draw_glyph(Gfx::Painter& painter, Gfx::FloatPoint
|
||||
return m_font_painter->draw_glyph(painter, point, width, char_code, renderer);
|
||||
}
|
||||
|
||||
PDFErrorOr<void> TrueTypeFont::append_glyph_path(Gfx::Path& path, Gfx::FloatPoint point, u8 char_code, Renderer const& renderer)
|
||||
{
|
||||
return m_font_painter->append_glyph_path(path, point, char_code, renderer);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,8 @@ public:
|
||||
Optional<float> get_glyph_width(u8 char_code) const;
|
||||
void set_font_size(float font_size);
|
||||
|
||||
PDFErrorOr<void> append_glyph_path(Gfx::Path&, Gfx::FloatPoint point, u8 char_code, Renderer const&);
|
||||
|
||||
private:
|
||||
TrueTypePainter(AK::NonnullRefPtr<Gfx::ScaledFont>, NonnullRefPtr<Encoding>, bool encoding_is_mac_roman_or_win_ansi, bool is_nonsymbolic, Optional<u8> high_byte, bool is_zapf_dingbats);
|
||||
|
||||
@@ -40,6 +42,8 @@ public:
|
||||
void set_font_size(float font_size) override;
|
||||
PDFErrorOr<void> draw_glyph(Gfx::Painter&, Gfx::FloatPoint, float, u8, Renderer const&) override;
|
||||
|
||||
PDFErrorOr<void> append_glyph_path(Gfx::Path&, Gfx::FloatPoint point, u8 char_code, Renderer const&) override;
|
||||
|
||||
DeprecatedFlyString base_font_name() const { return m_base_font_name; }
|
||||
|
||||
protected:
|
||||
|
||||
@@ -27,6 +27,13 @@ public:
|
||||
void set_font_size(float font_size) override;
|
||||
PDFErrorOr<void> draw_glyph(Gfx::Painter& painter, Gfx::FloatPoint point, float width, u8 char_code, Renderer const&) override;
|
||||
|
||||
virtual PDFErrorOr<void> append_glyph_path(Gfx::Path& path, Gfx::FloatPoint point, u8 char_code, Renderer const& renderer) override
|
||||
{
|
||||
if (!m_font_program)
|
||||
return m_fallback_font_painter->append_glyph_path(path, point, char_code, renderer);
|
||||
return Error { Error::Type::RenderingUnsupported, "append_glyph_path not implemented for font" };
|
||||
}
|
||||
|
||||
DeprecatedFlyString base_font_name() const { return m_base_font_name; }
|
||||
|
||||
protected:
|
||||
|
||||
@@ -451,15 +451,20 @@ void Renderer::stroke_current_path()
|
||||
}
|
||||
}
|
||||
|
||||
static void do_fill_path(Gfx::AntiAliasingPainter& painter, Gfx::Path const& path, ColorOrStyle const& style, float paint_style_opacity = 1.0f, Gfx::WindingRule winding_rule = Gfx::WindingRule::Nonzero)
|
||||
{
|
||||
if (auto* paint_style = style.get_pointer<NonnullRefPtr<Gfx::PaintStyle>>()) {
|
||||
painter.fill_path(path, *paint_style, paint_style_opacity, winding_rule);
|
||||
} else {
|
||||
painter.fill_path(path, style.get<Color>(), winding_rule);
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::fill_current_path(Gfx::WindingRule winding_rule)
|
||||
{
|
||||
auto path_end = m_current_path.end();
|
||||
m_current_path.close_all_subpaths();
|
||||
if (state().paint_style.has<NonnullRefPtr<Gfx::PaintStyle>>()) {
|
||||
anti_aliasing_painter().fill_path(m_current_path, state().paint_style.get<NonnullRefPtr<Gfx::PaintStyle>>(), state().paint_alpha_constant, winding_rule);
|
||||
} else {
|
||||
anti_aliasing_painter().fill_path(m_current_path, state().paint_style.get<Color>(), winding_rule);
|
||||
}
|
||||
do_fill_path(anti_aliasing_painter(), m_current_path, state().paint_style, state().paint_alpha_constant, winding_rule);
|
||||
// .close_all_subpaths() only adds to the end of the path, so we can .trim() the path to remove any changes.
|
||||
m_current_path.trim(path_end);
|
||||
}
|
||||
@@ -1375,7 +1380,28 @@ PDFErrorOr<void> Renderer::show_text(ByteString const& string)
|
||||
return Error::rendering_unsupported_error("Can't draw text because an invalid font was in use");
|
||||
|
||||
auto start_position = Gfx::FloatPoint { 0.0f, 0.0f };
|
||||
auto end_position = TRY(text_state().font->draw_string(painter(), start_position, string, *this));
|
||||
auto end_position = start_position;
|
||||
|
||||
if (m_text_rendering_matrix.is_identity_or_translation_or_scale(Gfx::AffineTransform::AllowNegativeScaling::No)) {
|
||||
// Fast path: Use cached bitmap glyphs.
|
||||
end_position = TRY(text_state().font->draw_string(painter(), start_position, string, *this));
|
||||
} else {
|
||||
Gfx::Path text_path;
|
||||
// Slow path: Create a Gfx::Path for the string, transform it, then draw it. This handles arbitrary transforms.
|
||||
if (auto maybe_end = text_state().font->append_text_path(text_path, start_position, string, *this); !maybe_end.is_error()) {
|
||||
end_position = maybe_end.release_value();
|
||||
// FIXME: This is a bit janky, but the font is already scaled by `m_text_rendering_matrix.x_scale() / horizontal_scaling`,
|
||||
// which is included in `m_text_rendering_matrix`. So, we must scale it by the reciprocal. Also, the y-axis is flipped.
|
||||
text_path.transform(m_text_rendering_matrix.multiply(
|
||||
Gfx::AffineTransform {}
|
||||
.set_scale(1 / m_text_rendering_matrix.x_scale() * text_state().horizontal_scaling,
|
||||
-1 / m_text_rendering_matrix.x_scale() * text_state().horizontal_scaling)));
|
||||
do_fill_path(anti_aliasing_painter(), text_path, state().paint_style, state().paint_alpha_constant);
|
||||
} else {
|
||||
// Fallback to the fast path in case `append_path` is unimplemented.
|
||||
end_position = TRY(text_state().font->draw_string(painter(), start_position, string, *this));
|
||||
}
|
||||
}
|
||||
|
||||
// Update text matrix.
|
||||
auto delta = end_position - start_position;
|
||||
|
||||
Reference in new issue
Block a user