Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions src/parse/page_items/embedded_font_blob.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ namespace pdflib
bool is_cid_font,
bool cid_to_gid_identity,
bool uses_builtin_encoding,
std::shared_ptr<const std::vector<uint8_t> > bytes);
std::shared_ptr<const std::vector<uint8_t> > bytes,
std::vector<uint16_t> code_to_gid_map);

// Content-derived identity (FNV-1a over the decoded bytes + size).
// PDF object ids are NOT used: they are only unique within one document,
Expand All @@ -53,6 +54,24 @@ namespace pdflib
// /CIDToGIDMap is /Identity or absent
bool get_cid_to_gid_identity() const { return cid_to_gid_identity; }

// True when a CID font's character codes address glyphs directly: an
// identity /CIDToGIDMap, or a /CIDToGIDMap stream whose CIDs the
// /Encoding CMap resolves.
bool has_code_to_gid() const
{
return is_cid_font and (cid_to_gid_identity or not code_to_gid_map.empty());
}

// Glyph index of a PDF character code under has_code_to_gid(). An
// identity map takes the code itself, as before (exact for
// Identity-H/V); an unmapped code gives GID 0.
uint32_t code_to_gid(int64_t code) const
{
if(cid_to_gid_identity) { return static_cast<uint32_t>(code); }
return (code >= 0 and static_cast<size_t>(code) < code_to_gid_map.size())
? code_to_gid_map[static_cast<size_t>(code)] : 0;
}

// True for a simple font that is symbolic (/FontDescriptor /Flags bit 3)
// and has no /Encoding override: its PDF character codes address the
// font program's builtin cmap directly (PDF 32000-1, 9.6.6.4). Shaping
Expand Down Expand Up @@ -83,6 +102,10 @@ namespace pdflib
bool uses_builtin_encoding;

std::shared_ptr<const std::vector<uint8_t> > bytes;

// /CIDToGIDMap stream composed with the /Encoding CMap (code -> GID);
// empty when the map is identity or absent, or its CIDs are unresolved
std::vector<uint16_t> code_to_gid_map;
};

inline embedded_font_blob::embedded_font_blob():
Expand All @@ -94,7 +117,8 @@ namespace pdflib
is_cid_font(false),
cid_to_gid_identity(false),
uses_builtin_encoding(false),
bytes(nullptr)
bytes(nullptr),
code_to_gid_map()
{}

inline embedded_font_blob::embedded_font_blob(std::string cache_key_,
Expand All @@ -105,7 +129,8 @@ namespace pdflib
bool is_cid_font_,
bool cid_to_gid_identity_,
bool uses_builtin_encoding_,
std::shared_ptr<const std::vector<uint8_t> > bytes_):
std::shared_ptr<const std::vector<uint8_t> > bytes_,
std::vector<uint16_t> code_to_gid_map_):
cache_key(std::move(cache_key_)),
font_name(std::move(font_name_)),
base_font(std::move(base_font_)),
Expand All @@ -114,7 +139,8 @@ namespace pdflib
is_cid_font(is_cid_font_),
cid_to_gid_identity(cid_to_gid_identity_),
uses_builtin_encoding(uses_builtin_encoding_),
bytes(std::move(bytes_))
bytes(std::move(bytes_)),
code_to_gid_map(std::move(code_to_gid_map_))
{}

inline bool embedded_font_blob::has_bytes() const
Expand Down
99 changes: 95 additions & 4 deletions src/parse/pdf_resources/page_font.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ namespace pdflib

embedded_font_format resolve_embedded_font_format() const;
bool resolve_cid_to_gid_identity() const;
std::vector<uint16_t> resolve_code_to_gid_map() const;
void build_embedded_font_blob();

// Resolution of this font onto one of the built-in base fonts. See the
Expand Down Expand Up @@ -257,6 +258,10 @@ namespace pdflib
bool has_explicit_encoding; // true if encoding was found in PDF, false if defaulted
bool is_symbolic = false; // /FontDescriptor /Flags bit 3 (PDF 32000-1 table 123)

// code -> CID of an embedded /Encoding CMap; left empty when the CMap
// inherits through usecmap, whose entries are not read
std::unordered_map<uint32_t, uint32_t> embedded_code_to_cid;

font_subtype_name subtype;

std::string font_key;
Expand Down Expand Up @@ -1196,8 +1201,10 @@ namespace pdflib
// An embedded CMap may inherit from a predefined one. Only its name is
// available here, so the inherited codespace is reported rather than
// silently assumed.
auto report_usecmap = [](const std::string& parent)
bool inherits = false;
auto report_usecmap = [&inherits](const std::string& parent)
{
inherits = true;
LOG_S(WARNING) << "embedded cmap inherits from " << parent
<< ": the inherited entries are not applied";
};
Expand All @@ -1220,6 +1227,11 @@ namespace pdflib

LOG_S(INFO) << "embedded cmap: " << cmap_codespaces.size() << " codespace-range(s), "
<< tables.code_to_cid.size() << " code-to-cid entries";

