feat: implemented base editing, underline and striking
This commit is contained in:
@@ -28,6 +28,25 @@ struct DocumentMetadata {
|
||||
std::string modificationDate;
|
||||
};
|
||||
|
||||
// Encryption + permission state of a loaded document. Booleans are the EFFECTIVE
|
||||
// permissions for how the doc was opened (an owner-unlocked doc reports all true).
|
||||
// The engine only surfaces these — enforcement happens in the gateway/app layers.
|
||||
struct DocumentPermissions {
|
||||
bool isEncrypted = false;
|
||||
std::string encryption = "None"; // "None", "RC4-40", "RC4-128", "AES-128", "AES-256"
|
||||
int securityRevision = -1; // FPDF_GetSecurityHandlerRevision (-1 if unencrypted)
|
||||
bool ownerUnlocked = false; // encrypted but opened with full (owner) access
|
||||
|
||||
bool canPrint = true;
|
||||
bool canPrintHighRes = true;
|
||||
bool canModify = true;
|
||||
bool canCopy = true; // extract text / graphics
|
||||
bool canAnnotate = true; // add/modify annotations
|
||||
bool canFillForms = true;
|
||||
bool canExtractForAccessibility = true;
|
||||
bool canAssemble = true; // insert / rotate / delete pages
|
||||
};
|
||||
|
||||
struct PageImage {
|
||||
int width;
|
||||
int height;
|
||||
@@ -214,6 +233,7 @@ public:
|
||||
|
||||
[[nodiscard]] virtual int pageCount() const noexcept = 0;
|
||||
[[nodiscard]] virtual DocumentMetadata metadata() const noexcept = 0;
|
||||
[[nodiscard]] virtual DocumentPermissions permissions() const noexcept = 0;
|
||||
|
||||
// A single entry in the document outline (bookmarks), flattened with a depth level.
|
||||
struct OutlineItem {
|
||||
|
||||
@@ -1299,6 +1299,10 @@ std::expected<std::vector<PdfPage::AnnotationInfo>, EngineError> PdfiumPage::ext
|
||||
info.type = "ink";
|
||||
} else if (subtype == FPDF_ANNOT_STRIKEOUT) {
|
||||
info.type = "strikeout";
|
||||
} else if (subtype == FPDF_ANNOT_UNDERLINE) {
|
||||
info.type = "underline";
|
||||
} else if (subtype == FPDF_ANNOT_SQUIGGLY) {
|
||||
info.type = "squiggly";
|
||||
} else if (subtype == FPDF_ANNOT_WIDGET) {
|
||||
info.type = "widget";
|
||||
|
||||
@@ -1605,6 +1609,49 @@ DocumentMetadata PdfiumDocument::metadata() const noexcept {
|
||||
return meta;
|
||||
}
|
||||
|
||||
DocumentPermissions PdfiumDocument::permissions() const noexcept {
|
||||
DocumentPermissions perms; // defaults: unencrypted, everything allowed
|
||||
#ifdef PDFENGINE_WITH_PDFIUM
|
||||
if (!doc_) return perms;
|
||||
|
||||
const int rev = FPDF_GetSecurityHandlerRevision(doc_);
|
||||
perms.securityRevision = rev;
|
||||
perms.isEncrypted = (rev != -1);
|
||||
if (!perms.isEncrypted) {
|
||||
return perms; // "None", all true
|
||||
}
|
||||
|
||||
switch (rev) {
|
||||
case 2: perms.encryption = "RC4-40"; break;
|
||||
case 3: perms.encryption = "RC4-128"; break;
|
||||
case 4: perms.encryption = "AES-128"; break; // R4 may be RC4-128; PDFium hides /CFM
|
||||
case 5:
|
||||
case 6: perms.encryption = "AES-256"; break;
|
||||
default: perms.encryption = "Unknown"; break;
|
||||
}
|
||||
|
||||
// FPDF_GetDocPermissions returns the EFFECTIVE /P flags for how the doc was
|
||||
// opened; FPDF_GetDocUserPermissions always returns the user-level flags. When
|
||||
// they differ, the doc was unlocked with the owner password (full access).
|
||||
// (PDFium grants full effective perms without always returning exactly
|
||||
// 0xFFFFFFFF, so a direct equality check is unreliable.)
|
||||
const unsigned long p = FPDF_GetDocPermissions(doc_);
|
||||
const unsigned long up = FPDF_GetDocUserPermissions(doc_);
|
||||
perms.ownerUnlocked = (p != up);
|
||||
|
||||
auto allowed = [p](unsigned long bit) { return (p & bit) != 0ul; };
|
||||
perms.canPrint = allowed(0x4); // bit 3
|
||||
perms.canModify = allowed(0x8); // bit 4
|
||||
perms.canCopy = allowed(0x10); // bit 5
|
||||
perms.canAnnotate = allowed(0x20); // bit 6
|
||||
perms.canFillForms = allowed(0x100); // bit 9
|
||||
perms.canExtractForAccessibility = allowed(0x200);// bit 10
|
||||
perms.canAssemble = allowed(0x400); // bit 11
|
||||
perms.canPrintHighRes = allowed(0x800); // bit 12
|
||||
#endif
|
||||
return perms;
|
||||
}
|
||||
|
||||
#ifdef PDFENGINE_WITH_PDFIUM
|
||||
namespace {
|
||||
// Depth-first flatten of the bookmark tree into OutlineItems with a depth level.
|
||||
@@ -2269,13 +2316,6 @@ std::expected<void, EngineError> PdfiumDocument::applyEdits(const std::string& e
|
||||
return std::unexpected(EngineError::InvalidFormat);
|
||||
}
|
||||
auto data = op["data"];
|
||||
double x = data.value("x", 0.0);
|
||||
double y = data.value("y", 0.0);
|
||||
double width = data.value("width", 0.0);
|
||||
double thickness = data.value("thickness", 1.0);
|
||||
std::string color = data.value("color", "#000000");
|
||||
|
||||
spdlog::info("Parsed {} operation: x={}, y={}, width={}", type, x, y, width);
|
||||
|
||||
FPDF_PAGE page = FPDF_LoadPage(doc_, pageIndex);
|
||||
if (!page) {
|
||||
@@ -2283,59 +2323,67 @@ std::expected<void, EngineError> PdfiumDocument::applyEdits(const std::string& e
|
||||
return std::unexpected(EngineError::Unknown);
|
||||
}
|
||||
|
||||
pdfengine::Path path;
|
||||
if (type == "underline") {
|
||||
path = DecorationBuilder::buildUnderline(x, y, width, thickness);
|
||||
} else if (type == "strikeout") {
|
||||
path = DecorationBuilder::buildStrikeout(x, y, width, thickness);
|
||||
} else if (type == "squiggly") {
|
||||
path = DecorationBuilder::buildSquiggly(x, y, width);
|
||||
// Adobe-grade: create a real text-markup annotation (editable, deletable,
|
||||
// round-trips, and positioned within the text quad by PDFium's appearance
|
||||
// generator) instead of baking a fixed-offset path. Mirrors the highlight handler.
|
||||
int subtype = FPDF_ANNOT_UNDERLINE;
|
||||
if (type == "strikeout") subtype = FPDF_ANNOT_STRIKEOUT;
|
||||
else if (type == "squiggly") subtype = FPDF_ANNOT_SQUIGGLY;
|
||||
|
||||
FPDF_ANNOTATION annot = FPDFPage_CreateAnnot(page, subtype);
|
||||
if (!annot) {
|
||||
spdlog::error("Failed to create {} annotation", type);
|
||||
FPDF_ClosePage(page);
|
||||
return std::unexpected(EngineError::Unknown);
|
||||
}
|
||||
|
||||
if (!path.empty()) {
|
||||
const auto& segments = path.segments();
|
||||
float startX = segments[0].points[0].x;
|
||||
float startY = segments[0].points[0].y;
|
||||
FPDF_PAGEOBJECT pathObj = FPDFPageObj_CreateNewPath(startX, startY);
|
||||
double pageHeight = FPDF_GetPageHeightF(page);
|
||||
unsigned int r = 0, g = 0, b = 0;
|
||||
parseHexColor(data.value("color", "#000000"), r, g, b);
|
||||
FPDFAnnot_SetColor(annot, FPDFANNOT_COLORTYPE_Color, r, g, b, 255);
|
||||
|
||||
bool isFill = (type != "squiggly");
|
||||
bool isStroke = (type == "squiggly");
|
||||
if (data.contains("quadPoints") && data["quadPoints"].is_array()) {
|
||||
FS_RECTF boundingBox;
|
||||
boundingBox.left = 99999.0f;
|
||||
boundingBox.right = -99999.0f;
|
||||
boundingBox.top = -99999.0f;
|
||||
boundingBox.bottom = 99999.0f;
|
||||
|
||||
for (size_t i = 1; i < segments.size(); ++i) { // Start from 1 to skip first MoveTo
|
||||
const auto& seg = segments[i];
|
||||
if (seg.verb == Path::Verb::MoveTo) {
|
||||
FPDFPath_MoveTo(pathObj, seg.points[0].x, seg.points[0].y);
|
||||
} else if (seg.verb == Path::Verb::LineTo) {
|
||||
FPDFPath_LineTo(pathObj, seg.points[0].x, seg.points[0].y);
|
||||
} else if (seg.verb == Path::Verb::CubicBezierTo) {
|
||||
FPDFPath_BezierTo(pathObj, seg.points[0].x, seg.points[0].y,
|
||||
seg.points[1].x, seg.points[1].y,
|
||||
seg.points[2].x, seg.points[2].y);
|
||||
} else if (seg.verb == Path::Verb::Close) {
|
||||
FPDFPath_Close(pathObj);
|
||||
}
|
||||
for (const auto& quad : data["quadPoints"]) {
|
||||
FS_QUADPOINTSF points;
|
||||
// Frontend is top-down; backend is bottom-up.
|
||||
points.x1 = static_cast<float>(quad.value("x1", 0.0));
|
||||
points.y1 = static_cast<float>(pageHeight - quad.value("y1", 0.0));
|
||||
points.x2 = static_cast<float>(quad.value("x2", 0.0));
|
||||
points.y2 = static_cast<float>(pageHeight - quad.value("y2", 0.0));
|
||||
points.x3 = static_cast<float>(quad.value("x3", 0.0));
|
||||
points.y3 = static_cast<float>(pageHeight - quad.value("y3", 0.0));
|
||||
points.x4 = static_cast<float>(quad.value("x4", 0.0));
|
||||
points.y4 = static_cast<float>(pageHeight - quad.value("y4", 0.0));
|
||||
|
||||
FPDFAnnot_AppendAttachmentPoints(annot, &points);
|
||||
|
||||
float minX = (std::min)({points.x1, points.x2, points.x3, points.x4});
|
||||
float maxX = (std::max)({points.x1, points.x2, points.x3, points.x4});
|
||||
float minY = (std::min)({points.y1, points.y2, points.y3, points.y4});
|
||||
float maxY = (std::max)({points.y1, points.y2, points.y3, points.y4});
|
||||
if (minX < boundingBox.left) boundingBox.left = minX;
|
||||
if (maxX > boundingBox.right) boundingBox.right = maxX;
|
||||
if (minY < boundingBox.bottom) boundingBox.bottom = minY;
|
||||
if (maxY > boundingBox.top) boundingBox.top = maxY;
|
||||
}
|
||||
|
||||
FPDFPath_SetDrawMode(pathObj, isFill ? FPDF_FILLMODE_ALTERNATE : FPDF_FILLMODE_NONE, isStroke);
|
||||
|
||||
unsigned int r = 0, g = 0, b = 0;
|
||||
parseHexColor(color, r, g, b);
|
||||
|
||||
if (isFill) {
|
||||
FPDFPageObj_SetFillColor(pathObj, r, g, b, 255);
|
||||
}
|
||||
if (isStroke) {
|
||||
FPDFPageObj_SetStrokeColor(pathObj, r, g, b, 255);
|
||||
FPDFPageObj_SetStrokeWidth(pathObj, thickness);
|
||||
}
|
||||
|
||||
FPDFPage_InsertObject(page, pathObj);
|
||||
|
||||
if (!FPDFPage_GenerateContent(page)) {
|
||||
spdlog::error("Failed to generate page content after adding decoration");
|
||||
if (boundingBox.left <= boundingBox.right) {
|
||||
FPDFAnnot_SetRect(annot, &boundingBox);
|
||||
}
|
||||
}
|
||||
|
||||
std::string author = data.value("author", "");
|
||||
if (!author.empty()) {
|
||||
auto utf16 = utf8_to_utf16le(author);
|
||||
FPDFAnnot_SetStringValue(annot, "T", reinterpret_cast<FPDF_WIDESTRING>(utf16.data()));
|
||||
}
|
||||
|
||||
FPDFPage_CloseAnnot(annot);
|
||||
FPDF_ClosePage(page);
|
||||
} else if (type == "redaction") {
|
||||
if (!op.contains("data") || !op["data"].is_object()) {
|
||||
|
||||
@@ -85,6 +85,7 @@ public:
|
||||
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user