Browse Source

Add bitmap font class, improve image class, and rect and rect pack classes

master
C. J. Howard 3 years ago
parent
commit
c428d80e48
19 changed files with 1193 additions and 68 deletions
  1. +1
    -0
      CMakeLists.txt
  2. +1
    -1
      src/application.cpp
  3. +76
    -0
      src/game/states/loading.cpp
  4. +239
    -0
      src/geom/rect-pack.hpp
  5. +14
    -12
      src/geom/rect.hpp
  6. +4
    -3
      src/resources/image-loader.cpp
  7. +72
    -26
      src/resources/image.cpp
  8. +64
    -16
      src/resources/image.hpp
  9. +6
    -6
      src/resources/texture-loader.cpp
  10. +219
    -0
      src/type/bitmap-font.cpp
  11. +151
    -0
      src/type/bitmap-font.hpp
  12. +48
    -0
      src/type/bitmap-glyph.hpp
  13. +51
    -0
      src/type/font-metrics.hpp
  14. +29
    -0
      src/type/font.cpp
  15. +80
    -4
      src/type/font.hpp
  16. +48
    -0
      src/type/glyph-metrics.hpp
  17. +33
    -0
      src/type/kerning-table.hpp
  18. +5
    -0
      src/type/type.hpp
  19. +52
    -0
      src/type/typeface.hpp

+ 1
- 0
CMakeLists.txt View File

@ -1,5 +1,6 @@
cmake_minimum_required(VERSION 3.7) cmake_minimum_required(VERSION 3.7)
option(VERSION_STRING "Project version string" "0.0.0") option(VERSION_STRING "Project version string" "0.0.0")
project(antkeeper VERSION ${VERSION_STRING} LANGUAGES CXX) project(antkeeper VERSION ${VERSION_STRING} LANGUAGES CXX)

+ 1
- 1
src/application.cpp View File

@ -391,7 +391,7 @@ void application::save_frame(const std::string& path) const
[frame, path] [frame, path]
{ {
stbi_flip_vertically_on_write(1); stbi_flip_vertically_on_write(1);
stbi_write_png(path.c_str(), frame->get_width(), frame->get_height(), frame->get_channels(), frame->get_pixels(), frame->get_width() * frame->get_channels());
stbi_write_png(path.c_str(), frame->get_width(), frame->get_height(), frame->get_channel_count(), frame->get_pixels(), frame->get_width() * frame->get_channel_count());
} }
).detach(); ).detach();

+ 76
- 0
src/game/states/loading.cpp View File

@ -50,6 +50,9 @@
#include "scene/ambient-light.hpp" #include "scene/ambient-light.hpp"
#include "scene/directional-light.hpp" #include "scene/directional-light.hpp"
#include "utility/timestamp.hpp" #include "utility/timestamp.hpp"
#include "type/type.hpp"
#include <stb/stb_image_write.h>
#include <stb/stb_truetype.h>
namespace game { namespace game {
namespace state { namespace state {
@ -118,6 +121,79 @@ void enter(game::context* ctx)
next_state.exit = std::bind(game::state::splash::exit, ctx); next_state.exit = std::bind(game::state::splash::exit, ctx);
} }
type::bitmap_font font;
image& font_bitmap = font.get_bitmap();
font_bitmap.format(sizeof(unsigned char), 1);
std::ifstream font_file(ctx->config_path + "Vollkorn-Regular.ttf", std::ios::binary | std::ios::ate);
std::streamsize size = font_file.tellg();
font_file.seekg(0, std::ios::beg);
std::vector<unsigned char> font_buffer(size);
if (!font_file.read((char*)font_buffer.data(), size))
{
ctx->logger->error("Failed to read font file");
}
// stb_truetype
{
stbtt_fontinfo font_info;
if (!stbtt_InitFont(&font_info, font_buffer.data(), 0))
{
ctx->logger->error("stb init font failed");
}
const float font_size = 64.0f;
const float scale = stbtt_ScaleForPixelHeight(&font_info, font_size);
int ascent = 0;
int descent = 0;
int linegap = 0;
stbtt_GetFontVMetrics(&font_info, &ascent, &descent, &linegap);
float scaled_ascent = static_cast<float>(ascent) * scale;
float scaled_descent = static_cast<float>(descent) * scale;
float scaled_linegap = static_cast<float>(linegap) * scale;
type::unicode::block block = type::unicode::block::basic_latin;
for (int c = block.first; c <= block.last; ++c)
{
int glyph_index = stbtt_FindGlyphIndex(&font_info, c);
if (!glyph_index)
continue;
type::bitmap_glyph& glyph = font[static_cast<char32_t>(c)];
int advance_width = 0;
int left_side_bearing = 0;
stbtt_GetGlyphHMetrics(&font_info, glyph_index, &advance_width, &left_side_bearing);
int min_x;
int min_y;
int max_x;
int max_y;
stbtt_GetGlyphBitmapBox(&font_info, glyph_index, scale, scale, &min_x, &min_y, &max_x, &max_y);
int glyph_width = max_x - min_x;
int glyph_height = max_y - min_y;
glyph.metrics.width = static_cast<float>(glyph_width);
glyph.metrics.height = static_cast<float>(glyph_height);
glyph.metrics.bearing_left = static_cast<float>(left_side_bearing) * scale;
glyph.metrics.bearing_top = 0.0f;
glyph.metrics.advance = static_cast<float>(advance_width) * scale;
glyph.bitmap.format(sizeof(unsigned char), 1);
glyph.bitmap.resize(glyph_width, glyph_height);
stbtt_MakeGlyphBitmap(&font_info, (unsigned char*)glyph.bitmap.get_pixels(), glyph_width, glyph_height, glyph_width, scale, scale, glyph_index);
}
}
font.pack();
std::string bitmap_path = ctx->config_path + "bitmap-font.png";
stbi_flip_vertically_on_write(0);
stbi_write_png(bitmap_path.c_str(), font_bitmap.get_width(), font_bitmap.get_height(), font_bitmap.get_channel_count(), font_bitmap.get_pixels(), font_bitmap.get_width() * font_bitmap.get_channel_count());
// Queue next game state // Queue next game state
ctx->app->queue_state(next_state); ctx->app->queue_state(next_state);
} }

