Files
pdf/engine/src/parser/pdfium_document.hpp
T

160 lines
7.1 KiB
C++

#pragma once
#include <pdfengine/pdf_document.hpp>
#ifdef PDFENGINE_WITH_PDFIUM
#include <fpdfview.h>
#include <fpdf_text.h>
#endif
#include <memory>
#include <string>
#include <vector>
#include <mutex>
#include <unordered_map>
namespace pdfengine::fonts::loader { class FontResolver; }
namespace pdfengine::fonts { class FontFace; }
namespace pdfengine::parser {
class PdfiumDocument; // a page keeps its owning document alive (see ownerDoc_)
#ifdef PDFENGINE_WITH_PDFIUM
using NativeDocHandle = FPDF_DOCUMENT;
using NativePageHandle = FPDF_PAGE;
using NativeTextHandle = FPDF_TEXTPAGE;
#else
using NativeDocHandle = void*;
using NativePageHandle = void*;
using NativeTextHandle = void*;
#endif
class PdfiumPage : public PdfPage {
public:
PdfiumPage(NativeDocHandle docHandle, NativePageHandle pageHandle, int pageIndex,
std::shared_ptr<PdfiumDocument> owner = nullptr);
~PdfiumPage() override;
PdfiumPage(const PdfiumPage&) = delete;
PdfiumPage& operator=(const PdfiumPage&) = delete;
PdfiumPage(PdfiumPage&& other) noexcept;
PdfiumPage& operator=(PdfiumPage&& other) noexcept;
double width() const noexcept override;
double height() const noexcept override;
std::expected<PageImage, EngineError> render(int dpi = 96) const override;
std::expected<std::string, EngineError> extractText() const override;
std::expected<std::vector<GlyphBounds>, EngineError> extractTextWithBounds() const override;
std::expected<PageModel, EngineError> extractDocumentModel() const override;
std::expected<std::vector<FontInfo>, EngineError> getFonts() const override;
std::expected<std::vector<std::string>, EngineError> extractAnnotationsText() const override;
std::expected<std::vector<PdfPage::AnnotationInfo>, EngineError> extractAnnotations() const override;
std::expected<double, EngineError> getGlyphWidth(const std::string& fontName, uint32_t charcode, double fontSize) const override;
DevicePoint pageToDevice(const Point2D& pagePoint, int deviceWidth, int deviceHeight, int rotate = 0) const noexcept override;
Point2D deviceToPage(const DevicePoint& devicePoint, int deviceWidth, int deviceHeight, int rotate = 0) const noexcept override;
private:
NativeDocHandle doc_ = nullptr;
NativePageHandle page_ = nullptr;
mutable NativeTextHandle textPage_ = nullptr;
int pageIndex_ = 0;
mutable std::mutex textMutex_;
// Keeps the owning document (and thus the native FPDF_DOCUMENT) alive for as
// long as this page exists, so page_/textPage_ can never dangle. Declared
// here (destroyed last) so it outlives the handle teardown in the destructor.
std::shared_ptr<PdfiumDocument> ownerDoc_;
#ifdef PDFENGINE_WITH_PDFIUM
mutable std::unordered_map<std::string, FPDF_FONT> fontHandleCache_;
#endif
void ensureTextPageLoaded() const;
};
class PdfiumDocument : public PdfDocument, public std::enable_shared_from_this<PdfiumDocument> {
public:
explicit PdfiumDocument(NativeDocHandle docHandle);
PdfiumDocument(NativeDocHandle docHandle, std::vector<uint8_t> memoryBuffer);
~PdfiumDocument() override;
PdfiumDocument(const PdfiumDocument&) = delete;
PdfiumDocument& operator=(const PdfiumDocument&) = delete;
PdfiumDocument(PdfiumDocument&& other) noexcept;
PdfiumDocument& operator=(PdfiumDocument&& other) noexcept;
int pageCount() const noexcept override;
DocumentMetadata metadata() const noexcept override;
DocumentPermissions permissions() const noexcept override;
std::expected<std::vector<OutlineItem>, EngineError> extractOutline() const override;
std::expected<std::shared_ptr<PdfPage>, EngineError> getPage(int pageIndex) override;
std::expected<std::vector<FontInfo>, EngineError> getFonts(int startPage = 0, int endPage = -1) const override;
std::expected<std::vector<uint8_t>, EngineError> getFontData(const std::string& internalFontId) const override;
std::expected<std::shared_ptr<fonts::pdf_fonts::Font>, std::string> getResolvedFont(const FontInfo& fontInfo) override;
void invalidateCaches();
std::expected<void, EngineError> applyEdits(const std::string& editsJson) override;
std::expected<std::vector<uint8_t>, EngineError> saveIncremental() const override;
std::expected<std::vector<uint8_t>, EngineError> saveFull() const override;
// Per-character layout (PDF coords) of the LAST reflow_paragraph op, as JSON. The WASM
// preview uses it to align a custom caret exactly to the rendered glyphs. Empty if the
// most recent applyEdits contained no reflow_paragraph operation.
std::string lastReflowLayout() const { return lastReflowLayout_; }
private:
mutable std::string lastReflowLayout_;
NativeDocHandle doc_ = nullptr;
std::vector<uint8_t> memoryBuffer_;
mutable std::vector<FontInfo> cachedFonts_;
mutable bool hasCachedFonts_ = false;
mutable std::mutex fontsMutex_;
// Cache to prevent O(N*M) full-document scans for font data extraction
mutable std::unordered_map<std::string, std::vector<uint8_t>> fontDataCache_;
mutable int fontDataScannedPages_ = 0;
// Weak so the document never co-owns its pages: ownership runs page → document
// only. A cached entry is reused while a page is still referenced elsewhere,
// and lazily rebuilt once it expires.
mutable std::unordered_map<int, std::weak_ptr<PdfPage>> pageCache_;
mutable std::mutex pageCacheMutex_;
// Font Engine Bridge
std::unique_ptr<pdfengine::fonts::loader::FontResolver> fontResolver_;
std::unordered_map<std::string, std::shared_ptr<fonts::pdf_fonts::Font>> resolvedFontsCache_;
std::mutex resolvedFontsMutex_;
// Loaded PDFium Font Cache for Font Reuse
#ifdef PDFENGINE_WITH_PDFIUM
mutable std::unordered_map<std::string, FPDF_FONT> loadedFontsCache_;
mutable std::unordered_map<std::string, std::vector<uint8_t>> loadedFontDataBuffers_;
mutable std::unordered_map<std::string, std::shared_ptr<fonts::FontFace>> loadedMeasureFaces_;
mutable std::mutex loadedFontsMutex_;
// Resolve + load a PDFium font for emitting NEW text (mirrors the replace_text tiers:
// embedded full/subset reuse, base-14 standard, or system-font subset embed). Returns
// the FPDF_FONT (for FPDFPageObj_CreateTextObj) and the resolved font (FontFace for
// HarfBuzz width measurement during line-breaking). Either field may be null on failure.
struct EmissionFont {
FPDF_FONT font = nullptr;
std::shared_ptr<fonts::pdf_fonts::Font> resolved;
// A FontFace built from the EXACT bytes loaded into PDFium, so width measurement
// matches what PDFium renders (the resolver's face can have different advances).
std::shared_ptr<fonts::FontFace> measureFace;
};
EmissionFont loadEmissionFont(int pageIndex, const std::string& internalFontId,
double fontSize, const std::vector<uint32_t>& codepoints);
#endif
};
// Exposed for testing
std::vector<unsigned short> utf8_to_utf16le(const std::string& utf8);
std::string utf16le_to_utf8(const char16_t* utf16, size_t length);
std::string code_point_to_utf8(unsigned int cp);
}