Hello libart-paperback

This commit is contained in:
2025-09-04 01:50:53 +02:00
commit f09d1d885c
100 changed files with 16611 additions and 0 deletions

17
.editorconfig Normal file
View File

@@ -0,0 +1,17 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
indent_size = 4
max_line_length = off
trim_trailing_whitespace = false
[*.yaml]
indent_size = 2

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
* text=auto

View File

@@ -0,0 +1,30 @@
name: on-push
on:
push:
tags-ignore:
- '*'
branches:
- '**'
jobs:
build-and-test:
runs-on: linux
container: code.helloryan.se/art/infra/buildenv/x86_64-fedora_42-unified:latest
volumes:
- /build
steps:
- name: Configure repository access
run: |
git config --global http.$GITHUB_SERVER_URL/.extraheader "Authorization: token ${{ secrets.ACT_RUNNER_TOKEN }}"
- name: Configure build directory
run: |
bpkg create -d /build cc config.cxx=clang++ config.cc.coptions="-Wall -Werror -Wno-unknown-pragmas -Wno-overloaded-virtual"
- name: Build package
run: |
cd /build
bpkg build --yes --trust-yes $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git##$GITHUB_SHA
- name: Test package
run: |
cd /build
bpkg test libart-paperback

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
patreon: helloryan

35
.gitignore vendored Normal file
View File

@@ -0,0 +1,35 @@
.bdep/
# Local default options files.
#
.build2/local/
# Compiler/linker output.
#
*.d
*.t
*.i
*.i.*
*.ii
*.ii.*
*.o
*.obj
*.gcm
*.pcm
*.ifc
*.so
*.dylib
*.dll
*.a
*.lib
*.exp
*.pdb
*.ilk
*.exe
*.exe.dlls/
*.exe.manifest
*.pc
# Compilation database.
#
compile_commands.json

2878
Doxyfile.in Normal file

File diff suppressed because it is too large Load Diff

31
LICENSE Normal file
View File

@@ -0,0 +1,31 @@
Copyright © 2025 Ryan. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software must
display the following acknowledgement:
This product includes software developed by Ryan, http://helloryan.se/.
4. Neither the name(s) of the copyright holder(s) nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

49
README.md Normal file
View File

@@ -0,0 +1,49 @@
\page readme README
# libart-paperback
libart-paperback is a PDF library for C++.
## Feature Requests
Feature requests can be sent to ryan@helloryan.se.
## Build System
libart-paperback uses the build2 build system for C++. More
information about installing build2 is available at https://build2.org.
This README is primarily intended for developers of libart-paperback.
For instructions on getting started with integrating libart-paperback
into your project, see \ref getting-started.
## Project Structure
The source code for libart-paperback is located at `$src_root$/art/paperback`.
Examples are available in `$src_root$/examples`.
## Documentation
Doxygen is used to generate the API reference documentation for
libart-paperback. If you have Doxygen installed and want to build the
documentation, you can do so by executing `b alias{docs}` in the
package root.
The documentation will be written to `$out_root$/docs`.
The Doxygen configuration is located at `$src_root$/Doxyfile.in`. This
file is preprocessed by build2 before Doxygen is invoked.
## Developer Documentation
The developer documentation can be built by executing
`b alias{docs-private}` in `$src_root$`. This version of the
documentation includes classes defined in .cxx files as well as
private class members.
Developer documentation will be written to the same directory as the
public documentation, i.e. `$out_root$/docs`.
## GitHub Pull Requests
Pull requests on GitHub will definitely be ignored.

9
art/paperback/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
# Generated version header.
#
version.hxx
# Unit test executables and Testscript output directories
# (can be symlinks).
#
*.test
test-*.test

63
art/paperback/buildfile Normal file
View File

@@ -0,0 +1,63 @@
intf_libs = # Interface dependencies.
impl_libs = # Implementation dependencies.
import impl_libs =+ libart-unicode%lib{art-unicode}
./: lib{art-paperback}: libul{art-paperback}
libul{art-paperback}: {hxx ixx txx cxx}{** -**.test... -version} \
{hxx }{ version}
libul{art-paperback}: $impl_libs $intf_libs
test_libs =
import test_libs =+ libart-validation%lib{art-validation}
# Unit tests.
#
exe{*.test}:
{
test = true
install = false
}
for t: cxx{**.test...}
{
d = $directory($t)
n = $name($t)...
./: $d/exe{$n}: $t $d/{hxx ixx txx}{+$n} $test_libs $d/testscript{+$n}
$d/exe{$n}: libul{art-paperback}: bin.whole = false
}
hxx{version}: in{version} $src_root/manifest
# Build options.
#
cxx.poptions =+ "-I$out_root" "-I$src_root"
# Export options.
#
lib{art-paperback}:
{
cxx.export.poptions = "-I$out_root" "-I$src_root"
cxx.export.libs = $intf_libs
}
# For pre-releases use the complete version to make sure they cannot
# be used in place of another pre-release or the final version. See
# the version module for details on the version.* variable values.
#
if $version.pre_release
lib{art-paperback}: bin.lib.version = "-$version.project_id"
else
lib{art-paperback}: bin.lib.version = "-$version.major.$version.minor"
# Install into the art/paperback/ subdirectory of, say, /usr/include/
# recreating subdirectories.
#
{hxx ixx txx}{*}:
{
install = include/art/paperback/
install.subdirs = true
}

View File

@@ -0,0 +1,203 @@
#include <art/paperback/carousel/array.hxx>
#include <art/paperback/carousel/object.hxx>
namespace Art::Paperback::Carousel
{
Array::
Array()
{}
Array::
Array(vector<Object> init)
: _data{init.begin(), init.end()}
{}
Array::
Array(Array const& other)
: _data{other._data}
{}
Array::
Array(Array&& other)
: _data{other._data}
{}
Array::
~Array() noexcept
{}
size_t
Array::
size() const
{
return _data.size();
}
bool
Array::
empty() const
{
return _data.empty();
}
Object&
Array::
front()
{
return _data.front();
}
Object const&
Array::
front() const
{
return _data.front();
}
Object&
Array::
back()
{
return _data.back();
}
Object const&
Array::
back() const
{
return _data.back();
}
Array::iterator
Array::
begin()
{
return _data.begin();
}
Array::const_iterator
Array::
begin() const
{
return _data.begin();
}
Array::const_iterator
Array::
cbegin() const
{
return _data.cbegin();
}
Array::iterator
Array::
end()
{
return _data.end();
}
Array::const_iterator
Array::
end() const
{
return _data.end();
}
Array::const_iterator
Array::
cend() const
{
return _data.cend();
}
Object&
Array::
push_front(Object const& object)
{
_data.push_front(object);
if (auto ptr = owner(); ptr) {
front().attach(*ptr);
ptr->mark_as_modified();
}
return _data.front();
}
Object&
Array::
push_back(Object const& object)
{
_data.push_back(object);
if (auto ptr = owner(); ptr) {
back().attach(*ptr);
ptr->mark_as_modified();
}
return _data.back();
}
void
Array::
pop_front()
{
_data.pop_front();
}
void
Array::
pop_back()
{
_data.pop_back();
}
Array&
Array::
operator=(Array const& other)
{
if (this != &other) {
_data = other._data;
if (auto ptr = owner(); ptr) {
ptr->mark_as_modified();
attach_children(*ptr);
}
}
return *this;
}
Array&
Array::
operator=(Array&& other)
{
return *this = other;
}
bool
Array::
operator==(Array const& other) const
{
return _data == other._data;
}
bool
Array::
operator!=(Array const& other) const
{
return !(*this == other);
}
void
Array::
attach_children(Object_model::Owner& owner)
{
for (auto& j : _data) {
j.attach(owner);
}
}
} // namespace Art::Paperback::Carousel

View File

@@ -0,0 +1,151 @@
#ifndef art__paperback__carousel__array_hxx_
#define art__paperback__carousel__array_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
#include <art/paperback/carousel/object-model.hxx>
namespace Art::Paperback::Carousel
{
class Array
: public Object_model::Value_base
{
public:
/// Iterator type.
///
using iterator = typename deque<Object>::iterator;
/// Immutbale iterator type.
///
using const_iterator = typename deque<Object>::const_iterator;
/// Constructor.
///
Array();
/// Constructor.
///
Array(vector<Object>);
/// Constructor.
///
Array(Array const&);
/// Constructor.
///
Array(Array&&);
/// Destructor.
///
~Array() noexcept;
/// Get the size of the array.
///
size_t
size() const;
/// Check if the array is empty.
///
bool
empty() const;
/// Get a reference to the first element.
///
Object&
front();
/// Get a reference to the first element.
///
Object const&
front() const;
/// Get a reference to the last element.
///
Object&
back();
/// Get a reference to the last element.
///
Object const&
back() const;
/// Get begin iterator.
///
iterator
begin();
/// Get begin iterator.
///
const_iterator
begin() const;
/// Get begin iterator.
///
const_iterator
cbegin() const;
/// Get past-the-end iterator.
///
iterator
end();
/// Get past-the-end iterator.
///
const_iterator
end() const;
/// Get past-the-end iterator.
///
const_iterator
cend() const;
/// Push element to the front of the array.
///
Object&
push_front(Object const&);
/// Push element to the back of the array.
///
Object&
push_back(Object const&);
void
pop_front();
void
pop_back();
/// Assignment.
///
Array&
operator=(Array const&);
/// Assignment.
///
Array&
operator=(Array&&);
/// Comparison.
///
bool
operator==(Array const&) const;
/// Comparison.
///
bool
operator!=(Array const&) const;
protected:
void
attach_children(Object_model::Owner&) override;
private:
deque<Object> _data;
};
} // namespace Art::Paperback::Carousel
#endif

View File

@@ -0,0 +1,72 @@
#include <art/paperback/carousel/boolean.hxx>
namespace Art::Paperback::Carousel
{
Boolean::
Boolean()
{}
Boolean::
Boolean(bool value)
: _data{value}
{}
Boolean::
Boolean(Boolean const& other)
: _data{other._data}
{}
Boolean::
Boolean(Boolean&& other)
: _data{other._data}
{}
Boolean::
~Boolean() noexcept
{}
bool const&
Boolean::
operator*() const
{
return _data;
}
Boolean&
Boolean::
operator=(Boolean const& other)
{
if (this != &other) {
_data = other._data;
if (auto ptr = owner(); ptr) {
ptr->mark_as_modified();
}
}
return *this;
}
Boolean&
Boolean::
operator=(Boolean&& other)
{
return *this = other;
}
bool
Boolean::
operator==(Boolean const& other) const
{
return _data == other._data;
}
bool
Boolean::
operator!=(Boolean const& other) const
{
return !(*this == other);
}
} // namespace Art::Paperback::Carousel

View File

@@ -0,0 +1,70 @@
#ifndef art__paperback__carousel__boolean_hxx_
#define art__paperback__carousel__boolean_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
#include <art/paperback/carousel/object-model.hxx>
namespace Art::Paperback::Carousel
{
/// Represents a COS boolean.
///
class Boolean
: public Object_model::Value_base
{
public:
/// Constructor.
///
Boolean();
/// Constructor.
///
Boolean(bool);
/// Constructor.
///
Boolean(Boolean const&);
/// Constructor.
///
Boolean(Boolean&&);
/// Destructor.
///
~Boolean() noexcept;
/// Access boolean.
///
bool const&
operator*() const;
/// Assignment.
///
Boolean&
operator=(Boolean const&);
/// Assignment.
///
Boolean&
operator=(Boolean&&);
/// Comparison.
///
bool
operator==(Boolean const&) const;
/// Comparison.
///
bool
operator!=(Boolean const&) const;
private:
bool _data{};
};
} // namespace Art::Paperback::Carousel
#endif

View File

@@ -0,0 +1,300 @@
#include <art/paperback/carousel/cross-reference.hxx>
#include <art/paperback/except.hxx>
namespace Art::Paperback::Carousel
{
/// Represents a free entry in the cross-reference table.
///
/// In contrast with used entries, free entries are always written
/// to the cross-reference section of the COS-file.
///
struct cross_Reference::Free_entry
{
/// The next free index in the cross-reference table.
///
Index next{};
/// The generation of the next entry.
///
Generation next_generation{};
};
/// Represents a used entry in the cross-reference table.
///
/// The offset of the object is set during write, and only entries
/// with a set offset will be written to the COS-file
/// cross-reference section.
///
struct cross_Reference::Used_entry
{
/// The offset in the COS-file of this entry.
///
optional<int64_t> offset{};
};
/// \class cross_Reference
///
/// The cross-reference table maintains a link between an object
/// index/generation and its position in the COS-file.
///
/// The paperback implementation of the PDF 1.4 specification
/// does not re-use the index of a previously deleted object, hence
/// the generation of new objects will always be zero (0).
///
/// This constructor creates a new cross-reference table.
///
/// Object 0 will be automatically allocated.
///
cross_Reference::
cross_Reference()
{
// Make sure to allocate the special object 0.
//
_table[Identity{0, 0}] = Free_entry{0, 0xffff};
}
cross_Reference::
cross_Reference(cross_Reference const&) = default;
cross_Reference::
cross_Reference(cross_Reference&&) = default;
cross_Reference::
~cross_Reference() noexcept
{}
/// \return Returns the size of the cross-reference table.
///
uint32_t
cross_Reference::
size() const
{
uint32_t size{};
for (auto const& j : _table) {
if (j.first.index > size) {
size = j.first.index;
}
}
return size + 1;
}
/// \return Returns the identity of the newly allocated object.
///
Identity
cross_Reference::
allocate()
{
auto identity = next();
_table[identity] = Used_entry{nullopt};
return identity;
}
/// \param identity The object identity for which to retrieve the offset.
/// \return Returns the file offset of the object.
///
int64_t
cross_Reference::
get_offset(Identity identity) const
{
#if 0
auto it = _table.find(identity);
if (it == _table.end()) {
return 0;
}
if (it->second.free) {
return 0;
}
return it->second.offset;
#endif
return 0;
}
/// \throw Internal_error Thrown if the object is not allocated.
///
void
cross_Reference::
set_offset(Identity identity, int64_t offset)
{
auto entry = _table.find(identity);
if (entry == _table.end()) {
raise<Internal_error>{} << "invalid object index";
}
if (!holds_alternative<Used_entry>(entry->second)) {
raise<Internal_error>{} << "invalid object index";
}
std::get<Used_entry>(entry->second).offset = offset;
}
/// \return Returns the index of the next available object.
///
Identity
cross_Reference::
next() const
{
Index next{};
for (auto const& j : _table) {
if (next < j.first.index) {
next = j.first.index;
}
}
return Identity{next + 1, 0};
}
/// \param w The COS-file writer.
/// \param update True if this is an update; false otherwise.
///
void
cross_Reference::
write(Writer& w, bool update)
{
w.begin_xref();
// Cross-reference table entry.
//
struct Entry
{
// The index of this entry.
//
Index index{};
// Either the offset for used entries, or the next free index
// for free entries.
//
uint32_t param0{};
// Either the generation for used entries, or the next
// generation for free entries.
//
uint16_t param1{};
// Is this a free entry?
//
bool free{};
};
vector<Entry> entries;
for (auto it = _table.begin(); it != _table.end(); ++it) {
auto v = [&](auto&& entry)
{
using T = std::decay_t<decltype(entry)>;
if constexpr (std::is_same_v<T, Used_entry>) {
// Ignore used entries without an offset. This will be the
// case for allocated objects that hasn't been written out.
//
if (entry.offset == nullopt) {
return;
}
auto index = it->first.index;
auto offset = *entry.offset;
auto generation = it->first.generation;
entries.emplace_back(
index,
offset,
generation,
false
);
}
else if constexpr (std::is_same_v<T, Free_entry>) {
// As we do not support the removal of objects, we only
// write free entries when writing the first cross-reference
// table in a file.
//
if (update) {
return;
}
auto index = it->first.index;
auto next = entry.next;
auto next_generation = entry.next_generation;
entries.emplace_back(
index,
next,
next_generation,
true
);
}
else {
static_assert(false, "non-exhaustive visitor");
}
};
std::visit(v, it->second);
}
// Write the cross-reference table.
//
for (auto it = entries.begin(); it != entries.end();) {
uint32_t start{it->index};
uint32_t count{1};
auto k = it;
auto prev = k++;
while (
k != entries.end() && k->index == prev->index + 1
) {
prev = k++;
++count;
}
w.begin_xref_chunk(start, count);
auto kt = it;
for (uint32_t i{0}; i < count; ++i, ++kt) {
if (kt->free) {
w.write_xref_free_entry(kt->param0, kt->param1);
continue;
}
w.write_xref_used_entry(kt->param0, kt->param1);
}
it = k;
}
}
// This is typically called after the cross-reference table has been
// written out.
//
void
cross_Reference::
clear_offsets()
{
for (auto& j : _table) {
if (holds_alternative<Used_entry>(j.second)) {
std::get<Used_entry>(j.second).offset = nullopt;
}
}
}
cross_Reference&
cross_Reference::
operator=(cross_Reference const&) = default;
cross_Reference&
cross_Reference::
operator=(cross_Reference&&) = default;
} // namespace Art::Paperback::Carousel

View File

@@ -0,0 +1,92 @@
#ifndef art__paperback__carousel__cross_reference_hxx_
#define art__paperback__carousel__cross_reference_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
#include <art/paperback/carousel/writer.hxx>
namespace Art::Paperback::Carousel
{
/// Implements the COS cross-reference table.
///
class cross_Reference
{
public:
/// Constructor.
///
cross_Reference();
/// Constructor.
///
cross_Reference(cross_Reference const&);
/// Constructor.
///
cross_Reference(cross_Reference&&);
/// Destructor.
///
~cross_Reference() noexcept;
/// Get the size of the cross-reference table.
///
uint32_t
size() const;
/// Allocate a new object index.
///
Identity
allocate();
/// Get the offset of a used entry.
///
int64_t
get_offset(Identity) const;
/// Set the offset of a used entry.
///
void
set_offset(Identity, int64_t);
/// Get the next available object identity.
///
Identity
next() const;
/// Write the cross-reference to an output stream.
///
void
write(Writer&, bool);
/// Clears the offsets of used entries.
///
void
clear_offsets();
/// Assignment.
///
cross_Reference&
operator=(cross_Reference const&);
/// Assignment.
///
cross_Reference&
operator=(cross_Reference&&);
private:
struct Free_entry;
struct Used_entry;
using Entry = variant<Free_entry, Used_entry>;
/// Cross-reference table entries.
///
map<Identity, Entry> _table;
};
} // namespace Art::Paperback::Carousel
#endif

View File

@@ -0,0 +1,183 @@
#include <art/paperback/carousel/dictionary.hxx>
#include <art/paperback/carousel/object.hxx>
#include <art/paperback/carousel/undefined.hxx>
namespace Art::Paperback::Carousel
{
Dictionary::
Dictionary()
{}
Dictionary::
Dictionary(map<Name, Object> data)
: _data{std::move(data)}
{}
Dictionary::
Dictionary(Dictionary const& other)
: _data{other._data}
{}
Dictionary::
Dictionary(Dictionary&& other)
: _data{other._data}
{}
Dictionary::
~Dictionary() noexcept
{}
size_t
Dictionary::
size() const
{
return _data.size();
}
bool
Dictionary::
empty() const
{
return _data.empty();
}
bool
Dictionary::
contains(Name const& key)
{
return _data.contains(key);
}
Object&
Dictionary::
at(Name const& key)
{
return _data.at(key);
}
Object const&
Dictionary::
at(Name const& key) const
{
return _data.at(key);
}
Dictionary::iterator
Dictionary::
begin()
{
return _data.begin();
}
Dictionary::const_iterator
Dictionary::
begin() const
{
return _data.begin();
}
Dictionary::const_iterator
Dictionary::
cbegin() const
{
return _data.cbegin();
}
Dictionary::iterator
Dictionary::
end()
{
return _data.end();
}
Dictionary::const_iterator
Dictionary::
end() const
{
return _data.end();
}
Dictionary::const_iterator
Dictionary::
cend() const
{
return _data.cend();
}
void
Dictionary::
insert(Name const& key, Object object)
{
if (is_of_type<Undefined>(object)) {
erase(key);
return;
}
auto [it, _] = _data.insert_or_assign(key, object);
if (auto ptr = owner(); ptr) {
it->second.attach(*ptr);
ptr->mark_as_modified();
}
}
void
Dictionary::
erase(Name const& key)
{
_data.erase(key);
if (auto ptr = owner(); ptr) {
ptr->mark_as_modified();
}
}
Dictionary&
Dictionary::
operator=(Dictionary const& other)
{
if (this != &other) {
_data = other._data;
if (auto ptr = owner(); ptr) {
ptr->mark_as_modified();
attach_children(*ptr);
}
}
return *this;
}
Dictionary&
Dictionary::
operator=(Dictionary&& other)
{
return operator=(other);
}
bool
Dictionary::
operator==(Dictionary const& other) const
{
return _data == other._data;
}
bool
Dictionary::
operator!=(Dictionary const& other) const
{
return !(*this == other);
}
void
Dictionary::
attach_children(Object_model::Owner& owner)
{
for (auto& j : _data) {
j.second.attach(owner);
}
}
} // namespace Art::Paperback::Carousel

