From c428d80e48f462c3fdc40588620ccb182ed7c429 Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Sat, 9 Oct 2021 17:36:40 +0800 Subject: [PATCH] Add bitmap font class, improve image class, and rect and rect pack classes --- CMakeLists.txt | 1 + src/application.cpp | 2 +- src/game/states/loading.cpp | 76 ++++++++ src/geom/rect-pack.hpp | 239 ++++++++++++++++++++++++++ src/{type/glyph.hpp => geom/rect.hpp} | 26 +-- src/resources/image-loader.cpp | 7 +- src/resources/image.cpp | 98 ++++++++--- src/resources/image.hpp | 80 +++++++-- src/resources/texture-loader.cpp | 12 +- src/type/bitmap-font.cpp | 219 +++++++++++++++++++++++ src/type/bitmap-font.hpp | 151 ++++++++++++++++ src/type/bitmap-glyph.hpp | 48 ++++++ src/type/font-metrics.hpp | 51 ++++++ src/type/font.cpp | 29 ++++ src/type/font.hpp | 84 ++++++++- src/type/glyph-metrics.hpp | 48 ++++++ src/type/kerning-table.hpp | 33 ++++ src/type/type.hpp | 5 + src/type/typeface.hpp | 52 ++++++ 19 files changed, 1193 insertions(+), 68 deletions(-) create mode 100644 src/geom/rect-pack.hpp rename src/{type/glyph.hpp => geom/rect.hpp} (72%) create mode 100644 src/type/bitmap-font.cpp create mode 100644 src/type/bitmap-font.hpp create mode 100644 src/type/bitmap-glyph.hpp create mode 100644 src/type/font-metrics.hpp create mode 100644 src/type/glyph-metrics.hpp create mode 100644 src/type/kerning-table.hpp create mode 100644 src/type/typeface.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d4aa351..af368e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,6 @@ cmake_minimum_required(VERSION 3.7) + option(VERSION_STRING "Project version string" "0.0.0") project(antkeeper VERSION ${VERSION_STRING} LANGUAGES CXX) diff --git a/src/application.cpp b/src/application.cpp index f1848f9..34a7ad6 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -391,7 +391,7 @@ void application::save_frame(const std::string& path) const [frame, path] { 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(); diff --git a/src/game/states/loading.cpp b/src/game/states/loading.cpp index 7449b6b..3e6c11f 100644 --- a/src/game/states/loading.cpp +++ b/src/game/states/loading.cpp @@ -50,6 +50,9 @@ #include "scene/ambient-light.hpp" #include "scene/directional-light.hpp" #include "utility/timestamp.hpp" +#include "type/type.hpp" +#include +#include namespace game { namespace state { @@ -118,6 +121,79 @@ void enter(game::context* 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 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(ascent) * scale; + float scaled_descent = static_cast(descent) * scale; + float scaled_linegap = static_cast(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(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(glyph_width); + glyph.metrics.height = static_cast(glyph_height); + glyph.metrics.bearing_left = static_cast(left_side_bearing) * scale; + glyph.metrics.bearing_top = 0.0f; + glyph.metrics.advance = static_cast(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 ctx->app->queue_state(next_state); } diff --git a/src/geom/rect-pack.hpp b/src/geom/rect-pack.hpp new file mode 100644 index 0000000..a83e375 --- /dev/null +++ b/src/geom/rect-pack.hpp @@ -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 . + */ + +#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 +struct rect_pack_node +{ + /// Scalar type. + typedef T scalar_type; + + /// Rect type. + typedef rect 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 +rect_pack_node::rect_pack_node(): + bounds{T(0), T(0), T(0), T(0)}, + occupied(false) +{ + children[0] = nullptr; + children[1] = nullptr; +} + +template +rect_pack_node::~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 rect_pack +{ +public: + /// Scalar type. + typedef T scalar_type; + + /// Node type. + typedef rect_pack_node 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 +rect_pack::rect_pack(scalar_type w, scalar_type h) +{ + root.bounds = {T(0), T(0), w, h}; +} + +template +rect_pack::rect_pack(): + rect_pack(0, 0) +{} + +template +void rect_pack::resize(scalar_type w, scalar_type h) +{ + clear(); + root.bounds = {T(0), T(0), w, h}; +} + +template +void rect_pack::clear() +{ + delete root.children[0]; + delete root.children[1]; + root.children[0] = nullptr; + root.children[1] = nullptr; + root.occupied = false; +} + +template +const typename rect_pack::node_type* rect_pack::pack(scalar_type w, scalar_type h) +{ + return insert(root, w, h); +} + +template +inline const typename rect_pack::node_type& rect_pack::get_root() const +{ + return root; +} + +template +typename rect_pack::node_type* rect_pack::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 diff --git a/src/type/glyph.hpp b/src/geom/rect.hpp similarity index 72% rename from src/type/glyph.hpp rename to src/geom/rect.hpp index 77e45a1..391940b 100644 --- a/src/type/glyph.hpp +++ b/src/geom/rect.hpp @@ -17,23 +17,25 @@ * along with Antkeeper source code. If not, see . */ -#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 +struct rect { - float2 size; - float2 bearing_h; - float2 bearing_v; - float advance_h; - float advance_v; + typedef math::vector vector_type; + + vector_type min; + vector_type max; }; -} // namespace type +} // namespace geom -#endif // ANTKEEPER_TYPE_GLYPH_HPP +#endif // ANTKEEPER_GEOM_RECT_HPP diff --git a/src/resources/image-loader.cpp b/src/resources/image-loader.cpp index 810bbaa..e967395 100644 --- a/src/resources/image-loader.cpp +++ b/src/resources/image-loader.cpp @@ -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."); } - + + std::size_t component_size = (hdr) ? sizeof(float) : sizeof(unsigned char); ::image* image = new ::image(); - image->format(static_cast(channels), hdr); + image->format(component_size, channels); image->resize(static_cast(width), static_cast(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 stbi_image_free(pixels); diff --git a/src/resources/image.cpp b/src/resources/image.cpp index f3cb648..0fd828a 100644 --- a/src/resources/image.cpp +++ b/src/resources/image.cpp @@ -18,12 +18,20 @@ */ #include "image.hpp" +#include +#include + +image::image(const image& source) +{ + *this = source; +} image::image(): - hdr(false), width(0), height(0), - channels(4), + component_size(0), + channel_count(0), + pixel_size(0), pixels(nullptr), size(0) {} @@ -33,16 +41,70 @@ image::~image() 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(source.pixels); + unsigned char* to_pixels = static_cast(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(); - 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(); } @@ -56,23 +118,15 @@ void image::resize(unsigned int width, unsigned int height) free_pixels(); this->width = width; this->height = height; + size = width * height * pixel_size; allocate_pixels(); } void image::allocate_pixels() { - size = width * height * channels; - 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 (hdr) - { - delete[] reinterpret_cast(pixels); - } - else - { - delete[] reinterpret_cast(pixels); - } - + delete[] reinterpret_cast(pixels); pixels = nullptr; size = 0; } diff --git a/src/resources/image.hpp b/src/resources/image.hpp index ba2fa1f..e0c6824 100644 --- a/src/resources/image.hpp +++ b/src/resources/image.hpp @@ -28,19 +28,58 @@ class image { public: + /** + * Creates a copy of another image. + * + * @param source Image from which to copy. + */ + image(const image& source); + /// Creates an image. image(); /// Destroys an 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. * - * @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. @@ -50,17 +89,17 @@ public: */ 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. unsigned int get_width() const; /// Returns the height of the image, in pixels. 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. - unsigned int get_channels() const; + std::size_t get_channel_count() const; /// Returns a pointer to the pixel data. const void* get_pixels() const; @@ -68,6 +107,9 @@ public: /// @copydoc image::get_pixels() const 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. std::size_t get_size() const; @@ -75,19 +117,15 @@ private: void allocate_pixels(); void free_pixels(); - bool hdr; unsigned int width; unsigned int height; - unsigned int channels; + std::size_t component_size; + std::size_t channel_count; void* pixels; + std::size_t pixel_size; std::size_t size; }; -inline bool image::is_hdr() const -{ - return hdr; -} - inline unsigned int image::get_width() const { return width; @@ -98,9 +136,14 @@ inline unsigned int image::get_height() const 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 @@ -113,6 +156,11 @@ inline void* image::get_pixels() return pixels; } +inline std::size_t image::get_pixel_size() const +{ + return pixel_size; +} + inline std::size_t image::get_size() const { return size; diff --git a/src/resources/texture-loader.cpp b/src/resources/texture-loader.cpp index 541262c..34fa8ea 100644 --- a/src/resources/texture-loader.cpp +++ b/src/resources/texture-loader.cpp @@ -100,30 +100,30 @@ gl::texture_2d* resource_loader::load(resource_manager* resource ::image* image = resource_manager->load<::image>(image_filename); // 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 gl::pixel_format format; - if (image->get_channels() == 1) + if (image->get_channel_count() == 1) { format = gl::pixel_format::r; } - else if (image->get_channels() == 2) + else if (image->get_channel_count() == 2) { format = gl::pixel_format::rg; } - else if (image->get_channels() == 3) + else if (image->get_channel_count() == 3) { format = gl::pixel_format::rgb; } - else if (image->get_channels() == 4) + else if (image->get_channel_count() == 4) { format = gl::pixel_format::rgba; } else { 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; throw std::runtime_error(stream.str().c_str()); } diff --git a/src/type/bitmap-font.cpp b/src/type/bitmap-font.cpp new file mode 100644 index 0000000..0d533a3 --- /dev/null +++ b/src/type/bitmap-font.cpp @@ -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 . + */ + +#include "type/bitmap-font.hpp" +#include "geom/rect-pack.hpp" +#include + +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 glyph_pack(bitmap_w, bitmap_h); + std::unordered_map::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(glyph.metrics.width + 0.5f); + unsigned int glyph_height = static_cast(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 diff --git a/src/type/bitmap-font.hpp b/src/type/bitmap-font.hpp new file mode 100644 index 0000000..3b875ad --- /dev/null +++ b/src/type/bitmap-font.hpp @@ -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 . + */ + +#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 + +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 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 diff --git a/src/type/bitmap-glyph.hpp b/src/type/bitmap-glyph.hpp new file mode 100644 index 0000000..4bba4c3 --- /dev/null +++ b/src/type/bitmap-glyph.hpp @@ -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 . + */ + +#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 diff --git a/src/type/font-metrics.hpp b/src/type/font-metrics.hpp new file mode 100644 index 0000000..d4546e5 --- /dev/null +++ b/src/type/font-metrics.hpp @@ -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 . + */ + +#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 diff --git a/src/type/font.cpp b/src/type/font.cpp index b91bede..48935f7 100644 --- a/src/type/font.cpp +++ b/src/type/font.cpp @@ -21,5 +21,34 @@ 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 diff --git a/src/type/font.hpp b/src/type/font.hpp index 0874545..f3cbf66 100644 --- a/src/type/font.hpp +++ b/src/type/font.hpp @@ -20,21 +20,97 @@ #ifndef ANTKEEPER_TYPE_FONT_HPP #define ANTKEEPER_TYPE_FONT_HPP -#include +#include "type/kerning-table.hpp" +#include "type/font-metrics.hpp" +#include "type/glyph-metrics.hpp" namespace type { +/** + * Abstract base class for fonts. + * + * @see type::font_metrics + * @see type::glyph_metrics + * @see type::bitmap_font + */ class font { 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 #endif // ANTKEEPER_TYPE_FONT_HPP diff --git a/src/type/glyph-metrics.hpp b/src/type/glyph-metrics.hpp new file mode 100644 index 0000000..80c3a1e --- /dev/null +++ b/src/type/glyph-metrics.hpp @@ -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 . + */ + +#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 diff --git a/src/type/kerning-table.hpp b/src/type/kerning-table.hpp new file mode 100644 index 0000000..6235b0f --- /dev/null +++ b/src/type/kerning-table.hpp @@ -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 . + */ + +#ifndef ANTKEEPER_TYPE_KERNING_TABLE_HPP +#define ANTKEEPER_TYPE_KERNING_TABLE_HPP + +#include "utility/fundamental-types.hpp" +#include + +namespace type { + +/// Table containing kerning offsets for pairs of glyphs. +typedef std::unordered_map> kerning_table; + +} // namespace type + +#endif // ANTKEEPER_TYPE_KERNING_TABLE_HPP diff --git a/src/type/type.hpp b/src/type/type.hpp index bf12988..feeb910 100644 --- a/src/type/type.hpp +++ b/src/type/type.hpp @@ -23,7 +23,12 @@ /// Text and typography. namespace type {} +#include "type/bitmap-font.hpp" +#include "type/bitmap-glyph.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" #endif // ANTKEEPER_TYPE_HPP diff --git a/src/type/typeface.hpp b/src/type/typeface.hpp new file mode 100644 index 0000000..8c3b457 --- /dev/null +++ b/src/type/typeface.hpp @@ -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 . + */ + +#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