blob: 4b19df5ba8659338f2068a2498c715ab03649da8 [file]
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef THIRD_PARTY_CREDENTIO_FORMATS_PDF_OBJECTS_H_
#define THIRD_PARTY_CREDENTIO_FORMATS_PDF_OBJECTS_H_
#include <cstdint>
#include <cstdlib>
#include <memory>
#include <optional>
#include <ostream>
#include <sstream>
#include <string>
#include <variant>
#include <vector>
#ifdef __GNUG__
#include <cxxabi.h> // IWYU pragma: keep
#endif
#include "absl/container/flat_hash_map.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/strings/substitute.h"
namespace credentio {
namespace internal {
inline std::string Demangle(const char* mangled) {
#ifdef __GNUG__
int status = 0;
std::unique_ptr<char, void (*)(void*)> res{
abi::__cxa_demangle(mangled, nullptr, nullptr, &status), std::free};
return (status == 0) ? res.get() : mangled;
#else
return mangled;
#endif
}
template <typename T>
std::string Stringify(const T& val) {
std::ostringstream ss;
ss << val;
return ss.str();
}
} // namespace internal
struct Object;
// Represents any PDF object.
// Uses std::variant to hold one of the possible PDF object types.
// See ISO 32000-2 Section 7.3.2 through 7.3.9 for more details.
struct Object {
// Represents a PDF boolean object.
struct Boolean {
bool value;
friend bool operator==(const Boolean&, const Boolean&) = default;
friend std::ostream& operator<<(std::ostream& os, const Boolean& b) {
return os << (b.value ? "true" : "false");
}
};
// Represents a PDF integer object.
struct Integer {
int64_t value;
friend bool operator==(const Integer&, const Integer&) = default;
friend std::ostream& operator<<(std::ostream& os, const Integer& i) {
return os << i.value;
}
};
// Represents a PDF real number object.
struct RealNumber {
std::string raw_value;
friend bool operator==(const RealNumber&, const RealNumber&) = default;
friend std::ostream& operator<<(std::ostream& os, const RealNumber& r) {
return os << r.raw_value;
}
};
// Represents a PDF literal string object.
struct LiteralString {
std::string raw_value;
friend bool operator==(const LiteralString&,
const LiteralString&) = default;
friend std::ostream& operator<<(std::ostream& os, const LiteralString& s) {
return os << "(" << s.raw_value << ")";
}
};
// Represents a PDF hexadecimal string object.
struct HexadecimalString {
std::string raw_value;
friend bool operator==(const HexadecimalString&,
const HexadecimalString&) = default;
friend std::ostream& operator<<(std::ostream& os,
const HexadecimalString& s) {
return os << "<" << s.raw_value << ">";
}
};
// Represents a PDF name object.
struct Name {
std::string value;
friend bool operator==(const Name&, const Name&) = default;
friend std::ostream& operator<<(std::ostream& os, const Name& n) {
return os << "/" << n.value;
}
};
// Represents a PDF array object.
struct Array {
std::vector<Object> objects;
friend bool operator==(const Array&, const Array&);
friend std::ostream& operator<<(std::ostream& os, const Array& a);
};
// Represents a PDF dictionary object.
struct Dictionary {
absl::flat_hash_map<std::string, Object> entries;
friend bool operator==(const Dictionary&, const Dictionary&);
friend std::ostream& operator<<(std::ostream& os, const Dictionary& d);
};
// Represents a PDF stream object.
struct Stream {
std::string raw_value;
friend bool operator==(const Stream&, const Stream&) = default;
friend std::ostream& operator<<(std::ostream& os, const Stream& s) {
return os << "stream\n" << s.raw_value << "\nendstream";
}
};
// Represents a PDF null object.
struct Null {
friend bool operator==(const Null&, const Null&) = default;
friend std::ostream& operator<<(std::ostream& os, const Null& n) {
return os << "null";
}
};
// Represents a reference to a PDF indirect object.
struct IndirectReference {
uint32_t object_number;
uint32_t generation_number;
friend bool operator==(const IndirectReference&,
const IndirectReference&) = default;
friend auto operator<=>(const IndirectReference&,
const IndirectReference&) = default;
friend std::ostream& operator<<(std::ostream& os,
const IndirectReference& r) {
return os << r.object_number << " " << r.generation_number << " R";
}
};
std::variant<Boolean, Integer, RealNumber, LiteralString, HexadecimalString,
Name, Array, Dictionary, Stream, Null, IndirectReference>
value;
friend bool operator==(const Object& lhs, const Object& rhs) {
return lhs.value == rhs.value;
}
friend std::ostream& operator<<(std::ostream& os, const Object& obj) {
std::visit([&os](const auto& alternative) { os << alternative; },
obj.value);
return os;
}
// Returns a copy of the value of type `T` held by `obj.value`. Returns an
// `InvalidArgumentError` if `obj.value` is not of type `T`.
template <typename T>
static absl::StatusOr<T> As(const Object& obj) {
if (!std::holds_alternative<T>(obj.value)) {
constexpr size_t kMaxErrorStringLen = 256;
std::string str = internal::Stringify(obj);
if (str.size() > kMaxErrorStringLen) {
str = absl::StrCat(absl::string_view(str).substr(0, kMaxErrorStringLen),
"...");
}
return absl::InvalidArgumentError(absl::StrCat(
"Object is not a ", internal::Demangle(typeid(T).name()), ": ", str));
}
return std::get<T>(obj.value);
}
};
// Define these after Object is fully defined because they use Object.
inline bool operator==(const Object::Array& lhs, const Object::Array& rhs) {
return lhs.objects == rhs.objects;
}
inline bool operator==(const Object::Dictionary& lhs,
const Object::Dictionary& rhs) {
return lhs.entries == rhs.entries;
}
inline std::ostream& operator<<(std::ostream& os, const Object::Array& a) {
static thread_local int depth = 0;
if (depth >= 100) {
return os << "[...]";
}
++depth;
os << "[";
for (size_t i = 0; i < a.objects.size(); ++i) {
if (i > 0) os << " ";
os << a.objects[i];
}
os << "]";
--depth;
return os;
}
inline std::ostream& operator<<(std::ostream& os, const Object::Dictionary& d) {
static thread_local int depth = 0;
if (depth >= 100) {
return os << "<<...>>";
}
++depth;
os << "<<";
for (const auto& [key, val] : d.entries) {
os << "/" << key << " " << val;
}
os << ">>";
--depth;
return os;
}
// Represents a PDF indirect object.
// See ISO 32000-2 Section 7.3.10 for more details.
struct IndirectObject {
uint32_t object_number;
uint32_t generation_number;
// Required if `object` is a stream.
std::optional<Object::Dictionary> stream_dictionary;
Object object;
friend bool operator==(const IndirectObject&,
const IndirectObject&) = default;
friend std::ostream& operator<<(std::ostream& os, const IndirectObject& obj) {
os << obj.object_number << " " << obj.generation_number << " obj\n";
if (obj.stream_dictionary.has_value()) {
os << *obj.stream_dictionary << "\n";
}
os << obj.object << "\nendobj";
return os;
}
};
template <typename T>
absl::StatusOr<T> FindObjectInDictionary(const Object::Dictionary& dict,
absl::string_view key) {
const auto it = dict.entries.find(key);
if (it == dict.entries.end()) {
return absl::NotFoundError(
absl::Substitute("Dictionary does not contain a '$0' entry.", key));
}
return Object::As<T>(it->second);
}
} // namespace credentio
#endif // THIRD_PARTY_CREDENTIO_FORMATS_PDF_OBJECTS_H_