View File

@@ -0,0 +1,144 @@
#ifndef art__paperback__carousel__dictionary_hxx_
#define art__paperback__carousel__dictionary_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
#include <art/paperback/carousel/name.hxx>
#include <art/paperback/carousel/object-model.hxx>
namespace Art::Paperback::Carousel
{
/// Represents a COS dictionary.
///
class Dictionary
: public Object_model::Value_base
{
public:
/// Iterator type.
///
using iterator = typename map<Name, Object>::iterator;
/// Immutable iterator type.
///
using const_iterator = typename map<Name, Object>::const_iterator;
/// Constructor.
///
Dictionary();
/// Constructor.
///
explicit
Dictionary(map<Name, Object>);
/// Constructor.
///
Dictionary(Dictionary const&);
/// Constructor.
///
Dictionary(Dictionary&&);
/// Destructor.
///
~Dictionary() noexcept;
/// Get the size of the dictionary.
///
size_t
size() const;
/// Check if the dictionary is empty.
////
bool
empty() const;
/// Check if the dictionary contains a key.
///
bool
contains(Name const&);
/// Access entry.
///
Object&
at(Name const&);
/// Access entry.
///
Object const&
at(Name const&) const;
/// Get begin iterator.
///
iterator
begin();
/// Get begin iterator.
///
const_iterator
begin() const;
/// Get begin iterator.
///
const_iterator
cbegin() const;
/// Get past-the-end iterator.
///
iterator
end();
/// Get past-the-end iterator.
///
const_iterator
end() const;
/// Get past-the-end iterator.
///
const_iterator
cend() const;
/// Insert entry into dictionary.
///
void
insert(Name const&, Object);
/// Remove entry from dictionary.
///
void
erase(Name const&);
/// Assignment.
///
Dictionary&
operator=(Dictionary const&);
/// Assignment.
///
Dictionary&
operator=(Dictionary&&);
/// Comparison.
///
bool
operator==(Dictionary const&) const;
/// Comparison.
///
bool
operator!=(Dictionary const&) const;
protected:
void
attach_children(Object_model::Owner&) override;
private:
map<Name, Object> _data;
};
} // namespace Art::Paperback::Carousel
#endif

View File

@@ -0,0 +1,305 @@
#include <art/paperback/carousel/file.hxx>
#include <art/paperback/carousel/dictionary.hxx>
#include <art/paperback/carousel/integer.hxx>
namespace Art::Paperback::Carousel
{
/// Represents a COS-file revision.
///
struct File::Revision
{
/// Construct a new revision.
///
Revision(int64_t offset, cross_Reference xref)
: offset{offset},
xref{std::move(xref)}
{}
/// The offset of this revision's cross-reference table.
///
int64_t offset{};
/// The cross-reference table for this revision.
///
cross_Reference xref;
};
/// COS-file internals.
///
struct File::Internal
{
/// Constructor.
///
/// This constructor is intended to be used when creating a new
/// COS-file.
///
Internal(Create_new const&,
iostream& ios,
string header,
int major,
int minor)
: ios{ios},
header{std::move(header)},
major{major},
minor{minor}
{}
/// Underlying io stream.
///
iostream& ios;
/// The file header.
///
string header;
/// The major version.
///
int major{};
/// The minor version.
///
int minor{};
/// Previous revisions of this file.
///
/// Revisions are created when writing the file, or when opening
/// an existing file.
///
deque<Revision> revisions;
/// All objects in this file. Populated as objects are created or
/// during file opening.
///
map<Identity, Object_model::Owner> objects;
/// The current revision's cross-reference table.
///
cross_Reference xref;
/// The document catalog, see Section 3.6.1, “Document Catalog” in
/// the PDF-1.4 specification. Allocated on first use or when
/// opening an existing PDF-file.
///
optional<Object> document_catalog;
/// The document information dictionary, see Section 9.2.1,
/// “Document Information Dictionary” in the PDF-1.4
/// specification. Allocated on first use or when opening an
/// existing PDF-file.
///
optional<Object> document_information;
};
/// \class File
///
/// Object generations are not used for new files in the
/// paperback implementation of the COS file format.
///
/// Newly allocated objects are always assigned the next available
/// object index with a generation of 0.
///
/// Note though that paperback io support _enerations when
/// opening an existing PDF file. That is, the index and generation
/// will be used when looking up an object reference.
///
/// Creates a new COS-file.
///
/// The parameters \a major and \a minor correspond to the version
/// of the PDF-document which the file represents.
///
/// \param ios The io stream for this COS-file.
/// \param major The major version of the document.
/// \param minor The minor version of the document.
/// \param header The COS-file header, e.g. \c PDF-1.4.
///
File::
File(Create_new const&,
iostream& ios,
int major,
int minor,
string header)
: internal{
new Internal{create_new, ios, std::move(header), major, minor}
}
{}
/// This destructor will automatically commit the current revision,
/// but any exceptions thrown during write will be ignored.
///
File::
~File() noexcept
{
try {
commit_revision();
}
catch (...) {
// Nothing to do here, so just ignore the exception.
//
}
}
/// \return Returns a reference to the COS-file catalog dictionary.
///
Dictionary&
File::
catalog()
{
if (!internal->document_catalog) {
internal->document_catalog.emplace(create_object<Dictionary>());
}
return object_cast<Dictionary>(*internal->document_catalog);
}
/// \return Returns a reference to the COS-file info dictionary.
///
Dictionary&
File::
info()
{
if (!internal->document_information) {
internal->document_information.emplace(create_object<Dictionary>());
}
return object_cast<Dictionary>(*internal->document_information);
}
/// Calling this function will write all objects in the current
/// revision to the output stream.
///
/// A new revision will be created automatically.
///
/// All modified objects will be marked as clean.
///
void
File::
commit_revision()
{
// Is this an update or a new file?
//
bool const update = internal->revisions.size() > 0;
Writer w{internal->ios, internal->major, internal->minor};
if (!update) {
// Write out header at the start for new files.
//
internal->ios.seekp(0);
w.write_header(internal->header);
}
else {
// Seek to the end for updates.
//
internal->ios.seekp(0, std::ios_base::end);
}
bool objects_written{false};
for (auto& j : internal->objects) {
if (!j.second.modified()) {
continue;
}
objects_written = true;
auto offset = static_cast<int64_t>(internal->ios.tellp());
auto identity = j.first;
internal->xref.set_offset(identity, offset);
w.write_object(identity, j.second.container());
// Clear the modifed mark.
//
j.second.reset();
}
// Skip writing the rest if this is an update and no objects have
// been written out.
//
if (update && !objects_written) {
return;
}
auto xref_offset = internal->ios.tellp();
internal->xref.write(w, update);
Dictionary trailer;
// Size of the cross-reference table. Required.
//
trailer.insert("Size", Integer{internal->xref.size()});
if (internal->document_catalog) {
trailer.insert("Root", *internal->document_catalog);
}
if (internal->document_information) {
trailer.insert("Info", *internal->document_information);
}
// Point to the previous revision, if we have one.
//
if (internal->revisions.size() > 0) {
auto const& prev = internal->revisions.back();
trailer.insert("Prev", Integer{prev.offset});
}
w.write_trailer(trailer);
w.write_eof(xref_offset);
// Add the revision we just wrote.
//
internal->revisions.emplace_back(xref_offset, internal->xref);
// Reset the offsets of the used objects in the cross-reference
// table. This ensures only modified objects will be written to
// the cross-reference section during the next commit.
//
internal->xref.clear_offsets();
// Make sure everything is flushed.
//
internal->ios.flush();
}
Object_model::Container_base&
File::
get_container(Identity const& identity)
{
return internal->objects.at(identity).container();
}
Object_model::Container_base const&
File::
get_container(Identity const& identity) const
{
return internal->objects.at(identity).container();
}
/// \return Returns the allocated index.
///
Identity
File::
allocate()
{
return internal->xref.allocate();
}
/// \param identity The identity of the object.
/// \param container The container of the object.
///
void
File::
insert(Identity const& identity,
shared_ptr<Object_model::Container_base> container)
{
internal->objects.emplace(identity, std::move(container));
}
} // namespace Art::Paperback::Carousel

View File

@@ -0,0 +1,107 @@
#ifndef art__paperback__carousel__file_hxx_
#define art__paperback__carousel__file_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
#include <art/paperback/carousel/array.hxx>
#include <art/paperback/carousel/cross-reference.hxx>
#include <art/paperback/carousel/name.hxx>
#include <art/paperback/carousel/object.hxx>
#include <art/paperback/carousel/writer.hxx>
namespace Art::Paperback::Carousel
{
/// Represents a COS-format file.
///
class File
{
public:
struct Create_new {};
/// Dispatch-tag for creating a new COS-file.
///
static constexpr Create_new create_new{};
/// Constructor.
///
File(Create_new const&, iostream&, int, int, string);
/// Destructor.
///
~File() noexcept;
/// Access the COS-file catalog dictionary object.
///
Dictionary&
catalog();
/// Access the COS-file info dictionary object.
///
Dictionary&
info();
/// Create a new object.
///
template<typename T, typename... Args>
Object
create_object(Args&&... args)
{
auto identity = allocate();
auto container = make_shared<Object_model::Container<T>>(
std::forward<Args>(args)...
);
insert(identity, std::move(container));
return Object{
make_shared<Object_model::Reference>(*this, identity)
};
}
/// Commit the current COS-file revision to the output stream.
///
void
commit_revision();
private:
friend Object_model::Reference;
/// Get container.
///
Object_model::Container_base&
get_container(Identity const&);
/// Get container.
///
Object_model::Container_base const&
get_container(Identity const&) const;
private:
File(File const&) = delete;
File(File&&) = delete;
File& operator=(File const&) = delete;
File& operator=(File&&) = delete;
/// Allocates a new index.
///
Identity
allocate();
/// Inserts an object into the cross-reference table.
///
void
insert(Identity const&, shared_ptr<Object_model::Container_base>);
struct Revision;
struct Internal;
unique_ptr<Internal> internal;
};
} // namespace Art::Paperback::Carousel
#endif

View File

@@ -0,0 +1,72 @@
#include <art/paperback/carousel/integer.hxx>
namespace Art::Paperback::Carousel
{
Integer::
Integer(int64_t value)
: _data{value}
{}
Integer::
Integer(Integer const& other)
: _data{other._data}
{}
Integer::
Integer(Integer&& other)
: _data{other._data}
{}
Integer::
~Integer() noexcept
{}
int64_t const&
Integer::
operator*() const
{
return _data;
}
Integer&
Integer::
operator=(Integer const& other)
{
_data = other._data;
if (auto ptr = owner(); ptr) {
ptr->mark_as_modified();
}
return *this;
}
Integer&
Integer::
operator=(Integer&& other)
{
_data = other._data;
if (auto ptr = owner(); ptr) {
ptr->mark_as_modified();
}
return *this;
}
bool
Integer::
operator==(Integer const& other) const
{
return _data == other._data;
}
bool
Integer::
operator!=(Integer const& other) const
{
return !(*this == other);
}
} // namespace Art::Paperback::Carousel

View File

@@ -0,0 +1,66 @@
#ifndef art__paperback__carousel__integer_hxx_
#define art__paperback__carousel__integer_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
#include <art/paperback/carousel/object-model.hxx>
namespace Art::Paperback::Carousel
{
/// Represents a COS integer.
///
class Integer
: public Object_model::Value_base
{
public:
/// Constructor.
///
Integer(int64_t);
/// Constructor.
///
Integer(Integer const&);
/// Constructor.
///
Integer(Integer&&);
/// Destructor.
///
~Integer() noexcept;
/// Access integer.
///
int64_t const&
operator*() const;
/// Assignment.
///
Integer&
operator=(Integer const&);
/// Assignment.
///
Integer&
operator=(Integer&&);
/// Comparison.
///
bool
operator==(Integer const&) const;
/// Comparison.
///
bool
operator!=(Integer const&) const;
private:
int64_t _data{};
};
} // namespace Art::Paperback::Carousel
#endif

View File

@@ -0,0 +1,112 @@
#include <art/paperback/carousel/name.hxx>
namespace Art::Paperback::Carousel
{
Name::
Name()
{}
Name::
Name(string data)
: _data{std::move(data)}
{}
Name::
Name(char const* data)
: _data{data}
{}
Name::
Name(Name const& other)
: _data{other._data}
{}
Name::
Name(Name&& other)
: _data{std::move(other._data)}
{}
Name::
~Name() noexcept
{}
string const&
Name::
operator*() const
{
return _data;
}
string const*
Name::
operator->() const
{
return &_data;
}
Name&
Name::
operator=(Name const& other)
{
if (this != &other) {
_data = other._data;
if (auto ptr = owner(); ptr) {
ptr->mark_as_modified();
}
}
return *this;
}
Name&
Name::
operator=(Name&& other)
{
return *this = other;
}
bool
Name::
operator==(Name const& other) const
{
return _data == other._data;
}
bool
Name::
operator!=(Name const& other) const
{
return !(*this == other);
}
bool
Name::
operator<(Name const& other) const
{
return _data < other._data;
}
bool
Name::
operator<=(Name const& other) const
{
return _data <= other._data;
}
bool
Name::
operator>(Name const& other) const
{
return _data > other._data;
}
bool
Name::
operator>=(Name const& other) const
{
return _data >= other._data;
}
} // namespace Art::Paperback::Carousel

View File

@@ -0,0 +1,99 @@
#ifndef art__paperback__carousel__name_hxx_
#define art__paperback__carousel__name_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
#include <art/paperback/carousel/object-model.hxx>
namespace Art::Paperback::Carousel
{
/// Represents a COS name.
///
class Name
: public Object_model::Value_base
{
public:
/// Constructor.
///
Name();
/// Constructor.
///
Name(string);
/// Constructor.
///
Name(char const*);
/// Constructor.
///
Name(Name const&);
/// Constructor.
///
Name(Name&&);
/// Destructor.
///
~Name() noexcept;
/// Access string value.
///
string const&
operator*() const;
/// Access string value.
///
string const*
operator->() const;
/// Assignment.
///
Name&
operator=(Name const&);
/// Assignment.
///
Name&
operator=(Name&&);
/// Comparison.
///
bool
operator==(Name const&) const;
/// Comparison.
///
bool
operator!=(Name const&) const;
/// Comparison.
///
bool
operator<(Name const&) const;
/// Comparison.
///
bool
operator<=(Name const&) const;
/// Comparison.
///
bool
operator>(Name const&) const;
/// Comparison.
///
bool
operator>=(Name const&) const;
private:
string _data;
};
} // namespace Art::Paperback::Carousel
#endif

View File

@@ -0,0 +1,214 @@
#include <art/paperback/carousel/object-model.hxx>
#include <art/paperback/carousel/file.hxx>
#include <art/paperback/except.hxx>
namespace Art::Paperback::Carousel::Object_model
{
/// \class Abstract
///
Abstract::
~Abstract() noexcept
{}
Abstract::
Abstract()
{}
/// \class Container_base
///
Container_base::
~Container_base() noexcept
{}
Container_base::
Container_base()
{}
/// \class Reference
///
///
/// \param file The parent file.
/// \param identity The identity of the reference.
///
Reference::
Reference(File& file, Identity identity)
: _file{file},
_identity{identity}
{}
File&
Reference::
file() const
{
return _file;
}
Identity const&
Reference::
identity() const
{
return _identity;
}
std::type_info const&
Reference::
type() const
{
return typeid(Reference);
}
Container_base&
Reference::
container()
{
return file().get_container(identity());
}
Container_base const&
Reference::
container() const
{
return file().get_container(identity());
}
Owner*
Reference::
owner()
{
return nullptr;
}
Owner const*
Reference::
owner() const
{
return nullptr;
}
void
Reference::
attach(Owner& owner)
{
// No-op.
//
}
shared_ptr<Abstract>
Reference::
clone() const
{
return make_shared<Reference>(file(), identity());
}
/// \class Value_base
///
/// \param owner Reference to the owner.
///
void
Value_base::
attach(Owner& owner)
{
if (_owner) {
raise<Internal_error>{} << "object already attached";
}
_owner = &owner;
attach_children(owner);
}
/// \return Returns a pointer to the owner, if attached.
///
Owner*
Value_base::
owner()
{
return _owner;
}
/// \return Returns a pointer to the owner, if attached.
///
Owner const*
Value_base::
owner() const
{
return _owner;
}
Value_base::
Value_base()
{}
Value_base::
~Value_base()
{}
/// \param owner The owner of the children.
///
void
Value_base::
attach_children(Owner& owner)
{}
/// \class Owner
///
/// \param container The object container.
///
Owner::
Owner(shared_ptr<Container_base> container)
: _container{container}
{
if (!_container) {
raise<Internal_error>{} << "container must not be null";
}
_container->attach(*this);
}
Owner::
~Owner() noexcept
{}
Container_base&
Owner::
container()
{
return *_container;
}
Container_base const&
Owner::
container() const
{
return *_container;
}
bool
Owner::
modified() const
{
return _modified;
}
void
Owner::
mark_as_modified()
{
_modified = true;
}
void
Owner::
reset()
{
_modified = false;
}
} // namespace Art::Paperback::Carousel::Object_model

View File

@@ -0,0 +1,394 @@
#ifndef art__paperback__carousel__object_model_hxx_
#define art__paperback__carousel__object_model_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
#include <art/paperback/visitor.hxx>
#include <typeinfo>
namespace Art::Paperback::Carousel::Object_model
{
/// Abstract base class for object tree.
///
class Abstract
{
public:
/// Destructor.
///
virtual
~Abstract() noexcept;
/// Get contained type info.
///
virtual
std::type_info const&
type() const = 0;
/// Get container.
///
virtual
Container_base&
container() = 0;
/// Get container.
///
virtual
Container_base const&
container() const = 0;
/// Access node owner.
///
virtual
Owner*
owner() = 0;
/// Access node owner.
///
virtual
Owner const*
owner() const = 0;
/// Attach node to owner.
///
virtual
void
attach(Owner&) = 0;
/// Clone.
///
virtual
shared_ptr<Abstract>
clone() const = 0;
protected:
/// Constructor.
///
Abstract();
private:
Abstract(Abstract const&) = delete;
Abstract(Abstract&&) = delete;
Abstract& operator=(Abstract const&) = delete;
Abstract& operator=(Abstract&&) = delete;
};
/// Base class for containers.
///
class Container_base
: public Abstract
{
public:
/// Destructor.
///
~Container_base() noexcept override;
/// Get.
///
template<typename T>
T&
get()
{
return dynamic_cast<Container<T>&>(*this).get();
}
/// Get.
///
template<typename T>
T const&
get() const
{
return dynamic_cast<Container<T> const&>(*this).get();
}
virtual
void
assign(Container_base const&) = 0;
virtual
bool
compare(Container_base const&) const = 0;
virtual
void
do_accept(Visitor&) = 0;
virtual
void
do_accept(Visitor&) const = 0;
protected:
/// Constructor.
///
Container_base();
private:
Container_base(Container_base const&) = delete;
Container_base(Container_base&&) = delete;
Container_base& operator=(Container_base const&) = delete;
Container_base& operator=(Container_base&&) = delete;
};
template<typename T>
class Container
: public Container_base,
public std::enable_shared_from_this<Container<T>>
{
public:
template<typename... Args>
explicit
Container(Args&&... args)
: _value{std::forward<Args>(args)...}
{}
Container_base&
container() override
{
return *this;
}
Container_base const&
container() const override
{
return *this;
}
std::type_info const&
type() const override
{
return typeid(T);
}
T&
get()
{
return _value;
}
T const&
get() const
{
return _value;
}
void
assign(Container_base const& other) override
{
_value = other.get<T>();
}
bool
compare(Container_base const& other) const override
{
return _value == other.get<T>();
}
void
do_accept(Visitor& v) override
{
accept(_value, v);
}
void
do_accept(Visitor& v) const override
{
accept(_value, v);
}
Owner*
owner() override
{
return _value.owner();
}
Owner const*
owner() const override
{
return _value.owner();
}
void
attach(Owner& owner) override
{
return _value.attach(owner);
}
shared_ptr<Abstract>
clone() const override
{
return make_shared<Container<T>>(_value);
}
private:
Container(Container<T> const&) = delete;
Container(Container<T>&&) = delete;
Container<T>& operator=(Container<T> const&) = delete;
Container<T>& operator=(Container<T>&&) = delete;
private:
T _value;
};
/// Represents a reference to an indirect object.
///
class Reference
: public Abstract,
public std::enable_shared_from_this<Reference>
{
public:
/// Constructor.
///
Reference(File&, Identity);
/// Access parent file.
///
File&
file() const;
/// Access the identity of this reference.
///
Identity const&
identity() const;
std::type_info const&
type() const override;
Container_base&
container() override;
Container_base const&
container() const override;
Owner*
owner() override;
Owner const*
owner() const override;
void
attach(Owner& owner) override;
/// Clone.
///
shared_ptr<Abstract>
clone() const override;
private:
Reference(Reference const&) = delete;
Reference(Reference&&) = delete;
Reference& operator=(Reference const&) = delete;
Reference& operator=(Reference&&) = delete;
private:
File& _file;
Identity _identity;
};
/// Base class for value types.
///
class Value_base
{
public:
/// Attach value to owner.
///
void
attach(Owner&);
/// Access owner, if any.
///
Owner*
owner();
/// Access owner, if any.
///
Owner const*
owner() const;
protected:
/// Constructor.
///
Value_base();
/// Destructor.
///
~Value_base();
/// Attach any children.
///
virtual
void
attach_children(Owner&);
private:
Value_base(Value_base const&) = delete;
Value_base(Value_base&&) = delete;
Value_base& operator=(Value_base const&) = delete;
Value_base& operator=(Value_base&&) = delete;
private:
Owner* _owner{};
};
/// Owner of an indirect object.
///
class Owner
{
public:
/// Constructor.
///
explicit
Owner(shared_ptr<Container_base>);
/// Destructor.
///
~Owner() noexcept;
/// Access container.
///
Container_base&
container();
/// Access container.
///
Container_base const&
container() const;
/// Check if modified.
///
bool
modified() const;
/// Mark as modified.
///
void
mark_as_modified();
/// Reset modification mark.
///
void
reset();
private:
Owner(Owner const&) = delete;
Owner(Owner&&) = delete;
Owner& operator=(Owner const&) = delete;
Owner& operator=(Owner&&) = delete;
private:
shared_ptr<Container_base> _container;
/// Change tracking.
///
bool _modified{true};
};
} // namespace Art::Paperback::Carousel::Object_model
#endif

