fix: font handling in revista

This commit is contained in:
Furqan-14
2026-06-24 10:42:05 +05:30
parent e8620947a5
commit e6e1e0ba7c
9 changed files with 254 additions and 78 deletions
@@ -118,6 +118,7 @@ struct Glyph {
double bboxX = 0.0, bboxY = 0.0, bboxW = 0.0, bboxH = 0.0;
double angle = 0.0;
int pageObjectIndex = -1;
int srcIndex = 0;
};
struct TextRun {
@@ -135,6 +135,21 @@ std::string FontFallback::getFallbackFontPath(const std::string& fontName, bool
stylePattern += "-italic";
}
#if defined(_WIN32)
{
auto has = [&](const char* s) { return lowerName.find(s) != std::string::npos; };
const bool serif = has("times") || has("serif") || has("roman") || has("georgia") ||
has("garamond") || has("minion") || has("cambria") || has("tinos") ||
has("book antiqua") || has("palatino");
const std::string sysBase = serif ? "times" : "arial";
const std::string sysSfx = (bold && italic) ? "bi" : bold ? "bd" : italic ? "i" : "";
const std::string sysPath = "C:\\Windows\\Fonts\\" + sysBase + sysSfx + ".ttf";
if (std::filesystem::exists(sysPath)) {
return sysPath;
}
}
#endif
std::lock_guard<std::mutex> lock(rules_mutex_);
for (const auto& rule : custom_rules_) {
+146 -35
View File
@@ -1040,7 +1040,8 @@ std::expected<PageModel, EngineError> PdfiumPage::extractDocumentModel() const {
Glyph g;
g.text = std::move(utf8_char);
g.unicode = cp;
g.srcIndex = i;
double left, right, bottom, top;
FPDFText_GetCharBox(textPage_, i, &left, &right, &bottom, &top);
g.bboxX = (std::min)(left, right);
@@ -1112,7 +1113,8 @@ std::expected<PageModel, EngineError> PdfiumPage::extractDocumentModel() const {
for (auto& line : lines) {
std::sort(line.glyphs.begin(), line.glyphs.end(), [](const Glyph& a, const Glyph& b) {
return a.originX < b.originX;
if (a.originX != b.originX) return a.originX < b.originX;
return a.srcIndex < b.srcIndex;
});
std::vector<double> gaps;
@@ -1128,6 +1130,17 @@ std::expected<PageModel, EngineError> PdfiumPage::extractDocumentModel() const {
p25Gap = gaps[gaps.size() / 4];
}
double lineEm = 0.0;
{
std::vector<double> hs;
for (const auto& gg : line.glyphs) if (gg.bboxH > 0.1) hs.push_back(gg.bboxH);
if (!hs.empty()) {
std::sort(hs.begin(), hs.end());
double capH = hs[(hs.size() * 9) / 10];
lineEm = capH / 0.7;
}
}
TextRun currentRun;
if (!line.glyphs.empty()) {
const Glyph* firstG = &line.glyphs[0];
@@ -1147,9 +1160,10 @@ std::expected<PageModel, EngineError> PdfiumPage::extractDocumentModel() const {
const auto& currG = line.glyphs[i];
double gap = currG.bboxX - (prevG.bboxX + prevG.bboxW);
double spaceThreshold = currG.fontSize * 0.2;
double em = (std::max)(static_cast<double>(currG.fontSize), lineEm);
double spaceThreshold = em * 0.2;
if (p25Gap > spaceThreshold) {
spaceThreshold = (std::min)(p25Gap * 1.5, currG.fontSize * 0.38);
spaceThreshold = (std::min)(p25Gap * 1.5, em * 0.38);
}
bool addSpace = gap > spaceThreshold && prevG.text != " " && currG.text != " ";
@@ -1231,21 +1245,35 @@ std::expected<PageModel, EngineError> PdfiumPage::extractDocumentModel() const {
}
std::vector<Paragraph> paragraphs;
auto lineStyleKey = [](const TextLine& ln) -> std::pair<bool,bool> {
for (const auto& r : ln.runs) {
if (r.text.empty()) continue;
std::string n = r.fontName + "|" + r.internalFontId;
std::transform(n.begin(), n.end(), n.begin(), [](unsigned char c){ return static_cast<char>(std::tolower(c)); });
bool bold = n.find("bold") != std::string::npos;
bool sans = n.find("arial") != std::string::npos || n.find("helvetica") != std::string::npos;
return {bold, sans};
}
return {false, false};
};
if (!lines.empty()) {
Paragraph currentPara;
currentPara.lines.push_back(std::move(lines[0]));
for (size_t i = 1; i < lines.size(); ++i) {
auto& prevLine = currentPara.lines.back();
auto& currLine = lines[i];
double prevY = prevLine.glyphs.empty() ? 0 : prevLine.glyphs[0].originY;
double currY = currLine.glyphs.empty() ? 0 : currLine.glyphs[0].originY;
double fontSize = currLine.runs.empty() ? 12.0 : currLine.runs[0].fontSize;
double capH = 0.0;
for (const auto& g : currLine.glyphs) if (g.bboxH > capH) capH = g.bboxH;
if (capH > 0.0) fontSize = (std::max)(fontSize, capH / 0.7);
double vGap = std::abs(prevY - currY);
if (vGap > fontSize * 1.5) {
bool styleChanged = lineStyleKey(currLine) != lineStyleKey(prevLine);
if (vGap > fontSize * 1.5 || styleChanged) {
paragraphs.push_back(std::move(currentPara));
currentPara = Paragraph();
}
@@ -1317,17 +1345,18 @@ std::expected<PageModel, EngineError> PdfiumPage::extractDocumentModel() const {
};
auto computeRunColor = [&](TextRun& r) {
if (r.objectIndices.empty()) return;
int minIdx = r.objectIndices[0];
for (int idx : r.objectIndices) {
if (idx < minIdx) minIdx = idx;
}
FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page_, minIdx);
if (!obj) return;
unsigned int cr = 0, cg = 0, cb = 0, ca = 0;
bool got = (FPDFPageObj_GetFillColor(obj, &cr, &cg, &cb, &ca) && ca != 0) ||
(FPDFPageObj_GetStrokeColor(obj, &cr, &cg, &cb, &ca) && ca != 0);
if (got) {
r.fillColor = "#" + hex2(cr) + hex2(cg) + hex2(cb);
std::vector<int> idxs = r.objectIndices;
std::sort(idxs.begin(), idxs.end());
for (int idx : idxs) {
FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page_, idx);
if (!obj || FPDFPageObj_GetType(obj) != FPDF_PAGEOBJ_TEXT) continue;
unsigned int cr = 0, cg = 0, cb = 0, ca = 0;
bool got = (FPDFPageObj_GetFillColor(obj, &cr, &cg, &cb, &ca) && ca != 0) ||
(FPDFPageObj_GetStrokeColor(obj, &cr, &cg, &cb, &ca) && ca != 0);
if (got) {
r.fillColor = "#" + hex2(cr) + hex2(cg) + hex2(cb);
}
return;
}
};
@@ -2143,7 +2172,7 @@ PdfiumDocument::EmissionFont PdfiumDocument::loadEmissionFont(
cacheKey += "#" + std::to_string(h);
}
if (useEmbedded && reuseFont) {
if (useEmbedded && reuseFont && !isSubsetFont) {
out.font = reuseFont;
if (auto perObj = getFontDataFromObjects(pageIndex, srcObjects, internalFontId);
perObj.has_value() && !perObj->empty()) {
@@ -2173,9 +2202,8 @@ PdfiumDocument::EmissionFont PdfiumDocument::loadEmissionFont(
sourceBytes = std::move(fontDataRes.value());
}
if (!sourceBytes.empty()) {
auto subset = fonts::pdf_fonts::FontSubset::buildSubsetByUnicode(sourceBytes, codepoints);
std::lock_guard<std::mutex> lock(loadedFontsMutex_);
loadedFontDataBuffers_[cacheKey] = !subset.empty() ? std::move(subset) : std::move(sourceBytes);
loadedFontDataBuffers_[cacheKey] = std::move(sourceBytes);
const auto& bytes = loadedFontDataBuffers_[cacheKey];
out.font = FPDFText_LoadFont(doc_, bytes.data(), static_cast<uint32_t>(bytes.size()), FPDF_FONT_TRUETYPE, true);
}
@@ -2186,9 +2214,8 @@ PdfiumDocument::EmissionFont PdfiumDocument::loadEmissionFont(
if (fs) {
std::vector<uint8_t> fileBytes((std::istreambuf_iterator<char>(fs)), std::istreambuf_iterator<char>());
if (!fileBytes.empty()) {
std::vector<uint8_t> subsetBytes = fonts::pdf_fonts::FontSubset::buildSubsetByUnicode(fileBytes, codepoints);
std::lock_guard<std::mutex> lock(loadedFontsMutex_);
loadedFontDataBuffers_[cacheKey] = !subsetBytes.empty() ? std::move(subsetBytes) : std::move(fileBytes);
loadedFontDataBuffers_[cacheKey] = std::move(fileBytes);
const auto& bytes = loadedFontDataBuffers_[cacheKey];
out.font = FPDFText_LoadFont(doc_, bytes.data(), static_cast<uint32_t>(bytes.size()), FPDF_FONT_TRUETYPE, true);
}
@@ -2500,13 +2527,31 @@ std::expected<void, EngineError> PdfiumDocument::applyEdits(const std::string& e
origCenterY = (origBottom + origTop) / 2.0;
}
bool axisAligned = (std::abs(b) < 1e-6 && std::abs(c) < 1e-6 && a > 0.0 && d > 0.0);
double colRight = origRight;
bool sawSibling = false;
if (axisAligned && hasOrigBounds) {
int nObjForCol = FPDFPage_CountObjects(page);
for (int k = 0; k < nObjForCol; ++k) {
if (std::find(objectIndices.begin(), objectIndices.end(), k) != objectIndices.end()) continue;
FPDF_PAGEOBJECT o = FPDFPage_GetObject(page, k);
if (!o || FPDFPageObj_GetType(o) != FPDF_PAGEOBJ_TEXT) continue;
float l = 0, bo = 0, rr = 0, tt = 0;
if (!FPDFPageObj_GetBounds(o, &l, &bo, &rr, &tt)) continue;
if (std::abs(l - origLeft) <= 3.0) { sawSibling = true; if (rr > colRight) colRight = rr; }
}
}
double justifyTol = (std::max)(4.0, (colRight - origLeft) * 0.02);
bool wasJustified = axisAligned && resolvedFont && hasOrigBounds && sawSibling &&
(colRight - origLeft) > 20.0 && (origRight >= colRight - justifyTol);
double deltaX = 0.0;
if (hasOrigBounds) {
deltaX = totalWidth - origWidth;
spdlog::info("Reflow Engine: origWidth = {}, newWidth = {}, deltaX = {}", origWidth, totalWidth, deltaX);
}
if (hasOrigBounds && std::abs(deltaX) > 0.001) {
if (!wasJustified && hasOrigBounds && std::abs(deltaX) > 0.001) {
int pageObjCount = FPDFPage_CountObjects(page);
double tolerance = (std::max)(5.0, fontSize * 0.5);
int reflowedCount = 0;
@@ -2627,16 +2672,82 @@ std::expected<void, EngineError> PdfiumDocument::applyEdits(const std::string& e
}
if (font) {
FPDF_PAGEOBJECT newTextObj = FPDFPageObj_CreateTextObj(doc_, font, static_cast<float>(fontSize));
if (newTextObj) {
FPDFPageObj_SetFillColor(newTextObj, r, g, b_color, a_color);
FPDFTextObj_SetTextRenderMode(newTextObj, renderMode);
FPDFText_SetText(newTextObj, reinterpret_cast<FPDF_WIDESTRING>(utf16.data()));
FPDFPageObj_Transform(newTextObj, a, b, c, d, e, f);
FPDFPage_InsertObjectAtIndex(page, newTextObj, minIndex);
constexpr unsigned int kRef = 1000;
double emToPage = (fontSize > 0.0 ? fontSize : 1.0) * a / static_cast<double>(kRef);
auto pageWidthOf = [&](const std::string& s) -> double {
if (s.empty() || !resolvedFont) return 0.0;
double sum = 0.0;
try {
fonts::HbShaper sh;
auto gl = sh.shapeRun(s, resolvedFont->getFontFace(), kRef);
for (const auto& gg : gl) sum += gg.advanceX;
} catch (...) {
for (unsigned char ch : s) sum += resolvedFont->getAdvanceWidth(ch, kRef);
}
return sum * emToPage;
};
std::vector<std::string> words;
if (wasJustified) {
std::string cur;
for (char ch : newText) {
if (ch == ' ') { if (!cur.empty()) { words.push_back(cur); cur.clear(); } }
else cur.push_back(ch);
}
if (!cur.empty()) words.push_back(cur);
}
auto measuredWidth = [&](const std::vector<unsigned short>& u16le) -> double {
FPDF_PAGEOBJECT m = FPDFPageObj_CreateTextObj(doc_, font, static_cast<float>(fontSize));
if (!m) return 0.0;
FPDFText_SetText(m, reinterpret_cast<FPDF_WIDESTRING>(u16le.data()));
FPDFPageObj_Transform(m, a, b, c, d, 0.0, 0.0);
float l = 0, bo = 0, rr = 0, tt = 0;
double w = FPDFPageObj_GetBounds(m, &l, &bo, &rr, &tt) ? (rr - l) : 0.0;
FPDFPageObj_Destroy(m);
return w;
};
if (wasJustified && words.size() > 1) {
std::vector<double> wpx;
wpx.reserve(words.size());
double estWords = 0.0;
for (const auto& w : words) { double ww = pageWidthOf(w); wpx.push_back(ww); estWords += ww; }
double estSpace = pageWidthOf(" ");
int gaps = static_cast<int>(words.size()) - 1;
double estTotal = estWords + gaps * estSpace;
double actualFull = measuredWidth(utf16);
double k = (estTotal > 1e-6 && actualFull > 1e-6) ? actualFull / estTotal : 1.0;
double targetW = colRight - e;
double slack = targetW - actualFull;
double extraPerGap = (slack > 0.0) ? slack / gaps : 0.0;
spdlog::info("replace_text: justify {} words targetW={:.1f} actualW={:.1f} extraPerGap={:.2f}",
words.size(), targetW, actualFull, extraPerGap);
double penX = e;
for (size_t wi = 0; wi < words.size(); ++wi) {
FPDF_PAGEOBJECT wobj = FPDFPageObj_CreateTextObj(doc_, font, static_cast<float>(fontSize));
if (wobj) {
FPDFPageObj_SetFillColor(wobj, r, g, b_color, a_color);
FPDFTextObj_SetTextRenderMode(wobj, renderMode);
auto wu = utf8_to_utf16le(words[wi]); wu.push_back(0);
FPDFText_SetText(wobj, reinterpret_cast<FPDF_WIDESTRING>(wu.data()));
FPDFPageObj_Transform(wobj, a, b, c, d, penX, f);
FPDFPage_InsertObjectAtIndex(page, wobj, minIndex);
}
penX += (wpx[wi] + estSpace) * k + extraPerGap;
}
} else {
spdlog::error("Failed to create new text object");
FPDF_PAGEOBJECT newTextObj = FPDFPageObj_CreateTextObj(doc_, font, static_cast<float>(fontSize));
if (newTextObj) {
FPDFPageObj_SetFillColor(newTextObj, r, g, b_color, a_color);
FPDFTextObj_SetTextRenderMode(newTextObj, renderMode);
FPDFText_SetText(newTextObj, reinterpret_cast<FPDF_WIDESTRING>(utf16.data()));
FPDFPageObj_Transform(newTextObj, a, b, c, d, e, f);
FPDFPage_InsertObjectAtIndex(page, newTextObj, minIndex);
} else {
spdlog::error("Failed to create new text object");
}
}
}