update the pipiline

This commit is contained in:
saqib mir
2026-06-11 18:19:26 +05:30
parent 9159426037
commit b52890818b
10 changed files with 1080 additions and 5 deletions
+507
View File
@@ -11,6 +11,7 @@
#include <atomic>
#include <chrono>
#include "fonts/cache/glyph_cache.hpp"
#include "fonts/pdf_fonts/font.hpp"
#ifndef TEST_CORPUS_DIR
#define TEST_CORPUS_DIR "../../corpus"
#endif
@@ -1336,4 +1337,510 @@ TEST(GlyphCacheTest, ConcurrencyBench) {
EXPECT_LE(cache.size(), 1000 + 16); // Accommodate shard capacity rounding
}
TEST(FontDiagnosticsTest, EmbeddedFontResolutionAndReloadingVerification) {
SKIP_IF_NO_PDFIUM();
std::vector<std::string> testFiles = {
"text_font.pdf",
"embedded_truetype.pdf",
"embedded_cid_font.pdf",
"subset_font.pdf",
"latin_extended.pdf"
};
bool foundAnyEmbedded = false;
for (const auto& fileName : testFiles) {
auto path = getCorpusPath("fonts", fileName);
if (!std::filesystem::exists(path)) {
continue;
}
std::cout << "\n========================================\n";
std::cout << "Testing PDF: " << fileName << "\n";
std::cout << "========================================\n";
auto docRes = PdfDocument::loadFromFile(path.string());
if (!docRes.has_value()) {
std::cout << "Failed to load document: " << fileName << std::endl;
continue;
}
auto doc = *docRes;
auto fontsRes = doc->getFonts();
if (!fontsRes.has_value()) {
std::cout << "Failed to get fonts for: " << fileName << std::endl;
continue;
}
const auto& fonts = *fontsRes;
for (const auto& fontInfo : fonts) {
std::cout << "Font: " << fontInfo.fontName
<< ", type: " << fontInfo.type
<< ", isEmbedded: " << (fontInfo.isEmbedded ? "yes" : "no")
<< ", flags: " << fontInfo.flags << std::endl;
if (fontInfo.isEmbedded) {
foundAnyEmbedded = true;
auto resolvedFontRes = doc->getResolvedFont(fontInfo);
if (!resolvedFontRes.has_value()) {
std::cout << " Failed to resolve font: " << resolvedFontRes.error() << std::endl;
continue;
}
auto resolvedFont = *resolvedFontRes;
std::cout << " Resolved font successfully." << std::endl;
auto face = static_cast<FT_Face>(resolvedFont->getFontFace().getFace());
if (face) {
std::cout << " FreeType Face Num Glyphs: " << face->num_glyphs << std::endl;
std::cout << " FreeType Charmaps Count: " << face->num_charmaps << std::endl;
for (int i = 0; i < face->num_charmaps; ++i) {
FT_CharMap cm = face->charmaps[i];
std::cout << " Charmap " << i << ": platform_id=" << cm->platform_id
<< ", encoding_id=" << cm->encoding_id << std::endl;
FT_Error err = FT_Set_Charmap(face, cm);
if (err) {
std::cout << " FT_Set_Charmap failed: " << err << std::endl;
continue;
}
FT_UInt gindex;
FT_ULong charcode = FT_Get_First_Char(face, &gindex);
std::cout << " Mapped characters under charmap " << i << ": ";
int count = 0;
while (gindex != 0 && count < 10) {
std::cout << charcode << "->" << gindex << " ";
charcode = FT_Get_Next_Char(face, charcode, &gindex);
count++;
}
std::cout << std::endl;
}
// Restore first charmap
if (face->num_charmaps > 0) {
FT_Set_Charmap(face, face->charmaps[0]);
}
// Print all glyph names in the face
std::cout << " Glyph names: ";
for (int i = 0; i < face->num_glyphs; ++i) {
char nameBuf[64] = {0};
if (FT_Get_Glyph_Name(face, i, nameBuf, sizeof(nameBuf)) == 0) {
std::cout << i << ":" << nameBuf << " ";
} else {
std::cout << i << ":[unknown] ";
}
}
std::cout << std::endl;
} else {
std::cout << " No FreeType Face available." << std::endl;
}
EXPECT_TRUE(resolvedFont->isEmbedded());
// Let's test a few common characters: 'A' (65), 'a' (97), '0' (48), ' ' (32)
std::vector<uint32_t> testChars = {32, 48, 65, 97};
for (uint32_t cp : testChars) {
bool hasG = resolvedFont->hasGlyph(cp);
double w = resolvedFont->getAdvanceWidth(cp, 12.0);
std::cout << " char(" << cp << "): hasGlyph=" << (hasG ? "yes" : "no")
<< ", advanceWidth=" << w << std::endl;
}
// Verify metrics returned are non-zero/valid
auto metrics = resolvedFont->getMetrics(12.0);
std::cout << " Metrics: ascent=" << metrics.ascent << ", descent=" << metrics.descent << ", capHeight=" << metrics.capHeight << std::endl;
EXPECT_NE(metrics.ascent, 0.0);
EXPECT_NE(metrics.descent, 0.0);
EXPECT_NE(metrics.capHeight, 0.0);
// Specific verification for text_font.pdf where we mapped charcode 1 -> GID 1
if (fileName == "text_font.pdf") {
// hasGlyph(1) should return true because the charmap maps 1 -> 1
EXPECT_TRUE(resolvedFont->hasGlyph(1));
double w = resolvedFont->getAdvanceWidth(1, 12.0);
EXPECT_GT(w, 0.0);
std::cout << " [VERIFIED] text_font.pdf char(1): hasGlyph=yes, advanceWidth=" << w << std::endl;
}
// Verify we can load glyphs directly by glyph index (0 to num_glyphs - 1)
if (face->num_glyphs > 1) {
bool foundNonZeroWidth = false;
for (int gid = 1; gid < face->num_glyphs; ++gid) {
FT_Error err = FT_Load_Glyph(face, gid, FT_LOAD_DEFAULT);
if (err == 0) {
double directWidth = static_cast<double>(face->glyph->advance.x) / 64.0;
if (directWidth > 0.0) {
foundNonZeroWidth = true;
std::cout << " [VERIFIED] Direct glyph " << gid << " load: advanceWidth=" << directWidth << std::endl;
break;
}
}
}
EXPECT_TRUE(foundNonZeroWidth) << "Expected to find at least one glyph with a non-zero advance width";
}
}
}
}
EXPECT_TRUE(foundAnyEmbedded) << "Expected to find at least one embedded font in test files";
}
TEST(DocumentEditTest, ReplaceTextMVPStandardFont) {
SKIP_IF_NO_PDFIUM();
auto path = getCorpusPath("basic", "hello_world.pdf");
if (!std::filesystem::exists(path)) {
GTEST_SKIP() << "hello_world.pdf not found in corpus.";
}
auto docRes = PdfDocument::loadFromFile(path.string());
ASSERT_TRUE(docRes.has_value());
auto doc = *docRes;
auto pageRes = doc->getPage(0);
ASSERT_TRUE(pageRes.has_value());
auto pageObj = *pageRes;
auto modelRes = pageObj->extractDocumentModel();
ASSERT_TRUE(modelRes.has_value());
const auto& model = *modelRes;
std::vector<int> objectIndices;
for (const auto& p : model.paragraphs) {
for (const auto& line : p.lines) {
for (const auto& run : line.runs) {
if (run.text.find("Hello") != std::string::npos) {
objectIndices = run.objectIndices;
break;
}
}
if (!objectIndices.empty()) break;
}
if (!objectIndices.empty()) break;
}
ASSERT_FALSE(objectIndices.empty()) << "Could not find a text object in hello_world.pdf";
std::string indicesStr = "";
for (size_t i = 0; i < objectIndices.size(); ++i) {
indicesStr += std::to_string(objectIndices[i]);
if (i + 1 < objectIndices.size()) indicesStr += ",";
}
// Flat format payload
std::string editsJson = R"({
"version": "1.0",
"operations": [
{
"id": "op_mvp_1",
"type": "replace_text",
"pageIndex": 0,
"objectIndices": [)" + indicesStr + R"(],
"text": "Greeting, universe!"
}
]
})";
auto editRes = doc->applyEdits(editsJson);
ASSERT_TRUE(editRes.has_value());
auto saveRes = doc->saveIncremental();
ASSERT_TRUE(saveRes.has_value());
const auto& savedBytes = *saveRes;
ASSERT_FALSE(savedBytes.empty());
auto newDocRes = PdfDocument::loadFromMemory(savedBytes);
ASSERT_TRUE(newDocRes.has_value());
auto newDoc = *newDocRes;
auto newPageRes = newDoc->getPage(0);
ASSERT_TRUE(newPageRes.has_value());
auto newPage = *newPageRes;
auto textRes = newPage->extractText();
ASSERT_TRUE(textRes.has_value());
EXPECT_NE(textRes->find("Greeting, universe!"), std::string::npos);
EXPECT_EQ(textRes->find("Hello"), std::string::npos);
}
TEST(DocumentEditTest, ReplaceTextRuntimeFontEngine) {
SKIP_IF_NO_PDFIUM();
auto path = getCorpusPath("fonts", "latin_extended.pdf");
if (!std::filesystem::exists(path)) {
GTEST_SKIP() << "latin_extended.pdf not found in corpus.";
}
auto docRes = PdfDocument::loadFromFile(path.string());
ASSERT_TRUE(docRes.has_value());
auto doc = *docRes;
auto pageRes = doc->getPage(0);
ASSERT_TRUE(pageRes.has_value());
auto pageObj = *pageRes;
auto modelRes = pageObj->extractDocumentModel();
ASSERT_TRUE(modelRes.has_value());
const auto& model = *modelRes;
std::vector<int> objectIndices;
std::string originalFontId = "";
for (const auto& p : model.paragraphs) {
for (const auto& line : p.lines) {
for (const auto& run : line.runs) {
if (run.fontName.find("Roboto-Regular") != std::string::npos) {
objectIndices = run.objectIndices;
originalFontId = run.internalFontId;
break;
}
}
if (!objectIndices.empty()) break;
}
if (!objectIndices.empty()) break;
}
ASSERT_FALSE(objectIndices.empty()) << "Could not find target text run in latin_extended.pdf";
std::string indicesStr = "";
for (size_t i = 0; i < objectIndices.size(); ++i) {
indicesStr += std::to_string(objectIndices[i]);
if (i + 1 < objectIndices.size()) indicesStr += ",";
}
// JSON payload including internalFontId to resolve
std::string editsJson = R"({
"version": "1.0",
"operations": [
{
"id": "op_engine_1",
"type": "replace_text",
"pageIndex": 0,
"objectIndices": [)" + indicesStr + R"(],
"text": "Font Engine Active!",
"internalFontId": ")" + originalFontId + R"("
}
]
})";
auto editRes = doc->applyEdits(editsJson);
ASSERT_TRUE(editRes.has_value());
auto saveRes = doc->saveIncremental();
ASSERT_TRUE(saveRes.has_value());
const auto& savedBytes = *saveRes;
ASSERT_FALSE(savedBytes.empty());
auto newDocRes = PdfDocument::loadFromMemory(savedBytes);
ASSERT_TRUE(newDocRes.has_value());
auto newDoc = *newDocRes;
auto newPageRes = newDoc->getPage(0);
ASSERT_TRUE(newPageRes.has_value());
auto newPage = *newPageRes;
auto textRes = newPage->extractText();
ASSERT_TRUE(textRes.has_value());
EXPECT_NE(textRes->find("Font Engine Active!"), std::string::npos);
}
TEST(DocumentEditTest, ReplaceTextFontReuseAndEmbedding) {
SKIP_IF_NO_PDFIUM();
auto path = getCorpusPath("basic", "hello_world.pdf");
if (!std::filesystem::exists(path)) {
GTEST_SKIP() << "hello_world.pdf not found in corpus.";
}
auto docRes = PdfDocument::loadFromFile(path.string());
ASSERT_TRUE(docRes.has_value());
auto doc = *docRes;
auto pageRes = doc->getPage(0);
ASSERT_TRUE(pageRes.has_value());
auto pageObj = *pageRes;
auto modelRes = pageObj->extractDocumentModel();
ASSERT_TRUE(modelRes.has_value());
const auto& model = *modelRes;
// Find the first text run
std::vector<int> objectIndices;
std::string originalFontId = "";
for (const auto& p : model.paragraphs) {
for (const auto& line : p.lines) {
for (const auto& run : line.runs) {
if (!run.objectIndices.empty()) {
objectIndices = run.objectIndices;
originalFontId = run.internalFontId;
break;
}
}
if (!objectIndices.empty()) break;
}
if (!objectIndices.empty()) break;
}
ASSERT_FALSE(objectIndices.empty()) << "Could not find a text run in hello_world.pdf";
std::string indicesStr = "";
for (size_t i = 0; i < objectIndices.size(); ++i) {
indicesStr += std::to_string(objectIndices[i]);
if (i + 1 < objectIndices.size()) indicesStr += ",";
}
// JSON payload containing replacement using system font embedding
std::string editsJson = R"({
"version": "1.0",
"operations": [
{
"id": "op_reuse_1",
"type": "replace_text",
"pageIndex": 0,
"objectIndices": [)" + indicesStr + R"(],
"text": "Embedded Arial",
"internalFontId": ")" + originalFontId + R"("
}
]
})";
auto editRes = doc->applyEdits(editsJson);
ASSERT_TRUE(editRes.has_value());
// Save and reload
auto saveRes = doc->saveIncremental();
ASSERT_TRUE(saveRes.has_value());
const auto& savedBytes = *saveRes;
ASSERT_FALSE(savedBytes.empty());
auto newDocRes = PdfDocument::loadFromMemory(savedBytes);
ASSERT_TRUE(newDocRes.has_value());
auto newDoc = *newDocRes;
// Get page fonts to verify that Arial was successfully embedded in the new document
auto fontsRes = newDoc->getFonts(0, 0);
ASSERT_TRUE(fontsRes.has_value());
bool foundEmbeddedArial = false;
for (const auto& f : *fontsRes) {
if (f.isEmbedded && (f.fontName.find("Arial") != std::string::npos || f.fontName.find("LiberationSans") != std::string::npos)) {
foundEmbeddedArial = true;
}
}
std::cout << "Font Embedding Test: foundEmbeddedArial = " << foundEmbeddedArial << std::endl;
}
TEST(DocumentEditTest, ReplaceTextHarfBuzzShapingAndReflow) {
SKIP_IF_NO_PDFIUM();
auto path = getCorpusPath("fonts", "latin_extended.pdf");
if (!std::filesystem::exists(path)) {
GTEST_SKIP() << "latin_extended.pdf not found in corpus.";
}
auto docRes = PdfDocument::loadFromFile(path.string());
ASSERT_TRUE(docRes.has_value());
auto doc = *docRes;
auto pageRes = doc->getPage(0);
ASSERT_TRUE(pageRes.has_value());
auto pageObj = *pageRes;
auto modelRes = pageObj->extractDocumentModel();
ASSERT_TRUE(modelRes.has_value());
const auto& model = *modelRes;
// Find a line that has at least 2 runs, where the first run uses Roboto-Regular
std::vector<int> targetIndices;
std::string originalFontId = "";
std::string runBText = "";
double runBOrigX = 0.0;
double runBOrigY = 0.0;
for (const auto& p : model.paragraphs) {
for (const auto& line : p.lines) {
if (line.runs.size() >= 2) {
const auto& runA = line.runs[0];
const auto& runB = line.runs[1];
if (runA.fontName.find("Roboto-Regular") != std::string::npos &&
!runA.objectIndices.empty() &&
runB.x > runA.x) {
targetIndices = runA.objectIndices;
originalFontId = runA.internalFontId;
runBText = runB.text;
runBOrigX = runB.x;
runBOrigY = runB.y;
break;
}
}
}
if (!targetIndices.empty()) break;
}
if (targetIndices.empty()) {
GTEST_SKIP() << "Could not find a suitable line with multiple runs to test reflow.";
}
std::string indicesStr = "";
for (size_t i = 0; i < targetIndices.size(); ++i) {
indicesStr += std::to_string(targetIndices[i]);
if (i + 1 < targetIndices.size()) indicesStr += ",";
}
// JSON payload: replacing runA with a very long text to trigger significant shift
std::string editsJson = R"({
"version": "1.0",
"operations": [
{
"id": "op_reflow_1",
"type": "replace_text",
"pageIndex": 0,
"objectIndices": [)" + indicesStr + R"(],
"text": "This is an extremely long replacement text to force the Reflow Engine to shift subsequent runs!",
"internalFontId": ")" + originalFontId + R"("
}
]
})";
auto editRes = doc->applyEdits(editsJson);
ASSERT_TRUE(editRes.has_value());
// Save and reload
auto saveRes = doc->saveIncremental();
ASSERT_TRUE(saveRes.has_value());
const auto& savedBytes = *saveRes;
ASSERT_FALSE(savedBytes.empty());
auto newDocRes = PdfDocument::loadFromMemory(savedBytes);
ASSERT_TRUE(newDocRes.has_value());
auto newDoc = *newDocRes;
auto newPageRes = newDoc->getPage(0);
ASSERT_TRUE(newPageRes.has_value());
auto newPage = *newPageRes;
auto newModelRes = newPage->extractDocumentModel();
ASSERT_TRUE(newModelRes.has_value());
const auto& newModel = *newModelRes;
// Find runB in the new document model and verify its X coordinate has shifted to the right
bool foundRunB = false;
double runBNewX = 0.0;
for (const auto& p : newModel.paragraphs) {
for (const auto& line : p.lines) {
for (const auto& run : line.runs) {
if (run.text == runBText && std::abs(run.y - runBOrigY) < 5.0) {
foundRunB = true;
runBNewX = run.x;
break;
}
}
if (foundRunB) break;
}
if (foundRunB) break;
}
ASSERT_TRUE(foundRunB) << "Could not find the subsequent text run '" << runBText << "' in the reflowed document.";
EXPECT_GT(runBNewX, runBOrigX + 10.0) << "The subsequent text run did not shift to the right by at least 10 points.";
std::cout << "Reflow Engine verified: '" << runBText << "' shifted from X=" << runBOrigX << " to X=" << runBNewX << std::endl;
}
}