View File

@@ -0,0 +1,326 @@
#include <art/paperback/carousel/object.hxx>
#include <art/paperback/except.hxx>
#include <art/paperback/carousel/file.hxx>
#include <art/paperback/carousel/object-model.hxx>
#include <art/paperback/carousel/array.hxx>
#include <art/paperback/carousel/boolean.hxx>
#include <art/paperback/carousel/dictionary.hxx>
#include <art/paperback/carousel/integer.hxx>
#include <art/paperback/carousel/name.hxx>
#include <art/paperback/carousel/real.hxx>
#include <art/paperback/carousel/stream.hxx>
#include <art/paperback/carousel/text.hxx>
#include <art/paperback/carousel/undefined.hxx>
namespace Art::Paperback::Carousel
{
/// \class Object
///
/// The Object class represents a COS-file object. An object can be
/// either a direct object or an indirect object. The eight supported
/// object types are:
///
/// - Boolean values
/// - Integer and real numbers
/// - Strings
/// - Names
/// - Arrays
/// - Dictionaries
/// - Streams
/// - Null (undefined)
///
/// See Section 3.2, "Objects" in the PDF-1.4 specification for
/// further information.
///
/// Instances of Object maintain a pointer to the underlying data.
/// Copying an object creates a new reference to the same underlying
/// data. This is not to be confused with indirect COS objects. See
/// the example below for clarification. Also note, that an object
/// can only have a maximum of one owner, so adding an object to
/// say two different arrays at the same time will result in an
/// exception being thrown.
///
/// \todo Describe assigning to already created objects.
///
/// ```cxx
/// using namespace Art::Paperback::Carousel;
///
/// // Create a direct object of array type.
/// //
/// Object object0{Array{}};
///
/// // Create a new direct object pointing to the same data as object0.
/// //
/// Object object1{object0};
///
/// // Changes made in object0 will be reflected in object1, and vice versa.
/// //
///
/// // If an identitcal copy is desired, the clone function can be used:
/// //
/// Object object2{clone(object0)};
///
/// // Changes made to object2 will not be reflected in object0 nor object1.
/// //
///
/// // Indirect objects work differently. Cloning an indirect object
/// // will clone the reference to the indirect object, not the data
/// // of the object.
/// //
///
/// File file;
///
/// // Create an indirect object of array type.
/// //
/// Object object3{file.create_object<Array>()};
///
/// Object object4{object3};
/// Object object5{clone(object3)};
///
/// // At this point, both object4 and object5 point to the indirect
/// // object first allocated in object3. This includes object5 since
/// // it's a clone of a reference.
/// //
/// ```
///
/// Access to the data of an object is provided by the \ref object_cast
/// function, see the example below:
///
/// ```cxx
/// using namespace Art::Paperback::Carousel;
///
/// Object my_array{Array{}};
///
/// object_cast<Array>(my_array).push_back(Integer{0});
/// ```
///
/// This constructor creates a null (undefined) object.
///
Object::
Object(Undefined const& undefined)
: _data{make_shared<Object_model::Container<Undefined>>(undefined)}
{}
/// This constructor creates an array object.
///
/// \param array The array.
///
Object::
Object(Array array)
: _data{make_shared<Object_model::Container<Array>>(std::move(array))}
{}
/// This constructor creates a boolean object.
///
/// \param boolean The boolean value.
///
Object::
Object(Boolean boolean)
: _data{make_shared<Object_model::Container<Boolean>>(std::move(boolean))}
{}
/// This constructor creates a dictionary object.
///
/// \param dictionary The dictionary.
///
Object::
Object(Dictionary dictionary)
: _data{make_shared<Object_model::Container<Dictionary>>(std::move(dictionary))}
{}
/// This constructor creates an integer object.
///
/// \param integer The integer value.
///
Object::
Object(Integer integer)
: _data{make_shared<Object_model::Container<Integer>>(std::move(integer))}
{}
/// This constructor creates a name object.
///
/// \param name The name.
///
Object::
Object(Name name)
: _data{make_shared<Object_model::Container<Name>>(std::move(name))}
{}
/// This constructor creates a real object.
///
/// \param real The real value.
///
Object::
Object(Real real)
: _data{make_shared<Object_model::Container<Real>>(std::move(real))}
{}
/// This constructor creates a text (string) object.
///
/// \param text The text (string) value.
///
Object::
Object(Text text)
: _data{make_shared<Object_model::Container<Text>>(std::move(text))}
{}
/// \param other The object to copy from.
///
Object::
Object(Object const& other)
: _data{other._data}
{
if (!_data) {
raise<Internal_error>{} << "invalid object data";
}
}
/// This constructor makes a copy of \a other rather than moving
/// from it. This avoids the problem of having invalid-state
/// objects.
///
/// \param other The object to move from.
///
Object::
Object(Object&& other)
: _data{other._data}
{
if (!_data) {
raise<Internal_error>{} << "invalid object data";
}
}
/// \param owner The owner to tie this object with.
///
void
Object::
attach(Object_model::Owner& owner)
{
_data->attach(owner);
}
/// \return Returns true if this object is an indirect reference.
///
bool
Object::
is_reference() const
{
return typeid(Object_model::Reference) == _data->type();
}
/// \throw Internal_error Thrown if the object is not an indirect object.
/// \return Returns the identity of the object, if it is an indirect
/// object.
///
Identity const&
Object::
identity() const
{
if (is_reference()) {
return dynamic_cast<Object_model::Reference const&>(*_data).identity();
}
raise<Internal_error>{} << "object is not a reference";
}
/// \param other The object to assign from.
///
Object&
Object::
operator=(Object const& other)
{
if (this != &other) {
container().assign(other.container());
}
return *this;
}
/// \param other The object to assign from.
///
Object&
Object::
operator=(Object&& other)
{
if (this != &other) {
container().assign(other.container());
}
return *this;
}
/// \param other The object to compare with.
///
bool
Object::
operator==(Object const& other) const
{
return container().compare(other.container());
}
/// \param other The object to compare with.
///
bool
Object::
operator!=(Object const& other) const
{
return !(*this == other);
}
/// This is an internal constructor to create an indirect object.
///
Object::
Object(shared_ptr<Object_model::Abstract> data)
: _data{std::move(data)}
{
if (!_data) {
raise<Internal_error>{} << "data must not be null";
}
}
Object_model::Container_base&
Object::
container()
{
return _data->container();
}
Object_model::Container_base const&
Object::
container() const
{
return _data->container();
}
/// \param object The object to clone.
///
Object
clone(Object& object)
{
return Object{object._data->clone()};
}
/// \param visitee The visitee.
/// \param v The visitor.
///
void
accept(Object& visitee, Visitor& v)
{
visitee.container().do_accept(v);
}
/// \param visitee The visitee.
/// \param v The visitor.
///
void
accept(Object const& visitee, Visitor& v)
{
visitee.container().do_accept(v);
}
} // namespace Art::Paperback::Carousel

View File

@@ -0,0 +1,192 @@
#ifndef art__paperback__carousel__object_hxx_
#define art__paperback__carousel__object_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
#include <art/paperback/visitor.hxx>
#include <art/paperback/carousel/object-model.hxx>
namespace Art::Paperback::Carousel
{
/// Represents a COS-file object.
///
class Object
{
public:
/// Constructor.
///
Object(Undefined const&);
/// Constructor.
///
Object(Array);
/// Constructor.
///
Object(Boolean);
/// Constructor.
///
Object(Dictionary);
/// Constructor.
///
Object(Integer);
/// Constructor.
///
Object(Name);
/// Constructor.
///
Object(Real);
/// Constructor.
///
Object(Text);
/// Constructor.
///
Object(Object const&);
/// Constructor.
///
Object(Object&&);
/// Attach object to owner.
///
void
attach(Object_model::Owner&);
/// Check if the objcet is a reference.
///
bool
is_reference() const;
/// Get object identity, if reference.
///
Identity const&
identity() const;
template<typename>
friend
bool
is_of_type(Object const&);
template<typename T>
friend
T&
object_cast(Object&);
template<typename T>
friend
T const&
object_cast(Object const&);
friend
Object
clone(Object&);
friend
void
accept(Object&, Visitor&);
friend
void
accept(Object const&, Visitor&);
/// Assignment.
///
Object&
operator=(Object const&);
/// Assignment.
///
Object&
operator=(Object&&);
/// Comparison.
///
bool
operator==(Object const&) const;
/// Comparison.
///
bool
operator!=(Object const&) const;
private:
friend File;
friend Writer;
/// Constructor.
///
explicit
Object(shared_ptr<Object_model::Abstract>);
/// Get the container of the object.
///
Object_model::Container_base&
container();
/// Get the container of the object.
///
Object_model::Container_base const&
container() const;
private:
/// Holds the data of the object, either an instance of
/// Object_model::Reference for references, or an instance of
/// Object_model::Container for direct objects.
///
shared_ptr<Object_model::Abstract> _data;
};
/// Check object type.
///
template<typename T>
bool
is_of_type(Object const& object)
{
return typeid(T) == object.container().type();
}
/// This function provides access to the value of an object.
///
template<typename T>
T&
object_cast(Object& object)
{
return object.container().get<T>();
}
/// This function provides access to the value of an object.
///
template<typename T>
T const&
object_cast(Object const& object)
{
return object.container().get<T>();
}
/// Clone an object.
///
Object
clone(Object&);
/// Accept visitor on object.
///
void
accept(Object&, Visitor&);
/// Accept visitor on object.
///
void
accept(Object const&, Visitor&);
} // namespace Art::Paperback::Carousel
#endif

View File

@@ -0,0 +1,75 @@
#include <art/paperback/carousel/object.hxx>
#include <art/paperback/carousel/text.hxx>
#include <art/validation/main.hxx>
using namespace Art::Paperback;
using namespace Art::Paperback::Carousel;
// Test the identity of two objects.
//
VALIDATION_TEST(identity)
{
Object object0{Text{"initial"}};
VALIDATION_ASSERT_EQUAL(
*object_cast<Text>(object0),
"initial"
);
Object object1{object0};
VALIDATION_ASSERT_EQUAL(
object_cast<Text>(object0),
object_cast<Text>(object1)
);
object0 = Text{"changed"};
VALIDATION_ASSERT_EQUAL(
*object_cast<Text>(object0),
"changed"
);
VALIDATION_ASSERT_EQUAL(
object_cast<Text>(object0),
object_cast<Text>(object1)
);
}
// Ensure changing a cloned object does not change the source object.
//
VALIDATION_TEST(cloning)
{
Object object0{Text{"initial"}};
VALIDATION_ASSERT_EQUAL(
*object_cast<Text>(object0),
"initial"
);
Object object1 = clone(object0);
VALIDATION_ASSERT_EQUAL(
object_cast<Text>(object0),
object_cast<Text>(object1)
);
object0 = Text{"changed"};
VALIDATION_ASSERT_EQUAL(
*object_cast<Text>(object0),
"changed"
);
VALIDATION_ASSERT_NOT_EQUAL(
object_cast<Text>(object0),
object_cast<Text>(object1)
);
}
int
main(int argc, char* argv[])
{
return art::validation::main(argc, argv);
}

View File

@@ -0,0 +1,72 @@
#include <art/paperback/carousel/real.hxx>
namespace Art::Paperback::Carousel
{
Real::
Real()
{}
Real::
Real(double value)
: _data{value}
{}
Real::
Real(Real const& other)
: _data{other._data}
{}
Real::
Real(Real&& other)
: _data{other._data}
{}
Real::
~Real() noexcept
{}
double const&
Real::
operator*() const
{
return _data;
}
Real&
Real::
operator=(Real const& other)
{
if (this != &other) {
_data = other._data;
if (auto ptr = owner(); ptr) {
ptr->mark_as_modified();
}
}
return *this;
}
Real&
Real::
operator=(Real&& other)
{
return *this = other;
}
bool
Real::
operator==(Real const& other) const
{
return _data == other._data;
}
bool
Real::
operator!=(Real const& other) const
{
return !(*this == other);
}
} // namespace Art::Paperback::Carousel

View File

@@ -0,0 +1,70 @@
#ifndef art__paperback__carousel__real_hxx_
#define art__paperback__carousel__real_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
#include <art/paperback/carousel/object-model.hxx>
namespace Art::Paperback::Carousel
{
/// Represents a COS real.
///
class Real
: public Object_model::Value_base
{
public:
/// Constructor.
///
Real();
/// Constructor.
///
Real(double);
/// Constructor.
///
Real(Real const&);
/// Constructor.
///
Real(Real&&);
/// Destructor.
///
~Real() noexcept;
/// Access real.
///
double const&
operator*() const;
/// Assignment.
///
Real&
operator=(Real const&);
/// Assignment.
///
Real&
operator=(Real&&);
/// Comparison.
///
bool
operator==(Real const&) const;
/// Comparison.
///
bool
operator!=(Real const&) const;
private:
double _data{};
};
} // namespace Art::Paperback::Carousel
#endif

View File

@@ -0,0 +1,173 @@
#include <art/paperback/carousel/stream.hxx>
#include <art/paperback/carousel/dictionary.hxx>
#include <art/paperback/carousel/integer.hxx>
#include <art/paperback/carousel/name.hxx>
#include <art/paperback/carousel/object.hxx>
namespace Art::Paperback::Carousel
{
struct Stream::Internal
{
Internal()
{}
stringstream data;
};
/// \class Stream
///
/// A stream is a special COS object type containing a data stream
/// of bytes. A stream object is always an indirect object.
///
Stream::
Stream()
: _internal{new Internal{}}
{}
Stream::
Stream(Stream const& other)
: _internal{new Internal{}}
{
_internal->data.str(other._internal->data.str());
}
Stream::
Stream(Stream&& other)
: _internal{new Internal{}}
{
_internal->data.str(other._internal->data.str());
}
Stream::
~Stream() noexcept
{}
void
Stream::
clear()
{
_internal->data.str(string{});
if (auto ptr = owner(); ptr) {
ptr->mark_as_modified();
}
}
Dictionary
Stream::
as_dictionary() const
{
_internal->data.seekg(0, std::ios::end);
auto size = _internal->data.tellg();
Dictionary dictionary;
dictionary.insert(
"Length",
Integer{static_cast<int64_t>(size)}
);
return dictionary;
}
string
Stream::
str() const
{
return _internal->data.str();
}
std::basic_streambuf<char>*
Stream::
rdbuf() const
{
_internal->data.seekg(0);
return _internal->data.rdbuf();
}
iostream*
Stream::
operator->()
{
if (auto ptr = owner(); ptr) {
ptr->mark_as_modified();
}
return &_internal->data;
}
iostream const*
Stream::
operator->() const
{
return &_internal->data;
}
iostream&
Stream::
operator*()
{
if (auto ptr = owner(); ptr) {
ptr->mark_as_modified();
}
return _internal->data;
}
iostream const&
Stream::
operator*() const
{
return _internal->data;
}
Stream&
Stream::
operator=(Stream const& other)
{
if (this != &other) {
_internal->data.str(other._internal->data.str());
if (auto ptr = owner(); ptr) {
ptr->mark_as_modified();
}
}
return *this;
}
Stream&
Stream::
operator=(Stream&& other)
{
return *this = other;
}
bool
Stream::
operator==(Stream const& other) const
{
return _internal->data.str() == other._internal->data.str();
}
bool
Stream::
operator!=(Stream const& other) const
{
return !(*this == other);
}
/// \param object The object containing a stream.
/// \return Returns a reference to the iostream.
///
iostream&
stream_cast(Object& object)
{
return *object_cast<Stream>(object);
}
} // namespace Art::Paperback::Carousel

View File

@@ -0,0 +1,107 @@
#ifndef art__paperback__carousel__stream_hxx_
#define art__paperback__carousel__stream_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
#include <art/paperback/carousel/object-model.hxx>
namespace Art::Paperback::Carousel
{
/// Represents a COS stream.
///
class Stream
: public Object_model::Value_base
{
public:
/// Constructor.
///
Stream();
/// Constructor.
///
Stream(Stream const&);
/// Constructor.
///
Stream(Stream&&);
/// Destructor.
///
~Stream() noexcept;
/// Clear stream.
///
void
clear();
/// Returns the stream metadata as a dictionary.
///
Dictionary
as_dictionary() const;
/// Access the buffer as a string.
///
string
str() const;
/// Access buffer.
///
std::basic_streambuf<char>*
rdbuf() const;
/// Access data stream.
///
iostream*
operator->();
/// Access data stream.
///
iostream const*
operator->() const;
/// Access data stream.
///
iostream&
operator*();
/// Access data stream.
///
iostream const&
operator*() const;
/// Assignment.
///
Stream&
operator=(Stream const&);
/// Assignment.
///
Stream&
operator=(Stream&&);
/// Comparison.
///
bool
operator==(Stream const&) const;
/// Comparison.
///
bool
operator!=(Stream const&) const;
private:
struct Internal;
unique_ptr<Internal> _internal;
};
/// Access stream object.
///
iostream&
stream_cast(Object&);
} // namespace Art::Paperback::Carousel
#endif

View File

@@ -0,0 +1,134 @@
#include <art/paperback/carousel/text.hxx>
namespace Art::Paperback::Carousel
{
/// \class Text
///
/// The Text class does not impose any limit on the length of the
/// text string.
Text::
Text(Convention convention)
: _convention{convention}
{}
Text::
Text(string data, Convention convention)
: _convention{convention},
_data{std::move(data)}
{}
Text::
Text(char const* data, Convention convention)
: _convention{convention},
_data{data}
{}
Text::
Text(Text const& other)
: _data{other._data}
{}
Text::
Text(Text&& other)
: _data{std::move(other._data)}
{}
Text::
~Text() noexcept
{}
Text::Convention
Text::
convention() const
{
return _convention;
}
string const&
Text::
operator*() const
{
return _data;
}
string const*
Text::
operator->() const
{
return &_data;
}
Text&
Text::
operator=(Text const& other)
{
if (this != &other) {
_convention = other._convention;
_data = other._data;
if (auto ptr = owner(); ptr) {
ptr->mark_as_modified();
}
}
return *this;
}
Text&
Text::
operator=(Text&& other)
{
return *this = other;
}
/// This function only compares the string value of \c this with \a
/// other. The text convention used by either string is ignored.
///
bool
Text::
operator==(Text const& other) const
{
return _data == other._data;
}
/// This function only compares the string value of \c this with \a
/// other. The text convention used by either string is ignored.
///
bool
Text::
operator!=(Text const& other) const
{
return !(*this == other);
}
bool
Text::
operator<(Text const& other) const
{
return _data < other._data;
}
bool
Text::
operator<=(Text const& other) const
{
return _data <= other._data;
}
bool
Text::
operator>(Text const& other) const
{
return _data > other._data;
}
bool
Text::
operator>=(Text const& other) const
{
return _data >= other._data;
}
} // namespace Art::Paperback::Carousel

