245 lines
8.4 KiB
C++
245 lines
8.4 KiB
C++
#pragma once
|
|
|
|
#include <expected>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <cstdint>
|
|
|
|
namespace pdfengine {
|
|
|
|
enum class EngineError {
|
|
FileNotFound,
|
|
InvalidFormat,
|
|
PasswordRequired,
|
|
InvalidPassword,
|
|
PageOutOfBounds,
|
|
RenderFailed,
|
|
WriteFailed,
|
|
Unknown
|
|
};
|
|
|
|
struct DocumentMetadata {
|
|
std::string title;
|
|
std::string author;
|
|
std::string creator;
|
|
std::string producer;
|
|
std::string creationDate;
|
|
std::string modificationDate;
|
|
};
|
|
|
|
struct PageImage {
|
|
int width;
|
|
int height;
|
|
std::vector<uint8_t> data;
|
|
};
|
|
|
|
struct Point2D {
|
|
double x;
|
|
double y;
|
|
};
|
|
|
|
struct DevicePoint {
|
|
int x;
|
|
int y;
|
|
};
|
|
|
|
struct GlyphBounds {
|
|
std::string text;
|
|
double x;
|
|
double y;
|
|
double w;
|
|
double h;
|
|
double fontSize;
|
|
};
|
|
|
|
// Result of a point hit-test against a page's glyphs (page-point space, top-left
|
|
// origin — the same space GlyphBounds use).
|
|
struct HitResult {
|
|
int glyphIndex = -1; // glyph directly under the point in reading order, -1 if none
|
|
int caret = 0; // nearest caret position (0..N) for selection anchoring
|
|
int line = -1; // line band the point resolved to, -1 if the page has no text
|
|
};
|
|
|
|
// A resolved text selection: a half-open glyph range plus its reconstructed text
|
|
// and per-line union rectangles (for drawing the selection).
|
|
struct TextSelection {
|
|
int startGlyph = 0; // inclusive, reading order
|
|
int endGlyph = 0; // exclusive
|
|
std::string text;
|
|
std::vector<GlyphBounds> rects; // per-line union rects (text field left empty)
|
|
};
|
|
|
|
struct FontInfo {
|
|
std::string fontName;
|
|
std::string type; // "TrueType", "Type1", "CIDFontType0", "CIDFontType2"
|
|
bool isEmbedded = false;
|
|
bool isSubset = false;
|
|
bool isVertical = false;
|
|
|
|
// Advanced Introspection & Diagnostics
|
|
std::string encoding; // "WinAnsiEncoding", "MacRomanEncoding", "Identity-H", "Identity-V", "Symbol", "Custom", "None"
|
|
bool hasToUnicode = false; // True if font has an active /ToUnicode map
|
|
std::string cmapName; // e.g. "Identity-H", "Identity-V", "UniJIS-UTF16-H"
|
|
std::string cidSystemInfo; // e.g. "Adobe-Japan1", "Adobe-GB1", "Adobe-Korea1"
|
|
std::string subsetTag; // e.g. "ABCDEE" (6-character uppercase tag)
|
|
std::string sourceType; // "Embedded", "SystemFallback", "Substituted"
|
|
std::string substitutedFrom; // e.g. "Helvetica" (Original requested font)
|
|
std::string substitutedTo; // e.g. "Liberation Sans" (Actual fallback font used)
|
|
std::string normalizedFamily; // e.g. "Arial" (Normalized family grouping name)
|
|
std::string internalFontId; // Unique stable identifier for internal tracking
|
|
uint32_t flags = 0; // PDF font descriptor flags
|
|
double ascent = 0.0; // Font descriptor Ascent metric
|
|
double descent = 0.0; // Font descriptor Descent metric
|
|
double capHeight = 0.0; // Font descriptor CapHeight metric
|
|
};
|
|
|
|
struct Glyph {
|
|
std::string text;
|
|
uint32_t unicode = 0;
|
|
|
|
std::string fontName;
|
|
uint32_t flags = 0;
|
|
double fontSize = 0.0;
|
|
|
|
double originX = 0.0;
|
|
double originY = 0.0;
|
|
double bboxX = 0.0, bboxY = 0.0, bboxW = 0.0, bboxH = 0.0;
|
|
double angle = 0.0;
|
|
};
|
|
|
|
struct TextRun {
|
|
std::string text;
|
|
std::string fontName;
|
|
uint32_t flags = 0;
|
|
double fontSize = 0.0;
|
|
std::string internalFontId;
|
|
bool isEmbedded = false;
|
|
std::string type;
|
|
std::vector<Glyph> glyphs;
|
|
double x = 0.0, y = 0.0, w = 0.0, h = 0.0;
|
|
};
|
|
|
|
struct TextLine {
|
|
std::vector<TextRun> runs;
|
|
std::vector<Glyph> glyphs;
|
|
double angle = 0.0;
|
|
double baselineY = 0.0;
|
|
double x = 0.0, y = 0.0, w = 0.0, h = 0.0;
|
|
};
|
|
|
|
struct Paragraph {
|
|
std::vector<TextLine> lines;
|
|
double x = 0.0, y = 0.0, w = 0.0, h = 0.0;
|
|
};
|
|
|
|
struct PageModel {
|
|
std::vector<Paragraph> paragraphs;
|
|
double width = 0.0;
|
|
double height = 0.0;
|
|
int pageIndex = 0;
|
|
};
|
|
|
|
class PdfPage {
|
|
public:
|
|
virtual ~PdfPage() = default;
|
|
|
|
[[nodiscard]] virtual double width() const noexcept = 0;
|
|
[[nodiscard]] virtual double height() const noexcept = 0;
|
|
|
|
[[nodiscard]] virtual std::expected<PageImage, EngineError> render(int dpi = 96) const = 0;
|
|
|
|
[[nodiscard]] virtual std::expected<std::string, EngineError> extractText() const = 0;
|
|
|
|
[[nodiscard]] virtual std::expected<std::vector<GlyphBounds>, EngineError> extractTextWithBounds() const = 0;
|
|
|
|
// Adobe-grade hit-testing & selection, layered on extractTextWithBounds().
|
|
// Concrete (non-virtual) so every page implementation gets them for free.
|
|
// Coordinates are in page-point space (top-left origin), matching GlyphBounds.
|
|
|
|
// Glyphs in reading order: clustered into lines top-to-bottom, left-to-right.
|
|
[[nodiscard]] std::expected<std::vector<GlyphBounds>, EngineError> orderedGlyphs() const;
|
|
|
|
// Nearest glyph/caret to a point.
|
|
[[nodiscard]] std::expected<HitResult, EngineError> hitGlyph(double x, double y) const;
|
|
|
|
// Reading-order selection between two points (e.g. drag anchor → focus).
|
|
[[nodiscard]] std::expected<TextSelection, EngineError>
|
|
selectRange(double ax, double ay, double bx, double by) const;
|
|
|
|
[[nodiscard]] virtual std::expected<PageModel, EngineError> extractDocumentModel() const = 0;
|
|
|
|
[[nodiscard]] virtual std::expected<std::vector<FontInfo>, EngineError> getFonts() const = 0;
|
|
[[nodiscard]] virtual std::expected<std::vector<std::string>, EngineError> extractAnnotationsText() const = 0;
|
|
|
|
struct AnnotationInfo {
|
|
std::string id;
|
|
std::string type; // "highlight", "comment", "ink", "strikeout", "signature", "widget"
|
|
double x = 0.0, y = 0.0, width = 0.0, height = 0.0;
|
|
std::string color;
|
|
std::string author;
|
|
std::string content;
|
|
std::string timestamp;
|
|
int pageIndex = 0;
|
|
std::vector<std::vector<Point2D>> paths;
|
|
|
|
// Form field specific properties
|
|
std::string fieldName;
|
|
std::string fieldValue;
|
|
std::string fieldType;
|
|
int fieldFlags = 0;
|
|
std::vector<std::string> fieldOptions;
|
|
};
|
|
[[nodiscard]] virtual std::expected<std::vector<AnnotationInfo>, EngineError> extractAnnotations() const = 0;
|
|
|
|
[[nodiscard]] virtual std::expected<double, EngineError> getGlyphWidth(const std::string& fontName, uint32_t charcode, double fontSize) const = 0;
|
|
|
|
[[nodiscard]] virtual DevicePoint pageToDevice(const Point2D& pagePoint, int deviceWidth, int deviceHeight, int rotate = 0) const noexcept = 0;
|
|
[[nodiscard]] virtual Point2D deviceToPage(const DevicePoint& devicePoint, int deviceWidth, int deviceHeight, int rotate = 0) const noexcept = 0;
|
|
};
|
|
|
|
namespace fonts::pdf_fonts { class Font; }
|
|
|
|
class PdfDocument {
|
|
public:
|
|
virtual ~PdfDocument() = default;
|
|
|
|
[[nodiscard]] static std::expected<std::shared_ptr<PdfDocument>, EngineError>
|
|
loadFromFile(const std::string& path, const std::string& password = "");
|
|
|
|
[[nodiscard]] static std::expected<std::shared_ptr<PdfDocument>, EngineError>
|
|
loadFromMemory(const std::vector<uint8_t>& data, const std::string& password = "");
|
|
|
|
[[nodiscard]] virtual int pageCount() const noexcept = 0;
|
|
[[nodiscard]] virtual DocumentMetadata metadata() const noexcept = 0;
|
|
|
|
// A single entry in the document outline (bookmarks), flattened with a depth level.
|
|
struct OutlineItem {
|
|
std::string title;
|
|
int pageIndex = -1; // -1 if the destination page can't be resolved
|
|
int level = 0; // 0 = top-level
|
|
};
|
|
[[nodiscard]] virtual std::expected<std::vector<OutlineItem>, EngineError> extractOutline() const = 0;
|
|
|
|
[[nodiscard]] virtual std::expected<std::shared_ptr<PdfPage>, EngineError>
|
|
getPage(int pageIndex) = 0;
|
|
|
|
[[nodiscard]] virtual std::expected<std::vector<FontInfo>, EngineError>
|
|
getFonts(int startPage = 0, int endPage = -1) const = 0;
|
|
|
|
[[nodiscard]] virtual std::expected<std::vector<uint8_t>, EngineError>
|
|
getFontData(const std::string& internalFontId) const = 0;
|
|
|
|
[[nodiscard]] virtual std::expected<std::shared_ptr<fonts::pdf_fonts::Font>, std::string>
|
|
getResolvedFont(const FontInfo& fontInfo) = 0;
|
|
|
|
virtual std::expected<void, EngineError> applyEdits(const std::string& editsJson) = 0;
|
|
|
|
[[nodiscard]] virtual std::expected<std::vector<uint8_t>, EngineError>
|
|
saveIncremental() const = 0;
|
|
|
|
[[nodiscard]] virtual std::expected<std::vector<uint8_t>, EngineError>
|
|
saveFull() const = 0;
|
|
};
|
|
|
|
} |