Compare commits

...

42 Commits
v1.1 ... master

Author SHA1 Message Date
Chlumsky 30b6f4fd1a Image format information #128, warning fix #129 2025-05-31 13:17:46 +02:00
Chlumsky f7eb7efdad Readme update 2025-01-27 12:18:50 +01:00
Chlumsky 7e8d34645a FontGeometry constructor fix 2024-11-24 09:39:37 +01:00
Chlumsky c27de5988d Charset parsing from argument string 2024-06-01 23:12:59 +02:00
Chlumsky d120bb3e05 MSDFgen 1.12 and new image formats 2024-05-26 19:07:38 +02:00
Chlumsky b0a423bf00 Updated MSDFgen (improved shape preprocessing) 2024-05-07 17:26:58 +02:00
Viktor Chlumský 2588a2aae3
Padding 2024-05-01 23:25:00 +02:00
Chlumsky b67fb967c3 MSDFgen font coordinate scaling fix 2024-04-21 12:24:14 +02:00
Chlumsky f424021a2b Dependency configuration optimized 2024-03-10 13:27:29 +01:00
Chlumsky bed35b13b0 Simplified argument parsing 2024-03-10 09:19:55 +01:00
Chlumsky 3a4f181674 Renamed padding to spacing 2024-03-10 00:01:51 +01:00
Chlumsky b716311880 Minor code style change 2024-03-09 23:23:17 +01:00
Chlumsky 37d185878d Uniform grid atlas completed 2024-03-09 23:03:56 +01:00
Chlumsky 0f48aaa727 Grid atlas origin pixel alignment 2024-03-08 02:07:35 +01:00
Chlumsky 37ffbd85ee Initial version of grid atlas packer, CLI integration 2024-03-03 21:31:28 +01:00
Chlumsky ae441c9989 Origin pixel alignment per axis, CLI setting 2024-03-03 20:43:56 +01:00
Chlumsky 3e73a31618 Glyph wrap keeps integer origin 2024-03-03 18:01:55 +01:00
Chlumsky f1ad23f7c4 Added -allglyphs option 2024-02-06 20:26:24 +01:00
Chlumsky 3cf874b39a Added CMake presets, errors to stderr 2023-09-09 20:35:06 +02:00
Chlumsky b1af88cfca Updated MSDFgen submodule 2023-04-12 18:14:11 +02:00
Chlumsky 39b0f28b44 DynamicAtlas argument passthrough, submodule update 2023-04-07 15:39:54 +02:00
Chlumsky f6a1bc9f76 Miscellaneous fixes 2023-01-25 22:32:35 +01:00
Chlumsky 132c3af316 Artery font made optional, print version. accept --args 2023-01-21 16:50:39 +01:00
Chlumsky 718a0ca77c Variable font support 2023-01-21 15:42:07 +01:00
Chlumsky 6932b35c00 Build configuration overhaul, vcpkg dependency management 2023-01-20 21:58:52 +01:00
Chlumsky 50d1a1c275 Fixed and updated CMake configuration 2022-01-14 13:32:30 +01:00
Chlumsky b086f93a36 Added documentation for library API 2021-12-17 19:39:10 +01:00
Chlumsky a99a4575a4 Expand dynamic atlas without rearranging previous glyphs 2021-12-15 23:55:13 +01:00
Chlumsky dadc65c85c Updated MSDFgen, improved DynamicAtlas API 2021-12-07 18:04:16 +01:00
Chlumsky b5d54f3012 Fixed generate call arguments in DynamicAtlas 2021-09-19 18:15:58 +02:00
Chlumsky ae40bf4a3a CMake support 2021-09-01 23:32:13 +02:00
Chlumsky 3ff9b05254 Fixed return value of saveImageText 2021-07-27 10:46:07 +02:00
Chlumsky ed96d92185 Updated to MSDFgen 1.9.1 2021-07-09 16:13:13 +02:00
Chlumsky a49bf0d5ea Added -coloringstrategy option, version 1.2 2021-05-29 18:52:42 +02:00
Chlumsky 0e81034513 Error correction buffer optimization 2021-05-29 14:45:59 +02:00
Chlumsky d238eecc5b Updated to MSDFgen 1.9 2021-05-29 11:56:55 +02:00
Chlumsky 67510959f1 Added -yorigin setting 2021-05-01 22:05:28 +02:00
Chlumsky 84e8c25241 Revert "Fixed error correction invoked twice with scanline pass"
This reverts commit e44853e47e.
2021-03-27 11:23:01 +01:00
Chlumsky 7dea527d8d Added -fontname argument 2021-03-21 12:45:54 +01:00
Chlumsky 690ae5fb16 Support for multiple fonts in one atlas 2021-03-21 12:45:54 +01:00
Chlumsky e44853e47e Fixed error correction invoked twice with scanline pass 2021-03-11 22:52:57 +01:00
Chlumsky c9ab9974ce Improved Shadron preview with floating-point atlas 2021-03-11 22:34:49 +01:00
65 changed files with 4165 additions and 1487 deletions

1
.gitattributes vendored Normal file
View File

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

23
.gitignore vendored
View File

