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.