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

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