if(not inherits)
{
embedded_code_to_cid = std::move(tables.code_to_cid);
}
}

void pdf_resource<PAGE_FONT>::init_subtype()
Expand Down Expand Up @@ -1761,8 +1773,8 @@ namespace pdflib
return false;
}

// PDF spec: /CIDToGIDMap defaults to /Identity when absent. Any stream
// value means an explicit map that we do not resolve here.
// PDF spec: /CIDToGIDMap defaults to /Identity when absent. A stream is
// an explicit map, read by resolve_code_to_gid_map().
QPDFObjectHandle value = qpdf_object::get_path(qpdf_desc_font, {"/CIDToGIDMap"});
if(value.isNull())
{
Expand All @@ -1773,6 +1785,83 @@ namespace pdflib
return qpdf_object::get_name_or_string(value, name) and name == "/Identity";
}

std::vector<uint16_t> pdf_resource<PAGE_FONT>::resolve_code_to_gid_map() const
{
std::vector<uint16_t> map;

// Only a CIDFontType2 (TrueType) program is addressed by GID through
// /CIDToGIDMap (ISO 32000-1, 9.7.4.2); CFF CIDFonts map through their
// own charset.
std::string desc_subtype;
QPDFObjectHandle value = qpdf_object::get_path(qpdf_desc_font, {"/CIDToGIDMap"});
if(subtype != TYPE_0 or not value.isStream() or
not qpdf_object::get_name_or_string(qpdf_desc_font, {"/Subtype"}, desc_subtype) or
desc_subtype != "/CIDFontType2")
{
return map;
}

try
{
auto buffer = value.getStreamData(qpdf_dl_all);
if(buffer)
{
// 2 bytes per CID, big-endian GID; CID n at bytes 2n..2n+1
const unsigned char* data = buffer->getBuffer();
map.resize(buffer->getSize() / 2);
for(size_t cid = 0; cid < map.size(); cid++)
{
map[cid] = static_cast<uint16_t>((data[2*cid] << 8) | data[2*cid + 1]);
}
}
}
catch(const std::exception& e)
{
LOG_S(WARNING) << "could not decode /CIDToGIDMap for font-key="
<< font_key << ": " << e.what();
map.clear();
}

// The renderer holds character codes, and the map is indexed by CID:
// compose it with the /Encoding CMap's code -> CID (9.7.6.2). Under
// Identity-H/V the code is the CID already.
if(map.empty() or encoding == IDENTITY_H or encoding == IDENTITY_V)
{
return map;
}

const std::unordered_map<uint32_t, uint32_t>* code_to_cid = nullptr;
if(encoding == CMAP_STREAM)
{
code_to_cid = &embedded_code_to_cid;
}
else if(encoding == CMAP_RESOURCES and cids.decode_cmap_resource(encoding_name))
{
code_to_cid = &cids.get(encoding_name).get_code_to_cid();
}

// Without a code -> CID table the map cannot be applied: an empty result
// keeps the Unicode-shaping path.
if(code_to_cid == nullptr)
{
return {};
}

// shortcut: codes above 0xFFFF (four-byte codespaces) are left unmapped
// and render through Unicode shaping; widen the table if one ever matters.
std::vector<uint16_t> code_to_gid;
for(const auto& [code, cid] : *code_to_cid)
{
if(code <= 0xFFFF and cid < map.size() and map[cid] != 0)
{
if(code >= code_to_gid.size()) { code_to_gid.resize(code + 1, 0); }
code_to_gid[code] = map[cid];
}
}

return code_to_gid;
}

void pdf_resource<PAGE_FONT>::build_embedded_font_blob()
{
if(not font_program.found or
Expand Down Expand Up @@ -1813,7 +1902,8 @@ namespace pdflib
subtype == TYPE_0,
resolve_cid_to_gid_identity(),
uses_builtin_encoding,
std::move(bytes));
std::move(bytes),
resolve_code_to_gid_map());

LOG_S(INFO) << __FUNCTION__
<< ": font-key=" << font_key
Expand All @@ -1824,6 +1914,7 @@ namespace pdflib
<< " cache-key=" << font_blob->get_cache_key()
<< " cid=" << font_blob->get_is_cid_font()
<< " cid-to-gid-identity=" << font_blob->get_cid_to_gid_identity()
<< " has-code-to-gid=" << font_blob->has_code_to_gid()
<< " uses-builtin-encoding=" << font_blob->get_uses_builtin_encoding();
}

Expand Down
2 changes: 2 additions & 0 deletions src/parse/pdf_resources/page_font/font_cid.h
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,8 @@ namespace pdflib
// resource declared none.
const std::vector<cmap_codespace_range>& get_codespaces() const;

const std::unordered_map<uint32_t, uint32_t>& get_code_to_cid() const { return cmap2cid; }

void decode_cmap_resource(std::string filename,
std::string cid2code,
std::vector<std::string> columns);
Expand Down
26 changes: 13 additions & 13 deletions src/render/blend2d_renderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -438,13 +438,13 @@ namespace pdflib
double h_scale);

