Files
pdf/engine/src/parser/content_builder.cpp
T
2026-06-22 10:17:51 +05:30

310 lines
11 KiB
C++

#include "content_builder.hpp"
#include "../core/image_decoder.hpp"
#include <iostream>
namespace pdfengine {
ContentBuilder::ContentBuilder(ResourceResolver* resolver) : resolver_(resolver) {}
std::vector<std::unique_ptr<ContentObject>> ContentBuilder::build(const std::vector<Operation>& operations) {
std::vector<std::unique_ptr<ContentObject>> objects;
for (const auto& op : operations) {
processOperation(op, objects);
}
return objects;
}
void ContentBuilder::processOperation(const Operation& op, std::vector<std::unique_ptr<ContentObject>>& outObjects) {
if (op.op == "q") {
stateStack_.push_back(state_);
} else if (op.op == "Q") {
if (!stateStack_.empty()) {
state_ = stateStack_.back();
stateStack_.pop_back();
}
} else if (op.op == "cm") {
handleCm(op);
} else if (op.op == "BT") {
state_.tm[0] = 1.0; state_.tm[1] = 0.0; state_.tm[2] = 0.0;
state_.tm[3] = 1.0; state_.tm[4] = 0.0; state_.tm[5] = 0.0;
} else if (op.op == "Tf") {
handleTf(op);
} else if (op.op == "Td" || op.op == "TD" || op.op == "Tm") {
handleTd(op);
} else if (op.op == "Tj" || op.op == "'") { // ' is equivalent to T* Tj
handleTj(op, outObjects);
} else if (op.op == "TJ") {
handleTJ_Array(op, outObjects);
} else if (op.op == "Do") {
handleDo(op, outObjects);
} else if (op.op == "m" || op.op == "l" || op.op == "c" || op.op == "re" || op.op == "h") {
handlePathConstruction(op);
} else if (op.op == "S") {
handlePathPaint(PathPaintOp::Stroke, outObjects);
} else if (op.op == "s") {
handlePathPaint(PathPaintOp::Stroke, outObjects, true);
} else if (op.op == "f" || op.op == "F" || op.op == "f*") {
handlePathPaint(PathPaintOp::Fill, outObjects);
} else if (op.op == "B" || op.op == "B*") {
handlePathPaint(PathPaintOp::FillStroke, outObjects);
} else if (op.op == "b" || op.op == "b*") {
handlePathPaint(PathPaintOp::FillStroke, outObjects, true);
} else if (op.op == "n") {
currentPath_.clear();
}
}
void ContentBuilder::handleTf(const Operation& op) {
if (op.operands.size() >= 2) {
auto it = op.operands.end();
auto numNode = *(--it);
auto nameNode = *(--it);
if (nameNode->type == AstNodeType::Name) {
state_.fontName = nameNode->stringValue;
}
if (numNode->type == AstNodeType::Number) {
state_.fontSize = numNode->numberValue;
}
}
}
void ContentBuilder::handleTd(const Operation& op) {
if (op.op == "Tm" && op.operands.size() >= 6) {
// Tm takes 6 operands: a b c d e f
auto it = op.operands.end();
auto fNode = *(--it);
auto eNode = *(--it);
auto dNode = *(--it);
auto cNode = *(--it);
auto bNode = *(--it);
auto aNode = *(--it);
if (aNode->type == AstNodeType::Number) state_.tm[0] = aNode->numberValue;
if (bNode->type == AstNodeType::Number) state_.tm[1] = bNode->numberValue;
if (cNode->type == AstNodeType::Number) state_.tm[2] = cNode->numberValue;
if (dNode->type == AstNodeType::Number) state_.tm[3] = dNode->numberValue;
if (eNode->type == AstNodeType::Number) state_.tm[4] = eNode->numberValue;
if (fNode->type == AstNodeType::Number) state_.tm[5] = fNode->numberValue;
} else if ((op.op == "Td" || op.op == "TD") && op.operands.size() >= 2) {
// Td simply offsets e and f in the matrix
auto it = op.operands.end();
auto yNode = *(--it);
auto xNode = *(--it);
if (xNode->type == AstNodeType::Number) state_.tm[4] += xNode->numberValue;
if (yNode->type == AstNodeType::Number) state_.tm[5] += yNode->numberValue;
}
}
void ContentBuilder::handleTj(const Operation& op, std::vector<std::unique_ptr<ContentObject>>& outObjects) {
if (op.operands.empty()) return;
auto strNode = op.operands.back();
if (strNode->type == AstNodeType::String || strNode->type == AstNodeType::HexString) {
auto textObj = std::make_unique<TextObject>();
if (strNode->type == AstNodeType::String) {
textObj->text = strNode->stringValue;
} else {
textObj->text = std::string(strNode->bytesValue.begin(), strNode->bytesValue.end());
}
textObj->fontName = state_.fontName;
textObj->fontSize = state_.fontSize;
for (int i=0; i<6; ++i) textObj->tm[i] = state_.tm[i];
outObjects.push_back(std::move(textObj));
}
}
void ContentBuilder::handleTJ_Array(const Operation& op, std::vector<std::unique_ptr<ContentObject>>& outObjects) {
if (op.operands.empty()) return;
auto arrNode = op.operands.back();
if (arrNode->type != AstNodeType::Array) return;
std::string combinedText;
const auto& arr = arrNode->arrayItems;
for (const auto& item : arr) {
if (item->type == AstNodeType::String) {
combinedText += item->stringValue;
} else if (item->type == AstNodeType::HexString) {
combinedText += std::string(item->bytesValue.begin(), item->bytesValue.end());
} else if (item->type == AstNodeType::Number) {
if (item->numberValue < -500.0) {
combinedText += " ";
}
}
}
if (!combinedText.empty()) {
auto textObj = std::make_unique<TextObject>();
textObj->text = combinedText;
textObj->fontName = state_.fontName;
textObj->fontSize = state_.fontSize;
for (int i=0; i<6; ++i) textObj->tm[i] = state_.tm[i];
outObjects.push_back(std::move(textObj));
}
}
void ContentBuilder::handleCm(const Operation& op) {
if (op.operands.size() >= 6) {
auto it = op.operands.end();
auto fNode = *(--it);
auto eNode = *(--it);
auto dNode = *(--it);
auto cNode = *(--it);
auto bNode = *(--it);
auto aNode = *(--it);
Matrix m;
if (aNode->type == AstNodeType::Number) m.a = static_cast<float>(aNode->numberValue);
if (bNode->type == AstNodeType::Number) m.b = static_cast<float>(bNode->numberValue);
if (cNode->type == AstNodeType::Number) m.c = static_cast<float>(cNode->numberValue);
if (dNode->type == AstNodeType::Number) m.d = static_cast<float>(dNode->numberValue);
if (eNode->type == AstNodeType::Number) m.e = static_cast<float>(eNode->numberValue);
if (fNode->type == AstNodeType::Number) m.f = static_cast<float>(fNode->numberValue);
state_.ctm = state_.ctm.multiply(m);
}
}
void ContentBuilder::handleDo(const Operation& op, std::vector<std::unique_ptr<ContentObject>>& outObjects) {
if (!resolver_ || op.operands.empty()) return;
auto nameNode = op.operands.back();
if (nameNode->type != AstNodeType::Name) return;
std::string name = nameNode->stringValue;
ResolvedXObject resolved = resolver_->resolveXObject(name);
if (resolved.type == XObjectType::Image) {
QPDFObjectHandle streamDict = resolved.object.getDict();
int width = streamDict.hasKey("/Width") ? static_cast<int>(streamDict.getKey("/Width").getNumericValue()) : 0;
int height = streamDict.hasKey("/Height") ? static_cast<int>(streamDict.getKey("/Height").getNumericValue()) : 0;
int bpc = streamDict.hasKey("/BitsPerComponent") ? static_cast<int>(streamDict.getKey("/BitsPerComponent").getNumericValue()) : 8;
std::string colorSpace;
if (streamDict.hasKey("/ColorSpace")) {
auto cs = streamDict.getKey("/ColorSpace");
if (cs.isName()) {
colorSpace = cs.getName();
} else if (cs.isArray() && cs.getArrayItem(0).isName()) {
colorSpace = cs.getArrayItem(0).getName();
}
}
std::string filter;
if (streamDict.hasKey("/Filter")) {
auto f = streamDict.getKey("/Filter");
if (f.isName()) {
filter = f.getName();
} else if (f.isArray() && f.getArrayItem(0).isName()) {
filter = f.getArrayItem(0).getName();
}
}
auto imageObj = std::make_unique<ImageObject>();
imageObj->name = name;
imageObj->width = width;
imageObj->height = height;
imageObj->bitsPerComponent = bpc;
imageObj->colorSpace = colorSpace;
imageObj->filter = filter;
imageObj->hasSoftMask = streamDict.hasKey("/SMask");
imageObj->transform = state_.ctm;
imageObj->pixelData = ImageDecoder::decode(resolved.object, colorSpace, width, height, bpc, filter);
outObjects.push_back(std::move(imageObj));
} else if (resolved.type == XObjectType::Form) {
std::cerr << "Form XObject not fully supported yet.\n";
}
}
void ContentBuilder::handlePathConstruction(const Operation& op) {
auto numberOperand = [&op](size_t index, float& value) {
if (index >= op.operands.size() || op.operands[index]->type != AstNodeType::Number) {
return false;
}
value = static_cast<float>(op.operands[index]->numberValue);
return true;
};
if (op.op == "h") {
if (!currentPath_.empty()) {
currentPath_.close();
}
return;
}
if (op.op == "m" || op.op == "l") {
if (op.operands.size() < 2) return;
float x = 0.0f;
float y = 0.0f;
if (!numberOperand(op.operands.size() - 2, x) || !numberOperand(op.operands.size() - 1, y)) {
return;
}
if (op.op == "m") {
currentPath_.moveTo(x, y);
} else {
currentPath_.lineTo(x, y);
}
return;
}
if (op.op == "c") {
if (op.operands.size() < 6) return;
float x1 = 0.0f;
float y1 = 0.0f;
float x2 = 0.0f;
float y2 = 0.0f;
float x3 = 0.0f;
float y3 = 0.0f;
const size_t start = op.operands.size() - 6;
if (!numberOperand(start + 0, x1) || !numberOperand(start + 1, y1) ||
!numberOperand(start + 2, x2) || !numberOperand(start + 3, y2) ||
!numberOperand(start + 4, x3) || !numberOperand(start + 5, y3)) {
return;
}
currentPath_.cubicTo(x1, y1, x2, y2, x3, y3);
return;
}
if (op.op == "re") {
if (op.operands.size() < 4) return;
float x = 0.0f;
float y = 0.0f;
float width = 0.0f;
float height = 0.0f;
const size_t start = op.operands.size() - 4;
if (!numberOperand(start + 0, x) || !numberOperand(start + 1, y) ||
!numberOperand(start + 2, width) || !numberOperand(start + 3, height)) {
return;
}
currentPath_.addRect(x, y, width, height);
}
}
void ContentBuilder::handlePathPaint(PathPaintOp paintOp,
std::vector<std::unique_ptr<ContentObject>>& outObjects,
bool closePath) {
if (closePath) {
if (!currentPath_.empty()) {
currentPath_.close();
}
}
if (currentPath_.empty()) {
return;
}
auto pathObj = std::make_unique<PathObject>();
pathObj->path = currentPath_;
pathObj->paintOp = paintOp;
pathObj->transform = state_.ctm;
outObjects.push_back(std::move(pathObj));
currentPath_.clear();
}
} // namespace pdfengine