2026-05-26 15:52:04 +05:30
|
|
|
#include "pdf_engine_facade.hpp"
|
2026-06-01 15:28:32 +05:30
|
|
|
#include "wasm_rasterizer.hpp"
|
2026-05-26 15:52:04 +05:30
|
|
|
#include <string_view>
|
|
|
|
|
#include <iostream>
|
2026-06-01 15:28:32 +05:30
|
|
|
#include <cmath>
|
|
|
|
|
#include <algorithm>
|
2026-05-26 15:52:04 +05:30
|
|
|
|
2026-06-01 15:28:32 +05:30
|
|
|
|
|
|
|
|
WasmMockPage::WasmMockPage(int pageIndex) : m_pageIndex(pageIndex) {
|
|
|
|
|
m_displayList.fillRect(0.0f, 0.0f, 800.0f, 1100.0f);
|
|
|
|
|
|
|
|
|
|
float gridSpacing = 50.0f;
|
|
|
|
|
for (float x = gridSpacing; x < 800.0f; x += gridSpacing) {
|
|
|
|
|
m_displayList.fillRect(x, 0.0f, 1.0f, 1100.0f);
|
|
|
|
|
}
|
|
|
|
|
for (float y = gridSpacing; y < 1100.0f; y += gridSpacing) {
|
|
|
|
|
m_displayList.fillRect(0.0f, y, 800.0f, 1.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float borderWidth = 4.0f;
|
2026-06-22 15:18:47 +05:30
|
|
|
m_displayList.fillRect(0.0f, 0.0f, 800.0f, borderWidth);
|
|
|
|
|
m_displayList.fillRect(0.0f, 1100.0f - borderWidth, 800.0f, borderWidth);
|
|
|
|
|
m_displayList.fillRect(0.0f, 0.0f, borderWidth, 1100.0f);
|
|
|
|
|
m_displayList.fillRect(800.0f - borderWidth, 0.0f, borderWidth, 1100.0f);
|
2026-06-01 15:28:32 +05:30
|
|
|
|
|
|
|
|
float centerX = 400.0f;
|
|
|
|
|
float centerY = 550.0f;
|
|
|
|
|
float size = 120.0f;
|
|
|
|
|
|
|
|
|
|
if (m_pageIndex == 0) {
|
|
|
|
|
m_displayList.fillRect(centerX - size / 2.0f, centerY - size / 2.0f, size, size);
|
|
|
|
|
} else {
|
|
|
|
|
m_displayList.saveState();
|
|
|
|
|
|
|
|
|
|
m_displayList.setTransform(pdfengine::Matrix(1.0f, 0.0f, 0.0f, 1.0f, centerX, centerY));
|
|
|
|
|
|
|
|
|
|
float angle = 45.0f * 3.14159265f / 180.0f;
|
|
|
|
|
float cosVal = std::cos(angle);
|
|
|
|
|
float sinVal = std::sin(angle);
|
|
|
|
|
m_displayList.setTransform(pdfengine::Matrix(cosVal, sinVal, -sinVal, cosVal, 0.0f, 0.0f));
|
|
|
|
|
|
|
|
|
|
m_displayList.fillRect(-size / 2.0f, -size / 2.0f, size, size);
|
|
|
|
|
|
|
|
|
|
m_displayList.restoreState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_displayList.drawText("Page " + std::to_string(m_pageIndex + 1), 60.0f, 80.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::expected<pdfengine::PageImage, pdfengine::EngineError> WasmMockPage::render(int dpi) const {
|
|
|
|
|
(void)dpi;
|
|
|
|
|
return std::unexpected(pdfengine::EngineError::Unknown);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::expected<std::string, pdfengine::EngineError> WasmMockPage::extractText() const {
|
|
|
|
|
return "Mock text on page " + std::to_string(m_pageIndex + 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::expected<std::vector<pdfengine::GlyphBounds>, pdfengine::EngineError> WasmMockPage::extractTextWithBounds() const {
|
|
|
|
|
std::vector<pdfengine::GlyphBounds> glyphs;
|
|
|
|
|
std::string text = "Page " + std::to_string(m_pageIndex + 1);
|
|
|
|
|
|
|
|
|
|
double startX = 60.0;
|
|
|
|
|
double startY = 80.0;
|
|
|
|
|
for (size_t i = 0; i < text.length(); ++i) {
|
|
|
|
|
pdfengine::GlyphBounds gb;
|
|
|
|
|
gb.text = std::string(1, text[i]);
|
|
|
|
|
gb.x = startX + i * 6.0;
|
|
|
|
|
gb.y = startY;
|
|
|
|
|
gb.w = 6.0;
|
|
|
|
|
gb.h = 8.0;
|
|
|
|
|
gb.fontSize = 12.0;
|
|
|
|
|
glyphs.push_back(gb);
|
|
|
|
|
}
|
|
|
|
|
return glyphs;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-09 10:39:45 +05:30
|
|
|
std::expected<pdfengine::PageModel, pdfengine::EngineError> WasmMockPage::extractDocumentModel() const {
|
|
|
|
|
pdfengine::PageModel model;
|
|
|
|
|
model.width = width();
|
|
|
|
|
model.height = height();
|
|
|
|
|
model.pageIndex = m_pageIndex;
|
|
|
|
|
|
|
|
|
|
pdfengine::Paragraph p;
|
|
|
|
|
p.x = 60.0;
|
|
|
|
|
p.y = 80.0;
|
|
|
|
|
p.w = 100.0;
|
|
|
|
|
p.h = 20.0;
|
|
|
|
|
|
|
|
|
|
pdfengine::TextLine line;
|
|
|
|
|
line.x = 60.0;
|
|
|
|
|
line.y = 80.0;
|
|
|
|
|
line.w = 100.0;
|
|
|
|
|
line.h = 20.0;
|
|
|
|
|
line.baselineY = 80.0;
|
|
|
|
|
|
|
|
|
|
pdfengine::TextRun run;
|
|
|
|
|
run.text = "Page " + std::to_string(m_pageIndex + 1);
|
|
|
|
|
run.fontName = "Helvetica";
|
|
|
|
|
run.fontSize = 12.0;
|
|
|
|
|
run.x = 60.0;
|
|
|
|
|
run.y = 80.0;
|
|
|
|
|
run.w = 100.0;
|
|
|
|
|
run.h = 20.0;
|
|
|
|
|
|
|
|
|
|
double startX = 60.0;
|
|
|
|
|
double startY = 80.0;
|
|
|
|
|
for (size_t i = 0; i < run.text.length(); ++i) {
|
|
|
|
|
pdfengine::Glyph g;
|
|
|
|
|
g.text = std::string(1, run.text[i]);
|
|
|
|
|
g.unicode = run.text[i];
|
|
|
|
|
g.fontName = "Helvetica";
|
|
|
|
|
g.fontSize = 12.0;
|
|
|
|
|
g.originX = startX + i * 6.0;
|
|
|
|
|
g.originY = startY;
|
|
|
|
|
g.bboxX = g.originX;
|
|
|
|
|
g.bboxY = g.originY;
|
|
|
|
|
g.bboxW = 6.0;
|
|
|
|
|
g.bboxH = 8.0;
|
|
|
|
|
run.glyphs.push_back(g);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
line.runs.push_back(run);
|
|
|
|
|
p.lines.push_back(line);
|
|
|
|
|
model.paragraphs.push_back(p);
|
|
|
|
|
|
|
|
|
|
return model;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 15:28:32 +05:30
|
|
|
std::expected<std::vector<std::string>, pdfengine::EngineError> WasmMockPage::extractAnnotationsText() const {
|
|
|
|
|
return std::vector<std::string>();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-09 11:08:45 +05:30
|
|
|
std::expected<std::vector<pdfengine::PdfPage::AnnotationInfo>, pdfengine::EngineError> WasmMockPage::extractAnnotations() const {
|
|
|
|
|
return std::vector<pdfengine::PdfPage::AnnotationInfo>();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-03 10:51:33 +05:30
|
|
|
std::expected<double, pdfengine::EngineError> WasmMockPage::getGlyphWidth(const std::string& fontName, uint32_t charcode, double fontSize) const {
|
|
|
|
|
(void)fontName; (void)charcode;
|
|
|
|
|
return 6.0 * (fontSize / 12.0);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 15:28:32 +05:30
|
|
|
std::expected<std::vector<pdfengine::FontInfo>, pdfengine::EngineError> WasmMockPage::getFonts() const {
|
|
|
|
|
pdfengine::FontInfo info;
|
|
|
|
|
info.fontName = "Helvetica";
|
|
|
|
|
info.type = "Type1";
|
|
|
|
|
info.isEmbedded = false;
|
|
|
|
|
info.isSubset = false;
|
|
|
|
|
info.isVertical = false;
|
|
|
|
|
info.encoding = "WinAnsiEncoding";
|
|
|
|
|
info.hasToUnicode = false;
|
|
|
|
|
info.sourceType = "Substituted";
|
|
|
|
|
info.substitutedFrom = "Helvetica";
|
|
|
|
|
info.substitutedTo = "Liberation Sans Regular";
|
|
|
|
|
info.normalizedFamily= "Arial";
|
|
|
|
|
info.internalFontId = "mock-page-" + std::to_string(m_pageIndex) + "-helvetica";
|
2026-06-22 15:18:47 +05:30
|
|
|
info.flags = 32;
|
2026-06-01 15:28:32 +05:30
|
|
|
info.ascent = 718.0;
|
|
|
|
|
info.descent = -207.0;
|
|
|
|
|
info.capHeight = 718.0;
|
|
|
|
|
return std::vector<pdfengine::FontInfo>{info};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pdfengine::DevicePoint WasmMockPage::pageToDevice(const pdfengine::Point2D& pagePoint, int deviceWidth, int deviceHeight, int rotate) const noexcept {
|
|
|
|
|
(void)rotate;
|
|
|
|
|
double scaleX = static_cast<double>(deviceWidth) / width();
|
|
|
|
|
double scaleY = static_cast<double>(deviceHeight) / height();
|
|
|
|
|
int dx = static_cast<int>(pagePoint.x * scaleX);
|
|
|
|
|
int dy = static_cast<int>((height() - pagePoint.y) * scaleY);
|
|
|
|
|
return {dx, dy};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pdfengine::Point2D WasmMockPage::deviceToPage(const pdfengine::DevicePoint& devicePoint, int deviceWidth, int deviceHeight, int rotate) const noexcept {
|
|
|
|
|
(void)rotate;
|
|
|
|
|
double scaleX = width() / static_cast<double>(deviceWidth);
|
|
|
|
|
double scaleY = height() / static_cast<double>(deviceHeight);
|
|
|
|
|
double px = devicePoint.x * scaleX;
|
|
|
|
|
double py = height() - (devicePoint.y * scaleY);
|
|
|
|
|
return {px, py};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
WasmMockDocument::WasmMockDocument(int pageCount, std::vector<uint8_t> data)
|
|
|
|
|
: m_pageCount(pageCount), m_data(std::move(data)) {}
|
|
|
|
|
|
|
|
|
|
pdfengine::DocumentMetadata WasmMockDocument::metadata() const noexcept {
|
|
|
|
|
pdfengine::DocumentMetadata meta;
|
|
|
|
|
meta.title = "WASM Mock Document";
|
|
|
|
|
meta.author = "Emscripten Engine";
|
|
|
|
|
meta.creator = "PdfEngine SDK";
|
|
|
|
|
meta.producer = "WebAssembly Facade";
|
|
|
|
|
meta.creationDate = "D:20260601090000";
|
|
|
|
|
meta.modificationDate = "D:20260601090000";
|
|
|
|
|
return meta;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::expected<std::shared_ptr<pdfengine::PdfPage>, pdfengine::EngineError> WasmMockDocument::getPage(int pageIndex) {
|
|
|
|
|
if (pageIndex < 0 || pageIndex >= m_pageCount) {
|
|
|
|
|
return std::unexpected(pdfengine::EngineError::PageOutOfBounds);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto it = m_pages.find(pageIndex);
|
|
|
|
|
if (it == m_pages.end()) {
|
|
|
|
|
auto page = std::make_shared<WasmMockPage>(pageIndex);
|
|
|
|
|
m_pages[pageIndex] = page;
|
|
|
|
|
return page;
|
|
|
|
|
}
|
|
|
|
|
return it->second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::expected<std::vector<pdfengine::FontInfo>, pdfengine::EngineError> WasmMockDocument::getFonts(int startPage, int endPage) const {
|
|
|
|
|
int last = (endPage < 0) ? m_pageCount - 1 : std::min(endPage, m_pageCount - 1);
|
|
|
|
|
std::vector<pdfengine::FontInfo> result;
|
|
|
|
|
|
|
|
|
|
for (int i = startPage; i <= last; ++i) {
|
|
|
|
|
bool found = false;
|
|
|
|
|
for (const auto& existing : result) {
|
|
|
|
|
if (existing.fontName == "Helvetica") { found = true; break; }
|
|
|
|
|
}
|
|
|
|
|
if (!found) {
|
|
|
|
|
pdfengine::FontInfo info;
|
|
|
|
|
info.fontName = "Helvetica";
|
|
|
|
|
info.type = "Type1";
|
|
|
|
|
info.isEmbedded = false;
|
|
|
|
|
info.isSubset = false;
|
|
|
|
|
info.isVertical = false;
|
|
|
|
|
info.encoding = "WinAnsiEncoding";
|
|
|
|
|
info.hasToUnicode = false;
|
|
|
|
|
info.sourceType = "Substituted";
|
|
|
|
|
info.substitutedFrom = "Helvetica";
|
|
|
|
|
info.substitutedTo = "Liberation Sans Regular";
|
|
|
|
|
info.normalizedFamily= "Arial";
|
|
|
|
|
info.internalFontId = "mock-doc-helvetica";
|
|
|
|
|
info.flags = 32;
|
|
|
|
|
info.ascent = 718.0;
|
|
|
|
|
info.descent = -207.0;
|
|
|
|
|
info.capHeight = 718.0;
|
|
|
|
|
result.push_back(info);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::expected<void, pdfengine::EngineError> WasmMockDocument::applyEdits(const std::string& editsJson) {
|
|
|
|
|
(void)editsJson;
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::expected<std::vector<uint8_t>, pdfengine::EngineError> WasmMockDocument::saveIncremental() const {
|
|
|
|
|
return m_data;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-09 10:39:45 +05:30
|
|
|
std::expected<std::vector<uint8_t>, pdfengine::EngineError> WasmMockDocument::getFontData(const std::string& internalFontId) const {
|
|
|
|
|
(void)internalFontId;
|
|
|
|
|
return std::unexpected(pdfengine::EngineError::Unknown);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::expected<std::shared_ptr<pdfengine::fonts::pdf_fonts::Font>, std::string> WasmMockDocument::getResolvedFont(const pdfengine::FontInfo& fontInfo) {
|
|
|
|
|
(void)fontInfo;
|
|
|
|
|
return std::unexpected("Not implemented in WASM mock");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::expected<std::vector<uint8_t>, pdfengine::EngineError> WasmMockDocument::saveFull() const {
|
|
|
|
|
return m_data;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 15:28:32 +05:30
|
|
|
|
|
|
|
|
PdfEngineFacade::PdfEngineFacade() : m_nextHandle(1) {}
|
2026-05-26 15:52:04 +05:30
|
|
|
|
|
|
|
|
PdfEngineFacade::~PdfEngineFacade() = default;
|
|
|
|
|
|
|
|
|
|
int PdfEngineFacade::loadDocument(const uint8_t* buffer, int size) {
|
|
|
|
|
if (!buffer || size <= 0) {
|
2026-06-22 15:18:47 +05:30
|
|
|
return 0;
|
2026-05-26 15:52:04 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int pages = 0;
|
|
|
|
|
if (size > 4 && buffer[0] == '%' && buffer[1] == 'P' && buffer[2] == 'D' && buffer[3] == 'F') {
|
|
|
|
|
std::string_view sv(reinterpret_cast<const char*>(buffer), size);
|
|
|
|
|
size_t pos = 0;
|
|
|
|
|
while ((pos = sv.find("/Type /Page", pos)) != std::string_view::npos) {
|
|
|
|
|
if (pos + 11 < sv.size() && sv[pos + 11] != 's') {
|
|
|
|
|
pages++;
|
|
|
|
|
}
|
|
|
|
|
pos += 11;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pages == 0) {
|
2026-06-22 15:18:47 +05:30
|
|
|
pages = 3;
|
2026-05-26 15:52:04 +05:30
|
|
|
}
|
|
|
|
|
|
2026-06-01 15:28:32 +05:30
|
|
|
std::vector<uint8_t> docBytes(buffer, buffer + size);
|
|
|
|
|
auto doc = std::make_shared<WasmMockDocument>(pages, std::move(docBytes));
|
|
|
|
|
|
|
|
|
|
int handle = m_nextHandle++;
|
2026-05-26 15:52:04 +05:30
|
|
|
m_documents[handle] = std::move(doc);
|
|
|
|
|
return handle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool PdfEngineFacade::renderPage(int docHandle, int pageIndex, float scale, uint8_t* outputBuffer, int width, int height) {
|
|
|
|
|
auto it = m_documents.find(docHandle);
|
|
|
|
|
if (it == m_documents.end()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto& doc = it->second;
|
2026-06-01 15:28:32 +05:30
|
|
|
auto pageRes = doc->getPage(pageIndex);
|
|
|
|
|
if (!pageRes.has_value()) {
|
2026-05-26 15:52:04 +05:30
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 15:28:32 +05:30
|
|
|
auto page = *pageRes;
|
|
|
|
|
auto mockPage = std::dynamic_pointer_cast<WasmMockPage>(page);
|
|
|
|
|
if (!mockPage) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int y = 0; y < height; ++y) {
|
|
|
|
|
for (int x = 0; x < width; ++x) {
|
|
|
|
|
int idx = (y * width + x) * 4;
|
2026-06-22 15:18:47 +05:30
|
|
|
outputBuffer[idx + 0] = 250;
|
|
|
|
|
outputBuffer[idx + 1] = 250;
|
|
|
|
|
outputBuffer[idx + 2] = 245;
|
|
|
|
|
outputBuffer[idx + 3] = 255;
|
2026-06-01 15:28:32 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WasmRasterizer rasterizer(outputBuffer, width, height, scale);
|
|
|
|
|
mockPage->getDisplayList().replay(rasterizer);
|
|
|
|
|
|
|
|
|
|
return true;
|
2026-05-26 15:52:04 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PdfEngineFacade::freeDocument(int docHandle) {
|
|
|
|
|
m_documents.erase(docHandle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char* PdfEngineFacade::buildInfo() {
|
|
|
|
|
return "PdfEngine WASM Facade (Phase 1/2 Enabled)";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool PdfEngineFacade::hasSkia() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-06-01 15:28:32 +05:30
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
std::string fontInfosToJsonLocal(const std::vector<pdfengine::FontInfo>& fonts) {
|
|
|
|
|
std::string json = "[";
|
|
|
|
|
for (size_t i = 0; i < fonts.size(); ++i) {
|
|
|
|
|
const auto& f = fonts[i];
|
|
|
|
|
if (i > 0) json += ",";
|
|
|
|
|
json += "{";
|
|
|
|
|
json += "\"fontName\":\"" + f.fontName + "\",";
|
|
|
|
|
json += "\"type\":\"" + f.type + "\",";
|
|
|
|
|
json += "\"isEmbedded\":" + std::string(f.isEmbedded ? "true" : "false") + ",";
|
|
|
|
|
json += "\"isSubset\":" + std::string(f.isSubset ? "true" : "false") + ",";
|
|
|
|
|
json += "\"isVertical\":" + std::string(f.isVertical ? "true" : "false") + ",";
|
|
|
|
|
json += "\"encoding\":\"" + f.encoding + "\",";
|
|
|
|
|
json += "\"hasToUnicode\":" + std::string(f.hasToUnicode ? "true" : "false") + ",";
|
|
|
|
|
json += "\"cmapName\":\"" + f.cmapName + "\",";
|
|
|
|
|
json += "\"cidSystemInfo\":\"" + f.cidSystemInfo + "\",";
|
|
|
|
|
json += "\"subsetTag\":\"" + f.subsetTag + "\",";
|
|
|
|
|
json += "\"sourceType\":\"" + f.sourceType + "\",";
|
|
|
|
|
json += "\"substitutedFrom\":\"" + f.substitutedFrom + "\",";
|
|
|
|
|
json += "\"substitutedTo\":\"" + f.substitutedTo + "\",";
|
|
|
|
|
json += "\"normalizedFamily\":\"" + f.normalizedFamily + "\",";
|
|
|
|
|
json += "\"internalFontId\":\"" + f.internalFontId + "\",";
|
|
|
|
|
json += "\"ascent\":" + std::to_string(f.ascent) + ",";
|
|
|
|
|
json += "\"descent\":" + std::to_string(f.descent) + ",";
|
|
|
|
|
json += "\"capHeight\":" + std::to_string(f.capHeight);
|
|
|
|
|
json += "}";
|
|
|
|
|
}
|
|
|
|
|
json += "]";
|
|
|
|
|
return json;
|
|
|
|
|
}
|
2026-06-09 10:39:45 +05:30
|
|
|
|
|
|
|
|
std::string escapeJsonString(const std::string& input) {
|
|
|
|
|
std::string output;
|
|
|
|
|
for (char c : input) {
|
|
|
|
|
if (c == '"') output += "\\\"";
|
|
|
|
|
else if (c == '\\') output += "\\\\";
|
|
|
|
|
else if (c == '\b') output += "\\b";
|
|
|
|
|
else if (c == '\f') output += "\\f";
|
|
|
|
|
else if (c == '\n') output += "\\n";
|
|
|
|
|
else if (c == '\r') output += "\\r";
|
|
|
|
|
else if (c == '\t') output += "\\t";
|
|
|
|
|
else if (static_cast<unsigned char>(c) < 32) {
|
|
|
|
|
char buf[16];
|
|
|
|
|
snprintf(buf, sizeof(buf), "\\u%04x", c);
|
|
|
|
|
output += buf;
|
|
|
|
|
} else {
|
|
|
|
|
output += c;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string glyphBoundsToJsonLocal(const std::vector<pdfengine::GlyphBounds>& glyphs) {
|
|
|
|
|
std::string json = "[";
|
|
|
|
|
for (size_t i = 0; i < glyphs.size(); ++i) {
|
|
|
|
|
const auto& g = glyphs[i];
|
|
|
|
|
if (i > 0) json += ",";
|
|
|
|
|
json += "{";
|
|
|
|
|
json += "\"text\":\"" + escapeJsonString(g.text) + "\",";
|
|
|
|
|
json += "\"x\":" + std::to_string(g.x) + ",";
|
|
|
|
|
json += "\"y\":" + std::to_string(g.y) + ",";
|
|
|
|
|
json += "\"w\":" + std::to_string(g.w) + ",";
|
|
|
|
|
json += "\"h\":" + std::to_string(g.h) + ",";
|
|
|
|
|
json += "\"fontSize\":" + std::to_string(g.fontSize);
|
|
|
|
|
json += "}";
|
|
|
|
|
}
|
|
|
|
|
json += "]";
|
|
|
|
|
return json;
|
|
|
|
|
}
|
2026-06-22 15:18:47 +05:30
|
|
|
}
|
2026-06-01 15:28:32 +05:30
|
|
|
|
|
|
|
|
std::string PdfEngineFacade::getDocumentFonts(int docHandle, int startPage, int endPage) {
|
|
|
|
|
auto it = m_documents.find(docHandle);
|
|
|
|
|
if (it == m_documents.end()) {
|
|
|
|
|
return "[]";
|
|
|
|
|
}
|
|
|
|
|
auto result = it->second->getFonts(startPage, endPage);
|
|
|
|
|
if (!result.has_value()) {
|
|
|
|
|
return "[]";
|
|
|
|
|
}
|
|
|
|
|
return fontInfosToJsonLocal(*result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string PdfEngineFacade::getPageFonts(int docHandle, int pageIndex) {
|
|
|
|
|
auto it = m_documents.find(docHandle);
|
|
|
|
|
if (it == m_documents.end()) {
|
|
|
|
|
return "[]";
|
|
|
|
|
}
|
|
|
|
|
auto pageRes = it->second->getPage(pageIndex);
|
|
|
|
|
if (!pageRes.has_value()) {
|
|
|
|
|
return "[]";
|
|
|
|
|
}
|
|
|
|
|
auto result = (*pageRes)->getFonts();
|
|
|
|
|
if (!result.has_value()) {
|
|
|
|
|
return "[]";
|
|
|
|
|
}
|
|
|
|
|
return fontInfosToJsonLocal(*result);
|
|
|
|
|
}
|
2026-06-09 10:39:45 +05:30
|
|
|
|
|
|
|
|
std::string PdfEngineFacade::getPageTextJson(int docHandle, int pageIndex) {
|
|
|
|
|
auto it = m_documents.find(docHandle);
|
|
|
|
|
if (it == m_documents.end()) {
|
|
|
|
|
return "[]";
|
|
|
|
|
}
|
|
|
|
|
auto pageRes = it->second->getPage(pageIndex);
|
|
|
|
|
if (!pageRes.has_value()) {
|
|
|
|
|
return "[]";
|
|
|
|
|
}
|
|
|
|
|
auto result = (*pageRes)->extractTextWithBounds();
|
|
|
|
|
if (!result.has_value()) {
|
|
|
|
|
return "[]";
|
|
|
|
|
}
|
|
|
|
|
return glyphBoundsToJsonLocal(*result);
|
|
|
|
|
}
|