// Glyph-identity mapping by PDF character code against an embedded
// (Blend2D-loaded) face: CID fonts with an identity CIDToGIDMap use the
// character code as glyph index directly; simple fonts are tried through
// the 0xF000 private-use convention ((3,0) symbol cmaps) and then with
// the raw character code ((1,0) / format-0 builtin cmaps, as written by
// Cairo/LibreOffice subsetters). Used both as the primary path for
// symbolic fonts and as recovery when Unicode shaping failed or
// produced only .notdef. On success gb holds a positioned glyph run
// (Blend2D-loaded) face: CID fonts map the character code to the glyph
// index through /Encoding and /CIDToGIDMap; simple fonts are
// tried through the 0xF000 private-use convention ((3,0) symbol cmaps)
// and then with the raw character code ((1,0) / format-0 builtin cmaps,
// as written by Cairo/LibreOffice subsetters). Used both as the primary
// path for symbolic fonts and as recovery when Unicode shaping failed
// or produced only .notdef. On success gb holds a positioned glyph run
// ready for fill_glyph_run.
static bool recover_embedded_glyphs(const BLFont& font,
text_instruction& instr,
Expand All @@ -455,17 +455,17 @@ namespace pdflib

const auto& blob = instr.get_embedded_font();

if (blob->get_is_cid_font() and blob->get_cid_to_gid_identity())
if (blob->has_code_to_gid())
{
const uint32_t glyph_id = static_cast<uint32_t>(char_code);
const uint32_t glyph_id = blob->code_to_gid(char_code);
gb.set_glyphs(&glyph_id, 1);
// shape() rejects glyph content, so position directly.
if (font.position_glyphs(gb) == BL_SUCCESS and
not gb.is_empty() and
gb.placement_data() != nullptr)
{
LOG_S(INFO) << "render_text: recovered glyph via CID identity"
<< " cid=" << char_code
LOG_S(INFO) << "render_text: recovered glyph via CIDToGIDMap"
<< " char_code=" << char_code
<< " font_name=`" << instr.get_font_name() << "`";
return true;
}
Expand Down Expand Up @@ -2889,7 +2889,7 @@ namespace pdflib
// glyph (e.g. U+0020 hitting subset code 0x20). Resolve by
// character code first for such faces.
//
// CID fonts with an identity CIDToGIDMap must also resolve by
// CID fonts with a /CIDToGIDMap must also resolve by
// character code first: the content-stream CID is the exact glyph
// the producer chose (contextual Arabic forms, lam-alef ligatures,
// …). Round-tripping through the Unicode cmap instead picks the
Expand All @@ -2900,7 +2900,7 @@ namespace pdflib
using_embedded_font and
instr.has_embedded_font() and
(embedded_blob->get_is_cid_font()
? embedded_blob->get_cid_to_gid_identity()
? embedded_blob->has_code_to_gid()
: embedded_blob->get_uses_builtin_encoding());

if (char_code_first)
Expand Down
15 changes: 9 additions & 6 deletions src/render/freetype_font_cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ namespace pdflib
// those files, so they are drawn from here instead.
//
// Glyph identity is resolved in decreasing order of authority:
// 1. CID with identity CIDToGIDMap — the CID is the glyph index;
// 1. CID font: the code through /Encoding and /CIDToGIDMap — the glyph index;
// 2. the glyph name assigned by /Encoding /Differences (FT_Get_Name_Index);
// 3. the decoded PDF character code through the font's builtin encoding
// (Adobe custom / standard / Latin-1, and MS symbol with the 0xF000
Expand Down Expand Up @@ -603,13 +603,16 @@ namespace pdflib
{
glyph_indices.clear();

// CID-keyed font with identity CIDToGIDMap: the character code is the
// CID is the glyph index.
if(blob->get_is_cid_font() and blob->get_cid_to_gid_identity() and char_code >= 0)
// CID-keyed font with a /CIDToGIDMap (identity or stream): the
// character code selects a CID, which the map turns into the glyph index.
if(blob->has_code_to_gid() and char_code >= 0)
{
if(char_code < entry.face->num_glyphs)
const uint32_t gid = blob->code_to_gid(char_code);
// An explicit map sends unused codes to GID 0 (.notdef): no glyph.
const bool mapped = blob->get_cid_to_gid_identity() or gid != 0;
if(mapped and gid < static_cast<uint32_t>(entry.face->num_glyphs))
{
glyph_indices.push_back(static_cast<FT_UInt>(char_code));
glyph_indices.push_back(static_cast<FT_UInt>(gid));
return true;
}
return false;
Expand Down
2 changes: 1 addition & 1 deletion tests/constants.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from tests.regression_page_selection import REGRESSION_PAGE_SELECTION

HF_DATASET_REPO_ID = "docling-project/regression-dataset-for-docling-parse"
HF_DATASET_REVISION = "6d3458e2a6a729defd700a7b91e57b6cb2ce0290"
HF_DATASET_REVISION = "21374211ec8e84cb58eea3c62f869c2adfcc9a54"

REGRESSION_DIR = "tests/data/regression"

Expand Down
Loading
Loading