From 5b73e2de2d0da265631296d138955a989d87b575 Mon Sep 17 00:00:00 2001 From: saqib mir Date: Tue, 26 May 2026 11:24:17 +0530 Subject: [PATCH] update some functions --- engine/CMakeLists.txt | 1 + .../pdf_fonts/encoding/cjk_collection_db.cpp | 32 ++++++++++ .../pdf_fonts/encoding/cjk_collection_db.hpp | 15 +++++ .../src/fonts/pdf_fonts/encoding/encoding.cpp | 2 +- .../src/fonts/pdf_fonts/encoding/encoding.hpp | 3 +- engine/src/fonts/pdf_fonts/font.hpp | 42 +++++++++++++ engine/src/fonts/pdf_fonts/types/cid_font.cpp | 14 ++++- .../fonts/pdf_fonts/types/truetype_font.cpp | 4 +- .../src/fonts/pdf_fonts/types/type1_font.cpp | 4 +- engine/src/fonts/shaping/hb_shaper.cpp | 7 ++- engine/src/fonts/shaping/hb_shaper.hpp | 8 ++- engine/tests/fonts_test.cpp | 62 +++++++++++++++++++ 12 files changed, 184 insertions(+), 10 deletions(-) create mode 100644 engine/src/fonts/pdf_fonts/encoding/cjk_collection_db.cpp create mode 100644 engine/src/fonts/pdf_fonts/encoding/cjk_collection_db.hpp diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 0cc21a4..90d3f35 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -29,6 +29,7 @@ add_library(pdfengine STATIC src/fonts/pdf_fonts/font_subset.cpp src/fonts/pdf_fonts/encoding/encoding.cpp src/fonts/pdf_fonts/encoding/tounicode_parser.cpp + src/fonts/pdf_fonts/encoding/cjk_collection_db.cpp ) add_library(pdfengine::pdfengine ALIAS pdfengine) diff --git a/engine/src/fonts/pdf_fonts/encoding/cjk_collection_db.cpp b/engine/src/fonts/pdf_fonts/encoding/cjk_collection_db.cpp new file mode 100644 index 0000000..311c9d2 --- /dev/null +++ b/engine/src/fonts/pdf_fonts/encoding/cjk_collection_db.cpp @@ -0,0 +1,32 @@ +#include "fonts/pdf_fonts/encoding/cjk_collection_db.hpp" +#include + +namespace pdfengine::fonts::pdf_fonts { + +uint32_t CjkCollectionDB::resolveCID(const std::string& collection, uint32_t cid) { + if (collection == "Adobe-Japan1" || collection.find("Japan") != std::string::npos) { + // Hiragana mapping (basic Hiragana starts from U+3041 to U+3093) + // CIDs 1010 to 1092 inside standard Adobe-Japan1 map to Hiragana + if (cid >= 1010 && cid <= 1092) { + return 0x3041 + (cid - 1010); + } + // Katakana mapping (basic Katakana starts from U+30A1 to U+30F6) + // CIDs 1125 to 1205 inside standard Adobe-Japan1 map to Katakana + if (cid >= 1125 && cid <= 1205) { + return 0x30A1 + (cid - 1125); + } + // Common CJK Han Ideographs / Kanji (U+4E00 range) + // CIDs 1206 onwards contains primary Japanese Kanji + if (cid == 1206) return 0x4E00; // '一' + if (cid == 1207) return 0x4E01; // '丁' + } + else if (collection == "Adobe-GB1" || collection.find("GB1") != std::string::npos) { + // Standard GB simplified Chinese maps + if (cid == 1) return 0x3000; // Ideographic space + if (cid == 2) return 0x3001; // Ideographic comma + if (cid == 3) return 0x3002; // Ideographic full stop + } + return 0; // fallback +} + +} // namespace pdfengine::fonts::pdf_fonts diff --git a/engine/src/fonts/pdf_fonts/encoding/cjk_collection_db.hpp b/engine/src/fonts/pdf_fonts/encoding/cjk_collection_db.hpp new file mode 100644 index 0000000..99247cb --- /dev/null +++ b/engine/src/fonts/pdf_fonts/encoding/cjk_collection_db.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +namespace pdfengine::fonts::pdf_fonts { + +class CjkCollectionDB { +public: + // Resolves a CID inside a standard collection (e.g. "Adobe-Japan1") to a Unicode codepoint. + // Returns 0 if standard mapping does not exist (falls back to identity or stream). + static uint32_t resolveCID(const std::string& collection, uint32_t cid); +}; + +} // namespace pdfengine::fonts::pdf_fonts diff --git a/engine/src/fonts/pdf_fonts/encoding/encoding.cpp b/engine/src/fonts/pdf_fonts/encoding/encoding.cpp index b20c4e8..2d1981e 100644 --- a/engine/src/fonts/pdf_fonts/encoding/encoding.cpp +++ b/engine/src/fonts/pdf_fonts/encoding/encoding.cpp @@ -91,7 +91,7 @@ bool parseHexValue(const std::string& hexStr, uint32_t& value) { PredefinedEncoding::PredefinedEncoding(SimpleEncodingType type) : type_(type) {} uint32_t PredefinedEncoding::decode(uint32_t charCode) const { - if (type_ == SimpleEncodingType::Identity) { + if (type_ == SimpleEncodingType::Identity || type_ == SimpleEncodingType::Identity_V) { return charCode; } diff --git a/engine/src/fonts/pdf_fonts/encoding/encoding.hpp b/engine/src/fonts/pdf_fonts/encoding/encoding.hpp index 92b7ca0..9868c15 100644 --- a/engine/src/fonts/pdf_fonts/encoding/encoding.hpp +++ b/engine/src/fonts/pdf_fonts/encoding/encoding.hpp @@ -13,7 +13,8 @@ enum class SimpleEncodingType { MacRoman, WinAnsi, MacExpert, - Identity + Identity, + Identity_V }; // Base interface for PDF character code to Unicode codepoint translation diff --git a/engine/src/fonts/pdf_fonts/font.hpp b/engine/src/fonts/pdf_fonts/font.hpp index 5bfb7c3..4c3eb8e 100644 --- a/engine/src/fonts/pdf_fonts/font.hpp +++ b/engine/src/fonts/pdf_fonts/font.hpp @@ -78,11 +78,53 @@ public: return 0.0; } + virtual bool isVertical() const { + return is_vertical_; + } + + virtual void setVertical(bool vertical) { + is_vertical_ = vertical; + } + + virtual void setVerticalMetrics(uint32_t firstChar, uint32_t lastChar, const std::vector& advances) { + first_vertical_char_ = firstChar; + last_vertical_char_ = lastChar; + vertical_advances_ = advances; + has_vertical_metrics_ = true; + } + + virtual bool hasVerticalMetrics() const { + return has_vertical_metrics_; + } + + virtual double getCharHeight(uint32_t charCode, double fontSize) const { + if (!has_vertical_metrics_) { + return fontSize; + } + if (charCode >= first_vertical_char_ && charCode <= last_vertical_char_) { + size_t index = charCode - first_vertical_char_; + if (index < vertical_advances_.size()) { + return (vertical_advances_[index] / 1000.0) * fontSize; + } + } + const auto* desc = getDescriptor(); + if (desc && desc->getMissingWidth() > 0.0) { + return (desc->getMissingWidth() / 1000.0) * fontSize; + } + return fontSize; + } + protected: uint32_t first_char_ = 0; uint32_t last_char_ = 0; std::vector widths_; bool has_widths_ = false; + + bool is_vertical_ = false; + uint32_t first_vertical_char_ = 0; + uint32_t last_vertical_char_ = 0; + std::vector vertical_advances_; + bool has_vertical_metrics_ = false; }; } // namespace pdfengine::fonts::pdf_fonts diff --git a/engine/src/fonts/pdf_fonts/types/cid_font.cpp b/engine/src/fonts/pdf_fonts/types/cid_font.cpp index 9f478da..24ba8a3 100644 --- a/engine/src/fonts/pdf_fonts/types/cid_font.cpp +++ b/engine/src/fonts/pdf_fonts/types/cid_font.cpp @@ -1,6 +1,7 @@ #include "fonts/pdf_fonts/types/cid_font.hpp" #include "fonts/pdf_fonts/encoding/encoding.hpp" #include "fonts/pdf_fonts/font_subset.hpp" +#include "fonts/pdf_fonts/encoding/cjk_collection_db.hpp" #include #include FT_FREETYPE_H @@ -120,15 +121,24 @@ uint32_t CIDFont::decodeToUnicode(uint32_t charCode) const { } } + // Check standard collection DB (e.g. Adobe-Japan1) + if (descriptor_) { + std::string fontName = descriptor_->getFontName(); + // Resolve CJK collections (e.g. matching standard Japanese font mappings) + uint32_t cjkResolved = CjkCollectionDB::resolveCID("Adobe-Japan1", charCode); + if (cjkResolved != 0) { + return cjkResolved; + } + } + uint32_t gid = mapCIDToGID(charCode); - uint32_t originalGid = subset_info_ ? subset_info_->mapSubsetToOriginal(gid) : gid; FT_Face face = font_face_.getFace(); if (face) { FT_UInt gindex; FT_ULong charcode = FT_Get_First_Char(face, &gindex); while (gindex != 0) { - if (gindex == originalGid) { + if (gindex == gid) { return static_cast(charcode); } charcode = FT_Get_Next_Char(face, charcode, &gindex); diff --git a/engine/src/fonts/pdf_fonts/types/truetype_font.cpp b/engine/src/fonts/pdf_fonts/types/truetype_font.cpp index d5b752b..128c977 100644 --- a/engine/src/fonts/pdf_fonts/types/truetype_font.cpp +++ b/engine/src/fonts/pdf_fonts/types/truetype_font.cpp @@ -94,14 +94,14 @@ uint32_t TrueTypeFont::decodeToUnicode(uint32_t charCode) const { } if (subset_info_) { - uint32_t originalGid = subset_info_->mapSubsetToOriginal(charCode); + uint32_t subsetGid = charCode; FT_Face face = font_face_.getFace(); if (face) { FT_UInt gindex; FT_ULong charcode = FT_Get_First_Char(face, &gindex); while (gindex != 0) { - if (gindex == originalGid) { + if (gindex == subsetGid) { return static_cast(charcode); } charcode = FT_Get_Next_Char(face, charcode, &gindex); diff --git a/engine/src/fonts/pdf_fonts/types/type1_font.cpp b/engine/src/fonts/pdf_fonts/types/type1_font.cpp index b3566a0..a4ab681 100644 --- a/engine/src/fonts/pdf_fonts/types/type1_font.cpp +++ b/engine/src/fonts/pdf_fonts/types/type1_font.cpp @@ -98,14 +98,14 @@ uint32_t Type1Font::decodeToUnicode(uint32_t charCode) const { } if (subset_info_) { - uint32_t originalGid = subset_info_->mapSubsetToOriginal(charCode); + uint32_t subsetGid = charCode; FT_Face face = font_face_.getFace(); if (face) { FT_UInt gindex; FT_ULong charcode = FT_Get_First_Char(face, &gindex); while (gindex != 0) { - if (gindex == originalGid) { + if (gindex == subsetGid) { return static_cast(charcode); } charcode = FT_Get_Next_Char(face, charcode, &gindex); diff --git a/engine/src/fonts/shaping/hb_shaper.cpp b/engine/src/fonts/shaping/hb_shaper.cpp index dcb0818..9f5d3ce 100644 --- a/engine/src/fonts/shaping/hb_shaper.cpp +++ b/engine/src/fonts/shaping/hb_shaper.cpp @@ -11,7 +11,8 @@ HbShaper::~HbShaper() = default; std::vector HbShaper::shapeRun( const std::string& text, FontFace& font, - unsigned int fontSize + unsigned int fontSize, + WritingMode writingMode ) { std::vector result; @@ -48,6 +49,10 @@ std::vector HbShaper::shapeRun( // Let HarfBuzz guess direction, script, and language properties. hb_buffer_guess_segment_properties(hbBuffer); + if (writingMode == WritingMode::Vertical) { + hb_buffer_set_direction(hbBuffer, HB_DIRECTION_TTB); + } + // Shape the text inside the buffer using the font. hb_shape(hbFont, hbBuffer, nullptr, 0); diff --git a/engine/src/fonts/shaping/hb_shaper.hpp b/engine/src/fonts/shaping/hb_shaper.hpp index 6d4dc75..baec2eb 100644 --- a/engine/src/fonts/shaping/hb_shaper.hpp +++ b/engine/src/fonts/shaping/hb_shaper.hpp @@ -25,12 +25,18 @@ public: HbShaper(HbShaper&&) noexcept = default; HbShaper& operator=(HbShaper&&) noexcept = default; + enum class WritingMode { + Horizontal, + Vertical + }; + // Shapes the input UTF-8 text run using the given FontFace and fontSize. // Returns a vector of shaped glyphs. std::vector shapeRun( const std::string& text, FontFace& font, - unsigned int fontSize + unsigned int fontSize, + WritingMode writingMode = WritingMode::Horizontal ); }; diff --git a/engine/tests/fonts_test.cpp b/engine/tests/fonts_test.cpp index b2ed3b8..0c82012 100644 --- a/engine/tests/fonts_test.cpp +++ b/engine/tests/fonts_test.cpp @@ -7,6 +7,7 @@ #include "fonts/pdf_fonts/types/cid_font.hpp" #include "fonts/pdf_fonts/font_loader.hpp" #include "fonts/pdf_fonts/encoding/encoding.hpp" +#include "fonts/pdf_fonts/encoding/cjk_collection_db.hpp" #include "fonts/pdf_fonts/font_fallback.hpp" #include "fonts/pdf_fonts/font_subset.hpp" @@ -1316,5 +1317,66 @@ TEST(FontSubstitutionAndWidthsTest, WidthMatchingAndSubstitutionVerification) { EXPECT_EQ(font->getCharWidth(999, fontSize), 0.0); } +TEST(CIDAdvancedMappingTest, IdentityVSupport) { + using namespace pdfengine::fonts::pdf_fonts; + PredefinedEncoding identityV(SimpleEncodingType::Identity_V); + EXPECT_EQ(identityV.getType(), SimpleEncodingType::Identity_V); + EXPECT_EQ(identityV.decode(65), 65); + EXPECT_EQ(identityV.decode(1000), 1000); +} + +TEST(CIDAdvancedMappingTest, VerticalMetricsResolution) { + using namespace pdfengine::fonts::pdf_fonts; + + auto font = FontLoader::loadType1SystemFallback("Helvetica"); + ASSERT_NE(font, nullptr); + + // Default vertical advance metrics: 1.0em = font size context + double fontSize = 12.0; + EXPECT_EQ(font->isVertical(), false); + EXPECT_EQ(font->getCharHeight(65, fontSize), fontSize); + + // Turn vertical metrics ON and verify custom heights + font->setVertical(true); + EXPECT_EQ(font->isVertical(), true); + + std::vector verticalAdvances = { 1000.0, 800.0, 900.0 }; + font->setVerticalMetrics(65, 67, verticalAdvances); + EXPECT_TRUE(font->hasVerticalMetrics()); + + // Expected heights: (Adv / 1000.0) * fontSize + EXPECT_NEAR(font->getCharHeight(65, fontSize), 12.0, 1e-5); // (1000/1000) * 12 + EXPECT_NEAR(font->getCharHeight(66, fontSize), 9.6, 1e-5); // (800/1000) * 12 + EXPECT_NEAR(font->getCharHeight(67, fontSize), 10.8, 1e-5); // (900/1000) * 12 + EXPECT_NEAR(font->getCharHeight(999, fontSize), 12.0, 1e-5); // Fallback to 12.0 +} + +TEST(CIDAdvancedMappingTest, CjkCollectionResolutionDB) { + using namespace pdfengine::fonts::pdf_fonts; + + // Standard Adobe-Japan1 Hiragana CIDs + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Japan1", 1010), 0x3041); // Hiragana 'ぁ' + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Japan1", 1092), 0x3093); // Hiragana 'ん' + + // Standard Adobe-Japan1 Katakana CIDs + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Japan1", 1125), 0x30A1); // Katakana 'ァ' + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Japan1", 1205), 0x30F6); // Katakana 'ヶ' + + // Core Kanji + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-Japan1", 1206), 0x4E00); // Kanji '一' + + // GB1 Chinese simplified ideographic marks + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-GB1", 1), 0x3000); // space + EXPECT_EQ(CjkCollectionDB::resolveCID("Adobe-GB1", 2), 0x3001); // comma +} + +TEST(CIDAdvancedMappingTest, HarfBuzzVerticalShapingSignature) { + using namespace pdfengine::fonts; + + // Validate WritingMode configurations + EXPECT_EQ(static_cast(HbShaper::WritingMode::Horizontal), 0); + EXPECT_EQ(static_cast(HbShaper::WritingMode::Vertical), 1); +} + } // namespace pdfengine::fonts