diff --git a/Userland/Libraries/LibPDF/CommonNames.cpp b/Userland/Libraries/LibPDF/CommonNames.cpp index f0ac03a0ca..f1a0a54996 100644 --- a/Userland/Libraries/LibPDF/CommonNames.cpp +++ b/Userland/Libraries/LibPDF/CommonNames.cpp @@ -13,5 +13,6 @@ ENUMERATE_COMMON_NAMES(ENUMERATE) #undef ENUMERATE DeprecatedFlyString CommonNames::IdentityH = "Identity-H"; +DeprecatedFlyString CommonNames::IdentityV = "Identity-V"; } diff --git a/Userland/Libraries/LibPDF/CommonNames.h b/Userland/Libraries/LibPDF/CommonNames.h index b0ba1762cb..36c3a38c34 100644 --- a/Userland/Libraries/LibPDF/CommonNames.h +++ b/Userland/Libraries/LibPDF/CommonNames.h @@ -206,7 +206,9 @@ public: ENUMERATE_COMMON_NAMES(ENUMERATE) #undef ENUMERATE + // Special cases where the string value isn't identical to the name. static DeprecatedFlyString IdentityH; + static DeprecatedFlyString IdentityV; }; } diff --git a/Userland/Libraries/LibPDF/Fonts/Type0Font.cpp b/Userland/Libraries/LibPDF/Fonts/Type0Font.cpp index 0595bc90b7..9658315190 100644 --- a/Userland/Libraries/LibPDF/Fonts/Type0Font.cpp +++ b/Userland/Libraries/LibPDF/Fonts/Type0Font.cpp @@ -239,6 +239,11 @@ Type0Font::~Type0Font() = default; class IdentityType0CMap : public Type0CMap { public: + IdentityType0CMap(WritingMode writing_mode) + : Type0CMap(writing_mode) + { + } + virtual PDFErrorOr> iterate(ReadonlyBytes bytes) const override { // 9.7.5.2 Predefined CMaps: @@ -278,10 +283,12 @@ static PDFErrorOr> make_cmap(NonnullRefPtr cons return Error::rendering_unsupported_error("Type0 font: support for general type 0 cmaps not yet implemented"); auto cmap_name = cmap_value->cast()->name(); - if (cmap_name != CommonNames::IdentityH) + if (cmap_name != CommonNames::IdentityH && cmap_name != CommonNames::IdentityV) return Error::rendering_unsupported_error("Type0 font: unimplemented named type 0 cmap {}", cmap_name); - return make(); + WritingMode writing_mode = cmap_name.ends_with("-H"sv) ? WritingMode::Horizontal : WritingMode::Vertical; + + return make(writing_mode); } PDFErrorOr Type0Font::initialize(Document* document, NonnullRefPtr const& dict, float font_size) @@ -438,10 +445,14 @@ PDFErrorOr Type0Font::draw_string(Gfx::Painter& painter, Gfx::F auto character_spacing = renderer.text_state().character_spacing; auto word_spacing = renderer.text_state().word_spacing; + WritingMode writing_mode = m_cmap->writing_mode(); + auto cids = TRY(m_cmap->iterate(string.bytes())); while (cids->has_next()) { auto cid = cids->next(); + // FIGURE 5.5 Metrics for horizontal and vertical writing modes + // Use the width specified in the font's dictionary if available, // and use the default width for the given font otherwise. float glyph_width; @@ -450,16 +461,35 @@ PDFErrorOr Type0Font::draw_string(Gfx::Painter& painter, Gfx::F else glyph_width = font_size * m_missing_width / 1000.0f; - Gfx::FloatPoint glyph_render_position = text_rendering_matrix.map(glyph_position); - TRY(m_cid_font_type->draw_glyph(painter, glyph_render_position, glyph_width, cid, renderer)); + VerticalMetric vertical_metric; + float vertical_displacement_vector_y; + float position_vector_x = 0.0f; + float position_vector_y = 0.0f; + if (writing_mode == WritingMode::Vertical) { + if (auto metric = m_vertical_metrics.get(cid); metric.has_value()) { + vertical_metric = metric.value(); + vertical_displacement_vector_y = renderer.text_state().font_size * vertical_metric.vertical_displacement_vector_y / 1000.0f; + position_vector_x = vertical_metric.position_vector_x / 1000.0f; + position_vector_y = vertical_metric.position_vector_y / 1000.0f; + } else { + vertical_displacement_vector_y = renderer.text_state().font_size * m_default_displacement_vector_y / 1000.0f; + position_vector_x = glyph_width / 2.0f / font_size; + position_vector_y = m_default_position_vector_y / 1000.0f; + } + } - // FIXME: Honor encoding's WMode for vertical text. + Gfx::FloatPoint glyph_render_position = text_rendering_matrix.map(glyph_position - Gfx::FloatPoint { position_vector_x, position_vector_y }); + TRY(m_cid_font_type->draw_glyph(painter, glyph_render_position, glyph_width, cid, renderer)); // 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; + float displacement; + if (writing_mode == WritingMode::Horizontal) + displacement = glyph_width / text_rendering_matrix.x_scale() * horizontal_scaling; + else + displacement = vertical_displacement_vector_y; + displacement += 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 @@ -468,9 +498,12 @@ PDFErrorOr Type0Font::draw_string(Gfx::Painter& painter, Gfx::F // FIXME: Identity-H always uses 2 bytes, but this will be true once we support more encodings. bool was_single_byte_code = false; if (cid == ' ' && was_single_byte_code) - tx += word_spacing; + displacement += word_spacing; - glyph_position += { tx, 0.0f }; + if (writing_mode == WritingMode::Horizontal) + glyph_position += { displacement, 0.0f }; + else + glyph_position += { 0.0f, displacement }; } return glyph_position; } diff --git a/Userland/Libraries/LibPDF/Fonts/Type0Font.h b/Userland/Libraries/LibPDF/Fonts/Type0Font.h index 34c96e44b7..ebdca2c341 100644 --- a/Userland/Libraries/LibPDF/Fonts/Type0Font.h +++ b/Userland/Libraries/LibPDF/Fonts/Type0Font.h @@ -27,10 +27,28 @@ public: virtual u32 next() = 0; }; +enum class WritingMode { + Horizontal, + Vertical, +}; + class Type0CMap { public: virtual ~Type0CMap() = default; + + // "(Writing mode is specified as part of the CMap because, in some cases, different shapes are used when writing horizontally and vertically. + // In such cases, the horizontal and vertical variants of a CMap specify different CIDs for a given character code.)" + WritingMode writing_mode() const { return m_writing_mode; } + virtual PDFErrorOr> iterate(ReadonlyBytes) const = 0; + +protected: + Type0CMap(WritingMode writing_mode) + : m_writing_mode(writing_mode) + { + } + + WritingMode m_writing_mode; }; class Type0Font : public PDFFont { diff --git a/Userland/Libraries/LibPDF/Renderer.cpp b/Userland/Libraries/LibPDF/Renderer.cpp index da6b5e86ff..6c69bb10e1 100644 --- a/Userland/Libraries/LibPDF/Renderer.cpp +++ b/Userland/Libraries/LibPDF/Renderer.cpp @@ -1036,9 +1036,9 @@ PDFErrorOr Renderer::show_text(ByteString const& string) auto end_position = TRY(text_state().font->draw_string(m_painter, start_position, string, *this)); // Update text matrix. - auto delta_x = end_position.x() - start_position.x(); + auto delta = end_position - start_position; m_text_rendering_matrix_is_dirty = true; - m_text_matrix.translate(delta_x * text_state().horizontal_scaling, 0.0f); + m_text_matrix.translate(delta); return {}; }