+ 239
- 0
src/geom/rect-pack.hpp View File

@ -0,0 +1,239 @@
/*
* Copyright (C) 2021 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_GEOM_RECT_PACK_HPP
#define ANTKEEPER_GEOM_RECT_PACK_HPP
#include "geom/rect.hpp"
namespace geom {
/**
* Node used in 2D rectangle packing.
*
* @see geom::rect_pack
*/
template <class T>
struct rect_pack_node
{
/// Scalar type.
typedef T scalar_type;
/// Rect type.
typedef rect<T> rect_type;
/// Creates a rect pack node.
rect_pack_node();
/// Destroys a rect pack node and its children.
~rect_pack_node();
/// Pointers to the two children of the node, if any.
rect_pack_node* children[2];
/// Bounds of the node.
rect_type bounds;
/// `true` if the node is occupied, `false` otherwise.
bool occupied;
};
template <class T>
rect_pack_node<T>::rect_pack_node():
bounds{T(0), T(0), T(0), T(0)},
occupied(false)
{
children[0] = nullptr;
children[1] = nullptr;
}
template <class T>
rect_pack_node<T>::~rect_pack_node()
{
delete children[0];
delete children[1];
}
/**
* Packs 2D rectangles.
*
* @see geom::rect_pack_node
*
* @see http://www.blackpawn.com/texts/lightmaps/
*/
template <class T>
class rect_pack
{
public:
/// Scalar type.
typedef T scalar_type;
/// Node type.
typedef rect_pack_node<T> node_type;
/**
* Creates a rect pack and sets the bounds of the root node.
*
* @param w Width of the root node.
* @param h Height of the root node.
*/
rect_pack(scalar_type w, scalar_type h);
/**
* Creates an empty rect pack.
*/
rect_pack();
/**
* Clears the pack and resizes the root node bounds.
*
* @param w New width of the root node.
* @param h New height of the root node.
*
* @see rect_pack::clear()
*/
void resize(scalar_type w, scalar_type h);
/// Clear the pack, deallocating all nodes.
void clear();
/**
* Packs a rect into the rect pack.
*
* @param w Width of the rect.
* @param h Height of the rect.
* @return Pointer to the node in which the rect was packed, or `nullptr` if the rect could not be packed.
*/
const node_type* pack(scalar_type w, scalar_type h);
/// Returns a reference to the root node.
const node_type& get_root() const;
private:
static node_type* insert(node_type& parent, scalar_type w, scalar_type h);
static void free();
node_type root;
};
template <class T>
rect_pack<T>::rect_pack(scalar_type w, scalar_type h)
{
root.bounds = {T(0), T(0), w, h};
}
template <class T>
rect_pack<T>::rect_pack():
rect_pack(0, 0)
{}
template <class T>
void rect_pack<T>::resize(scalar_type w, scalar_type h)
{
clear();
root.bounds = {T(0), T(0), w, h};
}
template <class T>
void rect_pack<T>::clear()
{
delete root.children[0];
delete root.children[1];
root.children[0] = nullptr;
root.children[1] = nullptr;
root.occupied = false;
}
template <class T>
const typename rect_pack<T>::node_type* rect_pack<T>::pack(scalar_type w, scalar_type h)
{
return insert(root, w, h);
}
template <class T>
inline const typename rect_pack<T>::node_type& rect_pack<T>::get_root() const
{
return root;
}
template <class T>
typename rect_pack<T>::node_type* rect_pack<T>::insert(node_type& node, scalar_type w, scalar_type h)
{
// If not a leaf node
if (node.children[0] && node.children[1])
{
// Attempt to insert into first child
node_type* result = insert(*node.children[0], w, h);
if (result)
return result;
// Cannot fit in first child, attempt to insert into second child
return insert(*node.children[1], w, h);
}
// Abort if node occupied
if (node.occupied)
return nullptr;
// Determine node dimensions
scalar_type node_w = node.bounds.max.x - node.bounds.min.x;
scalar_type node_h = node.bounds.max.y - node.bounds.min.y;
// Check if rect is larger than node
if (w > node_w || h > node_h)
return nullptr;
// Check for a perfect fit
if (w == node_w && h == node_h)
{
node.occupied = true;
return &node;
}
// Split the node
node.children[0] = new node_type();
node.children[1] = new node_type();
// Determine split direction
scalar_type dw = node_w - w;
scalar_type dh = node_h - h;
if (dw > dh)
{
node.children[0]->bounds.min = node.bounds.min;
node.children[0]->bounds.max = {node.bounds.min.x + w, node.bounds.max.y};
node.children[1]->bounds.min = {node.bounds.min.x + w, node.bounds.min.y};
node.children[1]->bounds.max = {node.bounds.max};
}
else
{
node.children[0]->bounds.min = node.bounds.min;
node.children[0]->bounds.max = {node.bounds.max.x, node.bounds.min.y + h};
node.children[1]->bounds.min = {node.bounds.min.x, node.bounds.min.y + h};
node.children[1]->bounds.max = {node.bounds.max};
}
// Insert into first child
return insert(*node.children[0], w, h);
}
} // namespace geom
#endif // ANTKEEPER_GEOM_RECT_PACK_HPP

