├── CMakeLists.txt ├── CMakeModules ├── FindGLM.cmake └── FindSFML.cmake ├── Doxyfile.in ├── LICENSE ├── README.md ├── demo ├── CMakeLists.txt └── demo.cpp ├── include └── textogl │ ├── font.hpp │ ├── static_text.hpp │ └── types.hpp ├── src ├── CMakeLists.txt ├── font.cpp ├── font_common.cpp ├── font_impl.hpp ├── shaders │ ├── font.gl33.frag │ ├── font.gl33.vert │ ├── font.gles20.frag │ ├── font.gles20.vert │ └── shaders.inl.in └── static_text.cpp ├── textogl-config-version.cmake.in ├── textogl-config.cmake.in └── textogl.pc.in /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | project(textogl) 3 | 4 | set(TITLE "Textogl") 5 | set(AUTHOR "Matthew Chandler ") 6 | set(COPYRIGHT "2022 Matthew Chandler") 7 | set(SUMMARY "OpenGL Text renderer") 8 | set(WEBSITE "http://github.com/mattvchandler/${PROJECT_NAME}") 9 | 10 | set(VERSION_MAJOR 1) 11 | set(VERSION_MINOR 1) 12 | set(VERSION_PATCH 2) 13 | 14 | set(VERSION_SHORT "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") 15 | 16 | if(NOT CMAKE_BUILD_TYPE) 17 | set(CMAKE_BUILD_TYPE "Release") 18 | endif() 19 | 20 | if (CMAKE_VERSION VERSION_LESS "3.1") 21 | add_compile_options(-std=c++11) 22 | else() 23 | set(CMAKE_CXX_STANDARD 11) 24 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 25 | endif() 26 | 27 | add_compile_options(-Wall -Wextra) 28 | 29 | set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG") 30 | set(CMAKE_CXX_FLAGS_DEBUG "-g -DDEBUG") 31 | 32 | # libraries 33 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules) 34 | 35 | option(USE_GLM "Search for GLM and use that instead of internal Color / Vec2" ON) 36 | 37 | add_subdirectory(src) 38 | 39 | option(BUILD_DEMO "build a demo / test program" OFF) 40 | if(BUILD_DEMO) 41 | add_subdirectory(demo) 42 | endif() 43 | 44 | # Doxygen documentation 45 | option(TEXTOGL_GEN_DOCS "Generate 'doc' target" ON) 46 | option(TEXTOGL_INTERNAL_DOCS "Generate documentation for private/internal methods, members, and functions" OFF) 47 | 48 | if(TEXTOGL_GEN_DOCS) 49 | find_package(Doxygen) 50 | if(DOXYGEN_FOUND) 51 | 52 | file(GENERATE OUTPUT ${PROJECT_BINARY_DIR}/doxygen_gen.cmake CONTENT 53 | "set(PROJECT_NAME \"${PROJECT_NAME}\") 54 | set(TITLE \"${TITLE}\") 55 | set(SUMMARY \"${SUMMARY}\") 56 | set(VERSION_SHORT \"${VERSION_SHORT}\") 57 | set(CMAKE_INSTALL_PREFIX \"${CMAKE_INSTALL_PREFIX}\") 58 | set(CMAKE_CURRENT_SOURCE_DIR \"${CMAKE_CURRENT_SOURCE_DIR}\") 59 | set(TEXTOGL_INTERNAL_DOCS ${TEXTOGL_INTERNAL_DOCS}) 60 | 61 | if(TEXTOGL_INTERNAL_DOCS) 62 | set(TEXTOGL_EXTRACT_INTERNAL_DOCS \"YES\") 63 | set(TEXTOGL_NO_EXTRACT_INTERNAL_DOCS \"NO\") 64 | set(TEXTOGL_DOC_FILE_PATTERNS \"*.cpp *.hpp *.md\") 65 | set(TEXTOGL_DOC_ENABLED_SECTIONS \"INTERNAL\") 66 | else() 67 | set(TEXTOGL_EXTRACT_INTERNAL_DOCS \"NO\") 68 | set(TEXTOGL_NO_EXTRACT_INTERNAL_DOCS \"YES\") 69 | set(TEXTOGL_DOC_FILE_PATTERNS \"*.hpp *.md\") 70 | set(TEXTOGL_DOC_ENABLED_SECTIONS \"\") 71 | endif() 72 | 73 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in 74 | ${PROJECT_BINARY_DIR}/Doxyfile) 75 | ") 76 | 77 | add_custom_command( 78 | OUTPUT ${PROJECT_BINARY_DIR}/Doxyfile 79 | COMMAND ${CMAKE_COMMAND} -P ${PROJECT_BINARY_DIR}/doxygen_gen.cmake 80 | DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) 81 | 82 | add_custom_command( 83 | OUTPUT ${PROJECT_BINARY_DIR}/doc/html/index.html 84 | COMMAND ${DOXYGEN_EXECUTABLE} ${PROJECT_BINARY_DIR}/Doxyfile 85 | WORKING_DIRECTORY ${PROJECT_BINARY_DIR} 86 | DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in 87 | DEPENDS ${PROJECT_BINARY_DIR}/Doxyfile 88 | DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/README.md 89 | DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/include/${PROJECT_NAME}/*.hpp 90 | DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp 91 | COMMENT "Generating documentation with Doxygen") 92 | 93 | add_custom_target(doc DEPENDS ${PROJECT_BINARY_DIR}/doc/html/index.html) 94 | 95 | set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES doc) 96 | endif() 97 | endif() 98 | 99 | # pkg-config and cmake 'find_package' files 100 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}-config.cmake.in 101 | ${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config.cmake 102 | @ONLY) 103 | 104 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}-config-version.cmake.in 105 | ${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake 106 | @ONLY) 107 | 108 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.pc.in 109 | ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.pc 110 | @ONLY) 111 | 112 | # installation 113 | 114 | install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/${PROJECT_NAME}" DESTINATION "include") 115 | 116 | install(FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" DESTINATION "lib/cmake/${PROJECT_NAME}") 117 | install(FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake" DESTINATION "lib/cmake/${PROJECT_NAME}") 118 | install(FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}.pc" DESTINATION "share/pkgconfig") 119 | 120 | # CPack vars 121 | set(CPACK_GENERATOR TGZ) 122 | set(CPACK_PACKAGE_CONTACT "${AUTHOR}") 123 | set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY) 124 | set(CPACK_PACKAGE_NAME "lib${PROJECT_NAME}-dev") 125 | set(CPACK_PACKAGE_VERSION_MAJOR "${VERSION_MAJOR}") 126 | set(CPACK_PACKAGE_VERSION_MINOR "${VERSION_MINOR}") 127 | set(CPACK_PACKAGE_VERSION_PATCH "${VERSION_PATCH}") 128 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${TITLE}: ${SUMMARY}") 129 | set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md") 130 | set(CPACK_PACKAGE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") 131 | set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") 132 | set(CPACK_PACKAGE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") 133 | set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") 134 | 135 | set(CPACK_SOURCE_IGNORE_FILES 136 | ${PROJECT_BINARY_DIR}/* 137 | ${CMAKE_CURRENT_SOURCE_DIR}/.git/* 138 | ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME} 139 | ) 140 | 141 | # Debian-specific settings 142 | if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") 143 | find_program(DPKG "dpkg") 144 | if(DPKG) 145 | set(CPACK_GENERATOR DEB;TGZ) 146 | execute_process(COMMAND ${DPKG} --print-architecture 147 | OUTPUT_STRIP_TRAILING_WHITESPACE 148 | OUTPUT_VARIABLE CPACK_DEBIAN_PACKAGE_ARCHITECTURE) 149 | set(CPACK_DEBIAN_PACKAGE_SECTION "devel") 150 | string(TOLOWER "${CPACK_PACKAGE_NAME}" CPACK_DEBIAN_PACKAGE_NAME) 151 | set(CPACK_PACKAGE_FILE_NAME 152 | "${CPACK_DEBIAN_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}") 153 | endif() 154 | endif() 155 | 156 | include(CPack) 157 | -------------------------------------------------------------------------------- /CMakeModules/FindGLM.cmake: -------------------------------------------------------------------------------- 1 | # FindGLM - attempts to locate the glm matrix/vector library. 2 | # 3 | # This module defines the following variables (on success): 4 | # GLM_INCLUDE_DIRS - where to find glm/glm.hpp 5 | # GLM_FOUND - if the library was successfully located 6 | # 7 | # It is trying a few standard installation locations, but can be customized 8 | # with the following variables: 9 | # GLM_ROOT_DIR - root directory of a glm installation 10 | # Headers are expected to be found in either: 11 | # /glm/glm.hpp OR 12 | # /include/glm/glm.hpp 13 | # This variable can either be a cmake or environment 14 | # variable. Note however that changing the value 15 | # of the environment varible will NOT result in 16 | # re-running the header search and therefore NOT 17 | # adjust the variables set by this module. 18 | 19 | #============================================================================= 20 | # Copyright 2012 Carsten Neumann 21 | # 22 | # Distributed under the OSI-approved BSD License (the "License"); 23 | # see accompanying file Copyright.txt for details. 24 | # 25 | # This software is distributed WITHOUT ANY WARRANTY; without even the 26 | # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 27 | # See the License for more information. 28 | #============================================================================= 29 | # (To distribute this file outside of CMake, substitute the full 30 | # License text for the above reference.) 31 | 32 | # default search dirs 33 | SET(_glm_HEADER_SEARCH_DIRS 34 | "/usr/include" 35 | "/usr/local/include") 36 | 37 | # check environment variable 38 | SET(_glm_ENV_ROOT_DIR "$ENV{GLM_ROOT_DIR}") 39 | 40 | IF(NOT GLM_ROOT_DIR AND _glm_ENV_ROOT_DIR) 41 | SET(GLM_ROOT_DIR "${_glm_ENV_ROOT_DIR}") 42 | ENDIF(NOT GLM_ROOT_DIR AND _glm_ENV_ROOT_DIR) 43 | 44 | # put user specified location at beginning of search 45 | IF(GLM_ROOT_DIR) 46 | SET(_glm_HEADER_SEARCH_DIRS "${GLM_ROOT_DIR}" 47 | "${GLM_ROOT_DIR}/include" 48 | ${_glm_HEADER_SEARCH_DIRS}) 49 | ENDIF(GLM_ROOT_DIR) 50 | 51 | # locate header 52 | FIND_PATH(GLM_INCLUDE_DIR "glm/glm.hpp" 53 | PATHS ${_glm_HEADER_SEARCH_DIRS}) 54 | 55 | INCLUDE(FindPackageHandleStandardArgs) 56 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(GLM DEFAULT_MSG 57 | GLM_INCLUDE_DIR) 58 | 59 | IF(GLM_FOUND) 60 | SET(GLM_INCLUDE_DIRS "${GLM_INCLUDE_DIR}") 61 | 62 | MESSAGE(STATUS "GLM_INCLUDE_DIR = ${GLM_INCLUDE_DIR}") 63 | ENDIF(GLM_FOUND) 64 | -------------------------------------------------------------------------------- /CMakeModules/FindSFML.cmake: -------------------------------------------------------------------------------- 1 | # This script locates the SFML library 2 | # ------------------------------------ 3 | # 4 | # Usage 5 | # ----- 6 | # 7 | # When you try to locate the SFML libraries, you must specify which modules you want to use (system, window, graphics, network, audio, main). 8 | # If none is given, the SFML_LIBRARIES variable will be empty and you'll end up linking to nothing. 9 | # example: 10 | # find_package(SFML COMPONENTS graphics window system) // find the graphics, window and system modules 11 | # 12 | # You can enforce a specific version, either MAJOR.MINOR or only MAJOR. 13 | # If nothing is specified, the version won't be checked (ie. any version will be accepted). 14 | # example: 15 | # find_package(SFML COMPONENTS ...) // no specific version required 16 | # find_package(SFML 2 COMPONENTS ...) // any 2.x version 17 | # find_package(SFML 2.4 COMPONENTS ...) // version 2.4 or greater 18 | # 19 | # By default, the dynamic libraries of SFML will be found. To find the static ones instead, 20 | # you must set the SFML_STATIC_LIBRARIES variable to TRUE before calling find_package(SFML ...). 21 | # In case of static linking, the SFML_STATIC macro will also be defined by this script. 22 | # example: 23 | # set(SFML_STATIC_LIBRARIES TRUE) 24 | # find_package(SFML 2 COMPONENTS network system) 25 | # 26 | # On Mac OS X if SFML_STATIC_LIBRARIES is not set to TRUE then by default CMake will search for frameworks unless 27 | # CMAKE_FIND_FRAMEWORK is set to "NEVER" for example. Please refer to CMake documentation for more details. 28 | # Moreover, keep in mind that SFML frameworks are only available as release libraries unlike dylibs which 29 | # are available for both release and debug modes. 30 | # 31 | # If SFML is not installed in a standard path, you can use the SFML_ROOT CMake (or environment) variable 32 | # to tell CMake where SFML is. 33 | # 34 | # Output 35 | # ------ 36 | # 37 | # This script defines the following variables: 38 | # - For each specified module XXX (system, window, graphics, network, audio, main): 39 | # - SFML_XXX_LIBRARY_DEBUG: the name of the debug library of the xxx module (set to SFML_XXX_LIBRARY_RELEASE is no debug version is found) 40 | # - SFML_XXX_LIBRARY_RELEASE: the name of the release library of the xxx module (set to SFML_XXX_LIBRARY_DEBUG is no release version is found) 41 | # - SFML_XXX_LIBRARY: the name of the library to link to for the xxx module (includes both debug and optimized names if necessary) 42 | # - SFML_XXX_FOUND: true if either the debug or release library of the xxx module is found 43 | # - SFML_LIBRARIES: the list of all libraries corresponding to the required modules 44 | # - SFML_FOUND: true if all the required modules are found 45 | # - SFML_INCLUDE_DIR: the path where SFML headers are located (the directory containing the SFML/Config.hpp file) 46 | # 47 | # example: 48 | # find_package(SFML 2 COMPONENTS system window graphics audio REQUIRED) 49 | # include_directories(${SFML_INCLUDE_DIR}) 50 | # add_executable(myapp ...) 51 | # target_link_libraries(myapp ${SFML_LIBRARIES}) 52 | 53 | # define the SFML_STATIC macro if static build was chosen 54 | if(SFML_STATIC_LIBRARIES) 55 | add_definitions(-DSFML_STATIC) 56 | endif() 57 | 58 | # deduce the libraries suffix from the options 59 | set(FIND_SFML_LIB_SUFFIX "") 60 | if(SFML_STATIC_LIBRARIES) 61 | set(FIND_SFML_LIB_SUFFIX "${FIND_SFML_LIB_SUFFIX}-s") 62 | endif() 63 | 64 | # find the SFML include directory 65 | find_path(SFML_INCLUDE_DIR SFML/Config.hpp 66 | PATH_SUFFIXES include 67 | PATHS 68 | ${SFML_ROOT} 69 | $ENV{SFML_ROOT} 70 | ~/Library/Frameworks 71 | /Library/Frameworks 72 | /usr/local/ 73 | /usr/ 74 | /sw # Fink 75 | /opt/local/ # DarwinPorts 76 | /opt/csw/ # Blastwave 77 | /opt/) 78 | 79 | # check the version number 80 | set(SFML_VERSION_OK TRUE) 81 | if(SFML_FIND_VERSION AND SFML_INCLUDE_DIR) 82 | # extract the major and minor version numbers from SFML/Config.hpp 83 | # we have to handle framework a little bit differently : 84 | if("${SFML_INCLUDE_DIR}" MATCHES "SFML.framework") 85 | set(SFML_CONFIG_HPP_INPUT "${SFML_INCLUDE_DIR}/Headers/Config.hpp") 86 | else() 87 | set(SFML_CONFIG_HPP_INPUT "${SFML_INCLUDE_DIR}/SFML/Config.hpp") 88 | endif() 89 | FILE(READ "${SFML_CONFIG_HPP_INPUT}" SFML_CONFIG_HPP_CONTENTS) 90 | STRING(REGEX MATCH ".*#define SFML_VERSION_MAJOR ([0-9]+).*#define SFML_VERSION_MINOR ([0-9]+).*" SFML_CONFIG_HPP_CONTENTS "${SFML_CONFIG_HPP_CONTENTS}") 91 | STRING(REGEX REPLACE ".*#define SFML_VERSION_MAJOR ([0-9]+).*" "\\1" SFML_VERSION_MAJOR "${SFML_CONFIG_HPP_CONTENTS}") 92 | STRING(REGEX REPLACE ".*#define SFML_VERSION_MINOR ([0-9]+).*" "\\1" SFML_VERSION_MINOR "${SFML_CONFIG_HPP_CONTENTS}") 93 | math(EXPR SFML_REQUESTED_VERSION "${SFML_FIND_VERSION_MAJOR} * 10 + ${SFML_FIND_VERSION_MINOR}") 94 | 95 | # if we could extract them, compare with the requested version number 96 | if (SFML_VERSION_MAJOR) 97 | # transform version numbers to an integer 98 | math(EXPR SFML_VERSION "${SFML_VERSION_MAJOR} * 10 + ${SFML_VERSION_MINOR}") 99 | 100 | # compare them 101 | if(SFML_VERSION LESS SFML_REQUESTED_VERSION) 102 | set(SFML_VERSION_OK FALSE) 103 | endif() 104 | else() 105 | # SFML version is < 2.0 106 | if (SFML_REQUESTED_VERSION GREATER 19) 107 | set(SFML_VERSION_OK FALSE) 108 | set(SFML_VERSION_MAJOR 1) 109 | set(SFML_VERSION_MINOR x) 110 | endif() 111 | endif() 112 | endif() 113 | 114 | # find the requested modules 115 | set(SFML_FOUND TRUE) # will be set to false if one of the required modules is not found 116 | set(FIND_SFML_LIB_PATHS 117 | ${SFML_ROOT} 118 | $ENV{SFML_ROOT} 119 | ~/Library/Frameworks 120 | /Library/Frameworks 121 | /usr/local 122 | /usr 123 | /sw 124 | /opt/local 125 | /opt/csw 126 | /opt) 127 | foreach(FIND_SFML_COMPONENT ${SFML_FIND_COMPONENTS}) 128 | string(TOLOWER ${FIND_SFML_COMPONENT} FIND_SFML_COMPONENT_LOWER) 129 | string(TOUPPER ${FIND_SFML_COMPONENT} FIND_SFML_COMPONENT_UPPER) 130 | set(FIND_SFML_COMPONENT_NAME sfml-${FIND_SFML_COMPONENT_LOWER}${FIND_SFML_LIB_SUFFIX}) 131 | 132 | # no suffix for sfml-main, it is always a static library 133 | if(FIND_SFML_COMPONENT_LOWER STREQUAL "main") 134 | set(FIND_SFML_COMPONENT_NAME sfml-${FIND_SFML_COMPONENT_LOWER}) 135 | endif() 136 | 137 | # debug library 138 | find_library(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG 139 | NAMES ${FIND_SFML_COMPONENT_NAME}-d 140 | PATH_SUFFIXES lib64 lib 141 | PATHS ${FIND_SFML_LIB_PATHS}) 142 | 143 | # release library 144 | find_library(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE 145 | NAMES ${FIND_SFML_COMPONENT_NAME} 146 | PATH_SUFFIXES lib64 lib 147 | PATHS ${FIND_SFML_LIB_PATHS}) 148 | 149 | if (SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG OR SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE) 150 | # library found 151 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_FOUND TRUE) 152 | 153 | # if both are found, set SFML_XXX_LIBRARY to contain both 154 | if (SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG AND SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE) 155 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY debug ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG} 156 | optimized ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE}) 157 | endif() 158 | 159 | # if only one debug/release variant is found, set the other to be equal to the found one 160 | if (SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG AND NOT SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE) 161 | # debug and not release 162 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG}) 163 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG}) 164 | endif() 165 | if (SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE AND NOT SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG) 166 | # release and not debug 167 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE}) 168 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE}) 169 | endif() 170 | else() 171 | # library not found 172 | set(SFML_FOUND FALSE) 173 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_FOUND FALSE) 174 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY "") 175 | set(FIND_SFML_MISSING "${FIND_SFML_MISSING} SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY") 176 | endif() 177 | 178 | # mark as advanced 179 | MARK_AS_ADVANCED(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY 180 | SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE 181 | SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG) 182 | 183 | # add to the global list of libraries 184 | set(SFML_LIBRARIES ${SFML_LIBRARIES} "${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY}") 185 | endforeach() 186 | 187 | # handle errors 188 | if(NOT SFML_VERSION_OK) 189 | # SFML version not ok 190 | set(FIND_SFML_ERROR "SFML found but version too low (requested: ${SFML_FIND_VERSION}, found: ${SFML_VERSION_MAJOR}.${SFML_VERSION_MINOR})") 191 | set(SFML_FOUND FALSE) 192 | elseif(NOT SFML_FOUND) 193 | # include directory or library not found 194 | set(FIND_SFML_ERROR "Could NOT find SFML (missing: ${FIND_SFML_MISSING})") 195 | endif() 196 | if (NOT SFML_FOUND) 197 | if(SFML_FIND_REQUIRED) 198 | # fatal error 199 | message(FATAL_ERROR ${FIND_SFML_ERROR}) 200 | elseif(NOT SFML_FIND_QUIETLY) 201 | # error but continue 202 | message("${FIND_SFML_ERROR}") 203 | endif() 204 | endif() 205 | 206 | # handle success 207 | if(SFML_FOUND) 208 | message(STATUS "Found SFML ${SFML_VERSION_MAJOR}.${SFML_VERSION_MINOR} in ${SFML_INCLUDE_DIR}") 209 | endif() 210 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Matthew Chandler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Textogl - A simple OpenGL text rendering library. 2 | 3 | Textogl renders text in OpenGL from nearly any font (any format supported by 4 | Freetype). 5 | 6 | ### [textogl on Github](https://github.com/mattvchandler/textogl) 7 | 8 | ### [Documentation](https://mattvchandler.github.io/textogl/index.html) 9 | 10 | ## Usage 11 | 12 | 1. Create a textogl::Font_sys object for the desired font. 13 | 2. Call textogl::Font_sys::render_text() with the desired text, position, and 14 | color. 15 | 3. If the text will not change each frame, consider using textogl::Static_text 16 | object. This will prevent needing to rebuild quads for each rendering call 17 | 18 | ## Building & Installation 19 | 20 | ### Dependencies 21 | 22 | * [Freetype](https://www.freetype.org/) 23 | * OpenGL 3.3 + OR OpenGL ES 2.0+ 24 | * GLM (Optional - Allows passing glm vectors to textogl for colors and positions) 25 | * Compiler supporting c++11 26 | 27 | Textogl uses CMake to build 28 | 29 | $ mkdir build && cd build 30 | $ cmake .. -DCMAKE_INSTALL_PREFIX= # add -DBUILD_SHARED_LIBS=1 for a shared libary, add -DUSE_GLM=0 to skip checking for GLM 31 | $ make 32 | # make install 33 | 34 | #### Debian & derivatives 35 | Textogl is configured to generate a .deb package file. To do so, substitute the 36 | above with the following: 37 | 38 | $ mkdir build && cd build 39 | $ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_SHARED_LIBS=1 40 | $ make 41 | $ cpack 42 | # dpkg -i libtextogl-dev*.deb 43 | 44 | #### Documentation 45 | If doxygen is installed, library documentation can be generated with: `$ make doc` 46 | -------------------------------------------------------------------------------- /demo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(MSYS AND CMAKE_BUILD_TYPE STREQUAL "Release") 2 | set(CMAKE_EXE_LINKER_FLAGS "-mwindows") 3 | endif() 4 | 5 | find_package(OpenGL REQUIRED) 6 | find_package(GLEW REQUIRED) 7 | find_package(SFML 2 COMPONENTS window system REQUIRED) 8 | find_package(Freetype REQUIRED) 9 | 10 | include_directories( 11 | ${CMAKE_CURRENT_LIST_DIR}/../include 12 | ${FREETYPE_INCLUDE_DIRS} 13 | ${OPENGL_INCLUDE_DIRS} 14 | ${GLEW_INCLUDE_DIRS} 15 | ${SFML_INCLUDE_DIRS} 16 | ) 17 | 18 | add_executable(textogl_demo 19 | demo.cpp) 20 | 21 | target_link_libraries(textogl_demo 22 | textogl 23 | ${FREETYPE_LIBRARIES} 24 | ${SFML_LIBRARIES} 25 | ${OPENGL_LIBRARIES} 26 | ${GLEW_LIBRARIES} 27 | ) 28 | -------------------------------------------------------------------------------- /demo/demo.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Matthew Chandler 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | #include "textogl/font.hpp" 35 | #include "textogl/static_text.hpp" 36 | 37 | int main(int argc, char * argv[]) 38 | { 39 | if(argc < 3) 40 | { 41 | std::cerr<<"no font specified (need 2)"< static_arr; 68 | for(int i = 0; i < 10; ++i) 69 | static_arr.emplace_back(font2, std::to_string(i)); 70 | 71 | const auto f = 1.0f / std::tan(static_cast(M_PI) / 12.0f); 72 | const auto aspect = static_cast(win.getSize().x) / static_cast(win.getSize().y); 73 | const float near = 0.1f, far = 10.0f; 74 | const textogl::Mat4 projection 75 | { 76 | f / aspect, 0.0f, 0.0f, 0.0f, 77 | 0.0f, f, 0.0f, 0.0f, 78 | 0.0f, 0.0f, (far + near) / (near - far), -1.0f, 79 | 0.0f, 0.0f, (2.0f * far * near) / (near - far), 0.0f 80 | }; 81 | 82 | const textogl::Mat4 view 83 | { 84 | 0.001f, 0.0f, 0.0f, 0.0f, 85 | 0.0f, -0.001f, 0.0f, 0.0f, 86 | 0.0f, 0.0f, 1.0f, 0.0f, 87 | -0.05f, 0.0f, -0.25f, 1.0f 88 | }; 89 | 90 | bool running = true; 91 | float angle = 0.0f; 92 | while(running) 93 | { 94 | if(win.pollEvent(ev)) 95 | { 96 | switch(ev.type) 97 | { 98 | // close window 99 | case sf::Event::Closed: 100 | running = false; 101 | break; 102 | 103 | case sf::Event::Resized: 104 | glViewport(0, 0, win.getSize().x, win.getSize().y); 105 | break; 106 | 107 | default: 108 | break; 109 | } 110 | } 111 | 112 | static int frame_count = 0; 113 | static std::ostringstream fps_format; 114 | 115 | static auto last_frame = std::chrono::high_resolution_clock::now(); 116 | auto now = std::chrono::high_resolution_clock::now(); 117 | auto duration = std::chrono::duration>(now - last_frame); 118 | 119 | static float fps = 0.0f; 120 | 121 | if(duration > std::chrono::milliseconds(100)) 122 | { 123 | fps = (float)frame_count / duration.count(); 124 | last_frame = now; 125 | frame_count = 0; 126 | 127 | fps_format.str(""); 128 | fps_format<<"Dynamic text: "<{(float)win.getSize().x, (float)win.getSize().y}, 139 | textogl::Vec2{0.0f, 0.0f}, textogl::ORIGIN_VERT_TOP | textogl::ORIGIN_HORIZ_LEFT); 140 | 141 | // Green static text, @ 0, 75 142 | static_text.render_text(textogl::Color{0.0f, 1.0f, 0.0f, 1.0f}, textogl::Vec2{(float)win.getSize().x, (float)win.getSize().y}, 143 | textogl::Vec2{0.0f, 75.0f}, textogl::ORIGIN_VERT_TOP | textogl::ORIGIN_HORIZ_LEFT); 144 | 145 | // blue larger font @ 0, 150 146 | static_text2.render_text(textogl::Color{0.0f, 0.0f, 1.0f, 1.0f}, textogl::Vec2{(float)win.getSize().x, (float)win.getSize().y}, 147 | textogl::Vec2{0.0f, 150.0f}, textogl::ORIGIN_VERT_TOP | textogl::ORIGIN_HORIZ_LEFT); 148 | 149 | // vertical array of numbers along right edge 150 | for(std::size_t i = 0; i < static_arr.size(); ++i) 151 | static_arr[i].render_text(textogl::Color{0.0f, 0.0f, 0.0f, 1.0f}, textogl::Vec2{(float)win.getSize().x, (float)win.getSize().y}, 152 | textogl::Vec2{(float)win.getSize().x, (float)(i * 60)}, textogl::ORIGIN_VERT_TOP | textogl::ORIGIN_HORIZ_RIGHT); 153 | 154 | // 2D rotating text centered on 100, 300 155 | rotating_text.render_text_rotate(textogl::Color{0.0f, 0.0f, 0.0f, 1.0f}, textogl::Vec2{(float)win.getSize().x, (float)win.getSize().y}, 156 | textogl::Vec2{100.0f, 300.0f}, angle, textogl::ORIGIN_VERT_CENTER | textogl::ORIGIN_HORIZ_CENTER); 157 | 158 | // 3D rotating / orbiting text 159 | textogl::Mat4 rotation 160 | { 161 | std::cos(angle), 0.0f, -std::sin(angle), 0.0f, 162 | 0.0f, 1.0f, 0.0f, 0.0f, 163 | std::sin(angle), 0.0f, std::cos(angle), 0.0f, 164 | 0.0f, 0.0f, -1.0f, 1.0f 165 | }; 166 | 167 | text_3d.render_text_mat(textogl::Color{0.0, 0.0f, 0.0f, 1.0f}, projection * rotation * view); 168 | 169 | win.display(); 170 | } 171 | 172 | win.close(); 173 | 174 | return EXIT_SUCCESS; 175 | } 176 | -------------------------------------------------------------------------------- /include/textogl/font.hpp: -------------------------------------------------------------------------------- 1 | /// @file 2 | /// @brief Font loading and text display 3 | 4 | // Copyright 2022 Matthew Chandler 5 | 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | #ifndef FONT_HPP 25 | #define FONT_HPP 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include "types.hpp" 32 | 33 | /// @ingroup textogl 34 | namespace textogl 35 | { 36 | /// Text origin specification 37 | enum Text_origin: int 38 | { 39 | ORIGIN_HORIZ_BASELINE = 0x00, ///< Horizontal text origin at baseline 40 | ORIGIN_HORIZ_LEFT = 0x01, ///< Horizontal text origin at left edge 41 | ORIGIN_HORIZ_RIGHT = 0x02, ///< Horizontal text origin at right edge 42 | ORIGIN_HORIZ_CENTER = 0x03, ///< Horizontal text origin at center 43 | 44 | ORIGIN_VERT_BASELINE = 0x00, ///< Vertical text origin at baseline 45 | ORIGIN_VERT_TOP = 0x04, ///< Vertical text origin at left edge 46 | ORIGIN_VERT_BOTTOM = 0x08, ///< Vertical text origin at right edge 47 | ORIGIN_VERT_CENTER = 0x0C ///< Vertical text origin at center 48 | }; 49 | 50 | /// Container for font and text rendering 51 | 52 | /// Contains everything needed for rendering from the specified font at the 53 | /// specified size. Unicode is supported for all glyphs provided by the 54 | /// specified font. 55 | /// 56 | /// Rendering data is created by unicode code page (block of 256 code-points), 57 | /// as it is used. Only those pages that are used are built. The 1st page 58 | /// (Basic Latin and Latin-1 Supplement) is always created. 59 | class Font_sys 60 | { 61 | public: 62 | /// Load a font file at a specified size 63 | Font_sys(const std::string & font_path, ///< Path to font file to use 64 | const unsigned int font_size ///< Font size (in pixels) 65 | ); 66 | /// Load a font at a specified size from memory 67 | 68 | /// data is not copied, so the client is responsible for maintaining the data for the lifetime of this object 69 | Font_sys(const unsigned char * font_data, ///< Font file data (in memory) 70 | const std::size_t font_data_size, ///< Font file data's size in memory 71 | const unsigned int font_size ///< Font size (in pixels) 72 | ); 73 | 74 | /// Resize font 75 | 76 | /// Resizes the font without destroying it 77 | /// @note This will require rebuilding font textures 78 | /// @note Any Static_text objects tied to this Font_sys will need to have Static_text::set_font_sys called 79 | void resize(const unsigned int font_size ///< Font size (in pixels) 80 | ); 81 | 82 | /// Render given text 83 | 84 | /// Renders the text supplied in utf8_input parameter 85 | /// @note This will rebuild the OpenGL primitives each call. 86 | /// If the text will not change frequently, use a Static_text object 87 | /// instead 88 | void render_text(const std::string & utf8_input, ///< Text to render, in UTF-8 encoding. For best performance, normalize the string before rendering 89 | const Color & color, ///< Text Color 90 | const Vec2 & win_size, ///< Window dimensions. A Vec2 with X = width and Y = height 91 | const Vec2 & pos, ///< Render position, in screen pixels 92 | const int align_flags = 0 ///< Text Alignment. Should be #Text_origin flags bitwise-OR'd together 93 | ); 94 | 95 | /// Render given text, with rotatation 96 | 97 | /// Renders the text supplied in utf8_input parameter 98 | /// @note This will rebuild the OpenGL primitives each call. 99 | /// If the text will not change frequently, use a Static_text object 100 | /// instead 101 | void render_text_rotate(const std::string & utf8_input, ///< Text to render, in UTF-8 encoding. For best performance, normalize the string before rendering 102 | const Color & color, ///< Text Color 103 | const Vec2 & win_size, ///< Window dimensions. A Vec2 with X = width and Y = height 104 | const Vec2 & pos, ///< Render position, in screen pixels 105 | const float rotation, ///< Clockwise text rotation (in radians) around origin as defined in align_flags. 0 is vertical 106 | const int align_flags = 0 ///< Text Alignment. Should be #Text_origin flags bitwise-OR'd together 107 | ); 108 | 109 | /// Render given text, using a model view projection matrix 110 | 111 | /// Renders the text supplied in utf8_input parameter, using a model view projection matrix 112 | /// @note This will rebuild the OpenGL primitives each call. 113 | /// If the text will not change frequently, use a Static_text object 114 | /// instead 115 | void render_text_mat(const std::string & utf8_input, ///< Text to render, in UTF-8 encoding. For best performance, normalize the string before rendering 116 | const Color & color, ///< Text Color 117 | /// Model view projection matrix. 118 | /// The text will be rendered as quads, one for each glyph, with vertex coordinates centered on the baselines and sized in pixels. 119 | /// This matrix will be used to transform that geometry 120 | const Mat4 & model_view_projection 121 | ); 122 | 123 | private: 124 | struct Impl; ///< Private internal implementation 125 | std::shared_ptr pimpl; ///< Pointer to private internal implementation 126 | 127 | /// @cond INTERNAL 128 | friend class Static_text; 129 | /// @endcond 130 | }; 131 | } 132 | 133 | #endif // FONT_HPP 134 | -------------------------------------------------------------------------------- /include/textogl/static_text.hpp: -------------------------------------------------------------------------------- 1 | /// @file 2 | /// @brief Static text object 3 | 4 | // Copyright 2022 Matthew Chandler 5 | 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | #ifndef STATIC_TEXT_HPP 25 | #define STATIC_TEXT_HPP 26 | 27 | #include "font.hpp" 28 | 29 | /// OpenGL Font rendering types 30 | 31 | /// @ingroup textogl 32 | namespace textogl 33 | { 34 | /// Object for text which does not change often 35 | 36 | /// Font_sys::render_text will re-build the OpenGL primitives on each call, 37 | /// which is inefficient if the text doesn't change every frame. 38 | /// 39 | /// This class is for text that doesn't need to change frequently. 40 | /// Text is initially built with the #Static_text constructor and can be 41 | /// changed with \ref set_text. 42 | class Static_text 43 | { 44 | public: 45 | /// Create and build text object 46 | /// @param font Font_sys object containing desired font. This Static_text 47 | /// will retain a shared_ptr to the Font_sys, but will not automatically 48 | /// rebuild when Font_sys::resize is called. Use Static_text::set_font_sys 49 | // to rebuild in that case. 50 | /// @param utf8_input Text to render, in UTF-8 encoding. For best performance, normalize the string before rendering 51 | Static_text(Font_sys & font, 52 | const std::string & utf8_input 53 | ); 54 | 55 | /// Recreate text object with new Font_sys 56 | 57 | /// When Font_sys::resize has been called, call this to rebuild this Static_text with the new size 58 | 59 | /// @param font Font_sys object containing desired font. 60 | void set_font_sys(Font_sys & font); 61 | 62 | /// Recreate text object with new string 63 | 64 | /// @param utf8_input Text to render, in UTF-8 encoding. For best performance, normalize the string before rendering 65 | void set_text(const std::string & utf8_input); 66 | 67 | /// Render the previously set text 68 | void render_text(const Color & color, ///< Text Color 69 | const Vec2 & win_size, ///< Window dimensions. A Vec2 with X = width and Y = height 70 | const Vec2 & pos, ///< Render position, in screen pixels 71 | const int align_flags = 0 ///< Text Alignment. Should be #Text_origin flags bitwise-OR'd together 72 | ); 73 | 74 | /// Render the previously set text, with rotation 75 | void render_text_rotate(const Color & color, ///< Text Color 76 | const Vec2 & win_size, ///< Window dimensions. A Vec2 with X = width and Y = height 77 | const Vec2 & pos, ///< Render position, in screen pixels 78 | const float rotation, ///< Clockwise text rotation (in radians) around center as defined in align_flags. 0 is vertical 79 | const int align_flags = 0 ///< Text Alignment. Should be #Text_origin flags bitwise-OR'd together 80 | ); 81 | 82 | /// Render the previously set text, using a model view projection matrix 83 | void render_text_mat(const Color & color, ///< Text Color 84 | /// Model view projection matrix. 85 | /// The text will be rendered as quads, one for each glyph, with vertex coordinates centered on the baselines and sized in pixels. 86 | /// This matrix will be used to transform that geometry 87 | const Mat4 & model_view_projection 88 | ); 89 | 90 | private: 91 | struct Impl; ///< Private internal implementation 92 | std::unique_ptr pimpl; ///< Pointer to private internal implementation 93 | }; 94 | } 95 | 96 | #endif // STATIC_TEXT_HPP 97 | -------------------------------------------------------------------------------- /include/textogl/types.hpp: -------------------------------------------------------------------------------- 1 | /// @file 2 | /// @brief Vector and matrix types 3 | 4 | // Copyright 2022 Matthew Chandler 5 | 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | #ifndef TYPES_HPP 25 | #define TYPES_HPP 26 | 27 | #include 28 | #include 29 | 30 | #ifdef USE_GLM 31 | #include 32 | #endif 33 | 34 | 35 | /// OpenGL Font rendering types 36 | 37 | /// @ingroup textogl 38 | namespace textogl 39 | { 40 | namespace detail 41 | { 42 | // fallback types to use if GLM is not available 43 | 44 | /// 2D Vector 45 | template 46 | struct Vec2 47 | { 48 | union {T x = {}, r;}; ///< X / R component 49 | union {T y = {}, g;}; ///< Y / G component 50 | 51 | Vec2() = default; 52 | Vec2(T x, T y): x(x), y(y) {} 53 | 54 | /// Access component by index 55 | 56 | /// To pass vector to OpenGL, do: &vec2[0] 57 | /// @{ 58 | T & operator[](std::size_t i) { return (&x)[i]; } 59 | const T & operator[](std::size_t i) const { return (&x)[i]; } 60 | /// @} 61 | }; 62 | 63 | /// 4D Vector 64 | template 65 | struct Vec4 66 | { 67 | union {T x = {}, r;}; ///< X / R component 68 | union {T y = {}, g;}; ///< Y / G component 69 | union {T z = {}, b;}; ///< Z / B component 70 | union {T w = {}, a;}; ///< W / A component 71 | 72 | Vec4() = default; 73 | Vec4(T x, T y, T z, T w): x(x), y(y), z(z), w(w) {} 74 | 75 | /// Access component by index 76 | 77 | /// To pass vector to OpenGL, do: &vec4[0] 78 | /// @{ 79 | T & operator[](std::size_t i) { return (&x)[i]; } 80 | const T & operator[](std::size_t i) const { return (&x)[i]; } 81 | /// @} 82 | }; 83 | 84 | /// 4x4 Matrix 85 | template 86 | class Mat4 87 | { 88 | private: 89 | Vec4 data_[4]{{}, {}, {}, {}}; 90 | public: 91 | 92 | Mat4() = default; 93 | 94 | Mat4(Vec4 & x, const Vec4 & y, const Vec4 & z, const Vec4 &w) 95 | { 96 | data_[0] = x; data_[1] = y; data_[2] = z; data_[3] = w; 97 | } 98 | 99 | Mat4(T xx, T xy, T xz, T xw, 100 | T yx, T yy, T yz, T yw, 101 | T zx, T zy, T zz, T zw, 102 | T wx, T wy, T wz, T ww) 103 | { 104 | data_[0][0] = xx; data_[0][1] = xy; data_[0][2] = xz; data_[0][3] = xw; 105 | data_[1][0] = yx; data_[1][1] = yy; data_[1][2] = yz; data_[1][3] = yw; 106 | data_[2][0] = zx; data_[2][1] = zy; data_[2][2] = zz; data_[2][3] = zw; 107 | data_[3][0] = wx; data_[3][1] = wy; data_[3][2] = wz; data_[3][3] = ww; 108 | } 109 | 110 | Mat4(T diagonal): Mat4(diagonal, 0, 0, 0, 111 | 0, diagonal, 0, 0, 112 | 0, 0, diagonal, 0, 113 | 0, 0, 0, diagonal) 114 | {} 115 | 116 | /// Access component by index 117 | 118 | /// To pass vector to OpenGL, do: &mat4[0][0] 119 | /// @{ 120 | Vec4 & operator[](std::size_t i) { return data_[i]; } 121 | const Vec4 & operator[](std::size_t i) const { return data_[i]; } 122 | /// @} 123 | 124 | #ifndef USE_GLM 125 | /// Multiply 2 Mat4s 126 | template 127 | Mat4() * std::declval())> operator*(const Mat4 & b) const 128 | { 129 | Mat4 out; 130 | for(int col = 0; col < 4; ++col) 131 | { 132 | for(int row = 0; row < 4; ++row) 133 | { 134 | out[col][row] = 0; 135 | for(int k = 0; k < 4; ++k) 136 | out[col][row] += data_[k][row] * b[col][k]; 137 | } 138 | } 139 | return out; 140 | } 141 | 142 | /// Multiply 2 Mat4s 143 | template 144 | Mat4() * std::declval())> & operator*=(const Mat4 & b) 145 | { 146 | return *this = *this * b; 147 | } 148 | #endif 149 | }; 150 | 151 | // for template alias specialization 152 | template struct Vec2_t { using type = Vec2; }; 153 | template struct Vec4_t { using type = Vec4; }; 154 | template struct Mat4_t { using type = Mat4; }; 155 | 156 | #ifdef USE_GLM 157 | // specialize to glm types 158 | template<> struct Vec2_t { using type = glm::vec2; }; 159 | template<> struct Vec2_t { using type = glm::dvec2; }; 160 | template<> struct Vec2_t { using type = glm::ivec2; }; 161 | template<> struct Vec2_t { using type = glm::uvec2; }; 162 | 163 | template<> struct Vec4_t { using type = glm::vec4; }; 164 | template<> struct Vec4_t { using type = glm::dvec4; }; 165 | template<> struct Vec4_t { using type = glm::ivec4; }; 166 | template<> struct Vec4_t { using type = glm::uvec4; }; 167 | 168 | template<> struct Mat4_t { using type = glm::mat4; }; 169 | template<> struct Mat4_t { using type = glm::dmat4; }; 170 | #endif 171 | } 172 | 173 | /// 2D Vector 174 | 175 | /// @note If GLM is available, this is an alias for glm::vec2 / dvec2 / ... 176 | template using Vec2 = typename detail::Vec2_t::type; 177 | 178 | /// 4D Vector 179 | 180 | /// @note If GLM is available, this is an alias for glm::vec4 / dvec4 / ... 181 | template using Vec4 = typename detail::Vec4_t::type; 182 | 183 | /// 4D Matrix 184 | 185 | /// @note If GLM is available, this is an alias for glm::mat4 / dmat4 186 | template using Mat4 = typename detail::Mat4_t::type; 187 | 188 | /// %Color vector 189 | 190 | /// @note If GLM is available, this is an alias for glm::vec4 191 | using Color = Vec4; 192 | 193 | } 194 | 195 | #endif // TYPES_HPP 196 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(ANDROID) # TODO: could probably include iOS or other OpenGL ES platforms 2 | # it is up to the android project to make sure these are included and linked correctly 3 | set(OPENGL_LIBRARIES GLESv2) 4 | set(FREETYPE_LIBRARIES freetype) 5 | 6 | set(VERT_SHADER_SRC ${CMAKE_CURRENT_LIST_DIR}/shaders/font.gles20.vert) 7 | set(FRAG_SHADER_SRC ${CMAKE_CURRENT_LIST_DIR}/shaders/font.gles20.frag) 8 | else() 9 | find_package(OpenGL REQUIRED) 10 | find_package(GLEW REQUIRED) 11 | find_package(Freetype REQUIRED) 12 | 13 | if(USE_GLM) 14 | find_package(GLM) 15 | endif() 16 | set(VERT_SHADER_SRC ${CMAKE_CURRENT_LIST_DIR}/shaders/font.gl33.vert) 17 | set(FRAG_SHADER_SRC ${CMAKE_CURRENT_LIST_DIR}/shaders/font.gl33.frag) 18 | endif() 19 | 20 | include_directories( 21 | ${CMAKE_CURRENT_LIST_DIR}/../include 22 | ${CMAKE_CURRENT_LIST_DIR} 23 | ${PROJECT_BINARY_DIR} 24 | ${FREETYPE_INCLUDE_DIRS} 25 | ${OPENGL_INCLUDE_DIRS} 26 | ${GLEW_INCLUDE_DIRS} 27 | ) 28 | 29 | add_library(${PROJECT_NAME} 30 | font.cpp 31 | font_common.cpp 32 | static_text.cpp 33 | ) 34 | 35 | if(GLM_FOUND) 36 | message(STATUS "Found glm, building with glm support") 37 | target_compile_definitions(${PROJECT_NAME} PUBLIC "-DUSE_GLM") 38 | endif() 39 | 40 | if(ANDROID) 41 | target_compile_definitions(${PROJECT_NAME} PUBLIC "-DUSE_OPENGL_ES") 42 | endif() 43 | 44 | target_link_libraries(${PROJECT_NAME} 45 | ${FREETYPE_LIBRARIES} 46 | ${OPENGL_LIBRARIES} 47 | ${GLEW_LIBRARIES} 48 | ) 49 | 50 | # load the shader source code into C++ strings 51 | file(GENERATE OUTPUT ${PROJECT_BINARY_DIR}/shaders.cmake CONTENT " 52 | file(READ ${VERT_SHADER_SRC} VERT_SHADER) 53 | file(READ ${FRAG_SHADER_SRC} FRAG_SHADER) 54 | configure_file(${CMAKE_CURRENT_LIST_DIR}/shaders/shaders.inl.in 55 | ${PROJECT_BINARY_DIR}/shaders.inl) 56 | ") 57 | 58 | add_custom_command( 59 | COMMAND ${CMAKE_COMMAND} -P ${PROJECT_BINARY_DIR}/shaders.cmake 60 | DEPENDS 61 | ${CMAKE_CURRENT_LIST_DIR}/shaders/shaders.inl.in 62 | ${VERT_SHADER_SRC} 63 | ${FRAG_SHADER_SRC} 64 | OUTPUT 65 | ${PROJECT_BINARY_DIR}/shaders.inl 66 | COMMENT "Including shader source files" 67 | ) 68 | 69 | add_custom_target(shaders DEPENDS ${PROJECT_BINARY_DIR}/shaders.inl) 70 | 71 | add_dependencies(${PROJECT_NAME} shaders) 72 | 73 | install(TARGETS ${PROJECT_NAME} 74 | ARCHIVE DESTINATION "lib" 75 | LIBRARY DESTINATION "lib" 76 | RUNTIME DESTINATION "bin" 77 | ) 78 | -------------------------------------------------------------------------------- /src/font.cpp: -------------------------------------------------------------------------------- 1 | /// @file 2 | /// @brief Font loading and text display implementation 3 | 4 | // Copyright 2022 Matthew Chandler 5 | 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | #include "font_impl.hpp" 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | 35 | /// Convert a UTF-8 string to a UTF-32 string 36 | 37 | /// Minimal verification is done to ensure that the input is valid UTF-8. If any 38 | /// invalid byte sequences are detected, they will be replaced by the replacement 39 | /// character: '�' 40 | /// @param utf8 UTF-8 string to convert 41 | /// @returns UTF-32 string corresponding to the input 42 | std::u32string utf8_to_utf32(const std::string & utf8) 43 | { 44 | std::u32string utf32; 45 | 46 | uint32_t code_pt = 0; 47 | int expected_bytes = 0; 48 | 49 | for(const uint8_t byte: utf8) 50 | { 51 | // detect invalid bytes 52 | if(byte == 0xC0 || byte == 0xC1 || byte >= 0xF5) 53 | { 54 | utf32.push_back(U'�'); 55 | expected_bytes = 0; 56 | } 57 | // 0b0xxxxxxx: single-byte char (ASCII) 58 | else if((byte & 0x80) == 0) 59 | { 60 | if(expected_bytes != 0) 61 | { 62 | // previous sequence ended prematurely. add replacement char 63 | utf32.push_back(U'�'); 64 | expected_bytes = 0; 65 | } 66 | utf32.push_back(byte); 67 | } 68 | // 0b11xxxxxx: leading byte 69 | else if((byte & 0xC0) == 0xC0) 70 | { 71 | if(expected_bytes != 0) 72 | { 73 | // previous sequence ended prematurely. add replacement char 74 | utf32.push_back(U'�'); 75 | } 76 | 77 | // 2-byte char 78 | if((byte & 0xE0) == 0xC0) 79 | { 80 | code_pt = byte & 0x1F; 81 | expected_bytes = 1; 82 | } 83 | // 3-byte char 84 | else if((byte & 0xF0) == 0xE0) 85 | { 86 | code_pt = byte & 0x0F; 87 | expected_bytes = 2; 88 | } 89 | // 4-byte char 90 | else if((byte & 0xF8) == 0xF0) 91 | { 92 | code_pt = byte & 0x07; 93 | expected_bytes = 3; 94 | } 95 | else 96 | { 97 | // invalid value. insert the replacement char 98 | utf32.push_back(U'�'); 99 | expected_bytes = 0; 100 | } 101 | } 102 | // 0b10xxxxxx: continuation byte 103 | else // (byte & 0xC0) == 0x80 104 | { 105 | if(expected_bytes == 0) 106 | { 107 | // continuation byte w/o leader. replace w/ replacement char 108 | utf32.push_back(U'�'); 109 | } 110 | else 111 | { 112 | code_pt <<= 6; 113 | code_pt |= byte & 0x3F; 114 | 115 | if(--expected_bytes == 0) 116 | { 117 | utf32.push_back(code_pt); 118 | } 119 | } 120 | } 121 | } 122 | 123 | if(expected_bytes > 0) 124 | { 125 | // end of string but still expecting continuation bytes. use the replacement char 126 | utf32.push_back(U'�'); 127 | } 128 | 129 | return utf32; 130 | } 131 | 132 | namespace textogl 133 | { 134 | Font_sys::Font_sys(const std::string & font_path, const unsigned int font_size): 135 | pimpl(std::make_shared(font_path, font_size)) 136 | {} 137 | Font_sys::Impl::Impl(const std::string & font_path, const unsigned int font_size) 138 | { 139 | FT_Open_Args args 140 | { 141 | FT_OPEN_PATHNAME, 142 | nullptr, 143 | 0, 144 | const_cast(font_path.c_str()), 145 | nullptr, 146 | nullptr, 147 | 0, 148 | nullptr 149 | }; 150 | init(args, font_size); 151 | } 152 | 153 | Font_sys::Font_sys(const unsigned char * font_data, std::size_t font_data_size, const unsigned int font_size): 154 | pimpl(std::make_shared(font_data, font_data_size, font_size)) 155 | {} 156 | Font_sys::Impl::Impl(const unsigned char * font_data, std::size_t font_data_size, const unsigned int font_size) 157 | { 158 | FT_Open_Args args 159 | { 160 | FT_OPEN_MEMORY, 161 | font_data, 162 | static_cast(font_data_size), 163 | nullptr, 164 | nullptr, 165 | nullptr, 166 | 0, 167 | nullptr 168 | }; 169 | init(args, font_size); 170 | } 171 | 172 | void Font_sys::Impl::init(FT_Open_Args & args, const unsigned int font_size) 173 | { 174 | // load freetype, and text shader - only once 175 | if(common_ref_cnt_ == 0) 176 | common_data_.reset(new Font_common); 177 | 178 | // open the font file 179 | FT_Error err = FT_Open_Face(common_data_->ft_lib, &args, 0, &face_); 180 | 181 | if(err != FT_Err_Ok) 182 | { 183 | if(common_ref_cnt_ == 0) 184 | common_data_.reset(); 185 | 186 | if(err == FT_Err_Unknown_File_Format) 187 | { 188 | throw std::system_error(err, std::system_category(), "Unknown format for font file"); 189 | } 190 | else 191 | { 192 | throw std::ios_base::failure("Error reading font file"); 193 | } 194 | } 195 | 196 | // select unicode charmap (should be default for most fonts) 197 | if(FT_Select_Charmap(face_, FT_ENCODING_UNICODE) != FT_Err_Ok) 198 | { 199 | FT_Done_Face(face_); 200 | 201 | if(common_ref_cnt_ == 0) 202 | common_data_.reset(); 203 | 204 | throw std::runtime_error("No unicode charmap in font file"); 205 | } 206 | 207 | try 208 | { 209 | resize(font_size); 210 | } 211 | catch(std::runtime_error &) 212 | { 213 | FT_Done_Face(face_); 214 | 215 | if(common_ref_cnt_ == 0) 216 | common_data_.reset(); 217 | 218 | throw; 219 | } 220 | 221 | // we're not going to throw now, so increment library ref count 222 | ++common_ref_cnt_; 223 | 224 | // create and set up vertex array and buffer 225 | #ifndef USE_OPENGL_ES 226 | glGenVertexArrays(1, &vao_); 227 | glBindVertexArray(vao_); 228 | #endif 229 | 230 | glGenBuffers(1, &vbo_); 231 | glBindBuffer(GL_ARRAY_BUFFER, vbo_); 232 | 233 | #ifndef USE_OPENGL_ES 234 | glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(Vec2), NULL); 235 | glEnableVertexAttribArray(0); 236 | glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(Vec2), (const GLvoid *)sizeof(Vec2)); 237 | glEnableVertexAttribArray(1); 238 | glBindVertexArray(0); 239 | #endif 240 | glBindBuffer(GL_ARRAY_BUFFER, 0); 241 | 242 | glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &max_tu_count_); 243 | max_tu_count_--; 244 | 245 | // get shader uniform locations 246 | glUseProgram(common_data_->prog); 247 | glUniform1i(common_data_->uniform_locations["font_page"], max_tu_count_); 248 | glUseProgram(0); 249 | } 250 | 251 | Font_sys::Impl::~Impl() 252 | { 253 | FT_Done_Face(face_); 254 | 255 | // only deallocate shared libs if this is the last Font_sys obj 256 | if(--common_ref_cnt_ == 0) 257 | common_data_.reset(); 258 | 259 | // destroy VAO/VBO 260 | glDeleteBuffers(1, &vbo_); 261 | #ifndef USE_OPENGL_ES 262 | glDeleteVertexArrays(1, &vao_); 263 | #endif 264 | 265 | // destroy textures 266 | for(auto & i: page_map_) 267 | { 268 | glDeleteTextures(1, &i.first); 269 | } 270 | } 271 | 272 | Font_sys::Impl::Impl(Impl && other): 273 | face_(other.face_), 274 | has_kerning_info_(other.has_kerning_info_), 275 | cell_bbox_(std::move(other.cell_bbox_)), 276 | line_height_(other.line_height_), 277 | tex_width_(other.tex_width_), 278 | tex_height_(other.tex_height_), 279 | page_map_(std::move(other.page_map_)), 280 | #ifndef USE_OPENGL_ES 281 | vao_(other.vao_), 282 | #endif 283 | vbo_(other.vbo_) 284 | { 285 | other.face_ = nullptr; 286 | #ifndef USE_OPENGL_ES 287 | other.vao_ = 0; 288 | #endif 289 | other.vbo_ = 0; 290 | ++common_ref_cnt_; 291 | } 292 | Font_sys::Impl & Font_sys::Impl::operator=(Impl && other) 293 | { 294 | if(this != &other) 295 | { 296 | face_ = other.face_; 297 | has_kerning_info_ = other.has_kerning_info_; 298 | cell_bbox_ = std::move(other.cell_bbox_); 299 | line_height_ = other.line_height_; 300 | tex_width_ = other.tex_width_; 301 | tex_height_ = other.tex_height_; 302 | page_map_ = std::move(other.page_map_); 303 | #ifndef USE_OPENGL_ES 304 | vao_ = other.vao_; 305 | #endif 306 | vbo_ = other.vbo_; 307 | 308 | other.face_ = nullptr; 309 | #ifndef USE_OPENGL_ES 310 | other.vao_ = 0; 311 | #endif 312 | other.vbo_ = 0; 313 | } 314 | return *this; 315 | } 316 | 317 | void Font_sys::resize(const unsigned int font_size) 318 | { 319 | pimpl->resize(font_size); 320 | } 321 | void Font_sys::Impl::resize(const unsigned int font_size) 322 | { 323 | // select font size 324 | if(FT_Set_Pixel_Sizes(face_, 0, font_size) != FT_Err_Ok) 325 | throw std::runtime_error("Can't set font size: " + std::to_string(font_size)); 326 | 327 | // get bounding box that will fit any glyph, plus 2 px padding 328 | // some glyphs overflow the reported box (antialiasing?) so at least one px is needed 329 | cell_bbox_.ul.x = FT_MulFix(face_->bbox.xMin, face_->size->metrics.x_scale) / 64 - 2; 330 | cell_bbox_.ul.y = FT_MulFix(face_->bbox.yMax, face_->size->metrics.y_scale) / 64 + 2; 331 | cell_bbox_.lr.x = FT_MulFix(face_->bbox.xMax, face_->size->metrics.x_scale) / 64 + 2; 332 | cell_bbox_.lr.y = FT_MulFix(face_->bbox.yMin, face_->size->metrics.y_scale) / 64 - 2; 333 | 334 | // get newline height 335 | line_height_ = FT_MulFix(face_->height, face_->size->metrics.y_scale) / 64; 336 | 337 | tex_width_ = cell_bbox_.width() * 16; 338 | tex_height_ = cell_bbox_.height() * 16; 339 | 340 | has_kerning_info_ = FT_HAS_KERNING(face_); 341 | 342 | page_map_.clear(); 343 | } 344 | 345 | void Font_sys::render_text(const std::string & utf8_input, const Color & color, 346 | const Vec2 & win_size, const Vec2 & pos, const int align_flags) 347 | { 348 | pimpl->render_text(utf8_input, color, win_size, pos, 0.0f, align_flags); 349 | } 350 | void Font_sys::render_text_rotate(const std::string & utf8_input, const Color & color, 351 | const Vec2 & win_size, const Vec2 & pos, const float rotation, 352 | const int align_flags) 353 | { 354 | pimpl->render_text(utf8_input, color, win_size, pos, rotation, align_flags); 355 | } 356 | void Font_sys::Impl::render_text(const std::string & utf8_input, const Color & color, 357 | const Vec2 & win_size, const Vec2 & pos, const float rotation, 358 | const int align_flags) 359 | { 360 | // build text buffer objs 361 | std::vector> coords; 362 | std::vector coord_data; 363 | Font_sys::Impl::Bbox text_box; 364 | std::tie(coords, coord_data, text_box) = build_text(utf8_input); 365 | 366 | load_text_vbo(coords); 367 | 368 | render_text_common(color, win_size, pos, align_flags, rotation, text_box, coord_data, 369 | #ifndef USE_OPENGL_ES 370 | vao_, 371 | #endif 372 | vbo_); 373 | } 374 | 375 | void Font_sys::Impl::render_text_common(const Color & color, const Vec2 & win_size, 376 | const Vec2 & pos, const int align_flags, const float rotation, 377 | const Bbox & text_box, const std::vector & coord_data, 378 | #ifndef USE_OPENGL_ES 379 | GLuint vao, 380 | #endif 381 | GLuint vbo) 382 | { 383 | Vec2 start_offset{0.0f, 0.0f}; 384 | 385 | // offset origin to align to text bounding box 386 | int horiz_align = align_flags & 0x3; 387 | switch(horiz_align) 388 | { 389 | case ORIGIN_HORIZ_BASELINE: 390 | break; 391 | case ORIGIN_HORIZ_LEFT: 392 | start_offset.x = text_box.ul.x; 393 | break; 394 | case ORIGIN_HORIZ_RIGHT: 395 | start_offset.x = text_box.lr.x; 396 | break; 397 | case ORIGIN_HORIZ_CENTER: 398 | start_offset.x = text_box.ul.x + text_box.width() / 2.0f; 399 | break; 400 | } 401 | 402 | int vert_align = align_flags & 0xC; 403 | switch(vert_align) 404 | { 405 | case ORIGIN_VERT_BASELINE: 406 | break; 407 | case ORIGIN_VERT_TOP: 408 | start_offset.y = text_box.ul.y; 409 | break; 410 | case ORIGIN_VERT_BOTTOM: 411 | start_offset.y = text_box.lr.y; 412 | break; 413 | case ORIGIN_VERT_CENTER: 414 | start_offset.y = text_box.lr.y + text_box.height() / 2.0f; 415 | break; 416 | } 417 | 418 | // this is the result of multiplying matrices as follows: 419 | // projection(0, win_size.x, win_size.y, 0) * translate(pos) * rotate(rotation, {0,0,1}) * translate(-start_offset) 420 | Mat4 model_view_projection 421 | { 422 | 2.0f * std::cos(rotation) / win_size.x, -2.0f * std::sin(rotation) / win_size.y, 0.0f, 0.0f, 423 | -2.0f * std::sin(rotation) / win_size.x, -2.0f * std::cos(rotation) / win_size.y, 0.0f, 0.0f, 424 | 0.0f, 0.0f, 1.0f, 0.0f, 425 | 426 | -1.0f + 2.0f * (pos.x - std::cos(rotation) * start_offset.x + std::sin(rotation) * start_offset.y) / win_size.x, 427 | 1.0f - 2.0f * (pos.y - std::sin(rotation) * start_offset.x - std::cos(rotation) * start_offset.y) / win_size.y, 428 | 0.0f, 1.0f 429 | }; 430 | 431 | render_text_common(color, model_view_projection, coord_data, 432 | #ifndef USE_OPENGL_ES 433 | vao, 434 | #endif 435 | vbo); 436 | } 437 | 438 | void Font_sys::render_text_mat(const std::string & utf8_input, const Color & color, const Mat4 & model_view_projection) 439 | { 440 | pimpl->render_text(utf8_input, color, model_view_projection); 441 | } 442 | void Font_sys::Impl::render_text(const std::string & utf8_input, const Color & color, const Mat4 & model_view_projection) 443 | { 444 | std::vector> coords; 445 | std::vector coord_data; 446 | std::tie(coords, coord_data, std::ignore) = build_text(utf8_input); 447 | 448 | render_text_common(color, model_view_projection, coord_data, 449 | #ifndef USE_OPENGL_ES 450 | vao_, 451 | #endif 452 | vbo_); 453 | } 454 | 455 | void Font_sys::Impl::render_text_common(const Color & color, const Mat4 & model_view_projection, 456 | const std::vector & coord_data, 457 | #ifndef USE_OPENGL_ES 458 | GLuint vao, 459 | #endif 460 | GLuint vbo) 461 | { 462 | // save old settings 463 | #ifndef USE_OPENGL_ES 464 | GLint old_vao{0}; 465 | #endif 466 | GLint old_vbo{0}, old_prog{0}; 467 | GLint old_blend_src{0}, old_blend_dst{0}; 468 | GLint old_active_texture{0}, old_texture_2d{0}; 469 | 470 | #ifndef USE_OPENGL_ES 471 | glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &old_vao); 472 | #endif 473 | glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &old_vbo); 474 | glGetIntegerv(GL_CURRENT_PROGRAM, &old_prog); 475 | glGetIntegerv(GL_BLEND_SRC_RGB, &old_blend_src); 476 | glGetIntegerv(GL_BLEND_DST_RGB, &old_blend_dst); 477 | glGetIntegerv(GL_ACTIVE_TEXTURE, &old_active_texture); 478 | glGetIntegerv(GL_TEXTURE_BINDING_2D, &old_texture_2d); 479 | 480 | auto old_depth_test = glIsEnabled(GL_DEPTH_TEST); 481 | auto old_blend = glIsEnabled(GL_BLEND); 482 | 483 | glBindBuffer(GL_ARRAY_BUFFER, vbo); 484 | #ifndef USE_OPENGL_ES 485 | glBindVertexArray(vao); 486 | #else 487 | 488 | glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(Vec2), NULL); 489 | glEnableVertexAttribArray(0); 490 | glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(Vec2), (const GLvoid *)sizeof(Vec2)); 491 | glEnableVertexAttribArray(1); 492 | #endif 493 | 494 | // set up shader uniforms 495 | glUseProgram(common_data_->prog); 496 | glUniformMatrix4fv(common_data_->uniform_locations["model_view_projection"], 1, GL_FALSE, &model_view_projection[0][0]); 497 | glUniform4fv(common_data_->uniform_locations["color"], 1, &color[0]); 498 | 499 | glDisable(GL_DEPTH_TEST); 500 | glEnable(GL_BLEND); 501 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 502 | glActiveTexture(GL_TEXTURE0 + max_tu_count_); 503 | 504 | // draw text, per page 505 | for(const auto & cd: coord_data) 506 | { 507 | // bind the page's texture 508 | glBindTexture(GL_TEXTURE_2D, page_map_[cd.page_no].tex); 509 | glDrawArrays(GL_TRIANGLES, cd.start, cd.num_elements); 510 | } 511 | 512 | // restore old settings 513 | #ifndef USE_OPENGL_ES 514 | glBindVertexArray(old_vao); 515 | #endif 516 | glBindBuffer(GL_ARRAY_BUFFER, old_vbo); 517 | glUseProgram(old_prog); 518 | 519 | if(old_depth_test) 520 | glEnable(GL_DEPTH_TEST); 521 | else 522 | glDisable(GL_DEPTH_TEST); 523 | 524 | if(old_blend) 525 | glEnable(GL_BLEND); 526 | else 527 | glDisable(GL_BLEND); 528 | 529 | glBlendFunc(old_blend_src, old_blend_dst); 530 | glActiveTexture(old_active_texture); 531 | glBindTexture(GL_TEXTURE_2D, old_texture_2d); 532 | } 533 | 534 | std::unordered_map::iterator Font_sys::Impl::load_page(const uint32_t page_no) 535 | { 536 | // this assumes the page has not been created yet 537 | auto page_i = page_map_.emplace(std::make_pair(page_no, Page())).first; 538 | Page & page = page_i->second; 539 | 540 | // greyscale pixel storage 541 | std::vector tex_data(tex_width_ * tex_height_, 0); 542 | 543 | FT_GlyphSlot slot = face_->glyph; 544 | 545 | // load each glyph in the page (256 per page) 546 | for(uint32_t code_pt = page_no << 8; code_pt < ((page_no + 1) << 8); code_pt++) 547 | { 548 | unsigned short tbl_row = (code_pt >> 4) & 0xF; 549 | unsigned short tbl_col = code_pt & 0xF; 550 | 551 | // have freetype render the glyph 552 | FT_UInt glyph_i = FT_Get_Char_Index(face_, code_pt); 553 | if(FT_Load_Glyph(face_, glyph_i, FT_LOAD_RENDER) != FT_Err_Ok) 554 | { 555 | std::cerr<<"Err loading glyph for: "<bitmap; 560 | Char_info & c = page.char_info[code_pt & 0xFF]; 561 | 562 | // set glyph properties 563 | c.origin.x = -cell_bbox_.ul.x + slot->bitmap_left; 564 | c.origin.y = cell_bbox_.ul.y - slot->bitmap_top; 565 | c.bbox.ul.x = slot->bitmap_left; 566 | c.bbox.ul.y = slot->bitmap_top; 567 | c.bbox.lr.x = (int)bmp->width + slot->bitmap_left; 568 | c.bbox.lr.y = slot->bitmap_top - (int)bmp->rows; 569 | c.advance.x = slot->advance.x; 570 | c.advance.y = slot->advance.y; 571 | c.glyph_i = glyph_i; 572 | 573 | // copy glyph from freetype to texture storage 574 | // TODO: we are assuming bmp->pixel_mode == FT_PIXEL_MODE_GRAY here. 575 | // We will probably want to allow other formats at some point 576 | for(std::size_t y = 0; y < (std::size_t)bmp->rows; ++y) 577 | { 578 | for(std::size_t x = 0; x < (std::size_t)bmp->width; ++x) 579 | { 580 | long tbl_img_y = tbl_row * cell_bbox_.height() + cell_bbox_.ul.y - slot->bitmap_top + y; 581 | long tbl_img_x = tbl_col * cell_bbox_.width() - cell_bbox_.ul.x + slot->bitmap_left + x; 582 | 583 | tex_data[tbl_img_y * tex_width_ + tbl_img_x] = bmp->buffer[y * bmp->width + x]; 584 | } 585 | } 586 | } 587 | 588 | // copy data to a new opengl texture 589 | glGenTextures(1, &page.tex); 590 | glActiveTexture(GL_TEXTURE0); 591 | glBindTexture(GL_TEXTURE_2D, page.tex); 592 | #ifndef USE_OPENGL_ES 593 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, tex_width_, tex_height_, 0, GL_RED, GL_UNSIGNED_BYTE, tex_data.data()); 594 | #else 595 | glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, tex_width_, tex_height_, 0, GL_ALPHA, GL_UNSIGNED_BYTE, tex_data.data()); 596 | #endif 597 | 598 | glGenerateMipmap(GL_TEXTURE_2D); 599 | // set params 600 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 601 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 602 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 603 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 604 | 605 | return page_i; 606 | } 607 | 608 | std::tuple>, std::vector, Font_sys::Impl::Bbox> 609 | Font_sys::Impl::build_text(const std::string & utf8_input) 610 | { 611 | Vec2 pen{0.0f, 0.0f}; 612 | 613 | // verts by font page 614 | std::unordered_map>> screen_and_tex_coords; 615 | 616 | Bbox font_box; 617 | 618 | font_box.ul.x = std::numeric_limits::max(); 619 | font_box.ul.y = std::numeric_limits::max(); 620 | font_box.lr.x = std::numeric_limits::min(); 621 | font_box.lr.y = std::numeric_limits::min(); 622 | 623 | FT_UInt prev_glyph_i = 0; 624 | 625 | for(auto & code_pt : utf8_to_utf32(utf8_input)) 626 | { 627 | // handle newlines 628 | if(code_pt == '\n') 629 | { 630 | pen.x = 0; 631 | pen.y += line_height_; 632 | prev_glyph_i = 0; 633 | continue; 634 | } 635 | 636 | // get font page struct 637 | uint32_t page_no = code_pt >> 8; 638 | auto page_i = page_map_.find(page_no); 639 | 640 | // load page if not already loaded 641 | if(page_i == page_map_.end()) 642 | { 643 | page_i = load_page(code_pt >> 8); 644 | } 645 | 646 | Font_sys::Impl::Page & page = page_i->second; 647 | Font_sys::Impl::Char_info & c = page.char_info[code_pt & 0xFF]; 648 | 649 | // add kerning if necessary 650 | if(has_kerning_info_ && prev_glyph_i && c.glyph_i) 651 | { 652 | FT_Vector kerning = {0, 0}; 653 | if(FT_Get_Kerning(face_, prev_glyph_i, c.glyph_i, FT_KERNING_DEFAULT, &kerning) != FT_Err_Ok) 654 | { 655 | std::cerr<<"Can't load kerning for: "<> 4) & 0xF; 662 | std::size_t tex_col = code_pt & 0xF; 663 | 664 | // texture coord of glyph's origin 665 | Vec2 tex_origin = {(float)(tex_col * cell_bbox_.width() - cell_bbox_.ul.x), 666 | (float)(tex_row * cell_bbox_.height() + cell_bbox_.ul.y)}; 667 | 668 | // push back vertex coords, and texture coords, interleaved, into a map by font page 669 | // 1 unit to pixel scale 670 | // lower left corner 671 | screen_and_tex_coords[page_no].push_back({pen.x + c.bbox.ul.x, 672 | pen.y - c.bbox.lr.y}); 673 | screen_and_tex_coords[page_no].push_back({(tex_origin.x + c.bbox.ul.x) / tex_width_, 674 | (tex_origin.y - c.bbox.lr.y) / tex_height_}); 675 | // lower right corner 676 | screen_and_tex_coords[page_no].push_back({pen.x + c.bbox.lr.x, 677 | pen.y - c.bbox.lr.y}); 678 | screen_and_tex_coords[page_no].push_back({(tex_origin.x + c.bbox.lr.x) / tex_width_, 679 | (tex_origin.y - c.bbox.lr.y) / tex_height_}); 680 | // upper left corner 681 | screen_and_tex_coords[page_no].push_back({pen.x + c.bbox.ul.x, 682 | pen.y - c.bbox.ul.y}); 683 | screen_and_tex_coords[page_no].push_back({(tex_origin.x + c.bbox.ul.x) / tex_width_, 684 | (tex_origin.y - c.bbox.ul.y) / tex_height_}); 685 | 686 | // upper left corner 687 | screen_and_tex_coords[page_no].push_back({pen.x + c.bbox.ul.x, 688 | pen.y - c.bbox.ul.y}); 689 | screen_and_tex_coords[page_no].push_back({(tex_origin.x + c.bbox.ul.x) / tex_width_, 690 | (tex_origin.y - c.bbox.ul.y) / tex_height_}); 691 | // lower right corner 692 | screen_and_tex_coords[page_no].push_back({pen.x + c.bbox.lr.x, 693 | pen.y - c.bbox.lr.y}); 694 | screen_and_tex_coords[page_no].push_back({(tex_origin.x + c.bbox.lr.x) / tex_width_, 695 | (tex_origin.y - c.bbox.lr.y) / tex_height_}); 696 | // upper right corner 697 | screen_and_tex_coords[page_no].push_back({pen.x + c.bbox.lr.x, 698 | pen.y - c.bbox.ul.y}); 699 | screen_and_tex_coords[page_no].push_back({(tex_origin.x + c.bbox.lr.x) / tex_width_, 700 | (tex_origin.y - c.bbox.ul.y) / tex_height_}); 701 | 702 | // expand bounding box for whole string 703 | font_box.ul.x = std::min(font_box.ul.x, pen.x + c.bbox.ul.x); 704 | font_box.ul.y = std::min(font_box.ul.y, pen.y - c.bbox.ul.y); 705 | font_box.lr.x = std::max(font_box.lr.x, pen.x + c.bbox.lr.x); 706 | font_box.lr.y = std::max(font_box.lr.y, pen.y - c.bbox.lr.y); 707 | 708 | // advance to next origin 709 | pen.x += c.advance.x / 64.0f; 710 | pen.y -= c.advance.y / 64.0f; 711 | 712 | prev_glyph_i = c.glyph_i; 713 | } 714 | 715 | // reorganize texture data into a contiguous array 716 | std::vector> coords; 717 | std::vector coord_data; 718 | 719 | 720 | for(const auto & page: screen_and_tex_coords) 721 | { 722 | coord_data.emplace_back(); 723 | Font_sys::Impl::Coord_data & c = coord_data.back(); 724 | 725 | c.page_no = page.first; 726 | 727 | c.start = coords.size() / 2; 728 | coords.insert(coords.end(), page.second.begin(), page.second.end()); 729 | c.num_elements = coords.size() / 2 - c.start; 730 | } 731 | 732 | return std::make_tuple(coords, coord_data, font_box); 733 | } 734 | 735 | void Font_sys::Impl::load_text_vbo(const std::vector> & coords) const 736 | { 737 | glBindBuffer(GL_ARRAY_BUFFER, vbo_); 738 | #ifndef USE_OPENGL_ES 739 | glBindVertexArray(vao_); 740 | #endif 741 | 742 | // load text into buffer object 743 | // call glBufferData with NULL first - this is apparently faster for dynamic data loading 744 | glBufferData(GL_ARRAY_BUFFER, sizeof(Vec2) * coords.size(), NULL, GL_DYNAMIC_DRAW); 745 | glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(Vec2) * coords.size(), coords.data()); 746 | 747 | } 748 | } 749 | -------------------------------------------------------------------------------- /src/font_common.cpp: -------------------------------------------------------------------------------- 1 | /// @file 2 | /// @brief Shared font data 3 | 4 | // Copyright 2022 Matthew Chandler 5 | 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | #include "font_impl.hpp" 25 | 26 | #include 27 | 28 | // include shader source strings (this file is assembled from shader files by CMake) 29 | #include "shaders.inl" 30 | 31 | namespace textogl 32 | { 33 | Font_sys::Impl::Font_common::Font_common() 34 | { 35 | FT_Error err = FT_Init_FreeType(&ft_lib); 36 | if(err != FT_Err_Ok) 37 | { 38 | throw std::system_error(err, std::system_category(), "Error loading freetype library"); 39 | } 40 | 41 | GLuint vert = glCreateShader(GL_VERTEX_SHADER); 42 | GLuint frag = glCreateShader(GL_FRAGMENT_SHADER); 43 | 44 | glShaderSource(vert, 1, &vert_shader_src, NULL); 45 | glShaderSource(frag, 1, &frag_shader_src, NULL); 46 | 47 | for(auto i : {std::make_pair(vert, "vertex"), std::make_pair(frag, "fragement")}) 48 | { 49 | glCompileShader(i.first); 50 | 51 | GLint compile_status; 52 | glGetShaderiv(i.first, GL_COMPILE_STATUS, &compile_status); 53 | 54 | if(compile_status != GL_TRUE) 55 | { 56 | GLint log_length {0}; 57 | glGetShaderiv(i.first, GL_INFO_LOG_LENGTH, &log_length); 58 | std::vector log(std::max(log_length + 1, 1)); 59 | log.back() = '\0'; 60 | glGetShaderInfoLog(i.first, log_length, NULL, log.data()); 61 | 62 | glDeleteShader(vert); 63 | glDeleteShader(frag); 64 | FT_Done_FreeType(ft_lib); 65 | 66 | throw std::system_error(compile_status, std::system_category(), std::string("Error compiling ") + i.second + " shader: \n" + 67 | std::string(log.data())); 68 | } 69 | } 70 | 71 | // create program and attatch new shaders to it 72 | prog = glCreateProgram(); 73 | glAttachShader(prog, vert); 74 | glAttachShader(prog, frag); 75 | 76 | // set attr locations 77 | glBindAttribLocation(prog, 0, "vert_pos"); 78 | glBindAttribLocation(prog, 1, "vert_tex_coords"); 79 | 80 | glLinkProgram(prog); 81 | 82 | // detatch and delete shaders 83 | glDetachShader(prog, vert); 84 | glDetachShader(prog, frag); 85 | glDeleteShader(vert); 86 | glDeleteShader(frag); 87 | 88 | // check for link errors 89 | GLint link_status; 90 | glGetProgramiv(prog, GL_LINK_STATUS, &link_status); 91 | if(link_status != GL_TRUE) 92 | { 93 | GLint log_length {0}; 94 | glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &log_length); 95 | std::vector log(std::max(log_length + 1, 1)); 96 | log.back() = '\0'; 97 | glGetProgramInfoLog(prog, log_length, NULL, log.data()); 98 | 99 | FT_Done_FreeType(ft_lib); 100 | glDeleteProgram(prog); 101 | 102 | throw std::system_error(link_status, std::system_category(), std::string("Error linking shader program:\n") + 103 | std::string(log.data())); 104 | } 105 | 106 | // get uniform locations 107 | GLint num_uniforms = 0; 108 | GLint max_buff_size = 0; 109 | glGetProgramiv(prog, GL_ACTIVE_UNIFORMS, &num_uniforms); 110 | glGetProgramiv(prog, GL_ACTIVE_UNIFORM_MAX_LENGTH, &max_buff_size); 111 | 112 | std::vector uniform(max_buff_size, '\0'); 113 | 114 | for(GLuint i = 0; i < static_cast(num_uniforms); ++i) 115 | { 116 | GLint size; GLenum type; 117 | glGetActiveUniform(prog, i, static_cast(uniform.size()), NULL, &size, &type, uniform.data()); 118 | 119 | GLint loc = glGetUniformLocation(prog, uniform.data()); 120 | if(loc != -1) 121 | uniform_locations[uniform.data()] = loc; 122 | } 123 | } 124 | 125 | Font_sys::Impl::Font_common::~Font_common() 126 | { 127 | FT_Done_FreeType(ft_lib); 128 | glDeleteProgram(prog); 129 | } 130 | 131 | unsigned int Font_sys::Impl::common_ref_cnt_ = 0; 132 | std::unique_ptr Font_sys::Impl::common_data_; 133 | } 134 | -------------------------------------------------------------------------------- /src/font_impl.hpp: -------------------------------------------------------------------------------- 1 | /// @file 2 | /// @brief Font loading and text display internal implementation 3 | 4 | // Copyright 2022 Matthew Chandler 5 | 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | #ifndef FONT_IMPL_HPP 25 | #define FONT_IMPL_HPP 26 | 27 | #include "textogl/font.hpp" 28 | 29 | #include 30 | #include 31 | 32 | #include 33 | #include FT_FREETYPE_H 34 | 35 | #ifdef USE_OPENGL_ES 36 | #include 37 | #else 38 | #include 39 | #endif 40 | 41 | /// @cond INTERNAL 42 | 43 | /// @ingroup textogl 44 | namespace textogl 45 | { 46 | /// Implementation details for font and text rendering 47 | struct Font_sys::Impl 48 | { 49 | /// Load a font file at a specified size 50 | Impl(const std::string & font_path, ///< Path to font file to use 51 | const unsigned int font_size ///< Font size (in pixels) 52 | ); 53 | /// Load a font at a specified size from memory 54 | 55 | /// data is not copied, so the client is responsible for maintaining the data for the lifetime of this object 56 | Impl(const unsigned char * font_data, ///< Font file data (in memory) 57 | const std::size_t font_data_size, ///< Font file data's size in memory 58 | const unsigned int font_size ///< Font size (in pixels) 59 | ); 60 | ~Impl(); 61 | 62 | /// @name Non-copyable 63 | /// @{ 64 | Impl(const Impl &) = delete; 65 | Impl & operator=(const Impl &) = delete; 66 | /// @} 67 | 68 | /// @name Movable 69 | /// @{ 70 | Impl(Impl &&); 71 | Impl & operator=(Impl &&); 72 | /// @} 73 | 74 | /// Common initialization code 75 | void init(FT_Open_Args & args, ///< Freetype face opening args (set by ctors) 76 | const unsigned int font_size ///< Font size (in pixels) 77 | ); 78 | 79 | /// Resize font 80 | 81 | /// Resizes the font without destroying it 82 | /// @note This will require rebuilding font textures 83 | /// @note Any Static_text objects tied to this Font_sys will need to have Static_text::set_font_sys called 84 | void resize(const unsigned int font_size ///< Font size (in pixels) 85 | ); 86 | 87 | /// Render given text 88 | 89 | /// Renders the text supplied in utf8_input parameter 90 | /// @note This will rebuild the OpenGL primitives each call. 91 | /// If the text will not change frequently, use a Static_text object 92 | /// instead 93 | void render_text(const std::string & utf8_input, ///< Text to render, in UTF-8 encoding. For best performance, normalize the string before rendering 94 | const Color & color, ///< Text Color 95 | const Vec2 & win_size, ///< Window dimensions. A Vec2 with X = width and Y = height 96 | const Vec2 & pos, ///< Render position, in screen pixels 97 | const float rotation, ///< Clockwise text rotation (in radians) around origin as defined in align_flags. 0 is vertical 98 | const int align_flags ///< Text Alignment. Should be #Text_origin flags bitwise-OR'd together 99 | ); 100 | 101 | /// Render given text 102 | 103 | /// Renders the text supplied in utf8_input parameter, using a model view projection matrix 104 | /// @note This will rebuild the OpenGL primitives each call. 105 | /// If the text will not change frequently, use a Static_text object 106 | /// instead 107 | void render_text(const std::string & utf8_input, ///< Text to render, in UTF-8 encoding. For best performance, normalize the string before rendering 108 | const Color & color, ///< Text Color 109 | /// Model view projection matrix. 110 | 111 | /// The text will be rendered as quads, one for each glyph, with vertex coordinates centered on the baselines and sized in pixels. 112 | /// This matrix will be used to transform that geometry 113 | const Mat4 & model_view_projection 114 | ); 115 | 116 | /// Container for Freetype library object and shader program 117 | 118 | /// Every Font_sys object can use the same instance of Font_Common, 119 | /// so only one should ever be initialized at any given time. 120 | /// Access to the single instance provied through Font_sys::common_data_ 121 | // TODO This is probably not thread-safe. Can we have it shared per-thread? 122 | class Font_common 123 | { 124 | public: 125 | Font_common(); 126 | ~Font_common(); 127 | 128 | /// @name Non-copyable, non-movable 129 | /// @{ 130 | Font_common(const Font_common &) = delete; 131 | Font_common & operator=(const Font_common &) = delete; 132 | 133 | Font_common(Font_common &&) = delete; 134 | Font_common & operator=(Font_common &&) = delete; 135 | /// @} 136 | 137 | FT_Library ft_lib; ///< Freetype library object. [See Freetype documentation](https://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_Library) 138 | GLuint prog; ///< OpenGL shader program index 139 | std::unordered_map uniform_locations; ///< OpenGL shader program uniform location indexes 140 | }; 141 | 142 | /// Bounding box 143 | 144 | /// Used for laying out text and defining character and text boundaries 145 | template 146 | struct Bbox 147 | { 148 | Vec2 ul; ///< Upper-left corner 149 | Vec2 lr; ///< Lower-right corner 150 | 151 | /// Get the width of the box 152 | T width() const 153 | { 154 | return lr.x - ul.x; 155 | } 156 | 157 | /// Get the height of the box 158 | T height() const 159 | { 160 | return ul.y - lr.y; 161 | } 162 | }; 163 | 164 | /// OpenGL Vertex buffer object data 165 | struct Coord_data 166 | { 167 | uint32_t page_no; ///< Unicode code page number for a set of characters 168 | std::size_t start; ///< Starting index into \ref vbo_ for this pages's quads 169 | std::size_t num_elements; ///< Number of indexs to render for this page 170 | }; 171 | 172 | /// Character info 173 | 174 | /// Contains information about a single code-point (character) 175 | struct Char_info 176 | { 177 | Vec2 origin; ///< Character's origin within \ref bbox 178 | Vec2 advance; ///< Distance to next char's origin 179 | Bbox bbox; ///< Bounding box for the character 180 | FT_UInt glyph_i; ///< Glyph index 181 | }; 182 | 183 | /// Font page 184 | 185 | /// Texture for a single Unicode code 'page' (where a page is 256 186 | /// consecutive code points) 187 | struct Page 188 | { 189 | GLuint tex; ///< OpenGL Texture index for the page 190 | Char_info char_info[256]; ///< Info for each code point on the page 191 | }; 192 | 193 | /// Create data for a code page 194 | 195 | /// Creates texture and layout information for the given code page 196 | /// @param page_no The Unicode page number to build 197 | /// @returns iterator into \ref page_map_ containing the page data 198 | /// @note This assumes the page hasn't been created yet. Do not call 199 | /// if the page already exists in \ref page_map_ 200 | std::unordered_map::iterator load_page(const uint32_t page_no); 201 | 202 | /// Common font rendering routine 203 | 204 | /// Rendering calls common to Font_sys and Static_text 205 | void render_text_common(const Color & color, ///< Text Color 206 | const Vec2 & win_size, ///< Window dimensions. A Vec2 with X = width and Y = height 207 | const Vec2 & pos, ///< Render position, in screen pixels 208 | const int align_flags, ///< Text Alignment. Should be #Text_origin flags bitwise-OR'd together 209 | const float rotation, ///< Clockwise text rotation (in radians) around origin as defined in align_flags. 0 is vertical 210 | const Bbox & text_box, ///< Text's bounding box 211 | const std::vector & coord_data, ///< Pre-calculated coordinate data as returned by \ref build_text 212 | #ifndef USE_OPENGL_ES 213 | GLuint vao, ///< OpenGL vertex array object 214 | #endif 215 | GLuint vbo ///< OpenGL vertex buffer object 216 | ); 217 | 218 | /// Common font rendering routine 219 | 220 | /// Rendering calls common to Font_sys and Static_text 221 | void render_text_common(const Color & color, ///< Text Color 222 | const Mat4 & model_view_projection, ///< Model view projection matrix to transform text by 223 | const std::vector & coord_data, ///< Pre-calculated coordinate data as returned by \ref build_text 224 | #ifndef USE_OPENGL_ES 225 | GLuint vao, ///< OpenGL vertex array object 226 | #endif 227 | GLuint vbo ///< OpenGL vertex buffer object 228 | ); 229 | 230 | /// Build buffer of quads for and coordinate data for text display 231 | 232 | /// @param utf8_input Text to build data for 233 | /// @returns A tuple of 234 | /// * Quad coordinates, ready to be stored into an OpenGL VBO 235 | /// * VBO start and end data for use in glDrawArrays 236 | /// * Bounding box of resulting text 237 | std::tuple>, std::vector, Bbox> 238 | build_text(const std::string & utf8_input); 239 | 240 | /// Load text into OpenGL vertex buffer object 241 | void load_text_vbo(const std::vector> & coords ///< Vertex coordinates returned as part of \ref build_text 242 | ) const; 243 | 244 | static std::unique_ptr common_data_; ///< Font data common to all instances of Font_sys 245 | static unsigned int common_ref_cnt_; ///< Reference count for \ref common_data_ 246 | 247 | /// @name Font data 248 | /// @{ 249 | unsigned char * font_data_ = nullptr; ///< Font file data 250 | FT_Face face_; ///< Font face. [See Freetype documentation](https://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_Face) 251 | bool has_kerning_info_; ///< \c true if the font has kerning information available 252 | Bbox cell_bbox_; ///< Bounding box representing maximum extents of a glyph 253 | int line_height_; ///< Spacing between baselines for each line of text 254 | /// @} 255 | 256 | /// @name Texture size 257 | /// Width and height of the texture. Each font page will be rendered to a 258 | /// grid of 16x16 glyphs, with each cell in the grid being 259 | /// \ref cell_bbox_ sized + 2px for padding 260 | /// @{ 261 | size_t tex_width_; 262 | size_t tex_height_; 263 | /// @} 264 | 265 | std::unordered_map page_map_; ///< Font pages 266 | 267 | #ifndef USE_OPENGL_ES 268 | GLuint vao_; ///< OpenGL Vertex array object index 269 | #endif 270 | GLuint vbo_; ///< OpenGL Vertex buffer object index 271 | GLint max_tu_count_; ///< Max texture units supported by graphic driver 272 | }; 273 | } 274 | /// @endcond INTERNAL 275 | #endif // FONT_IMPL_HPP 276 | -------------------------------------------------------------------------------- /src/shaders/font.gl33.frag: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Matthew Chandler 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | #version 130 22 | 23 | in vec2 tex_coord; 24 | 25 | uniform sampler2D font_page; 26 | uniform vec4 color; 27 | 28 | out vec4 frag_color; 29 | 30 | void main() 31 | { 32 | // get alpha from font texture 33 | frag_color = vec4(color.rgb, color.a * textureLod(font_page, tex_coord, 0.0).r); 34 | } 35 | -------------------------------------------------------------------------------- /src/shaders/font.gl33.vert: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Matthew Chandler 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | #version 130 22 | 23 | in vec2 vert_pos; 24 | in vec2 vert_tex_coords; 25 | 26 | uniform mat4 model_view_projection; 27 | 28 | out vec2 tex_coord; 29 | 30 | void main() 31 | { 32 | tex_coord = vert_tex_coords; 33 | gl_Position = model_view_projection * vec4(vert_pos, 0.0, 1.0); 34 | } 35 | -------------------------------------------------------------------------------- /src/shaders/font.gles20.frag: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Matthew Chandler 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | precision mediump float; 22 | 23 | varying vec2 tex_coord; 24 | 25 | uniform sampler2D font_page; 26 | uniform vec4 color; 27 | 28 | void main() 29 | { 30 | // get alpha from font texture 31 | gl_FragColor = vec4(color.rgb, color.a * texture2D(font_page, tex_coord).a); 32 | } 33 | -------------------------------------------------------------------------------- /src/shaders/font.gles20.vert: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Matthew Chandler 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | attribute vec2 vert_pos; 22 | attribute vec2 vert_tex_coords; 23 | 24 | uniform mat4 model_view_projection; 25 | 26 | varying vec2 tex_coord; 27 | 28 | void main() 29 | { 30 | tex_coord = vert_tex_coords; 31 | gl_Position = model_view_projection * vec4(vert_pos, 0.0, 1.0); 32 | } 33 | -------------------------------------------------------------------------------- /src/shaders/shaders.inl.in: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Matthew Chandler 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | // NOTE: The shader source itself will be filled in by CMake 22 | 23 | const char * vert_shader_src = R"( 24 | @VERT_SHADER@ 25 | )"; 26 | 27 | const char * frag_shader_src = R"( 28 | @FRAG_SHADER@ 29 | )"; 30 | -------------------------------------------------------------------------------- /src/static_text.cpp: -------------------------------------------------------------------------------- 1 | /// @file 2 | /// @brief Static text object 3 | 4 | // Copyright 2022 Matthew Chandler 5 | 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | #include "textogl/static_text.hpp" 25 | #include "font_impl.hpp" 26 | 27 | #include 28 | 29 | #ifdef USE_OPENGL_ES 30 | #include 31 | #else 32 | #include 33 | #endif 34 | 35 | 36 | namespace textogl 37 | { 38 | /// Implementation details for font and text rendering 39 | struct Static_text::Impl 40 | { 41 | /// Create and build text object 42 | /// @param font Font_sys object containing desired font. A pointer 43 | /// to this is stored internally, so the Font_sys object must 44 | /// remain valid for the life of the Static_text object 45 | /// @param utf8_input Text to render, in UTF-8 encoding. For best performance, normalize the string before rendering 46 | Impl(Font_sys & font, 47 | const std::string & utf8_input 48 | ); 49 | ~Impl(); 50 | 51 | /// @name Non-copyable 52 | /// @{ 53 | Impl(const Impl &) = delete; 54 | Impl & operator=(const Impl &) = delete; 55 | /// @} 56 | 57 | /// @name Movable 58 | /// @{ 59 | Impl(Impl && other); 60 | Impl & operator=(Impl && other); 61 | /// @} 62 | 63 | /// Recreate text object with new Font_sys 64 | 65 | /// Useful when Font_sys::resize has been called 66 | 67 | /// @param font Font_sys object containing desired font. A pointer 68 | /// to this is stored internally, so the Font_sys object must 69 | /// remain valid for the life of the Static_text object 70 | void set_font_sys(Font_sys & font); 71 | 72 | /// Recreate text object with new string 73 | 74 | /// @param utf8_input Text to render, in UTF-8 encoding. For best performance, normalize the string before rendering 75 | void set_text(const std::string & utf8_input); 76 | 77 | /// Render the previously set text 78 | void render_text(const Color & color, ///< Text Color 79 | const Vec2 & win_size, ///< Window dimensions. A Vec2 with X = width and Y = height 80 | const Vec2 & pos, ///< Render position, in screen pixels 81 | const float rotation, ///< Clockwise text rotation (in radians) around origin as defined in align_flags. 0 is vertical 82 | const int align_flags ///< Text Alignment. Should be #Text_origin flags bitwise-OR'd together 83 | ); 84 | 85 | /// Render the previously set text, using a model view projection matrix 86 | void render_text(const Color & color, ///< Text Color 87 | /// Model view projection matrix. 88 | 89 | /// The text will be rendered as quads, one for each glyph, with vertex coordinates centered on the baselines and sized in pixels. 90 | /// This matrix will be used to transform that geometry 91 | const Mat4 & model_view_projection 92 | ); 93 | 94 | void rebuild(); ///< Rebuild text data 95 | 96 | /// Points to Font_sys chosen at construction. 97 | std::shared_ptr font_; 98 | 99 | std::string text_; ///< Text to render, in UTF-8 encoding. 100 | 101 | #ifndef USE_OPENGL_ES 102 | GLuint vao_; ///< OpenGL Vertex array object index 103 | #endif 104 | GLuint vbo_; ///< OpenGL Vertex buffer object index 105 | 106 | std::vector coord_data_; ///< Start and end indexs into \ref vbo_ 107 | Font_sys::Impl::Bbox text_box_; ///< Bounding box for the text 108 | }; 109 | 110 | Static_text::Static_text(Font_sys & font, const std::string & utf8_input): pimpl(new Impl(font, utf8_input), [](Impl * impl){ delete impl; }) {} 111 | Static_text::Impl::Impl(Font_sys & font, const std::string & utf8_input): font_(font.pimpl), text_(utf8_input) 112 | { 113 | #ifndef USE_OPENGL_ES 114 | glGenVertexArrays(1, &vao_); 115 | glBindVertexArray(vao_); 116 | #endif 117 | glGenBuffers(1, &vbo_); 118 | glBindBuffer(GL_ARRAY_BUFFER, vbo_); 119 | 120 | // set up buffer obj properties, load vertex data 121 | rebuild(); 122 | 123 | #ifndef USE_OPENGL_ES 124 | glBindVertexArray(vao_); 125 | glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(Vec2), NULL); 126 | glEnableVertexAttribArray(0); 127 | glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(Vec2), (const GLvoid *)sizeof(Vec2)); 128 | glEnableVertexAttribArray(1); 129 | 130 | glBindVertexArray(0); 131 | #endif 132 | } 133 | 134 | Static_text::Impl::~Impl() 135 | { 136 | // destroy VAO/VBO 137 | glDeleteBuffers(1, &vbo_); 138 | #ifndef USE_OPENGL_ES 139 | glDeleteVertexArrays(1, &vao_); 140 | #endif 141 | } 142 | 143 | Static_text::Impl::Impl(Impl && other): 144 | font_(other.font_), 145 | text_(other.text_), 146 | #ifndef USE_OPENGL_ES 147 | vao_(other.vao_), 148 | #endif 149 | vbo_(other.vbo_), 150 | coord_data_(std::move(other.coord_data_)), 151 | text_box_(std::move(other.text_box_)) 152 | { 153 | #ifndef USE_OPENGL_ES 154 | other.vao_ = 0; 155 | #endif 156 | other.vbo_ = 0; 157 | } 158 | Static_text::Impl & Static_text::Impl::operator=(Impl && other) 159 | { 160 | if(this != &other) 161 | { 162 | font_ = other.font_; 163 | text_ = other.text_; 164 | #ifndef USE_OPENGL_ES 165 | vao_ = other.vao_; 166 | #endif 167 | vbo_ = other.vbo_; 168 | coord_data_ = std::move(other.coord_data_); 169 | text_box_ = std::move(other.text_box_); 170 | 171 | #ifndef USE_OPENGL_ES 172 | other.vao_ = 0; 173 | #endif 174 | other.vbo_ = 0; 175 | } 176 | return *this; 177 | } 178 | 179 | void Static_text::set_font_sys(Font_sys & font) 180 | { 181 | pimpl->set_font_sys(font); 182 | } 183 | void Static_text::Impl::set_font_sys(Font_sys & font) 184 | { 185 | font_ = font.pimpl; 186 | rebuild(); 187 | } 188 | 189 | void Static_text::set_text(const std::string & utf8_input) 190 | { 191 | pimpl->set_text(utf8_input); 192 | } 193 | void Static_text::Impl::set_text(const std::string & utf8_input) 194 | { 195 | text_ = utf8_input; 196 | rebuild(); 197 | } 198 | 199 | void Static_text::render_text(const Color & color, const Vec2 & win_size, 200 | const Vec2 & pos, const int align_flags) 201 | { 202 | pimpl->render_text(color, win_size, pos, 0.0f, align_flags); 203 | } 204 | void Static_text::render_text_rotate(const Color & color, const Vec2 & win_size, 205 | const Vec2 & pos, const float rotation, const int align_flags) 206 | { 207 | pimpl->render_text(color, win_size, pos, rotation, align_flags); 208 | } 209 | void Static_text::Impl::render_text(const Color & color, const Vec2 & win_size, 210 | const Vec2 & pos, const float rotation, const int align_flags) 211 | { 212 | font_->render_text_common(color, win_size, pos, align_flags, rotation, text_box_, coord_data_, 213 | #ifndef USE_OPENGL_ES 214 | vao_, 215 | #endif 216 | vbo_); 217 | } 218 | 219 | void Static_text::render_text_mat(const Color & color, const Mat4 & model_view_projection) 220 | { 221 | pimpl->render_text(color, model_view_projection); 222 | } 223 | void Static_text::Impl::render_text(const Color & color, const Mat4 & model_view_projection) 224 | { 225 | font_->render_text_common(color, model_view_projection, coord_data_, 226 | #ifndef USE_OPENGL_ES 227 | vao_, 228 | #endif 229 | vbo_); 230 | } 231 | 232 | void Static_text::Impl::rebuild() 233 | { 234 | // build the text 235 | std::vector> coords; 236 | std::tie(coords, coord_data_, text_box_) = font_->build_text(text_); 237 | 238 | glBindBuffer(GL_ARRAY_BUFFER, vbo_); 239 | #ifndef USE_OPENGL_ES 240 | glBindVertexArray(vao_); 241 | #else 242 | glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(Vec2), NULL); 243 | glEnableVertexAttribArray(0); 244 | glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(Vec2), (const GLvoid *)sizeof(Vec2)); 245 | glEnableVertexAttribArray(1); 246 | #endif 247 | 248 | // reload vertex data 249 | glBufferData(GL_ARRAY_BUFFER, sizeof(Vec2) * coords.size(), coords.data(), GL_STATIC_DRAW); 250 | 251 | #ifndef USE_OPENGL_ES 252 | glBindVertexArray(0); 253 | #endif 254 | } 255 | 256 | } 257 | -------------------------------------------------------------------------------- /textogl-config-version.cmake.in: -------------------------------------------------------------------------------- 1 | set(PACKAGE_VERSION "@VERSION_SHORT@") 2 | if("${PACKAGE_FIND_VERSION}" VERSION_EQUAL "@VERSION_SHORT@") 3 | set(PACKAGE_VERSION_EXACT 1) 4 | endif() 5 | if("${PACKAGE_FIND_VERSION_MAJOR}" EQUAL @VERSION_MAJOR@) 6 | set(PACKAGE_VERSION_COMPATIBLE 1) 7 | endif() 8 | -------------------------------------------------------------------------------- /textogl-config.cmake.in: -------------------------------------------------------------------------------- 1 | include("@CMAKE_INSTALL_PREFIX@/lib/cmake/textogl/textogl-targets.cmake") 2 | get_filename_component(TEXTOGL_INCLUDE_DIRS "@CMAKE_INSTALL_PREFIX@/include/textogl" ABSOLUTE) 3 | -------------------------------------------------------------------------------- /textogl.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | exec_prefix=${prefix} 3 | libdir=${exec_prefix}/lib 4 | includedir=${prefix}/include 5 | 6 | Name: @TITLE@ 7 | Description: @SUMMARY@ 8 | Version: @VERSION_SHORT@ 9 | Libs: -L${libdir} -l@PROJECT_NAME@ 10 | Cflags: -I${includedir} 11 | --------------------------------------------------------------------------------