Skip to content

Commit bfa35dd

Browse files
Introduce C++ examples using pdfRest API
- Added multiple C++ samples demonstrating API flows: JSON payload (`markdown_json`, `rasterized_pdf_json`), multipart payload (`markdown_multipart`, `rasterized_pdf_multipart`), and complex flows (`merge_different_file_types`). - Organized related samples into separate directories with dedicated `CMakeLists.txt` for modular inclusion. - Enabled dotenv-based environment variable loading for simpler integration. - Updated `CMakeLists.txt` to centralize executable output in the build directory and added necessary dependencies (`cpr`, `nlohmann_json`). - Improved sample input validation, error handling, and API key setup for seamless usability.
1 parent c6043cd commit bfa35dd

9 files changed

Lines changed: 557 additions & 64 deletions

File tree

CPlusPlus/CMakeLists.txt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ project(CPlusPlus)
33

44
set(CMAKE_CXX_STANDARD 20)
55

6-
add_executable(CPlusPlus main.cpp)
6+
# Place all executables in the top-level build directory for convenience
7+
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
8+
foreach(OUTPUTCONFIG DEBUG RELEASE RELWITHDEBINFO MINSIZEREL)
9+
string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG_UPPER)
10+
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG_UPPER} ${CMAKE_BINARY_DIR})
11+
endforeach()
712