View File

@@ -0,0 +1,131 @@
#ifndef art__paperback__carousel__text_hxx_
#define art__paperback__carousel__text_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
#include <art/paperback/carousel/object-model.hxx>
namespace Art::Paperback::Carousel
{
/// Represents COS text.
///
class Text
: public Object_model::Value_base
{
public:
/// Enumeration of text conventions.
///
/// See Section 3.2.3, "String Objects" in the PDF-1.4 specification
/// for details about text strings and their representation.
///
enum class Convention
{
/// Indicates the literal convention. When used, the text string
/// will be written verbatim to the output file, enclosed in
/// parentheses.
///
/// Special characters will be written as escape-sequences.
///
literal,
/// Indicates the hexadecimal convention. When used, the text
/// string will be written as a sequence of hexadecimal digits,
/// enclosed within angle brackets.
///
/// The size of the written data will be double the length of
/// the text string when this convention is used.
///
hexadecimal
};
/// Constructor.
///
Text(Convention = Convention::literal);
/// Constructor.
///
Text(string, Convention = Convention::literal);
/// Constructor.
///
Text(char const*, Convention = Convention::literal);
/// Constructor.
///
Text(Text const&);
/// Constructor.
///
Text(Text&&);
/// Destructor.
///
~Text() noexcept;
/// Get the text convention used for this object.
///
Convention
convention() const;
/// Access text string.
///
string const&
operator*() const;
/// Access text string.
///
string const*
operator->() const;
/// Assignment.
///
Text&
operator=(Text const&);
/// Assignment.
///
Text&
operator=(Text&&);
/// Comparison.
///
bool
operator==(Text const&) const;
/// Comparison.
///
bool
operator!=(Text const&) const;
/// Comparison.
///
bool
operator<(Text const&) const;
/// Comparison.
///
bool
operator<=(Text const&) const;
/// Comparison.
///
bool
operator>(Text const&) const;
/// Comparison.
///
bool
operator>=(Text const&) const;
private:
Convention _convention;
string _data;
};
} // namespace Art::Paperback::Carousel
#endif

View File

@@ -0,0 +1,49 @@
#include <art/paperback/carousel/undefined.hxx>
namespace Art::Paperback::Carousel
{
Undefined::
Undefined()
{}
Undefined::
Undefined(Undefined const&)
{}
Undefined::
Undefined(Undefined&&)
{}
Undefined::
~Undefined() noexcept
{}
Undefined&
Undefined::
operator=(Undefined const&)
{
return *this;
}
Undefined&
Undefined::
operator=(Undefined&&)
{
return *this;
}
bool
Undefined::
operator==(Undefined const&) const
{
return true;
}
bool
Undefined::
operator!=(Undefined const&) const
{
return false;
}
} // namespace Art::Paperback::Carousel

View File

@@ -0,0 +1,56 @@
#ifndef art__paperback__carousel__undefined_hxx_
#define art__paperback__carousel__undefined_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
#include <art/paperback/carousel/object-model.hxx>
namespace Art::Paperback::Carousel
{
class Undefined
: public Object_model::Value_base
{
public:
/// Constructor.
///
Undefined();
/// Constructor.
///
Undefined(Undefined const&);
/// Constructor.
///
Undefined(Undefined&&);
/// Destructor.
///
~Undefined() noexcept;
/// Assignment.
///
Undefined&
operator=(Undefined const&);
/// Assignment.
///
Undefined&
operator=(Undefined&&);
/// Comparison.
///
bool
operator==(Undefined const&) const;
/// Comparison.
///
bool
operator!=(Undefined const&) const;
};
} // namespace Art::Paperback::Carousel
#endif

View File

@@ -0,0 +1,487 @@
#include <art/paperback/carousel/writer.hxx>
#include <art/paperback/carousel/object.hxx>
#include <art/paperback/carousel/array.hxx>
#include <art/paperback/carousel/boolean.hxx>
#include <art/paperback/carousel/dictionary.hxx>
#include <art/paperback/carousel/integer.hxx>
#include <art/paperback/carousel/name.hxx>
#include <art/paperback/carousel/real.hxx>
#include <art/paperback/carousel/stream.hxx>
#include <art/paperback/carousel/text.hxx>
#include <art/paperback/except.hxx>
#include <art/unicode/writer.hxx>
#include <art/unicode/utf8-encoder.hxx>
#include <iomanip>
namespace Art::Paperback::Carousel
{
/// \param output The output stream to write to.
/// \param major The major version of the document to write.
/// \param minor The minor version of the document to write.
///
Writer::
Writer(ostream& output, int major, int minor)
: _output{output},
_major{major},
_minor{minor}
{}
/// \return Returns a reference to the underlying output stream.
///
ostream&
Writer::
output()
{
return _output;
}
/// This function will automatically write a binary comment immediately
/// following the COS-file header, as per the recommendation of the PDF-1.4
/// specification.
///
/// \param header The COS-file header.
///
void
Writer::
write_header(string const& header)
{
output() << '%' << header << "\r\n" << "%\x80\xff\x80\xff\r\n";
}
/// \param identity The identity of the object.
/// \param container The object container.
///
void
Writer::
write_object(Identity const& identity,
Object_model::Container_base const& container)
{
output() << identity.index << ' '
<< identity.generation << " obj\r\n";
dispatch_emit(container);
if (container.type() == typeid(Stream)) {
auto str = container.get<Stream>().str();
output() << "\r\nstream\r\n";
output().write(str.c_str(), str.size());
output() << "\r\nendstream";
}
output() << "\r\nendobj\r\n";
}
void
Writer::
begin_xref()
{
output() << "xref\r\n";
}
/// \param index The first index of this chunk.
/// \param count The size of this chunk.
///
void
Writer::
begin_xref_chunk(Index index, uint16_t count)
{
output() << index << ' ' << count << "\r\n";
}
/// \param offset The offset of the entry.
/// \param generation The generation of the entry.
///
void
Writer::
write_xref_used_entry(streamoff offset, Generation generation)
{
using std::setfill;
using std::setw;
std::ostringstream str;
str << setw(10) << setfill('0') << offset << ' ';
str << setw(5) << setfill('0') << generation << " n\r\n";
output() << str.str();
}
/// \param next The index of the next entry.
/// \param generation The next generation number of the entry.
///
void
Writer::
write_xref_free_entry(Index next, Generation generation)
{
using std::setfill;
using std::setw;
std::ostringstream str;
str << setw(10) << setfill('0') << next << ' ';
str << setw(5) << setfill('0') << generation << " f\r\n";
output() << str.str();
}
/// \param dictionary The trailer dictionary.
///
void
Writer::
write_trailer(Dictionary const& dictionary)
{
output() << "trailer\r\n";
emit(dictionary);
output() << "\r\n";
}
/// \param xref_offset The offset of the cross-reference table.
///
void
Writer::
write_eof(streamoff xref_offset)
{
output() << "startxref\r\n";
output() << xref_offset << "\r\n";
output() << "%%EOF\r\n";
}
/// \param index The object reference index.
/// \param generation The object reference generation.
///
void
Writer::
emit_reference(Identity const& identity)
{
output() << identity.index << ' ' << identity.generation << ' ' << 'R';
}
void
Writer::
emit(Undefined const&)
{
output() << "null";
}
/// \param boolean The boolean to write.
///
void
Writer::
emit(Boolean const& boolean)
{
output() << (*boolean ? "true" : "false");
}
/// \param integer The integer to write.
///
void
Writer::
emit(Integer const& integer)
{
output() << *integer;
}
/// \param real The real to write.
///
void
Writer::
emit(Real const& real)
{
stringstream str;
str.imbue(std::locale::classic());
str << std::fixed << *real;
output() << str.str();
}
/// \param text The text to write.
///
void
Writer::
emit(Text const& text)
{
auto to_hex = [](char c) -> char
{
return (c < 10) ? ('0' + c) : ('a' + (c - 10));
};
switch (text.convention()) {
case Text::Convention::literal: {
output() << '(';
for (auto const& j : *text) {
switch (j) {
case '\n':
output() << "\\n";
break;
case '\r':
output() << "\\r";
break;
case '\t':
output() << "\\t";
break;
case '\b':
output() << "\\b";
break;
case '\f':
output() << "\\f";
break;
case '(':
output() << "\\(";
break;
case ')':
output() << "\\)";
break;
case '\\':
output() << "\\\\";
break;
default:
output() << j;
break;
}
}
output() << ')';
return;
}
case Text::Convention::hexadecimal: {
output() << '<';
for (auto const& j : *text) {
char high = (j & 0xF0) >> 4;
char low = (j & 0x0F);
output() << to_hex(high) << to_hex(low);
}
output() << '>';
return;
}
}
raise<Internal_error>{} << "unhandled text convention";
}
void
Writer::
emit(Name const& name)
{
if (_major == 1 && _minor < 2) {
output() << *name;
return;
}
auto to_hex = [](char c) -> char
{
return (c < 10) ? ('0' + c) : ('a' + (c - 10));
};
output() << '/';
for (auto const& j : *name) {
if (j == '#') {
output() << "#23";
}
else if (33 <= j && j <= 126) {
output() << j;
}
else {
char high = (j & 0xF0) >> 4;
char low = (j & 0x0F);
output() << "#" << to_hex(high) << to_hex(low);
}
}
}
void
Writer::
emit(Array const& array)
{
output() << "[ ";
auto it = array.begin();
if (it == array.end()) {
output() << "]";
return;
}
auto base_indent = _indent;
_indent += 1;
dispatch_emit(*it);
++it;
for (; it != array.end(); ++it) {
new_line();
output() << ' ';
dispatch_emit(*it);
}
_indent = base_indent;
new_line();
output() << ']';
}
void
Writer::
emit(Dictionary const& dictionary)
{
output() << "<< ";
auto it = dictionary.begin();
if (it == dictionary.end()) {
output() << ">>";
return;
}
auto base_indent = _indent;
emit(it->first);
output() << ' ';
_indent = base_indent + it->first->length() + 5;
dispatch_emit(it->second);
++it;
_indent = base_indent;
new_line();
for (; it != dictionary.end(); ++it) {
output() << " ";
emit(it->first);
output() << " ";
_indent = base_indent + it->first->length() + 5;
dispatch_emit(it->second);
_indent = base_indent;
new_line();
}
_indent = base_indent;
output() << ">>";
}
void
Writer::
dispatch_emit(Object const& object)
{
// If the object is an indirect object we emit a reference (N N R)
// instead of the object value.
//
if (object.is_reference()) {
emit_reference(object.identity());
return;
}
dispatch_emit(object.container());
}
void
Writer::
dispatch_emit(Object_model::Container_base const& container)
{
// Else emit the object directly.
//
struct dispatcher : Visitor,
Basic_visitor<Undefined >,
Basic_visitor<Boolean>,
Basic_visitor<Integer>,
Basic_visitor<Real>,
Basic_visitor<Text>,
Basic_visitor<Name>,
Basic_visitor<Array>,
Basic_visitor<Dictionary>,
Basic_visitor<Stream> {
Writer& w;
explicit
dispatcher(Writer& w)
: w{w}
{}
void
visit(Undefined const& v) override
{
w.emit(v);
}
void
visit(Boolean const& v) override
{
w.emit(v);
}
void
visit(Integer const& v) override
{
w.emit(v);
}
void
visit(Real const& v) override
{
w.emit(v);
}
void
visit(Text const& v) override
{
w.emit(v);
}
void
visit(Name const& v) override
{
w.emit(v);
}
void
visit(Array const& v) override
{
w.emit(v);
}
void
visit(Dictionary const& v) override
{
w.emit(v);
}
void
visit(Stream const& v) override
{
w.emit(v.as_dictionary());
}
};
dispatcher d{*this};
container.do_accept(d);
}
void
Writer::
new_line()
{
output() << "\r\n" << std::string(_indent, ' ');
}
} // namespace Art::Paperback::Carousel

View File

@@ -0,0 +1,142 @@
#ifndef art__paperback__carousel__writer_hxx_
#define art__paperback__carousel__writer_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
#include <art/paperback/carousel/object-model.hxx>
namespace Art::Paperback::Carousel
{
class Writer
{
public:
/// Constructor.
///
Writer(ostream&, int, int);
/// Access the underlying output stream.
///
ostream&
output();
/// Write a COS-file header.
///
void
write_header(string const&);
/// Write an object.
///
void
write_object(Identity const&, Object_model::Container_base const&);
/// Begin a new cross-reference table.
///
void
begin_xref();
/// Begin a new cross-reference table chunk.
///
void
begin_xref_chunk(Index, uint16_t);
/// Write a used entry.
///
void
write_xref_used_entry(streamoff, Generation);
/// Write a free entry.
///
void
write_xref_free_entry(Index, Generation);
/// Write a COS-file trailer.
///
void
write_trailer(Dictionary const&);
/// Write an EOF marker.
///
void
write_eof(streamoff);
private:
/// Emit a reference to an indirect object.
///
void
emit_reference(Identity const&);
/// Emit undefined.
///
void
emit(Undefined const&);
/// Emit a boolean object.
///
void
emit(Boolean const&);
/// Emit an integer object.
///
void
emit(Integer const&);
/// Emit a real object.
///
void
emit(Real const&);
/// Emit a text object.
///
void
emit(Text const&);
/// Emit a name object.
///
void
emit(Name const&);
/// Emit an array object.
///
void
emit(Array const&);
/// Emit a dictionary object.
///
void
emit(Dictionary const&);
/// Dispatch the emission of an object.
///
void
dispatch_emit(Object const&);
/// Dispatch the emission of an object.
///
void
dispatch_emit(Object_model::Container_base const&);
/// Write a new line marker.
///
void
new_line();
private:
Writer(Writer const&) = delete;
Writer(Writer&&) = delete;
Writer& operator=(Writer const&) = delete;
Writer& operator=(Writer&&) = delete;
private:
ostream& _output;
int _major{};
int _minor{};
string::size_type _indent{};
};
} // namespace Art::Paperback::Carousel
#endif

View File

@@ -0,0 +1,229 @@
#include <art/paperback/document-information.hxx>
#include <art/paperback/document.hxx>
#include <art/paperback/except.hxx>
#include <art/paperback/carousel/dictionary.hxx>
#include <art/paperback/carousel/file.hxx>
#include <art/paperback/carousel/name.hxx>
#include <art/paperback/carousel/object.hxx>
#include <art/paperback/carousel/text.hxx>
namespace Art::Paperback
{
/// Holds document information internal data.
///
struct Document_information::Internal
{
Internal(Document& document)
: document{document},
data{document.file().info()}
{}
Document& document;
Carousel::Dictionary& data;
};
Document_information::
Document_information(Create_new const&,
Document& document)
: internal{new Internal{document}}
{}
Document_information::
~Document_information() noexcept
{}
Document&
Document_information::
document()
{
return internal->document;
}
Document const&
Document_information::
document() const
{
return internal->document;
}
/// Returns the title of the document, if available.
///
std::optional<std::string>
Document_information::
title() const
{
document().check_minimum_version(1, 1);
if (internal->data.contains("Title")) {
return *object_cast<Carousel::Text>(internal->data.at("Title"));
}
return nullopt;
}
/// If \a value is \c nullopt the title is removed.
///
/// \param value The new title.
///
void
Document_information::
set_title(std::optional<std::string> value)
{
document().check_minimum_version(1, 1);
if (value) {
internal->data.insert("Title", Carousel::Text{*value});
}
else {
internal->data.erase("Title");
}
}
std::optional<std::string>
Document_information::
author() const
{
if (internal->data.contains("Author")) {
return *object_cast<Carousel::Text>(internal->data.at("Author"));
}
return nullopt;
}
/// If \a value is \c nullopt the author is removed.
///
/// \param value The new author.
///
void
Document_information::
set_author(std::optional<std::string> value)
{
if (value) {
internal->data.insert("Author", Carousel::Text{*value});
}
else {
internal->data.erase("Author");
}
}
std::optional<std::string>
Document_information::
subject() const
{
document().check_minimum_version(1, 1);
if (internal->data.contains("Subject")) {
return *object_cast<Carousel::Text>(internal->data.at("Subject"));
}
return nullopt;
}
/// If \a value is \c nullopt the subject is removed.
///
/// \param value The new subject.
///
void
Document_information::
set_subject(std::optional<std::string> value)
{
document().check_minimum_version(1, 1);
if (value) {
internal->data.insert("Subject", Carousel::Text{*value});
}
else {
internal->data.erase("Subject");
}
}
std::optional<std::string>
Document_information::
keywords() const
{
document().check_minimum_version(1, 1);
if (internal->data.contains("Keywords")) {
return *object_cast<Carousel::Text>(internal->data.at("Keywords"));
}
return nullopt;
}
/// If \a value is \c nullopt the keywords are removed.
///
/// \param value The new keywords.
///
void
Document_information::
set_keywords(std::optional<std::string> value)
{
document().check_minimum_version(1, 1);
if (value) {
internal->data.insert("Keywords", Carousel::Text{*value});
}
else {
internal->data.erase("Keywords");
}
}
std::optional<std::string>
Document_information::
creator() const
{
if (internal->data.contains("Creator")) {
return *object_cast<Carousel::Text>(internal->data.at("Creator"));
}
return nullopt;
}
/// If \a value is \c nullopt the creator is removed.
///
/// \param value The new creator.
///
void
Document_information::
set_creator(std::optional<std::string> value)
{
if (value) {
internal->data.insert("Creator", Carousel::Text{*value});
}
else {
internal->data.erase("Creator");
}
}
std::optional<std::string>
Document_information::
producer() const
{
if (internal->data.contains("Producer")) {
return *object_cast<Carousel::Text>(internal->data.at("Producer"));
}
return nullopt;
}
/// If \a value is \c nullopt the producer is removed.
///
/// \param value The new producer.
///
void
Document_information::
set_producer(std::optional<std::string> value)
{
if (value) {
internal->data.insert("Producer", Carousel::Text{*value});
}
else {
internal->data.erase("Producer");
}
}
} // namespace Art::Paperback

View File

@@ -0,0 +1,108 @@
#ifndef art__paperback__document_information_hxx_
#define art__paperback__document_information_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
namespace Art::Paperback
{
class Document_information
{
public:
struct Create_new {};
static constexpr Create_new const create_new{};
Document_information(Create_new const&, Document&);
~Document_information() noexcept;
/// Access the owning document.
///
Document&
document();
/// Access the owning document.
///
Document const&
document() const;
/// Get the object for the document information.
///
Carousel::Object
object();
/// Access the title.
///
optional<string>
title() const;
/// Set or remove the title.
///
void
set_title(optional<string>);
/// Access the author.
///
optional<string>
author() const;
/// Set or remove the author.
///
void
set_author(optional<string>);
/// Access the subject.
///
optional<string>
subject() const;
/// Set or remove the subject.
///
void
set_subject(optional<string>);
/// Access the keywords.
///
optional<string>
keywords() const;
/// Set or remove the keywords.
///
void
set_keywords(optional<string>);
/// Access the creator.
///
optional<string>
creator() const;
/// Set or remove the creator.
///
void
set_creator(optional<string>);
/// Access the producer.
///
optional<string>
producer() const;
/// Set or remove the producer.
///
void
set_producer(optional<string>);
private:
Document_information(Document_information const&) = delete;
Document_information(Document_information&&) = delete;
Document_information& operator=(Document_information const&) = delete;
Document_information& operator=(Document_information&&) = delete;
struct Internal;
unique_ptr<Internal> internal;
};
} // namespace Art::Paperback
#endif

141
art/paperback/document.cxx Normal file
View File