@ -1,10 +1,15 @@
Debug/
Release/
Debug Library/
Release Library/
x86/
x64/
/build/
/Debug/
/Release/
/Debug Library/
/Release Library/
/x86/
/x64/
.vs/
.vscode/
.DS_Store
*.exe
*.zip
*.user
*.sdf
*.pdb
@ -13,7 +18,9 @@ x64/
*.suo
*.VC.opendb
*.VC.db
bin/*.lib
/bin/*.lib
/bin/msdf-atlas-gen
output.png
CMakeUserPresets.json
out/
build/
/cmake-gen.bat

View File

@ -1,4 +1,41 @@
## Version 1.3 (2024-06-01)
- Updated to MSDFgen 1.12
- Switched to vcpkg as the primary dependency management system
- Removed Visual Studio solution and Makefile - now has to be generated by CMake
- CMake configuration overhaul, added installation configuration
- Switched to libpng as the primary PNG file encoder
- Added uniform grid mode (`-uniformgrid`) where atlas is laid out in a rectangular grid
- Added options to add extra padding around glyphs (`-empadding`, `-pxpadding` and similar)
- Added the possibility to specify asymmetrical distance range (`-aemrange`, `-apxrange`)
- Added `-pxalign` option which governs glyph alignment with the pixel grid
- Added `-allglyphs` option as alternative to explicit charset / glyphset
- Added `-chars` and `-glyphs` options to specify charset / glyphset directly on command line
- Added `-varfont` option to configure variables of variable fonts
- Added `-version` option to print program version
- Arguments with double dash (e.g. `--font`) now also accepted
- Minor fix to positioning for `-type hardmask`
- Errors are now reported to `stderr`
- TinyXML 2 no longer required as a dependency
### Version 1.2.2 (2021-09-06)
- CMake support
- Conan package manager support
### Version 1.2.1 (2021-07-09)
- Updated to MSDFgen 1.9.1
## Version 1.2 (2021-05-29)
- Updated to MSDFgen 1.9.
- Multiple fonts or font sizes can now be compiled into a single atlas.
- Added `-yorigin` option to choose if Y-coordinates increase from bottom to top or from top to bottom.
- Added `-coloringstrategy` option to select MSDF edge coloring heuristic.
- Shadron preview now properly loads floating-point image outputs in full range mode.
## Version 1.1 (2020-10-18)
- Updated to MSDFgen 1.8.

207
CMakeLists.txt Normal file
View File

@ -0,0 +1,207 @@
cmake_minimum_required(VERSION 3.15)
include(cmake/version.cmake)
option(MSDF_ATLAS_BUILD_STANDALONE "Build the msdf-atlas-gen standalone executable" ON)
option(MSDF_ATLAS_USE_VCPKG "Use vcpkg package manager to link project dependencies" ON)
option(MSDF_ATLAS_USE_SKIA "Build with the Skia library" ON)
option(MSDF_ATLAS_NO_ARTERY_FONT "Disable Artery Font export and do not require its submodule" OFF)
option(MSDF_ATLAS_MSDFGEN_EXTERNAL "Do not build the msdfgen submodule but find it as an external package" OFF)
option(MSDF_ATLAS_INSTALL "Generate installation target" OFF)
option(MSDF_ATLAS_DYNAMIC_RUNTIME "Link dynamic runtime library instead of static" OFF)
option(BUILD_SHARED_LIBS "Generate dynamic library files instead of static" OFF)
if(NOT MSDF_ATLAS_MSDFGEN_EXTERNAL)
set(MSDFGEN_DISABLE_SVG ON CACHE BOOL "Disable unused SVG functionality to minimize dependencies")
set(MSDFGEN_CORE_ONLY OFF CACHE INTERNAL "Only build the core msdfgen library with no dependencies (disabled for msdf-atlas-gen)" FORCE)
set(MSDFGEN_BUILD_STANDALONE OFF CACHE BOOL "Build the msdfgen standalone executable")
set(MSDFGEN_USE_VCPKG ${MSDF_ATLAS_USE_VCPKG} CACHE INTERNAL "Use vcpkg package manager to link msdfgen project dependencies" FORCE)
set(MSDFGEN_USE_OPENMP OFF CACHE INTERNAL "Build with OpenMP support for multithreaded code (disabled for msdf-atlas-gen)" FORCE)
set(MSDFGEN_USE_CPP11 ON CACHE INTERNAL "Build with C++11 enabled (always enabled for msdf-atlas-gen)" FORCE)
set(MSDFGEN_USE_SKIA ${MSDF_ATLAS_USE_SKIA} CACHE INTERNAL "Build msdfgen with the Skia library" FORCE)
set(MSDFGEN_INSTALL ${MSDF_ATLAS_INSTALL} CACHE INTERNAL "Generate installation target for msdfgen" FORCE)
set(MSDFGEN_DYNAMIC_RUNTIME ${MSDF_ATLAS_DYNAMIC_RUNTIME} CACHE INTERNAL "Link dynamic runtime library instead of static for msdfgen" FORCE)
endif()
get_property(MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(NOT MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE)
message(STATUS "CMAKE_BUILD_TYPE not set, defaulting to Release")
set(CMAKE_BUILD_TYPE Release)
endif()
if(MSDF_ATLAS_DYNAMIC_RUNTIME)
set(MSDF_ATLAS_MSVC_RUNTIME "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
else()
set(MSDF_ATLAS_MSVC_RUNTIME "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()
if(BUILD_SHARED_LIBS)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif()
if(MSDF_ATLAS_USE_VCPKG)
# Make sure that vcpkg toolchain file is set
if(NOT CMAKE_TOOLCHAIN_FILE)
if(DEFINED ENV{VCPKG_ROOT})
set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake")
else()
message(SEND_ERROR "Vcpkg toolchain not configured. Either set VCPKG_ROOT environment variable or pass -DCMAKE_TOOLCHAIN_FILE=VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake to cmake")
endif()
endif()
# Default to statically linked vcpkg triplet on Windows
if(WIN32 AND NOT VCPKG_TARGET_TRIPLET AND NOT MSDF_ATLAS_DYNAMIC_RUNTIME)
if(CMAKE_GENERATOR_PLATFORM MATCHES "64$" AND NOT CMAKE_GENERATOR_PLATFORM STREQUAL "ARM64")
set(VCPKG_TARGET_TRIPLET "x64-windows-static")
elseif(CMAKE_GENERATOR_PLATFORM MATCHES "32$" OR CMAKE_GENERATOR_PLATFORM STREQUAL "x86")
set(VCPKG_TARGET_TRIPLET "x86-windows-static")
else()
if(CMAKE_GENERATOR_PLATFORM)
message(WARNING "Vcpkg triplet not explicitly specified and could not be deduced. Recommend using -DVCPKG_TARGET_TRIPLET=x64-windows-static or similar")
else()
message(WARNING "Vcpkg triplet not explicitly specified and could not be deduced. Recommend using -A to explicitly select platform (Win32 or x64)")
endif()
endif()
endif()
# Select project features
if(NOT MSDF_ATLAS_VCPKG_FEATURES_SET)
set(VCPKG_MANIFEST_NO_DEFAULT_FEATURES ON)
if(MSDF_ATLAS_USE_SKIA)
list(APPEND VCPKG_MANIFEST_FEATURES "geometry-preprocessing")
endif()
endif()
set(MSDFGEN_VCPKG_FEATURES_SET ON)
endif()
# Version is specified in vcpkg.json
project(msdf-atlas-gen VERSION ${MSDF_ATLAS_VERSION} LANGUAGES CXX)
if(MSDF_ATLAS_MSDFGEN_EXTERNAL)
if(NOT TARGET msdfgen::msdfgen)
find_package(msdfgen REQUIRED)
endif()
else()
add_subdirectory(msdfgen)
endif()
find_package(Threads REQUIRED)
if(NOT MSDFGEN_DISABLE_PNG AND NOT TARGET PNG::PNG)
find_package(PNG REQUIRED)
endif()
file(GLOB_RECURSE MSDF_ATLAS_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "msdf-atlas-gen/*.h" "msdf-atlas-gen/*.hpp")
file(GLOB_RECURSE MSDF_ATLAS_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "msdf-atlas-gen/*.cpp")
# msdf-atlas-gen library
add_library(msdf-atlas-gen ${MSDF_ATLAS_HEADERS} ${MSDF_ATLAS_SOURCES})
add_library(msdf-atlas-gen::msdf-atlas-gen ALIAS msdf-atlas-gen)
set_target_properties(msdf-atlas-gen PROPERTIES PUBLIC_HEADER "${MSDF_ATLAS_HEADERS}")
set_property(TARGET msdf-atlas-gen PROPERTY MSVC_RUNTIME_LIBRARY "${MSDF_ATLAS_MSVC_RUNTIME}")
target_compile_definitions(msdf-atlas-gen PUBLIC
MSDF_ATLAS_VERSION=${MSDF_ATLAS_VERSION}
MSDF_ATLAS_VERSION_MAJOR=${MSDF_ATLAS_VERSION_MAJOR}
MSDF_ATLAS_VERSION_MINOR=${MSDF_ATLAS_VERSION_MINOR}
MSDF_ATLAS_VERSION_REVISION=${MSDF_ATLAS_VERSION_REVISION}
MSDF_ATLAS_COPYRIGHT_YEAR=${MSDF_ATLAS_COPYRIGHT_YEAR}
)
target_include_directories(msdf-atlas-gen INTERFACE
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
)
if(MSDF_ATLAS_NO_ARTERY_FONT)
target_compile_definitions(msdf-atlas-gen PUBLIC MSDF_ATLAS_NO_ARTERY_FONT)
else()
target_include_directories(msdf-atlas-gen PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/artery-font-format)
endif()
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT msdf-atlas-gen)
target_compile_features(msdf-atlas-gen PUBLIC cxx_std_11)
target_link_libraries(msdf-atlas-gen PRIVATE Threads::Threads)
if(NOT MSDFGEN_DISABLE_PNG)
target_link_libraries(msdf-atlas-gen PRIVATE PNG::PNG)
endif()
target_link_libraries(msdf-atlas-gen PUBLIC msdfgen::msdfgen)
if(BUILD_SHARED_LIBS AND WIN32)
target_compile_definitions(msdf-atlas-gen PRIVATE "MSDF_ATLAS_PUBLIC=__declspec(dllexport)")
target_compile_definitions(msdf-atlas-gen INTERFACE "MSDF_ATLAS_PUBLIC=__declspec(dllimport)")
else()
target_compile_definitions(msdf-atlas-gen PUBLIC MSDF_ATLAS_PUBLIC=)
endif()
# msdf-atlas-gen standalone executable
if(MSDF_ATLAS_BUILD_STANDALONE)
set(MSDF_ATLAS_STANDALONE_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/msdf-atlas-gen/main.cpp")
if(MSVC)
set(MSDF_ATLAS_STANDALONE_SOURCES ${MSDF_ATLAS_STANDALONE_SOURCES} "${CMAKE_CURRENT_SOURCE_DIR}/msdf-atlas-gen.rc")
endif()
add_executable(msdf-atlas-gen-standalone ${MSDF_ATLAS_STANDALONE_SOURCES})
target_compile_definitions(msdf-atlas-gen-standalone PUBLIC MSDF_ATLAS_STANDALONE)
target_compile_definitions(msdf-atlas-gen-standalone PRIVATE MSDF_ATLAS_VERSION_UNDERLINE=${MSDF_ATLAS_VERSION_UNDERLINE})
set_property(TARGET msdf-atlas-gen-standalone PROPERTY MSVC_RUNTIME_LIBRARY "${MSDF_ATLAS_MSVC_RUNTIME}")
set_target_properties(msdf-atlas-gen-standalone PROPERTIES
OUTPUT_NAME msdf-atlas-gen
ARCHIVE_OUTPUT_NAME msdf-atlas-gen-standalone
# Avoid deleting msdf-atlas-gen.lib during clean
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
target_link_libraries(msdf-atlas-gen-standalone PRIVATE msdf-atlas-gen::msdf-atlas-gen)
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT msdf-atlas-gen-standalone)
endif()
# Installation
if(MSDF_ATLAS_INSTALL)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
set(MSDF_ATLAS_CONFIG_PATH "lib/cmake/msdf-atlas-gen")
# install tree package config
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/msdf-atlas-gen-config-version.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)
configure_package_config_file(
cmake/msdf-atlas-gen-config.cmake.in
${MSDF_ATLAS_CONFIG_PATH}/msdf-atlas-gen-config.cmake
INSTALL_DESTINATION ${MSDF_ATLAS_CONFIG_PATH}
NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
# build tree package config
configure_file(
cmake/msdf-atlas-gen-config.cmake.in
msdf-atlas-gen-config.cmake
@ONLY
)
install(TARGETS msdf-atlas-gen EXPORT msdf-atlas-gen-targets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
FRAMEWORK DESTINATION ${CMAKE_INSTALL_LIBDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/msdf-atlas-gen
)
if(MSVC AND BUILD_SHARED_LIBS)
install(FILES $<TARGET_PDB_FILE:msdf-atlas-gen> DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL)
endif()
export(EXPORT msdf-atlas-gen-targets NAMESPACE msdf-atlas-gen:: FILE "${CMAKE_CURRENT_BINARY_DIR}/msdf-atlas-gen-targets.cmake")
install(EXPORT msdf-atlas-gen-targets FILE msdf-atlas-gen-targets.cmake NAMESPACE msdf-atlas-gen:: DESTINATION ${MSDF_ATLAS_CONFIG_PATH})
if(MSDF_ATLAS_BUILD_STANDALONE)
install(TARGETS msdf-atlas-gen-standalone EXPORT msdf-atlas-gen-binary-targets DESTINATION ${CMAKE_INSTALL_BINDIR})
if(MSVC)
install(FILES $<TARGET_PDB_FILE:msdf-atlas-gen-standalone> DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL)
endif()
export(EXPORT msdf-atlas-gen-binary-targets NAMESPACE msdf-atlas-gen-standalone:: FILE "${CMAKE_CURRENT_BINARY_DIR}/msdf-atlas-gen-binary-targets.cmake")
install(EXPORT msdf-atlas-gen-binary-targets FILE msdf-atlas-gen-binary-targets.cmake NAMESPACE msdf-atlas-gen-standalone:: DESTINATION ${MSDF_ATLAS_CONFIG_PATH})
endif()
install(
FILES
"${CMAKE_CURRENT_BINARY_DIR}/${MSDF_ATLAS_CONFIG_PATH}/msdf-atlas-gen-config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/msdf-atlas-gen-config-version.cmake"
DESTINATION ${MSDF_ATLAS_CONFIG_PATH}
)
endif()

155
CMakePresets.json Normal file
View File

@ -0,0 +1,155 @@
{
"version": 4,
"cmakeMinimumRequired": {
"major": 3,
"minor": 23,
"patch": 0
},
"include": [
"cmake/CMakePresets.json"
],
"configurePresets": [
{
"name": "win64",
"displayName": "Windows x64 default config (static, vcpkg, Skia)",
"inherits": [ "win64-base", "vcpkg", "skia" ]
}, {
"name": "win32",
"displayName": "Windows x86 default config (static, vcpkg, Skia)",
"inherits": [ "win32-base", "vcpkg", "skia" ]
}, {
"name": "win64-dynamic",
"displayName": "Windows x64 dynamic config (vcpkg, Skia)",
"inherits": [ "win64-base", "vcpkg", "skia", "dynamic-runtime", "dynamic-lib" ],
"binaryDir": "${sourceDir}/build/win64-dynamic"
}, {
"name": "win32-dynamic",
"displayName": "Windows x86 dynamic config (vcpkg, Skia)",
"inherits": [ "win32-base", "vcpkg", "skia", "dynamic-runtime", "dynamic-lib" ],
"binaryDir": "${sourceDir}/build/win32-dynamic"
}, {
"name": "win64-no-skia",
"displayName": "Windows x64 config without Skia (static, vcpkg)",
"inherits": [ "win64-base", "vcpkg", "no-skia" ]
}, {
"name": "win32-no-skia",
"displayName": "Windows x86 config without Skia (static, vcpkg)",
"inherits": [ "win32-base", "vcpkg", "no-skia" ]
},
{
"name": "osx-vcpkg-rel",
"displayName": "Mac OS release config with vcpkg and Skia (static)",
"inherits": [ "osx-rel-base", "vcpkg", "skia" ]
}, {
"name": "osx-vcpkg-dbg",
"displayName": "Mac OS debug config with vcpkg and Skia (static)",
"inherits": [ "osx-dbg-base", "vcpkg", "skia" ]
}, {
"name": "osx-no-skia-rel",
"displayName": "Mac OS release config with system libraries and no Skia (static, install)",
"inherits": [ "osx-rel-base", "no-vcpkg", "no-skia", "install" ]
}, {
"name": "osx-no-skia-dbg",
"displayName": "Mac OS debug config with system libraries and no Skia (static, install)",
"inherits": [ "osx-dbg-base", "no-vcpkg", "no-skia", "install" ]
},
{
"name": "linux-vcpkg-rel",
"displayName": "Linux release config with vcpkg and Skia (static)",
"inherits": [ "linux-rel-base", "vcpkg", "skia" ]
}, {
"name": "linux-vcpkg-dbg",
"displayName": "Linux debug config with vcpkg and Skia (static)",
"inherits": [ "linux-dbg-base", "vcpkg", "skia" ]
}, {
"name": "linux-no-skia-rel",
"displayName": "Linux release config with system libraries and no Skia (static, install)",
"inherits": [ "linux-rel-base", "no-vcpkg", "no-skia", "install" ]
}, {
"name": "linux-no-skia-dbg",
"displayName": "Linux debug config with system libraries and no Skia (static, install)",
"inherits": [ "linux-dbg-base", "no-vcpkg", "no-skia", "install" ]
}
],
"buildPresets": [
{
"name": "win64-rel",
"configurePreset": "win64",
"configuration": "Release"
}, {
"name": "win64-dbg",
"configurePreset": "win64",
"configuration": "Debug"
}, {
"name": "win32-rel",
"configurePreset": "win32",
"configuration": "Release"
}, {
"name": "win32-dbg",
"configurePreset": "win32",
"configuration": "Debug"
}, {
"name": "win64-dynamic-rel",
"configurePreset": "win64-dynamic",
"configuration": "Release"
}, {
"name": "win64-dynamic-dbg",
"configurePreset": "win64-dynamic",
"configuration": "Debug"
}, {
"name": "win32-dynamic-rel",
"configurePreset": "win32-dynamic",
"configuration": "Release"
}, {
"name": "win32-dynamic-dbg",
"configurePreset": "win32-dynamic",
"configuration": "Debug"
}, {
"name": "win64-no-skia-rel",
"configurePreset": "win64-no-skia",
"configuration": "Release"
}, {
"name": "win64-no-skia-dbg",
"configurePreset": "win64-no-skia",
"configuration": "Debug"
}, {
"name": "win32-no-skia-rel",
"configurePreset": "win32-no-skia",
"configuration": "Release"
}, {
"name": "win32-no-skia-dbg",
"configurePreset": "win32-no-skia",
"configuration": "Debug"
},
{
"name": "osx-vcpkg-rel",
"configurePreset": "osx-vcpkg-rel"
}, {
"name": "osx-vcpkg-dbg",
"configurePreset": "osx-vcpkg-dbg"
}, {
"name": "osx-no-skia-rel",
"configurePreset": "osx-no-skia-rel"
}, {
"name": "osx-no-skia-dbg",
"configurePreset": "osx-no-skia-dbg"
},
{
"name": "linux-vcpkg-rel",
"configurePreset": "linux-vcpkg-rel"
}, {
"name": "linux-vcpkg-dbg",
"configurePreset": "linux-vcpkg-dbg"
}, {
"name": "linux-no-skia-rel",
"configurePreset": "linux-no-skia-rel"
}, {
"name": "linux-no-skia-dbg",
"configurePreset": "linux-no-skia-dbg"
}
]
}

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020 Viktor Chlumsky
Copyright (c) 2020 - 2025 Viktor Chlumsky
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,4 +0,0 @@
all:
mkdir -p bin
g++ -I /usr/local/include/freetype2 -I /usr/include/freetype2 -I artery-font-format -I msdfgen/include -I msdfgen -D MSDFGEN_USE_CPP11 -D MSDF_ATLAS_STANDALONE -std=c++11 -pthread -O2 -o bin/msdf-atlas-gen msdfgen/core/*.cpp msdfgen/lib/*.cpp msdfgen/ext/*.cpp msdf-atlas-gen/*.cpp -lfreetype

199
README.md
View File

@ -5,7 +5,7 @@ This is a utility for generating compact font atlases using [MSDFgen](https://gi
The atlas generator loads a subset of glyphs from a TTF or OTF font file, generates a distance field for each of them, and tightly packs them into an atlas bitmap (example below). The finished atlas and/or its layout metadata can be exported as an [Artery Font](https://github.com/Chlumsky/artery-font-format) file, a plain image file, a CSV sheet or a structured JSON file.
![Atlas example](https://user-images.githubusercontent.com/18639794/76163889-811f2e80-614a-11ea-9b28-1eed54dbb899.png)
![Atlas example](https://github.com/Chlumsky/msdf-atlas-gen/assets/18639794/ee8bfc77-7d36-4cbb-82df-aa8a02424b4a)
A font atlas is typically stored in texture memory and used to draw text in real-time rendering contexts such as video games.
@ -27,14 +27,17 @@ The atlas generator can generate the following six types of atlases.
Notes:
- *Sharp corners* refers to preservation of corner sharpness when upscaled.
- *Soft effects* refers to the support of effects that use true distance, such as glows, rounded borders, or simplified shadows.
- *Hard effects* refers to the support of effects that use pseudo-distance, such as mitered borders or thickness adjustment.
- *Soft effects* refers to the support of effects that use true distance, such as glows, rounded outlines, or simplified shadows.
- *Hard effects* refers to the support of effects that use perpendicular distance, such as mitered outlines or thickness adjustment.
## Getting started
This project can be used either as a library or as a standalone console program.
To start using the program immediately, there is a Windows binary available for download in the ["Releases" section](https://github.com/Chlumsky/msdf-atlas-gen/releases).
To build the project, you may use the included [Visual Studio solution](msdf-atlas-gen.sln) or the [Unix Makefile](Makefile).
Examples of how to use it as a library are available at the [bottom of the page](#library-usage-examples).
To start using the program right away, you may download a Windows binary in the ["Releases" section](https://github.com/Chlumsky/msdf-atlas-gen/releases).
To build the project from source, you may use the included [CMake script](CMakeLists.txt).
In its default configuration, it requires [vcpkg](https://vcpkg.io/) as the provider for third-party library dependencies.
If you set the environment variable `VCPKG_ROOT` to the vcpkg directory, the CMake configuration will take care of fetching all required packages from vcpkg.
## Command line arguments
@ -42,9 +45,17 @@ Use the following command line arguments for the standalone version of the atlas
### Input
- `-font <fontfile.ttf/otf>` &ndash; sets the input font file.
- `-charset <charset.txt>` &ndash; sets the character set. The ASCII charset will be used if not specified. See [the syntax specification](#character-set-specification-syntax) of `charset.txt`.
- `-font <fontfile.ttf/otf>` (required) &ndash; sets the input font file.
- Alternatively, use `-varfont <fontfile.ttf/otf?var0=value0&var1=value1>` to configure a variable font.
- `-charset <charset.txt>` &ndash; sets the character set. See [the syntax specification](#character-set-specification-syntax) of `charset.txt`.
- `-glyphset <glyphset.txt>` &ndash; sets the set of input glyphs using their indices within the font file. See [the syntax specification](#glyph-set-specification).
- `-chars` / `-glyphs <set string>` sets the above character / glyph set in-line. See [the syntax specification](#character-set-specification-syntax).
- `-allglyphs` &ndash; sets the set of input glyphs to all glyphs present within the font file.
- `-fontscale <scale>` &ndash; applies a scaling transformation to the font's glyphs. Mainly to be used to generate multiple sizes in a single atlas, otherwise use [`-size`](#glyph-configuration).
- `-fontname <name>` &ndash; sets a name for the font that will be stored in certain output files as metadata.
- `-and` &ndash; separates multiple inputs to be combined into a single atlas.
If no character set or glyph set is provided, and `-allglyphs` is not used, the ASCII charset will be used.
### Bitmap atlas type
@ -55,7 +66,7 @@ Use the following command line arguments for the standalone version of the atlas
- `hardmask` &ndash; a non-anti-aliased binary image
- `softmask` &ndash; an anti-aliased image
- `sdf` &ndash; a true signed distance field (SDF)
- `psdf` &ndash; a pseudo-distance field
- `psdf` &ndash; a signed perpendicular distance field (PSDF)
- `msdf` (default) &ndash; a multi-channel signed distance field (MSDF)
- `mtsdf` &ndash; a combination of MSDF and true SDF in the alpha channel
@ -68,50 +79,91 @@ Use the following command line arguments for the standalone version of the atlas
- `png` &ndash; a compressed PNG image
- `bmp` &ndash; an uncompressed BMP image
- `tiff` &ndash; an uncompressed floating-point TIFF image
- `rgba` &ndash; an uncompressed [RGBA](https://github.com/bzotto/rgba_bitmap) file
- `fl32` &ndash; an uncompressed floating-point FL32 file
- `text` &ndash; a sequence of pixel values in plain text
- `textfloat` &ndash; a sequence of floating-point pixel values in plain text
- `bin` &ndash; a sequence of pixel values encoded as raw bytes of data
- `binfloat` &ndash; a sequence of pixel values encoded as raw 32-bit floating-point values
- `binfloat` &ndash; a sequence of pixel values encoded as raw 32-bit floating-point values (little endian, `binfloatbe` for big endian)
If format is not specified, it may be deduced from the extension of the `-imageout` argument or other clues.
Please note that all color values must be interpreted as if they were linear (not sRGB) like the alpha channel, even if the image format implies otherwise.
### Atlas dimensions
`-dimensions <width> <height>` &ndash; sets fixed atlas dimensions
Alternativelly, the minimum possible dimensions may be selected automatically if a dimensions constraint is set instead:
Alternatively, the minimum possible dimensions may be selected automatically if a dimensions constraint is set instead:
- `-pots` &ndash; a power-of-two square
- `-potr` &ndash; a power-of-two square or rectangle (2:1)
- `-potr` &ndash; a power-of-two square or rectangle (typically 2:1 aspect ratio)
- `-square` &ndash; any square dimensions
- `-square2` &ndash; square with even side length
- `-square4` (default) &ndash; square with side length divisible by four
### Uniform grid atlas
By default, glyphs in the atlas have different dimensions and are bin-packed in an irregular fashion to maximize use of space.
With the `-uniformgrid` switch, you can instead force all glyphs to have identical dimensions and be laid out in a grid.
In that case, these additional options are available to customize the layout:
- `-uniformcols <N>` &ndash; sets the number of columns
- `-uniformcell <width> <height>` &ndash; sets the dimensions of the grid's cells
- `-uniformcellconstraint <none / pots / potr / square / square2 / square4>` &ndash; sets constraint for cell dimensions (see explanation of options above)
- `-uniformorigin <off / on / horizontal / vertical>` &ndash; sets whether the glyph's origin point should be fixed at the same position in each cell
### Outputs
Any subset of the following may be specified:
Any non-empty subset of the following may be specified:
- `-imageout <filename.*>` &ndash; saves the atlas bitmap as a plain image file. Format matches `-format`
- `-json <filename.json>` &ndash; writes the atlas's layout data as well as other metrics into a structured JSON file
- `-csv <filename.csv>` &ndash; writes the glyph layout data into a simple CSV file
- `-json <filename.json>` &ndash; writes the atlas's layout data as well as other metrics into a structured JSON file <details><summary>JSON fields</summary>
- `atlas` section includes the settings used to generate the atlas, including its type and dimensions. The `size` field represents the font size in pixels per em.
- If there are multiple input fonts (`-and` parameter), the remaining data are grouped into `variants`, each representing an input font.
- `metrics` section contains useful font metric values retrieved from the font. All values are in em's.
- `glyphs` is an array of individual glyphs identified by Unicode character index (`unicode`) or glyph index (`index`), depending on whether character set or glyph set mode is used.
- `advance` is the horizontal advance in em's.
- `planeBounds` represents the glyph quad's bounds in em's relative to the baseline and horizontal cursor position.
- `atlasBounds` represents the glyph's bounds in the atlas in pixels.
- If available, `kerning` lists all kerning pairs and their advance adjustment (which needs to be added to the base advance of the first glyph in the pair).
</details>
- `-csv <filename.csv>` &ndash; writes the glyph layout data into a simple CSV file <details><summary>CSV columns</summary>
- If there are multiple input fonts (`-and` parameter), the first column is the font index, otherwise it is skipped.
- Character Unicode value or glyph index, depending on whether character set or glyph set mode is used.
- Horizontal advance in em's.
- The next 4 columns are the glyph quad's bounds in em's relative to the baseline and cursor. Depending on the `-yorigin` setting, this is either *left, bottom, right, top* (bottom-up Y) or *left, top, right, bottom* (top-down Y).
- The last 4 columns the the glyph's bounds in the atlas in pixels. Depending on the `-yorigin` setting, this is either *left, bottom, right, top* (bottom-up Y) or *left, top, right, bottom* (top-down Y).
</details>
- `-arfont <filename.arfont>` &ndash; saves the atlas and its layout data as an [Artery Font](https://github.com/Chlumsky/artery-font-format) file
- `-shadronpreview <filename.shadron> <sample text>` &ndash; generates a [Shadron script](https://www.arteryengine.com/shadron/) that uses the generated atlas to draw a sample text as a preview
### Glyph configuration
- `-size <EM size>` &ndash; sets the size of the glyphs in the atlas in pixels per EM
- `-minsize <EM size>` &ndash; sets the minimum size. The largest possible size that fits the same atlas dimensions will be used
- `-emrange <EM range>` &ndash; sets the distance field range in EM's
- `-size <em size>` &ndash; sets the size of the glyphs in the atlas in pixels per em
- `-minsize <em size>` &ndash; sets the minimum size. The largest possible size that fits the same atlas dimensions will be used
- `-emrange <em range>` &ndash; sets the distance field range in em's
- `-pxrange <pixel range>` (default = 2) &ndash; sets the distance field range in output pixels
- `-aemrange` / `-apxrange <outermost distance> <innermost distance>` &ndash; sets the distance field range asymmetrically by specifying the minimum and maximum representable signed distances (outside distances are negative!)
- `-pxalign <off / on / horizontal / vertical>` (default = vertical) &ndash; enables or disables alignment of glyph's origin point with the pixel grid
- `-empadding` / `-pxpadding <width>` &ndash; sets additional padding within each glyph's box (in em's / pixels)
- `-outerempadding` / `-outerpxpadding <width>` &ndash; sets additional padding around each glyph's box
- `-aempadding` / `-apxpadding` / `-aouterempadding` / `-aouterpxpadding <left> <bottom> <right> <top>` &ndash; sets additional padding (see above) asymmetrically with a separate width value for each side
### Distance field generator settings
- `-angle <angle>` &ndash; sets the minimum angle between adjacent edges to be considered a corner. Append D for degrees (`msdf` / `mtsdf` only)
- `-errorcorrection <threshold>` &ndash; sets the threshold used to detect and correct potential artifacts. 0 disables error correction (`msdf` / `mtsdf` only)
- `-coloringstrategy <simple / inktrap / distance>` &ndash; selects the edge coloring heuristic (`msdf` / `mtsdf` only)
- `-errorcorrection <mode>` &ndash; selects the error correction algorithm. Use `help` as mode for more information (`msdf` / `mtsdf` only)
- `-miterlimit <value>` &ndash; sets the miter limit that limits the extension of each glyph's bounding box due to very sharp corners (`psdf` / `msdf` / `mtsdf` only)
- `-overlap` &ndash; switches to distance field generator with support for overlapping contours
- `-nopreprocess` &ndash; disables path preprocessing which resolves self-intersections and overlapping contours
- `-scanline` &ndash; performs an additional scanline pass to fix the signs of the distances
- `-seed <N>` &ndash; sets the initial seed for the edge coloring heuristic
- `-threads <N>` &ndash; sets the number of threads for the parallel computation (0 = auto)
- `-yorigin <bottom / top>` &ndash; specifies the direction of the Y-axis in output coordinates. The default is bottom-up.
Use `-help` for an exhaustive list of options.
## Character set specification syntax
@ -134,3 +186,114 @@ It must be written on a separate line:
### Glyph set specification
The syntax of the glyph set specification is mostly the same as that of a character set, but only numeric values (decimal and hexadecimal) are allowed.
## Library usage examples
Here are commented snippets of code that demonstrate how the project can be used as a library.
### Generating whole atlas at once
```c++
#include <msdf-atlas-gen/msdf-atlas-gen.h>
using namespace msdf_atlas;
bool generateAtlas(const char *fontFilename) {
bool success = false;
// Initialize instance of FreeType library
if (msdfgen::FreetypeHandle *ft = msdfgen::initializeFreetype()) {
// Load font file
if (msdfgen::FontHandle *font = msdfgen::loadFont(ft, fontFilename)) {
// Storage for glyph geometry and their coordinates in the atlas
std::vector<GlyphGeometry> glyphs;
// FontGeometry is a helper class that loads a set of glyphs from a single font.
// It can also be used to get additional font metrics, kerning information, etc.
FontGeometry fontGeometry(&glyphs);
// Load a set of character glyphs:
// The second argument can be ignored unless you mix different font sizes in one atlas.
// In the last argument, you can specify a charset other than ASCII.
// To load specific glyph indices, use loadGlyphs instead.
fontGeometry.loadCharset(font, 1.0, Charset::ASCII);
// Apply MSDF edge coloring. See edge-coloring.h for other coloring strategies.
const double maxCornerAngle = 3.0;
for (GlyphGeometry &glyph : glyphs)
glyph.edgeColoring(&msdfgen::edgeColoringInkTrap, maxCornerAngle, 0);
// TightAtlasPacker class computes the layout of the atlas.
TightAtlasPacker packer;
// Set atlas parameters:
// setDimensions or setDimensionsConstraint to find the best value
packer.setDimensionsConstraint(TightAtlasPacker::DimensionsConstraint::SQUARE);
// setScale for a fixed size or setMinimumScale to use the largest that fits
packer.setMinimumScale(24.0);
// setPixelRange or setUnitRange
packer.setPixelRange(2.0);
packer.setMiterLimit(1.0);
// Compute atlas layout - pack glyphs
packer.pack(glyphs.data(), glyphs.size());
// Get final atlas dimensions
int width = 0, height = 0;
packer.getDimensions(width, height);
// The ImmediateAtlasGenerator class facilitates the generation of the atlas bitmap.
ImmediateAtlasGenerator<
float, // pixel type of buffer for individual glyphs depends on generator function
3, // number of atlas color channels
msdfGenerator, // function to generate bitmaps for individual glyphs
BitmapAtlasStorage<byte, 3> // class that stores the atlas bitmap
// For example, a custom atlas storage class that stores it in VRAM can be used.
> generator(width, height);
// GeneratorAttributes can be modified to change the generator's default settings.
GeneratorAttributes attributes;
generator.setAttributes(attributes);
generator.setThreadCount(4);
// Generate atlas bitmap
generator.generate(glyphs.data(), glyphs.size());
// The atlas bitmap can now be retrieved via atlasStorage as a BitmapConstRef.
// The glyphs array (or fontGeometry) contains positioning data for typesetting text.
success = my_project::submitAtlasBitmapAndLayout(generator.atlasStorage(), glyphs);
// Cleanup
msdfgen::destroyFont(font);
}
msdfgen::deinitializeFreetype(ft);
}
return success;
}
```
### Dynamic atlas
The `DynamicAtlas` class allows you to add glyphs to the atlas "on-the-fly" as they are needed. In this example, the `ImmediateAtlasGenerator` is used as the underlying atlas generator, which however isn't ideal for this purpose because it may launch new threads every time. In practice, you would typically define your own atlas generator class that properly handles your specific performance and synchronization requirements.
Acquiring the `GlyphGeometry` objects can be adapted from the previous example.
```c++
#include <msdf-atlas-gen/msdf-atlas-gen.h>
using namespace msdf_atlas;
using MyDynamicAtlas = DynamicAtlas<ImmediateAtlasGenerator<float, 3, msdfGenerator, BitmapAtlasStorage<byte, 3>>>;
const double pixelRange = 2.0;
const double glyphScale = 32.0;
const double miterLimit = 1.0;
const double maxCornerAngle = 3.0;
MyDynamicAtlas atlas;
void addGlyphsToAtlas(GlyphGeometry *glyphs, int count) {
for (int i = 0; i < count; ++i) {
// Apply MSDF edge coloring. See edge-coloring.h for other coloring strategies.
glyphs[i].edgeColoring(&msdfgen::edgeColoringInkTrap, maxCornerAngle, 0);
// Finalize glyph box size based on the parameters
glyphs[i].wrapBox(glyphScale, pixelRange/glyphScale, miterLimit);
}
// Add glyphs to atlas - invokes the underlying atlas generator
// Adding multiple glyphs at once may improve packing efficiency.
MyDynamicAtlas::ChangeFlags change = atlas.add(glyphs, count);
if (change&MyDynamicAtlas::RESIZED) {
// Atlas has been enlarged - can be handled here or directly in custom generator class
}
// Glyph positioning data is now stored in glyphs.
}
```
The atlas storage (and its bitmap) can be accessed as `dynamicAtlas.atlasGenerator().atlasStorage()`.

@ -1 +1 @@
Subproject commit 00ac3d8f964ec00a836c2bb5aeb126235ac98234
Subproject commit af79386abe0857fe1c30be97eec760dbd84022c5

154
cmake/CMakePresets.json Normal file
View File

@ -0,0 +1,154 @@
{
"version": 4,
"cmakeMinimumRequired": {
"major": 3,
"minor": 23,
"patch": 0
},
"configurePresets": [
{
"name": "release-only",
"displayName": "Release only configuration",
"hidden": true,
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}, {
"name": "debug-only",
"displayName": "Debug only configuration",
"hidden": true,
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
}, {
"name": "win64-base",
"displayName": "Windows 64-bit base configuration",
"hidden": true,
"architecture": "x64",
"binaryDir": "${sourceDir}/build/win64",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
}
}, {
"name": "win32-base",
"displayName": "Windows 32-bit base configuration",
"hidden": true,
"architecture": "Win32",
"binaryDir": "${sourceDir}/build/win32",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
}
}, {
"name": "osx-base",
"displayName": "Mac OS base configuration",
"hidden": true,
"binaryDir": "${sourceDir}/build/osx",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Darwin"
}
}, {
"name": "osx-rel-base",
"displayName": "Mac OS base release configuration",
"inherits": [ "osx-base", "release-only" ],
"hidden": true,
"binaryDir": "${sourceDir}/build/osx-rel"
}, {
"name": "osx-dbg-base",
"displayName": "Mac OS base debug configuration",
"inherits": [ "osx-base", "debug-only" ],
"hidden": true,
"binaryDir": "${sourceDir}/build/osx-dbg"
}, {
"name": "linux-base",
"displayName": "Linux base configuration",
"hidden": true,
"binaryDir": "${sourceDir}/build/linux",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Linux"
}
}, {
"name": "linux-rel-base",
"displayName": "Linux base release configuration",
"inherits": [ "linux-base", "release-only" ],
"hidden": true,
"binaryDir": "${sourceDir}/build/linux-rel"
}, {
"name": "linux-dbg-base",
"displayName": "Linux base debug configuration",
"inherits": [ "linux-base", "debug-only" ],
"hidden": true,
"binaryDir": "${sourceDir}/build/linux-dbg"
}, {
"name": "vcpkg",
"displayName": "Configuration with vcpkg as dependency management system",
"hidden": true,
"cacheVariables": {
"MSDF_ATLAS_USE_VCPKG": "ON"
}
}, {
"name": "no-vcpkg",
"displayName": "Configuration with dependencies not managed by vcpkg",
"hidden": true,
"cacheVariables": {
"MSDF_ATLAS_USE_VCPKG": "OFF"
}
}, {
"name": "skia",
"displayName": "Configuration with Skia geometry preprocessing",
"hidden": true,
"cacheVariables": {
"MSDF_ATLAS_USE_SKIA": "ON"
}
}, {
"name": "no-skia",
"displayName": "Configuration without Skia geometry preprocessing",
"hidden": true,
"cacheVariables": {
"MSDF_ATLAS_USE_SKIA": "OFF"
}
}, {
"name": "install",
"displayName": "Configuration with installation targets",
"hidden": true,
"cacheVariables": {
"MSDF_ATLAS_INSTALL": "ON"
}
}, {
"name": "static-runtime",
"displayName": "Configuration that links against the static runtime",
"hidden": true,
"cacheVariables": {
"MSDF_ATLAS_DYNAMIC_RUNTIME": "OFF"
}
}, {
"name": "dynamic-runtime",
"displayName": "Configuration that links against the dynamic runtime",
"hidden": true,
"cacheVariables": {
"MSDF_ATLAS_DYNAMIC_RUNTIME": "ON"
}
}, {
"name": "static-lib",
"displayName": "Configuration that builds and links msdfgen statically",
"hidden": true,
"cacheVariables": {
"BUILD_SHARED_LIBS": "OFF"
}
}, {
"name": "dynamic-lib",
"displayName": "Configuration that builds and links msdfgen dynamically",
"hidden": true,
"cacheVariables": {
"BUILD_SHARED_LIBS": "ON"
}
}
]
}

View File

@ -0,0 +1,21 @@
include(CMakeFindDependencyMacro)
set(MSDF_ATLAS_STANDALONE_AVAILABLE @MSDF_ATLAS_BUILD_STANDALONE@)
set(MSDF_ATLAS_NO_PNG @MSDFGEN_DISABLE_PNG@)
if(NOT MSDF_ATLAS_NO_PNG)
find_dependency(PNG REQUIRED)
endif()
find_dependency(msdfgen REQUIRED)
include("${CMAKE_CURRENT_LIST_DIR}/msdf-atlas-gen-targets.cmake")
if(MSDF_ATLAS_STANDALONE_AVAILABLE)
include("${CMAKE_CURRENT_LIST_DIR}/msdf-atlas-gen-binary-targets.cmake")
if(${CMAKE_VERSION} VERSION_LESS "3.18.0")
set_target_properties(msdf-atlas-gen-standalone::msdf-atlas-gen-standalone PROPERTIES IMPORTED_GLOBAL TRUE)
endif()
add_executable(msdf-atlas-gen::msdf-atlas-gen-run ALIAS msdf-atlas-gen-standalone::msdf-atlas-gen-standalone)
set(MSDF_ATLAS_GEN_EXECUTABLE "@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_BINDIR@/msdf-atlas-gen@CMAKE_EXECUTABLE_SUFFIX@")
endif()

19
cmake/version.cmake Normal file
View File

@ -0,0 +1,19 @@
# This script reads version from vcpkg.json and sets it to ${MSDF_ATLAS_VERSION} etc.
cmake_minimum_required(VERSION 3.15)
file(STRINGS "${CMAKE_CURRENT_LIST_DIR}/../vcpkg.json" MSDF_ATLAS_VCPKG_JSON)
string(REGEX MATCH "\"version\"[ \t\n\r]*:[ \t\n\r]*\"[^\"]*\"" MSDF_ATLAS_TMP_VERSION_PAIR ${MSDF_ATLAS_VCPKG_JSON})
string(REGEX REPLACE "\"version\"[ \t\n\r]*:[ \t\n\r]*\"([^\"]*)\"" "\\1" MSDF_ATLAS_VERSION ${MSDF_ATLAS_TMP_VERSION_PAIR})
string(REGEX REPLACE "^([0-9]*)\\.([0-9]*)\\.([0-9]*)" "\\1" MSDF_ATLAS_VERSION_MAJOR ${MSDF_ATLAS_VERSION})
string(REGEX REPLACE "^([0-9]*)\\.([0-9]*)\\.([0-9]*)" "\\2" MSDF_ATLAS_VERSION_MINOR ${MSDF_ATLAS_VERSION})
string(REGEX REPLACE "^([0-9]*)\\.([0-9]*)\\.([0-9]*)" "\\3" MSDF_ATLAS_VERSION_REVISION ${MSDF_ATLAS_VERSION})
string(LENGTH ${MSDF_ATLAS_VERSION} MSDF_ATLAS_VERSION_LENGTH)
string(REPEAT "-" ${MSDF_ATLAS_VERSION_LENGTH} MSDF_ATLAS_VERSION_UNDERLINE)
string(TIMESTAMP MSDF_ATLAS_COPYRIGHT_YEAR "%Y")
unset(MSDF_ATLAS_TMP_VERSION_PAIR)
unset(MSDF_ATLAS_VERSION_LENGTH)
unset(MSDF_ATLAS_VCPKG_JSON)

Binary file not shown.

Binary file not shown.

View File

@ -1,61 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Msdfgen", "msdfgen\Msdfgen.vcxproj", "{84BE2D91-F071-4151-BE12-61460464C494}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "msdf-atlas-gen", "msdf-atlas-gen.vcxproj", "{223EDB94-5B35-45F2-A584-273DE6E45F6F}"
ProjectSection(ProjectDependencies) = postProject
{84BE2D91-F071-4151-BE12-61460464C494} = {84BE2D91-F071-4151-BE12-61460464C494}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug Library|x64 = Debug Library|x64
Debug Library|x86 = Debug Library|x86
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release Library|x64 = Release Library|x64
Release Library|x86 = Release Library|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{84BE2D91-F071-4151-BE12-61460464C494}.Debug Library|x64.ActiveCfg = Debug Library|x64
{84BE2D91-F071-4151-BE12-61460464C494}.Debug Library|x64.Build.0 = Debug Library|x64
{84BE2D91-F071-4151-BE12-61460464C494}.Debug Library|x86.ActiveCfg = Debug Library|Win32
{84BE2D91-F071-4151-BE12-61460464C494}.Debug Library|x86.Build.0 = Debug Library|Win32
{84BE2D91-F071-4151-BE12-61460464C494}.Debug|x64.ActiveCfg = Debug Library|x64
{84BE2D91-F071-4151-BE12-61460464C494}.Debug|x64.Build.0 = Debug Library|x64
{84BE2D91-F071-4151-BE12-61460464C494}.Debug|x86.ActiveCfg = Debug Library|Win32
{84BE2D91-F071-4151-BE12-61460464C494}.Debug|x86.Build.0 = Debug Library|Win32
{84BE2D91-F071-4151-BE12-61460464C494}.Release Library|x64.ActiveCfg = Release Library|x64
{84BE2D91-F071-4151-BE12-61460464C494}.Release Library|x64.Build.0 = Release Library|x64
{84BE2D91-F071-4151-BE12-61460464C494}.Release Library|x86.ActiveCfg = Release Library|Win32
{84BE2D91-F071-4151-BE12-61460464C494}.Release Library|x86.Build.0 = Release Library|Win32
{84BE2D91-F071-4151-BE12-61460464C494}.Release|x64.ActiveCfg = Release Library|x64
{84BE2D91-F071-4151-BE12-61460464C494}.Release|x64.Build.0 = Release Library|x64
{84BE2D91-F071-4151-BE12-61460464C494}.Release|x86.ActiveCfg = Release Library|Win32
{84BE2D91-F071-4151-BE12-61460464C494}.Release|x86.Build.0 = Release Library|Win32
{223EDB94-5B35-45F2-A584-273DE6E45F6F}.Debug Library|x64.ActiveCfg = Debug Library|x64
{223EDB94-5B35-45F2-A584-273DE6E45F6F}.Debug Library|x64.Build.0 = Debug Library|x64
{223EDB94-5B35-45F2-A584-273DE6E45F6F}.Debug Library|x86.ActiveCfg = Debug Library|Win32
{223EDB94-5B35-45F2-A584-273DE6E45F6F}.Debug Library|x86.Build.0 = Debug Library|Win32
{223EDB94-5B35-45F2-A584-273DE6E45F6F}.Debug|x64.ActiveCfg = Debug|x64
{223EDB94-5B35-45F2-A584-273DE6E45F6F}.Debug|x64.Build.0 = Debug|x64
{223EDB94-5B35-45F2-A584-273DE6E45F6F}.Debug|x86.ActiveCfg = Debug|Win32
{223EDB94-5B35-45F2-A584-273DE6E45F6F}.Debug|x86.Build.0 = Debug|Win32
{223EDB94-5B35-45F2-A584-273DE6E45F6F}.Release Library|x64.ActiveCfg = Release Library|x64
{223EDB94-5B35-45F2-A584-273DE6E45F6F}.Release Library|x64.Build.0 = Release Library|x64
{223EDB94-5B35-45F2-A584-273DE6E45F6F}.Release Library|x86.ActiveCfg = Release Library|Win32
{223EDB94-5B35-45F2-A584-273DE6E45F6F}.Release Library|x86.Build.0 = Release Library|Win32
{223EDB94-5B35-45F2-A584-273DE6E45F6F}.Release|x64.ActiveCfg = Release|x64
{223EDB94-5B35-45F2-A584-273DE6E45F6F}.Release|x64.Build.0 = Release|x64
{223EDB94-5B35-45F2-A584-273DE6E45F6F}.Release|x86.ActiveCfg = Release|Win32
{223EDB94-5B35-45F2-A584-273DE6E45F6F}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@ -1,372 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug Library|Win32">
<Configuration>Debug Library</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug Library|x64">
<Configuration>Debug Library</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release Library|Win32">
<Configuration>Release Library</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release Library|x64">
<Configuration>Release Library</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{223EDB94-5B35-45F2-A584-273DE6E45F6F}</ProjectGuid>
<RootNamespace>msdfatlasgen</RootNamespace>
<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug Library|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Library|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug Library|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Library|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug Library|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release Library|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug Library|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release Library|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<TargetName>msdf-atlas-gen</TargetName>
<OutDir>$(Configuration)\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug Library|Win32'">
<TargetName>msdf-atlas-gen</TargetName>
<OutDir>$(Configuration)\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<TargetName>msdf-atlas-gen</TargetName>
<OutDir>bin\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Library|Win32'">
<TargetName>msdf-atlas-gen</TargetName>
<OutDir>bin\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<TargetName>msdf-atlas-gen</TargetName>
<OutDir>$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug Library|x64'">
<TargetName>msdf-atlas-gen</TargetName>
<OutDir>$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<TargetName>msdf-atlas-gen</TargetName>
<OutDir>$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Library|x64'">
<TargetName>msdf-atlas-gen</TargetName>
<OutDir>$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDFGEN_USE_SKIA;MSDF_ATLAS_STANDALONE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<AdditionalDependencies>freetype.lib;skia.lib;msdfgen.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>msdfgen\freetype\win$(PlatformArchitecture);msdfgen\skia\win$(PlatformArchitecture)\$(Configuration);msdfgen\$(Configuration) Library;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug Library|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDFGEN_USE_SKIA;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<AdditionalDependencies>freetype.lib;msdfgen.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\msdfgen\freetype\win32;$(SolutionDir)$(Configuration) Library;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<Lib>
<TargetMachine>MachineX86</TargetMachine>
<AdditionalLibraryDirectories>msdfgen\freetype\win$(PlatformArchitecture);msdfgen\skia\win$(PlatformArchitecture)\debug;msdfgen\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDFGEN_USE_SKIA;MSDF_ATLAS_STANDALONE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<AdditionalDependencies>freetype.lib;skia.lib;msdfgen.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>msdfgen\freetype\win$(PlatformArchitecture);msdfgen\skia\win$(PlatformArchitecture)\$(Configuration);msdfgen\$(Platform)\$(Configuration) Library;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug Library|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDFGEN_USE_SKIA;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<AdditionalDependencies>freetype.lib;msdfgen.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\msdfgen\freetype\win64;$(SolutionDir)$(Platform)\$(Configuration) Library;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<Lib>
<AdditionalLibraryDirectories>msdfgen\freetype\win$(PlatformArchitecture);msdfgen\skia\win$(PlatformArchitecture)\debug;msdfgen\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDFGEN_USE_SKIA;MSDF_ATLAS_STANDALONE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<DebugInformationFormat>None</DebugInformationFormat>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<SubSystem>Console</SubSystem>
<AdditionalDependencies>freetype.lib;skia.lib;msdfgen.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>msdfgen\freetype\win$(PlatformArchitecture);msdfgen\skia\win$(PlatformArchitecture)\$(Configuration);msdfgen\bin;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<GenerateDebugInformation>false</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release Library|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDFGEN_USE_SKIA;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<SubSystem>Console</SubSystem>
<AdditionalDependencies>freetype.lib;msdfgen.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\msdfgen\freetype\win32;$(SolutionDir)bin;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<Lib>
<TargetMachine>MachineX86</TargetMachine>
<AdditionalLibraryDirectories>msdfgen\freetype\win$(PlatformArchitecture);msdfgen\skia\win$(PlatformArchitecture)\release;msdfgen\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDFGEN_USE_SKIA;MSDF_ATLAS_STANDALONE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<DebugInformationFormat>None</DebugInformationFormat>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<SubSystem>Console</SubSystem>
<AdditionalDependencies>freetype.lib;skia.lib;msdfgen.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>msdfgen\freetype\win$(PlatformArchitecture);msdfgen\skia\win$(PlatformArchitecture)\$(Configuration);msdfgen\$(Platform)\$(Configuration) Library;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<GenerateDebugInformation>false</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release Library|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>msdfgen\include;msdfgen\freetype\include;msdfgen;artery-font-format;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;MSDFGEN_USE_CPP11;MSDFGEN_USE_SKIA;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<SubSystem>Console</SubSystem>
<AdditionalDependencies>freetype.lib;msdfgen.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\msdfgen\freetype\win64;$(SolutionDir)$(Platform)\$(Configuration) Library;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<Lib>
<AdditionalLibraryDirectories>msdfgen\freetype\win$(PlatformArchitecture);msdfgen\skia\win$(PlatformArchitecture)\release;msdfgen\$(Platform)\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Lib>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="msdf-atlas-gen\artery-font-export.cpp" />
<ClCompile Include="msdf-atlas-gen\bitmap-blit.cpp" />
<ClCompile Include="msdf-atlas-gen\charset-parser.cpp" />
<ClCompile Include="msdf-atlas-gen\Charset.cpp" />
<ClCompile Include="msdf-atlas-gen\csv-export.cpp" />
<ClCompile Include="msdf-atlas-gen\glyph-generators.cpp" />
<ClCompile Include="msdf-atlas-gen\GlyphGeometry.cpp" />
<ClCompile Include="msdf-atlas-gen\image-encode.cpp" />
<ClCompile Include="msdf-atlas-gen\json-export.cpp" />
<ClCompile Include="msdf-atlas-gen\main.cpp" />
<ClCompile Include="msdf-atlas-gen\RectanglePacker.cpp" />
<ClCompile Include="msdf-atlas-gen\shadron-preview-generator.cpp" />
<ClCompile Include="msdf-atlas-gen\size-selectors.cpp" />
<ClCompile Include="msdf-atlas-gen\TightAtlasPacker.cpp" />
<ClCompile Include="msdf-atlas-gen\utf8.cpp" />
<ClCompile Include="msdf-atlas-gen\Workload.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="resource.h" />
<ClInclude Include="msdf-atlas-gen\artery-font-export.h" />
<ClInclude Include="msdf-atlas-gen\AtlasGenerator.h" />
<ClInclude Include="msdf-atlas-gen\AtlasStorage.h" />
<ClInclude Include="msdf-atlas-gen\bitmap-blit.h" />
<ClInclude Include="msdf-atlas-gen\BitmapAtlasStorage.h" />
<ClInclude Include="msdf-atlas-gen\BitmapAtlasStorage.hpp" />
<ClInclude Include="msdf-atlas-gen\csv-export.h" />
<ClInclude Include="msdf-atlas-gen\DynamicAtlas.h" />
<ClInclude Include="msdf-atlas-gen\DynamicAtlas.hpp" />
<ClInclude Include="msdf-atlas-gen\glyph-generators.h" />
<ClInclude Include="msdf-atlas-gen\image-encode.h" />
<ClInclude Include="msdf-atlas-gen\Charset.h" />
<ClInclude Include="msdf-atlas-gen\GlyphGeometry.h" />
<ClInclude Include="msdf-atlas-gen\image-save.h" />
<ClInclude Include="msdf-atlas-gen\image-save.hpp" />
<ClInclude Include="msdf-atlas-gen\ImmediateAtlasGenerator.h" />
<ClInclude Include="msdf-atlas-gen\ImmediateAtlasGenerator.hpp" />
<ClInclude Include="msdf-atlas-gen\json-export.h" />
<ClInclude Include="msdf-atlas-gen\msdf-atlas-gen.h" />
<ClInclude Include="msdf-atlas-gen\rectangle-packing.h" />
<ClInclude Include="msdf-atlas-gen\rectangle-packing.hpp" />
<ClInclude Include="msdf-atlas-gen\Rectangle.h" />
<ClInclude Include="msdf-atlas-gen\RectanglePacker.h" />
<ClInclude Include="msdf-atlas-gen\Remap.h" />
<ClInclude Include="msdf-atlas-gen\shadron-preview-generator.h" />
<ClInclude Include="msdf-atlas-gen\size-selectors.h" />
<ClInclude Include="msdf-atlas-gen\GlyphBox.h" />
<ClInclude Include="msdf-atlas-gen\types.h" />
<ClInclude Include="msdf-atlas-gen\utf8.h" />
<ClInclude Include="msdf-atlas-gen\Workload.h" />
<ClInclude Include="msdf-atlas-gen\TightAtlasPacker.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="msdf-atlas-gen.rc" />
</ItemGroup>
<ItemGroup>
<Image Include="icon.ico" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -1,178 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Template Source Files">
<UniqueIdentifier>{ee785f45-c1cf-48ae-b864-f27237b077c1}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="msdf-atlas-gen\artery-font-export.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="msdf-atlas-gen\bitmap-blit.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="msdf-atlas-gen\csv-export.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="msdf-atlas-gen\glyph-generators.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="msdf-atlas-gen\GlyphGeometry.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="msdf-atlas-gen\Charset.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="msdf-atlas-gen\charset-parser.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="msdf-atlas-gen\image-encode.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="msdf-atlas-gen\json-export.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="msdf-atlas-gen\main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="msdf-atlas-gen\RectanglePacker.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="msdf-atlas-gen\shadron-preview-generator.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="msdf-atlas-gen\size-selectors.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="msdf-atlas-gen\TightAtlasPacker.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="msdf-atlas-gen\utf8.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="msdf-atlas-gen\Workload.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\artery-font-export.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\AtlasGenerator.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\AtlasStorage.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\BitmapAtlasStorage.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\BitmapAtlasStorage.hpp">
<Filter>Template Source Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\bitmap-blit.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\csv-export.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\DynamicAtlas.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\DynamicAtlas.hpp">
<Filter>Template Source Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\GlyphBox.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\glyph-generators.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\GlyphGeometry.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\Charset.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\image-encode.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\image-save.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\image-save.hpp">
<Filter>Template Source Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\ImmediateAtlasGenerator.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\ImmediateAtlasGenerator.hpp">
<Filter>Template Source Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\json-export.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\msdf-atlas-gen.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\Rectangle.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\RectanglePacker.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\rectangle-packing.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\rectangle-packing.hpp">
<Filter>Template Source Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\Remap.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\shadron-preview-generator.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\size-selectors.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\TightAtlasPacker.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\types.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\utf8.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="msdf-atlas-gen\Workload.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="msdf-atlas-gen.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<Image Include="icon.ico">
<Filter>Resource Files</Filter>
</Image>
</ItemGroup>
</Project>

View File

@ -31,9 +31,8 @@ public:
/// Configuration of signed distance field generator
struct GeneratorAttributes {
bool overlapSupport = false;
msdfgen::MSDFGeneratorConfig config;
bool scanlinePass = false;
double errorCorrectionThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD;
};
/// A function that generates the bitmap for a single glyph

View File

@ -48,7 +48,7 @@ BitmapAtlasStorage<T, N>::operator msdfgen::BitmapRef<T, N>() {
template <typename T, int N>
BitmapAtlasStorage<T, N>::operator msdfgen::Bitmap<T, N>() && {
return (msdfgen::Bitmap<T, N>() &&) bitmap;
return (msdfgen::Bitmap<T, N> &&) bitmap;
}
template <typename T, int N>

View File

@ -5,6 +5,10 @@
#include <set>
#include "types.h"
#ifndef MSDF_ATLAS_PUBLIC
#define MSDF_ATLAS_PUBLIC
#endif
namespace msdf_atlas {
/// Represents a set of Unicode codepoints (characters)
@ -12,7 +16,7 @@ class Charset {
public:
/// The set of the 95 printable ASCII characters
static const Charset ASCII;
static MSDF_ATLAS_PUBLIC const Charset ASCII;
/// Adds a codepoint
void add(unicode_t cp);
@ -24,8 +28,10 @@ public:
std::set<unicode_t>::const_iterator begin() const;
std::set<unicode_t>::const_iterator end() const;
/// Load character set from a text file with the correct syntax
/// Load character set from a text file with compliant syntax
bool load(const char *filename, bool disableCharLiterals = false);
/// Parse character set from a string with compliant syntax
bool parse(const char *str, size_t strLength, bool disableCharLiterals = false);
private:
std::set<unicode_t> codepoints;

View File

@ -16,25 +16,34 @@ template <class AtlasGenerator>
class DynamicAtlas {
public:
enum ChangeFlag {
NO_CHANGE = 0x00,
RESIZED = 0x01,
REARRANGED = 0x02
};
typedef int ChangeFlags;
DynamicAtlas();
/// Initializes generator with dimensions and custom arguments for generator
template <typename... ARGS>
explicit DynamicAtlas(int minSide, ARGS... args);
/// Creates with a configured generator. The generator must not contain any prior glyphs!
explicit DynamicAtlas(AtlasGenerator &&generator);
/// Adds a batch of glyphs. Adding more than one glyph at a time may improve packing efficiency
void add(GlyphGeometry *glyphs, int count);
ChangeFlags add(GlyphGeometry *glyphs, int count, bool allowRearrange = false);
/// Allows access to generator. Do not add glyphs to the generator directly!
AtlasGenerator & atlasGenerator();
const AtlasGenerator & atlasGenerator() const;
AtlasGenerator &atlasGenerator();
const AtlasGenerator &atlasGenerator() const;
private:
AtlasGenerator generator;
RectanglePacker packer;
int glyphCount;
int side;
int spacing;
int glyphCount;
int totalArea;
RectanglePacker packer;
AtlasGenerator generator;
std::vector<Rectangle> rectangles;
std::vector<Remap> remapBuffer;
int totalArea;
GeneratorAttributes genAttribs;
int padding;
};

View File

@ -1,50 +1,64 @@
#include "DynamicAtlas.h"
#include "utils.hpp"
namespace msdf_atlas {
template <class AtlasGenerator>
DynamicAtlas<AtlasGenerator>::DynamicAtlas() : glyphCount(0), side(0), totalArea(0), padding(0) { }
DynamicAtlas<AtlasGenerator>::DynamicAtlas() : side(0), spacing(0), glyphCount(0), totalArea(0) { }
template <class AtlasGenerator>
DynamicAtlas<AtlasGenerator>::DynamicAtlas(AtlasGenerator &&generator) : generator((AtlasGenerator &&) generator), glyphCount(0), side(0), totalArea(0), padding(0) { }
template <typename... ARGS>
DynamicAtlas<AtlasGenerator>::DynamicAtlas(int minSide, ARGS... args) : side(minSide > 0 ? ceilToPOT(minSide) : 0), spacing(0), glyphCount(0), totalArea(0), packer(side+spacing, side+spacing), generator(side, side, args...) { }
template <class AtlasGenerator>
void DynamicAtlas<AtlasGenerator>::add(GlyphGeometry *glyphs, int count) {
DynamicAtlas<AtlasGenerator>::DynamicAtlas(AtlasGenerator &&generator) : side(0), spacing(0), glyphCount(0), totalArea(0), generator((AtlasGenerator &&) generator) { }
template <class AtlasGenerator>
typename DynamicAtlas<AtlasGenerator>::ChangeFlags DynamicAtlas<AtlasGenerator>::add(GlyphGeometry *glyphs, int count, bool allowRearrange) {
ChangeFlags changeFlags = 0;
int start = rectangles.size();
for (int i = 0; i < count; ++i) {
if (!glyphs[i].isWhitespace()) {
int w, h;
glyphs[i].getBoxSize(w, h);
Rectangle rect = { 0, 0, w+padding, h+padding };
Rectangle rect = { 0, 0, w+spacing, h+spacing };
rectangles.push_back(rect);
Remap remapEntry = { };
remapEntry.index = glyphCount+i;
remapEntry.width = w;
remapEntry.height = h;
remapBuffer.push_back(remapEntry);
totalArea += (w+padding)*(h+padding);
totalArea += (w+spacing)*(h+spacing);
}
}
if ((int) rectangles.size() > start) {
int oldSide = side;
int packerStart = start;
while (packer.pack(rectangles.data()+packerStart, rectangles.size()-packerStart) > 0) {
side = side+!side<<1;
int remaining;
while ((remaining = packer.pack(rectangles.data()+packerStart, rectangles.size()-packerStart)) > 0) {
side = (side|!side)<<1;
while (side*side < totalArea)
side <<= 1;
packer = RectanglePacker(side+padding, side+padding);
packerStart = 0;
if (allowRearrange) {
packer = RectanglePacker(side+spacing, side+spacing);
packerStart = 0;
} else {
packer.expand(side+spacing, side+spacing);
packerStart = rectangles.size()-remaining;
}
changeFlags |= RESIZED;
}
if (packerStart < start) {
for (int i = 0; i < start; ++i) {
for (int i = packerStart; i < start; ++i) {
Remap &remap = remapBuffer[i];
remap.source = remap.target;
remap.target.x = rectangles[i].x;
remap.target.y = rectangles[i].y;
}
generator.rearrange(side, side, remapBuffer.data(), start);
} else if (side != oldSide)
changeFlags |= REARRANGED;
} else if (changeFlags&RESIZED)
generator.resize(side, side);
for (int i = start; i < (int) rectangles.size(); ++i) {
remapBuffer[i].target.x = rectangles[i].x;
@ -52,17 +66,18 @@ void DynamicAtlas<AtlasGenerator>::add(GlyphGeometry *glyphs, int count) {
glyphs[remapBuffer[i].index-glyphCount].placeBox(rectangles[i].x, rectangles[i].y);
}
}
generator.generate(glyphs, count, genAttribs);
generator.generate(glyphs, count);
glyphCount += count;
return changeFlags;
}
template <class AtlasGenerator>
AtlasGenerator & DynamicAtlas<AtlasGenerator>::atlasGenerator() {
AtlasGenerator &DynamicAtlas<AtlasGenerator>::atlasGenerator() {
return generator;
}
template <class AtlasGenerator>
const AtlasGenerator & DynamicAtlas<AtlasGenerator>::atlasGenerator() const {
const AtlasGenerator &DynamicAtlas<AtlasGenerator>::atlasGenerator() const {
return generator;
}

View File

@ -0,0 +1,230 @@
#include "FontGeometry.h"
#define DEFAULT_FONT_UNITS_PER_EM 2048.0
namespace msdf_atlas {
FontGeometry::GlyphRange::GlyphRange() : glyphs(), rangeStart(), rangeEnd() { }
FontGeometry::GlyphRange::GlyphRange(const std::vector<GlyphGeometry> *glyphs, size_t rangeStart, size_t rangeEnd) : glyphs(glyphs), rangeStart(rangeStart), rangeEnd(rangeEnd) { }
size_t FontGeometry::GlyphRange::size() const {
return glyphs->size();
}
bool FontGeometry::GlyphRange::empty() const {
return glyphs->empty();
}
const GlyphGeometry *FontGeometry::GlyphRange::begin() const {
return glyphs->data()+rangeStart;
}
const GlyphGeometry *FontGeometry::GlyphRange::end() const {
return glyphs->data()+rangeEnd;
}
FontGeometry::FontGeometry() : geometryScale(1), metrics(), preferredIdentifierType(GlyphIdentifierType::UNICODE_CODEPOINT), glyphs(&ownGlyphs), rangeStart(0), rangeEnd(0) { }
FontGeometry::FontGeometry(std::vector<GlyphGeometry> *glyphStorage) : geometryScale(1), metrics(), preferredIdentifierType(GlyphIdentifierType::UNICODE_CODEPOINT) {
glyphs = glyphStorage ? glyphStorage : &ownGlyphs;
rangeStart = glyphs->size();
rangeEnd = glyphs->size();
}
FontGeometry::FontGeometry(FontGeometry &&orig) : geometryScale(orig.geometryScale), metrics(orig.metrics), preferredIdentifierType(orig.preferredIdentifierType), glyphs(orig.glyphs), rangeStart(orig.rangeStart), rangeEnd(orig.rangeEnd), glyphsByIndex((std::map<int, size_t> &&) orig.glyphsByIndex), glyphsByCodepoint((std::map<unicode_t, size_t> &&) orig.glyphsByCodepoint), kerning((std::map<std::pair<int, int>, double> &&) orig.kerning), ownGlyphs((std::vector<GlyphGeometry> &&) orig.ownGlyphs), name((std::string &&) orig.name) {
if (glyphs == &orig.ownGlyphs)
glyphs = &ownGlyphs;
}
FontGeometry &FontGeometry::operator=(FontGeometry &&orig) {
if (this != &orig) {
geometryScale = orig.geometryScale;
metrics = orig.metrics;
glyphs = orig.glyphs == &orig.ownGlyphs ? &ownGlyphs : orig.glyphs;
rangeStart = orig.rangeStart;
rangeEnd = orig.rangeEnd;
glyphsByIndex = (std::map<int, size_t> &&) orig.glyphsByIndex;
glyphsByCodepoint = (std::map<unicode_t, size_t> &&) orig.glyphsByCodepoint;
kerning = (std::map<std::pair<int, int>, double> &&) orig.kerning;
ownGlyphs = (std::vector<GlyphGeometry> &&) orig.ownGlyphs;
name = (std::string &&) orig.name;
}
return *this;
}
int FontGeometry::loadGlyphRange(msdfgen::FontHandle *font, double fontScale, unsigned rangeStart, unsigned rangeEnd, bool preprocessGeometry, bool enableKerning) {
if (!(glyphs->size() == this->rangeEnd && loadMetrics(font, fontScale)))
return -1;
glyphs->reserve(glyphs->size()+(rangeEnd-rangeStart));
int loaded = 0;
for (unsigned index = rangeStart; index < rangeEnd; ++index) {
GlyphGeometry glyph;
if (glyph.load(font, geometryScale, msdfgen::GlyphIndex(index), preprocessGeometry)) {
addGlyph((GlyphGeometry &&) glyph);
++loaded;
}
}
if (enableKerning)
loadKerning(font);
preferredIdentifierType = GlyphIdentifierType::GLYPH_INDEX;
return loaded;
}
int FontGeometry::loadGlyphset(msdfgen::FontHandle *font, double fontScale, const Charset &glyphset, bool preprocessGeometry, bool enableKerning) {
if (!(glyphs->size() == rangeEnd && loadMetrics(font, fontScale)))
return -1;
glyphs->reserve(glyphs->size()+glyphset.size());
int loaded = 0;
for (unicode_t index : glyphset) {
GlyphGeometry glyph;
if (glyph.load(font, geometryScale, msdfgen::GlyphIndex(index), preprocessGeometry)) {
addGlyph((GlyphGeometry &&) glyph);
++loaded;
}
}
if (enableKerning)
loadKerning(font);
preferredIdentifierType = GlyphIdentifierType::GLYPH_INDEX;
return loaded;
}
int FontGeometry::loadCharset(msdfgen::FontHandle *font, double fontScale, const Charset &charset, bool preprocessGeometry, bool enableKerning) {
if (!(glyphs->size() == rangeEnd && loadMetrics(font, fontScale)))
return -1;
glyphs->reserve(glyphs->size()+charset.size());
int loaded = 0;
for (unicode_t cp : charset) {
GlyphGeometry glyph;
if (glyph.load(font, geometryScale, cp, preprocessGeometry)) {
addGlyph((GlyphGeometry &&) glyph);
++loaded;
}
}
if (enableKerning)
loadKerning(font);
preferredIdentifierType = GlyphIdentifierType::UNICODE_CODEPOINT;
return loaded;
}
bool FontGeometry::loadMetrics(msdfgen::FontHandle *font, double fontScale) {
if (!msdfgen::getFontMetrics(metrics, font, msdfgen::FONT_SCALING_NONE))
return false;
if (metrics.emSize <= 0)
metrics.emSize = DEFAULT_FONT_UNITS_PER_EM;
geometryScale = fontScale/metrics.emSize;
metrics.emSize *= geometryScale;
metrics.ascenderY *= geometryScale;
metrics.descenderY *= geometryScale;
metrics.lineHeight *= geometryScale;
metrics.underlineY *= geometryScale;
metrics.underlineThickness *= geometryScale;
return true;
}
bool FontGeometry::addGlyph(const GlyphGeometry &glyph) {
if (glyphs->size() != rangeEnd)
return false;
glyphsByIndex.insert(std::make_pair(glyph.getIndex(), rangeEnd));
if (glyph.getCodepoint())
glyphsByCodepoint.insert(std::make_pair(glyph.getCodepoint(), rangeEnd));
glyphs->push_back(glyph);
++rangeEnd;
return true;
}
bool FontGeometry::addGlyph(GlyphGeometry &&glyph) {
if (glyphs->size() != rangeEnd)
return false;
glyphsByIndex.insert(std::make_pair(glyph.getIndex(), rangeEnd));
if (glyph.getCodepoint())
glyphsByCodepoint.insert(std::make_pair(glyph.getCodepoint(), rangeEnd));
glyphs->push_back((GlyphGeometry &&) glyph);
++rangeEnd;
return true;
}
int FontGeometry::loadKerning(msdfgen::FontHandle *font) {
int loaded = 0;
for (size_t i = rangeStart; i < rangeEnd; ++i)
for (size_t j = rangeStart; j < rangeEnd; ++j) {
double advance;
if (msdfgen::getKerning(advance, font, (*glyphs)[i].getGlyphIndex(), (*glyphs)[j].getGlyphIndex(), msdfgen::FONT_SCALING_NONE) && advance) {
kerning[std::make_pair<int, int>((*glyphs)[i].getIndex(), (*glyphs)[j].getIndex())] = geometryScale*advance;
++loaded;
}
}
return loaded;
}
void FontGeometry::setName(const char *name) {
if (name)
this->name = name;
else
this->name.clear();
}
double FontGeometry::getGeometryScale() const {
return geometryScale;
}
const msdfgen::FontMetrics &FontGeometry::getMetrics() const {
return metrics;
}
GlyphIdentifierType FontGeometry::getPreferredIdentifierType() const {
return preferredIdentifierType;
}
FontGeometry::GlyphRange FontGeometry::getGlyphs() const {
return GlyphRange(glyphs, rangeStart, rangeEnd);
}
const GlyphGeometry *FontGeometry::getGlyph(msdfgen::GlyphIndex index) const {
std::map<int, size_t>::const_iterator it = glyphsByIndex.find(index.getIndex());
if (it != glyphsByIndex.end())
return &(*glyphs)[it->second];
return nullptr;
}
const GlyphGeometry *FontGeometry::getGlyph(unicode_t codepoint) const {
std::map<unicode_t, size_t>::const_iterator it = glyphsByCodepoint.find(codepoint);
if (it != glyphsByCodepoint.end())
return &(*glyphs)[it->second];
return nullptr;
}
bool FontGeometry::getAdvance(double &advance, msdfgen::GlyphIndex index1, msdfgen::GlyphIndex index2) const {
const GlyphGeometry *glyph1 = getGlyph(index1);
if (!glyph1)
return false;
advance = glyph1->getAdvance();
std::map<std::pair<int, int>, double>::const_iterator it = kerning.find(std::make_pair<int, int>(index1.getIndex(), index2.getIndex()));
if (it != kerning.end())
advance += it->second;
return true;
}
bool FontGeometry::getAdvance(double &advance, unicode_t codepoint1, unicode_t codepoint2) const {
const GlyphGeometry *glyph1, *glyph2;
if (!((glyph1 = getGlyph(codepoint1)) && (glyph2 = getGlyph(codepoint2))))
return false;
advance = glyph1->getAdvance();
std::map<std::pair<int, int>, double>::const_iterator it = kerning.find(std::make_pair<int, int>(glyph1->getIndex(), glyph2->getIndex()));
if (it != kerning.end())
advance += it->second;
return true;
}
const std::map<std::pair<int, int>, double> &FontGeometry::getKerning() const {
return kerning;
}
const char *FontGeometry::getName() const {
if (name.empty())
return nullptr;
return name.c_str();
}
}

View File

@ -0,0 +1,91 @@
#pragma once
#include <utility>
#include <vector>
#include <string>
#include <map>
#include <msdfgen.h>
#include <msdfgen-ext.h>
#include "types.h"
#include "GlyphGeometry.h"
#include "Charset.h"
namespace msdf_atlas {
/// Represents the geometry of all glyphs of a given font or font variant
class FontGeometry {
public:
class GlyphRange {
public:
GlyphRange();
GlyphRange(const std::vector<GlyphGeometry> *glyphs, size_t rangeStart, size_t rangeEnd);
size_t size() const;
bool empty() const;
const GlyphGeometry *begin() const;
const GlyphGeometry *end() const;
private:
const std::vector<GlyphGeometry> *glyphs;
size_t rangeStart, rangeEnd;
};
FontGeometry();
explicit FontGeometry(std::vector<GlyphGeometry> *glyphStorage);
FontGeometry(FontGeometry &&orig);
FontGeometry &operator=(FontGeometry &&orig);
/// Loads the consecutive range of glyphs between rangeStart (inclusive) and rangeEnd (exclusive), returns the number of successfully loaded glyphs
int loadGlyphRange(msdfgen::FontHandle *font, double fontScale, unsigned rangeStart, unsigned rangeEnd, bool preprocessGeometry = true, bool enableKerning = true);
/// Loads all glyphs in a glyphset (Charset elements are glyph indices), returns the number of successfully loaded glyphs
int loadGlyphset(msdfgen::FontHandle *font, double fontScale, const Charset &glyphset, bool preprocessGeometry = true, bool enableKerning = true);
/// Loads all glyphs in a charset (Charset elements are Unicode codepoints), returns the number of successfully loaded glyphs
int loadCharset(msdfgen::FontHandle *font, double fontScale, const Charset &charset, bool preprocessGeometry = true, bool enableKerning = true);
/// Only loads font metrics and geometry scale from font
bool loadMetrics(msdfgen::FontHandle *font, double fontScale);
/// Adds a loaded glyph
bool addGlyph(const GlyphGeometry &glyph);
bool addGlyph(GlyphGeometry &&glyph);
/// Loads kerning pairs for all glyphs that are currently present, returns the number of loaded kerning pairs
int loadKerning(msdfgen::FontHandle *font);
/// Sets a name to be associated with the font
void setName(const char *name);
/// Returns the geometry scale to be used when loading glyphs
double getGeometryScale() const;
/// Returns the processed font metrics
const msdfgen::FontMetrics &getMetrics() const;
/// Returns the type of identifier that was used to load glyphs
GlyphIdentifierType getPreferredIdentifierType() const;
/// Returns the list of all glyphs
GlyphRange getGlyphs() const;
/// Finds a glyph by glyph index or Unicode codepoint, returns null if not found
const GlyphGeometry *getGlyph(msdfgen::GlyphIndex index) const;
const GlyphGeometry *getGlyph(unicode_t codepoint) const;
/// Outputs the advance between two glyphs with kerning taken into consideration, returns false on failure
bool getAdvance(double &advance, msdfgen::GlyphIndex index1, msdfgen::GlyphIndex index2) const;
bool getAdvance(double &advance, unicode_t codepoint1, unicode_t codepoint2) const;
/// Returns the complete mapping of kerning pairs (by glyph indices) and their respective advance values
const std::map<std::pair<int, int>, double> &getKerning() const;
/// Returns the name associated with the font or null if not set
const char *getName() const;
private:
double geometryScale;
msdfgen::FontMetrics metrics;
GlyphIdentifierType preferredIdentifierType;
std::vector<GlyphGeometry> *glyphs;
size_t rangeStart, rangeEnd;
std::map<int, size_t> glyphsByIndex;
std::map<unicode_t, size_t> glyphsByCodepoint;
std::map<std::pair<int, int>, double> kerning;
std::vector<GlyphGeometry> ownGlyphs;
std::string name;
FontGeometry(const FontGeometry &);
FontGeometry &operator=(const FontGeometry &);
};
}

View File

@ -1,6 +1,8 @@
#pragma once
#include "Rectangle.h"
namespace msdf_atlas {
/// The glyph box - its bounds in plane and atlas
@ -10,9 +12,7 @@ struct GlyphBox {
struct {
double l, b, r, t;
} bounds;
struct {
int x, y, w, h;
} rect;
Rectangle rect;
};

View File

@ -6,12 +6,14 @@
namespace msdf_atlas {
GlyphGeometry::GlyphGeometry() : index(), codepoint(), bounds(), advance(), box() { }
GlyphGeometry::GlyphGeometry() : index(), codepoint(), geometryScale(), bounds(), advance(), box() { }
bool GlyphGeometry::load(msdfgen::FontHandle *font, msdfgen::GlyphIndex index, bool preprocessGeometry) {
if (font && msdfgen::loadGlyph(shape, font, index, &advance) && shape.validate()) {
bool GlyphGeometry::load(msdfgen::FontHandle *font, double geometryScale, msdfgen::GlyphIndex index, bool preprocessGeometry) {
if (font && msdfgen::loadGlyph(shape, font, index, msdfgen::FONT_SCALING_NONE, &advance) && shape.validate()) {
this->index = index.getIndex();
this->geometryScale = geometryScale;
codepoint = 0;
advance *= geometryScale;
#ifdef MSDFGEN_USE_SKIA
if (preprocessGeometry)
msdfgen::resolveShapeGeometry(shape);
@ -34,10 +36,10 @@ bool GlyphGeometry::load(msdfgen::FontHandle *font, msdfgen::GlyphIndex index, b
return false;
}
bool GlyphGeometry::load(msdfgen::FontHandle *font, unicode_t codepoint, bool preprocessGeometry) {
bool GlyphGeometry::load(msdfgen::FontHandle *font, double geometryScale, unicode_t codepoint, bool preprocessGeometry) {
msdfgen::GlyphIndex index;
if (msdfgen::getGlyphIndex(index, font, codepoint)) {
if (load(font, index, preprocessGeometry)) {
if (load(font, geometryScale, index, preprocessGeometry)) {
this->codepoint = codepoint;
return true;
}
@ -45,35 +47,142 @@ bool GlyphGeometry::load(msdfgen::FontHandle *font, unicode_t codepoint, bool pr
return false;
}
void GlyphGeometry::edgeColoring(double angleThreshold, unsigned long long seed) {
msdfgen::edgeColoringInkTrap(shape, angleThreshold, seed);
void GlyphGeometry::edgeColoring(void (*fn)(msdfgen::Shape &, double, unsigned long long), double angleThreshold, unsigned long long seed) {
fn(shape, angleThreshold, seed);
}
void GlyphGeometry::wrapBox(double scale, double range, double miterLimit) {
void GlyphGeometry::wrapBox(const GlyphAttributes &glyphAttributes) {
double scale = glyphAttributes.scale*geometryScale;
msdfgen::Range range = glyphAttributes.range/geometryScale;
Padding fullPadding = (glyphAttributes.innerPadding+glyphAttributes.outerPadding)/geometryScale;
box.range = range;
box.scale = scale;
if (bounds.l < bounds.r && bounds.b < bounds.t) {
double l = bounds.l, b = bounds.b, r = bounds.r, t = bounds.t;
l -= .5*range, b -= .5*range;
r += .5*range, t += .5*range;
if (miterLimit > 0)
shape.boundMiters(l, b, r, t, .5*range, miterLimit, 1);
double w = scale*(r-l);
double h = scale*(t-b);
box.rect.w = (int) ceil(w)+1;
box.rect.h = (int) ceil(h)+1;
box.translate.x = -l+.5*(box.rect.w-w)/scale;
box.translate.y = -b+.5*(box.rect.h-h)/scale;
l += range.lower, b += range.lower;
r -= range.lower, t -= range.lower;
if (glyphAttributes.miterLimit > 0)
shape.boundMiters(l, b, r, t, -range.lower, glyphAttributes.miterLimit, 1);
l -= fullPadding.l, b -= fullPadding.b;
r += fullPadding.r, t += fullPadding.t;
if (glyphAttributes.pxAlignOriginX) {
int sl = (int) floor(scale*l-.5);
int sr = (int) ceil(scale*r+.5);
box.rect.w = sr-sl;
box.translate.x = -sl/scale;
} else {
double w = scale*(r-l);
box.rect.w = (int) ceil(w)+1;
box.translate.x = -l+.5*(box.rect.w-w)/scale;
}
if (glyphAttributes.pxAlignOriginY) {
int sb = (int) floor(scale*b-.5);
int st = (int) ceil(scale*t+.5);
box.rect.h = st-sb;
box.translate.y = -sb/scale;
} else {
double h = scale*(t-b);
box.rect.h = (int) ceil(h)+1;
box.translate.y = -b+.5*(box.rect.h-h)/scale;
}
box.outerPadding = glyphAttributes.scale*glyphAttributes.outerPadding;
} else {
box.rect.w = 0, box.rect.h = 0;
box.translate = msdfgen::Vector2();
}
}
void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool pxAlignOrigin) {
GlyphAttributes attribs = { };
attribs.scale = scale;
attribs.range = range;
attribs.miterLimit = miterLimit;
attribs.pxAlignOriginX = pxAlignOrigin;
attribs.pxAlignOriginY = pxAlignOrigin;
wrapBox(attribs);
}
void GlyphGeometry::wrapBox(double scale, double range, double miterLimit, bool pxAlignOriginX, bool pxAlignOriginY) {
GlyphAttributes attribs = { };
attribs.scale = scale;
attribs.range = range;
attribs.miterLimit = miterLimit;
attribs.pxAlignOriginX = pxAlignOriginX;
attribs.pxAlignOriginY = pxAlignOriginY;
wrapBox(attribs);
}
void GlyphGeometry::frameBox(const GlyphAttributes &glyphAttributes, int width, int height, const double *fixedX, const double *fixedY) {
double scale = glyphAttributes.scale*geometryScale;
msdfgen::Range range = glyphAttributes.range/geometryScale;
Padding fullPadding = (glyphAttributes.innerPadding+glyphAttributes.outerPadding)/geometryScale;
box.range = range;
box.scale = scale;
box.rect.w = width;
box.rect.h = height;
if (fixedX && fixedY) {
box.translate.x = *fixedX/geometryScale;
box.translate.y = *fixedY/geometryScale;
} else {
double l = bounds.l, b = bounds.b, r = bounds.r, t = bounds.t;
l += range.lower, b += range.lower;
r -= range.lower, t -= range.lower;
if (glyphAttributes.miterLimit > 0)
shape.boundMiters(l, b, r, t, -range.lower, glyphAttributes.miterLimit, 1);
l -= fullPadding.l, b -= fullPadding.b;
r += fullPadding.r, t += fullPadding.t;
if (fixedX)
box.translate.x = *fixedX/geometryScale;
else if (glyphAttributes.pxAlignOriginX) {
int sl = (int) floor(scale*l-.5);
int sr = (int) ceil(scale*r+.5);
box.translate.x = (-sl+(box.rect.w-(sr-sl))/2)/scale;
} else {
double w = scale*(r-l);
box.translate.x = -l+.5*(box.rect.w-w)/scale;
}
if (fixedY)
box.translate.y = *fixedY/geometryScale;
else if (glyphAttributes.pxAlignOriginY) {
int sb = (int) floor(scale*b-.5);
int st = (int) ceil(scale*t+.5);
box.translate.y = (-sb+(box.rect.h-(st-sb))/2)/scale;
} else {
double h = scale*(t-b);
box.translate.y = -b+.5*(box.rect.h-h)/scale;
}
}
box.outerPadding = glyphAttributes.scale*glyphAttributes.outerPadding;
}
void GlyphGeometry::frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY, bool pxAlignOrigin) {
GlyphAttributes attribs = { };
attribs.scale = scale;
attribs.range = range;
attribs.miterLimit = miterLimit;
attribs.pxAlignOriginX = pxAlignOrigin;
attribs.pxAlignOriginY = pxAlignOrigin;
frameBox(attribs, width, height, fixedX, fixedY);
}
void GlyphGeometry::frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY, bool pxAlignOriginX, bool pxAlignOriginY) {
GlyphAttributes attribs = { };
attribs.scale = scale;
attribs.range = range;
attribs.miterLimit = miterLimit;
attribs.pxAlignOriginX = pxAlignOriginX;
attribs.pxAlignOriginY = pxAlignOriginY;
frameBox(attribs, width, height, fixedX, fixedY);
}
void GlyphGeometry::placeBox(int x, int y) {
box.rect.x = x, box.rect.y = y;
}
void GlyphGeometry::setBoxRect(const Rectangle &rect) {
box.rect = rect;
}
int GlyphGeometry::getIndex() const {
return index;
}
@ -96,14 +205,26 @@ int GlyphGeometry::getIdentifier(GlyphIdentifierType type) const {
return 0;
}
const msdfgen::Shape & GlyphGeometry::getShape() const {
double GlyphGeometry::getGeometryScale() const {
return geometryScale;
}
const msdfgen::Shape &GlyphGeometry::getShape() const {
return shape;
}
const msdfgen::Shape::Bounds &GlyphGeometry::getShapeBounds() const {
return bounds;
}
double GlyphGeometry::getAdvance() const {
return advance;
}
Rectangle GlyphGeometry::getBoxRect() const {
return box.rect;
}
void GlyphGeometry::getBoxRect(int &x, int &y, int &w, int &h) const {
x = box.rect.x, y = box.rect.y;
w = box.rect.w, h = box.rect.h;
@ -113,10 +234,14 @@ void GlyphGeometry::getBoxSize(int &w, int &h) const {
w = box.rect.w, h = box.rect.h;
}
double GlyphGeometry::getBoxRange() const {
msdfgen::Range GlyphGeometry::getBoxRange() const {
return box.range;
}
msdfgen::Projection GlyphGeometry::getBoxProjection() const {
return msdfgen::Projection(msdfgen::Vector2(box.scale), box.translate);
}
double GlyphGeometry::getBoxScale() const {
return box.scale;
}
@ -127,20 +252,21 @@ msdfgen::Vector2 GlyphGeometry::getBoxTranslate() const {
void GlyphGeometry::getQuadPlaneBounds(double &l, double &b, double &r, double &t) const {
if (box.rect.w > 0 && box.rect.h > 0) {
l = -box.translate.x+.5/box.scale;
b = -box.translate.y+.5/box.scale;
r = -box.translate.x+(box.rect.w-.5)/box.scale;
t = -box.translate.y+(box.rect.h-.5)/box.scale;
double invBoxScale = 1/box.scale;
l = geometryScale*(-box.translate.x+(box.outerPadding.l+.5)*invBoxScale);
b = geometryScale*(-box.translate.y+(box.outerPadding.b+.5)*invBoxScale);
r = geometryScale*(-box.translate.x+(-box.outerPadding.r+box.rect.w-.5)*invBoxScale);
t = geometryScale*(-box.translate.y+(-box.outerPadding.t+box.rect.h-.5)*invBoxScale);
} else
l = 0, b = 0, r = 0, t = 0;
}
void GlyphGeometry::getQuadAtlasBounds(double &l, double &b, double &r, double &t) const {
if (box.rect.w > 0 && box.rect.h > 0) {
l = box.rect.x+.5;
b = box.rect.y+.5;
r = box.rect.x+box.rect.w-.5;
t = box.rect.y+box.rect.h-.5;
l = box.rect.x+box.outerPadding.l+.5;
b = box.rect.y+box.outerPadding.b+.5;
r = box.rect.x-box.outerPadding.r+box.rect.w-.5;
t = box.rect.y-box.outerPadding.t+box.rect.h-.5;
} else
l = 0, b = 0, r = 0, t = 0;
}
@ -158,4 +284,8 @@ GlyphGeometry::operator GlyphBox() const {
return box;
}
msdfgen::Range operator+(msdfgen::Range a, msdfgen::Range b) {
return msdfgen::Range(a.lower+b.lower, a.upper+b.upper);
}
}

View File

@ -4,24 +4,42 @@
#include <msdfgen.h>
#include <msdfgen-ext.h>
#include "types.h"
#include "Rectangle.h"
#include "Padding.h"
#include "GlyphBox.h"
namespace msdf_atlas {
/// Represent's the shape geometry of a single glyph as well as its configuration
/// Represents the shape geometry of a single glyph as well as its configuration
class GlyphGeometry {
public:
struct GlyphAttributes {
double scale;
msdfgen::Range range;
Padding innerPadding, outerPadding;
double miterLimit;
bool pxAlignOriginX, pxAlignOriginY;
};
GlyphGeometry();
/// Loads glyph geometry from font
bool load(msdfgen::FontHandle *font, msdfgen::GlyphIndex index, bool preprocessGeometry = true);
bool load(msdfgen::FontHandle *font, unicode_t codepoint, bool preprocessGeometry = true);
bool load(msdfgen::FontHandle *font, double geometryScale, msdfgen::GlyphIndex index, bool preprocessGeometry = true);
bool load(msdfgen::FontHandle *font, double geometryScale, unicode_t codepoint, bool preprocessGeometry = true);
/// Applies edge coloring to glyph shape
void edgeColoring(double angleThreshold, unsigned long long seed);
void edgeColoring(void (*fn)(msdfgen::Shape &, double, unsigned long long), double angleThreshold, unsigned long long seed);
/// Computes the dimensions of the glyph's box as well as the transformation for the generator function
void wrapBox(double scale, double range, double miterLimit);
void wrapBox(const GlyphAttributes &glyphAttributes);
void wrapBox(double scale, double range, double miterLimit, bool pxAlignOrigin = false);
void wrapBox(double scale, double range, double miterLimit, bool pxAlignOriginX, bool pxAlignOriginY);
/// Computes the glyph's transformation and alignment (unless specified) for given dimensions
void frameBox(const GlyphAttributes &glyphAttributes, int width, int height, const double *fixedX, const double *fixedY);
void frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY, bool pxAlignOrigin = false);
void frameBox(double scale, double range, double miterLimit, int width, int height, const double *fixedX, const double *fixedY, bool pxAlignOriginX, bool pxAlignOriginY);
/// Sets the glyph's box's position in the atlas
void placeBox(int x, int y);
/// Sets the glyph's box's rectangle in the atlas
void setBoxRect(const Rectangle &rect);
/// Returns the glyph's index within the font
int getIndex() const;
/// Returns the glyph's index as a msdfgen::GlyphIndex
@ -30,16 +48,24 @@ public:
unicode_t getCodepoint() const;
/// Returns the glyph's identifier specified by the supplied identifier type
int getIdentifier(GlyphIdentifierType type) const;
/// Returns the glyph's geometry scale
double getGeometryScale() const;
/// Returns the glyph's shape
const msdfgen::Shape & getShape() const;
const msdfgen::Shape &getShape() const;
/// Returns the glyph's shape's raw bounds
const msdfgen::Shape::Bounds &getShapeBounds() const;
/// Returns the glyph's advance
double getAdvance() const;
/// Returns the glyph's box in the atlas
Rectangle getBoxRect() const;
/// Outputs the position and dimensions of the glyph's box in the atlas
void getBoxRect(int &x, int &y, int &w, int &h) const;
/// Outputs the dimensions of the glyph's box in the atlas
void getBoxSize(int &w, int &h) const;
/// Returns the range needed to generate the glyph's SDF
double getBoxRange() const;
msdfgen::Range getBoxRange() const;
/// Returns the projection needed to generate the glyph's bitmap
msdfgen::Projection getBoxProjection() const;
/// Returns the scale needed to generate the glyph's bitmap
double getBoxScale() const;
/// Returns the translation vector needed to generate the glyph's bitmap
@ -56,18 +82,20 @@ public:
private:
int index;
unicode_t codepoint;
double geometryScale;
msdfgen::Shape shape;
msdfgen::Shape::Bounds bounds;
double advance;
struct {
struct {
int x, y, w, h;
} rect;
double range;
Rectangle rect;
msdfgen::Range range;
double scale;
msdfgen::Vector2 translate;
Padding outerPadding;
} box;
};
msdfgen::Range operator+(msdfgen::Range a, msdfgen::Range b);
}

View File

@ -0,0 +1,636 @@
#include "GridAtlasPacker.h"
#include <algorithm>
#include "utils.hpp"
namespace msdf_atlas {
static bool squareConstraint(DimensionsConstraint constraint) {
switch (constraint) {
case DimensionsConstraint::SQUARE:
case DimensionsConstraint::EVEN_SQUARE:
case DimensionsConstraint::MULTIPLE_OF_FOUR_SQUARE:
case DimensionsConstraint::POWER_OF_TWO_SQUARE:
return true;
case DimensionsConstraint::NONE:
case DimensionsConstraint::POWER_OF_TWO_RECTANGLE:
return false;
}
return true;
}
void GridAtlasPacker::lowerToConstraint(int &width, int &height, DimensionsConstraint constraint) {
if (squareConstraint(constraint))
width = height = std::min(width, height);
switch (constraint) {
case DimensionsConstraint::NONE:
case DimensionsConstraint::SQUARE:
break;
case DimensionsConstraint::EVEN_SQUARE:
width &= ~1;
height &= ~1;
break;
case DimensionsConstraint::MULTIPLE_OF_FOUR_SQUARE:
width &= ~3;
height &= ~3;
break;
case DimensionsConstraint::POWER_OF_TWO_RECTANGLE:
case DimensionsConstraint::POWER_OF_TWO_SQUARE:
if (width > 0)
width = floorToPOT(width);
if (height > 0)
height = floorToPOT(height);
break;
}
}
void GridAtlasPacker::raiseToConstraint(int &width, int &height, DimensionsConstraint constraint) {
if (squareConstraint(constraint))
width = height = std::max(width, height);
switch (constraint) {
case DimensionsConstraint::NONE:
case DimensionsConstraint::SQUARE:
break;
case DimensionsConstraint::EVEN_SQUARE:
width += width&1;
height += height&1;
break;
case DimensionsConstraint::MULTIPLE_OF_FOUR_SQUARE:
width += -width&3;
height += -height&3;
break;
case DimensionsConstraint::POWER_OF_TWO_RECTANGLE:
case DimensionsConstraint::POWER_OF_TWO_SQUARE:
if (width > 0)
width = ceilToPOT(width);
if (height > 0)
height = ceilToPOT(height);
break;
}
}
double GridAtlasPacker::dimensionsRating(int width, int height, bool aligned) const {
return ((double) width*width+(double) height*height)*(aligned ? 1-alignedColumnsBias : 1);
}
GridAtlasPacker::GridAtlasPacker() :
columns(-1), rows(-1),
width(-1), height(-1),
cellWidth(-1), cellHeight(-1),
spacing(0),
dimensionsConstraint(DimensionsConstraint::NONE),
cellDimensionsConstraint(DimensionsConstraint::NONE),
hFixed(false), vFixed(false),
scale(-1),
minScale(1),
fixedX(0), fixedY(0),
unitRange(0),
pxRange(0),
miterLimit(0),
pxAlignOriginX(false), pxAlignOriginY(false),
scaleMaximizationTolerance(.001),
alignedColumnsBias(.125),
cutoff(false)
{ }
msdfgen::Shape::Bounds GridAtlasPacker::getMaxBounds(double &maxWidth, double &maxHeight, GlyphGeometry *glyphs, int count, double scale, double outerRange) const {
static const double LARGE_VALUE = 1e240;
msdfgen::Shape::Bounds maxBounds = { +LARGE_VALUE, +LARGE_VALUE, -LARGE_VALUE, -LARGE_VALUE };
for (GlyphGeometry *glyph = glyphs, *end = glyphs+count; glyph < end; ++glyph) {
if (!glyph->isWhitespace()) {
double geometryScale = glyph->getGeometryScale();
double shapeOuterRange = outerRange/geometryScale;
geometryScale *= scale;
const msdfgen::Shape::Bounds &shapeBounds = glyph->getShapeBounds();
double l = shapeBounds.l, b = shapeBounds.b, r = shapeBounds.r, t = shapeBounds.t;
l -= shapeOuterRange, b -= shapeOuterRange;
r += shapeOuterRange, t += shapeOuterRange;
if (miterLimit > 0)
glyph->getShape().boundMiters(l, b, r, t, shapeOuterRange, miterLimit, 1);
l *= geometryScale, b *= geometryScale;
r *= geometryScale, t *= geometryScale;
maxBounds.l = std::min(maxBounds.l, l);
maxBounds.b = std::min(maxBounds.b, b);
maxBounds.r = std::max(maxBounds.r, r);
maxBounds.t = std::max(maxBounds.t, t);
maxWidth = std::max(maxWidth, r-l);
maxHeight = std::max(maxHeight, t-b);
}
}
if (maxBounds.l >= maxBounds.r || maxBounds.b >= maxBounds.t)
maxBounds = msdfgen::Shape::Bounds();
Padding fullPadding = scale*(innerUnitPadding+outerUnitPadding)+innerPxPadding+outerPxPadding;
pad(maxBounds, fullPadding);
maxWidth += fullPadding.l+fullPadding.r;
maxHeight += fullPadding.b+fullPadding.t;
// If origin is pixel-aligned but not fixed, one pixel has to be added to max dimensions to allow for aligning the origin by shifting by < 1 pixel
if (hFixed)
maxWidth = maxBounds.r-maxBounds.l;
else if (pxAlignOriginX)
maxWidth += 1;
if (vFixed)
maxHeight = maxBounds.t-maxBounds.b;
else if (pxAlignOriginY)
maxHeight += 1;
return maxBounds;
}
double GridAtlasPacker::scaleToFit(GlyphGeometry *glyphs, int count, int cellWidth, int cellHeight, msdfgen::Shape::Bounds &maxBounds, double &maxWidth, double &maxHeight) const {
static const int BIG_VALUE = 1<<28;
if (cellWidth <= 0)
cellWidth = BIG_VALUE;
if (cellHeight <= 0)
cellHeight = BIG_VALUE;
--cellWidth, --cellHeight; // Implicit half-pixel padding from each side to make sure that no representable values are beyond outermost pixel centers
cellWidth -= spacing, cellHeight -= spacing;
bool lastResult = false;
#define TRY_FIT(scale) (maxWidth = 0, maxHeight = 0, maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, (scale), -(unitRange.lower+pxRange.lower/(scale))), lastResult = maxWidth <= cellWidth && maxHeight <= cellHeight)
double minScale = 1, maxScale = 1;
if (TRY_FIT(1)) {
while (maxScale < 1e+32 && ((maxScale = 2*minScale), TRY_FIT(maxScale)))
minScale = maxScale;
} else {
while (minScale > 1e-32 && ((minScale = .5*maxScale), !TRY_FIT(minScale)))
maxScale = minScale;
}
if (minScale == maxScale)
return 0;
while (minScale/maxScale < 1-scaleMaximizationTolerance) {
double midScale = .5*(minScale+maxScale);
if (TRY_FIT(midScale))
minScale = midScale;
else
maxScale = midScale;
}
if (!lastResult)
TRY_FIT(minScale);
return minScale;
}
// Can this spaghetti code be simplified?
// Idea: Maybe it could be rewritten into a while (not all properties deduced) cycle, and compute one value in each iteration
int GridAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
if (!count)
return 0;
GridAtlasPacker initial(*this);
int cellCount = 0;
if (columns > 0 && rows > 0)
cellCount = columns*rows;
else {
// Count non-whitespace glyphs only
for (const GlyphGeometry *glyph = glyphs, *end = glyphs+count; glyph < end; ++glyph) {
if (!glyph->isWhitespace())
++cellCount;
}
if (columns > 0)
rows = (cellCount+columns-1)/columns;
else if (rows > 0)
columns = (cellCount+rows-1)/rows;
else if (width > 0 && cellWidth > 0) {
columns = (width+spacing)/cellWidth;
rows = (cellCount+columns-1)/columns;
}
}
if (width < 0 && cellWidth > 0 && columns > 0)
width = columns*cellWidth;
if (height < 0 && cellHeight > 0 && rows > 0)
height = rows*cellHeight;
if (width != initial.width || height != initial.height)
raiseToConstraint(width, height, dimensionsConstraint);
if (cellWidth < 0 && width > 0 && columns > 0)
cellWidth = (width+spacing)/columns;
if (cellHeight < 0 && height > 0 && rows > 0)
cellHeight = (height+spacing)/rows;
if (cellWidth != initial.cellWidth || cellHeight != initial.cellHeight) {
bool positiveCellWidth = cellWidth > 0, positiveCellHeight = cellHeight > 0;
lowerToConstraint(cellWidth, cellHeight, cellDimensionsConstraint);
if ((cellWidth == 0 && positiveCellWidth) || (cellHeight == 0 && positiveCellHeight))
return -1;
}
if ((cellWidth > 0 && cellWidth-spacing-1 <= -2*pxRange.lower) || (cellHeight > 0 && cellHeight-spacing-1 <= -2*pxRange.lower)) // cells definitely too small
return -1;
msdfgen::Shape::Bounds maxBounds = { };
double maxWidth = 0, maxHeight = 0;
if (scale <= 0) {
// If both pxRange and miterLimit is non-zero, miter bounds have to be computed for all potential scales
if (pxRange.lower != pxRange.upper && miterLimit > 0) {
if (cellWidth > 0 || cellHeight > 0) {
scale = scaleToFit(glyphs, count, cellWidth, cellHeight, maxBounds, maxWidth, maxHeight);
if (scale < minScale) {
scale = minScale;
cutoff = true;
maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, scale, -(unitRange.lower+pxRange.lower/scale));
}
}
else if (width > 0 && height > 0) {
double bestAlignedScale = 0;
int bestCols = 0, bestAlignedCols = 0;
for (int q = (int) sqrt(cellCount)+1; q > 0; --q) {
int cols = q;
int rows = (cellCount+cols-1)/cols;
int tWidth = (width+spacing)/cols;
int tHeight = (height+spacing)/rows;
lowerToConstraint(tWidth, tHeight, cellDimensionsConstraint);
if (tWidth > 0 && tHeight > 0) {
double curScale = scaleToFit(glyphs, count, tWidth, tHeight, maxBounds, maxWidth, maxHeight);
if (curScale > scale) {
scale = curScale;
bestCols = cols;
}
if (cols*tWidth == width && curScale > bestAlignedScale) {
bestAlignedScale = curScale;
bestAlignedCols = cols;
}
}
cols = (cellCount+q-1)/q;
rows = (cellCount+cols-1)/cols;
tWidth = (width+spacing)/cols;
tHeight = (height+spacing)/rows;
lowerToConstraint(tWidth, tHeight, cellDimensionsConstraint);
if (tWidth > 0 && tHeight > 0) {
double curScale = scaleToFit(glyphs, count, tWidth, tHeight, maxBounds, maxWidth, maxHeight);
if (curScale > scale) {
scale = curScale;
bestCols = cols;
}
if (cols*tWidth == width && curScale > bestAlignedScale) {
bestAlignedScale = curScale;
bestAlignedCols = cols;
}
}
}
if (!bestCols)
return -1;
// If columns can be aligned with total width at a slight cost to glyph scale, use that number of columns instead
if (bestAlignedScale >= minScale && (alignedColumnsBias+1)*bestAlignedScale >= scale) {
scale = bestAlignedScale;
bestCols = bestAlignedCols;
}
columns = bestCols;
rows = (cellCount+columns-1)/columns;
cellWidth = (width+spacing)/columns;
cellHeight = (height+spacing)/rows;
lowerToConstraint(cellWidth, cellHeight, cellDimensionsConstraint);
scale = scaleToFit(glyphs, count, cellWidth, cellHeight, maxBounds, maxWidth, maxHeight);
if (scale < minScale)
scale = -1;
}
if (scale <= 0) {
maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, minScale, -(unitRange.lower+pxRange.lower/minScale));
cellWidth = (int) ceil(maxWidth)+spacing+1;
cellHeight = (int) ceil(maxHeight)+spacing+1;
raiseToConstraint(cellWidth, cellHeight, cellDimensionsConstraint);
scale = scaleToFit(glyphs, count, cellWidth, cellHeight, maxBounds, maxWidth, maxHeight);
if (scale < minScale)
maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, scale = minScale, -(unitRange.lower+pxRange.lower/minScale));
}
if (initial.rows < 0 && initial.cellHeight < 0) {
int optimalCellWidth = cellWidth, optimalCellHeight = (int) ceil(maxHeight)+spacing+1;
raiseToConstraint(optimalCellWidth, optimalCellHeight, cellDimensionsConstraint);
if (optimalCellHeight < cellHeight && optimalCellWidth <= cellWidth) {
cellWidth = optimalCellWidth;
cellHeight = optimalCellHeight;
}
}
} else {
Padding pxPadding = innerPxPadding+outerPxPadding;
maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, 1, -unitRange.lower);
// Undo pxPadding added by getMaxBounds before pixel scale is known
pad(maxBounds, -pxPadding);
maxWidth -= pxPadding.l+pxPadding.r;
maxHeight -= pxPadding.b+pxPadding.t;
int hSlack = 0, vSlack = 0;
if (pxAlignOriginX && !hFixed) {
maxWidth -= 1; // Added by getMaxBounds
hSlack = 1;
}
if (pxAlignOriginY && !vFixed) {
maxHeight -= 1; // Added by getMaxBounds
vSlack = 1;
}
double extraPxWidth = -2*pxRange.lower+pxPadding.l+pxPadding.r;
double extraPxHeight = -2*pxRange.lower+pxPadding.b+pxPadding.t;
double hScale = 0, vScale = 0;
if (cellWidth > 0)
hScale = (cellWidth-hSlack-spacing-extraPxWidth-1)/maxWidth;
if (cellHeight > 0)
vScale = (cellHeight-vSlack-spacing-extraPxHeight-1)/maxHeight;
if (hScale || vScale) {
if (hScale && vScale)
scale = std::min(hScale, vScale);
else
scale = hScale+vScale;
if (scale < minScale) {
scale = minScale;
cutoff = true;
}
}
else if (width > 0 && height > 0) {
double bestAlignedScale = 0;
int bestCols = 0, bestAlignedCols = 0;
// TODO optimize to only test up to sqrt(cellCount) cols and rows like in the above branch (for (int q = (int) sqrt(cellCount)+1; ...)
for (int cols = 1; cols < width; ++cols) {
int rows = (cellCount+cols-1)/cols;
int tWidth = (width+spacing)/cols;
int tHeight = (height+spacing)/rows;
lowerToConstraint(tWidth, tHeight, cellDimensionsConstraint);
if (tWidth > 0 && tHeight > 0) {
hScale = (tWidth-hSlack-spacing-extraPxWidth-1)/maxWidth;
vScale = (tHeight-vSlack-spacing-extraPxHeight-1)/maxHeight;
double curScale = std::min(hScale, vScale);
if (curScale > scale) {
scale = curScale;
bestCols = cols;
}
if (cols*tWidth == width && curScale > bestAlignedScale) {
bestAlignedScale = curScale;
bestAlignedCols = cols;
}
}
}
if (!bestCols)
return -1;
// If columns can be aligned with total width at a slight cost to glyph scale, use that number of columns instead
if (bestAlignedScale >= minScale && (alignedColumnsBias+1)*bestAlignedScale >= scale) {
scale = bestAlignedScale;
bestCols = bestAlignedCols;
}
columns = bestCols;
rows = (cellCount+columns-1)/columns;
cellWidth = (width+spacing)/columns;
cellHeight = (height+spacing)/rows;
lowerToConstraint(cellWidth, cellHeight, cellDimensionsConstraint);
if (scale < minScale)
scale = -1;
}
if (scale <= 0) {
cellWidth = (int) ceil(minScale*maxWidth+extraPxWidth)+hSlack+spacing+1;
cellHeight = (int) ceil(minScale*maxHeight+extraPxHeight)+vSlack+spacing+1;
raiseToConstraint(cellWidth, cellHeight, cellDimensionsConstraint);
hScale = (cellWidth-hSlack-spacing-extraPxWidth-1)/maxWidth;
vScale = (cellHeight-vSlack-spacing-extraPxHeight-1)/maxHeight;
scale = std::min(hScale, vScale);
}
if (initial.rows < 0 && initial.cellHeight < 0) {
int optimalCellWidth = cellWidth, optimalCellHeight = (int) ceil(scale*maxHeight+extraPxHeight)+vSlack+spacing+1;
raiseToConstraint(optimalCellWidth, optimalCellHeight, cellDimensionsConstraint);
if (optimalCellHeight < cellHeight && optimalCellWidth <= cellWidth) {
cellWidth = optimalCellWidth;
cellHeight = optimalCellHeight;
}
}
maxBounds.l *= scale, maxBounds.b *= scale;
maxBounds.r *= scale, maxBounds.t *= scale;
maxWidth *= scale, maxHeight *= scale;
// Redo addition of pxPadding once scale is known
pad(maxBounds, pxPadding);
maxWidth += pxPadding.l+pxPadding.r;
maxHeight += pxPadding.b+pxPadding.t;
}
} else {
maxBounds = getMaxBounds(maxWidth, maxHeight, glyphs, count, scale, -(unitRange.lower+pxRange.lower/scale));
int optimalCellWidth = (int) ceil(maxWidth)+spacing+1;
int optimalCellHeight = (int) ceil(maxHeight)+spacing+1;
if (cellWidth < 0 || cellHeight < 0) {
cellWidth = optimalCellWidth;
cellHeight = optimalCellHeight;
raiseToConstraint(cellWidth, cellHeight, cellDimensionsConstraint);
} else if (cellWidth < optimalCellWidth || cellHeight < optimalCellHeight)
cutoff = true;
}
// Compute fixed origin
if (hFixed) {
if (pxAlignOriginX) {
int sl = (int) floor(maxBounds.l-.5);
int sr = (int) ceil(maxBounds.r+.5);
fixedX = (-sl+(cellWidth-spacing-(sr-sl))/2)/scale;
} else
fixedX = (-maxBounds.l+.5*(cellWidth-spacing-maxWidth))/scale;
}
if (vFixed) {
if (pxAlignOriginY) {
int sb = (int) floor(maxBounds.b-.5);
int st = (int) ceil(maxBounds.t+.5);
fixedY = (-sb+(cellHeight-spacing-(st-sb))/2)/scale;
} else
fixedY = (-maxBounds.b+.5*(cellHeight-spacing-maxHeight))/scale;
}
if (width < 0 || height < 0) {
if (columns <= 0) {
double bestRating = -1;
for (int q = (int) sqrt(cellCount)+1; q > 0; --q) {
int cols = q;
int rows = (cellCount+cols-1)/cols;
int curWidth = cols*cellWidth, curHeight = rows*cellHeight;
raiseToConstraint(curWidth, curHeight, dimensionsConstraint);
double rating = dimensionsRating(curWidth, curHeight, cols*cellWidth == curWidth);
if (rating < bestRating || bestRating < 0) {
bestRating = rating;
columns = cols;
}
rows = q;
cols = (cellCount+rows-1)/rows;
curWidth = cols*cellWidth, curHeight = rows*cellHeight;
raiseToConstraint(curWidth, curHeight, dimensionsConstraint);
rating = dimensionsRating(curWidth, curHeight, cols*cellWidth == curWidth);
if (rating < bestRating || bestRating < 0) {
bestRating = rating;
columns = cols;
}
}
rows = (cellCount+columns-1)/columns;
}
width = columns*cellWidth, height = rows*cellHeight;
raiseToConstraint(width, height, dimensionsConstraint);
// raiseToConstraint may have increased dimensions significantly, rerun if cell dimensions can be optimized.
if (dimensionsConstraint != DimensionsConstraint::NONE && initial.cellWidth < 0 && initial.cellHeight < 0) {
cellWidth = initial.cellWidth;
cellHeight = initial.cellHeight;
columns = initial.columns;
rows = initial.rows;
scale = initial.scale;
return pack(glyphs, count);
}
}
if (columns < 0) {
columns = (width+spacing)/cellWidth;
rows = (cellCount+columns-1)/columns;
}
if (rows*cellHeight > height)
rows = height/cellHeight;
GlyphGeometry::GlyphAttributes attribs = { };
attribs.scale = scale;
attribs.range = unitRange+pxRange/scale;
attribs.innerPadding = innerUnitPadding+1/scale*innerPxPadding;
attribs.outerPadding = outerUnitPadding+1/scale*outerPxPadding;
attribs.miterLimit = miterLimit;
attribs.pxAlignOriginX = pxAlignOriginX;
attribs.pxAlignOriginY = pxAlignOriginY;
int col = 0, row = 0;
for (GlyphGeometry *glyph = glyphs, *end = glyphs+count; glyph < end; ++glyph) {
if (!glyph->isWhitespace()) {
glyph->frameBox(attribs, cellWidth-spacing, cellHeight-spacing, hFixed ? &fixedX : nullptr, vFixed ? &fixedY : nullptr);
glyph->placeBox(col*cellWidth, height-(row+1)*cellHeight);
if (++col >= columns) {
if (++row >= rows) {
return end-glyph-1;
}
col = 0;
}
}
}
return 0;
}
void GridAtlasPacker::setFixedOrigin(bool horizontal, bool vertical) {
hFixed = horizontal, vFixed = vertical;
}
void GridAtlasPacker::setCellDimensions(int width, int height) {
cellWidth = width, cellHeight = height;
}
void GridAtlasPacker::unsetCellDimensions() {
cellWidth = -1, cellHeight = -1;
}
void GridAtlasPacker::setCellDimensionsConstraint(DimensionsConstraint dimensionsConstraint) {
cellDimensionsConstraint = dimensionsConstraint;
}
void GridAtlasPacker::setColumns(int columns) {
this->columns = columns;
}
void GridAtlasPacker::setRows(int rows) {
this->rows = rows;
}
void GridAtlasPacker::unsetColumns() {
columns = -1;
}
void GridAtlasPacker::unsetRows() {
rows = -1;
}
void GridAtlasPacker::setDimensions(int width, int height) {
this->width = width, this->height = height;
}
void GridAtlasPacker::unsetDimensions() {
width = -1, height = -1;
}
void GridAtlasPacker::setDimensionsConstraint(DimensionsConstraint dimensionsConstraint) {
this->dimensionsConstraint = dimensionsConstraint;
}
void GridAtlasPacker::setSpacing(int spacing) {
this->spacing = spacing;
}
void GridAtlasPacker::setScale(double scale) {
this->scale = scale;
}
void GridAtlasPacker::setMinimumScale(double minScale) {
this->minScale = minScale;
}
void GridAtlasPacker::setUnitRange(msdfgen::Range unitRange) {
this->unitRange = unitRange;
}
void GridAtlasPacker::setPixelRange(msdfgen::Range pxRange) {
this->pxRange = pxRange;
}
void GridAtlasPacker::setMiterLimit(double miterLimit) {
this->miterLimit = miterLimit;
}
void GridAtlasPacker::setOriginPixelAlignment(bool align) {
pxAlignOriginX = align, pxAlignOriginY = align;
}
void GridAtlasPacker::setOriginPixelAlignment(bool alignX, bool alignY) {
pxAlignOriginX = alignX, pxAlignOriginY = alignY;
}
void GridAtlasPacker::setInnerUnitPadding(const Padding &padding) {
innerUnitPadding = padding;
}
void GridAtlasPacker::setOuterUnitPadding(const Padding &padding) {
outerUnitPadding = padding;
}
void GridAtlasPacker::setInnerPixelPadding(const Padding &padding) {
innerPxPadding = padding;
}
void GridAtlasPacker::setOuterPixelPadding(const Padding &padding) {
outerPxPadding = padding;
}
void GridAtlasPacker::getDimensions(int &width, int &height) const {
width = this->width, height = this->height;
}
void GridAtlasPacker::getCellDimensions(int &width, int &height) const {
width = cellWidth, height = cellHeight;
}
int GridAtlasPacker::getColumns() const {
return columns;
}
int GridAtlasPacker::getRows() const {
return rows;
}
double GridAtlasPacker::getScale() const {
return scale;
}
msdfgen::Range GridAtlasPacker::getPixelRange() const {
return pxRange+scale*unitRange;
}
void GridAtlasPacker::getFixedOrigin(double &x, double &y) {
x = fixedX-.5/scale;
y = fixedY-.5/scale;
}
bool GridAtlasPacker::hasCutoff() const {
return cutoff;
}
}

View File

@ -0,0 +1,108 @@
#pragma once
#include "Padding.h"
#include "GlyphGeometry.h"
namespace msdf_atlas {
/**
* This class computes the layout of a static uniform grid atlas and may optionally
* also find the minimum required dimensions and/or the maximum glyph scale
*/
class GridAtlasPacker {
public:
GridAtlasPacker();
/// Computes the layout for the array of glyphs. Returns 0 on success
int pack(GlyphGeometry *glyphs, int count);
/// Sets whether the origin point should be at the same position in each glyph, separately for horizontal and vertical dimension
void setFixedOrigin(bool horizontal, bool vertical);
void setCellDimensions(int width, int height);
void unsetCellDimensions();
void setCellDimensionsConstraint(DimensionsConstraint dimensionsConstraint);
void setColumns(int columns);
void setRows(int rows);
void unsetColumns();
void unsetRows();
/// Sets the atlas's fixed dimensions
void setDimensions(int width, int height);
/// Sets the atlas's dimensions to be determined during pack
void unsetDimensions();
/// Sets the constraint to be used when determining dimensions
void setDimensionsConstraint(DimensionsConstraint dimensionsConstraint);
/// Sets the spacing between glyph boxes
void setSpacing(int spacing);
/// Sets fixed glyph scale
void setScale(double scale);
/// Sets the minimum glyph scale
void setMinimumScale(double minScale);
/// Sets the unit component of the total distance range
void setUnitRange(msdfgen::Range unitRange);
/// Sets the pixel component of the total distance range
void setPixelRange(msdfgen::Range pxRange);
/// Sets the miter limit for bounds computation
void setMiterLimit(double miterLimit);
/// Sets whether each glyph's origin point should stay aligned with the pixel grid
void setOriginPixelAlignment(bool align);
void setOriginPixelAlignment(bool alignX, bool alignY);
/// Sets the unit component of width of additional padding that is part of each glyph quad
void setInnerUnitPadding(const Padding &padding);
/// Sets the unit component of width of additional padding around each glyph quad
void setOuterUnitPadding(const Padding &padding);
/// Sets the pixel component of width of additional padding that is part of each glyph quad
void setInnerPixelPadding(const Padding &padding);
/// Sets the pixel component of width of additional padding around each glyph quad
void setOuterPixelPadding(const Padding &padding);
/// Outputs the atlas's final dimensions
void getDimensions(int &width, int &height) const;
/// Outputs the horizontal and vertical difference between the bottom left corners of consecutive grid cells
void getCellDimensions(int &width, int &height) const;
/// Returns the final number of grid columns
int getColumns() const;
/// Returns the final number of grid rows
int getRows() const;
/// Returns the final glyph scale
double getScale() const;
/// Returns the final combined pixel range (including converted unit range)
msdfgen::Range getPixelRange() const;
/// Outputs the position of the origin within each cell, each value is only valid if the origin is fixed in the respective dimension
void getFixedOrigin(double &x, double &y);
/// Returns true if the explicitly constrained cell dimensions aren't large enough to fit each glyph fully
bool hasCutoff() const;
private:
int columns, rows;
int width, height;
int cellWidth, cellHeight;
int spacing;
DimensionsConstraint dimensionsConstraint;
DimensionsConstraint cellDimensionsConstraint;
bool hFixed, vFixed;
double scale;
double minScale;
double fixedX, fixedY;
msdfgen::Range unitRange;
msdfgen::Range pxRange;
double miterLimit;
bool pxAlignOriginX, pxAlignOriginY;
Padding innerUnitPadding, outerUnitPadding;
Padding innerPxPadding, outerPxPadding;
double scaleMaximizationTolerance;
double alignedColumnsBias;
bool cutoff;
static void lowerToConstraint(int &width, int &height, DimensionsConstraint constraint);
static void raiseToConstraint(int &width, int &height, DimensionsConstraint constraint);
double dimensionsRating(int width, int height, bool aligned) const;
msdfgen::Shape::Bounds getMaxBounds(double &maxWidth, double &maxHeight, GlyphGeometry *glyphs, int count, double scale, double outerRange) const;
double scaleToFit(GlyphGeometry *glyphs, int count, int cellWidth, int cellHeight, msdfgen::Shape::Bounds &maxBounds, double &maxWidth, double &maxHeight) const;
};
}