8-
# Dependencies via vcpkg
9-
find_package(nlohmann_json CONFIG QUIET)
10-
find_package(cpr CONFIG QUIET)
11-
12-
if (cpr_FOUND AND nlohmann_json_FOUND)
13-
add_executable(markdown_json markdown_json.cpp)
14-
target_link_libraries(markdown_json PRIVATE cpr::cpr nlohmann_json::nlohmann_json)
15-
target_compile_features(markdown_json PRIVATE cxx_std_20)
16-
endif()
13+
# Define sample targets in subdirectories
14+
add_subdirectory("Endpoint Examples/JSON Payload")
15+
add_subdirectory("Endpoint Examples/Multipart Payload")
16+
add_subdirectory("Complex Flow Examples")
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
cmake_minimum_required(VERSION 3.16)
2+
3+
find_package(cpr CONFIG QUIET)
4+
find_package(nlohmann_json CONFIG QUIET)
5+
6+
if (cpr_FOUND AND nlohmann_json_FOUND)
7+
add_executable(merge_different_file_types merge_different_file_types.cpp)
8+
target_link_libraries(merge_different_file_types PRIVATE cpr::cpr nlohmann_json::nlohmann_json)
9+
target_compile_features(merge_different_file_types PRIVATE cxx_std_20)
10+
endif()
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* What this sample does:
3+
* - Converts two different file types to PDF via multipart (/pdf), then merges them via /merged-pdf.
4+
*
5+
* Setup (environment):
6+
* - Copy .env.example to .env
7+
* - Set PDFREST_API_KEY=your_api_key_here
8+
* - Optional: set PDFREST_URL to override the API region. For EU/GDPR compliance and proximity, use:
9+
* PDFREST_URL=https://eu-api.pdfrest.com
10+
* For more information visit https://pdfrest.com/pricing#how-do-eu-gdpr-api-calls-work
11+
*
12+
* Usage:
13+
* ./merge_different_file_types image.png slides.pptx
14+
*
15+
* Output:
16+
* - Prints JSON responses for the two conversions and the final merge; non-2xx exits with concise error.
17+
*/
18+
19+
#include <cpr/cpr.h>
20+
#include <nlohmann/json.hpp>
21+
22+
#include <cstdlib>
23+
#include <filesystem>
24+
#include <fstream>
25+
#include <iostream>
26+
#include <string>
27+
28+
namespace fs = std::filesystem;
29+
using json = nlohmann::json;
30+
31+
static std::string rtrim_slashes(std::string s) {
32+
while (!s.empty() && (s.back() == '/' || s.back() == '\\')) {
33+
s.pop_back();
34+
}
35+
return s;
36+
}
37+
38+
static void load_dotenv_if_present(const fs::path &p) {
39+
std::ifstream f(p);
40+
if (!f.is_open()) return;
41+
std::string l;
42+
while (std::getline(f, l)) {
43+
if (l.empty() || l[0] == '#') continue;
44+
auto pos = l.find('=');
45+
if (pos == std::string::npos) continue;
46+
std::string k = l.substr(0, pos);
47+
std::string v = l.substr(pos + 1);
48+
auto trim = [](std::string &s) {
49+
size_t b = s.find_first_not_of(" \t\r\n");
50+
size_t e = s.find_last_not_of(" \t\r\n");
51+
if (b == std::string::npos) { s.clear(); return; }
52+
s = s.substr(b, e - b + 1);
53+
};
54+
trim(k); trim(v);
55+
if (k.empty()) continue;
56+
if (!std::getenv(k.c_str())) {
57+
#ifdef _WIN32
58+
_putenv_s(k.c_str(), v.c_str());
59+
#else
60+
setenv(k.c_str(), v.c_str(), 0);
61+
#endif
62+
}
63+
}
64+
}
65+
66+
static void load_env() {
67+
auto here = fs::current_path();
68+
load_dotenv_if_present(here / ".env");
69+
if (fs::exists(here.parent_path())) {
70+
load_dotenv_if_present(here.parent_path() / ".env");
71+
}
72+
}
73+
74+
static std::string convert_to_pdf_id(const std::string &base,
75+
const std::string &api_key,
76+
const fs::path &input) {
77+
cpr::Header hdr{{"Api-Key", api_key}, {"Accept", "application/json"}};
78+
cpr::Multipart mp{{"file", cpr::File{input.string()}}};
79+
auto res = cpr::Post(cpr::Url{base + "/pdf"}, hdr, mp);
80+
if (res.error || res.status_code < 200 || res.status_code >= 300) {
81+
throw std::runtime_error(
82+
"Convert failed: " + std::to_string(res.status_code) + "\n" +
83+
res.error.message + "\n" + res.text);
84+
}
85+
std::cout << res.text << "\n";
86+
auto j = json::parse(res.text);
87+
return j.at("outputId").get<std::string>();
88+
}
89+
90+
int main(int argc, char **argv) {
91+
load_env();
92+
if (argc < 3) {
93+
std::cerr << "Usage: merge_different_file_types <imageFile> <pptFile>\n";
94+
return 1;
95+
}
96+
fs::path img = argv[1], ppt = argv[2];
97+
if (!fs::exists(img) || !fs::exists(ppt)) {
98+
std::cerr << "One or more input files not found.\n";
99+
return 1;
100+
}
101+
const char *key = getenv("PDFREST_API_KEY");
102+
if (!key || !*key) {
103+
std::cerr << "Missing PDFREST_API_KEY\n";
104+
return 1;
105+
}
106+
std::string base = getenv("PDFREST_URL") ? getenv("PDFREST_URL") : "https://api.pdfrest.com";
107+
base = rtrim_slashes(base);
108+
109+
try {
110+
std::string img_id = convert_to_pdf_id(base, key, img);
111+
std::string ppt_id = convert_to_pdf_id(base, key, ppt);
112+
113+
cpr::Header hdr{{"Api-Key", key}, {"Accept", "application/json"}};
114+
cpr::Multipart mp{
115+
{"id[]", img_id}, {"type[]", "id"}, {"pages[]", "all"},
116+
{"id[]", ppt_id}, {"type[]", "id"}, {"pages[]", "all"}
117+
};
118+
auto merge = cpr::Post(cpr::Url{base + "/merged-pdf"}, hdr, mp);
119+
if (merge.error || merge.status_code < 200 || merge.status_code >= 300) {
120+
std::cerr << "Merge failed (" << merge.status_code << ")\n"
121+
<< merge.error.message << "\n" << merge.text << "\n";
122+
return 1;
123+
}
124+
std::cout << merge.text << "\n";
125+
} catch (const std::exception &e) {
126+
std::cerr << e.what() << "\n";
127+
return 1;
128+
}
129+
return 0;
130+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
cmake_minimum_required(VERSION 3.16)
2+
3+
find_package(cpr CONFIG QUIET)
4+
find_package(nlohmann_json CONFIG QUIET)
5+
6+
if (cpr_FOUND AND nlohmann_json_FOUND)
7+
add_executable(markdown_json markdown.cpp)
8+
target_link_libraries(markdown_json PRIVATE cpr::cpr nlohmann_json::nlohmann_json)
9+
target_compile_features(markdown_json PRIVATE cxx_std_20)
10+
11+
add_executable(rasterized_pdf_json rasterized_pdf.cpp)
12+
target_link_libraries(rasterized_pdf_json PRIVATE cpr::cpr nlohmann_json::nlohmann_json)
13+
target_compile_features(rasterized_pdf_json PRIVATE cxx_std_20)
14+
endif()

CPlusPlus/markdown_json.cpp renamed to CPlusPlus/Endpoint Examples/JSON Payload/markdown.cpp

Lines changed: 49 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
#include <optional>
2828
#include <string>
2929

30+
// Loads .env manually (no external dotenv dependency required)
31+
3032
namespace fs = std::filesystem;
3133
using json = nlohmann::json;
3234

@@ -47,7 +49,6 @@ static void load_dotenv_if_present(const fs::path &path) {
4749
if (pos == std::string::npos) continue;
4850
std::string key = line.substr(0, pos);
4951
std::string val = line.substr(pos + 1);
50-
// Trim whitespace
5152
auto trim = [](std::string &s) {
5253
size_t start = s.find_first_not_of(" \t\r\n");
5354
size_t end = s.find_last_not_of(" \t\r\n");
@@ -57,7 +58,6 @@ static void load_dotenv_if_present(const fs::path &path) {
5758
trim(key);
5859
trim(val);
5960
if (key.empty()) continue;
60-
// Do not override if already set
6161
if (std::getenv(key.c_str()) == nullptr) {
6262
#ifdef _WIN32
6363
_putenv_s(key.c_str(), val.c_str());
@@ -69,7 +69,6 @@ static void load_dotenv_if_present(const fs::path &path) {
6969
}
7070

7171
static void load_env() {
72-
// Try ./.env then ../.env
7372
const fs::path here = fs::current_path();
7473
load_dotenv_if_present(here / ".env");
7574
if (fs::exists(here.parent_path())) {
@@ -88,7 +87,7 @@ int main(int argc, char *argv[]) {
8887
load_env();
8988

9089
if (argc < 2) {
91-
std::cerr << "Usage: " << argv[0] << " <input.pdf>\n";
90+
std::cerr << "Usage: markdown_json <input.pdf>\n";
9291
return 1;
9392
}
9493
fs::path input_path(argv[1]);
@@ -117,60 +116,56 @@ int main(int argc, char *argv[]) {
117116
std::string body = std::move(*maybe_data);
118117

119118
// Upload
120-
{
121-
cpr::Header headers{
122-
{"Api-Key", api_key},
123-
{"Accept", "application/json"},
124-
{"Content-Type", "application/octet-stream"},
125-
{"Content-Filename", input_path.filename().string()}
126-
};
127-
128-
auto res = cpr::Post(
129-
cpr::Url{base_url + "/upload"},
130-
headers,
131-
cpr::Body{body}
132-
);
133-
134-
if (res.error || res.status_code < 200 || res.status_code >= 300) {
135-
std::cerr << "Upload failed (status " << res.status_code << "): "
136-
<< res.error.message << "\n" << res.text << "\n";
137-
return 1;
138-
}
139-
140-
std::cout << res.text << "\n"; // Upload response (JSON)
141-
142-
// Parse id
143-
std::string uploaded_id;
144-
try {
145-
auto j = json::parse(res.text);
146-
uploaded_id = j.at("files").at(0).at("id").get<std::string>();
147-
} catch (const std::exception &e) {
148-
std::cerr << "Failed to parse upload id: " << e.what() << "\n";
149-
return 1;
150-
}
119+
cpr::Header headers{
120+
{"Api-Key", api_key},
121+
{"Accept", "application/json"},
122+
{"Content-Type", "application/octet-stream"},
123+
{"Content-Filename", input_path.filename().string()}
124+
};
125+
auto res = cpr::Post(
126+
cpr::Url{base_url + "/upload"},
127+
headers,
128+
cpr::Body{body}
129+
);
130+
131+
if (res.error || res.status_code < 200 || res.status_code >= 300) {
132+
std::cerr << "Upload failed (status " << res.status_code << "): "
133+
<< res.error.message << "\n" << res.text << "\n";
134+
return 1;
135+
}
151136

152-
// Markdown request
153-
json payload = { {"id", uploaded_id} };
137+
std::cout << res.text << "\n"; // Upload response (JSON)
154138

155-
cpr::Header md_headers{
156-
{"Api-Key", api_key},
157-
{"Accept", "application/json"},
158-
{"Content-Type", "application/json"}
159-
};
160-
auto md_res = cpr::Post(
161-
cpr::Url{base_url + "/markdown"},
162-
md_headers,
163-
cpr::Body{payload.dump()}
164-
);
165-
166-
if (md_res.error || md_res.status_code < 200 || md_res.status_code >= 300) {
167-
std::cerr << "Markdown failed (status " << md_res.status_code << "): "
168-
<< md_res.error.message << "\n" << md_res.text << "\n";
169-
return 1;
170-
}
139+
// Parse id
140+
std::string uploaded_id;
141+
try {
142+
auto j = json::parse(res.text);
143+
uploaded_id = j.at("files").at(0).at("id").get<std::string>();
144+
} catch (const std::exception &e) {
145+
std::cerr << "Failed to parse upload id: " << e.what() << "\n";
146+
return 1;
147+
}
171148

172-
std::cout << md_res.text << "\n"; // Markdown response (JSON)
149+
// Markdown request
150+
json payload = { {"id", uploaded_id} };
151+
152+
cpr::Header md_headers{
153+
{"Api-Key", api_key},
154+
{"Accept", "application/json"},
155+
{"Content-Type", "application/json"}
156+
};
157+
auto md_res = cpr::Post(
158+
cpr::Url{base_url + "/markdown"},
159+
md_headers,
160+
cpr::Body{payload.dump()}
161+
);
162+
163+
if (md_res.error || md_res.status_code < 200 || md_res.status_code >= 300) {
164+
std::cerr << "Markdown failed (status " << md_res.status_code << "): "
165+
<< md_res.error.message << "\n" << md_res.text << "\n";
166+
return 1;
173167
}
174168

169+
std::cout << md_res.text << "\n"; // Markdown response (JSON)
175170
return 0;
176171
}

0 commit comments

Comments
 (0)