@@ -0,0 +1,141 @@
#include <art/paperback/document.hxx>
#include <art/paperback/except.hxx>
#include <art/paperback/document-information.hxx>
#include <art/paperback/internals/document-catalog.hxx>
#include <art/paperback/carousel/file.hxx>
#include <sstream>
namespace Art::Paperback
{
/// Helper to create a PDF-file header.
///
static
string
make_header(int major, int minor)
{
std::stringstream str;
str << "PDF-" << major << '.' << minor;
return str.str();
}
struct Document::Internal
{
Internal(iostream& ios, int major, int minor)
: file{
Carousel::File::create_new,
ios,
major,
minor,
make_header(major, minor),
},
major{major},
minor{minor}
{}
Carousel::File file;
int major{};
int minor{};
optional<Internals::Document_catalog> catalog;
optional<Document_information> info;
};
Document::
Document(Create_new const&, iostream& ios, int major, int minor)
: internal{new Internal{ios, major, minor}}
{
if (major < 1 || 1 < major) {
throw invalid_argument{"unsupported major version"};
}
if (minor < 1 || 4 < minor) {
throw invalid_argument{"unsupported minor version"};
}
}
Document::
~Document()
{
file().commit_revision();
}
int
Document::
major() const
{
return internal->major;
}
int
Document::
minor() const
{
return internal->minor;
}
/// \param major The minimum required major version.
/// \param minor The minimum required minor version.
/// \throw Upgrade_required Thrown if the document does not meet
/// the specified version requirements.
void
Document::
check_minimum_version(int major, int minor) const
{
if (major > this->major()) {
raise<Upgrade_required>{} << "document version upgrade required";
}
if (minor > this->minor()) {
raise<Upgrade_required>{} << "document version upgrade required";
}
}
Document_information&
Document::
information()
{
if (!internal->info) {
internal->info.emplace(Document_information::create_new, *this);
}
return *internal->info;
}
/// \param properties The properties of the new page.
///
Page&
Document::
create_page(Page::Properties const& properties)
{
if (!internal->catalog) {
internal->catalog.emplace(
Internals::Document_catalog::create_new,
*this
);
}
return internal->catalog->pages().create_page(properties);
}
Carousel::File&
Document::
file()
{
return internal->file;
}
void
Document::
flush()
{
internal->file.commit_revision();
}
} // namespace Art::Paperback

View File

@@ -0,0 +1,77 @@
#ifndef art__paperback__document_hxx_
#define art__paperback__document_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
#include <art/paperback/page.hxx>
namespace Art::Paperback
{
class Document
{
public:
struct Create_new {};
/// Tag used to indicate the creation of a new document.
///
static constexpr Create_new const create_new{};
/// Constructor.
///
Document(Create_new const&, std::iostream&, int, int);
/// Destructor.
///
~Document();
/// Get document major version.
///
int
major() const;
/// Get document minor version.
///
int
minor() const;
/// Check that the document meets minimum version requirements.
///
void
check_minimum_version(int, int) const;
/// Get document information.
///
Document_information&
information();
/// Create new page.
///
Page&
create_page(Page::Properties const&);
/// Access the underlying COS file.
///
Carousel::File&
file();
/// Flush current document state to output stream.
///
void
flush();
private:
Document(Document const&) = delete;
Document(Document&&) = delete;
Document& operator=(Document const&) = delete;
Document& operator=(Document&&) = delete;
private:
struct Internal;
std::unique_ptr<Internal> internal;
};
} // namespace Art::Paperback
#endif

117
art/paperback/except.hxx Normal file
View File

@@ -0,0 +1,117 @@
#ifndef art__paperback__exception_hxx_
#define art__paperback__exception_hxx_
#include <art/paperback/types.hxx>
#include <source_location>
namespace Art::Paperback
{
/// Base class for errors.
///
class Fault
: public runtime_error
{
public:
/// Constructor.
///
/// \param origin The C++ source origin of the exception.
/// \param what A description of the error.
///
Fault(std::source_location origin, string what)
: runtime_error{std::move(what)},
_origin{std::move(origin)}
{}
/// Access the C++ source origin of the exception.
///
std::source_location const&
origin() const;
private:
std::source_location _origin;
};
/// Exception class used to indicate internal errors, typically the result
/// of an insect hiding somewhere. If found, please be so kind and squash it
/// mercilessly.
///
class Internal_error
: public Fault
{
public:
using Fault::Fault;
};
/// Exception class used to indicate too low document version.
///
class Upgrade_required
: public Fault
{
public:
using Fault::Fault;
};
/// Exception class used to indicate an invalid operation.
///
class Invalid_operation
: public Fault
{
public:
using Fault::Fault;
};
/// Helper to throw exceptions.
///
template<typename T>
class raise
{
public:
raise(std::source_location origin = std::source_location::current())
: _origin{origin}
{}
/// Copy-construction is prohibited.
///
raise(raise const&) = delete;
/// Move-construction is prohibited.
///
raise(raise&&) = delete;
[[noreturn]]
~raise() noexcept(false)
{
throw T{_origin, _str.str()};
}
/// Copy-assignment is prohibited.
///
raise& operator=(raise const&) = delete;
/// Move-assignment is prohibited.
///
raise& operator=(raise&&) = delete;
template<typename U>
raise<T>&
operator<<(U const& other)
{
_str << other;
return *this;
}
private:
std::source_location _origin;
stringstream _str;
};
} // namespace Art::Paperback
#endif

122
art/paperback/forward.hxx Normal file
View File

@@ -0,0 +1,122 @@
#ifndef art__paperback__forward_hxx_
#define art__paperback__forward_hxx_
/// Primary paperback namespace.
///
namespace Art::Paperback
{
class Visitor;
template<typename>
class Basic_visitor;
/// COS file format implementation namespace.
///
namespace Carousel
{
class Object;
template<typename>
bool
is_of_type(Object const&);
template<typename T>
T&
object_cast(Object&);
template<typename T>
T const&
object_cast(Object const&);
Object
clone(Object&);
void
accept(Object&, Visitor&);
void
accept(Object const&, Visitor&);
class Undefined;
class Array;
class Boolean;
class Dictionary;
class Integer;
class Name;
class Real;
class Text;
class Stream;
class Cross_reference;
class Writer;
class File;
/// COS object model namespace.
///
/// This namespace contains classes implementing the COS object
/// model (values, references, containers and owners).
///
namespace Object_model
{
class abstract_value;
template<typename>
class Value;
class Abstract;
class Reference;
class Owner;
class Container_base;
template<typename>
class Container;
} // namespace Object_model
} // namespace Carousel
/// Internal namespace.
///
/// This namespace contains internal classes and functions.
///
namespace Internals
{
class Document_catalog;
class Font_collection;
class Page_tree;
class Resource_collection;
} // namespace Internals
/// Graphics namespace.
///
/// Text, font and graphics related classes.
///
namespace Graphics
{
class Canvas;
class Font;
class Standard_font;
} // namespace Graphics
class Document_information;
class Document;
class Page;
class Rectangle;
} // namespace Art::Paperback
#endif

View File

@@ -0,0 +1,462 @@
#include <art/paperback/graphics/canvas.hxx>
#include <art/paperback/graphics/font.hxx>
#include <art/paperback/except.hxx>
#include <art/paperback/page.hxx>
#include <art/paperback/carousel/name.hxx>
#include <art/paperback/carousel/object.hxx>
#include <art/paperback/carousel/stream.hxx>
#include <art/paperback/internals/cp1252.hxx>
#include <art/paperback/internals/graphics-state.hxx>
#include <art/paperback/internals/resource-collection.hxx>
namespace Art::Paperback::Graphics
{
/// Holds internal canvas data.
///
struct Canvas::Internal
{
Internal(Page& page)
: page{page},
contents{page.contents()}
{
gstates.push(Internals::Graphics_state{});
}
/// Write single byte to content stream.
///
void
write(char c)
{
contents->write(&c, 1);
}
/// Write string to content stream.
///
void
write(string const& str)
{
contents->write(str.c_str(), str.size());
}
/// Write real value to stream.
///
void
write(double v)
{
stringstream str;
str.imbue(std::locale::classic());
str << std::fixed << v;
write(str.str());
}
/// Write integer value to stream.
///
void
write(long long int v)
{
stringstream str;
str.imbue(std::locale::classic());
str << v;
write(str.str());
}
/// Write name to content stream.
///
void
write_name(string const& n)
{
write('/');
write(n);
}
/// Write encoded text string to content stream.
///
/// The text is hexadecimally coded.
///
void
write_encode_text(string const& text)
{
auto to_hex = [](char c) -> char
{
return (c < 10) ? ('0' + c) : ('a' + (c - 10));
};
auto encoded = Internals::cp1252::from_utf8(text);
write('<');
for (auto const& j : encoded) {
char high = (j & 0xF0) >> 4;
char low = (j & 0x0F);
write(to_hex(high));
write(to_hex(low));
}
write('>');
}
/// Reference to parent page.
///
Page& page;
/// Canvas content stream.
///
Carousel::Stream& contents;
/// Holds the graphics states.
///
stack<Internals::Graphics_state> gstates;
};
/// \param page The parent page of this canvas.
///
Canvas::
Canvas(Clear const&, Page& page)
: internal{new Internal{page}}
{
content().clear();
}
Canvas::
~Canvas()
{}
Page&
Canvas::
page()
{
return internal->page;
}
Page const&
Canvas::
page() const
{
return internal->page;
}
Carousel::Stream&
Canvas::
content()
{
return internal->contents;
}
Carousel::Stream const&
Canvas::
content() const
{
return internal->contents;
}
/// \param grey_level The new grey level.
///
void
Canvas::
set_stroke(double grey_level)
{
if (grey_level < 0 || 1 < grey_level) {
throw std::out_of_range{"grey level out of range (0-1)"};
}
internal->write(grey_level);
internal->write("G\r\n");
internal->gstates.top().cs_stroke = Color_space::device_grey;
internal->gstates.top().grey_stroke = grey_level;
}
/// \param grey_level The new grey level.
///
void
Canvas::
set_fill(double grey_level)
{
if (grey_level < 0 || 1 < grey_level) {
throw std::out_of_range{"grey level out of range (0-1)"};
}
internal->write(grey_level);
internal->write(' ');
internal->write("g\r\n");
internal->gstates.top().cs_fill = Color_space::device_grey;
internal->gstates.top().grey_fill = grey_level;
}
/// \param color The new RGB color.
///
void
Canvas::
set_stroke(RGB const& color)
{
internal->write(color.red());
internal->write(' ');
internal->write(color.green());
internal->write(' ');
internal->write(color.blue());
internal->write(' ');
internal->write("RG\r\n");
internal->gstates.top().cs_stroke = Color_space::device_rgb;
internal->gstates.top().rgb_stroke = color;
}
/// \param color The new RGB color.
///
void
Canvas::
set_fill(RGB const& color)
{
internal->write(color.red());
internal->write(' ');
internal->write(color.green());
internal->write(' ');
internal->write(color.blue());
internal->write(' ');
internal->write("rg\r\n");
internal->gstates.top().cs_fill = Color_space::device_rgb;
internal->gstates.top().rgb_fill = color;
}
/// \param text The text for which to compute the width.
///
double
Canvas::
get_text_width(string const& text) const
{
auto& gstate = internal->gstates.top();
auto& font = *gstate.current_font;
auto font_size = gstate.font_size;
auto tw = font.get_text_width(text);
auto width = static_cast<double>(tw.width) * font_size / 1000;
// FIXME: the missing functions mentioned below.
//
width += tw.character_count * 0; // gstate.get_text_character_spacing();
width += tw.space_count * 0; // gstate.get_text_word_spacing();
return width;
}
/// \param canvas The parent canvas.
///
Canvas::Save::
Save(Canvas& canvas)
: _canvas{canvas}
{
canvas.internal->gstates.push(canvas.internal->gstates.top());
}
Canvas::Save::
~Save() noexcept
{
canvas().internal->gstates.pop();
}
Canvas&
Canvas::Save::
canvas()
{
return _canvas;
}
Canvas const&
Canvas::Save::
canvas() const
{
return _canvas;
}
/// \param canvas The parent canvas.
/// \param mode The paint mode.
///
Canvas::Path::
Path(Canvas& canvas, Paint_mode mode)
: _canvas{canvas},
_mode{mode}
{}
Canvas::Path::
~Path() noexcept
{
canvas().internal->write("h\r\n");
switch (_mode) {
case stroke:
canvas().internal->write("S\r\n");
break;
case fill:
canvas().internal->write("R\r\n");
break;
case fill_then_stroke:
canvas().internal->write("B\r\n");
break;
}
}
/// \return Returns a reference to the parent canvas.
///
Canvas&
Canvas::Path::
canvas()
{
return _canvas;
}
/// \return Returns a reference to the parent canvas.
///
Canvas const&
Canvas::Path::
canvas() const
{
return _canvas;
}
void
Canvas::Path::
move_to(double x, double y)
{
canvas().internal->write(x);
canvas().internal->write(' ');
canvas().internal->write(y);
canvas().internal->write(" m\r\n");
}
void
Canvas::Path::
line_to(double x, double y)
{
canvas().internal->write(x);
canvas().internal->write(' ');
canvas().internal->write(y);
canvas().internal->write(" l\r\n");
}
void
Canvas::Path::
bezier_curve_to(double x1,
double y1,
double x2,
double y2,
double x3,
double y3)
{
canvas().internal->write(x1);
canvas().internal->write(' ');
canvas().internal->write(y1);
canvas().internal->write(' ');
canvas().internal->write(x2);
canvas().internal->write(' ');
canvas().internal->write(y2);
canvas().internal->write(' ');
canvas().internal->write(x3);
canvas().internal->write(' ');
canvas().internal->write(y3);
canvas().internal->write(" c\r\n");
}
/// \param canvas The parent canvas.
///
Canvas::Begin_text::
Begin_text(Canvas& canvas)
: _canvas{canvas}
{
canvas.internal->write("BT\r\n");
}
Canvas::Begin_text::
~Begin_text() noexcept
{
canvas().internal->write("ET\r\n");
}
Canvas&
Canvas::Begin_text::
canvas()
{
return _canvas;
}
Canvas const&
Canvas::Begin_text::
canvas() const
{
return _canvas;
}
void
Canvas::Begin_text::
move_text_pos(double x, double y)
{
canvas().internal->write(x);
canvas().internal->write(' ');
canvas().internal->write(y);
canvas().internal->write(" Td\r\n");
}
void
Canvas::Begin_text::
show_text(string const& text)
{
canvas().internal->write_encode_text(text);
canvas().internal->write(" Tj\r\n");
}
/// \param canvas The parent canvas.
/// \param f The new font.
/// \param size The font size.
/// \throw Invalid_operation Thrown if a font is already set.
///
Canvas::Set_font::
Set_font(Canvas& canvas, Font& f, double size)
: _canvas{canvas}
{
if (canvas.internal->gstates.top().current_font) {
raise<Invalid_operation>{} << "font already set";
}
auto local_name = canvas.page().resources().fonts().embed(f);
canvas.internal->write_name(*local_name);
canvas.internal->write(' ');
canvas.internal->write(size);
canvas.internal->write(" Tf ");
canvas.internal->gstates.top().current_font = &f;
canvas.internal->gstates.top().font_size = size;
}
/// The current font will be resent during deconstruction.
///
Canvas::Set_font::
~Set_font() noexcept
{
canvas().internal->gstates.top().current_font = nullptr;
}
Canvas&
Canvas::Set_font::
canvas()
{
return _canvas;
}
Canvas const&
Canvas::Set_font::
canvas() const
{
return _canvas;
}
} // namespace Art::Paperback::Graphics

View File

@@ -0,0 +1,274 @@
#ifndef art__paperback__graphics__canvas_hxx_
#define art__paperback__graphics__canvas_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
#include <art/paperback/graphics/color.hxx>
namespace Art::Paperback::Graphics
{
/// Represents the drawable canvas of a page.
///
class Canvas
{
public:
struct Clear {};
/// Dispatch-tag used to clear a canvas.
///
static constexpr Clear const clear{};
/// Constructor.
///
Canvas(Clear const&, Page&);
/// Destructor.
///
~Canvas();
/// Access the parent page.
///
Page&
page();
/// Access the parent page.
///
Page const&
page() const;
/// Access the content stream.
///
Carousel::Stream&
content();
/// Access the content stream.
///
Carousel::Stream const&
content() const;
class Save;
friend Save;
/// Set stroke greyscale value.
///
void
set_stroke(double);
/// Set fill greyscale value.
///
void
set_fill(double);
/// Set stroke RGB color.
///
void
set_stroke(RGB const&);
/// Set fill RGB color.
///
void
set_fill(RGB const&);
class Path;
friend Path;
class Begin_text;
friend Begin_text;
class Set_font;
friend Set_font;
/// Compute text width.
///
double
get_text_width(string const&) const;
private:
Canvas(Canvas const&) = delete;
Canvas(Canvas&&) = delete;
Canvas& operator=(Canvas const&) = delete;
Canvas& operator=(Canvas&&) = delete;
private:
struct Internal;
unique_ptr<Internal> internal;
};
/// Saves graphics state.
///
class Canvas::Save
{
public:
/// Constructor.
///
explicit
Save(Canvas&);
/// Destructor.
///
~Save() noexcept;
/// Access parent canvas.
///
Canvas&
canvas();
/// Access parent canvas.
Canvas const&
canvas() const;
private:
Save(Save const&) = delete;
Save(Save&&) = delete;
Save& operator=(Save const&) = delete;
Save& operator=(Save&&) = delete;
private:
Canvas& _canvas;
};
/// Path construction class.
///
/// \todo Add example.
///
class Canvas::Path
{
public:
/// Paint mode enumeration.
///
enum Paint_mode
{
/// Stroke.
///
stroke,
/// Fill.
///
fill,
/// Fill then stroke.
///
fill_then_stroke
};
/// Constructor.
///
Path(Canvas&, Paint_mode);
/// Destructor.
///
~Path() noexcept;
/// Access parent canvas.
///
Canvas&
canvas();
/// Access parent canvas.
Canvas const&
canvas() const;
/// Move starting point.
///
void
move_to(double, double);
/// Draw line.
///
void
line_to(double, double);
/// Draw bezier curve.
///
void
bezier_curve_to(double,
double,
double,
double,
double,
double);
private:
Path(Path const&) = delete;
Path(Path&&) = delete;
Path& operator=(Path const&) = delete;
Path& operator=(Path&&) = delete;
private:
Canvas& _canvas;
Paint_mode _mode;
};
class Canvas::Begin_text
{
public:
explicit
Begin_text(Canvas&);
Begin_text(Begin_text const&) = delete;
Begin_text(Begin_text&&) = delete;
~Begin_text() noexcept;
Canvas&
canvas();
Canvas const&
canvas() const;
void
move_text_pos(double, double);
void
show_text(string const&);
Begin_text&
operator=(Begin_text const&) = delete;
Begin_text&
operator=(Begin_text&&) = delete;
private:
Canvas& _canvas;
};
class Canvas::Set_font
{
public:
Set_font(Canvas&, Font&, double);
Set_font(Set_font const&) = delete;
Set_font(Set_font&&) = delete;
~Set_font() noexcept;
Canvas&
canvas();
Canvas const&
canvas() const;
Set_font&
operator=(Set_font const&) = delete;
Set_font&
operator=(Set_font&&) = delete;
private:
Canvas& _canvas;
};
} // namespace Art::Paperback::Graphics
#endif

View File

@@ -0,0 +1,91 @@
#include <art/paperback/graphics/color.hxx>
namespace Art::Paperback::Graphics
{
RGB::
RGB()
{}
/// \param red The red component.
/// \param green The green component.
/// \param blue The blue component.
RGB::
RGB(double red, double green, double blue)
: _red{red},
_green{green},
_blue{blue}
{}
/// \param other The RGB to copy from.
///
RGB::
RGB(RGB const& other)
: _red{other._red},
_green{other._green},
_blue{other._blue}
{}
/// \param other The RGB to move from.
///
RGB::
RGB(RGB&& other)
: _red{other._red},
_green{other._green},
_blue{other._blue}
{}
/// \return Returns the red component.
///
double
RGB::
red() const
{
return _red;
}
/// \return Returns the green component.
///
double
RGB::
green() const
{
return _green;
}
/// \return Returns the blue component.
///
double
RGB::
blue() const
{
return _blue;
}
/// \param other The RGB to copy from.
///
RGB&
RGB::
operator=(RGB const& other)
{
_red = other._red;
_green = other._green;
_blue = other._blue;
return *this;
}
/// \param other The RGB to move from.
///
RGB&
RGB::
operator=(RGB&& other)
{
_red = other._red;
_green = other._green;
_blue = other._blue;
return *this;
}
} // namespace Art::Paperback::Graphics

View File

@@ -0,0 +1,78 @@
#ifndef art__paperback__graphics__color_hxx_
#define art__paperback__graphics__color_hxx_
#include <art/paperback/types.hxx>
namespace Art::Paperback::Graphics
{
/// PDF color space enumeration.
///
enum class Color_space
{
/// Device grey.
///
device_grey,
/// Device RGB.
///
device_rgb
};
/// Represents an RGB color value.
///
class RGB
{
public:
/// Constructor.
///
RGB();
/// Constructor.
///
RGB(RGB const&);
/// Constructor.
///
RGB(RGB&&);
/// Constructor.
///
RGB(double, double, double);
/// Access red component.
///
double
red() const;
/// Access green component.
///
double
green() const;
/// Access blue component.
///
double
blue() const;
/// Assignment.
///
RGB&
operator=(RGB const&);
/// Assignment.
///
RGB&
operator=(RGB&&);
private:
double _red{};
double _green{};
double _blue{};
};
} // namespace Art::Paperback::Graphics
#endif

