feat: implemented multi page reflow

This commit is contained in:
Furqan-14
2026-06-18 16:48:29 +05:30
parent ff0962b111
commit a124a43877
23 changed files with 1559 additions and 890 deletions
+3 -18
View File
@@ -54,10 +54,6 @@ void get_or_throw(std::expected<void, pdfengine::EngineError>&& res) {
#include <serializer/content_serializer.hpp>
#include <serializer/ast_serializer.hpp>
// A TJ array number more negative than this is treated as an inter-word space when flattening a
// TJ run to display/edit text (numbers are thousandths-of-em, subtracted from the X position; a
// word space is a large positive gap = large-negative adjustment). extract and replace MUST share
// this constant so the flattened text they produce stays positionally aligned (see P1a redistribution).
static constexpr double kTjSpaceKern = -500.0;
class StreamEditor {
@@ -140,14 +136,6 @@ public:
}
if (!combinedText.empty()) {
if (textCount == object_index) {
// P1a — preserve TJ positioning. When the edit keeps the SAME length as the
// extracted text (the typo-fix common case), slice new_text back into the
// original string slots and KEEP every kern number, so glyph spacing/kerning
// survives. We mirror extract's reconstruction (a synthetic space per
// large-negative kern) to keep positions aligned; if a synthetic-space
// position was itself edited (can't map cleanly) or the length changed, we
// fall back to a single string at natural advances — exactly the old behaviour,
// so this is never worse than before, only better when it aligns.
bool redistributed = false;
if (new_text.size() == combinedText.size()) {
std::vector<std::pair<pdfengine::AstNode*, std::string>> assign;
@@ -162,7 +150,7 @@ public:
} else if (item->type == pdfengine::AstNodeType::Number &&
item->numberValue < kTjSpaceKern) {
if (pos >= new_text.size() || new_text[pos] != ' ') { ok = false; break; }
pos += 1; // consume the synthetic space; keep the kern number as-is
pos += 1;
}
}
if (ok && pos == new_text.size()) {
@@ -316,7 +304,8 @@ PYBIND11_MODULE(pdfengine, m) {
.def_readonly("w", &pdfengine::TextRun::w)
.def_readonly("h", &pdfengine::TextRun::h)
.def_readonly("object_indices", &pdfengine::TextRun::objectIndices)
.def_readonly("fill_color", &pdfengine::TextRun::fillColor);
.def_readonly("fill_color", &pdfengine::TextRun::fillColor)
.def_readonly("para_id", &pdfengine::TextRun::paraId);
py::class_<pdfengine::TextLine>(m, "TextLine")
.def_readonly("runs", &pdfengine::TextLine::runs)
@@ -365,8 +354,6 @@ PYBIND11_MODULE(pdfengine, m) {
return get_or_throw(self.render(dpi));
}, py::arg("dpi") = 96)
.def("render_region_raw", [](const pdfengine::PdfPage& self, int dpi, double y_top_pt, double height_pt) {
// Returns (width, height, raw RGBA bytes) of the band [y_top_pt, y_top_pt+height_pt]
// (points from page top); height_pt<=0 → to page bottom. No PNG. For preview/perf tests.
auto img = get_or_throw(self.renderRegionRaw(dpi, y_top_pt, height_pt));
return py::make_tuple(img.width, img.height,
py::bytes(reinterpret_cast<const char*>(img.data.data()), img.data.size()));
@@ -474,8 +461,6 @@ PYBIND11_MODULE(pdfengine, m) {
return get_or_throw(self.getFonts(start_page, end_page));
}, py::arg("start_page") = 0, py::arg("end_page") = -1)
.def("get_font_data", [](const pdfengine::PdfDocument& self, const std::string& internal_font_id) {
// Raw embedded font bytes for the given run font; empty bytes when the font
// is not embedded / not found (the gateway maps that to a 404 + CSS fallback).
auto res = self.getFontData(internal_font_id);
if (!res || res->empty()) {
return py::bytes();