src/type/glyph.hpp → src/geom/rect.hpp View File

@ -17,23 +17,25 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>. * along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef ANTKEEPER_TYPE_GLYPH_HPP
#define ANTKEEPER_TYPE_GLYPH_HPP
#ifndef ANTKEEPER_GEOM_RECT_HPP
#define ANTKEEPER_GEOM_RECT_HPP
namespace type {
#include "math/vector-type.hpp"
namespace geom {
/** /**
*
* 2D rectangle.
*/ */
struct glyph_metrics
template <class T>
struct rect
{ {
float2 size;
float2 bearing_h;
float2 bearing_v;
float advance_h;
float advance_v;
typedef math::vector<T, 2> vector_type;
vector_type min;
vector_type max;
}; };
} // namespace type
} // namespace geom
#endif // ANTKEEPER_TYPE_GLYPH_HPP
#endif // ANTKEEPER_GEOM_RECT_HPP

+ 4
- 3
src/resources/image-loader.cpp View File

@ -64,11 +64,12 @@ image* resource_loader::load(resource_manager* resource_manager, PHYSFS_F
{ {
throw std::runtime_error("STBI failed to load image from memory."); throw std::runtime_error("STBI failed to load image from memory.");
} }
std::size_t component_size = (hdr) ? sizeof(float) : sizeof(unsigned char);
::image* image = new ::image(); ::image* image = new ::image();
image->format(static_cast<unsigned int>(channels), hdr);
image->format(component_size, channels);
image->resize(static_cast<unsigned int>(width), static_cast<unsigned int>(height)); image->resize(static_cast<unsigned int>(width), static_cast<unsigned int>(height));
std::memcpy(image->get_pixels(), pixels, width * height * channels * ((hdr) ? sizeof(float) : sizeof(unsigned char)));
std::memcpy(image->get_pixels(), pixels, image->get_size());
// Free loaded image data // Free loaded image data
stbi_image_free(pixels); stbi_image_free(pixels);

+ 72
- 26
src/resources/image.cpp View File

@ -18,12 +18,20 @@
*/ */
#include "image.hpp" #include "image.hpp"
#include <cstring>
#include <stdexcept>
image::image(const image& source)
{
*this = source;
}
image::image(): image::image():
hdr(false),
width(0), width(0),
height(0), height(0),
channels(4),
component_size(0),
channel_count(0),
pixel_size(0),
pixels(nullptr), pixels(nullptr),
size(0) size(0)
{} {}
@ -33,16 +41,70 @@ image::~image()
free_pixels(); free_pixels();
} }
void image::format(unsigned int channels, bool hdr)
image& image::operator=(const image& source)
{
format(source.component_size, source.channel_count);
resize(source.width, source.height);
std::memcpy(pixels, source.pixels, size);
return *this;
}
bool image::compatible(const image& other) const
{
return (other.component_size == component_size && other.channel_count == channel_count);
}
void image::copy(const image& source, unsigned int w, unsigned int h, unsigned int from_x, int unsigned from_y, unsigned int to_x, unsigned int to_y)
{ {
if (this->channels == channels && this->hdr == hdr)
if (!compatible(source))
{ {
return;
throw std::runtime_error("Cannot copy image with mismatched format");
} }
const unsigned char* from_pixels = static_cast<const unsigned char*>(source.pixels);
unsigned char* to_pixels = static_cast<unsigned char*>(pixels);
for (unsigned int i = 0; i < h; ++i)
{
// Calculate vertical pixel offset
unsigned int from_i = from_y + i;
unsigned int to_i = to_y + i;
// Bounds check
if (from_i >= source.height || to_i >= height)
break;
for (unsigned int j = 0; j < w; ++j)
{
// Calculate horizontal pixel offsets
unsigned int from_j = from_x + j;
unsigned int to_j = to_x + j;
// Bounds check
if (from_j >= source.width || to_j >= width)
continue;
// Calculate pixel data offset (in bytes)
std::size_t from_offset = (from_i * source.width + from_j) * pixel_size;
std::size_t to_offset = (to_i * width + to_j) * pixel_size;
// Copy single pixel
std::memcpy(to_pixels + to_offset, from_pixels + from_offset, pixel_size);
}
}
}
void image::format(std::size_t component_size, std::size_t channel_count)
{
if (this->component_size == component_size && this->channel_count == channel_count)
return;
free_pixels(); free_pixels();
this->channels = channels;
this->hdr = hdr;
this->component_size = component_size;
this->channel_count = channel_count;
pixel_size = component_size * channel_count;
size = width * height * pixel_size;
allocate_pixels(); allocate_pixels();
} }
@ -56,23 +118,15 @@ void image::resize(unsigned int width, unsigned int height)
free_pixels(); free_pixels();
this->width = width; this->width = width;
this->height = height; this->height = height;
size = width * height * pixel_size;
allocate_pixels(); allocate_pixels();
} }
void image::allocate_pixels() void image::allocate_pixels()
{ {
size = width * height * channels;
if (size != 0) if (size != 0)
{ {
if (hdr)
{
pixels = new float[size];
}
else
{
pixels = new unsigned char[size];
}
pixels = new unsigned char[size];
} }
} }
@ -80,15 +134,7 @@ void image::free_pixels()
{ {
if (pixels != nullptr) if (pixels != nullptr)
{ {
if (hdr)
{
delete[] reinterpret_cast<float*>(pixels);
}
else
{
delete[] reinterpret_cast<unsigned char*>(pixels);
}
delete[] reinterpret_cast<unsigned char*>(pixels);
pixels = nullptr; pixels = nullptr;
size = 0; size = 0;
} }

+ 64
- 16
src/resources/image.hpp View File

@ -28,19 +28,58 @@
class image class image
{ {
public: public:
/**
* Creates a copy of another image.
*
* @param source Image from which to copy.
*/
image(const image& source);
/// Creates an image. /// Creates an image.
image(); image();
/// Destroys an image. /// Destroys an image.
~image(); ~image();
/**
* Makes this image a copy of another image.
*
* @param source Image from which to copy.
*/
image& operator=(const image& source);
/**
* Checks whether another image has the same number of channels and pixel size as this image.
*
* @param other Image for with which to compare compatibility.
* @return `true` if the image formats are compatible, `false` otherwise.
*/
bool compatible(const image& other) const;
/**
* Copies pixel data from another image with a compatible format into this image.
*
* @param source Source image from which to copy pixel data.
* @param w Width of the subimage to copy.
* @param h Height of the subimage to copy.
* @param from_x X-coordinate of the first pixel to copy from the source subimage.
* @param from_y Y-coordinate of the first pixel to copy from the source subimage.
* @param to_x X-coordinate of the first pixel in the destination subimage.
* @param to_y Y-coordinate of the first pixel in the destination subimage.
*
* @except std::runtime_error Cannot copy image with mismatched format.
*
* @see image::compatible(const image&) const
*/
void copy(const image& source, unsigned int w, unsigned int h, unsigned int from_x = 0, int unsigned from_y = 0, unsigned int to_x = 0, unsigned int to_y = 0);
/** /**
* Changes the format of the image. Existing pixel data will be erased if the format has changed. * Changes the format of the image. Existing pixel data will be erased if the format has changed.
* *
* @param channels Number of color channels.
* @param hdr Whether or not the image will contain HDR data. HDR pixels are stored as floats. Standard pixels are stored as unsigned chars.
* @param component_size Size of channel components, in bytes.
* @param channel_count Number of channels in the image.
*/ */
void format(unsigned int channels, bool hdr);
void format(std::size_t component_size, std::size_t channel_count);
/** /**
* Resizes the image. Existing pixel data will be erased if the size has changed. * Resizes the image. Existing pixel data will be erased if the size has changed.
@ -50,17 +89,17 @@ public:
*/ */
void resize(unsigned int width, unsigned int height); void resize(unsigned int width, unsigned int height);
/// Returns whether or not the image contains HDR data.
bool is_hdr() const;
/// Returns the width of the image, in pixels. /// Returns the width of the image, in pixels.
unsigned int get_width() const; unsigned int get_width() const;
/// Returns the height of the image, in pixels. /// Returns the height of the image, in pixels.
unsigned int get_height() const; unsigned int get_height() const;
/// Returns the size of channel components, in bytes.
std::size_t get_component_size() const;
/// Returns the number of color channels in the image. /// Returns the number of color channels in the image.
unsigned int get_channels() const;
std::size_t get_channel_count() const;
/// Returns a pointer to the pixel data. /// Returns a pointer to the pixel data.
const void* get_pixels() const; const void* get_pixels() const;
@ -68,6 +107,9 @@ public:
/// @copydoc image::get_pixels() const /// @copydoc image::get_pixels() const
void* get_pixels(); void* get_pixels();
/// Returns the size of a single pixel, in bytes.
std::size_t get_pixel_size() const;
/// Returns the size of the image, in bytes. /// Returns the size of the image, in bytes.
std::size_t get_size() const; std::size_t get_size() const;
@ -75,19 +117,15 @@ private:
void allocate_pixels(); void allocate_pixels();
void free_pixels(); void free_pixels();
bool hdr;
unsigned int width; unsigned int width;
unsigned int height; unsigned int height;
unsigned int channels;
std::size_t component_size;
std::size_t channel_count;
void* pixels; void* pixels;
std::size_t pixel_size;
std::size_t size; std::size_t size;
}; };
inline bool image::is_hdr() const
{
return hdr;
}
inline unsigned int image::get_width() const inline unsigned int image::get_width() const
{ {
return width; return width;
@ -98,9 +136,14 @@ inline unsigned int image::get_height() const
return height; return height;
} }
inline unsigned int image::get_channels() const
inline std::size_t image::get_component_size() const
{
return component_size;
}
inline std::size_t image::get_channel_count() const
{ {
return channels;
return channel_count;
} }
inline const void* image::get_pixels() const inline const void* image::get_pixels() const
@ -113,6 +156,11 @@ inline void* image::get_pixels()
return pixels; return pixels;
} }
inline std::size_t image::get_pixel_size() const
{
return pixel_size;
}
inline std::size_t image::get_size() const inline std::size_t image::get_size() const
{ {
return size; return size;

+ 6
- 6
src/resources/texture-loader.cpp View File

@ -100,30 +100,30 @@ gl::texture_2d* resource_loader::load(resource_manager* resource
::image* image = resource_manager->load<::image>(image_filename); ::image* image = resource_manager->load<::image>(image_filename);
// Determine pixel type // Determine pixel type
gl::pixel_type type = (image->is_hdr()) ? gl::pixel_type::float_32 : gl::pixel_type::uint_8;
gl::pixel_type type = (image->get_component_size() == sizeof(float)) ? gl::pixel_type::float_32 : gl::pixel_type::uint_8;
// Determine pixel format // Determine pixel format
gl::pixel_format format; gl::pixel_format format;
if (image->get_channels() == 1)
if (image->get_channel_count() == 1)
{ {
format = gl::pixel_format::r; format = gl::pixel_format::r;
} }
else if (image->get_channels() == 2)
else if (image->get_channel_count() == 2)
{ {
format = gl::pixel_format::rg; format = gl::pixel_format::rg;
} }
else if (image->get_channels() == 3)
else if (image->get_channel_count() == 3)
{ {
format = gl::pixel_format::rgb; format = gl::pixel_format::rgb;
} }
else if (image->get_channels() == 4)
else if (image->get_channel_count() == 4)
{ {
format = gl::pixel_format::rgba; format = gl::pixel_format::rgba;
} }
else else
{ {
std::stringstream stream; std::stringstream stream;
stream << std::string("Texture cannot be created from an image with an unsupported number of color channels (") << image->get_channels() << std::string(").");
stream << std::string("Texture cannot be created from an image with an unsupported number of channels (") << image->get_channel_count() << std::string(").");
delete image; delete image;
throw std::runtime_error(stream.str().c_str()); throw std::runtime_error(stream.str().c_str());
} }

+ 219
- 0
src/type/bitmap-font.cpp View File

@ -0,0 +1,219 @@
/*
* Copyright (C) 2021 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "type/bitmap-font.hpp"
#include "geom/rect-pack.hpp"
#include <stdexcept>
namespace type {
bitmap_font::bitmap_font(const font_metrics& metrics):
font(metrics)
{}
bitmap_font::bitmap_font()
{}
bool bitmap_font::contains(char32_t code) const
{
return glyphs.count(code) != 0;
}
void bitmap_font::insert(char32_t code, const bitmap_glyph& glyph)
{
glyphs[code] = glyph;
}
void bitmap_font::remove(char32_t code)
{
if (auto it = glyphs.find(code); it != glyphs.end())
glyphs.erase(it);
}
void bitmap_font::clear()
{
glyphs.clear();
}
bool bitmap_font::pack(bool resize)
{
// Returns the smallest power of two that is not smaller than @p x.
auto ceil2 = [](unsigned int x) -> unsigned int
{
if (x <= 1)
return 1;
unsigned int y = 2;
--x;
while (x >>= 1)
y <<= 1;
return y;
};
// Calculate initial size of the font bitmap
unsigned int bitmap_w;
unsigned int bitmap_h;
if (resize)
{
// Find the maximum glyph dimensions
unsigned int max_glyph_w = 0;
unsigned int max_glyph_h = 0;
for (auto it = glyphs.begin(); it != glyphs.end(); ++it)
{
max_glyph_w = std::max(max_glyph_w, it->second.bitmap.get_width());
max_glyph_h = std::max(max_glyph_h, it->second.bitmap.get_height());
}
// Find minimum power of two dimensions that can accommodate maximum glyph dimensions
bitmap_w = ceil2(max_glyph_w);
bitmap_h = ceil2(max_glyph_h);
}
else
{
bitmap_w = bitmap.get_width();
bitmap_h = bitmap.get_height();
}
bool packed = false;
geom::rect_pack<unsigned int> glyph_pack(bitmap_w, bitmap_h);
std::unordered_map<char32_t, const typename geom::rect_pack<unsigned int>::node_type*> glyph_map;
while (!packed)
{
// For each glyph
for (auto it = glyphs.begin(); it != glyphs.end(); ++it)
{
// Attempt to pack glyph bitmap
const auto* node = glyph_pack.pack(it->second.bitmap.get_width(), it->second.bitmap.get_height());
// Abort if packing failed
if (!node)
break;
// Map pack node to glyph character code
glyph_map[it->first] = node;
}
// Check if not all glyphs were packed
if (glyph_map.size() != glyphs.size())
{
if (!resize)
{
// No resize, packing failed
packed = false;
break;
}
// Clear glyph map
glyph_map.clear();
// Clear glyph pack
glyph_pack.clear();
// Resize glyph pack
if (bitmap_w > bitmap_h)
bitmap_h = ceil2(++bitmap_h);
else
bitmap_w = ceil2(++bitmap_w);
glyph_pack.resize(bitmap_w, bitmap_h);
}
else
{
packed = true;
}
}
// Copy glyph bitmaps into font bitmap
if (packed)
{
// Resize font bitmap
bitmap.resize(bitmap_w, bitmap_h);
// For each glyph
for (auto it = glyphs.begin(); it != glyphs.end(); ++it)
{
// Find rect pack node corresponding to the glyph
const auto* node = glyph_map[it->first];
// Copy glyph bitmap data into font bitmap
image& glyph_bitmap = it->second.bitmap;
bitmap.copy(glyph_bitmap, glyph_bitmap.get_width(), glyph_bitmap.get_height(), 0, 0, node->bounds.min.x, node->bounds.min.y);
// Record coordinates of glyph bitmap within font bitmap
it->second.position = {node->bounds.min.x, node->bounds.min.y};
// Clear glyph bitmap data
glyph_bitmap.resize(0, 0);
}
}
return packed;
}
void bitmap_font::unpack(bool resize)
{
for (auto it = glyphs.begin(); it != glyphs.end(); ++it)
{
bitmap_glyph& glyph = it->second;
// Get glyph dimensions
unsigned int glyph_width = static_cast<unsigned int>(glyph.metrics.width + 0.5f);
unsigned int glyph_height = static_cast<unsigned int>(glyph.metrics.height + 0.5f);
// Reformat glyph bitmap if necessary
if (!glyph.bitmap.compatible(bitmap))
glyph.bitmap.format(bitmap.get_component_size(), bitmap.get_channel_count());
// Resize glyph bitmap if necessary
if (glyph.bitmap.get_width() != glyph_width || glyph.bitmap.get_height() != glyph_height)
glyph.bitmap.resize(glyph_width, glyph_height);
// Copy pixel data from font bitmap to glyph bitmap
glyph.bitmap.copy(bitmap, glyph_width, glyph_height, glyph.position.x, glyph.position.y);
}
// Free font bitmap pixel data
if (resize)
{
bitmap.resize(0, 0);
}
}
const glyph_metrics& bitmap_font::get_glyph_metrics(char32_t code) const
{
if (auto it = glyphs.find(code); it != glyphs.end())
return it->second.metrics;
throw std::invalid_argument("Cannot fetch metrics of unknown bitmap glyph");
}
const bitmap_glyph& bitmap_font::get_glyph(char32_t code) const
{
if (auto it = glyphs.find(code); it != glyphs.end())
return it->second;
throw std::invalid_argument("Cannot get unknown bitmap glyph");
}
bitmap_glyph& bitmap_font::get_glyph(char32_t code)
{
if (auto it = glyphs.find(code); it != glyphs.end())
return it->second;
throw std::invalid_argument("Cannot get unknown bitmap glyph");
}
} // namespace type

+ 151
- 0
src/type/bitmap-font.hpp View File

@ -0,0 +1,151 @@
/*
* Copyright (C) 2021 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_TYPE_BITMAP_FONT_HPP
#define ANTKEEPER_TYPE_BITMAP_FONT_HPP
#include "type/font.hpp"
#include "type/bitmap-glyph.hpp"
#include "resources/image.hpp"
#include <unordered_map>
namespace type {
/**
* Raster font in which glyphs are stored as arrays of pixels.
*
* @see type::font
* @see type::font_metrics
* @see type::bitmap_glyph
* @see image
*/
class bitmap_font: public font
{
public:
/**
* Creates a bitmap font and sets its metrics.
*
* @param metrics Metrics describing the font.
*/
bitmap_font(const font_metrics& metrics);
/// Creates an empty bitmap font.
bitmap_font();
/// Destroys a bitmap font.
virtual ~bitmap_font() = default;
/// @copydoc font::contains(char32_t) const
virtual bool contains(char32_t code) const;
/**
* Inserts a glyph into the font.
*
* @param code UTF-32 character code of the glyph to insert.
* @param glyph Bitmap glyph data.
*/
void insert(char32_t code, const bitmap_glyph& glyph);
/**
* Removes a glyph from the font.
*
* @param code UTF-32 character code of the glyph to remove.
*/
void remove(char32_t code);
/**
* Removes all glyphs from the font.
*/
void clear();
/**
* Packs all glyph bitmaps into the font bitmap.
*
* @param resize Automatically resize the font bitmap to contain all glyphs. Bitmap size will start at the closest power of two to the largest glyph, then its dimensions will increase to the next power of two until its large enough that all glyphs can be contained.
* @return `true` if all glyphs were successfully packed, `false` otherwise.
*
* @except std::runtime_error Glyph bitmap format doesn't match font bitmap format.
* @except std::runtime_error Not enough space in font bitmap to pack glyph.
*/
bool pack(bool resize = true);
/**
* Unpacks all glyph bitmaps from the font bitmap.
*
* @param resize Automatically resizes the font bitmap to zero.
*/
void unpack(bool resize = true);
/// Returns a reference to the bitmap containing glyph pixel data.
const image& get_bitmap() const;
/// @copydoc bitmap_font::get_bitmap() const
image& get_bitmap();
/**
* @copydoc font::get_glyph_metrics(char32_t) const
*
* @except std::invalid_argument Cannot fetch metrics of unknown bitmap glyph
*/
virtual const glyph_metrics& get_glyph_metrics(char32_t code) const;
/**
* Returns a reference to the glyph corresponding to a UTF-32 character code.
*
* @param code UTF-32 character code of a glyph.
* @return Reference to the corresponding glyph.
*
* @except std::invalid_argument Cannot get unknown bitmap glyph
*/
const bitmap_glyph& get_glyph(char32_t code) const;
/// @copydoc bitmap_font::get_glyph(char32_t) const
bitmap_glyph& get_glyph(char32_t code);
/**
* Returns a reference to the glyph corresponding to a UTF-32 character code, performing an insertion if such glyph does not already exist.
*
* @param code UTF-32 character code of a glyph.
* @return Reference to the corresponding glyph.
*/
bitmap_glyph& operator[](char32_t code);
private:
std::unordered_map<char32_t, bitmap_glyph> glyphs;
image bitmap;
};
inline const image& bitmap_font::get_bitmap() const
{
return bitmap;
}
inline image& bitmap_font::get_bitmap()
{
return bitmap;
}
inline bitmap_glyph& bitmap_font::operator[](char32_t code)
{
return glyphs[code];
}
} // namespace type
#endif // ANTKEEPER_TYPE_BITMAP_FONT_HPP

+ 48
- 0
src/type/bitmap-glyph.hpp View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2021 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_TYPE_BITMAP_GLYPH_HPP
#define ANTKEEPER_TYPE_BITMAP_GLYPH_HPP
#include "resources/image.hpp"
#include "type/glyph-metrics.hpp"
#include "utility/fundamental-types.hpp"
namespace type {
/**
* Single glyph in a bitmap font.
*
* @see type::bitmap_font
*/
struct bitmap_glyph
{
/// Metrics describing the glyph.
glyph_metrics metrics;
/// Bitmap representing the glyph.
image bitmap;
/// Position of the packed glyph bitmap within the font bitmap.
uint2 position;
};
} // namespace type
#endif // ANTKEEPER_TYPE_BITMAP_GLYPH_HPP

+ 51
- 0
src/type/font-metrics.hpp View File

@ -0,0 +1,51 @@
/*
* Copyright (C) 2021 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_TYPE_FONT_METRICS_HPP
#define ANTKEEPER_TYPE_FONT_METRICS_HPP
namespace type {
/**
* Metrics describing properties of a font.
*/
struct font_metrics
{
/// Positive distance from the baseline to the highest or upper grid coordinate.
float ascent;
/// Negative distance from the baseline to the lowest grid coordinate.
float descent;
/// Distance that must be placed between two lines of text.
float linegap;
/// Baseline-to-baseline distance, computed as ascent - descent + linegap`.
float linespace;
/// Vertical position of an underline.
float underline_position;
/// Thickness of an underline.
float underline_thickness;
};
} // namespace type
#endif // ANTKEEPER_TYPE_FONT_METRICS_HPP

+ 29
- 0
src/type/font.cpp View File

@ -21,5 +21,34 @@
namespace type { namespace type {
font::font(const font_metrics& metrics):
metrics(metrics)
{}
font::font()
{}
font::~font()
{}
void font::kern(char32_t first, char32_t second, const float2& offset)
{
kerning_table[first][second] = offset;
}
void font::set_font_metrics(const font_metrics& metrics)
{
this->metrics = metrics;
}
const float2& font::get_kerning(char32_t first, char32_t second) const
{
if (auto it_first = kerning_table.find(first); it_first != kerning_table.end())
if (auto it_second = it_first->second.find(second); it_second != it_first->second.end())
return it_second->second;
static const float2 no_kerning = {0.0f, 0.0f};
return no_kerning;
}
} // namespace type } // namespace type

+ 80
- 4
src/type/font.hpp View File

@ -20,21 +20,97 @@
#ifndef ANTKEEPER_TYPE_FONT_HPP #ifndef ANTKEEPER_TYPE_FONT_HPP
#define ANTKEEPER_TYPE_FONT_HPP #define ANTKEEPER_TYPE_FONT_HPP
#include <string>
#include "type/kerning-table.hpp"
#include "type/font-metrics.hpp"
#include "type/glyph-metrics.hpp"
namespace type { namespace type {
/**
* Abstract base class for fonts.
*
* @see type::font_metrics
* @see type::glyph_metrics
* @see type::bitmap_font
*/
class font class font
{ {
public: public:
//font();
/**
* Creates a font and sets its metrics.
*
* @param metrics Metrics describing the font.
*/
font(const font_metrics& metrics);
/// Creates an empty font.
font();
/// Destroys a font.
virtual ~font();
/**
* Returns `true` if the font contains a glyph with the given character code.
*
* @param code UTF-32 character code of a glyph.
* @return `true` if the font contains the glyph, `false` otherwise.
*/
virtual bool contains(char32_t code) const = 0;
/**
* Sets the kerning offset for a pair of glyphs.
*
* @param first UTF-32 character code of the first glyph.
* @param second UTF-32 character code of the second glyph.
* @param offset Kerning offset.
*/
void kern(char32_t first, char32_t second, const float2& offset);
/**
* Sets the font metrics
*
* @param metrics Font metrics.
*/
void set_font_metrics(const font_metrics& metrics);
//float measure(const std::u32string& text) const;
/**
* Returns metrics describing a glyph.
*
* @param code UTF-32 character code of a glyph.
* @return Metrics describing the glyph.
*/
virtual const glyph_metrics& get_glyph_metrics(char32_t code) const = 0;
private:
/**
* Returns the kerning offset for a pair of glyphs.
*
* @param first UTF-32 character code of the first glyph.
* @param second UTF-32 character code of the second glyph.
* @return Kerning offset.
*/
const float2& get_kerning(char32_t first, char32_t second) const;
/// Returns the font's kerning table.
const kerning_table& get_kerning_table() const;
/// Returns metrics describing the font.
const font_metrics& get_font_metrics() const;
protected:
font_metrics metrics;
kerning_table kerning_table;
}; };
inline const kerning_table& font::get_kerning_table() const
{
return kerning_table;
}
inline const font_metrics& font::get_font_metrics() const
{
return metrics;
}
} // namespace type } // namespace type
#endif // ANTKEEPER_TYPE_FONT_HPP #endif // ANTKEEPER_TYPE_FONT_HPP

+ 48
- 0
src/type/glyph-metrics.hpp View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2021 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_TYPE_GLYPH_METRICS_HPP
#define ANTKEEPER_TYPE_GLYPH_METRICS_HPP
namespace type {
/**
* Metrics describing properties of a glyph.
*/
struct glyph_metrics
{
/// Horizontal extent of the glyph.
float width;
/// Vertical extent of the glyph.
float height;
/// Horizontal distance from the pen position to the glyph's left edge.
float bearing_left;
/// Vertical distance from the baseline to the glyph's top edge.
float bearing_top;
/// Distance to move the pen position after the glyph has been rendered.
float advance;
};
} // namespace type
#endif // ANTKEEPER_TYPE_GLYPH_METRICS_HPP

+ 33
- 0
src/type/kerning-table.hpp View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2021 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_TYPE_KERNING_TABLE_HPP
#define ANTKEEPER_TYPE_KERNING_TABLE_HPP
#include "utility/fundamental-types.hpp"
#include <unordered_map>
namespace type {
/// Table containing kerning offsets for pairs of glyphs.
typedef std::unordered_map<char32_t, std::unordered_map<char32_t, float2>> kerning_table;
} // namespace type
#endif // ANTKEEPER_TYPE_KERNING_TABLE_HPP

+ 5
- 0
src/type/type.hpp View File

@ -23,7 +23,12 @@
/// Text and typography. /// Text and typography.
namespace type {} namespace type {}
#include "type/bitmap-font.hpp"
#include "type/bitmap-glyph.hpp"
#include "type/font.hpp" #include "type/font.hpp"
#include "type/font-metrics.hpp"
#include "type/glyph-metrics.hpp"
#include "type/kerning-table.hpp"
#include "type/unicode/unicode.hpp" #include "type/unicode/unicode.hpp"
#endif // ANTKEEPER_TYPE_HPP #endif // ANTKEEPER_TYPE_HPP

+ 52
- 0
src/type/typeface.hpp View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2021 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_TYPE_TYPEFACE_HPP
#define ANTKEEPER_TYPE_TYPEFACE_HPP
namespace type {
enum class typeface_style
{
normal,
italic,
oblique
};
/**
* Variant a typeface family, with a specific style and weight. A typeface corresponds to a single digital font file.
*
* @see type::font
*/
class typeface
{
public:
typeface(typeface_style style, int weight);
typeface_style get_style() const;
int get_weight() const;
private:
typeface_style style;
int weight;
};
} // namespace type
#endif // ANTKEEPER_TYPE_TYPEFACE_HPP

Loading…
Cancel
Save