View File

@@ -0,0 +1,107 @@
#ifndef art__paperback__graphics__font_hxx_
#define art__paperback__graphics__font_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
namespace Art::Paperback::Graphics
{
/// Represents text width computations.
///
struct Text_width
{
/// Character count.
///
uint32_t character_count{};
/// Space count.
///
uint32_t space_count{};
/// Text width.
///
double width{};
};
/// Base class for fonts.
///
class Font
{
public:
/// Destructor.
///
virtual
~Font() noexcept
{}
/// Access the parent document of this font.
///
virtual
Document&
document() = 0;
/// Get the COS object for this font.
///
virtual
Carousel::Object
object() = 0;
/// Get the name of the font.
///
/// \return Returns the name of the font.
///
virtual
string
name() const = 0;
/// Get the font ascent.
///
/// \return Returns the font ascent.
///
virtual
int16_t
get_ascent() const = 0;
/// Get the font descent.
///
virtual
int16_t
get_descent() const = 0;
/// Get the font X-height.
///
virtual
uint16_t
get_xheight() const = 0;
/// Get the font cap-height.
///
virtual
uint16_t
get_capheight() const = 0;
/// Compute text width.
///
virtual
Text_width
get_text_width(string const&) = 0;
protected:
/// Constructor.
///
Font()
{}
private:
Font(Font const&) = delete;
Font(Font&&) = delete;
Font& operator=(Font const&) = delete;
Font& operator=(Font&&) = delete;
};
} // namespace Art::Paperback::Graphics
#endif

View File

@@ -0,0 +1,158 @@
#include <art/paperback/graphics/standard-font.hxx>
#include <art/paperback/document.hxx>
#include <art/paperback/carousel/dictionary.hxx>
#include <art/paperback/carousel/file.hxx>
#include <art/paperback/carousel/object.hxx>
#include <art/paperback/internals/base14-fontdata.hxx>
#include <art/unicode/reader.hxx>
#include <art/unicode/utf8-decoder.hxx>
namespace Art::Paperback::Graphics
{
struct Standard_font::Internal
{
Internal(Document& document)
: document{document},
object{document.file().create_object<Carousel::Dictionary>()},
data{object_cast<Carousel::Dictionary>(object)}
{}
Document& document;
Carousel::Object object;
Carousel::Dictionary& data;
string name;
Internals::Base14_font_data const* font_data;
};
/// \class Standard_font
///
/// This class represents one of the 14 standard fonts supported by
/// the PDF-specification.
///
/// \param document A reference to the parent document.
/// \param base_font The name of the base font.
///
Standard_font::
Standard_font(Document& document, string const& base_font)
: internal{new Internal{document}}
{
internal->data.insert("Type", Carousel::Name{"Font"});
internal->data.insert("Subtype", Carousel::Name{"Type1"});
internal->data.insert("BaseFont", Carousel::Name{base_font});
internal->data.insert("Encoding", Carousel::Name{"WinAnsiEncoding"});
internal->name = base_font;
auto font_data = Internals::base14_fonts.find(base_font);
if (font_data == Internals::base14_fonts.end()) {
throw std::invalid_argument{"invalid base font"};
}
internal->font_data = &font_data->second;
}
Standard_font::
~Standard_font() noexcept
{}
Document&
Standard_font::
document()
{
return internal->document;
}
Carousel::Object
Standard_font::
object()
{
return internal->object;
}
string
Standard_font::
name() const
{
return internal->name;
}
/// \return Returns the font ascent.
///
int16_t
Standard_font::
get_ascent() const
{
return internal->font_data->ascent;
}
/// \return Returns the font descent.
///
int16_t
Standard_font::
get_descent() const
{
return internal->font_data->descent;
}
/// \return Returns the font X-height.
///
uint16_t
Standard_font::
get_xheight() const
{
return internal->font_data->x_height;
}
/// \return Returns the font Cap-height.
///
uint16_t
Standard_font::
get_capheight() const
{
return internal->font_data->cap_height;
}
/// \a text is assumed to be encoded in UTF-8 and will be
/// decoded as such during text width computation.
///
/// \param text The text for which to compute metrics.
///
Text_width
Standard_font::
get_text_width(string const& text)
{
Text_width tw{};
art::unicode::iterator_reader_t reader{text.begin(), text.end()};
art::unicode::utf8_decoder_t decoder{reader};
for (auto it = decoder.begin(); it != decoder.end(); ++it) {
auto const c = *it;
if (c == ' ')
++tw.space_count;
++tw.character_count;
for (auto const& j : internal->font_data->cdata) {
if (c == j.unicode) {
tw.width += j.width;
break;
}
}
}
return tw;
}
} // namespace Art::Paperback::Graphics

View File

@@ -0,0 +1,64 @@
#ifndef art__paperback__graphics__standard_font_hxx_
#define art__paperback__graphics__standard_font_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
#include <art/paperback/graphics/font.hxx>
namespace Art::Paperback::Graphics
{
/// Represents a standard PDF font.
///
class Standard_font
: public Font
{
public:
/// Constructor.
///
Standard_font(Document&, string const&);
/// Destructor.
///
~Standard_font() noexcept override;
Document&
document() override;
Carousel::Object
object() override;
string
name() const override;
int16_t
get_ascent() const override;
int16_t
get_descent() const override;
uint16_t
get_xheight() const override;
uint16_t
get_capheight() const override;
Text_width
get_text_width(string const&) override;
private:
Standard_font(Standard_font const&) = delete;
Standard_font(Standard_font&&) = delete;
Standard_font& operator=(Standard_font const&) = delete;
Standard_font& operator=(Standard_font&&) = delete;
private:
struct Internal;
unique_ptr<Internal> internal;
};
} // namespace Art::Paperback::Graphics
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,64 @@
#ifndef art__paperback__internals__base14_fontdata_hxx_
#define art__paperback__internals__base14_fontdata_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/primitives.hxx>
namespace Art::Paperback::Internals
{
/// Base font character data.
///
struct Base14_character_data
{
int16_t char_cd;
/// Unicode code point.
///
uint16_t unicode;
double width;
};
/// Represents font data for one of the 14 standard PDF fonts.
///
struct Base14_font_data
{
/// Font character data.
///
vector<Base14_character_data> const& cdata;
bool is_font_specific;
/// Font ascent.
///
int16_t ascent;
/// Font descent.
///
int16_t descent;
/// Font X-height.
///
uint16_t x_height;
/// Font Cap-height.
///
uint16_t cap_height;
/// Font bounding box.
///
Rectangle bbox;
};
/// Map of base fonts.
///
extern
map<string, Base14_font_data> const
base14_fonts;
} // namespace Art::Paperback::Internals
#endif

View File

@@ -0,0 +1,533 @@
#include <art/paperback/internals/cp1252.hxx>
namespace Art::Paperback::Internals::cp1252
{
char
from_unicode(uint32_t unicode)
{
switch (unicode) {
case 0x0000:
return static_cast<char>(0x00);
case 0x0001:
return static_cast<char>(0x01);
case 0x0002:
return static_cast<char>(0x02);
case 0x0003:
return static_cast<char>(0x03);
case 0x0004:
return static_cast<char>(0x04);
case 0x0005:
return static_cast<char>(0x05);
case 0x0006:
return static_cast<char>(0x06);
case 0x0007:
return static_cast<char>(0x07);
case 0x0008:
return static_cast<char>(0x08);
case 0x0009:
return static_cast<char>(0x09);
case 0x000a:
return static_cast<char>(0x0a);
case 0x000b:
return static_cast<char>(0x0b);
case 0x000c:
return static_cast<char>(0x0c);
case 0x000d:
return static_cast<char>(0x0d);
case 0x000e:
return static_cast<char>(0x0e);
case 0x000f:
return static_cast<char>(0x0f);
case 0x0010:
return static_cast<char>(0x10);
case 0x0011:
return static_cast<char>(0x11);
case 0x0012:
return static_cast<char>(0x12);
case 0x0013:
return static_cast<char>(0x13);
case 0x0014:
return static_cast<char>(0x14);
case 0x0015:
return static_cast<char>(0x15);
case 0x0016:
return static_cast<char>(0x16);
case 0x0017:
return static_cast<char>(0x17);
case 0x0018:
return static_cast<char>(0x18);
case 0x0019:
return static_cast<char>(0x19);
case 0x001a:
return static_cast<char>(0x1a);
case 0x001b:
return static_cast<char>(0x1b);
case 0x001c:
return static_cast<char>(0x1c);
case 0x001d:
return static_cast<char>(0x1d);
case 0x001e:
return static_cast<char>(0x1e);
case 0x001f:
return static_cast<char>(0x1f);
case 0x0020:
return static_cast<char>(0x20);
case 0x0021:
return static_cast<char>(0x21);
case 0x0022:
return static_cast<char>(0x22);
case 0x0023:
return static_cast<char>(0x23);
case 0x0024:
return static_cast<char>(0x24);
case 0x0025:
return static_cast<char>(0x25);
case 0x0026:
return static_cast<char>(0x26);
case 0x0027:
return static_cast<char>(0x27);
case 0x0028:
return static_cast<char>(0x28);
case 0x0029:
return static_cast<char>(0x29);
case 0x002a:
return static_cast<char>(0x2a);
case 0x002b:
return static_cast<char>(0x2b);
case 0x002c:
return static_cast<char>(0x2c);
case 0x002d:
return static_cast<char>(0x2d);
case 0x002e:
return static_cast<char>(0x2e);
case 0x002f:
return static_cast<char>(0x2f);
case 0x0030:
return static_cast<char>(0x30);
case 0x0031:
return static_cast<char>(0x31);
case 0x0032:
return static_cast<char>(0x32);
case 0x0033:
return static_cast<char>(0x33);
case 0x0034:
return static_cast<char>(0x34);
case 0x0035:
return static_cast<char>(0x35);
case 0x0036:
return static_cast<char>(0x36);
case 0x0037:
return static_cast<char>(0x37);
case 0x0038:
return static_cast<char>(0x38);
case 0x0039:
return static_cast<char>(0x39);
case 0x003a:
return static_cast<char>(0x3a);
case 0x003b:
return static_cast<char>(0x3b);
case 0x003c:
return static_cast<char>(0x3c);
case 0x003d:
return static_cast<char>(0x3d);
case 0x003e:
return static_cast<char>(0x3e);
case 0x003f:
return static_cast<char>(0x3f);
case 0x0040:
return static_cast<char>(0x40);
case 0x0041:
return static_cast<char>(0x41);
case 0x0042:
return static_cast<char>(0x42);
case 0x0043:
return static_cast<char>(0x43);
case 0x0044:
return static_cast<char>(0x44);
case 0x0045:
return static_cast<char>(0x45);
case 0x0046:
return static_cast<char>(0x46);
case 0x0047:
return static_cast<char>(0x47);
case 0x0048:
return static_cast<char>(0x48);
case 0x0049:
return static_cast<char>(0x49);
case 0x004a:
return static_cast<char>(0x4a);
case 0x004b:
return static_cast<char>(0x4b);
case 0x004c:
return static_cast<char>(0x4c);
case 0x004d:
return static_cast<char>(0x4d);
case 0x004e:
return static_cast<char>(0x4e);
case 0x004f:
return static_cast<char>(0x4f);
case 0x0050:
return static_cast<char>(0x50);
case 0x0051:
return static_cast<char>(0x51);
case 0x0052:
return static_cast<char>(0x52);
case 0x0053:
return static_cast<char>(0x53);
case 0x0054:
return static_cast<char>(0x54);
case 0x0055:
return static_cast<char>(0x55);
case 0x0056:
return static_cast<char>(0x56);
case 0x0057:
return static_cast<char>(0x57);
case 0x0058:
return static_cast<char>(0x58);
case 0x0059:
return static_cast<char>(0x59);
case 0x005a:
return static_cast<char>(0x5a);
case 0x005b:
return static_cast<char>(0x5b);
case 0x005c:
return static_cast<char>(0x5c);
case 0x005d:
return static_cast<char>(0x5d);
case 0x005e:
return static_cast<char>(0x5e);
case 0x005f:
return static_cast<char>(0x5f);
case 0x0060:
return static_cast<char>(0x60);
case 0x0061:
return static_cast<char>(0x61);
case 0x0062:
return static_cast<char>(0x62);
case 0x0063:
return static_cast<char>(0x63);
case 0x0064:
return static_cast<char>(0x64);
case 0x0065:
return static_cast<char>(0x65);
case 0x0066:
return static_cast<char>(0x66);
case 0x0067:
return static_cast<char>(0x67);
case 0x0068:
return static_cast<char>(0x68);
case 0x0069:
return static_cast<char>(0x69);
case 0x006a:
return static_cast<char>(0x6a);
case 0x006b:
return static_cast<char>(0x6b);
case 0x006c:
return static_cast<char>(0x6c);
case 0x006d:
return static_cast<char>(0x6d);
case 0x006e:
return static_cast<char>(0x6e);
case 0x006f:
return static_cast<char>(0x6f);
case 0x0070:
return static_cast<char>(0x70);
case 0x0071:
return static_cast<char>(0x71);
case 0x0072:
return static_cast<char>(0x72);
case 0x0073:
return static_cast<char>(0x73);
case 0x0074:
return static_cast<char>(0x74);
case 0x0075:
return static_cast<char>(0x75);
case 0x0076:
return static_cast<char>(0x76);
case 0x0077:
return static_cast<char>(0x77);
case 0x0078:
return static_cast<char>(0x78);
case 0x0079:
return static_cast<char>(0x79);
case 0x007a:
return static_cast<char>(0x7a);
case 0x007b:
return static_cast<char>(0x7b);
case 0x007c:
return static_cast<char>(0x7c);
case 0x007d:
return static_cast<char>(0x7d);
case 0x007e:
return static_cast<char>(0x7e);
case 0x007f:
return static_cast<char>(0x7f);
case 0x20ac:
return static_cast<char>(0x80);
case 0x0081:
return static_cast<char>(0x81);
case 0x201a:
return static_cast<char>(0x82);
case 0x0192:
return static_cast<char>(0x83);
case 0x201e:
return static_cast<char>(0x84);
case 0x2026:
return static_cast<char>(0x85);
case 0x2020:
return static_cast<char>(0x86);
case 0x2021:
return static_cast<char>(0x87);
case 0x02c6:
return static_cast<char>(0x88);
case 0x2030:
return static_cast<char>(0x89);
case 0x0160:
return static_cast<char>(0x8a);
case 0x2039:
return static_cast<char>(0x8b);
case 0x0152:
return static_cast<char>(0x8c);
case 0x008d:
return static_cast<char>(0x8d);
case 0x017d:
return static_cast<char>(0x8e);
case 0x008f:
return static_cast<char>(0x8f);
case 0x0090:
return static_cast<char>(0x90);
case 0x2018:
return static_cast<char>(0x91);
case 0x2019:
return static_cast<char>(0x92);
case 0x201c:
return static_cast<char>(0x93);
case 0x201d:
return static_cast<char>(0x94);
case 0x2022:
return static_cast<char>(0x95);
case 0x2013:
return static_cast<char>(0x96);
case 0x2014:
return static_cast<char>(0x97);
case 0x02dc:
return static_cast<char>(0x98);
case 0x2122:
return static_cast<char>(0x99);
case 0x0161:
return static_cast<char>(0x9a);
case 0x203a:
return static_cast<char>(0x9b);
case 0x0153:
return static_cast<char>(0x9c);
case 0x009d:
return static_cast<char>(0x9d);
case 0x017e:
return static_cast<char>(0x9e);
case 0x0178:
return static_cast<char>(0x9f);
case 0x00a0:
return static_cast<char>(0xa0);
case 0x00a1:
return static_cast<char>(0xa1);
case 0x00a2:
return static_cast<char>(0xa2);
case 0x00a3:
return static_cast<char>(0xa3);
case 0x00a4:
return static_cast<char>(0xa4);
case 0x00a5:
return static_cast<char>(0xa5);
case 0x00a6:
return static_cast<char>(0xa6);
case 0x00a7:
return static_cast<char>(0xa7);
case 0x00a8:
return static_cast<char>(0xa8);
case 0x00a9:
return static_cast<char>(0xa9);
case 0x00aa:
return static_cast<char>(0xaa);
case 0x00ab:
return static_cast<char>(0xab);
case 0x00ac:
return static_cast<char>(0xac);
case 0x00ad:
return static_cast<char>(0xad);
case 0x00ae:
return static_cast<char>(0xae);
case 0x00af:
return static_cast<char>(0xaf);
case 0x00b0:
return static_cast<char>(0xb0);
case 0x00b1:
return static_cast<char>(0xb1);
case 0x00b2:
return static_cast<char>(0xb2);
case 0x00b3:
return static_cast<char>(0xb3);
case 0x00b4:
return static_cast<char>(0xb4);
case 0x00b5:
return static_cast<char>(0xb5);
case 0x00b6:
return static_cast<char>(0xb6);
case 0x00b7:
return static_cast<char>(0xb7);
case 0x00b8:
return static_cast<char>(0xb8);
case 0x00b9:
return static_cast<char>(0xb9);
case 0x00ba:
return static_cast<char>(0xba);
case 0x00bb:
return static_cast<char>(0xbb);
case 0x00bc:
return static_cast<char>(0xbc);
case 0x00bd:
return static_cast<char>(0xbd);
case 0x00be:
return static_cast<char>(0xbe);
case 0x00bf:
return static_cast<char>(0xbf);
case 0x00c0:
return static_cast<char>(0xc0);
case 0x00c1:
return static_cast<char>(0xc1);
case 0x00c2:
return static_cast<char>(0xc2);
case 0x00c3:
return static_cast<char>(0xc3);
case 0x00c4:
return static_cast<char>(0xc4);
case 0x00c5:
return static_cast<char>(0xc5);
case 0x00c6:
return static_cast<char>(0xc6);
case 0x00c7:
return static_cast<char>(0xc7);
case 0x00c8:
return static_cast<char>(0xc8);
case 0x00c9:
return static_cast<char>(0xc9);
case 0x00ca:
return static_cast<char>(0xca);
case 0x00cb:
return static_cast<char>(0xcb);
case 0x00cc:
return static_cast<char>(0xcc);
case 0x00cd:
return static_cast<char>(0xcd);
case 0x00ce:
return static_cast<char>(0xce);
case 0x00cf:
return static_cast<char>(0xcf);
case 0x00d0:
return static_cast<char>(0xd0);
case 0x00d1:
return static_cast<char>(0xd1);
case 0x00d2:
return static_cast<char>(0xd2);
case 0x00d3:
return static_cast<char>(0xd3);
case 0x00d4:
return static_cast<char>(0xd4);
case 0x00d5:
return static_cast<char>(0xd5);
case 0x00d6:
return static_cast<char>(0xd6);
case 0x00d7:
return static_cast<char>(0xd7);
case 0x00d8:
return static_cast<char>(0xd8);
case 0x00d9:
return static_cast<char>(0xd9);
case 0x00da:
return static_cast<char>(0xda);
case 0x00db:
return static_cast<char>(0xdb);
case 0x00dc:
return static_cast<char>(0xdc);
case 0x00dd:
return static_cast<char>(0xdd);
case 0x00de:
return static_cast<char>(0xde);
case 0x00df:
return static_cast<char>(0xdf);
case 0x00e0:
return static_cast<char>(0xe0);
case 0x00e1:
return static_cast<char>(0xe1);
case 0x00e2:
return static_cast<char>(0xe2);
case 0x00e3:
return static_cast<char>(0xe3);
case 0x00e4:
return static_cast<char>(0xe4);
case 0x00e5:
return static_cast<char>(0xe5);
case 0x00e6:
return static_cast<char>(0xe6);
case 0x00e7:
return static_cast<char>(0xe7);
case 0x00e8:
return static_cast<char>(0xe8);
case 0x00e9:
return static_cast<char>(0xe9);
case 0x00ea:
return static_cast<char>(0xea);
case 0x00eb:
return static_cast<char>(0xeb);
case 0x00ec:
return static_cast<char>(0xec);
case 0x00ed:
return static_cast<char>(0xed);
case 0x00ee:
return static_cast<char>(0xee);
case 0x00ef:
return static_cast<char>(0xef);
case 0x00f0:
return static_cast<char>(0xf0);
case 0x00f1:
return static_cast<char>(0xf1);
case 0x00f2:
return static_cast<char>(0xf2);
case 0x00f3:
return static_cast<char>(0xf3);
case 0x00f4:
return static_cast<char>(0xf4);
case 0x00f5:
return static_cast<char>(0xf5);
case 0x00f6:
return static_cast<char>(0xf6);
case 0x00f7:
return static_cast<char>(0xf7);
case 0x00f8:
return static_cast<char>(0xf8);
case 0x00f9:
return static_cast<char>(0xf9);
case 0x00fa:
return static_cast<char>(0xfa);
case 0x00fb:
return static_cast<char>(0xfb);
case 0x00fc:
return static_cast<char>(0xfc);
case 0x00fd:
return static_cast<char>(0xfd);
case 0x00fe:
return static_cast<char>(0xfe);
case 0x00ff:
return static_cast<char>(0xff);
}
return static_cast<char>(0x3f);
}
string
from_utf8(string const& text)
{
return from_utf8(text.begin(), text.end());
}
} // namespace Art::Paperback::Internals::cp1252

