Save and Write
This page explains how to save PDF documents to files and how to write PDF bytes in memory with rix/pdf.
The examples use the public Rix facade:
#include <rix.hpp>and access PDF through:
rix.pdfUse save when you want to create a .pdf file on disk.
Use write when you want the generated PDF bytes as a string.
Basic save example
Create a file:
mkdir -p ~/rix-pdf-save-write
cd ~/rix-pdf-save-write
touch save_write.cppAdd:
#include <rix.hpp>
int main()
{
auto doc = rix.pdf.document();
auto &page = doc.add_page();
page.text(
page.x_left(),
page.y_top(),
"Hello from rix.pdf");
auto saved = rix.pdf.save(doc, "hello.pdf");
if (saved.failed())
{
rix.debug.eprint(
"pdf error:",
rix.pdf.error.to_string(saved.error()),
saved.error().message());
return 1;
}
rix.debug.print("created:", "hello.pdf");
return 0;
}Run it:
vix run save_write.cppIf Rix is not available yet for single-file usage:
vix install -g rix/rix
vix run save_write.cppThis creates:
hello.pdfSave a document
Use:
auto saved = rix.pdf.save(doc, "output.pdf");save serializes the document and writes it to the given file path.
It returns a status object.
Check the result:
if (saved.failed())
{
rix.debug.eprint(
"pdf error:",
rix.pdf.error.to_string(saved.error()),
saved.error().message());
return 1;
}Save returns a status
save returns PdfStatus.
A status has:
saved.ok()
saved.failed()
saved.error()Example:
auto saved = rix.pdf.save(doc, "report.pdf");
if (saved.ok())
{
rix.debug.print("created:", "report.pdf");
}For errors:
if (saved.failed())
{
const auto &error = saved.error();
rix.debug.eprint(
"pdf error:",
rix.pdf.error.to_string(error),
error.message());
return 1;
}Write PDF bytes
Use:
auto bytes = rix.pdf.write(doc);write serializes the document and returns the PDF bytes in memory.
It does not create a file by itself.
Example:
auto bytes = rix.pdf.write(doc);
if (bytes.failed())
{
rix.debug.eprint(
"pdf error:",
rix.pdf.error.to_string(bytes.error()),
bytes.error().message());
return 1;
}
rix.debug.print("pdf bytes:", bytes.value().size());Write returns a result
write returns a result object.
A result has:
bytes.ok()
bytes.failed()
bytes.value()
bytes.error()Use value() only after checking that the result succeeded.
auto bytes = rix.pdf.write(doc);
if (bytes.failed())
{
return 1;
}
const auto &pdf_data = bytes.value();Save vs write
Use save when the output should be a file:
auto saved = rix.pdf.save(doc, "invoice.pdf");Use write when another part of the application will handle the bytes:
auto bytes = rix.pdf.write(doc);Common examples:
| Operation | Use |
|---|---|
| Create a local PDF file | rix.pdf.save(doc, "file.pdf") |
| Return a PDF from an HTTP route | rix.pdf.write(doc) |
| Store generated bytes in another system | rix.pdf.write(doc) |
| Create a simple report on disk | rix.pdf.save(doc, "report.pdf") |
| Inspect generated PDF size | rix.pdf.write(doc) |
Complete write example
#include <rix.hpp>
int main()
{
auto doc = rix.pdf.document();
doc.set_title("Write PDF bytes");
auto &page = doc.add_page();
page.heading(
page.x_left(),
page.y_top(),
"Write PDF bytes",
1);
auto bytes = rix.pdf.write(doc);
if (bytes.failed())
{
rix.debug.eprint(
"pdf error:",
rix.pdf.error.to_string(bytes.error()),
bytes.error().message());
return 1;
}
rix.debug.print("generated bytes:", bytes.value().size());
return 0;
}Run:
vix run save_write.cppSave after write
If you use write, you can still save the returned bytes yourself.
#include <rix.hpp>
#include <fstream>
int main()
{
auto doc = rix.pdf.document();
auto &page = doc.add_page();
page.text(
page.x_left(),
page.y_top(),
"PDF bytes written manually");
auto bytes = rix.pdf.write(doc);
if (bytes.failed())
{
rix.debug.eprint(
"pdf error:",
rix.pdf.error.to_string(bytes.error()),
bytes.error().message());
return 1;
}
std::ofstream file{
"manual.pdf",
std::ios::binary};
if (!file)
{
rix.debug.eprint("file error:", "cannot open manual.pdf");
return 1;
}
file.write(
bytes.value().data(),
static_cast<std::streamsize>(bytes.value().size()));
if (!file)
{
rix.debug.eprint("file error:", "cannot write manual.pdf");
return 1;
}
rix.debug.print("created:", "manual.pdf");
return 0;
}For normal file output, prefer:
rix.pdf.save(doc, "manual.pdf");Use manual writing only when you need full control.
Save with generated content
#include <rix.hpp>
int main()
{
auto doc = rix.pdf.document();
doc.set_title("Generated Report")
.set_author("Rix");
auto &page = doc.add_page();
auto y = page.heading(
page.x_left(),
page.y_top(),
"Generated Report",
1);
y -= 15.0F;
page.paragraph(
page.x_left(),
y,
page.content_width(),
"This PDF was generated and saved to disk with rix.pdf.save.");
auto saved = rix.pdf.save(doc, "generated-report.pdf");
if (saved.failed())
{
rix.debug.eprint(
"pdf error:",
rix.pdf.error.to_string(saved.error()),
saved.error().message());
return 1;
}
rix.debug.print("created:", "generated-report.pdf");
return 0;
}Save a simple text PDF
For a simple text file, use make_text:
auto saved = rix.pdf.make_text(
"hello.pdf",
"Hello from rix.pdf",
"Rix PDF");Complete example:
#include <rix.hpp>
int main()
{
auto saved = rix.pdf.make_text(
"hello.pdf",
"This file was generated with rix.pdf.make_text.",
"Rix PDF");
if (saved.failed())
{
rix.debug.eprint(
"pdf error:",
rix.pdf.error.to_string(saved.error()),
saved.error().message());
return 1;
}
rix.debug.print("created:", "hello.pdf");
return 0;
}make_text internally creates a document, adds a page, writes text, and saves the file.
Save to a nested path
If you save to a nested path, make sure the folder exists.
mkdir -p outputThen:
auto saved = rix.pdf.save(doc, "output/report.pdf");If the folder does not exist, saving will fail.
Invalid output path
This fails:
auto saved = rix.pdf.save(doc, "");Handle the error:
if (saved.failed())
{
rix.debug.eprint(
"pdf error:",
rix.pdf.error.to_string(saved.error()),
saved.error().message());
}An empty path is invalid because save needs a real file destination.
Error handling pattern
Use the same pattern for save and write.
For save:
auto saved = rix.pdf.save(doc, "output.pdf");
if (saved.failed())
{
const auto &error = saved.error();
rix.debug.eprint(
"pdf error:",
rix.pdf.error.to_string(error),
error.message());
return 1;
}For write:
auto bytes = rix.pdf.write(doc);
if (bytes.failed())
{
const auto &error = bytes.error();
rix.debug.eprint(
"pdf error:",
rix.pdf.error.to_string(error),
error.message());
return 1;
}Use PDF bytes in an HTTP route
When building an HTTP response, use write.
A simplified route can look like this:
#include <vix.hpp>
#include <rix.hpp>
int main()
{
vix::App app;
app.get("/report.pdf", [](vix::Request &, vix::Response &res) {
auto doc = rix.pdf.document();
auto &page = doc.add_page();
page.text(
page.x_left(),
page.y_top(),
"Hello from rix.pdf");
auto bytes = rix.pdf.write(doc);
if (bytes.failed())
{
res.status(500).json({
"ok", false,
"error", rix.pdf.error.to_string(bytes.error()),
"message", bytes.error().message()});
return;
}
res.header("Content-Type", "application/pdf");
res.header("Content-Disposition", "inline; filename=\"report.pdf\"");
res.send(bytes.value());
});
app.run();
return 0;
}Use write here because the PDF should be returned as a response body, not saved to a local file first.
Use save in a job
For a background job or local command, use save.
auto status = rix.pdf.save(doc, "daily-report.pdf");
if (status.failed())
{
rix.debug.eprint(
"pdf error:",
rix.pdf.error.to_string(status.error()),
status.error().message());
return 1;
}This is the normal pattern for reports written to disk.
Use a standalone writer
The facade also exposes a writer module:
auto writer = rix.pdf.writer.create();Then:
auto bytes = writer.write(doc);
auto saved = writer.save(doc, "output.pdf");Example:
#include <rix.hpp>
int main()
{
auto doc = rix.pdf.document();
auto &page = doc.add_page();
page.text(
page.x_left(),
page.y_top(),
"Saved through a standalone writer");
auto writer = rix.pdf.writer.create();
auto saved = writer.save(doc, "writer.pdf");
if (saved.failed())
{
rix.debug.eprint(
"pdf error:",
rix.pdf.error.to_string(saved.error()),
saved.error().message());
return 1;
}
rix.debug.print("created:", "writer.pdf");
return 0;
}For most applications, rix.pdf.save and rix.pdf.write are enough.
Empty documents
If a document has no pages, the writer can still generate a valid PDF by adding a blank default page during serialization.
auto doc = rix.pdf.document();
auto saved = rix.pdf.save(doc, "blank.pdf");This can be useful, but most applications should add visible content explicitly:
auto &page = doc.add_page();Save with metadata
Metadata is included when saving or writing:
auto doc = rix.pdf.document();
doc.set_title("Saved Document")
.set_author("Rix")
.set_subject("Save and write")
.set_keywords("rix,pdf,save,write");Then:
rix.pdf.save(doc, "metadata.pdf");or:
rix.pdf.write(doc);Both include the metadata in the generated PDF.
Use in a Vix project
Create a Vix application:
vix new pdf-save-write --app
cd pdf-save-writeAdd Rix:
vix add rix/rix
vix installIn vix.app, make sure Rix is listed under deps:
deps = [
"rix/rix",
]A small vix.app can look like this:
name = "pdf-save-write"
type = "executable"
standard = "c++20"
output_dir = "bin"
sources = [
"src/main.cpp",
]
include_dirs = [
"include",
"src",
]
deps = [
"rix/rix",
]
packages = [
"vix",
]
links = [
"vix::vix",
]Then use save in src/main.cpp:
#include <rix.hpp>
int main()
{
auto doc = rix.pdf.document();
auto &page = doc.add_page();
page.text(
page.x_left(),
page.y_top(),
"Hello from rix.pdf");
auto saved = rix.pdf.save(doc, "hello.pdf");
if (saved.failed())
{
rix.debug.eprint(
"pdf error:",
rix.pdf.error.to_string(saved.error()),
saved.error().message());
return 1;
}
rix.debug.print("created:", "hello.pdf");
return 0;
}Build and run:
vix build
vix runSingle-file usage
For small scripts, examples, and experiments:
vix run save_write.cppIf Rix is installed globally for single-file usage:
vix install -g rix/rix
vix run save_write.cppFor project usage, prefer:
vix add rix/rix
vix installand keep the dependency in vix.app:
deps = [
"rix/rix",
]Use only PDF with the facade
If you want the rix.* facade style but only want PDF mounted, define the feature macro before including rix.hpp:
#define RIX_ENABLE_PDF
#include <rix.hpp>
int main()
{
auto doc = rix.pdf.document();
auto &page = doc.add_page();
page.text(
page.x_left(),
page.y_top(),
"Hello from rix.pdf");
return rix.pdf.save(doc, "hello.pdf").ok() ? 0 : 1;
}When at least one RIX_ENABLE_* macro is defined, only selected modules are mounted.
Use the independent package
For independent usage, install:
vix add rix/pdf
vix installIn vix.app:
deps = [
"rix/pdf",
]Then include:
#include <rix/pdf.hpp>Use this style when a project only needs PDF and does not need the full unified Rix facade.
For most application documentation, prefer:
#include <rix.hpp>Common mistakes
Using write and expecting a file
This does not create a file:
auto bytes = rix.pdf.write(doc);To create a file, use:
auto saved = rix.pdf.save(doc, "output.pdf");Using save when you need HTTP bytes
This writes a file:
rix.pdf.save(doc, "report.pdf");For an HTTP response, use:
auto bytes = rix.pdf.write(doc);Not checking save errors
Wrong:
rix.pdf.save(doc, "output.pdf");Better:
auto saved = rix.pdf.save(doc, "output.pdf");
if (saved.failed())
{
rix.debug.eprint(
"pdf error:",
rix.pdf.error.to_string(saved.error()),
saved.error().message());
return 1;
}Not checking write errors
Wrong:
auto bytes = rix.pdf.write(doc);
auto size = bytes.value().size();Better:
auto bytes = rix.pdf.write(doc);
if (bytes.failed())
{
rix.debug.eprint(
"pdf error:",
rix.pdf.error.to_string(bytes.error()),
bytes.error().message());
return 1;
}
auto size = bytes.value().size();Saving to a missing folder
This can fail if output/ does not exist:
rix.pdf.save(doc, "output/report.pdf");Create the folder first:
mkdir -p outputSaving to an empty path
This fails:
rix.pdf.save(doc, "");Use a real path:
rix.pdf.save(doc, "output.pdf");Confusing deps and packages
For a Vix project, do not put Rix packages in packages.
Wrong:
packages = [
"rix/rix",
]Correct:
deps = [
"rix/rix",
]deps is for Vix Registry packages.
packages is for CMake package discovery.
What you should remember
Use save for files:
auto saved = rix.pdf.save(doc, "output.pdf");Use write for bytes:
auto bytes = rix.pdf.write(doc);Check save errors:
if (saved.failed())
{
rix.debug.eprint(
"pdf error:",
rix.pdf.error.to_string(saved.error()),
saved.error().message());
}Check write errors:
if (bytes.failed())
{
rix.debug.eprint(
"pdf error:",
rix.pdf.error.to_string(bytes.error()),
bytes.error().message());
}Use make_text for a quick text PDF:
rix.pdf.make_text(
"hello.pdf",
"Hello from rix.pdf",
"Rix PDF");For a Vix project, install Rix:
vix add rix/rix
vix installand use:
deps = [
"rix/rix",
]Next step
Learn how to handle PDF errors.
Next: Errors