Rix is the official package layer for Vix.cpp Browse packages
Skip to content

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:

cpp
#include <rix.hpp>

and access PDF through:

cpp
rix.pdf

Use 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:

bash
mkdir -p ~/rix-pdf-save-write
cd ~/rix-pdf-save-write
touch save_write.cpp

Add:

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;
}

Run it:

bash
vix run save_write.cpp

If Rix is not available yet for single-file usage:

bash
vix install -g rix/rix
vix run save_write.cpp

This creates:

txt
hello.pdf

Save a document

Use:

cpp
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:

cpp
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:

cpp
saved.ok()
saved.failed()
saved.error()

Example:

cpp
auto saved = rix.pdf.save(doc, "report.pdf");

if (saved.ok())
{
  rix.debug.print("created:", "report.pdf");
}

For errors:

cpp
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:

cpp
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:

cpp
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:

cpp
bytes.ok()
bytes.failed()
bytes.value()
bytes.error()

Use value() only after checking that the result succeeded.

cpp
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:

cpp
auto saved = rix.pdf.save(doc, "invoice.pdf");

Use write when another part of the application will handle the bytes:

cpp
auto bytes = rix.pdf.write(doc);

Common examples:

OperationUse
Create a local PDF filerix.pdf.save(doc, "file.pdf")
Return a PDF from an HTTP routerix.pdf.write(doc)
Store generated bytes in another systemrix.pdf.write(doc)
Create a simple report on diskrix.pdf.save(doc, "report.pdf")
Inspect generated PDF sizerix.pdf.write(doc)

Complete write example

cpp
#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:

bash
vix run save_write.cpp

Save after write

If you use write, you can still save the returned bytes yourself.

cpp
#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:

cpp
rix.pdf.save(doc, "manual.pdf");

Use manual writing only when you need full control.

Save with generated content

cpp
#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:

cpp
auto saved = rix.pdf.make_text(
    "hello.pdf",
    "Hello from rix.pdf",
    "Rix PDF");

Complete example:

cpp
#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.

bash
mkdir -p output

Then:

cpp
auto saved = rix.pdf.save(doc, "output/report.pdf");

If the folder does not exist, saving will fail.

Invalid output path

This fails:

cpp
auto saved = rix.pdf.save(doc, "");

Handle the error:

cpp
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:

cpp
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:

cpp
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:

cpp
#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.

cpp
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:

cpp
auto writer = rix.pdf.writer.create();

Then:

cpp
auto bytes = writer.write(doc);
auto saved = writer.save(doc, "output.pdf");

Example:

cpp
#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.

cpp
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:

cpp
auto &page = doc.add_page();

Save with metadata

Metadata is included when saving or writing:

cpp
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:

cpp
rix.pdf.save(doc, "metadata.pdf");

or:

cpp
rix.pdf.write(doc);

Both include the metadata in the generated PDF.

Use in a Vix project

Create a Vix application:

bash
vix new pdf-save-write --app
cd pdf-save-write

Add Rix:

bash
vix add rix/rix
vix install

In vix.app, make sure Rix is listed under deps:

txt
deps = [
  "rix/rix",
]

A small vix.app can look like this:

txt
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:

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:

bash
vix build
vix run

Single-file usage

For small scripts, examples, and experiments:

bash
vix run save_write.cpp

If Rix is installed globally for single-file usage:

bash
vix install -g rix/rix
vix run save_write.cpp

For project usage, prefer:

bash
vix add rix/rix
vix install

and keep the dependency in vix.app:

txt
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:

cpp
#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:

bash
vix add rix/pdf
vix install

In vix.app:

txt
deps = [
  "rix/pdf",
]

Then include:

cpp
#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:

cpp
#include <rix.hpp>

Common mistakes

Using write and expecting a file

This does not create a file:

cpp
auto bytes = rix.pdf.write(doc);

To create a file, use:

cpp
auto saved = rix.pdf.save(doc, "output.pdf");

Using save when you need HTTP bytes

This writes a file:

cpp
rix.pdf.save(doc, "report.pdf");

For an HTTP response, use:

cpp
auto bytes = rix.pdf.write(doc);

Not checking save errors

Wrong:

cpp
rix.pdf.save(doc, "output.pdf");

Better:

cpp
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:

cpp
auto bytes = rix.pdf.write(doc);
auto size = bytes.value().size();

Better:

cpp
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:

cpp
rix.pdf.save(doc, "output/report.pdf");

Create the folder first:

bash
mkdir -p output

Saving to an empty path

This fails:

cpp
rix.pdf.save(doc, "");

Use a real path:

cpp
rix.pdf.save(doc, "output.pdf");

Confusing deps and packages

For a Vix project, do not put Rix packages in packages.

Wrong:

txt
packages = [
  "rix/rix",
]

Correct:

txt
deps = [
  "rix/rix",
]

deps is for Vix Registry packages.

packages is for CMake package discovery.

What you should remember

Use save for files:

cpp
auto saved = rix.pdf.save(doc, "output.pdf");

Use write for bytes:

cpp
auto bytes = rix.pdf.write(doc);

Check save errors:

cpp
if (saved.failed())
{
  rix.debug.eprint(
      "pdf error:",
      rix.pdf.error.to_string(saved.error()),
      saved.error().message());
}

Check write errors:

cpp
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:

cpp
rix.pdf.make_text(
    "hello.pdf",
    "Hello from rix.pdf",
    "Rix PDF");

For a Vix project, install Rix:

bash
vix add rix/rix
vix install

and use:

txt
deps = [
  "rix/rix",
]

Next step

Learn how to handle PDF errors.

Next: Errors

Released under the MIT License.