View File

@@ -0,0 +1,26 @@
#ifndef art__paperback__internals__cp1252_hxx_
#define art__paperback__internals__cp1252_hxx_
#include <art/paperback/types.hxx>
#include <art/unicode/reader.hxx>
#include <art/unicode/utf8-decoder.hxx>
namespace Art::Paperback::Internals::cp1252
{
char
from_unicode(uint32_t);
template<typename I>
string
from_utf8(I&&, I const&);
string
from_utf8(string const&);
} // namespace Art::Paperback::Internals::cp1252
#include <art/paperback/internals/cp1252.txx>
#endif

View File

@@ -0,0 +1,21 @@
namespace Art::Paperback::Internals::cp1252
{
template<typename I>
string
from_utf8(I&& first, I const& last)
{
stringstream str;
str.imbue(std::locale::classic());
art::unicode::iterator_reader_t reader{first, last};
art::unicode::utf8_decoder_t decoder{reader};
for (auto const& unicode : decoder) {
str << from_unicode(unicode);
}
return str.str();
}
} // namespace Art::Paperback::Internals::cp1252

View File

@@ -0,0 +1,63 @@
#include <art/paperback/internals/document-catalog.hxx>
#include <art/paperback/document.hxx>
#include <art/paperback/carousel/dictionary.hxx>
#include <art/paperback/carousel/file.hxx>
namespace Art::Paperback::Internals
{
struct Document_catalog::Internal
{
Internal(Document& document)
: document{document}
{}
Document& document;
/// The page tree of the document. Allocated on first use.
///
optional<Page_tree> pages;
};
Document_catalog::
Document_catalog(Create_new const&, Document& document)
: internal{new Internal{document}}
{
document.file().catalog() = Carousel::Dictionary{};
document.file().catalog().insert("Type", Carousel::Name{"Catalog"});
}
Document_catalog::
~Document_catalog() noexcept
{}
Document&
Document_catalog::
document()
{
return internal->document;
}
Document const&
Document_catalog::
document() const
{
return internal->document;
}
Page_tree&
Document_catalog::
pages()
{
if (!internal->pages) {
internal->pages.emplace(Page_tree::create_new, *this);
document().file().catalog().insert("Pages", internal->pages->object());
}
return *internal->pages;
}
} // namespace Art::Paperback::Internals

View File

@@ -0,0 +1,54 @@
#ifndef art__paperback__internals__document_catalog_hxx_
#define art__paperback__internals__document_catalog_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
#include <art/paperback/internals/page-tree.hxx>
namespace Art::Paperback::Internals
{
class Document_catalog
{
public:
struct Create_new {};
static constexpr Create_new const create_new{};
/// Constructor.
///
Document_catalog(Create_new const&, Document&);
/// Destructor.
///
~Document_catalog() noexcept;
/// Access the parent document.
///
Document&
document();
/// Access the parent document.
///
Document const&
document() const;
/// Access the page tree.
///
Page_tree&
pages();
private:
Document_catalog(Document_catalog const&) = delete;
Document_catalog(Document_catalog&&) = delete;
Document_catalog& operator=(Document_catalog const&) = delete;
Document_catalog& operator=(Document_catalog&&) = delete;
struct Internal;
unique_ptr<Internal> internal;
};
} // namespace Art::Paperback::Internals
#endif

View File

@@ -0,0 +1,90 @@
#include <art/paperback/internals/font-collection.hxx>
#include <art/paperback/internals/resource-collection.hxx>
#include <art/paperback/except.hxx>
#include <art/paperback/document.hxx>
#include <art/paperback/carousel/dictionary.hxx>
#include <art/paperback/carousel/file.hxx>
#include <art/paperback/carousel/name.hxx>
#include <art/paperback/carousel/object.hxx>
#include <art/paperback/graphics/font.hxx>
namespace Art::Paperback::Internals
{
struct Font_collection::Internal
{
Internal(Resource_collection& parent)
: parent{parent},
object{Carousel::Dictionary{}},
data{object_cast<Carousel::Dictionary>(object)}
{}
Resource_collection& parent;
Carousel::Object object;
Carousel::Dictionary& data;
Carousel::Name
get_next_local_name()
{
stringstream str;
str << "F" << data.size();
return Carousel::Name{str.str()};
}
};
/// This constructor creates a new font collection.
///
/// \param parent The parent resource collection.
///
Font_collection::
Font_collection(Create_new const&,
Resource_collection& parent)
: internal{new Internal{parent}}
{}
Font_collection::
~Font_collection() noexcept
{}
/// \return Returns a reference to the parent resource collection.
///
Resource_collection&
Font_collection::
resource_collection()
{
return internal->parent;
}
/// \return Return the data object for this font collection.
///
Carousel::Object
Font_collection::
object()
{
return internal->object;
}
/// \param font The font to embed.
/// \return Returns the local name of the embedded font.
///
Carousel::Name
Font_collection::
embed(Graphics::Font& font)
{
auto local_name = internal->get_next_local_name();
if (internal->data.contains(local_name)) {
raise<Internal_error>{} << "font local name already used";
}
internal->data.insert(local_name, font.object());
return local_name;
}
} // namespace Art::Paperback::Internals

View File

@@ -0,0 +1,54 @@
#ifndef art__paperback__internals__font_collection_hxx_
#define art__paperback__internals__font_collection_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
namespace Art::Paperback::Internals
{
class Font_collection
{
public:
struct Create_new {};
static constexpr Create_new const create_new{};
/// Constructor.
///
Font_collection(Create_new const&, Resource_collection&);
/// Destructor.
///
~Font_collection() noexcept;
/// Access the parent resource collection.
///
Resource_collection&
resource_collection();
/// Get the object for this font collection.
///
Carousel::Object
object();
/// Embed font in this font collection and return the local name of the
/// embedded font.
///
Carousel::Name
embed(Graphics::Font& f);
private:
Font_collection(Font_collection const&) = delete;
Font_collection(Font_collection&&) = delete;
Font_collection& operator=(Font_collection const&) = delete;
Font_collection& operator=(Font_collection&&) = delete;
private:
struct Internal;
unique_ptr<Internal> internal;
};
} // namespace Art::Paperback::Internals
#endif

View File

@@ -0,0 +1,19 @@
#include <art/paperback/internals/graphics-state.hxx>
namespace Art::Paperback::Internals
{
Matrix
apply(Matrix const& ctm, Matrix const& cm)
{
return Matrix{
cm.a * ctm.a + cm.c * ctm.b,
cm.b * ctm.a + cm.d * ctm.b,
cm.a * ctm.c + cm.c * ctm.d,
cm.b * ctm.c + cm.d * ctm.d,
cm.a * ctm.e + cm.c * ctm.f + cm.e,
cm.b * ctm.e + cm.d * ctm.f + cm.f
};
}
} // namespace Art::Paperback::Internals

View File

@@ -0,0 +1,87 @@
#ifndef art__paperback__internals__graphics_state_hxx_
#define art__paperback__internals__graphics_state_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/graphics/color.hxx>
#include <art/paperback/graphics/font.hxx>
namespace Art::Paperback::Internals
{
struct Matrix
{
double a;
double b;
double c;
double d;
double e;
double f;
};
Matrix
apply(Matrix const&, Matrix const&);
enum class Text_rendering_mode
{
fill,
stroke,
fill_then_stroke,
invisble,
fill_clippping,
stroke_clipping,
fill_stroke_clipping,
clipping
};
enum class Line_cap_style
{
butt_end,
round_end,
projecting_square_end
};
enum class Line_join_style
{
miter_join,
round_join,
bevel_join
};
struct Graphics_state
{
Matrix ctm;
Graphics::Color_space cs_stroke{Graphics::Color_space::device_grey};
Graphics::Color_space cs_fill{Graphics::Color_space::device_grey};
Graphics::RGB rgb_stroke;
Graphics::RGB rgb_fill;
double grey_stroke{};
double grey_fill{};
double text_cspace{};
double text_hspace{};
double text_hscale{};
double text_leading{};
Graphics::Font* current_font{};
double font_size{};
Text_rendering_mode text_rendering_mode{};
double ts_rise{};
double line_width{1.0};
Line_cap_style line_cap{};
Line_join_style line_join{};
double miter_limit{10.0};
double flatness{1.0};
};
} // namespace Art::Paperback::Internals
#endif

View File

@@ -0,0 +1,147 @@
#include <art/paperback/internals/page-tree.hxx>
#include <art/paperback/internals/document-catalog.hxx>
#include <art/paperback/carousel/array.hxx>
#include <art/paperback/carousel/dictionary.hxx>
#include <art/paperback/carousel/file.hxx>
#include <art/paperback/carousel/integer.hxx>
#include <art/paperback/carousel/name.hxx>
#include <art/paperback/document.hxx>
#include <art/paperback/except.hxx>
#include <art/paperback/page.hxx>
namespace Art::Paperback::Internals
{
struct Page_tree::Internal
{
Internal(Create_new const&,
Document_catalog& document_catalog)
: document_catalog{document_catalog},
object{
document_catalog.document().file().create_object<Carousel::Dictionary>(
map<Carousel::Name, Carousel::Object>{
{Carousel::Name{"Type"}, Carousel::Name{"Pages"}},
{Carousel::Name{"Kids"}, Carousel::Array{}},
{Carousel::Name{"Count"}, Carousel::Integer{0}}
}
)
},
data{object_cast<Carousel::Dictionary>(object)},
kids{object_cast<Carousel::Array>(data.at("Kids"))},
count{object_cast<Carousel::Integer>(data.at("Count"))}
{}
Document_catalog& document_catalog;
Carousel::Object object;
Carousel::Dictionary& data;
Carousel::Array& kids;
Carousel::Integer& count;
// This is a map of all currently loaded pages, rather than all
// pages in the document. It is populated on page creation or when
// requesting a page not already loaded.
//
map<size_t, Page> pages;
};
/// \class Page_tree
///
/// \warning
/// The current Page_tree implementation maintains a flat array of
/// all pages in the tree. This is far from optimal. We should
/// probably implement a self-balancing tree in the future.
///
/// This constructor creates a new page tree.
///
/// \param document_catalog The parent document catalog.
///
Page_tree::
Page_tree(Create_new const&, Document_catalog& document_catalog)
: internal{new Internal{create_new, document_catalog}}
{}
Page_tree::
~Page_tree() noexcept
{}
/// \return Returns a reference to the parent document catalog.
///
Document_catalog&
Page_tree::
document_catalog() const
{
return internal->document_catalog;
}
Carousel::Object
Page_tree::
object() const
{
return internal->object;
}
/// \param properties The properties of the new page.
///
Page&
Page_tree::
create_page(Page::Properties const& properties)
{
auto index = internal->kids.size();
auto [it, unused] = internal->pages.try_emplace(
index,
Page::create_new,
*this,
properties
);
internal->kids.push_back(it->second.object());
internal->count = internal->kids.size();
return it->second;
}
Page&
Page_tree::
get_page(size_t)
{
raise<Internal_error>{} << "function not implemented";
}
Page const&
Page_tree::
get_page(size_t) const
{
raise<Internal_error>{} << "function not implemented";
}
void
Page_tree::
erase_page(size_t)
{
raise<Internal_error>{} << "function not implemented";
}
/// \param page_tree The page tree.
/// \return Returns a reference to the parent document.
///
Document&
document(Page_tree& page_tree)
{
return page_tree.document_catalog().document();
}
/// \param page_tree The page tree.
/// \return Returns a reference to the parent document.
///
Document const&
document(Page_tree const& page_tree)
{
return page_tree.document_catalog().document();
}
} // namespace Art::Paperback::Internals

View File

@@ -0,0 +1,83 @@
#ifndef art__paperback__internals__page_tree_hxx_
#define art__paperback__internals__page_tree_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
#include <art/paperback/page.hxx>
#include <art/paperback/carousel/object.hxx>
namespace Art::Paperback::Internals
{
/// Represents a PDF page tree.
///
class Page_tree
{
public:
struct Create_new {};
static constexpr Create_new const create_new{};
/// Constructor.
///
Page_tree(Create_new const&, Document_catalog&);
/// Destructor.
///
~Page_tree() noexcept;
/// Access the parent document catalog.
///
Document_catalog&
document_catalog() const;
/// Access the associated data object.
///
Carousel::Object
object() const;
/// Create a new page and add it to the page tree.
///
Page&
create_page(Page::Properties const&);
/// Get page.
///
Page&
get_page(size_t);
/// Get page.
///
Page const&
get_page(size_t) const;
/// Erase page.
///
void
erase_page(size_t);
private:
Page_tree(Page_tree const&) = delete;
Page_tree(Page_tree&&) = delete;
Page_tree& operator=(Page_tree const&) = delete;
Page_tree& operator=(Page_tree&&) = delete;
private:
struct Internal;
unique_ptr<Internal> internal;
};
/// Get the document associated with a page tree.
///
Document&
document(Page_tree&);
/// Get the document associated with a page tree.
///
Document const&
document(Page_tree const&);
} // namespace Art::Paperback::Internals
#endif

View File

@@ -0,0 +1,110 @@
#include <art/paperback/internals/resource-collection.hxx>
#include <art/paperback/carousel/dictionary.hxx>
#include <art/paperback/carousel/file.hxx>
#include <art/paperback/carousel/name.hxx>
#include <art/paperback/carousel/object.hxx>
#include <art/paperback/document.hxx>
#include <art/paperback/page.hxx>
namespace Art::Paperback::Internals
{
struct Resource_collection::Internal
{
Internal(Create_new const&, Page& page)
: page{page},
object{Carousel::Dictionary{}},
data{object_cast<Carousel::Dictionary>(object)}
{}
Page& page;
/// COS object for this resource collection.
///
Carousel::Object object;
/// Data dictionary for this resource collection.
///
Carousel::Dictionary& data;
/// Optional font collection, allocated on first use.
///
optional<Font_collection> fonts;
};
/// This contructor creates a new resource collection.
///
/// \param page The parent page of this resource collection.
/// \param object The object for this resource collection.
///
Resource_collection::
Resource_collection(Create_new, Page& page)
: internal{new Internal{create_new, page}}
{
Carousel::Array procset;
procset.push_back(Carousel::Name{"PDF"});
procset.push_back(Carousel::Name{"Text"});
procset.push_back(Carousel::Name{"ImageB"});
procset.push_back(Carousel::Name{"ImageC"});
procset.push_back(Carousel::Name{"ImageI"});
internal->data.insert("ProcSet", procset);
}
Resource_collection::
~Resource_collection() noexcept
{}
Page&
Resource_collection::
page()
{
return internal->page;
}
Page const&
Resource_collection::
page() const
{
return internal->page;
}
Carousel::Object
Resource_collection::
object()
{
return internal->object;
}
Font_collection&
Resource_collection::
fonts()
{
if (!internal->fonts) {
internal->fonts.emplace(Font_collection::create_new, *this);
internal->data.insert("Font", internal->fonts->object());
}
return *internal->fonts;
}
/// \return Returns a reference to the document.
///
Document&
document(Resource_collection& resource_collection)
{
return document(resource_collection.page());
}
/// \return Returns a reference to the document.
///
Document const&
document(Resource_collection const& resource_collection)
{
return document(resource_collection.page());
}
} // namespace cooper

View File

@@ -0,0 +1,74 @@
#ifndef art__paperback__internals__resource_collection_hxx_
#define art__paperback__internals__resource_collection_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
#include <art/paperback/internals/font-collection.hxx>
#include <art/paperback/carousel/object.hxx>
namespace Art::Paperback::Internals
{
/// Represents a PDF resource collection.
///
class Resource_collection
{
public:
struct Create_new {};
static constexpr Create_new const create_new{};
/// Constructor.
///
Resource_collection(Create_new, Page&);
/// Destructor.
///
~Resource_collection() noexcept;
/// Get the parent page.
///
Page&
page();
/// Get the parent page.
///
Page const&
page() const;
/// Get the object for this resource collection.
///
Carousel::Object
object();
/// Get the font collection.
///
Font_collection&
fonts();
private:
Resource_collection(Resource_collection const&) = delete;
Resource_collection(Resource_collection&&) = delete;
Resource_collection& operator=(Resource_collection const&) = delete;
Resource_collection& operator=(Resource_collection&&) = delete;
private:
struct Internal;
unique_ptr<Internal> internal;
};
/// Get the document associated with a resource collection.
///
Document&
document(Resource_collection&);
/// Get the document associated with a resource collection.
///
Document const&
document(Resource_collection const&);
} // namespace cooper
#endif

131
art/paperback/page.cxx Normal file
View File

@@ -0,0 +1,131 @@
#include <art/paperback/page.hxx>
#include <art/paperback/primitives.hxx>
#include <art/paperback/document.hxx>
#include <art/paperback/carousel/array.hxx>
#include <art/paperback/carousel/dictionary.hxx>
#include <art/paperback/carousel/file.hxx>
#include <art/paperback/carousel/name.hxx>
#include <art/paperback/carousel/object.hxx>
#include <art/paperback/carousel/stream.hxx>
#include <art/paperback/internals/document-catalog.hxx>
#include <art/paperback/internals/page-tree.hxx>
#include <art/paperback/internals/resource-collection.hxx>
namespace Art::Paperback
{
struct Page::Internal
{
Internal(Create_new const&,
Internals::Page_tree& page_tree)
: page_tree{page_tree},
object{document(page_tree).file().create_object<Carousel::Dictionary>()},
data{object_cast<Carousel::Dictionary>(object)},
contents{document(page_tree).file().create_object<Carousel::Stream>()}
{}
/// Parent page tree.
///
Internals::Page_tree& page_tree;
/// COS object for this page.
///
Carousel::Object object;
/// Data dictionary for this page.
///
Carousel::Dictionary& data;
/// The page's contents stream object.
///
Carousel::Object contents;
/// On-demand allocated resource collection.
///
optional<Internals::Resource_collection> resources;
};
/// This constructor creates a new page.
///
/// \param page_tree A reference to the parent page tree.
/// \param properties The properties of the new page.
///
Page::
Page(Create_new const&,
Internals::Page_tree& page_tree,
Properties const& properties)
: internal{new Internal{create_new, page_tree}}
{
internal->data.insert("Type", Carousel::Name{"Page"});
internal->data.insert("MediaBox", properties.media_box.to_array());
internal->data.insert("Parent", page_tree.object());
internal->data.insert("Contents", internal->contents);
internal->resources.emplace(
Internals::Resource_collection::create_new, *this
);
internal->data.insert("Resources", internal->resources->object());
}
Page::
~Page() noexcept
{}
Internals::Page_tree&
Page::
page_tree() const
{
return internal->page_tree;
}
Carousel::Object
Page::
object() const
{
return internal->object;
}
Carousel::Stream&
Page::
contents()
{
return object_cast<Carousel::Stream>(internal->contents);
#if 0
if (!internal->contents) {
auto stream = document(*this).file().create_object<Carousel::Stream>();
internal->data.insert("Contents", stream);
internal->contents.emplace(stream);
}
return *internal->contents;
#endif
}
Internals::Resource_collection&
Page::
resources()
{
if (!internal->resources) {
}
return *internal->resources;
}
Document&
document(Page& page)
{
return document(page.page_tree());
}
Document const&
document(Page const& page)
{
return document(page.page_tree());
}
} // namespace Art::Paperback

97
art/paperback/page.hxx Normal file
View File

