fix: font handling in revista
This commit is contained in:
@@ -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_) {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user