View File

@ -20,6 +20,8 @@ class ImmediateAtlasGenerator {
public:
ImmediateAtlasGenerator();
ImmediateAtlasGenerator(int width, int height);
template <typename... ARGS>
ImmediateAtlasGenerator(int width, int height, ARGS... storageArgs);
void generate(const GlyphGeometry *glyphs, int count);
void rearrange(int width, int height, const Remap *remapping, int count);
void resize(int width, int height);
@ -28,12 +30,15 @@ public:
/// Sets the number of threads to be run by generate
void setThreadCount(int threadCount);
/// Allows access to the underlying AtlasStorage
const AtlasStorage & atlasStorage() const;
const AtlasStorage &atlasStorage() const;
/// Returns the layout of the contained glyphs as a list of GlyphBoxes
const std::vector<GlyphBox> &getLayout() const;
private:
AtlasStorage storage;
std::vector<GlyphBox> layout;
std::vector<T> glyphBuffer;
std::vector<byte> errorCorrectionBuffer;
GeneratorAttributes attributes;
int threadCount;

View File

@ -11,6 +11,10 @@ ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::ImmediateAtlasGenerator() :
template <typename T, int N, GeneratorFunction<T, N> GEN_FN, class AtlasStorage>
ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::ImmediateAtlasGenerator(int width, int height) : storage(width, height), threadCount(1) { }
template <typename T, int N, GeneratorFunction<T, N> GEN_FN, class AtlasStorage>
template <typename... ARGS>
ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::ImmediateAtlasGenerator(int width, int height, ARGS... storageArgs) : storage(width, height, storageArgs...), threadCount(1) { }
template <typename T, int N, GeneratorFunction<T, N> GEN_FN, class AtlasStorage>
void ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::generate(const GlyphGeometry *glyphs, int count) {
int maxBoxArea = 0;
@ -22,14 +26,21 @@ void ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::generate(const GlyphGe
int threadBufferSize = N*maxBoxArea;
if (threadCount*threadBufferSize > (int) glyphBuffer.size())
glyphBuffer.resize(threadCount*threadBufferSize);
if (threadCount*maxBoxArea > (int) errorCorrectionBuffer.size())
errorCorrectionBuffer.resize(threadCount*maxBoxArea);
std::vector<GeneratorAttributes> threadAttributes(threadCount);
for (int i = 0; i < threadCount; ++i) {
threadAttributes[i] = attributes;
threadAttributes[i].config.errorCorrection.buffer = errorCorrectionBuffer.data()+i*maxBoxArea;
}
Workload([this, &glyphs, threadBufferSize](int i, int threadNo) -> bool {
Workload([this, glyphs, &threadAttributes, threadBufferSize](int i, int threadNo) -> bool {
const GlyphGeometry &glyph = glyphs[i];
if (!glyph.isWhitespace()) {
int l, b, w, h;
glyph.getBoxRect(l, b, w, h);
msdfgen::BitmapRef<T, N> glyphBitmap(glyphBuffer.data()+threadNo*threadBufferSize, w, h);
GEN_FN(glyphBitmap, glyph, attributes);
GEN_FN(glyphBitmap, glyph, threadAttributes[threadNo]);
storage.put(l, b, msdfgen::BitmapConstRef<T, N>(glyphBitmap));
}
return true;
@ -63,8 +74,13 @@ void ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::setThreadCount(int thr
}
template <typename T, int N, GeneratorFunction<T, N> GEN_FN, class AtlasStorage>
const AtlasStorage & ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::atlasStorage() const {
const AtlasStorage &ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::atlasStorage() const {
return storage;
}
template <typename T, int N, GeneratorFunction<T, N> GEN_FN, class AtlasStorage>
const std::vector<GlyphBox> &ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::getLayout() const {
return layout;
}
}

View File

@ -0,0 +1,37 @@
#include "Padding.h"
namespace msdf_atlas {
void pad(msdfgen::Shape::Bounds &bounds, const Padding &padding) {
bounds.l -= padding.l;
bounds.b -= padding.b;
bounds.r += padding.r;
bounds.t += padding.t;
}
Padding operator-(const Padding &padding) {
return Padding(-padding.l, -padding.b, -padding.r, -padding.t);
}
Padding operator+(const Padding &a, const Padding &b) {
return Padding(a.l+b.l, a.b+b.b, a.r+b.r, a.t+b.t);
}
Padding operator-(const Padding &a, const Padding &b) {
return Padding(a.l-b.l, a.b-b.b, a.r-b.r, a.t-b.t);
}
Padding operator*(double factor, const Padding &padding) {
return Padding(factor*padding.l, factor*padding.b, factor*padding.r, factor*padding.t);
}
Padding operator*(const Padding &padding, double factor) {
return Padding(padding.l*factor, padding.b*factor, padding.r*factor, padding.t*factor);
}
Padding operator/(const Padding &padding, double divisor) {
return Padding(padding.l/divisor, padding.b/divisor, padding.r/divisor, padding.t/divisor);
}
}

24
msdf-atlas-gen/Padding.h Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include <msdfgen.h>
namespace msdf_atlas {
struct Padding {
double l, b, r, t;
inline Padding(double uniformPadding = 0) : l(uniformPadding), b(uniformPadding), r(uniformPadding), t(uniformPadding) { }
inline Padding(double l, double b, double r, double t) : l(l), b(b), r(r), t(t) { }
};
void pad(msdfgen::Shape::Bounds &bounds, const Padding &padding);
Padding operator-(const Padding &padding);
Padding operator+(const Padding &a, const Padding &b);
Padding operator-(const Padding &a, const Padding &b);
Padding operator*(double factor, const Padding &padding);
Padding operator*(const Padding &padding, double factor);
Padding operator/(const Padding &padding, double divisor);
}

View File

@ -25,12 +25,26 @@ RectanglePacker::RectanglePacker(int width, int height) {
spaces.push_back(Rectangle { 0, 0, width, height });
}
void RectanglePacker::expand(int width, int height) {
if (width > 0 && height > 0) {
int oldWidth = 0, oldHeight = 0;
for (const Rectangle &space : spaces) {
if (space.x+space.w > oldWidth)
oldWidth = space.x+space.w;
if (space.y+space.h > oldHeight)
oldHeight = space.y+space.h;
}
spaces.push_back(Rectangle { 0, 0, width, height });
splitSpace(int(spaces.size()-1), oldWidth, oldHeight);
}
}
void RectanglePacker::splitSpace(int index, int w, int h) {
Rectangle space = spaces[index];
removeFromUnorderedVector(spaces, index);
Rectangle a = { space.x, space.y+h, w, space.h-h };
Rectangle b = { space.x+w, space.y, space.w-w, h };
if (w*(space.h-h) <= h*(space.w-w))
if (w*(space.h-h) < h*(space.w-w))
a.w = space.w;
else
b.h = space.h;

View File

@ -12,6 +12,8 @@ class RectanglePacker {
public:
RectanglePacker();
RectanglePacker(int width, int height);
/// Expands the packing area - both width and height must be greater or equal to the previous value
void expand(int width, int height);
/// Packs the rectangle array, returns how many didn't fit (0 on success)
int pack(Rectangle *rectangles, int count);
int pack(OrientedRectangle *rectangles, int count);

View File

@ -8,16 +8,37 @@
namespace msdf_atlas {
int TightAtlasPacker::tryPack(GlyphGeometry *glyphs, int count, DimensionsConstraint dimensionsConstraint, int &width, int &height, int padding, double scale, double range, double miterLimit) {
TightAtlasPacker::TightAtlasPacker() :
width(-1), height(-1),
spacing(0),
dimensionsConstraint(DimensionsConstraint::POWER_OF_TWO_SQUARE),
scale(-1),
minScale(1),
unitRange(0),
pxRange(0),
miterLimit(0),
pxAlignOriginX(false), pxAlignOriginY(false),
scaleMaximizationTolerance(.001)
{ }
int TightAtlasPacker::tryPack(GlyphGeometry *glyphs, int count, DimensionsConstraint dimensionsConstraint, int &width, int &height, double scale) const {
// Wrap glyphs into boxes
std::vector<Rectangle> rectangles;
std::vector<GlyphGeometry *> rectangleGlyphs;
rectangles.reserve(count);
rectangleGlyphs.reserve(count);
GlyphGeometry::GlyphAttributes attribs = { };
attribs.scale = scale;
attribs.range = unitRange+pxRange/scale;
attribs.innerPadding = innerUnitPadding+innerPxPadding/scale;
attribs.outerPadding = outerUnitPadding+outerPxPadding/scale;
attribs.miterLimit = miterLimit;
attribs.pxAlignOriginX = pxAlignOriginX;
attribs.pxAlignOriginY = pxAlignOriginY;
for (GlyphGeometry *glyph = glyphs, *end = glyphs+count; glyph < end; ++glyph) {
if (!glyph->isWhitespace()) {
Rectangle rect = { };
glyph->wrapBox(scale, range, miterLimit);
glyph->wrapBox(attribs);
glyph->getBoxSize(rect.w, rect.h);
if (rect.w > 0 && rect.h > 0) {
rectangles.push_back(rect);
@ -36,26 +57,27 @@ int TightAtlasPacker::tryPack(GlyphGeometry *glyphs, int count, DimensionsConstr
std::pair<int, int> dimensions = std::make_pair(width, height);
switch (dimensionsConstraint) {
case DimensionsConstraint::POWER_OF_TWO_SQUARE:
dimensions = packRectangles<SquarePowerOfTwoSizeSelector>(rectangles.data(), rectangles.size(), padding);
dimensions = packRectangles<SquarePowerOfTwoSizeSelector>(rectangles.data(), rectangles.size(), spacing);
break;
case DimensionsConstraint::POWER_OF_TWO_RECTANGLE:
dimensions = packRectangles<PowerOfTwoSizeSelector>(rectangles.data(), rectangles.size(), padding);
dimensions = packRectangles<PowerOfTwoSizeSelector>(rectangles.data(), rectangles.size(), spacing);
break;
case DimensionsConstraint::MULTIPLE_OF_FOUR_SQUARE:
dimensions = packRectangles<SquareSizeSelector<4> >(rectangles.data(), rectangles.size(), padding);
dimensions = packRectangles<SquareSizeSelector<4> >(rectangles.data(), rectangles.size(), spacing);
break;
case DimensionsConstraint::EVEN_SQUARE:
dimensions = packRectangles<SquareSizeSelector<2> >(rectangles.data(), rectangles.size(), padding);
dimensions = packRectangles<SquareSizeSelector<2> >(rectangles.data(), rectangles.size(), spacing);
break;
case DimensionsConstraint::SQUARE:
dimensions = packRectangles<SquareSizeSelector<> >(rectangles.data(), rectangles.size(), padding);
default:
dimensions = packRectangles<SquareSizeSelector<> >(rectangles.data(), rectangles.size(), spacing);
break;
}
if (!(dimensions.first > 0 && dimensions.second > 0))
return -1;
width = dimensions.first, height = dimensions.second;
} else {
if (int result = packRectangles(rectangles.data(), rectangles.size(), width, height, padding))
if (int result = packRectangles(rectangles.data(), rectangles.size(), width, height, spacing))
return result;
}
// Set glyph box placement
@ -64,20 +86,21 @@ int TightAtlasPacker::tryPack(GlyphGeometry *glyphs, int count, DimensionsConstr
return 0;
}
double TightAtlasPacker::packAndScale(GlyphGeometry *glyphs, int count, int width, int height, int padding, double unitRange, double pxRange, double miterLimit, double tolerance) {
double TightAtlasPacker::packAndScale(GlyphGeometry *glyphs, int count) const {
bool lastResult = false;
#define TRY_PACK(scale) (lastResult = !tryPack(glyphs, count, DimensionsConstraint(), width, height, padding, (scale), unitRange+pxRange/(scale), miterLimit))
int w = width, h = height;
#define TRY_PACK(scale) (lastResult = !tryPack(glyphs, count, DimensionsConstraint(), w, h, (scale)))
double minScale = 1, maxScale = 1;
if (TRY_PACK(1)) {
while (maxScale < 1e+32 && TRY_PACK(maxScale = 2*minScale))
while (maxScale < 1e+32 && ((maxScale = 2*minScale), TRY_PACK(maxScale)))
minScale = maxScale;
} else {
while (minScale > 1e-32 && !TRY_PACK(minScale = .5*maxScale))
while (minScale > 1e-32 && ((minScale = .5*maxScale), !TRY_PACK(minScale)))
maxScale = minScale;
}
if (minScale == maxScale)
return 0;
while (minScale/maxScale < 1-tolerance) {
while (minScale/maxScale < 1-scaleMaximizationTolerance) {
double midScale = .5*(minScale+maxScale);
if (TRY_PACK(midScale))
minScale = midScale;
@ -89,31 +112,17 @@ double TightAtlasPacker::packAndScale(GlyphGeometry *glyphs, int count, int widt
return minScale;
}
TightAtlasPacker::TightAtlasPacker() :
width(-1), height(-1),
padding(0),
dimensionsConstraint(DimensionsConstraint::POWER_OF_TWO_SQUARE),
scale(-1),
minScale(1),
unitRange(0),
pxRange(0),
miterLimit(0),
scaleMaximizationTolerance(.001)
{ }
int TightAtlasPacker::pack(GlyphGeometry *glyphs, int count) {
double initialScale = scale > 0 ? scale : minScale;
if (initialScale > 0) {
if (int remaining = tryPack(glyphs, count, dimensionsConstraint, width, height, padding, initialScale, unitRange+pxRange/initialScale, miterLimit))
if (int remaining = tryPack(glyphs, count, dimensionsConstraint, width, height, initialScale))
return remaining;
} else if (width < 0 || height < 0)
return -1;
if (scale <= 0)
scale = packAndScale(glyphs, count, width, height, padding, unitRange, pxRange, miterLimit, scaleMaximizationTolerance);
scale = packAndScale(glyphs, count);
if (scale <= 0)
return -1;
pxRange += scale*unitRange;
unitRange = 0;
return 0;
}
@ -129,8 +138,8 @@ void TightAtlasPacker::setDimensionsConstraint(DimensionsConstraint dimensionsCo
this->dimensionsConstraint = dimensionsConstraint;
}
void TightAtlasPacker::setPadding(int padding) {
this->padding = padding;
void TightAtlasPacker::setSpacing(int spacing) {
this->spacing = spacing;
}
void TightAtlasPacker::setScale(double scale) {
@ -141,11 +150,11 @@ void TightAtlasPacker::setMinimumScale(double minScale) {
this->minScale = minScale;
}
void TightAtlasPacker::setUnitRange(double unitRange) {
void TightAtlasPacker::setUnitRange(msdfgen::Range unitRange) {
this->unitRange = unitRange;
}
void TightAtlasPacker::setPixelRange(double pxRange) {
void TightAtlasPacker::setPixelRange(msdfgen::Range pxRange) {
this->pxRange = pxRange;
}
@ -153,6 +162,30 @@ void TightAtlasPacker::setMiterLimit(double miterLimit) {
this->miterLimit = miterLimit;
}
void TightAtlasPacker::setOriginPixelAlignment(bool align) {
pxAlignOriginX = align, pxAlignOriginY = align;
}
void TightAtlasPacker::setOriginPixelAlignment(bool alignX, bool alignY) {
pxAlignOriginX = alignX, pxAlignOriginY = alignY;
}
void TightAtlasPacker::setInnerUnitPadding(const Padding &padding) {
innerUnitPadding = padding;
}
void TightAtlasPacker::setOuterUnitPadding(const Padding &padding) {
outerUnitPadding = padding;
}
void TightAtlasPacker::setInnerPixelPadding(const Padding &padding) {
innerPxPadding = padding;
}
void TightAtlasPacker::setOuterPixelPadding(const Padding &padding) {
outerPxPadding = padding;
}
void TightAtlasPacker::getDimensions(int &width, int &height) const {
width = this->width, height = this->height;
}
@ -161,8 +194,8 @@ double TightAtlasPacker::getScale() const {
return scale;
}
double TightAtlasPacker::getPixelRange() const {
return pxRange;
msdfgen::Range TightAtlasPacker::getPixelRange() const {
return pxRange+scale*unitRange;
}
}

View File

@ -1,70 +1,77 @@
#pragma once
#include "types.h"
#include "Padding.h"
#include "GlyphGeometry.h"
namespace msdf_atlas {
/**
* This class computes the layout of a static atlas and may optionally
* This class computes the layout of a static tightly packed atlas and may optionally
* also find the minimum required dimensions and/or the maximum glyph scale
*/
class TightAtlasPacker {
public:
/// Constraints for the atlas's dimensions - see size selectors for more info
enum class DimensionsConstraint {
POWER_OF_TWO_SQUARE,
POWER_OF_TWO_RECTANGLE,
MULTIPLE_OF_FOUR_SQUARE,
EVEN_SQUARE,
SQUARE
};
TightAtlasPacker();
/// Computes the layout for the array of glyphs. Returns 0 on success
int pack(GlyphGeometry *glyphs, int count);
/// Sets the atlas's dimensions to be fixed
/// Sets the atlas's fixed dimensions
void setDimensions(int width, int height);
/// Sets the atlas's dimensions to be determined during pack
void unsetDimensions();
/// Sets the constraint to be used when determining dimensions
void setDimensionsConstraint(DimensionsConstraint dimensionsConstraint);
/// Sets the padding between glyph boxes
void setPadding(int padding);
/// Sets the spacing between glyph boxes
void setSpacing(int spacing);
/// Sets fixed glyph scale
void setScale(double scale);
/// Sets the minimum glyph scale
void setMinimumScale(double minScale);
/// Sets the unit component of the total distance range
void setUnitRange(double unitRange);
void setUnitRange(msdfgen::Range unitRange);
/// Sets the pixel component of the total distance range
void setPixelRange(double pxRange);
void setPixelRange(msdfgen::Range pxRange);
/// Sets the miter limit for bounds computation
void setMiterLimit(double miterLimit);
/// Sets whether each glyph's origin point should stay aligned with the pixel grid
void setOriginPixelAlignment(bool align);
void setOriginPixelAlignment(bool alignX, bool alignY);
/// Sets the unit component of width of additional padding that is part of each glyph quad
void setInnerUnitPadding(const Padding &padding);
/// Sets the unit component of width of additional padding around each glyph quad
void setOuterUnitPadding(const Padding &padding);
/// Sets the pixel component of width of additional padding that is part of each glyph quad
void setInnerPixelPadding(const Padding &padding);
/// Sets the pixel component of width of additional padding around each glyph quad
void setOuterPixelPadding(const Padding &padding);
/// Outputs the atlas's final dimensions
void getDimensions(int &width, int &height) const;
/// Returns the final glyph scale
double getScale() const;
/// Returns the final combined pixel range (including converted unit range)
double getPixelRange() const;
msdfgen::Range getPixelRange() const;
private:
int width, height;
int padding;
int spacing;
DimensionsConstraint dimensionsConstraint;
double scale;
double minScale;
double unitRange;
double pxRange;
msdfgen::Range unitRange;
msdfgen::Range pxRange;
double miterLimit;
bool pxAlignOriginX, pxAlignOriginY;
Padding innerUnitPadding, outerUnitPadding;
Padding innerPxPadding, outerPxPadding;
double scaleMaximizationTolerance;
static int tryPack(GlyphGeometry *glyphs, int count, DimensionsConstraint dimensionsConstraint, int &width, int &height, int padding, double scale, double range, double miterLimit);
static double packAndScale(GlyphGeometry *glyphs, int count, int width, int height, int padding, double unitRange, double pxRange, double miterLimit, double tolerance);
int tryPack(GlyphGeometry *glyphs, int count, DimensionsConstraint dimensionsConstraint, int &width, int &height, double scale) const;
double packAndScale(GlyphGeometry *glyphs, int count) const;
};

View File

@ -1,8 +1,11 @@
#include "artery-font-export.h"
#ifndef MSDF_ATLAS_NO_ARTERY_FONT
#include <artery-font/std-artery-font.h>
#include <artery-font/stdio-serialization.h>
#include "GlyphGeometry.h"
#include "image-encode.h"
namespace msdf_atlas {
@ -53,76 +56,98 @@ artery_font::PixelFormat getPixelFormat<float>() {
}
template <typename REAL, typename T, int N>
bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef<T, N> &atlas, const char *filename, const ArteryFontExportProperties &properties) {
bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef<T, N> &atlas, const char *filename, const ArteryFontExportProperties &properties) {
artery_font::StdArteryFont<REAL> arfont = { };
arfont.metadataFormat = artery_font::METADATA_NONE;
if (glyphCount > 0) {
msdfgen::FontMetrics fontMetrics;
if (!msdfgen::getFontMetrics(fontMetrics, font))
return false;
double fsScale = 1/fontMetrics.emSize;
artery_font::StdFontVariant<REAL> fontVariant = { };
fontVariant.codepointType = convertCodepointType(properties.glyphIdentifierType);
arfont.variants = artery_font::StdList<typename artery_font::StdArteryFont<REAL>::Variant>(fontCount);
for (int i = 0; i < fontCount; ++i) {
const FontGeometry &font = fonts[i];
GlyphIdentifierType identifierType = font.getPreferredIdentifierType();
const msdfgen::FontMetrics &fontMetrics = font.getMetrics();
typename artery_font::StdArteryFont<REAL>::Variant &fontVariant = arfont.variants[i] = typename artery_font::StdArteryFont<REAL>::Variant();
fontVariant.codepointType = convertCodepointType(identifierType);
fontVariant.imageType = convertImageType(properties.imageType);
fontVariant.metrics.fontSize = REAL(properties.fontSize);
if (properties.imageType != ImageType::HARD_MASK)
fontVariant.metrics.distanceRange = REAL(properties.pxRange);
fontVariant.metrics.emSize = REAL(fsScale*fontMetrics.emSize);
fontVariant.metrics.ascender = REAL(fsScale*fontMetrics.ascenderY);
fontVariant.metrics.descender = REAL(fsScale*fontMetrics.descenderY);
fontVariant.metrics.lineHeight = REAL(fsScale*fontMetrics.lineHeight);
fontVariant.metrics.underlineY = REAL(fsScale*fontMetrics.underlineY);
fontVariant.metrics.underlineThickness = REAL(fsScale*fontMetrics.underlineThickness);
fontVariant.glyphs = artery_font::StdList<artery_font::Glyph<REAL> >(glyphCount);
for (int i = 0; i < glyphCount; ++i) {
artery_font::Glyph<REAL> &glyph = fontVariant.glyphs[i];
glyph.codepoint = glyphs[i].getIdentifier(properties.glyphIdentifierType);
fontVariant.metrics.fontSize = REAL(properties.fontSize*fontMetrics.emSize);
if (properties.imageType != ImageType::HARD_MASK) {
fontVariant.metrics.distanceRange = REAL(properties.pxRange.upper-properties.pxRange.lower);
fontVariant.metrics.distanceRangeMiddle = REAL(.5*(properties.pxRange.lower+properties.pxRange.upper));
}
fontVariant.metrics.emSize = REAL(fontMetrics.emSize);
fontVariant.metrics.ascender = REAL(fontMetrics.ascenderY);
fontVariant.metrics.descender = REAL(fontMetrics.descenderY);
fontVariant.metrics.lineHeight = REAL(fontMetrics.lineHeight);
fontVariant.metrics.underlineY = REAL(fontMetrics.underlineY);
fontVariant.metrics.underlineThickness = REAL(fontMetrics.underlineThickness);
const char *name = font.getName();
if (name)
(std::string &) fontVariant.name = name;
fontVariant.glyphs = artery_font::StdList<artery_font::Glyph<REAL> >(font.getGlyphs().size());
int j = 0;
for (const GlyphGeometry &glyphGeom : font.getGlyphs()) {
artery_font::Glyph<REAL> &glyph = fontVariant.glyphs[j++];
glyph.codepoint = glyphGeom.getIdentifier(identifierType);
glyph.image = 0;
double l, b, r, t;
glyphs[i].getQuadPlaneBounds(l, b, r, t);
glyph.planeBounds.l = REAL(fsScale*l);
glyph.planeBounds.b = REAL(fsScale*b);
glyph.planeBounds.r = REAL(fsScale*r);
glyph.planeBounds.t = REAL(fsScale*t);
glyphs[i].getQuadAtlasBounds(l, b, r, t);
glyphGeom.getQuadPlaneBounds(l, b, r, t);
glyph.planeBounds.l = REAL(l);
glyph.planeBounds.b = REAL(b);
glyph.planeBounds.r = REAL(r);
glyph.planeBounds.t = REAL(t);
glyphGeom.getQuadAtlasBounds(l, b, r, t);
glyph.imageBounds.l = REAL(l);
glyph.imageBounds.b = REAL(b);
glyph.imageBounds.r = REAL(r);
glyph.imageBounds.t = REAL(t);
glyph.advance.h = REAL(fsScale*glyphs[i].getAdvance());
glyph.advance.h = REAL(glyphGeom.getAdvance());
glyph.advance.v = REAL(0);
for (int j = 0; j < glyphCount; ++j) {
double kerning;
if (msdfgen::getKerning(kerning, font, glyphs[i].getGlyphIndex(), glyphs[j].getGlyphIndex()) && kerning) {
artery_font::KernPair<REAL> kernPair = { };
kernPair.codepoint1 = glyphs[i].getIdentifier(properties.glyphIdentifierType);
kernPair.codepoint2 = glyphs[j].getIdentifier(properties.glyphIdentifierType);
kernPair.advance.h = REAL(fsScale*kerning);
fontVariant.kernPairs.vector.push_back((artery_font::KernPair<REAL> &&) kernPair);
}
}
}
arfont.variants.vector.push_back((artery_font::StdFontVariant<REAL> &&) fontVariant);
switch (identifierType) {
case GlyphIdentifierType::GLYPH_INDEX:
for (const std::pair<std::pair<int, int>, double> &elem : font.getKerning()) {
artery_font::KernPair<REAL> kernPair = { };
kernPair.codepoint1 = elem.first.first;
kernPair.codepoint2 = elem.first.second;
kernPair.advance.h = REAL(elem.second);
((std::vector<artery_font::KernPair<REAL> > &) fontVariant.kernPairs).push_back((artery_font::KernPair<REAL> &&) kernPair);
}
break;
case GlyphIdentifierType::UNICODE_CODEPOINT:
for (const std::pair<std::pair<int, int>, double> &elem : font.getKerning()) {
const GlyphGeometry *glyph1 = font.getGlyph(msdfgen::GlyphIndex(elem.first.first));
const GlyphGeometry *glyph2 = font.getGlyph(msdfgen::GlyphIndex(elem.first.second));
if (glyph1 && glyph2 && glyph1->getCodepoint() && glyph2->getCodepoint()) {
artery_font::KernPair<REAL> kernPair = { };
kernPair.codepoint1 = glyph1->getCodepoint();
kernPair.codepoint2 = glyph2->getCodepoint();
kernPair.advance.h = REAL(elem.second);
((std::vector<artery_font::KernPair<REAL> > &) fontVariant.kernPairs).push_back((artery_font::KernPair<REAL> &&) kernPair);
}
}
break;
}
}
arfont.images = artery_font::StdList<typename artery_font::StdArteryFont<REAL>::Image>(1);
{
artery_font::StdImage image = { };
typename artery_font::StdArteryFont<REAL>::Image &image = arfont.images[0] = typename artery_font::StdArteryFont<REAL>::Image();
image.width = atlas.width;
image.height = atlas.height;
image.channels = N;
image.imageType = convertImageType(properties.imageType);
switch (properties.imageFormat) {
#ifndef MSDFGEN_DISABLE_PNG
case ImageFormat::PNG:
image.encoding = artery_font::IMAGE_PNG;
image.pixelFormat = artery_font::PIXEL_UNSIGNED8;
if (!encodePng(image.data.vector, atlas))
if (!encodePng((std::vector<byte> &) image.data, atlas))
return false;
break;
#endif
case ImageFormat::TIFF:
image.encoding = artery_font::IMAGE_TIFF;
image.pixelFormat = artery_font::PIXEL_FLOAT32;
if (!encodeTiff(image.data.vector, atlas))
if (!encodeTiff((std::vector<byte> &) image.data, atlas))
return false;
break;
case ImageFormat::BINARY:
@ -136,24 +161,38 @@ bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, in
return false;
image.encoding = artery_font::IMAGE_RAW_BINARY;
image.rawBinaryFormat.rowLength = N*sizeof(T)*atlas.width;
image.rawBinaryFormat.orientation = artery_font::ORIENTATION_BOTTOM_UP;
image.data = artery_font::StdByteArray(N*sizeof(T)*atlas.width*atlas.height);
memcpy((byte *) image.data, atlas.pixels, N*sizeof(T)*atlas.width*atlas.height);
switch (properties.yDirection) {
case YDirection::BOTTOM_UP:
image.rawBinaryFormat.orientation = artery_font::ORIENTATION_BOTTOM_UP;
memcpy((byte *) image.data, atlas.pixels, N*sizeof(T)*atlas.width*atlas.height);
break;
case YDirection::TOP_DOWN: {
image.rawBinaryFormat.orientation = artery_font::ORIENTATION_TOP_DOWN;
byte *imageData = (byte *) image.data;
for (int y = atlas.height-1; y >= 0; --y) {
memcpy(imageData, atlas.pixels+N*atlas.width*y, N*sizeof(T)*atlas.width);
imageData += N*sizeof(T)*atlas.width;
}
break;
}
}
break;
default:
return false;
}
arfont.images.vector.push_back((artery_font::StdImage &&) image);
}
return artery_font::writeFile(arfont, filename);
}
template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef<byte, 1> &atlas, const char *filename, const ArteryFontExportProperties &properties);
template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef<byte, 3> &atlas, const char *filename, const ArteryFontExportProperties &properties);
template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef<byte, 4> &atlas, const char *filename, const ArteryFontExportProperties &properties);
template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef<float, 1> &atlas, const char *filename, const ArteryFontExportProperties &properties);
template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef<float, 3> &atlas, const char *filename, const ArteryFontExportProperties &properties);
template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef<float, 4> &atlas, const char *filename, const ArteryFontExportProperties &properties);
template bool exportArteryFont<float>(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef<byte, 1> &atlas, const char *filename, const ArteryFontExportProperties &properties);
template bool exportArteryFont<float>(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef<byte, 3> &atlas, const char *filename, const ArteryFontExportProperties &properties);
template bool exportArteryFont<float>(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef<byte, 4> &atlas, const char *filename, const ArteryFontExportProperties &properties);
template bool exportArteryFont<float>(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef<float, 1> &atlas, const char *filename, const ArteryFontExportProperties &properties);
template bool exportArteryFont<float>(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef<float, 3> &atlas, const char *filename, const ArteryFontExportProperties &properties);
template bool exportArteryFont<float>(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef<float, 4> &atlas, const char *filename, const ArteryFontExportProperties &properties);
}
#endif

View File

@ -1,23 +1,27 @@
#pragma once
#ifndef MSDF_ATLAS_NO_ARTERY_FONT
#include <msdfgen.h>
#include <msdfgen-ext.h>
#include "types.h"
#include "GlyphGeometry.h"
#include "FontGeometry.h"
namespace msdf_atlas {
struct ArteryFontExportProperties {
GlyphIdentifierType glyphIdentifierType;
double fontSize;
double pxRange;
msdfgen::Range pxRange;
ImageType imageType;
ImageFormat imageFormat;
YDirection yDirection;
};
/// Encodes the atlas bitmap and its layout into an Artery Atlas Font file
template <typename REAL, typename T, int N>
bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, const msdfgen::BitmapConstRef<T, N> &atlas, const char *filename, const ArteryFontExportProperties &properties);
bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef<T, N> &atlas, const char *filename, const ArteryFontExportProperties &properties);
}
#endif

View File

@ -2,11 +2,22 @@
#include "bitmap-blit.h"
#include <cstring>
#include <algorithm>
namespace msdf_atlas {
#define BOUND_AREA() { \
if (dx < 0) w += dx, sx -= dx, dx = 0; \
if (dy < 0) h += dy, sy -= dy, dy = 0; \
if (sx < 0) w += sx, dx -= sx, sx = 0; \
if (sy < 0) h += sy, dy -= sy, sy = 0; \
w = std::max(0, std::min(w, std::min(dst.width-dx, src.width-sx))); \
h = std::max(0, std::min(h, std::min(dst.height-dy, src.height-sy))); \
}
template <typename T, int N>
void blitSameType(const msdfgen::BitmapRef<T, N> &dst, const msdfgen::BitmapConstRef<T, N> &src, int dx, int dy, int sx, int sy, int w, int h) {
BOUND_AREA();
for (int y = 0; y < h; ++y)
memcpy(dst(dx, dy+y), src(sx, sy+y), sizeof(T)*N*w);
}
@ -21,6 +32,7 @@ BLIT_SAME_TYPE_IMPL(float, 3)
BLIT_SAME_TYPE_IMPL(float, 4)
void blit(const msdfgen::BitmapRef<byte, 1> &dst, const msdfgen::BitmapConstRef<float, 1> &src, int dx, int dy, int sx, int sy, int w, int h) {
BOUND_AREA();
for (int y = 0; y < h; ++y) {
byte *dstPixel = dst(dx, dy+y);
for (int x = 0; x < w; ++x) {
@ -31,6 +43,7 @@ void blit(const msdfgen::BitmapRef<byte, 1> &dst, const msdfgen::BitmapConstRef<
}
void blit(const msdfgen::BitmapRef<byte, 3> &dst, const msdfgen::BitmapConstRef<float, 3> &src, int dx, int dy, int sx, int sy, int w, int h) {
BOUND_AREA();
for (int y = 0; y < h; ++y) {
byte *dstPixel = dst(dx, dy+y);
for (int x = 0; x < w; ++x) {
@ -43,6 +56,7 @@ void blit(const msdfgen::BitmapRef<byte, 3> &dst, const msdfgen::BitmapConstRef<
}
void blit(const msdfgen::BitmapRef<byte, 4> &dst, const msdfgen::BitmapConstRef<float, 4> &src, int dx, int dy, int sx, int sy, int w, int h) {
BOUND_AREA();
for (int y = 0; y < h; ++y) {
byte *dstPixel = dst(dx, dy+y);
for (int x = 0; x < w; ++x) {

View File

@ -8,7 +8,6 @@ namespace msdf_atlas {
/*
* Copies a rectangular section from source bitmap to destination bitmap.
* Width and height are not checked and must not exceed bitmap bounds!
*/
void blit(const msdfgen::BitmapRef<byte, 1> &dst, const msdfgen::BitmapConstRef<byte, 1> &src, int dx, int dy, int sx, int sy, int w, int h);

View File

@ -25,36 +25,6 @@ static char escapedChar(char c) {
}
}
static int readWord(std::string &str, FILE *f) {
while (true) {
int c = fgetc(f);
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_')
str.push_back((char) c);
else
return c;
}
}
static bool readString(std::string &str, FILE *f, char terminator) {
bool escape = false;
while (true) {
int c = fgetc(f);
if (c < 0)
return false;
if (escape) {
str.push_back(escapedChar((char) c));
escape = false;
} else {
if (c == terminator)
return true;
else if (c == '\\')
escape = true;
else
str.push_back((char) c);
}
}
}
static bool parseInt(int &i, const char *str) {
i = 0;
if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { // hex
@ -84,6 +54,181 @@ static bool parseInt(int &i, const char *str) {
return true;
}
template <int (READ_CHAR)(void *)>
static int readWord(void *userData, std::string &str) {
while (true) {
int c = READ_CHAR(userData);
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_')
str.push_back((char) c);
else
return c;
}
}
template <int (READ_CHAR)(void *)>
static bool readString(void *userData, std::string &str, char terminator) {
bool escape = false;
while (true) {
int c = READ_CHAR(userData);
if (c < 0)
return false;
if (escape) {
str.push_back(escapedChar((char) c));
escape = false;
} else {
if (c == terminator)
return true;
else if (c == '\\')
escape = true;
else
str.push_back((char) c);
}
}
}
template <int (READ_CHAR)(void *), void (ADD)(void *, unicode_t), bool (INCLUDE)(void *, const std::string &)>
static bool charsetParse(void *userData, bool disableCharLiterals, bool disableInclude) {
enum {
CLEAR,
TIGHT,
RANGE_BRACKET,
RANGE_START,
RANGE_SEPARATOR,
RANGE_END
} state = CLEAR;
std::string buffer;
std::vector<unicode_t> unicodeBuffer;
unicode_t rangeStart = 0;
for (int c = READ_CHAR(userData), start = true; c >= 0; start = false) {
switch (c) {
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // number
if (!(state == CLEAR || state == RANGE_BRACKET || state == RANGE_SEPARATOR))
return false;
buffer.push_back((char) c);
c = readWord<READ_CHAR>(userData, buffer);
{
int cp;
if (!parseInt(cp, buffer.c_str()))
return false;
switch (state) {
case CLEAR:
if (cp >= 0)
ADD(userData, (unicode_t) cp);
state = TIGHT;
break;
case RANGE_BRACKET:
rangeStart = (unicode_t) cp;
state = RANGE_START;
break;
case RANGE_SEPARATOR:
for (unicode_t u = rangeStart; (int) u <= cp; ++u)
ADD(userData, u);
state = RANGE_END;
break;
default:;
}
}
buffer.clear();
continue; // next character already read
case '\'': // single UTF-8 character
if (!(state == CLEAR || state == RANGE_BRACKET || state == RANGE_SEPARATOR) || disableCharLiterals)
return false;
if (!readString<READ_CHAR>(userData, buffer, '\''))
return false;
utf8Decode(unicodeBuffer, buffer.c_str());
if (unicodeBuffer.size() == 1) {
switch (state) {
case CLEAR:
if (unicodeBuffer[0] > 0)
ADD(userData, unicodeBuffer[0]);
state = TIGHT;
break;
case RANGE_BRACKET:
rangeStart = unicodeBuffer[0];
state = RANGE_START;
break;
case RANGE_SEPARATOR:
for (unicode_t u = rangeStart; u <= unicodeBuffer[0]; ++u)
ADD(userData, u);
state = RANGE_END;
break;
default:;
}
} else
return false;
unicodeBuffer.clear();
buffer.clear();
break;
case '"': // string of UTF-8 characters
if (state != CLEAR || disableCharLiterals)
return false;
if (!readString<READ_CHAR>(userData, buffer, '"'))
return false;
utf8Decode(unicodeBuffer, buffer.c_str());
for (unicode_t cp : unicodeBuffer)
ADD(userData, cp);
unicodeBuffer.clear();
buffer.clear();
state = TIGHT;
break;
case '[': // character range start
if (state != CLEAR)
return false;
state = RANGE_BRACKET;
break;
case ']': // character range end
if (state == RANGE_END)
state = TIGHT;
else
return false;
break;
case '@': // annotation
if (state != CLEAR)
return false;
c = readWord<READ_CHAR>(userData, buffer);
if (buffer == "include") {
while (c == ' ' || c == '\t' || c == '\n' || c == '\r')
c = READ_CHAR(userData);
if (c != '"')
return false;
buffer.clear();
if (!readString<READ_CHAR>(userData, buffer, '"'))
return false;
INCLUDE(userData, buffer);
state = TIGHT;
} else
return false;
buffer.clear();
break;
case ',': case ';': // separator
if (!(state == CLEAR || state == TIGHT)) {
if (state == RANGE_START)
state = RANGE_SEPARATOR;
else
return false;
} // else treat as whitespace
// fallthrough
case ' ': case '\n': case '\r': case '\t': // whitespace
if (state == TIGHT)
state = CLEAR;
break;
case 0xef: // UTF-8 byte order mark
if (start) {
if (!(READ_CHAR(userData) == 0xbb && READ_CHAR(userData) == 0xbf))
return false;
break;
}
default: // unexpected character
return false;
}
c = READ_CHAR(userData);
}
return state == CLEAR || state == TIGHT;
}
static std::string combinePath(const char *basePath, const char *relPath) {
if (relPath[0] == '/' || (relPath[0] && relPath[1] == ':')) // absolute path?
return relPath;
@ -96,155 +241,57 @@ static std::string combinePath(const char *basePath, const char *relPath) {
return std::string(basePath, lastSlash+1)+relPath;
}
bool Charset::load(const char *filename, bool disableCharLiterals) {
struct CharsetLoadData {
Charset *charset;
const char *filename;
bool disableCharLiterals;
FILE *file;
if (FILE *f = fopen(filename, "rb")) {
enum {
CLEAR,
TIGHT,
RANGE_BRACKET,
RANGE_START,
RANGE_SEPARATOR,
RANGE_END
} state = CLEAR;
std::string buffer;
std::vector<unicode_t> unicodeBuffer;
unicode_t rangeStart = 0;
for (int c = fgetc(f), start = true; c >= 0; start = false) {
switch (c) {
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // number
if (!(state == CLEAR || state == RANGE_BRACKET || state == RANGE_SEPARATOR))
goto FAIL;
buffer.push_back((char) c);
c = readWord(buffer, f);
{
int cp;
if (!parseInt(cp, buffer.c_str()))
goto FAIL;
switch (state) {
case CLEAR:
if (cp >= 0)
add((unicode_t) cp);
state = TIGHT;
break;
case RANGE_BRACKET:
rangeStart = (unicode_t) cp;
state = RANGE_START;
break;
case RANGE_SEPARATOR:
for (unicode_t u = rangeStart; (int) u <= cp; ++u)
add(u);
state = RANGE_END;
break;
default:;
}
}
buffer.clear();
continue; // next character already read
case '\'': // single UTF-8 character
if (!(state == CLEAR || state == RANGE_BRACKET || state == RANGE_SEPARATOR) || disableCharLiterals)
goto FAIL;
if (!readString(buffer, f, '\''))
goto FAIL;
utf8Decode(unicodeBuffer, buffer.c_str());
if (unicodeBuffer.size() == 1) {
switch (state) {
case CLEAR:
if (unicodeBuffer[0] > 0)
add(unicodeBuffer[0]);
state = TIGHT;
break;
case RANGE_BRACKET:
rangeStart = unicodeBuffer[0];
state = RANGE_START;
break;
case RANGE_SEPARATOR:
for (unicode_t u = rangeStart; u <= unicodeBuffer[0]; ++u)
add(u);
state = RANGE_END;
break;
default:;
}
} else
goto FAIL;
unicodeBuffer.clear();
buffer.clear();
break;
case '"': // string of UTF-8 characters
if (state != CLEAR || disableCharLiterals)
goto FAIL;
if (!readString(buffer, f, '"'))
goto FAIL;
utf8Decode(unicodeBuffer, buffer.c_str());
for (unicode_t cp : unicodeBuffer)
add(cp);
unicodeBuffer.clear();
buffer.clear();
state = TIGHT;
break;
case '[': // character range start
if (state != CLEAR)
goto FAIL;
state = RANGE_BRACKET;
break;
case ']': // character range end
if (state == RANGE_END)
state = TIGHT;
else
goto FAIL;
break;
case '@': // annotation
if (state != CLEAR)
goto FAIL;
c = readWord(buffer, f);
if (buffer == "include") {
while (c == ' ' || c == '\t' || c == '\n' || c == '\r')
c = fgetc(f);
if (c != '"')
goto FAIL;
buffer.clear();
if (!readString(buffer, f, '"'))
goto FAIL;
load(combinePath(filename, buffer.c_str()).c_str());
state = TIGHT;
} else
goto FAIL;
buffer.clear();
break;
case ',': case ';': // separator
if (!(state == CLEAR || state == TIGHT)) {
if (state == RANGE_START)
state = RANGE_SEPARATOR;
else
goto FAIL;
} // else treat as whitespace
case ' ': case '\n': case '\r': case '\t': // whitespace
if (state == TIGHT)
state = CLEAR;
break;
case 0xef: // UTF-8 byte order mark
if (start) {
if (!(fgetc(f) == 0xbb && fgetc(f) == 0xbf))
goto FAIL;
break;
}
default: // unexpected character
goto FAIL;
}
c = fgetc(f);
}
fclose(f);
return state == CLEAR || state == TIGHT;
FAIL:
fclose(f);
return false;
static int readChar(void *userData) {
return fgetc(reinterpret_cast<CharsetLoadData *>(userData)->file);
}
static void add(void *userData, unicode_t cp) {
reinterpret_cast<CharsetLoadData *>(userData)->charset->add(cp);
}
static bool include(void *userData, const std::string &path) {
const CharsetLoadData &ud = *reinterpret_cast<CharsetLoadData *>(userData);
return ud.charset->load(combinePath(ud.filename, path.c_str()).c_str(), ud.disableCharLiterals);
}
};
bool Charset::load(const char *filename, bool disableCharLiterals) {
if (FILE *f = fopen(filename, "rb")) {
CharsetLoadData userData = { this, filename, disableCharLiterals, f };
bool success = charsetParse<CharsetLoadData::readChar, CharsetLoadData::add, CharsetLoadData::include>(&userData, disableCharLiterals, false);
fclose(f);
return success;
}
return false;
}
struct CharsetParseData {
Charset *charset;
const char *cur, *end;
static int readChar(void *userData) {
CharsetParseData &ud = *reinterpret_cast<CharsetParseData *>(userData);
return ud.cur < ud.end ? (int) (unsigned char) *ud.cur++ : -1;
}
static void add(void *userData, unicode_t cp) {
reinterpret_cast<CharsetParseData *>(userData)->charset->add(cp);
}
static bool include(void *, const std::string &) {
return false;
}
};
bool Charset::parse(const char *str, size_t strLength, bool disableCharLiterals) {
CharsetParseData userData = { this, str, str+strLength };
return charsetParse<CharsetParseData::readChar, CharsetParseData::add, CharsetParseData::include>(&userData, disableCharLiterals, true);
}
}

View File

@ -2,22 +2,40 @@
#include "csv-export.h"
#include <cstdio>
#include "GlyphGeometry.h"
namespace msdf_atlas {
bool exportCSV(const GlyphGeometry *glyphs, int glyphCount, GlyphIdentifierType glyphIdentifierType, double emSize, const char *filename) {
bool exportCSV(const FontGeometry *fonts, int fontCount, int atlasWidth, int atlasHeight, YDirection yDirection, const char *filename) {
FILE *f = fopen(filename, "w");
if (!f)
return false;
double fsScale = 1/emSize;
for (int i = 0; i < glyphCount; ++i) {
double l, b, r, t;
fprintf(f, "%d,%.17g,", glyphs[i].getIdentifier(glyphIdentifierType), fsScale*glyphs[i].getAdvance());
glyphs[i].getQuadPlaneBounds(l, b, r, t);
fprintf(f, "%.17g,%.17g,%.17g,%.17g,", fsScale*l, fsScale*b, fsScale*r, fsScale*t);
glyphs[i].getQuadAtlasBounds(l, b, r, t);
fprintf(f, "%.17g,%.17g,%.17g,%.17g\n", l, b, r, t);
for (int i = 0; i < fontCount; ++i) {
for (const GlyphGeometry &glyph : fonts[i].getGlyphs()) {
double l, b, r, t;
if (fontCount > 1)
fprintf(f, "%d,", i);
fprintf(f, "%d,%.17g,", glyph.getIdentifier(fonts[i].getPreferredIdentifierType()), glyph.getAdvance());
glyph.getQuadPlaneBounds(l, b, r, t);
switch (yDirection) {
case YDirection::BOTTOM_UP:
fprintf(f, "%.17g,%.17g,%.17g,%.17g,", l, b, r, t);
break;
case YDirection::TOP_DOWN:
fprintf(f, "%.17g,%.17g,%.17g,%.17g,", l, -t, r, -b);
break;
}
glyph.getQuadAtlasBounds(l, b, r, t);
switch (yDirection) {
case YDirection::BOTTOM_UP:
fprintf(f, "%.17g,%.17g,%.17g,%.17g\n", l, b, r, t);
break;
case YDirection::TOP_DOWN:
fprintf(f, "%.17g,%.17g,%.17g,%.17g\n", l, atlasHeight-t, r, atlasHeight-b);
break;
}
}
}
fclose(f);

View File

@ -1,15 +1,14 @@
#pragma once
#include "types.h"
#include "GlyphGeometry.h"
#include "FontGeometry.h"
namespace msdf_atlas {
/**
* Writes the positioning data and atlas layout of the glyphs into a CSV file
* The columns are: glyph identifier (index or Unicode), horizontal advance, plane bounds (l, b, r, t), atlas bounds (l, b, r, t)
* The columns are: font variant index (if fontCount > 1), glyph identifier (index or Unicode), horizontal advance, plane bounds (l, b, r, t), atlas bounds (l, b, r, t)
*/
bool exportCSV(const GlyphGeometry *glyphs, int glyphCount, GlyphIdentifierType glyphIdentifierType, double emSize, const char *filename);
bool exportCSV(const FontGeometry *fonts, int fontCount, int atlasWidth, int atlasHeight, YDirection yDirection, const char *filename);
}

View File

@ -8,32 +8,44 @@ void scanlineGenerator(const msdfgen::BitmapRef<float, 1> &output, const GlyphGe
}
void sdfGenerator(const msdfgen::BitmapRef<float, 1> &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) {
msdfgen::generateSDF(output, glyph.getShape(), glyph.getBoxRange(), glyph.getBoxScale(), glyph.getBoxTranslate(), attribs.overlapSupport);
msdfgen::generateSDF(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), attribs.config);
if (attribs.scanlinePass)
msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxScale(), glyph.getBoxTranslate(), MSDF_ATLAS_GLYPH_FILL_RULE);
msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxProjection(), MSDF_ATLAS_GLYPH_FILL_RULE);
}
void psdfGenerator(const msdfgen::BitmapRef<float, 1> &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) {
msdfgen::generatePseudoSDF(output, glyph.getShape(), glyph.getBoxRange(), glyph.getBoxScale(), glyph.getBoxTranslate(), attribs.overlapSupport);
msdfgen::generatePSDF(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), attribs.config);
if (attribs.scanlinePass)
msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxScale(), glyph.getBoxTranslate(), MSDF_ATLAS_GLYPH_FILL_RULE);
msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxProjection(), MSDF_ATLAS_GLYPH_FILL_RULE);
}
void msdfGenerator(const msdfgen::BitmapRef<float, 3> &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) {
msdfgen::generateMSDF(output, glyph.getShape(), glyph.getBoxRange(), glyph.getBoxScale(), glyph.getBoxTranslate(), attribs.errorCorrectionThreshold, attribs.overlapSupport);
msdfgen::MSDFGeneratorConfig config = attribs.config;
if (attribs.scanlinePass)
config.errorCorrection.mode = msdfgen::ErrorCorrectionConfig::DISABLED;
msdfgen::generateMSDF(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), config);
if (attribs.scanlinePass) {
msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxScale(), glyph.getBoxTranslate(), MSDF_ATLAS_GLYPH_FILL_RULE);
if (attribs.errorCorrectionThreshold > 0)
msdfgen::msdfErrorCorrection(output, attribs.errorCorrectionThreshold/(glyph.getBoxScale()*glyph.getBoxRange()));
msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxProjection(), MSDF_ATLAS_GLYPH_FILL_RULE);
if (attribs.config.errorCorrection.mode != msdfgen::ErrorCorrectionConfig::DISABLED) {
config.errorCorrection.mode = attribs.config.errorCorrection.mode;
config.errorCorrection.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
msdfgen::msdfErrorCorrection(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), config);
}
}
}
void mtsdfGenerator(const msdfgen::BitmapRef<float, 4> &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) {
msdfgen::generateMTSDF(output, glyph.getShape(), glyph.getBoxRange(), glyph.getBoxScale(), glyph.getBoxTranslate(), attribs.errorCorrectionThreshold, attribs.overlapSupport);
msdfgen::MSDFGeneratorConfig config = attribs.config;
if (attribs.scanlinePass)
config.errorCorrection.mode = msdfgen::ErrorCorrectionConfig::DISABLED;
msdfgen::generateMTSDF(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), config);
if (attribs.scanlinePass) {
msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxScale(), glyph.getBoxTranslate(), MSDF_ATLAS_GLYPH_FILL_RULE);
if (attribs.errorCorrectionThreshold > 0)
msdfgen::msdfErrorCorrection(output, attribs.errorCorrectionThreshold/(glyph.getBoxScale()*glyph.getBoxRange()));
msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxProjection(), MSDF_ATLAS_GLYPH_FILL_RULE);
if (attribs.config.errorCorrection.mode != msdfgen::ErrorCorrectionConfig::DISABLED) {
config.errorCorrection.mode = attribs.config.errorCorrection.mode;
config.errorCorrection.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
msdfgen::msdfErrorCorrection(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), config);
}
}
}

View File

@ -15,7 +15,7 @@ namespace msdf_atlas {
void scanlineGenerator(const msdfgen::BitmapRef<float, 1> &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs);
/// Generates a true signed distance field of the glyph
void sdfGenerator(const msdfgen::BitmapRef<float, 1> &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs);
/// Generates a signed pseudo-distance field of the glyph
/// Generates a signed perpendicular distance field of the glyph
void psdfGenerator(const msdfgen::BitmapRef<float, 1> &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs);
/// Generates a multi-channel signed distance field of the glyph
void msdfGenerator(const msdfgen::BitmapRef<float, 3> &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs);

View File

@ -1,6 +1,98 @@
#include "image-encode.h"
#include <core/pixel-conversion.hpp>
#ifdef MSDFGEN_USE_LIBPNG
#include <png.h>
namespace msdf_atlas {
class PngGuard {
png_structp png;
png_infop info;
public:
inline PngGuard(png_structp png, png_infop info) : png(png), info(info) { }
inline ~PngGuard() {
png_destroy_write_struct(&png, &info);
}
};
static void pngIgnoreError(png_structp, png_const_charp) { }
static void pngWrite(png_structp png, png_bytep data, png_size_t length) {
std::vector<byte> &output = *reinterpret_cast<std::vector<byte> *>(png_get_io_ptr(png));
output.insert(output.end(), data, data+length);
}
static void pngFlush(png_structp) { }
static bool pngEncode(std::vector<byte> &output, const byte *pixels, int width, int height, int channels, int colorType) {
if (!(pixels && width && height))
return false;
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, &pngIgnoreError, &pngIgnoreError);
if (!png)
return false;
png_infop info = png_create_info_struct(png);
PngGuard guard(png, info);
if (!info)
return false;
std::vector<const byte *> rows(height);
for (int y = 0; y < height; ++y)
rows[y] = pixels+channels*width*(height-y-1);
if (setjmp(png_jmpbuf(png)))
return false;
png_set_write_fn(png, &output, &pngWrite, &pngFlush);
png_set_IHDR(png, info, width, height, 8, colorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_set_compression_level(png, 9);
png_set_rows(png, info, const_cast<png_bytepp>(&rows[0]));
png_write_png(png, info, PNG_TRANSFORM_IDENTITY, NULL);
return true;
}
static bool pngEncode(std::vector<byte> &output, const float *pixels, int width, int height, int channels, int colorType) {
if (!(pixels && width && height))
return false;
int subpixels = channels*width*height;
std::vector<byte> bytePixels(subpixels);
for (int i = 0; i < subpixels; ++i)
bytePixels[i] = msdfgen::pixelFloatToByte(pixels[i]);
return pngEncode(output, bytePixels.data(), width, height, channels, colorType);
}
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<msdfgen::byte, 1> &bitmap) {
return pngEncode(output, bitmap.pixels, bitmap.width, bitmap.height, 1, PNG_COLOR_TYPE_GRAY);
}
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<msdfgen::byte, 3> &bitmap) {
return pngEncode(output, bitmap.pixels, bitmap.width, bitmap.height, 3, PNG_COLOR_TYPE_RGB);
}
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<msdfgen::byte, 4> &bitmap) {
return pngEncode(output, bitmap.pixels, bitmap.width, bitmap.height, 4, PNG_COLOR_TYPE_RGB_ALPHA);
}
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<float, 1> &bitmap) {
return pngEncode(output, bitmap.pixels, bitmap.width, bitmap.height, 1, PNG_COLOR_TYPE_GRAY);
}
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<float, 3> &bitmap) {
return pngEncode(output, bitmap.pixels, bitmap.width, bitmap.height, 3, PNG_COLOR_TYPE_RGB);
}
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<float, 4> &bitmap) {
return pngEncode(output, bitmap.pixels, bitmap.width, bitmap.height, 4, PNG_COLOR_TYPE_RGB_ALPHA);
}
}
#endif
#ifdef MSDFGEN_USE_LODEPNG
#include <lodepng.h>
namespace msdf_atlas {
@ -61,3 +153,5 @@ bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<float, 4
}
}
#endif

View File

@ -5,6 +5,8 @@
#include <msdfgen.h>
#include "types.h"
#ifndef MSDFGEN_DISABLE_PNG
namespace msdf_atlas {
// Functions to encode an image as a sequence of bytes in memory
@ -18,3 +20,5 @@ bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<float, 3
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<float, 4> &bitmap);
}
#endif

View File

@ -8,7 +8,7 @@ namespace msdf_atlas {
/// Saves the bitmap as an image file with the specified format
template <typename T, int N>
bool saveImage(const msdfgen::BitmapConstRef<T, N> &bitmap, ImageFormat format, const char *filename);
bool saveImage(const msdfgen::BitmapConstRef<T, N> &bitmap, ImageFormat format, const char *filename, YDirection outputYDirection = YDirection::BOTTOM_UP);
}

View File

@ -7,32 +7,38 @@
namespace msdf_atlas {
template <int N>
bool saveImageBinary(const msdfgen::BitmapConstRef<byte, N> &bitmap, const char *filename);
bool saveImageBinary(const msdfgen::BitmapConstRef<byte, N> &bitmap, const char *filename, YDirection outputYDirection);
template <int N>
bool saveImageBinaryLE(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename);
bool saveImageBinaryLE(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename, YDirection outputYDirection);
template <int N>
bool saveImageBinaryBE(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename);
bool saveImageBinaryBE(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename, YDirection outputYDirection);
template <int N>
bool saveImageText(const msdfgen::BitmapConstRef<byte, N> &bitmap, const char *filename);
bool saveImageText(const msdfgen::BitmapConstRef<byte, N> &bitmap, const char *filename, YDirection outputYDirection);
template <int N>
bool saveImageText(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename);
bool saveImageText(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename, YDirection outputYDirection);
template <int N>
bool saveImage(const msdfgen::BitmapConstRef<byte, N> &bitmap, ImageFormat format, const char *filename) {
bool saveImage(const msdfgen::BitmapConstRef<byte, N> &bitmap, ImageFormat format, const char *filename, YDirection outputYDirection = YDirection::BOTTOM_UP) {
switch (format) {
#ifndef MSDFGEN_DISABLE_PNG
case ImageFormat::PNG:
return msdfgen::savePng(bitmap, filename);
#endif
case ImageFormat::BMP:
return msdfgen::saveBmp(bitmap, filename);
case ImageFormat::TIFF:
return false;
case ImageFormat::RGBA:
return msdfgen::saveRgba(bitmap, filename);
case ImageFormat::FL32:
return false;
case ImageFormat::TEXT:
return saveImageText(bitmap, filename);
return saveImageText(bitmap, filename, outputYDirection);
case ImageFormat::TEXT_FLOAT:
return false;
case ImageFormat::BINARY:
return saveImageBinary(bitmap, filename);
return saveImageBinary(bitmap, filename, outputYDirection);
case ImageFormat::BINARY_FLOAT:
case ImageFormat::BINARY_FLOAT_BE:
return false;
@ -42,34 +48,50 @@ bool saveImage(const msdfgen::BitmapConstRef<byte, N> &bitmap, ImageFormat forma
}
template <int N>
bool saveImage(const msdfgen::BitmapConstRef<float, N> &bitmap, ImageFormat format, const char *filename) {
bool saveImage(const msdfgen::BitmapConstRef<float, N> &bitmap, ImageFormat format, const char *filename, YDirection outputYDirection = YDirection::BOTTOM_UP) {
switch (format) {
#ifndef MSDFGEN_DISABLE_PNG
case ImageFormat::PNG:
return msdfgen::savePng(bitmap, filename);
#endif
case ImageFormat::BMP:
return msdfgen::saveBmp(bitmap, filename);
case ImageFormat::TIFF:
return msdfgen::saveTiff(bitmap, filename);
case ImageFormat::RGBA:
return msdfgen::saveRgba(bitmap, filename);
case ImageFormat::FL32:
return msdfgen::saveFl32(bitmap, filename);
case ImageFormat::TEXT:
return false;
case ImageFormat::TEXT_FLOAT:
return saveImageText(bitmap, filename);
return saveImageText(bitmap, filename, outputYDirection);
case ImageFormat::BINARY:
return false;
case ImageFormat::BINARY_FLOAT:
return saveImageBinaryLE(bitmap, filename);
return saveImageBinaryLE(bitmap, filename, outputYDirection);
case ImageFormat::BINARY_FLOAT_BE:
return saveImageBinaryBE(bitmap, filename);
return saveImageBinaryBE(bitmap, filename, outputYDirection);
default:;
}
return false;
}
template <int N>
bool saveImageBinary(const msdfgen::BitmapConstRef<byte, N> &bitmap, const char *filename) {
bool saveImageBinary(const msdfgen::BitmapConstRef<byte, N> &bitmap, const char *filename, YDirection outputYDirection) {
bool success = false;
if (FILE *f = fopen(filename, "wb")) {
success = fwrite(bitmap.pixels, 1, N*bitmap.width*bitmap.height, f) == N*bitmap.width*bitmap.height;
size_t written = 0;
switch (outputYDirection) {
case YDirection::BOTTOM_UP:
written = fwrite(bitmap.pixels, 1, (size_t) N*bitmap.width*bitmap.height, f);
break;
case YDirection::TOP_DOWN:
for (int y = bitmap.height-1; y >= 0; --y)
written += fwrite(bitmap.pixels+(size_t) N*bitmap.width*y, 1, (size_t) N*bitmap.width, f);
break;
}
success = written == (size_t) N*bitmap.width*bitmap.height;
fclose(f);
}
return success;
@ -82,10 +104,20 @@ bool
#else
saveImageBinaryLE
#endif
(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename) {
(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename, YDirection outputYDirection) {
bool success = false;
if (FILE *f = fopen(filename, "wb")) {
success = fwrite(bitmap.pixels, sizeof(float), N*bitmap.width*bitmap.height, f) == N*bitmap.width*bitmap.height;
size_t written = 0;
switch (outputYDirection) {
case YDirection::BOTTOM_UP:
written = fwrite(bitmap.pixels, sizeof(float), (size_t) N*bitmap.width*bitmap.height, f);
break;
case YDirection::TOP_DOWN:
for (int y = bitmap.height-1; y >= 0; --y)
written += fwrite(bitmap.pixels+(size_t) N*bitmap.width*y, sizeof(float), (size_t) N*bitmap.width, f);
break;
}
success = written == (size_t) N*bitmap.width*bitmap.height;
fclose(f);
}
return success;
@ -98,18 +130,19 @@ bool
#else
saveImageBinaryBE
#endif
(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename) {
(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename, YDirection outputYDirection) {
bool success = false;
if (FILE *f = fopen(filename, "wb")) {
const float *p = bitmap.pixels;
int count = N*bitmap.width*bitmap.height;
int written = 0;
for (int i = 0; i < count; ++i) {
const unsigned char *b = reinterpret_cast<const unsigned char *>(p++);
for (int i = sizeof(float)-1; i >= 0; --i)
written += fwrite(b+i, 1, 1, f);
size_t written = 0;
for (int y = 0; y < bitmap.height; ++y) {
const float *p = bitmap.pixels+(size_t) N*bitmap.width*(outputYDirection == YDirection::TOP_DOWN ? bitmap.height-y-1 : y);
for (int x = 0; x < bitmap.width; ++x) {
const unsigned char *b = reinterpret_cast<const unsigned char *>(p++);
for (int i = sizeof(float)-1; i >= 0; --i)
written += fwrite(b+i, 1, 1, f);
}
}
success = written == sizeof(float)*count;
success = written == sizeof(float)*N*bitmap.width*bitmap.height;
fclose(f);
}
return success;
@ -117,15 +150,15 @@ bool
template <int N>
bool saveImageText(const msdfgen::BitmapConstRef<byte, N> &bitmap, const char *filename) {
bool saveImageText(const msdfgen::BitmapConstRef<byte, N> &bitmap, const char *filename, YDirection outputYDirection) {
bool success = false;
if (FILE *f = fopen(filename, "wb")) {
const byte *p = bitmap.pixels;
success = true;
for (int y = 0; y < bitmap.height; ++y) {
for (int x = 0; x < N*bitmap.width; ++x) {
fprintf(f, x ? " %02X" : "%02X", (unsigned) *p++);
}
fprintf(f, "\n");
const byte *p = bitmap.pixels+(size_t) N*bitmap.width*(outputYDirection == YDirection::TOP_DOWN ? bitmap.height-y-1 : y);
for (int x = 0; x < N*bitmap.width; ++x)
success &= fprintf(f, x ? " %02X" : "%02X", (unsigned) *p++) > 0;
success &= fprintf(f, "\n") > 0;
}
fclose(f);
}
@ -133,15 +166,15 @@ bool saveImageText(const msdfgen::BitmapConstRef<byte, N> &bitmap, const char *f
}
template <int N>
bool saveImageText(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename) {
bool saveImageText(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename, YDirection outputYDirection) {
bool success = false;
if (FILE *f = fopen(filename, "wb")) {
const float *p = bitmap.pixels;
success = true;
for (int y = 0; y < bitmap.height; ++y) {
for (int x = 0; x < N*bitmap.width; ++x) {
fprintf(f, x ? " %g" : "%g", *p++);
}
fprintf(f, "\n");
const float *p = bitmap.pixels+(size_t) N*bitmap.width*(outputYDirection == YDirection::TOP_DOWN ? bitmap.height-y-1 : y);
for (int x = 0; x < N*bitmap.width; ++x)
success &= fprintf(f, x ? " %g" : "%g", *p++) > 0;
success &= fprintf(f, "\n") > 0;
}
fclose(f);
}

View File

@ -1,9 +1,46 @@
#include "json-export.h"
#include <string>
#include "GlyphGeometry.h"
namespace msdf_atlas {
static const char * imageTypeString(ImageType type) {
static std::string escapeJsonString(const char *str) {
char uval[7] = "\\u0000";
std::string outStr;
while (*str) {
switch (*str) {
case '\\':
outStr += "\\\\";
break;
case '"':
outStr += "\\\"";
break;
case '\n':
outStr += "\\n";
break;
case '\r':
outStr += "\\r";
break;
case '\t':
outStr += "\\t";
break;
case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: /* \\t */ /* \\n */ case 0x0b: case 0x0c: /* \\r */ case 0x0e: case 0x0f:
case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f:
uval[4] = '0'+(*str >= 0x10);
uval[5] = "0123456789abcdef"[*str&0x0f];
outStr += uval;
break;
default:
outStr.push_back(*str);
}
++str;
}
return outStr;
}
static const char *imageTypeString(ImageType type) {
switch (type) {
case ImageType::HARD_MASK:
return "hardmask";
@ -21,12 +58,7 @@ static const char * imageTypeString(ImageType type) {
return nullptr;
}
bool exportJSON(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, GlyphIdentifierType glyphIdentifierType, double fontSize, double pxRange, int atlasWidth, int atlasHeight, ImageType imageType, const char *filename) {
msdfgen::FontMetrics fontMetrics;
if (!msdfgen::getFontMetrics(fontMetrics, font))
return false;
double fsScale = 1/fontMetrics.emSize;
bool exportJSON(const FontGeometry *fonts, int fontCount, ImageType imageType, const JsonAtlasMetrics &metrics, const char *filename, bool kerning) {
FILE *f = fopen(filename, "w");
if (!f)
return false;
@ -35,64 +67,138 @@ bool exportJSON(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyp
// Atlas properties
fputs("\"atlas\":{", f); {
fprintf(f, "\"type\":\"%s\",", imageTypeString(imageType));
if (imageType == ImageType::SDF || imageType == ImageType::PSDF || imageType == ImageType::MSDF || imageType == ImageType::MTSDF)
fprintf(f, "\"distanceRange\":%.17g,", pxRange);
fprintf(f, "\"size\":%.17g,", fontSize);
fprintf(f, "\"width\":%d,", atlasWidth);
fprintf(f, "\"height\":%d,", atlasHeight);
fputs("\"yOrigin\":\"bottom\"", f);
} fputs("},", f);
// Font metrics
fputs("\"metrics\":{", f); {
fprintf(f, "\"lineHeight\":%.17g,", fsScale*fontMetrics.lineHeight);
fprintf(f, "\"ascender\":%.17g,", fsScale*fontMetrics.ascenderY);
fprintf(f, "\"descender\":%.17g,", fsScale*fontMetrics.descenderY);
fprintf(f, "\"underlineY\":%.17g,", fsScale*fontMetrics.underlineY);
fprintf(f, "\"underlineThickness\":%.17g", fsScale*fontMetrics.underlineThickness);
} fputs("},", f);
// Glyph mapping
fputs("\"glyphs\":[", f);
for (int i = 0; i < glyphCount; ++i) {
fputs(i == 0 ? "{" : ",{", f);
switch (glyphIdentifierType) {
case GlyphIdentifierType::GLYPH_INDEX:
fprintf(f, "\"index\":%d,", glyphs[i].getIndex());
break;
case GlyphIdentifierType::UNICODE_CODEPOINT:
fprintf(f, "\"unicode\":%u,", glyphs[i].getCodepoint());
break;
if (imageType == ImageType::SDF || imageType == ImageType::PSDF || imageType == ImageType::MSDF || imageType == ImageType::MTSDF) {
fprintf(f, "\"distanceRange\":%.17g,", metrics.distanceRange.upper-metrics.distanceRange.lower);
fprintf(f, "\"distanceRangeMiddle\":%.17g,", .5*(metrics.distanceRange.lower+metrics.distanceRange.upper));
}
fprintf(f, "\"advance\":%.17g", fsScale*glyphs[i].getAdvance());
double l, b, r, t;
glyphs[i].getQuadPlaneBounds(l, b, r, t);
if (l || b || r || t)
fprintf(f, ",\"planeBounds\":{\"left\":%.17g,\"bottom\":%.17g,\"right\":%.17g,\"top\":%.17g}", fsScale*l, fsScale*b, fsScale*r, fsScale*t);
glyphs[i].getQuadAtlasBounds(l, b, r, t);
if (l || b || r || t)
fprintf(f, ",\"atlasBounds\":{\"left\":%.17g,\"bottom\":%.17g,\"right\":%.17g,\"top\":%.17g}", l, b, r, t);
fputs("}", f);
}
fputs("],", f);
// Kerning pairs
fputs("\"kerning\":[", f);
bool firstPair = true;
for (int i = 0; i < glyphCount; ++i) {
for (int j = 0; j < glyphCount; ++j) {
double kerning;
if (msdfgen::getKerning(kerning, font, glyphs[i].getCodepoint(), glyphs[j].getCodepoint()) && kerning) {
fputs(firstPair ? "{" : ",{", f);
fprintf(f, "\"unicode1\":%u,", glyphs[i].getCodepoint());
fprintf(f, "\"unicode2\":%u,", glyphs[j].getCodepoint());
fprintf(f, "\"advance\":%.17g", fsScale*kerning);
fputs("}", f);
firstPair = false;
fprintf(f, "\"size\":%.17g,", metrics.size);
fprintf(f, "\"width\":%d,", metrics.width);
fprintf(f, "\"height\":%d,", metrics.height);
fprintf(f, "\"yOrigin\":\"%s\"", metrics.yDirection == YDirection::TOP_DOWN ? "top" : "bottom");
if (metrics.grid) {
fputs(",\"grid\":{", f);
fprintf(f, "\"cellWidth\":%d,", metrics.grid->cellWidth);
fprintf(f, "\"cellHeight\":%d,", metrics.grid->cellHeight);
fprintf(f, "\"columns\":%d,", metrics.grid->columns);
fprintf(f, "\"rows\":%d", metrics.grid->rows);
if (metrics.grid->originX)
fprintf(f, ",\"originX\":%.17g", *metrics.grid->originX);
if (metrics.grid->originY) {
switch (metrics.yDirection) {
case YDirection::BOTTOM_UP:
fprintf(f, ",\"originY\":%.17g", *metrics.grid->originY);
break;
case YDirection::TOP_DOWN:
fprintf(f, ",\"originY\":%.17g", (metrics.grid->cellHeight-metrics.grid->spacing-1)/metrics.size-*metrics.grid->originY);
break;
}
}
fputs("}", f);
}
} fputs("},", f);
if (fontCount > 1)
fputs("\"variants\":[", f);
for (int i = 0; i < fontCount; ++i) {
const FontGeometry &font = fonts[i];
if (fontCount > 1)
fputs(i == 0 ? "{" : ",{", f);
// Font name
const char *name = font.getName();
if (name)
fprintf(f, "\"name\":\"%s\",", escapeJsonString(name).c_str());
// Font metrics
fputs("\"metrics\":{", f); {
double yFactor = metrics.yDirection == YDirection::TOP_DOWN ? -1 : 1;
const msdfgen::FontMetrics &fontMetrics = font.getMetrics();
fprintf(f, "\"emSize\":%.17g,", fontMetrics.emSize);
fprintf(f, "\"lineHeight\":%.17g,", fontMetrics.lineHeight);
fprintf(f, "\"ascender\":%.17g,", yFactor*fontMetrics.ascenderY);
fprintf(f, "\"descender\":%.17g,", yFactor*fontMetrics.descenderY);
fprintf(f, "\"underlineY\":%.17g,", yFactor*fontMetrics.underlineY);
fprintf(f, "\"underlineThickness\":%.17g", fontMetrics.underlineThickness);
} fputs("},", f);
// Glyph mapping
fputs("\"glyphs\":[", f);
bool firstGlyph = true;
for (const GlyphGeometry &glyph : font.getGlyphs()) {
fputs(firstGlyph ? "{" : ",{", f);
switch (font.getPreferredIdentifierType()) {
case GlyphIdentifierType::GLYPH_INDEX:
fprintf(f, "\"index\":%d,", glyph.getIndex());
break;
case GlyphIdentifierType::UNICODE_CODEPOINT:
fprintf(f, "\"unicode\":%u,", glyph.getCodepoint());
break;
}
fprintf(f, "\"advance\":%.17g", glyph.getAdvance());
double l, b, r, t;
glyph.getQuadPlaneBounds(l, b, r, t);
if (l || b || r || t) {
switch (metrics.yDirection) {
case YDirection::BOTTOM_UP:
fprintf(f, ",\"planeBounds\":{\"left\":%.17g,\"bottom\":%.17g,\"right\":%.17g,\"top\":%.17g}", l, b, r, t);
break;
case YDirection::TOP_DOWN:
fprintf(f, ",\"planeBounds\":{\"left\":%.17g,\"top\":%.17g,\"right\":%.17g,\"bottom\":%.17g}", l, -t, r, -b);
break;
}
}
glyph.getQuadAtlasBounds(l, b, r, t);
if (l || b || r || t) {
switch (metrics.yDirection) {
case YDirection::BOTTOM_UP:
fprintf(f, ",\"atlasBounds\":{\"left\":%.17g,\"bottom\":%.17g,\"right\":%.17g,\"top\":%.17g}", l, b, r, t);
break;
case YDirection::TOP_DOWN:
fprintf(f, ",\"atlasBounds\":{\"left\":%.17g,\"top\":%.17g,\"right\":%.17g,\"bottom\":%.17g}", l, metrics.height-t, r, metrics.height-b);
break;
}
}
fputs("}", f);
firstGlyph = false;
} fputs("]", f);
// Kerning pairs
if (kerning) {
fputs(",\"kerning\":[", f);
bool firstPair = true;
switch (font.getPreferredIdentifierType()) {
case GlyphIdentifierType::GLYPH_INDEX:
for (const std::pair<std::pair<int, int>, double> &kernPair : font.getKerning()) {
fputs(firstPair ? "{" : ",{", f);
fprintf(f, "\"index1\":%d,", kernPair.first.first);
fprintf(f, "\"index2\":%d,", kernPair.first.second);
fprintf(f, "\"advance\":%.17g", kernPair.second);
fputs("}", f);
firstPair = false;
}
break;
case GlyphIdentifierType::UNICODE_CODEPOINT:
for (const std::pair<std::pair<int, int>, double> &kernPair : font.getKerning()) {
const GlyphGeometry *glyph1 = font.getGlyph(msdfgen::GlyphIndex(kernPair.first.first));
const GlyphGeometry *glyph2 = font.getGlyph(msdfgen::GlyphIndex(kernPair.first.second));
if (glyph1 && glyph2 && glyph1->getCodepoint() && glyph2->getCodepoint()) {
fputs(firstPair ? "{" : ",{", f);
fprintf(f, "\"unicode1\":%u,", glyph1->getCodepoint());
fprintf(f, "\"unicode2\":%u,", glyph2->getCodepoint());
fprintf(f, "\"advance\":%.17g", kernPair.second);
fputs("}", f);
firstPair = false;
}
}
break;
} fputs("]", f);
}
if (fontCount > 1)
fputs("}", f);
}
fputs("]", f);
if (fontCount > 1)
fputs("]", f);
fputs("}\n", f);
fclose(f);

View File

@ -4,11 +4,25 @@
#include <msdfgen.h>
#include <msdfgen-ext.h>
#include "types.h"
#include "GlyphGeometry.h"
#include "FontGeometry.h"
namespace msdf_atlas {
struct JsonAtlasMetrics {
struct GridMetrics {
int cellWidth, cellHeight;
int columns, rows;
const double *originX, *originY;
int spacing;
};
msdfgen::Range distanceRange;
double size;
int width, height;
YDirection yDirection;
const GridMetrics *grid;
};
/// Writes the font and glyph metrics and atlas layout data into a comprehensive JSON file
bool exportJSON(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, GlyphIdentifierType glyphIdentifierType, double fontSize, double pxRange, int atlasWidth, int atlasHeight, ImageType imageType, const char *filename);
bool exportJSON(const FontGeometry *fonts, int fontCount, ImageType imageType, const JsonAtlasMetrics &metrics, const char *filename, bool kerning);
}

File diff suppressed because it is too large Load Diff

View File

@ -2,12 +2,10 @@
#pragma once
/*
* MULTI-CHANNEL SIGNED DISTANCE FIELD ATLAS GENERATOR v1.1 (2020-10-18)
* ---------------------------------------------------------------------
* A utility by Viktor Chlumsky, (c) 2020
*
* Generates compact bitmap font atlases using MSDFGEN.
*
* MULTI-CHANNEL SIGNED DISTANCE FIELD ATLAS GENERATOR
* ---------------------------------------------------
* A utility by Viktor Chlumsky, (c) 2020 - 2025
* Generates compact bitmap font atlases using MSDFgen
*/
#include <msdfgen.h>
@ -16,9 +14,11 @@
#include "types.h"
#include "utf8.h"
#include "Rectangle.h"
#include "Padding.h"
#include "Charset.h"
#include "GlyphBox.h"
#include "GlyphGeometry.h"
#include "FontGeometry.h"
#include "RectanglePacker.h"
#include "rectangle-packing.h"
#include "Workload.h"
@ -27,6 +27,7 @@
#include "AtlasStorage.h"
#include "BitmapAtlasStorage.h"
#include "TightAtlasPacker.h"
#include "GridAtlasPacker.h"
#include "AtlasGenerator.h"
#include "ImmediateAtlasGenerator.h"
#include "DynamicAtlas.h"
@ -37,5 +38,3 @@
#include "csv-export.h"
#include "json-export.h"
#include "shadron-preview-generator.h"
#define MSDF_ATLAS_VERSION "1.1"

View File

@ -8,11 +8,11 @@ namespace msdf_atlas {
/// Packs the rectangle array into an atlas with fixed dimensions, returns how many didn't fit (0 on success)
template <typename RectangleType>
int packRectangles(RectangleType *rectangles, int count, int width, int height, int padding = 0);
int packRectangles(RectangleType *rectangles, int count, int width, int height, int spacing = 0);
/// Packs the rectangle array into an atlas of unknown size, returns the minimum required dimensions constrained by SizeSelector
template <class SizeSelector, typename RectangleType>
std::pair<int, int> packRectangles(RectangleType *rectangles, int count, int padding = 0);
std::pair<int, int> packRectangles(RectangleType *rectangles, int count, int spacing = 0);
}

View File

@ -18,35 +18,35 @@ static void copyRectanglePlacement(OrientedRectangle &dst, const OrientedRectang
}
template <typename RectangleType>
int packRectangles(RectangleType *rectangles, int count, int width, int height, int padding) {
if (padding)
int packRectangles(RectangleType *rectangles, int count, int width, int height, int spacing) {
if (spacing)
for (int i = 0; i < count; ++i) {
rectangles[i].w += padding;
rectangles[i].h += padding;
rectangles[i].w += spacing;
rectangles[i].h += spacing;
}
int result = RectanglePacker(width+padding, height+padding).pack(rectangles, count);
if (padding)
int result = RectanglePacker(width+spacing, height+spacing).pack(rectangles, count);
if (spacing)
for (int i = 0; i < count; ++i) {
rectangles[i].w -= padding;
rectangles[i].h -= padding;
rectangles[i].w -= spacing;
rectangles[i].h -= spacing;
}
return result;
}
template <class SizeSelector, typename RectangleType>
std::pair<int, int> packRectangles(RectangleType *rectangles, int count, int padding) {
std::pair<int, int> packRectangles(RectangleType *rectangles, int count, int spacing) {
std::vector<RectangleType> rectanglesCopy(count);
int totalArea = 0;
for (int i = 0; i < count; ++i) {
rectanglesCopy[i].w = rectangles[i].w+padding;
rectanglesCopy[i].h = rectangles[i].h+padding;
rectanglesCopy[i].w = rectangles[i].w+spacing;
rectanglesCopy[i].h = rectangles[i].h+spacing;
totalArea += rectangles[i].w*rectangles[i].h;
}
std::pair<int, int> dimensions;
SizeSelector sizeSelector(totalArea);
int width, height;
while (sizeSelector(width, height)) {
if (!RectanglePacker(width+padding, height+padding).pack(rectanglesCopy.data(), count)) {
if (!RectanglePacker(width+spacing, height+spacing).pack(rectanglesCopy.data(), count)) {
dimensions.first = width;
dimensions.second = height;
for (int i = 0; i < count; ++i)

View File

@ -6,25 +6,25 @@
namespace msdf_atlas {
static const char * const shadronFillGlyphMask = R"(
template <ATLAS, RANGE, COLOR>
static const char *const shadronFillGlyphMask = R"(
template <ATLAS, RANGE, ZERO_DIST, COLOR>
glsl vec4 fillGlyph(vec2 texCoord) {
float fill = texture((ATLAS), texCoord).r;
return vec4(vec3(COLOR), fill);
}
)";
static const char * const shadronFillGlyphSdf = R"(
template <ATLAS, RANGE, COLOR>
static const char *const shadronFillGlyphSdf = R"(
template <ATLAS, RANGE, ZERO_DIST, COLOR>
glsl vec4 fillGlyph(vec2 texCoord) {
vec3 s = texture((ATLAS), texCoord).rgb;
float sd = dot(vec2(RANGE), 0.5/fwidth(texCoord))*(median(s.r, s.g, s.b)-0.5);
float sd = dot(vec2(RANGE), 0.5/fwidth(texCoord))*(median(s.r, s.g, s.b)-ZERO_DIST);
float fill = clamp(sd+0.5, 0.0, 1.0);
return vec4(vec3(COLOR), fill);
}
)";
static const char * const shadronPreviewPreamble = R"(
static const char *const shadronPreviewPreamble = R"(
#include <median>
glsl struct GlyphVertex {
@ -43,11 +43,11 @@ glsl vec4 projectVertex(out vec2 texCoord, in GlyphVertex vertex) {
return vec4(coord, 0.0, 1.0);
}
%s
#define PREVIEW_IMAGE(NAME, ATLAS, RANGE, COLOR, VERTEX_LIST, TEXT_SIZE, DIMENSIONS) model image NAME : \
#define PREVIEW_IMAGE(NAME, ATLAS, RANGE, ZERO_DIST, COLOR, VERTEX_LIST, TEXT_SIZE, DIMENSIONS) model image NAME : \
vertex_data(GlyphVertex), \
fragment_data(vec2), \
vertex(projectVertex<TEXT_SIZE>, triangles, VERTEX_LIST), \
fragment(fillGlyph<ATLAS, RANGE, COLOR>), \
fragment(fillGlyph<ATLAS, RANGE, ZERO_DIST, COLOR>), \
depth(false), \
blend(transparency), \
background(vec4(vec3(COLOR), 0.0)), \
@ -77,23 +77,48 @@ static std::string relativizePath(const char *base, const char *target) {
return output;
}
bool generateShadronPreview(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, ImageType atlasType, int atlasWidth, int atlasHeight, double pxRange, const unicode_t *text, const char *imageFilename, const char *outputFilename) {
static std::string escapeString(const std::string &str) {
std::string output;
for (int i = 0; i < (int) str.size(); ++i) {
switch (str[i]) {
case '"':
output += "\\\"";
break;
case '\\':
output += "\\\\";
break;
default:
output.push_back(str[i]);
}
}
return output;
}
bool generateShadronPreview(const FontGeometry *fonts, int fontCount, ImageType atlasType, int atlasWidth, int atlasHeight, msdfgen::Range pxRange, const unicode_t *text, const char *imageFilename, bool fullRange, const char *outputFilename) {
if (fontCount <= 0)
return false;
double texelWidth = 1./atlasWidth;
double texelHeight = 1./atlasHeight;
bool anyGlyphs = false;
FILE *file = fopen(outputFilename, "w");
if (!file)
return false;
fprintf(file, shadronPreviewPreamble, atlasType == ImageType::HARD_MASK || atlasType == ImageType::SOFT_MASK ? shadronFillGlyphMask : shadronFillGlyphSdf);
if (imageFilename)
fprintf(file, "image Atlas = file(\"%s\")", relativizePath(outputFilename, imageFilename).c_str());
fprintf(file, "image Atlas = file(\"%s\")", escapeString(relativizePath(outputFilename, imageFilename)).c_str());
else
fprintf(file, "image Atlas = file()");
fprintf(file, " : filter(%s), map(repeat);\n", atlasType == ImageType::HARD_MASK ? "nearest" : "linear");
fprintf(file, "const vec2 txRange = vec2(%.9g, %.9g);\n\n", pxRange*texelWidth, pxRange*texelHeight);
fprintf(file, " : %sfilter(%s), map(repeat);\n", fullRange ? "full_range(true), " : "", atlasType == ImageType::HARD_MASK ? "nearest" : "linear");
double pxRangeWidth = pxRange.upper-pxRange.lower;
fprintf(file, "const vec2 txRange = vec2(%.9g, %.9g);\n", pxRangeWidth*texelWidth, pxRangeWidth*texelHeight);
fprintf(file, "const float zeroDistanceValue = %.9g;\n\n", -pxRange.lower/(pxRange.upper-pxRange.lower));
{
msdfgen::FontMetrics fontMetrics;
if (!msdfgen::getFontMetrics(fontMetrics, font))
return false;
msdfgen::FontMetrics fontMetrics = fonts->getMetrics();
for (int i = 1; i < fontCount; ++i) {
fontMetrics.lineHeight = std::max(fontMetrics.lineHeight, fonts[i].getMetrics().lineHeight);
fontMetrics.ascenderY = std::max(fontMetrics.ascenderY, fonts[i].getMetrics().ascenderY);
fontMetrics.descenderY = std::min(fontMetrics.descenderY, fonts[i].getMetrics().descenderY);
}
double fsScale = 1/(fontMetrics.ascenderY-fontMetrics.descenderY);
fputs("vertex_list GlyphVertex textQuadVertices = {\n", file);
double x = 0, y = -fsScale*fontMetrics.ascenderY;
@ -107,13 +132,14 @@ bool generateShadronPreview(msdfgen::FontHandle *font, const GlyphGeometry *glyp
y -= fsScale*fontMetrics.lineHeight;
continue;
}
for (int i = 0; i < glyphCount; ++i) {
if (glyphs[i].getCodepoint() == *cp) {
if (!glyphs[i].isWhitespace()) {
for (int i = 0; i < fontCount; ++i) {
const GlyphGeometry *glyph = fonts[i].getGlyph(*cp);
if (glyph) {
if (!glyph->isWhitespace()) {
double pl, pb, pr, pt;
double il, ib, ir, it;
glyphs[i].getQuadPlaneBounds(pl, pb, pr, pt);
glyphs[i].getQuadAtlasBounds(il, ib, ir, it);
glyph->getQuadPlaneBounds(pl, pb, pr, pt);
glyph->getQuadAtlasBounds(il, ib, ir, it);
pl *= fsScale, pb *= fsScale, pr *= fsScale, pt *= fsScale;
pl += x, pb += y, pr += x, pt += y;
il *= texelWidth, ib *= texelHeight, ir *= texelWidth, it *= texelHeight;
@ -126,10 +152,10 @@ bool generateShadronPreview(msdfgen::FontHandle *font, const GlyphGeometry *glyp
pr, pb, ir, ib
);
}
x += fsScale*glyphs[i].getAdvance();
double kerning;
if (msdfgen::getKerning(kerning, font, cp[0], cp[1]))
x += fsScale*kerning;
double advance = glyph->getAdvance();
fonts[i].getAdvance(advance, cp[0], cp[1]);
x += fsScale*advance;
anyGlyphs = true;
break;
}
}
@ -139,10 +165,10 @@ bool generateShadronPreview(msdfgen::FontHandle *font, const GlyphGeometry *glyp
fputs("};\n", file);
fprintf(file, "const vec2 textSize = vec2(%.9g, %.9g);\n\n", textWidth, -y);
}
fputs("PREVIEW_IMAGE(Preview, Atlas, txRange, vec3(1.0), textQuadVertices, textSize, ivec2(1200, 400));\n", file);
fputs("PREVIEW_IMAGE(Preview, Atlas, txRange, zeroDistanceValue, vec3(1.0), textQuadVertices, textSize, ivec2(1200, 400));\n", file);
fputs("export png(Preview, \"preview.png\");\n", file);
fclose(file);
return true;
return anyGlyphs;
}
}

View File

@ -4,11 +4,11 @@
#include <msdfgen.h>
#include <msdfgen-ext.h>
#include "types.h"
#include "GlyphGeometry.h"
#include "FontGeometry.h"
namespace msdf_atlas {
/// Generates a Shadron script that displays a string using the generated atlas
bool generateShadronPreview(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, ImageType atlasType, int atlasWidth, int atlasHeight, double pxRange, const unicode_t *text, const char *imageFilename, const char *outputFilename);
bool generateShadronPreview(const FontGeometry *fonts, int fontCount, ImageType atlasType, int atlasWidth, int atlasHeight, msdfgen::Range pxRange, const unicode_t *text, const char *imageFilename, bool fullRange, const char *outputFilename);
}

View File

@ -15,7 +15,7 @@ SquareSizeSelector<MULTIPLE>::SquareSizeSelector(int minArea) : lowerBound(0), u
template <int MULTIPLE>
void SquareSizeSelector<MULTIPLE>::updateCurrent() {
if (upperBound < 0)
current = 5*lowerBound/4+16/MULTIPLE;
current = 5*lowerBound/4+16/MULTIPLE+1;
else
current = lowerBound+(upperBound-lowerBound)/2;
}
@ -27,14 +27,14 @@ bool SquareSizeSelector<MULTIPLE>::operator()(int &width, int &height) const {
}
template <int MULTIPLE>
SquareSizeSelector<MULTIPLE> & SquareSizeSelector<MULTIPLE>::operator++() {
SquareSizeSelector<MULTIPLE> &SquareSizeSelector<MULTIPLE>::operator++() {
lowerBound = current+1;
updateCurrent();
return *this;
}
template <int MULTIPLE>
SquareSizeSelector<MULTIPLE> & SquareSizeSelector<MULTIPLE>::operator--() {
SquareSizeSelector<MULTIPLE> &SquareSizeSelector<MULTIPLE>::operator--() {
upperBound = current;
updateCurrent();
return *this;
@ -54,12 +54,12 @@ bool SquarePowerOfTwoSizeSelector::operator()(int &width, int &height) const {
return side > 0;
}
SquarePowerOfTwoSizeSelector & SquarePowerOfTwoSizeSelector::operator++() {
SquarePowerOfTwoSizeSelector &SquarePowerOfTwoSizeSelector::operator++() {
side <<= 1;
return *this;
}
SquarePowerOfTwoSizeSelector & SquarePowerOfTwoSizeSelector::operator--() {
SquarePowerOfTwoSizeSelector &SquarePowerOfTwoSizeSelector::operator--() {
side = 0;
return *this;
}
@ -74,7 +74,7 @@ bool PowerOfTwoSizeSelector::operator()(int &width, int &height) const {
return w > 0 && h > 0;
}
PowerOfTwoSizeSelector & PowerOfTwoSizeSelector::operator++() {
PowerOfTwoSizeSelector &PowerOfTwoSizeSelector::operator++() {
if (w == h)
w <<= 1;
else
@ -82,7 +82,7 @@ PowerOfTwoSizeSelector & PowerOfTwoSizeSelector::operator++() {
return *this;
}
PowerOfTwoSizeSelector & PowerOfTwoSizeSelector::operator--() {
PowerOfTwoSizeSelector &PowerOfTwoSizeSelector::operator--() {
w = 0, h = 0;
return *this;
}

View File

@ -12,8 +12,8 @@ class SquareSizeSelector {
public:
explicit SquareSizeSelector(int minArea = 0);
bool operator()(int &width, int &height) const;
SquareSizeSelector<MULTIPLE> & operator++();
SquareSizeSelector<MULTIPLE> & operator--();
SquareSizeSelector<MULTIPLE> &operator++();
SquareSizeSelector<MULTIPLE> &operator--();
private:
int lowerBound, upperBound;
@ -29,8 +29,8 @@ class SquarePowerOfTwoSizeSelector {
public:
explicit SquarePowerOfTwoSizeSelector(int minArea = 0);
bool operator()(int &width, int &height) const;
SquarePowerOfTwoSizeSelector & operator++();
SquarePowerOfTwoSizeSelector & operator--();
SquarePowerOfTwoSizeSelector &operator++();
SquarePowerOfTwoSizeSelector &operator--();
private:
int side;
@ -43,8 +43,8 @@ class PowerOfTwoSizeSelector {
public:
explicit PowerOfTwoSizeSelector(int minArea = 0);
bool operator()(int &width, int &height) const;
PowerOfTwoSizeSelector & operator++();
PowerOfTwoSizeSelector & operator--();
PowerOfTwoSizeSelector &operator++();
PowerOfTwoSizeSelector &operator--();
private:
int w, h;

View File

@ -16,7 +16,7 @@ enum class ImageType {
SOFT_MASK,
/// Signed (true) distance field
SDF,
/// Signed pseudo-distance field
/// Signed perpendicular distance field
PSDF,
/// Multi-channel signed distance field
MSDF,
@ -30,6 +30,8 @@ enum class ImageFormat {
PNG,
BMP,
TIFF,
RGBA,
FL32,
TEXT,
TEXT_FLOAT,
BINARY,
@ -43,4 +45,26 @@ enum class GlyphIdentifierType {
UNICODE_CODEPOINT
};
/// Direction of the Y-axis
enum class YDirection {
BOTTOM_UP,
TOP_DOWN
};
/// The method of computing the layout of the atlas
enum class PackingStyle {
TIGHT,
GRID
};
/// Constraints for the atlas's dimensions - see size selectors for more info
enum class DimensionsConstraint {
NONE,
SQUARE,
EVEN_SQUARE,
MULTIPLE_OF_FOUR_SQUARE,
POWER_OF_TWO_RECTANGLE,
POWER_OF_TWO_SQUARE
};
}

22
msdf-atlas-gen/utils.hpp Normal file
View File

@ -0,0 +1,22 @@
#pragma once
namespace msdf_atlas {
/// Floor positive integer to nearest lower or equal power of two
inline int floorToPOT(int x) {
int y = 1;
while (x >= y && y<<1)
y <<= 1;
return y>>1;
}
/// Ceil positive integer to nearest higher or equal power of two
inline int ceilToPOT(int x) {
int y = 1;
while (x > y && y<<1)
y <<= 1;
return y;
}
}

@ -1 +1 @@
Subproject commit 9d22335ea093422d7bf212d31bfaa6adcb9b89f0
Subproject commit 03889564a50452fa2e0b0a60973b5057001b391b

Binary file not shown.

24
vcpkg.json Normal file
View File

@ -0,0 +1,24 @@
{
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/master/docs/vcpkg.schema.json",
"name": "msdf-atlas-gen",
"version": "1.3.0",
"description": "Multi-channel signed distance field atlas generator",
"homepage": "https://github.com/Chlumsky/msdf-atlas-gen",
"license": "MIT",
"dependencies": [
"freetype",
"libpng"
],
"default-features": [
"geometry-preprocessing"
],
"features": {
"geometry-preprocessing": {
"description": "Preprocessing of non-compliant vector geometry via the Skia library",
"dependencies": [ {
"name": "skia",
"default-features": false
} ]
}
}
}