@@ -0,0 +1,97 @@
#ifndef art__paperback__page_hxx_
#define art__paperback__page_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
#include <art/paperback/primitives.hxx>
namespace Art::Paperback
{
class Page
{
public:
struct Create_new {};
static constexpr Create_new const create_new{};
/// Properties of a new page.
///
struct Properties
{
/// Specifies the page's media box (required).
///
Rectangle media_box;
/// Specifies the page's crop box.
///
optional<Rectangle> crop_box;
/// Specifies the page's bleed box.
///
optional<Rectangle> bleed_box;
/// Specifies the page's trim box.
///
optional<Rectangle> trim_box;
/// Specifies the page's art box.
///
optional<Rectangle> art_box;
};
/// Constructor.
///
Page(Create_new const&,
Internals::Page_tree&,
Properties const&);
/// Destructor.
///
~Page() noexcept;
/// Access the parent page tree.
///
Internals::Page_tree&
page_tree() const;
/// Access the page's object.
///
Carousel::Object
object() const;
/// Access the page content stream.
///
Carousel::Stream&
contents();
/// Access page resource collection.
///
Internals::Resource_collection&
resources();
private:
Page(Page const&) = delete;
Page(Page&&) = delete;
Page& operator=(Page const&) = delete;
Page& operator=(Page&&) = delete;
private:
struct Internal;
unique_ptr<Internal> internal;
};
/// Get the document associated with a page.
///
Document&
document(Page&);
/// Get the document associated with a page.
///
Document const&
document(Page const&);
} // namespace Art::Paperback
#endif

View File

@@ -0,0 +1,136 @@
#include <art/paperback/primitives.hxx>
#include <art/paperback/carousel/array.hxx>
#include <art/paperback/carousel/object.hxx>
#include <art/paperback/carousel/real.hxx>
namespace Art::Paperback
{
Rectangle::
Rectangle()
{}
Rectangle::
Rectangle(double left, double bottom, double right, double top)
: _left{left},
_bottom{bottom},
_right{right},
_top{top}
{}
double
Rectangle::
left() const
{
return _left;
}
double
Rectangle::
bottom() const
{
return _bottom;
}
double
Rectangle::
right() const
{
return _right;
}
double
Rectangle::
top() const
{
return _top;
}
Carousel::Array
Rectangle::
to_array() const
{
Carousel::Array array{
{
Carousel::Real{left()},
Carousel::Real{bottom()},
Carousel::Real{right()},
Carousel::Real{top()}
}
};
return array;
}
Rectangle
Rectangle::
letter()
{
return Rectangle{0, 0, 612, 792};
}
Rectangle
Rectangle::
legal()
{
return Rectangle{0, 0, 612, 1008};
}
Rectangle
Rectangle::
tabloid()
{
return Rectangle{0, 0, 792, 1224};
}
Rectangle
Rectangle::
a3()
{
return Rectangle{0, 0, 842, 1191};
}
Rectangle
Rectangle::
a4()
{
return Rectangle{0, 0, 595, 842};
}
Rectangle
Rectangle::
a5()
{
return Rectangle{0, 0, 420, 595};
}
Rectangle
Rectangle::
c3()
{
return Rectangle{0, 0, 918, 1296};
}
Rectangle
Rectangle::
c4()
{
return Rectangle{0, 0, 649, 918};
}
Rectangle
Rectangle::
c5()
{
return Rectangle{0, 0, 459, 649};
}
Rectangle
Rectangle::
executive()
{
return Rectangle{0, 0, 522, 756};
}
} // namespace Art::Paperback

View File

@@ -0,0 +1,104 @@
#ifndef art__paperback__primitives_hxx_
#define art__paperback__primitives_hxx_
#include <art/paperback/types.hxx>
#include <art/paperback/forward.hxx>
namespace Art::Paperback
{
/// Represents a point in a two-dimensional space.
///
struct Point_2D
{
/// The X coordinate.
///
double x{};
/// The Y coordinate.
///
double y{};
};
/// Represents a PDF rectangle.
///
class Rectangle
{
public:
/// Constructor.
///
Rectangle();
/// Constructor.
///
Rectangle(double, double, double, double);
double
left() const;
double
bottom() const;
double
right() const;
double
top() const;
/// Create COS array from rectangle.
///
Carousel::Array
to_array() const;
static
Rectangle
letter();
static
Rectangle
legal();
static
Rectangle
tabloid();
static
Rectangle
a3();
static
Rectangle
a4();
static
Rectangle
a5();
static
Rectangle
c3();
static
Rectangle
c4();
static
Rectangle
c5();
static
Rectangle
executive();
private:
double _left{};
double _bottom{};
double _right{};
double _top{};
};
} // namespace Art::Paperback
#endif

142
art/paperback/types.hxx Normal file
View File

@@ -0,0 +1,142 @@
#ifndef art__paperback__types_hxx_
#define art__paperback__types_hxx_
#include <cstdint>
#include <deque>
#include <initializer_list>
#include <iostream>
#include <list>
#include <map>
#include <memory>
#include <optional>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <string>
#include <variant>
#include <vector>
namespace Art::Paperback
{
#ifndef GENERATING_DOCUMENTATION
using std::initializer_list;
using std::invalid_argument;
using std::logic_error;
using std::runtime_error;
using std::int8_t;
using std::int16_t;
using std::int32_t;
using std::int64_t;
using std::uint8_t;
using std::uint16_t;
using std::uint32_t;
using std::uint64_t;
using std::unique_ptr;
using std::shared_ptr;
using std::make_shared;
using std::make_unique;
using std::variant;
using std::holds_alternative;
using std::deque;
using std::list;
using std::map;
using std::stack;
using std::vector;
using std::optional;
using std::nullopt;
using std::string;
using std::istream;
using std::ostream;
using std::iostream;
using std::streampos;
using std::streamoff;
using std::stringstream;
using std::cin;
using std::cout;
using std::cerr;
#endif
/// Index type.
///
using Index = uint32_t;
/// Generation type.
///
using Generation = uint16_t;
/// Represents the identity of an indirect object.
///
struct Identity
{
/// Constructor.
///
/// This constructor creates an identity with index 0 and
/// generation 0.
///
Identity()
{}
/// Constructor.
///
/// \param index The index of the identity.
/// \param generation The generation of the identity.
///
Identity(Index index, Generation generation)
: index{index}, generation{generation}
{}
Index index{};
Generation generation{};
/// Compare this identity with another.
///
/// \param other The identity to compare with.
///
bool
operator==(Identity const& other) const
{
return index == other.index && generation == other.generation;
}
/// Compare this identity with another.
///
/// \param other The identity to compare with.
///
bool
operator!=(Identity const& other) const
{
return !(*this == other);
}
/// Compare this identity with another.
///
bool
operator<(Identity const& other) const
{
if (index < other.index) {
return true;
}
if (generation < other.generation) {
return true;
}
return false;
}
};
} // namespace Art::Paperback
#endif

View File

@@ -0,0 +1,37 @@
#ifndef libart__paperback__version_hxx_
#define libart__paperback__version_hxx_
// The numeric version format is AAAAABBBBBCCCCCDDDE where:
//
// AAAAA - major version number
// BBBBB - minor version number
// CCCCC - bugfix version number
// DDD - alpha / beta (DDD + 500) version number
// E - final (0) / snapshot (1)
//
// When DDDE is not 0, 1 is subtracted from AAAAABBBBBCCCCC. For example:
//
// Version AAAAABBBBBCCCCCDDDE
//
// 0.1.0 0000000001000000000
// 0.1.2 0000000001000020000
// 1.2.3 0000100002000030000
// 2.2.0-a.1 0000200001999990010
// 3.0.0-b.2 0000299999999995020
// 2.2.0-a.1.z 0000200001999990011
//
#define LIBART_PAPERBACK_VERSION $libart_paperback.version.project_number$ULL
#define LIBART_PAPERBACK_VERSION_STR "$libart_paperback.version.project$"
#define LIBART_PAPERBACK_VERSION_ID "$libart_paperback.version.project_id$"
#define LIBART_PAPERBACK_VERSION_FULL "$libart_paperback.version$"
#define LIBART_PAPERBACK_VERSION_MAJOR $libart_paperback.version.major$
#define LIBART_PAPERBACK_VERSION_MINOR $libart_paperback.version.minor$
#define LIBART_PAPERBACK_VERSION_PATCH $libart_paperback.version.patch$
#define LIBART_PAPERBACK_PRE_RELEASE $libart_paperback.version.pre_release$
#define LIBART_PAPERBACK_SNAPSHOT_SN $libart_paperback.version.snapshot_sn$ULL
#define LIBART_PAPERBACK_SNAPSHOT_ID "$libart_paperback.version.snapshot_id$"
#endif

71
art/paperback/visitor.hxx Normal file
View File

@@ -0,0 +1,71 @@
#ifndef libart_paperback__visitor_hxx_
#define libart_paperback__visitor_hxx_
namespace Art::Paperback
{
/// Virtual base class for visitors.
///
class Visitor
{
protected:
Visitor() = default;
virtual
~Visitor() noexcept = default;
};
/// Base class for visitors of type T.
///
template<typename T>
class Basic_visitor
{
public:
/// Visit.
///
/// \param visitee The visitee.
///
virtual
void
visit(T& visitee)
{
visit(const_cast<T const&>(visitee));
}
/// Visit.
///
/// \param visitee The visitee.
///
virtual
void
visit(T const& visitee) = 0;
protected:
/// Constructor.
///
Basic_visitor() = default;
/// Destructor.
///
virtual
~Basic_visitor() noexcept = default;
};
/// Accept visitor on visitee.
///
/// \param visitee The visitee.
/// \param v The visitor.
///
template<typename T>
void
accept(T const& visitee, Visitor& v)
{
auto& v_cast = dynamic_cast<Basic_visitor<T>&>(v);
v_cast.visit(visitee);
}
} // namespace Art::Paperback
#endif

4
build/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/config.build
/root/
/bootstrap/
build/

7
build/bootstrap.build Normal file
View File

@@ -0,0 +1,7 @@
project = libart-paperback
using version
using config
using test
using install
using dist

6
build/export.build Normal file
View File

@@ -0,0 +1,6 @@
$out_root/
{
include art/paperback/
}
export $out_root/art/paperback/$import.target

16
build/root.build Normal file
View File

@@ -0,0 +1,16 @@
# Uncomment to suppress warnings coming from external libraries.
#
#cxx.internal.scope = current
cxx.std = latest
using cxx
hxx{*}: extension = hxx
ixx{*}: extension = ixx
txx{*}: extension = txx
cxx{*}: extension = cxx
# The test target for cross-testing (running tests under Wine, etc).
#
test.target = $cxx.target

31
buildfile Normal file
View File

@@ -0,0 +1,31 @@
./: {art/ tests/ examples/} doc{README.md} legal{LICENSE} manifest
define doxyfile: file
doxyfile{*}: extension = ""
doxyfile{*}: in.substitution = lax
doxyfile{Doxyfile}: in{Doxyfile} $src_root/manifest
{
PRIVATE = "NO"
}
doxyfile{Doxyfile-private}: in{Doxyfile} $src_root/manifest
{
PRIVATE = "YES"
}
alias{docs}: doxyfile{Doxyfile} fsdir{$out_root/docs}
{{
diag doxygen $path($<[0])
doxygen $path($<[0])
}}
alias{docs-private}: doxyfile{Doxyfile-private} fsdir{$out_root/docs}
{{
diag doxygen $path($<[0])
doxygen $path($<[0])
}}
# Don't install tests.
#
tests/: install = false

2683
docs/doxygen-awesome.css Normal file

File diff suppressed because it is too large Load Diff

116
docs/getting-started.md Normal file
View File

@@ -0,0 +1,116 @@
\page getting-started Getting Started
# Getting Started
Paperback uses the build2 build system, see https://build2.org/.
The first step is to include Paperback in your build2 project.
Add the following lines to your repositories.manifest file:
```
:
role: prerequisite
location: https://code.helloryan.se/art/libart-paperback.git##HEAD
```
And the following to your manifest file:
```
depends: libart-paperback ^0.1.0-
```
Finally, link your executable or library with Paperback:
```
import libs =+ libart-paperback%lib{art-paperback}
```
## Usage Example
The example below shows how to use Paperback to create a new PDF
document and render some text on a page. More examples are available
in `$src_root$/examples`.
```c++
#include <fstream>
#include <iostream>
#include <art/paperback/document.hxx>
#include <art/paperback/page.hxx>
#include <art/paperback/graphics/canvas.hxx>
#include <art/paperback/graphics/standard-font.hxx>
using namespace std;
using namespace Art::Paperback;
using namespace Art::Paperback::Graphics;
int
main(int argc, char* argv[])
{
if (argc != 2) {
cerr << "usage: " << argv[0] << " <path>\n";
return 1;
}
try {
// Open the output file.
//
fstream fs{argv[1], std::ios_base::out | std::ios_base::binary};
// Create a new document.
//
Document document{Document::create_new, fs, 1, 4};
Standard_font helvetica{document, "Helvetica"};
// Create a new A4 page.
//
double width = 595;
double height = 842;
auto& page = document.create_page(Page::Properties{
Rectangle{0, 0, width, height}
});
// Create a page canvas and render some text.
//
{
Canvas canvas{Canvas::clear, page};
Canvas::Set_font use_font{canvas, helvetica, 14};
// Measure the width of the text, so we can center it.
//
auto tw = helvetica.get_text_width("Hello, world!");
auto text_width = (tw.width * 14) / 1000;
auto capheight = (helvetica.get_capheight() * 14) / 1000;
double cx = (width / 2) - (text_width / 2);
double cy = (height / 2) - (capheight / 2);
Canvas::Begin_text bt{canvas};
bt.move_text_pos(cx, cy);
bt.show_text("Hello, world!");
}
// Write the document explicitly to the underlying I/O stream.
//
// Otherwise, the Document destructor flushes it automatically,
// ignoring any exceptions thrown.
//
document.flush();
}
catch (exception const& ex) {
cerr << argv[0] << ": an error occurred: " << ex.what() << "\n";
return 1;
}
catch (...) {
cerr << argv[0] << ": an unknown error occurred\n";
return 1;
}
return 0;
}
```

BIN
docs/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

48
docs/logo.svg Normal file
View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 400.32031 74.570312"
width="400.32031"
height="74.570312"
version="1.1"
id="svg2"
sodipodi:docname="logo.svg"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs2" />
<sodipodi:namedview
id="namedview2"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="2.55625"
inkscape:cx="200.0978"
inkscape:cy="37.359413"
inkscape:window-width="2192"
inkscape:window-height="1164"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<!-- Main logo text -->
<text
x="197.44531"
y="57.96875"
font-family="Arial, sans-serif"
font-size="80px"
font-weight="700"
text-anchor="middle"
letter-spacing="-1px"
id="text2"><tspan
fill="#27ae60"
id="tspan1">Paper</tspan><tspan
fill="#2c3e50"
id="tspan2">back</tspan></text>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

10
docs/main.md Normal file
View File

@@ -0,0 +1,10 @@
\mainpage Paperback
Paperback implements the PDF file format in C++, based on the PDF 1.4
specification, available online at:
https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.4.pdf.
The ultimate goal is to provide a library capable of generating PDF
files that conform to the PDF/A ISO 19005-1 standard.
See \ref getting-started for information on how to use Paperback in
your application.

55
docs/styles.css Normal file
View File

@@ -0,0 +1,55 @@
@import url('https://fonts.googleapis.com/css2?family=Google+Sans+Code:ital,wght@0,300..800;1,300..800&family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap');
html
{
--font-family-normal: "Open Sans", sans-serif;
--font-family-monospace: "Google Sans Code", monospace;
--font-family-nav: "Open Sans", sans-serif;
--font-family-title: "Open Sans", sans-serif;
--font-family-toc: "Open Sans", sans-serif;
--font-family-search: "Open Sans", sans-serif;
--font-family-icon: "Open Sans", sans-serif;
--font-family-tooltip: "Open Sans", sans-serif;
--page-link-color: #0057ae;
--page-visited-link-color: #0057ae;
}
#projectlogo img
{
height: 40px;
}
h1
{
margin-top: 2rem;
font-size: 2rem;
}
h2
{
margin-top: 1.5rem;
font-size: 1.25rem;
}
h2:first-child
{
margin-top: 0;
}
h3
{
margin-top: 1.5rem;
font-size: 1rem;
font-weight: bold;
}
h3:first-child
{
margin-top: 0;
}
code
{
background: rgb(242, 242, 242);
}

8
examples/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
# Test executables.
#
driver
# Testscript output directories (can be symlinks).
#
test
test-*

2
examples/basics/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
example
*.pdf

View File

@@ -0,0 +1,3 @@
import libs = libart-paperback%lib{art-paperback}
exe{example}: {hxx ixx txx cxx}{**} $libs

102
examples/basics/example.cxx Normal file
View File

@@ -0,0 +1,102 @@
#include <fstream>
#include <iostream>
#include <art/paperback/document.hxx>
#include <art/paperback/document-information.hxx>
#include <art/paperback/page.hxx>
#include <art/paperback/graphics/canvas.hxx>
#include <art/paperback/graphics/standard-font.hxx>
using namespace std;
using namespace Art::Paperback;
using namespace Art::Paperback::Graphics;
int
main(int argc, char* argv[])
{
if (argc != 2) {
cerr << "usage: " << argv[0] << " <path>\n";
return 1;
}
try {
fstream fs{argv[1], ios_base::out | ios_base::binary};
// Create a new document.
//
Document document{Document::create_new, fs, 1, 4};
document.information().set_title("Example Document");
document.information().set_author("Example Author");
document.information().set_subject("Example Subject");
document.information().set_keywords("PDF for C++");
document.information().set_creator("libart-paperback");
document.information().set_producer("libart-paperback");
// Load one of the 14 standard fonts.
//
Standard_font font{document, "Helvetica-BoldOblique"};
double width = 595;
double height = 842;
// Create a new page.
//
auto& page = document.create_page(Page::Properties{
Rectangle{0, 0, width, height}
});
// Create a new canvas and render some text on the page.
//
Canvas canvas{Canvas::clear, page};
// Set the font with a size of 14.
//
Canvas::Set_font use_font{canvas, font, 14};
// Render some text and graphics.
//
{
// Measure the width of the text, so we can center it.
//
auto tw = font.get_text_width("Hello, world!");
auto text_width = (tw.width * 14) / 1000;
auto capheight = (font.get_capheight() * 14) / 1000;
double cx = (width / 2) - (text_width / 2);
double cy = (height / 2) - (capheight / 2);
{
Canvas::Begin_text bt{canvas};
bt.move_text_pos(cx, cy);
bt.show_text("Hello, world!");
}
{
Canvas::Path path{canvas, Canvas::Path::Paint_mode::stroke};
// Draw borders around the text.
//
path.move_to(cx - 10, cy - 10);
path.line_to(cx - 10, cy + capheight + 10);
path.line_to(cx + text_width + 10, cy + capheight + 10);
path.line_to(cx + text_width + 10, cy - 10);
}
}
document.flush();
}
catch (exception const& ex) {
cerr << argv[0] << ": error: " << ex.what() << '\n';
}
catch (int u) {
cerr << argv[0] << ": " << u << '\n';
}
catch (...) {
cerr << argv[0] << ": an unknown error occurred\n";
}
return 0;
}

4
examples/build/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/config.build
/root/
/bootstrap/
build/

View File

@@ -0,0 +1,5 @@
project = # Unnamed tests subproject.
using config
using test
using dist

12
examples/build/root.build Normal file
View File

@@ -0,0 +1,12 @@
cxx.std = latest
using cxx
hxx{*}: extension = hxx
ixx{*}: extension = ixx
txx{*}: extension = txx
cxx{*}: extension = cxx
# No exe{} in this subproject is a test.
#
exe{*}: test = false

1
examples/buildfile Normal file
View File

@@ -0,0 +1 @@
./: {*/ -build/}

13
manifest Normal file
View File

@@ -0,0 +1,13 @@
: 1
name: libart-paperback
version: 0.1.0-a.0.z
language: c++
summary: libart-paperback PDF library for C++
license: BSD-4-Clause
description-file: README.md
url: https://art.helloryan.se/
email: art@helloryan.se
depends: * build2 >= 0.17.0
depends: * bpkg >= 0.17.0
depends: libart-validation ^0.1.0-
depends: libart-unicode ^0.1.0-

10
repositories.manifest Normal file
View File

@@ -0,0 +1,10 @@
: 1
summary: libart-paperback project repository
:
role: prerequisite
location: https://code.helloryan.se/art/libart-validation.git##HEAD
:
role: prerequisite
location: https://code.helloryan.se/art/libart-unicode.git##HEAD

8
tests/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
# Test executables.
#
driver
# Testscript output directories (can be symlinks).
#
test
test-*

4
tests/build/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/config.build
/root/
/bootstrap/
build/

View File

@@ -0,0 +1,5 @@
project = # Unnamed tests subproject.
using config
using test
using dist

16
tests/build/root.build Normal file
View File

@@ -0,0 +1,16 @@
cxx.std = latest
using cxx
hxx{*}: extension = hxx
ixx{*}: extension = ixx
txx{*}: extension = txx
cxx{*}: extension = cxx
# Every exe{} in this subproject is by default a test.
#
exe{*}: test = true
# The test target for cross-testing (running tests under Wine, etc).
#
test.target = $cxx.target

1
tests/buildfile Normal file
View File

@@ -0,0 +1 @@
./: {*/ -build/}