Version 1.0

This commit is contained in:
Chlumsky 2020-03-08 11:26:47 +01:00
parent d3a54faff6
commit cad221afcd
54 changed files with 4005 additions and 0 deletions

4
Makefile Normal file
View File

@ -0,0 +1,4 @@
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

BIN
icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
msdf-atlas-gen.rc Normal file

Binary file not shown.

61
msdf-atlas-gen.sln Normal file
View File

@ -0,0 +1,61 @@

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

360
msdf-atlas-gen.vcxproj Normal file
View File

@ -0,0 +1,360 @@
<?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;MSDF_ATLAS_STANDALONE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<AdditionalDependencies>freetype.lib;msdfgen.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>msdfgen\freetype\win32;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;%(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>
</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;MSDF_ATLAS_STANDALONE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<AdditionalDependencies>freetype.lib;msdfgen.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>msdfgen\freetype\win64;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;%(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>
</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;MSDF_ATLAS_STANDALONE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<SubSystem>Console</SubSystem>
<AdditionalDependencies>freetype.lib;msdfgen.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>msdfgen\freetype\win32;msdfgen\bin;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</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;%(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>
</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;MSDF_ATLAS_STANDALONE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<SubSystem>Console</SubSystem>
<AdditionalDependencies>freetype.lib;msdfgen.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>msdfgen\freetype\win64;msdfgen\$(Platform)\$(Configuration) Library;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</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;%(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>
</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

@ -0,0 +1,178 @@
<?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

@ -0,0 +1,43 @@
#pragma once
#include <msdfgen.h>
#include "Remap.h"
#include "GlyphGeometry.h"
namespace msdf_atlas {
namespace {
/** Prototype of an atlas generator class.
* An atlas generator maintains the atlas bitmap (AtlasStorage) and its layout and facilitates
* generation of bitmap representation of glyphs. The layout of the atlas is given by the caller.
*/
class AtlasGenerator {
public:
AtlasGenerator();
AtlasGenerator(int width, int height);
/// Generates bitmap representation for the supplied array of glyphs
void generate(const GlyphGeometry *glyphs, int count);
/// Resizes the atlas and rearranges the generated pixels according to the remapping array
void rearrange(int width, int height, const Remap *remapping, int count);
/// Resizes the atlas and keeps the generated pixels in place
void resize(int width, int height);
};
}
/// Configuration of signed distance field generator
struct GeneratorAttributes {
bool overlapSupport = true;
bool scanlinePass = true;
double errorCorrectionThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD;
};
/// A function that generates the bitmap for a single glyph
template <typename T, int N>
using GeneratorFunction = void (*)(const msdfgen::BitmapRef<T, N> &, const GlyphGeometry &, const GeneratorAttributes &);
}

View File

@ -0,0 +1,37 @@
#pragma once
#include <msdfgen.h>
#include "Remap.h"
namespace msdf_atlas {
namespace {
/** Prototype of an atlas storage class.
* An atlas storage physically holds the pixels of the atlas
* and allows to read and write subsections represented as bitmaps.
* Can be implemented using a simple bitmap (BitmapAtlasStorage),
* as texture memory, or any other way.
*/
class AtlasStorage {
public:
AtlasStorage();
AtlasStorage(int width, int height);
/// Creates a copy with different dimensions
AtlasStorage(const AtlasStorage &orig, int width, int height);
/// Creates a copy with different dimensions and rearranges the pixels according to the remapping array
AtlasStorage(const AtlasStorage &orig, int width, int height, const Remap *remapping, int count);
/// Stores a subsection at x, y into the atlas storage. May be implemented for only some T, N
template <typename T, int N>
void put(int x, int y, const msdfgen::BitmapConstRef<T, N> &subBitmap);
/// Retrieves a subsection at x, y from the atlas storage. May be implemented for only some T, N
template <typename T, int N>
void get(int x, int y, const msdfgen::BitmapRef<T, N> &subBitmap) const;
};
}
}

View File

@ -0,0 +1,33 @@
#pragma once
#include "AtlasStorage.h"
namespace msdf_atlas {
/// An implementation of AtlasStorage represented by a bitmap in memory (msdfgen::Bitmap)
template <typename T, int N>
class BitmapAtlasStorage {
public:
BitmapAtlasStorage();
BitmapAtlasStorage(int width, int height);
explicit BitmapAtlasStorage(const msdfgen::BitmapConstRef<T, N> &bitmap);
explicit BitmapAtlasStorage(msdfgen::Bitmap<T, N> &&bitmap);
BitmapAtlasStorage(const BitmapAtlasStorage<T, N> &orig, int width, int height);
BitmapAtlasStorage(const BitmapAtlasStorage<T, N> &orig, int width, int height, const Remap *remapping, int count);
operator msdfgen::BitmapConstRef<T, N>() const;
operator msdfgen::BitmapRef<T, N>();
operator msdfgen::Bitmap<T, N>() &&;
template <typename S>
void put(int x, int y, const msdfgen::BitmapConstRef<S, N> &subBitmap);
void get(int x, int y, const msdfgen::BitmapRef<T, N> &subBitmap) const;
private:
msdfgen::Bitmap<T, N> bitmap;
};
}
#include "BitmapAtlasStorage.hpp"

View File

@ -0,0 +1,65 @@
#include "BitmapAtlasStorage.h"
#include <cstring>
#include <algorithm>
#include "bitmap-blit.h"
namespace msdf_atlas {
template <typename T, int N>
BitmapAtlasStorage<T, N>::BitmapAtlasStorage() { }
template <typename T, int N>
BitmapAtlasStorage<T, N>::BitmapAtlasStorage(int width, int height) : bitmap(width, height) {
memset((T *) bitmap, 0, sizeof(T)*N*width*height);
}
template <typename T, int N>
BitmapAtlasStorage<T, N>::BitmapAtlasStorage(const msdfgen::BitmapConstRef<T, N> &bitmap) : bitmap(bitmap) { }
template <typename T, int N>
BitmapAtlasStorage<T, N>::BitmapAtlasStorage(msdfgen::Bitmap<T, N> &&bitmap) : bitmap((msdfgen::Bitmap<T, N> &&) bitmap) { }
template <typename T, int N>
BitmapAtlasStorage<T, N>::BitmapAtlasStorage(const BitmapAtlasStorage<T, N> &orig, int width, int height) : bitmap(width, height) {
memset((T *) bitmap, 0, sizeof(T)*N*width*height);
blit(bitmap, orig.bitmap, 0, 0, 0, 0, std::min(width, orig.bitmap.width()), std::min(height, orig.bitmap.height()));
}
template <typename T, int N>
BitmapAtlasStorage<T, N>::BitmapAtlasStorage(const BitmapAtlasStorage<T, N> &orig, int width, int height, const Remap *remapping, int count) : bitmap(width, height) {
memset((T *) bitmap, 0, sizeof(T)*N*width*height);
for (int i = 0; i < count; ++i) {
const Remap &remap = remapping[i];
blit(bitmap, orig.bitmap, remap.target.x, remap.target.y, remap.source.x, remap.source.y, remap.width, remap.height);
}
}
template <typename T, int N>
BitmapAtlasStorage<T, N>::operator msdfgen::BitmapConstRef<T, N>() const {
return bitmap;
}
template <typename T, int N>
BitmapAtlasStorage<T, N>::operator msdfgen::BitmapRef<T, N>() {
return bitmap;
}
template <typename T, int N>
BitmapAtlasStorage<T, N>::operator msdfgen::Bitmap<T, N>() && {
return (msdfgen::Bitmap<T, N>() &&) bitmap;
}
template <typename T, int N>
template <typename S>
void BitmapAtlasStorage<T, N>::put(int x, int y, const msdfgen::BitmapConstRef<S, N> &subBitmap) {
blit(bitmap, subBitmap, x, y, 0, 0, subBitmap.width, subBitmap.height);
}
template <typename T, int N>
void BitmapAtlasStorage<T, N>::get(int x, int y, const msdfgen::BitmapRef<T, N> &subBitmap) const {
blit(subBitmap, bitmap, 0, 0, x, y, subBitmap.width, subBitmap.height);
}
}

View File

@ -0,0 +1,39 @@
#include "Charset.h"
namespace msdf_atlas {
static Charset createAsciiCharset() {
Charset ascii;
for (unicode_t cp = 0x20; cp < 0x7f; ++cp)
ascii.add(cp);
return ascii;
}
const Charset Charset::ASCII = createAsciiCharset();
void Charset::add(unicode_t cp) {
codepoints.insert(cp);
}
void Charset::remove(unicode_t cp) {
codepoints.erase(cp);
}
size_t Charset::size() const {
return codepoints.size();
}
bool Charset::empty() const {
return codepoints.empty();
}
std::set<unicode_t>::const_iterator Charset::begin() const {
return codepoints.begin();
}
std::set<unicode_t>::const_iterator Charset::end() const {
return codepoints.end();
}
}

35
msdf-atlas-gen/Charset.h Normal file
View File

@ -0,0 +1,35 @@
#pragma once
#include <cstdlib>
#include <set>
#include "types.h"
namespace msdf_atlas {
/// Represents a set of Unicode codepoints (characters)
class Charset {
public:
/// The set of the 95 printable ASCII characters
static const Charset ASCII;
/// Adds a codepoint
void add(unicode_t cp);
/// Removes a codepoint
void remove(unicode_t cp);
size_t size() const;
bool empty() const;
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
bool load(const char *filename);
private:
std::set<unicode_t> codepoints;
};
}

View File

@ -0,0 +1,43 @@
#pragma once
#include <vector>
#include "RectanglePacker.h"
#include "AtlasGenerator.h"
namespace msdf_atlas {
/**
* This class can be used to produce a dynamic atlas to which more glyphs are added over time.
* It takes care of laying out and enlarging the atlas as necessary and delegates the actual work
* to the specified AtlasGenerator, which may e.g. do the work asynchronously.
*/
template <class AtlasGenerator>
class DynamicAtlas {
public:
DynamicAtlas();
/// 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);
/// Allows access to generator. Do not add glyphs to the generator directly!
AtlasGenerator & atlasGenerator();
const AtlasGenerator & atlasGenerator() const;
private:
AtlasGenerator generator;
RectanglePacker packer;
int glyphCount;
int side;
std::vector<Rectangle> rectangles;
std::vector<Remap> remapBuffer;
int totalArea;
GeneratorAttributes genAttribs;
int padding;
};
}
#include "DynamicAtlas.hpp"

View File

@ -0,0 +1,69 @@
#include "DynamicAtlas.h"
namespace msdf_atlas {
template <class AtlasGenerator>
DynamicAtlas<AtlasGenerator>::DynamicAtlas() : glyphCount(0), side(0), totalArea(0), padding(0) { }
template <class AtlasGenerator>
DynamicAtlas<AtlasGenerator>::DynamicAtlas(AtlasGenerator &&generator) : generator((AtlasGenerator &&) generator), glyphCount(0), side(0), totalArea(0), padding(0) { }
template <class AtlasGenerator>
void DynamicAtlas<AtlasGenerator>::add(GlyphGeometry *glyphs, int count) {
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 };
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);
}
}
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;
while (side*side < totalArea)
side <<= 1;
packer = RectanglePacker(side+padding, side+padding);
packerStart = 0;
}
if (packerStart < start) {
for (int i = 0; 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)
generator.resize(side, side);
for (int i = start; i < (int) rectangles.size(); ++i) {
remapBuffer[i].target.x = rectangles[i].x;
remapBuffer[i].target.y = rectangles[i].y;
glyphs[remapBuffer[i].index-glyphCount].placeBox(rectangles[i].x, rectangles[i].y);
}
}
generator.generate(glyphs, count, genAttribs);
glyphCount += count;
}
template <class AtlasGenerator>
AtlasGenerator & DynamicAtlas<AtlasGenerator>::atlasGenerator() {
return generator;
}
template <class AtlasGenerator>
const AtlasGenerator & DynamicAtlas<AtlasGenerator>::atlasGenerator() const {
return generator;
}
}

21
msdf-atlas-gen/GlyphBox.h Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include "types.h"
namespace msdf_atlas {
/// The glyph box - its bounds in plane and atlas
struct GlyphBox {
unicode_t codepoint;
double advance;
struct {
double l, b, r, t;
} bounds;
struct {
int x, y, w, h;
} rect;
};
}

View File

@ -0,0 +1,133 @@
#include "GlyphGeometry.h"
#include <cmath>
namespace msdf_atlas {
GlyphGeometry::GlyphGeometry() : codepoint(), bounds(), reverseWinding(), advance(), box() { }
double GlyphGeometry::simpleSignedDistance(const msdfgen::Point2 &p) const {
double dummy;
msdfgen::SignedDistance minDistance;
for (const msdfgen::Contour &contour : shape.contours)
for (const msdfgen::EdgeHolder &edge : contour.edges) {
msdfgen::SignedDistance distance = edge->signedDistance(p, dummy);
if (distance < minDistance)
minDistance = distance;
}
return minDistance.distance;
}
bool GlyphGeometry::load(msdfgen::FontHandle *font, unicode_t codepoint) {
if (font && msdfgen::loadGlyph(shape, font, codepoint, &advance) && shape.validate()) {
this->codepoint = codepoint;
shape.normalize();
bounds = shape.getBounds();
msdfgen::Point2 outerPoint(bounds.l-(bounds.r-bounds.l)-1, bounds.b-(bounds.t-bounds.b)-1);
reverseWinding = simpleSignedDistance(outerPoint) > 0;
return true;
}
return false;
}
void GlyphGeometry::edgeColoring(double angleThreshold, unsigned long long seed) {
msdfgen::edgeColoringInkTrap(shape, angleThreshold, seed);
}
void GlyphGeometry::wrapBox(double scale, double range, double miterLimit) {
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, reverseWinding ? -1 : +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;
} else {
box.rect.w = 0, box.rect.h = 0;
box.translate = msdfgen::Vector2();
}
}
void GlyphGeometry::placeBox(int x, int y) {
box.rect.x = x, box.rect.y = y;
}
unicode_t GlyphGeometry::getCodepoint() const {
return codepoint;
}
const msdfgen::Shape & GlyphGeometry::getShape() const {
return shape;
}
double GlyphGeometry::getAdvance() const {
return advance;
}
bool GlyphGeometry::isWindingReverse() const {
return reverseWinding;
}
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;
}
void GlyphGeometry::getBoxSize(int &w, int &h) const {
w = box.rect.w, h = box.rect.h;
}
double GlyphGeometry::getBoxRange() const {
return box.range;
}
double GlyphGeometry::getBoxScale() const {
return box.scale;
}
msdfgen::Vector2 GlyphGeometry::getBoxTranslate() const {
return box.translate;
}
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;
} 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;
} else
l = 0, b = 0, r = 0, t = 0;
}
bool GlyphGeometry::isWhitespace() const {
return shape.contours.empty();
}
GlyphGeometry::operator GlyphBox() const {
GlyphBox box;
box.codepoint = codepoint;
box.advance = advance;
getQuadPlaneBounds(box.bounds.l, box.bounds.b, box.bounds.r, box.bounds.t);
box.rect.x = this->box.rect.x, box.rect.y = this->box.rect.y, box.rect.w = this->box.rect.w, box.rect.h = this->box.rect.h;
return box;
}
}

View File

@ -0,0 +1,71 @@
#pragma once
#include <msdfgen.h>
#include <msdfgen-ext.h>
#include "types.h"
#include "GlyphBox.h"
namespace msdf_atlas {
/// Represent's the shape geometry of a single glyph as well as its configuration
class GlyphGeometry {
public:
GlyphGeometry();
/// Loads glyph geometry from font
bool load(msdfgen::FontHandle *font, unicode_t codepoint);
/// Applies edge coloring to glyph shape
void edgeColoring(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);
/// Sets the glyph's box's position in the atlas
void placeBox(int x, int y);
/// Returns the glyph's Unicode index
unicode_t getCodepoint() const;
/// Returns the glyph's shape
const msdfgen::Shape & getShape() const;
/// Returns the glyph's advance
double getAdvance() const;
/// Returns true if the shape has reverse winding
bool isWindingReverse() 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;
/// Returns the scale needed to generate the glyph's bitmap
double getBoxScale() const;
/// Returns the translation vector needed to generate the glyph's bitmap
msdfgen::Vector2 getBoxTranslate() const;
/// Outputs the bounding box of the glyph as it should be placed on the baseline
void getQuadPlaneBounds(double &l, double &b, double &r, double &t) const;
/// Outputs the bounding box of the glyph in the atlas
void getQuadAtlasBounds(double &l, double &b, double &r, double &t) const;
/// Returns true if the glyph is a whitespace and has no geometry
bool isWhitespace() const;
/// Simplifies to GlyphBox
operator GlyphBox() const;
private:
unicode_t codepoint;
msdfgen::Shape shape;
msdfgen::Shape::Bounds bounds;
bool reverseWinding;
double advance;
struct {
struct {
int x, y, w, h;
} rect;
double range;
double scale;
msdfgen::Vector2 translate;
} box;
/// Computes the signed distance from point p in a naive way
double simpleSignedDistance(const msdfgen::Point2 &p) const;
};
}

View File

@ -0,0 +1,44 @@
#pragma once
#include <vector>
#include "GlyphBox.h"
#include "Workload.h"
#include "AtlasGenerator.h"
namespace msdf_atlas {
/**
* An implementation of AtlasGenerator that uses the specified generator function
* and AtlasStorage class and generates glyph bitmaps immediately
* (does not return until all submitted work is finished),
* but may use multiple threads (setThreadCount).
*/
template <typename T, int N, GeneratorFunction<T, N> GEN_FN, class AtlasStorage>
class ImmediateAtlasGenerator {
public:
ImmediateAtlasGenerator();
ImmediateAtlasGenerator(int width, int height);
void generate(const GlyphGeometry *glyphs, int count);
void rearrange(int width, int height, const Remap *remapping, int count);
void resize(int width, int height);
/// Sets attributes for the generator function
void setAttributes(const GeneratorAttributes &attributes);
/// Sets the number of threads to be run by generate
void setThreadCount(int threadCount);
/// Allows access to the underlying AtlasStorage
const AtlasStorage & atlasStorage() const;
private:
AtlasStorage storage;
std::vector<GlyphBox> layout;
std::vector<T> glyphBuffer;
GeneratorAttributes attributes;
int threadCount;
};
}
#include "ImmediateAtlasGenerator.hpp"

View File

@ -0,0 +1,70 @@
#include "ImmediateAtlasGenerator.h"
#include <algorithm>
namespace msdf_atlas {
template <typename T, int N, GeneratorFunction<T, N> GEN_FN, class AtlasStorage>
ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::ImmediateAtlasGenerator() : threadCount(1) { }
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>
void ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::generate(const GlyphGeometry *glyphs, int count) {
int maxBoxArea = 0;
for (int i = 0; i < count; ++i) {
GlyphBox box = glyphs[i];
maxBoxArea = std::max(maxBoxArea, box.rect.w*box.rect.h);
layout.push_back((GlyphBox &&) box);
}
int threadBufferSize = N*maxBoxArea;
if (threadCount*threadBufferSize > (int) glyphBuffer.size())
glyphBuffer.resize(threadCount*threadBufferSize);
Workload([this, &glyphs, 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);
storage.put(l, b, msdfgen::BitmapConstRef<T, N>(glyphBitmap));
}
return true;
}, count).finish(threadCount);
}
template <typename T, int N, GeneratorFunction<T, N> GEN_FN, class AtlasStorage>
void ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::rearrange(int width, int height, const Remap *remapping, int count) {
for (int i = 0; i < count; ++i) {
layout[remapping[i].index].rect.x = remapping[i].target.x;
layout[remapping[i].index].rect.y = remapping[i].target.y;
}
AtlasStorage newStorage((AtlasStorage &&) storage, width, height, remapping, count);
storage = (AtlasStorage &&) newStorage;
}
template <typename T, int N, GeneratorFunction<T, N> GEN_FN, class AtlasStorage>
void ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::resize(int width, int height) {
AtlasStorage newStorage((AtlasStorage &&) storage, width, height);
storage = (AtlasStorage &&) newStorage;
}
template <typename T, int N, GeneratorFunction<T, N> GEN_FN, class AtlasStorage>
void ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::setAttributes(const GeneratorAttributes &attributes) {
this->attributes = attributes;
}
template <typename T, int N, GeneratorFunction<T, N> GEN_FN, class AtlasStorage>
void ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::setThreadCount(int threadCount) {
this->threadCount = threadCount;
}
template <typename T, int N, GeneratorFunction<T, N> GEN_FN, class AtlasStorage>
const AtlasStorage & ImmediateAtlasGenerator<T, N, GEN_FN, AtlasStorage>::atlasStorage() const {
return storage;
}
}

View File

@ -0,0 +1,14 @@
#pragma once
namespace msdf_atlas {
struct Rectangle {
int x, y, w, h;
};
struct OrientedRectangle : Rectangle {
bool rotated;
};
}

View File

@ -0,0 +1,143 @@
#include "RectanglePacker.h"
#include <algorithm>
namespace msdf_atlas {
#define WORST_FIT 0x7fffffff
template <typename T>
static void removeFromUnorderedVector(std::vector<T> &vector, size_t index) {
if (index != vector.size()-1)
std::swap(vector[index], vector.back());
vector.pop_back();
}
int RectanglePacker::rateFit(int w, int h, int sw, int sh) {
return std::min(sw-w, sh-h);
}
RectanglePacker::RectanglePacker() : RectanglePacker(0, 0) { }
RectanglePacker::RectanglePacker(int width, int height) {
if (width > 0 && height > 0)
spaces.push_back(Rectangle { 0, 0, width, height });
}
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))
a.w = space.w;
else
b.h = space.h;
if (a.w > 0 && a.h > 0)
spaces.push_back(a);
if (b.w > 0 && b.h > 0)
spaces.push_back(b);
}
int RectanglePacker::pack(Rectangle *rectangles, int count) {
std::vector<int> remainingRects(count);
for (int i = 0; i < count; ++i)
remainingRects[i] = i;
while (!remainingRects.empty()) {
int bestFit = WORST_FIT;
int bestSpace = -1;
int bestRect = -1;
for (size_t i = 0; i < spaces.size(); ++i) {
const Rectangle &space = spaces[i];
for (size_t j = 0; j < remainingRects.size(); ++j) {
const Rectangle &rect = rectangles[remainingRects[j]];
if (rect.w == space.w && rect.h == space.h) {
bestSpace = i;
bestRect = j;
goto BEST_FIT_FOUND;
}
if (rect.w <= space.w && rect.h <= space.h) {
int fit = rateFit(rect.w, rect.h, space.w, space.h);
if (fit < bestFit) {
bestSpace = i;
bestRect = j;
bestFit = fit;
}
}
}
}
if (bestSpace < 0 || bestRect < 0)
break;
BEST_FIT_FOUND:
Rectangle &rect = rectangles[remainingRects[bestRect]];
rect.x = spaces[bestSpace].x;
rect.y = spaces[bestSpace].y;
splitSpace(bestSpace, rect.w, rect.h);
removeFromUnorderedVector(remainingRects, bestRect);
}
return (int) remainingRects.size();
}
int RectanglePacker::pack(OrientedRectangle *rectangles, int count) {
std::vector<int> remainingRects(count);
for (int i = 0; i < count; ++i)
remainingRects[i] = i;
while (!remainingRects.empty()) {
int bestFit = WORST_FIT;
int bestSpace = -1;
int bestRect = -1;
bool bestRotated = false;
for (size_t i = 0; i < spaces.size(); ++i) {
const Rectangle &space = spaces[i];
for (size_t j = 0; j < remainingRects.size(); ++j) {
const OrientedRectangle &rect = rectangles[remainingRects[j]];
if (rect.w == space.w && rect.h == space.h) {
bestSpace = i;
bestRect = j;
bestRotated = false;
goto BEST_FIT_FOUND;
}
if (rect.h == space.w && rect.w == space.h) {
bestSpace = i;
bestRect = j;
bestRotated = true;
goto BEST_FIT_FOUND;
}
if (rect.w <= space.w && rect.h <= space.h) {
int fit = rateFit(rect.w, rect.h, space.w, space.h);
if (fit < bestFit) {
bestSpace = i;
bestRect = j;
bestRotated = false;
bestFit = fit;
}
}
if (rect.h <= space.w && rect.w <= space.h) {
int fit = rateFit(rect.h, rect.w, space.w, space.h);
if (fit < bestFit) {
bestSpace = i;
bestRect = j;
bestRotated = true;
bestFit = fit;
}
}
}
}
if (bestSpace < 0 || bestRect < 0)
break;
BEST_FIT_FOUND:
OrientedRectangle &rect = rectangles[remainingRects[bestRect]];
rect.x = spaces[bestSpace].x;
rect.y = spaces[bestSpace].y;
rect.rotated = bestRotated;
if (bestRotated)
splitSpace(bestSpace, rect.h, rect.w);
else
splitSpace(bestSpace, rect.w, rect.h);
removeFromUnorderedVector(remainingRects, bestRect);
}
return (int) remainingRects.size();
}
}

View File

@ -0,0 +1,28 @@
#pragma once
#include <vector>
#include "Rectangle.h"
namespace msdf_atlas {
/// Guillotine 2D single bin packer
class RectanglePacker {
public:
RectanglePacker();
RectanglePacker(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);
private:
std::vector<Rectangle> spaces;
static int rateFit(int w, int h, int sw, int sh);
void splitSpace(int index, int w, int h);
};
}

15
msdf-atlas-gen/Remap.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
namespace msdf_atlas {
/// Represents the repositioning of a subsection of the atlas
struct Remap {
int index;
struct {
int x, y;
} source, target;
int width, height;
};
}

View File

@ -0,0 +1,168 @@
#include "TightAtlasPacker.h"
#include <vector>
#include "Rectangle.h"
#include "rectangle-packing.h"
#include "size-selectors.h"
namespace msdf_atlas {
int TightAtlasPacker::tryPack(GlyphGeometry *glyphs, int count, DimensionsConstraint dimensionsConstraint, int &width, int &height, int padding, double scale, double range, double miterLimit) {
// Wrap glyphs into boxes
std::vector<Rectangle> rectangles;
std::vector<GlyphGeometry *> rectangleGlyphs;
rectangles.reserve(count);
rectangleGlyphs.reserve(count);
for (GlyphGeometry *glyph = glyphs, *end = glyphs+count; glyph < end; ++glyph) {
if (!glyph->isWhitespace()) {
Rectangle rect = { };
glyph->wrapBox(scale, range, miterLimit);
glyph->getBoxSize(rect.w, rect.h);
if (rect.w > 0 && rect.h > 0) {
rectangles.push_back(rect);
rectangleGlyphs.push_back(glyph);
}
}
}
// No non-zero size boxes?
if (rectangles.empty()) {
if (width < 0 || height < 0)
width = 0, height = 0;
return 0;
}
// Box rectangle packing
if (width < 0 || height < 0) {
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);
break;
case DimensionsConstraint::POWER_OF_TWO_RECTANGLE:
dimensions = packRectangles<PowerOfTwoSizeSelector>(rectangles.data(), rectangles.size(), padding);
break;
case DimensionsConstraint::MULTIPLE_OF_FOUR_SQUARE:
dimensions = packRectangles<SquareSizeSelector<4> >(rectangles.data(), rectangles.size(), padding);
break;
case DimensionsConstraint::EVEN_SQUARE:
dimensions = packRectangles<SquareSizeSelector<2> >(rectangles.data(), rectangles.size(), padding);
break;
case DimensionsConstraint::SQUARE:
dimensions = packRectangles<SquareSizeSelector<> >(rectangles.data(), rectangles.size(), padding);
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))
return result;
}
// Set glyph box placement
for (size_t i = 0; i < rectangles.size(); ++i)
rectangleGlyphs[i]->placeBox(rectangles[i].x, height-(rectangles[i].y+rectangles[i].h));
return 0;
}
double TightAtlasPacker::packAndScale(GlyphGeometry *glyphs, int count, int width, int height, int padding, double unitRange, double pxRange, double miterLimit, double tolerance) {
bool lastResult = false;
#define TRY_PACK(scale) (lastResult = !tryPack(glyphs, count, DimensionsConstraint(), width, height, padding, (scale), unitRange+pxRange/(scale), miterLimit))
double minScale = 1, maxScale = 1;
if (TRY_PACK(1)) {
while (maxScale < 1e+32 && TRY_PACK(maxScale = 2*minScale))
minScale = maxScale;
} else {
while (minScale > 1e-32 && !TRY_PACK(minScale = .5*maxScale))
maxScale = minScale;
}
if (minScale == maxScale)
return 0;
while (minScale/maxScale < 1-tolerance) {
double midScale = .5*(minScale+maxScale);
if (TRY_PACK(midScale))
minScale = midScale;
else
maxScale = midScale;
}
if (!lastResult)
TRY_PACK(minScale);
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))
return remaining;
} else if (width < 0 || height < 0)
return -1;
if (scale <= 0)
scale = packAndScale(glyphs, count, width, height, padding, unitRange, pxRange, miterLimit, scaleMaximizationTolerance);
if (scale <= 0)
return -1;
pxRange += scale*unitRange;
unitRange = 0;
return 0;
}
void TightAtlasPacker::setDimensions(int width, int height) {
this->width = width, this->height = height;
}
void TightAtlasPacker::unsetDimensions() {
width = -1, height = -1;
}
void TightAtlasPacker::setDimensionsConstraint(DimensionsConstraint dimensionsConstraint) {
this->dimensionsConstraint = dimensionsConstraint;
}
void TightAtlasPacker::setPadding(int padding) {
this->padding = padding;
}
void TightAtlasPacker::setScale(double scale) {
this->scale = scale;
}
void TightAtlasPacker::setMinimumScale(double minScale) {
this->minScale = minScale;
}
void TightAtlasPacker::setUnitRange(double unitRange) {
this->unitRange = unitRange;
}
void TightAtlasPacker::setPixelRange(double pxRange) {
this->pxRange = pxRange;
}
void TightAtlasPacker::setMiterLimit(double miterLimit) {
this->miterLimit = miterLimit;
}
void TightAtlasPacker::getDimensions(int &width, int &height) const {
width = this->width, height = this->height;
}
double TightAtlasPacker::getScale() const {
return scale;
}
double TightAtlasPacker::getPixelRange() const {
return pxRange;
}
}

View File

@ -0,0 +1,71 @@
#pragma once
#include "GlyphGeometry.h"
namespace msdf_atlas {
/**
* This class computes the layout of a static 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
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 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);
/// Sets the pixel component of the total distance range
void setPixelRange(double pxRange);
/// Sets the miter limit for bounds computation
void setMiterLimit(double miterLimit);
/// 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;
private:
int width, height;
int padding;
DimensionsConstraint dimensionsConstraint;
double scale;
double minScale;
double unitRange;
double pxRange;
double miterLimit;
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);
};
}

View File

@ -0,0 +1,49 @@
#include "Workload.h"
#include <vector>
#include <thread>
#include <atomic>
namespace msdf_atlas {
Workload::Workload() : chunks(0) { }
Workload::Workload(const std::function<bool(int, int)> &workerFunction, int chunks) : workerFunction(workerFunction), chunks(chunks) { }
bool Workload::finishSequential() {
for (int i = 0; i < chunks; ++i)
if (!workerFunction(i, 0))
return false;
return true;
}
bool Workload::finishParallel(int threadCount) {
bool result = true;
std::atomic<int> next(0);
std::function<void(int)> threadWorker = [this, &result, &next](int threadNo) {
for (int i = next++; result && i < chunks; i = next++) {
if (!workerFunction(i, threadNo))
result = false;
}
};
std::vector<std::thread> threads;
threads.reserve(threadCount);
for (int i = 0; i < threadCount; ++i)
threads.emplace_back(threadWorker, i);
for (std::thread &thread : threads)
thread.join();
return result;
}
bool Workload::finish(int threadCount) {
if (!chunks)
return true;
if (threadCount == 1 || chunks == 1)
return finishSequential();
if (threadCount > 1)
return finishParallel(threadCount);
return false;
}
}

32
msdf-atlas-gen/Workload.h Normal file
View File

@ -0,0 +1,32 @@
#pragma once
#include <functional>
namespace msdf_atlas {
/**
* This function allows to split a workload into multiple threads.
* The worker function:
* bool FN(int chunk, int threadNo);
* should process the given chunk (out of chunks) and return true.
* If false is returned, the process is interrupted.
*/
class Workload {
public:
Workload();
Workload(const std::function<bool(int, int)> &workerFunction, int chunks);
/// Runs the process and returns true if all chunks have been processed
bool finish(int threadCount);
private:
std::function<bool(int, int)> workerFunction;
int chunks;
bool finishSequential();
bool finishParallel(int threadCount);
};
}

View File

@ -0,0 +1,149 @@
#include "artery-font-export.h"
#include <artery-font/std-artery-font.h>
#include <artery-font/stdio-serialization.h>
#include "image-encode.h"
namespace msdf_atlas {
static artery_font::ImageType convertImageType(ImageType imageType) {
switch (imageType) {
case ImageType::HARD_MASK:
case ImageType::SOFT_MASK:
return artery_font::IMAGE_LINEAR_MASK;
case ImageType::SDF:
return artery_font::IMAGE_SDF;
case ImageType::PSDF:
return artery_font::IMAGE_PSDF;
case ImageType::MSDF:
return artery_font::IMAGE_MSDF;
case ImageType::MTSDF:
return artery_font::IMAGE_MTSDF;
}
return artery_font::IMAGE_NONE;
}
template <typename T, int N>
static bool encodeTiff(std::vector<byte> &output, const msdfgen::BitmapConstRef<T, N> &atlas) {
// TODO
return false;
}
template <typename T>
static artery_font::PixelFormat getPixelFormat();
template <>
artery_font::PixelFormat getPixelFormat<byte>() {
return artery_font::PIXEL_UNSIGNED8;
}
template <>
artery_font::PixelFormat getPixelFormat<float>() {
return artery_font::PIXEL_FLOAT32;
}
template <typename REAL, typename T, int N>
bool exportArteryFont(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef<T, N> &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename) {
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 = artery_font::CP_UNICODE;
fontVariant.imageType = convertImageType(imageType);
fontVariant.metrics.fontSize = REAL(fontSize);
if (imageType != ImageType::HARD_MASK)
fontVariant.metrics.distanceRange = REAL(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].getCodepoint();
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);
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.v = REAL(0);
for (int j = 0; j < glyphCount; ++j) {
double kerning;
if (msdfgen::getKerning(kerning, font, glyphs[i].getCodepoint(), glyphs[j].getCodepoint()) && kerning) {
artery_font::KernPair<REAL> kernPair = { };
kernPair.codepoint1 = glyphs[i].getCodepoint();
kernPair.codepoint2 = glyphs[j].getCodepoint();
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);
}
{
artery_font::StdImage image = { };
image.width = atlas.width;
image.height = atlas.height;
image.channels = N;
image.imageType = convertImageType(imageType);
switch (imageFormat) {
case ImageFormat::PNG:
image.encoding = artery_font::IMAGE_PNG;
image.pixelFormat = artery_font::PIXEL_UNSIGNED8;
if (!encodePng(image.data.vector, atlas))
return false;
break;
case ImageFormat::TIFF:
image.encoding = artery_font::IMAGE_TIFF;
image.pixelFormat = artery_font::PIXEL_FLOAT32;
if (!encodeTiff(image.data.vector, atlas))
return false;
break;
case ImageFormat::BINARY:
image.pixelFormat = artery_font::PIXEL_UNSIGNED8;
goto BINARY_EITHER;
case ImageFormat::BINARY_FLOAT:
image.pixelFormat = artery_font::PIXEL_FLOAT32;
goto BINARY_EITHER;
BINARY_EITHER:
if (image.pixelFormat != getPixelFormat<T>())
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);
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, double fontSize, double pxRange, const msdfgen::BitmapConstRef<byte, 1> &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename);
template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef<byte, 3> &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename);
template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef<byte, 4> &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename);
template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef<float, 1> &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename);
template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef<float, 3> &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename);
template bool exportArteryFont<float>(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, double fontSize, double pxRange, const msdfgen::BitmapConstRef<float, 4> &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename);
}

View File

@ -0,0 +1,15 @@
#pragma once
#include <msdfgen.h>
#include <msdfgen-ext.h>
#include "types.h"
#include "GlyphGeometry.h"
namespace msdf_atlas {
/// 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, double fontSize, double pxRange, const msdfgen::BitmapConstRef<T, N> &atlas, ImageType imageType, ImageFormat imageFormat, const char *filename);
}

View File

@ -0,0 +1,58 @@
#include "bitmap-blit.h"
#include <cstring>
namespace msdf_atlas {
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) {
for (int y = 0; y < h; ++y)
memcpy(dst(dx, dy+y), src(sx, sy+y), sizeof(T)*N*w);
}
#define BLIT_SAME_TYPE_IMPL(T, N) void blit(const msdfgen::BitmapRef<T, N> &dst, const msdfgen::BitmapConstRef<T, N> &src, int dx, int dy, int sx, int sy, int w, int h) { blitSameType(dst, src, dx, dy, sx, sy, w, h); }
BLIT_SAME_TYPE_IMPL(byte, 1)
BLIT_SAME_TYPE_IMPL(byte, 3)
BLIT_SAME_TYPE_IMPL(byte, 4)
BLIT_SAME_TYPE_IMPL(float, 1)
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) {
for (int y = 0; y < h; ++y) {
byte *dstPixel = dst(dx, dy+y);
for (int x = 0; x < w; ++x) {
const float *srcPixel = src(sx+x, sy+y);
*dstPixel++ = msdfgen::pixelFloatToByte(*srcPixel);
}
}
}
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) {
for (int y = 0; y < h; ++y) {
byte *dstPixel = dst(dx, dy+y);
for (int x = 0; x < w; ++x) {
const float *srcPixel = src(sx+x, sy+y);
*dstPixel++ = msdfgen::pixelFloatToByte(srcPixel[0]);
*dstPixel++ = msdfgen::pixelFloatToByte(srcPixel[1]);
*dstPixel++ = msdfgen::pixelFloatToByte(srcPixel[2]);
}
}
}
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) {
for (int y = 0; y < h; ++y) {
byte *dstPixel = dst(dx, dy+y);
for (int x = 0; x < w; ++x) {
const float *srcPixel = src(sx+x, sy+y);
*dstPixel++ = msdfgen::pixelFloatToByte(srcPixel[0]);
*dstPixel++ = msdfgen::pixelFloatToByte(srcPixel[1]);
*dstPixel++ = msdfgen::pixelFloatToByte(srcPixel[2]);
*dstPixel++ = msdfgen::pixelFloatToByte(srcPixel[3]);
}
}
}
}

View File

@ -0,0 +1,26 @@
#pragma once
#include <msdfgen.h>
#include "types.h"
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);
void blit(const msdfgen::BitmapRef<byte, 3> &dst, const msdfgen::BitmapConstRef<byte, 3> &src, int dx, int dy, int sx, int sy, int w, int h);
void blit(const msdfgen::BitmapRef<byte, 4> &dst, const msdfgen::BitmapConstRef<byte, 4> &src, int dx, int dy, int sx, int sy, int w, int h);
void blit(const msdfgen::BitmapRef<float, 1> &dst, const msdfgen::BitmapConstRef<float, 1> &src, int dx, int dy, int sx, int sy, int w, int h);
void blit(const msdfgen::BitmapRef<float, 3> &dst, const msdfgen::BitmapConstRef<float, 3> &src, int dx, int dy, int sx, int sy, int w, int h);
void blit(const msdfgen::BitmapRef<float, 4> &dst, const msdfgen::BitmapConstRef<float, 4> &src, int dx, int dy, int sx, int sy, int w, int h);
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);
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);
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);
}

View File

@ -0,0 +1,251 @@
#include "Charset.h"
#include <cstdio>
#include <string>
#include "utf8.h"
namespace msdf_atlas {
static char escapedChar(char c) {
switch (c) {
case '0':
return '\0';
case 'n': case 'N':
return '\n';
case 'r': case 'R':
return '\r';
case 's': case 'S':
return ' ';
case 't': case 'T':
return '\t';
case '\\': case '"': case '\'':
default:
return 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
str += 2;
for (; *str; ++str) {
if (*str >= '0' && *str <= '9') {
i <<= 4;
i += *str-'0';
} else if (*str >= 'A' && *str <= 'F') {
i <<= 4;
i += *str-'A'+10;
} else if (*str >= 'a' && *str <= 'f') {
i <<= 4;
i += *str-'a'+10;
} else
return false;
}
} else { // dec
for (; *str; ++str) {
if (*str >= '0' && *str <= '9') {
i *= 10;
i += *str-'0';
} else
return false;
}
}
return true;
}
static std::string combinePath(const char *basePath, const char *relPath) {
if (relPath[0] == '/' || (relPath[0] && relPath[1] == ':')) // absolute path?
return relPath;
int lastSlash = -1;
for (int i = 0; basePath[i]; ++i)
if (basePath[i] == '/' || basePath[i] == '\\')
lastSlash = i;
if (lastSlash < 0)
return relPath;
return std::string(basePath, lastSlash+1)+relPath;
}
bool Charset::load(const char *filename) {
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))
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();
state = TIGHT;
break;
case '"': // string of UTF-8 characters
if (state != CLEAR)
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;
}
return false;
}
}

View File

@ -0,0 +1,27 @@
#include "csv-export.h"
#include <cstdio>
namespace msdf_atlas {
bool exportCSV(const GlyphGeometry *glyphs, int glyphCount, double emSize, 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, "%u,%.17g,", glyphs[i].getCodepoint(), 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);
}
fclose(f);
return true;
}
}

View File

@ -0,0 +1,14 @@
#pragma once
#include "GlyphGeometry.h"
namespace msdf_atlas {
/**
* Writes the positioning data and atlas layout of the glyphs into a CSV file
* The columns are: Unicode index, horizontal advance, plane bounds (l, b, r, t), atlas bounds (l, b, r, t)
*/
bool exportCSV(const GlyphGeometry *glyphs, int glyphCount, double emSize, const char *filename);
}

View File

@ -0,0 +1,51 @@
#include "glyph-generators.h"
namespace msdf_atlas {
template <int N>
static void invertColor(const msdfgen::BitmapRef<float, N> &bitmap) {
const float *end = bitmap.pixels+N*bitmap.width*bitmap.height;
for (float *p = bitmap.pixels; p < end; ++p)
*p = 1.f-*p;
}
void scanlineGenerator(const msdfgen::BitmapRef<float, 1> &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) {
msdfgen::rasterize(output, glyph.getShape(), glyph.getBoxScale(), glyph.getBoxTranslate(), MSDF_ATLAS_GLYPH_FILL_RULE);
}
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);
if (glyph.isWindingReverse())
invertColor(output);
if (attribs.scanlinePass)
msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxScale(), glyph.getBoxTranslate(), 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);
if (glyph.isWindingReverse())
invertColor(output);
if (attribs.scanlinePass)
msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxScale(), glyph.getBoxTranslate(), 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(), 0, attribs.overlapSupport);
if (glyph.isWindingReverse())
invertColor(output);
if (attribs.scanlinePass)
msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxScale(), glyph.getBoxTranslate(), MSDF_ATLAS_GLYPH_FILL_RULE);
msdfgen::msdfErrorCorrection(output, attribs.errorCorrectionThreshold/(glyph.getBoxScale()*glyph.getBoxRange()));
}
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(), 0, attribs.overlapSupport);
if (glyph.isWindingReverse())
invertColor(output);
if (attribs.scanlinePass)
msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxScale(), glyph.getBoxTranslate(), MSDF_ATLAS_GLYPH_FILL_RULE);
msdfgen::msdfErrorCorrection(output, attribs.errorCorrectionThreshold/(glyph.getBoxScale()*glyph.getBoxRange()));
}
}

View File

@ -0,0 +1,25 @@
#pragma once
#include <msdfgen.h>
#include "GlyphGeometry.h"
#include "AtlasGenerator.h"
#define MSDF_ATLAS_GLYPH_FILL_RULE msdfgen::FILL_NONZERO
namespace msdf_atlas {
// Glyph bitmap generator functions
/// Generates non-anti-aliased binary image of the glyph using scanline rasterization
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
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);
/// Generates a multi-channel and alpha-encoded true signed distance field of the glyph
void mtsdfGenerator(const msdfgen::BitmapRef<float, 4> &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs);
}

View File

@ -0,0 +1,63 @@
#include "image-encode.h"
#include <lodepng.h>
namespace msdf_atlas {
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<msdfgen::byte, 1> &bitmap) {
std::vector<byte> pixels(bitmap.width*bitmap.height);
for (int y = 0; y < bitmap.height; ++y)
memcpy(&pixels[bitmap.width*y], bitmap(0, bitmap.height-y-1), bitmap.width);
return !lodepng::encode(output, pixels, bitmap.width, bitmap.height, LCT_GREY);
}
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<msdfgen::byte, 3> &bitmap) {
std::vector<byte> pixels(3*bitmap.width*bitmap.height);
for (int y = 0; y < bitmap.height; ++y)
memcpy(&pixels[3*bitmap.width*y], bitmap(0, bitmap.height-y-1), 3*bitmap.width);
return !lodepng::encode(output, pixels, bitmap.width, bitmap.height, LCT_RGB);
}
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<msdfgen::byte, 4> &bitmap) {
std::vector<byte> pixels(4*bitmap.width*bitmap.height);
for (int y = 0; y < bitmap.height; ++y)
memcpy(&pixels[4*bitmap.width*y], bitmap(0, bitmap.height-y-1), 4*bitmap.width);
return !lodepng::encode(output, pixels, bitmap.width, bitmap.height, LCT_RGBA);
}
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<float, 1> &bitmap) {
std::vector<byte> pixels(bitmap.width*bitmap.height);
std::vector<byte>::iterator it = pixels.begin();
for (int y = bitmap.height-1; y >= 0; --y)
for (int x = 0; x < bitmap.width; ++x)
*it++ = msdfgen::pixelFloatToByte(*bitmap(x, y));
return !lodepng::encode(output, pixels, bitmap.width, bitmap.height, LCT_GREY);
}
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<float, 3> &bitmap) {
std::vector<byte> pixels(3*bitmap.width*bitmap.height);
std::vector<byte>::iterator it = pixels.begin();
for (int y = bitmap.height-1; y >= 0; --y)
for (int x = 0; x < bitmap.width; ++x) {
*it++ = msdfgen::pixelFloatToByte(bitmap(x, y)[0]);
*it++ = msdfgen::pixelFloatToByte(bitmap(x, y)[1]);
*it++ = msdfgen::pixelFloatToByte(bitmap(x, y)[2]);
}
return !lodepng::encode(output, pixels, bitmap.width, bitmap.height, LCT_RGB);
}
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<float, 4> &bitmap) {
std::vector<byte> pixels(4*bitmap.width*bitmap.height);
std::vector<byte>::iterator it = pixels.begin();
for (int y = bitmap.height-1; y >= 0; --y)
for (int x = 0; x < bitmap.width; ++x) {
*it++ = msdfgen::pixelFloatToByte(bitmap(x, y)[0]);
*it++ = msdfgen::pixelFloatToByte(bitmap(x, y)[1]);
*it++ = msdfgen::pixelFloatToByte(bitmap(x, y)[2]);
*it++ = msdfgen::pixelFloatToByte(bitmap(x, y)[3]);
}
return !lodepng::encode(output, pixels, bitmap.width, bitmap.height, LCT_RGBA);
}
}

View File

@ -0,0 +1,20 @@
#pragma once
#include <vector>
#include <msdfgen.h>
#include "types.h"
namespace msdf_atlas {
// Functions to encode an image as a sequence of bytes in memory
// Only PNG format available currently
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<msdfgen::byte, 1> &bitmap);
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<msdfgen::byte, 3> &bitmap);
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<msdfgen::byte, 4> &bitmap);
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<float, 1> &bitmap);
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<float, 3> &bitmap);
bool encodePng(std::vector<byte> &output, const msdfgen::BitmapConstRef<float, 4> &bitmap);
}

View File

@ -0,0 +1,15 @@
#pragma once
#include <msdfgen.h>
#include "types.h"
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);
}
#include "image-save.hpp"

View File

@ -0,0 +1,151 @@
#include "image-save.h"
#include <cstdio>
#include <msdfgen-ext.h>
namespace msdf_atlas {
template <int N>
bool saveImageBinary(const msdfgen::BitmapConstRef<byte, N> &bitmap, const char *filename);
template <int N>
bool saveImageBinaryLE(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename);
template <int N>
bool saveImageBinaryBE(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename);
template <int N>
bool saveImageText(const msdfgen::BitmapConstRef<byte, N> &bitmap, const char *filename);
template <int N>
bool saveImageText(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename);
template <int N>
bool saveImage(const msdfgen::BitmapConstRef<byte, N> &bitmap, ImageFormat format, const char *filename) {
switch (format) {
case ImageFormat::PNG:
return msdfgen::savePng(bitmap, filename);
case ImageFormat::BMP:
return msdfgen::saveBmp(bitmap, filename);
case ImageFormat::TIFF:
return false;
case ImageFormat::TEXT:
return saveImageText(bitmap, filename);
case ImageFormat::TEXT_FLOAT:
return false;
case ImageFormat::BINARY:
return saveImageBinary(bitmap, filename);
case ImageFormat::BINARY_FLOAT:
case ImageFormat::BINARY_FLOAT_BE:
return false;
default:;
}
return false;
}
template <int N>
bool saveImage(const msdfgen::BitmapConstRef<float, N> &bitmap, ImageFormat format, const char *filename) {
switch (format) {
case ImageFormat::PNG:
return msdfgen::savePng(bitmap, filename);
case ImageFormat::BMP:
return msdfgen::saveBmp(bitmap, filename);
case ImageFormat::TIFF:
return msdfgen::saveTiff(bitmap, filename);
case ImageFormat::TEXT:
return false;
case ImageFormat::TEXT_FLOAT:
return saveImageText(bitmap, filename);
case ImageFormat::BINARY:
return false;
case ImageFormat::BINARY_FLOAT:
return saveImageBinaryLE(bitmap, filename);
case ImageFormat::BINARY_FLOAT_BE:
return saveImageBinaryBE(bitmap, filename);
default:;
}
return false;
}
template <int N>
bool saveImageBinary(const msdfgen::BitmapConstRef<byte, N> &bitmap, const char *filename) {
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;
fclose(f);
}
return success;
}
template <int N>
bool
#ifdef __BIG_ENDIAN__
saveImageBinaryBE
#else
saveImageBinaryLE
#endif
(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename) {
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;
fclose(f);
}
return success;
}
template <int N>
bool
#ifdef __BIG_ENDIAN__
saveImageBinaryLE
#else
saveImageBinaryBE
#endif
(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename) {
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);
}
success = written == sizeof(float)*count;
fclose(f);
}
return success;
}
template <int N>
bool saveImageText(const msdfgen::BitmapConstRef<byte, N> &bitmap, const char *filename) {
bool success = false;
if (FILE *f = fopen(filename, "wb")) {
const byte *p = bitmap.pixels;
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");
}
fclose(f);
}
return success;
}
template <int N>
bool saveImageText(const msdfgen::BitmapConstRef<float, N> &bitmap, const char *filename) {
bool success = false;
if (FILE *f = fopen(filename, "wb")) {
const float *p = bitmap.pixels;
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");
}
fclose(f);
}
return success;
}
}

View File

@ -0,0 +1,95 @@
#include "json-export.h"
namespace msdf_atlas {
static const char * imageTypeString(ImageType type) {
switch (type) {
case ImageType::HARD_MASK:
return "hardmask";
case ImageType::SOFT_MASK:
return "softmask";
case ImageType::SDF:
return "sdf";
case ImageType::PSDF:
return "psdf";
case ImageType::MSDF:
return "msdf";
case ImageType::MTSDF:
return "mtsdf";
}
return nullptr;
}
bool exportJSON(msdfgen::FontHandle *font, const GlyphGeometry *glyphs, int glyphCount, 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;
FILE *f = fopen(filename, "w");
if (!f)
return false;
fputs("{", f);
// 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);
fprintf(f, "\"unicode\":%u,", glyphs[i].getCodepoint());
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;
}
}
}
fputs("]", f);
fputs("}\n", f);
fclose(f);
return true;
}
}

View File

@ -0,0 +1,14 @@
#pragma once
#include <msdfgen.h>
#include <msdfgen-ext.h>
#include "types.h"
#include "GlyphGeometry.h"
namespace msdf_atlas {
/// 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, double fontSize, double pxRange, int atlasWidth, int atlasHeight, ImageType imageType, const char *filename);
}

688
msdf-atlas-gen/main.cpp Normal file
View File

@ -0,0 +1,688 @@
/*
* MULTI-CHANNEL SIGNED DISTANCE FIELD ATLAS GENERATOR v1.0 (2020-03-08) - standalone console program
* --------------------------------------------------------------------------------------------------
* A utility by Viktor Chlumsky, (c) 2020
*
*/
#ifdef MSDF_ATLAS_STANDALONE
#define _USE_MATH_DEFINES
#include <cstdio>
#include <cmath>
#include <cassert>
#include <algorithm>
#include <thread>
#include "msdf-atlas-gen.h"
using namespace msdf_atlas;
#define DEFAULT_ANGLE_THRESHOLD 3.0
#define DEFAULT_MITER_LIMIT 1.0
#define DEFAULT_EM_SIZE 32.0
#define DEFAULT_PIXEL_RANGE 2.0
#define SDF_ERROR_ESTIMATE_PRECISION 19
#define GLYPH_FILL_RULE msdfgen::FILL_NONZERO
#define MCG_MULTIPLIER 6364136223846793005ull
static const char * const helpText = R"(
MSDF Atlas Generator by Viktor Chlumsky v)" MSDF_ATLAS_VERSION R"( (with MSDFGEN v)" MSDFGEN_VERSION R"()
----------------------------------------------------------------
INPUT SPECIFICATION
-font <filename.ttf/otf>
Specifies the input TrueType / OpenType font file. This is required.
-charset <filename>
Specifies the input character set. Refer to the documentation for format of charset specification. Defaults to ASCII.
ATLAS CONFIGURATION
-type <hardmask / softmask / sdf / psdf / msdf / mtsdf>
Selects the type of atlas to be generated.
-format <png / bmp / tiff / text / textfloat / bin / binfloat / binfloatbe>
Selects the format for the atlas image output. Some image formats may be incompatible with embedded output formats.
-dimensions <width> <height>
Sets the atlas to have fixed dimensions (width x height).
-pots / -potr / -square / -square2 / -square4
Picks the minimum atlas dimensions that fit all glyphs and satisfy the selected constraint:
power of two square / ... rectangle / any square / square with side divisible by 2 / ... 4
OUTPUT SPECIFICATION - one or more can be specified
-imageout <filename.*>
Saves the atlas as an image file with the specified format. Layout data must be stored separately.
-json <filename.json>
Writes the atlas's layout data, as well as other metrics into a structured JSON file.
-csv <filename.csv>
Writes the layout data of the glyphs into a simple CSV file.
-arfont <filename.arfont>
Stores the atlas and its layout data as an Artery Font file. Supported formats: png, bin, binfloat.
-shadronpreview <filename.shadron> <sample text>
Generates a Shadron script that uses the generated atlas to draw a sample text as a preview.
GLYPH CONFIGURATION
-size <EM size>
Specified the size of the glyphs in the atlas bitmap in pixels per EM.
-minsize <EM size>
Specifies the minimum size. The largest possible size that fits the same atlas dimensions will be used.
-emrange <EM range>
Specifies the SDF distance range in EM's.
-pxrange <pixel range>
Specifies the SDF distance range in output pixels. The default value is 2.
DISTANCE FIELD GENERATOR SETTINGS
-angle <angle>
Specifies the minimum angle between adjacent edges to be considered a corner. Append D for degrees. (msdf / mtsdf only)
-errorcorrection <threshold>
Changes the threshold used to detect and correct potential artifacts. 0 disables error correction. (msdf / mtsdf only)
-miterlimit <value>
Sets the miter limit that limits the extension of each glyph's bounding box due to very sharp corners. (psdf / msdf / mtsdf only)
-nooverlap
Disables resolution of overlapping contours.
-noscanline
Disables the scanline pass, which corrects the distance field's signs according to the non-zero fill rule.
-seed <N>
Sets the initial seed for the edge coloring heuristic.
)";
static char toupper(char c) {
return c >= 'a' && c <= 'z' ? c-'a'+'A' : c;
}
static bool parseUnsigned(unsigned &value, const char *arg) {
static char c;
return sscanf(arg, "%u%c", &value, &c) == 1;
}
static bool parseUnsignedLL(unsigned long long &value, const char *arg) {
static char c;
return sscanf(arg, "%llu%c", &value, &c) == 1;
}
static bool parseDouble(double &value, const char *arg) {
static char c;
return sscanf(arg, "%lf%c", &value, &c) == 1;
}
static bool parseAngle(double &value, const char *arg) {
char c1, c2;
int result = sscanf(arg, "%lf%c%c", &value, &c1, &c2);
if (result == 1)
return true;
if (result == 2 && (c1 == 'd' || c1 == 'D')) {
value *= M_PI/180;
return true;
}
return false;
}
static bool cmpExtension(const char *path, const char *ext) {
for (const char *a = path+strlen(path)-1, *b = ext+strlen(ext)-1; b >= ext; --a, --b)
if (a < path || toupper(*a) != toupper(*b))
return false;
return true;
}
static void loadGlyphs(std::vector<GlyphGeometry> &glyphs, msdfgen::FontHandle *font, const Charset &charset) {
glyphs.clear();
glyphs.reserve(charset.size());
for (unicode_t cp : charset) {
GlyphGeometry glyph;
if (glyph.load(font, cp))
glyphs.push_back((GlyphGeometry &&) glyph);
else
printf("Glyph for codepoint 0x%X missing\n", cp);
}
}
struct Configuration {
ImageType imageType;
ImageFormat imageFormat;
int width, height;
double emSize;
double pxRange;
double angleThreshold;
double miterLimit;
unsigned long long coloringSeed;
GeneratorAttributes generatorAttributes;
int threadCount;
const char *arteryFontFilename;
const char *imageFilename;
const char *jsonFilename;
const char *csvFilename;
const char *shadronPreviewFilename;
const char *shadronPreviewText;
};
template <typename T, typename S, int N, GeneratorFunction<S, N> GEN_FN>
static bool makeAtlas(const std::vector<GlyphGeometry> &glyphs, msdfgen::FontHandle *font, const Configuration &config) {
ImmediateAtlasGenerator<S, N, GEN_FN, BitmapAtlasStorage<T, N> > generator(config.width, config.height);
generator.setAttributes(config.generatorAttributes);
generator.setThreadCount(config.threadCount);
generator.generate(glyphs.data(), glyphs.size());
msdfgen::BitmapConstRef<T, N> bitmap = (msdfgen::BitmapConstRef<T, N>) generator.atlasStorage();
bool success = true;
if (config.imageFilename) {
if (saveImage(bitmap, config.imageFormat, config.imageFilename))
puts("Atlas image file saved.");
else {
success = false;
puts("Failed to save the atlas as an image file.");
}
}
if (config.arteryFontFilename) {
if (exportArteryFont<float>(font, glyphs.data(), glyphs.size(), config.emSize, config.pxRange, bitmap, config.imageType, config.imageFormat, config.arteryFontFilename))
puts("Artery Font file generated.");
else {
success = false;
puts("Failed to generate Artery Font file.");
}
}
return success;
}
int main(int argc, const char * const *argv) {
#define ABORT(msg) { puts(msg); return 1; }
int result = 0;
Configuration config = { };
const char *fontFilename = nullptr;
const char *charsetFilename = nullptr;
config.imageType = ImageType::MSDF;
config.imageFormat = ImageFormat::UNSPECIFIED;
const char *imageFormatName = nullptr;
int fixedWidth = -1, fixedHeight = -1;
config.generatorAttributes.overlapSupport = true;
config.generatorAttributes.scanlinePass = true;
config.generatorAttributes.errorCorrectionThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD;
double minEmSize = 0;
enum {
/// Range specified in EMs
RANGE_EM,
/// Range specified in output pixels
RANGE_PIXEL,
} rangeMode = RANGE_PIXEL;
double rangeValue = 0;
TightAtlasPacker::DimensionsConstraint atlasSizeConstraint = TightAtlasPacker::DimensionsConstraint::MULTIPLE_OF_FOUR_SQUARE;
config.angleThreshold = DEFAULT_ANGLE_THRESHOLD;
config.miterLimit = DEFAULT_MITER_LIMIT;
config.threadCount = std::max((int) std::thread::hardware_concurrency(), 1);
// Parse command line
int argPos = 1;
bool suggestHelp = false;
while (argPos < argc) {
const char *arg = argv[argPos];
#define ARG_CASE(s, p) if (!strcmp(arg, s) && argPos+(p) < argc)
ARG_CASE("-type", 1) {
arg = argv[++argPos];
if (!strcmp(arg, "hardmask"))
config.imageType = ImageType::HARD_MASK;
else if (!strcmp(arg, "softmask"))
config.imageType = ImageType::SOFT_MASK;
else if (!strcmp(arg, "sdf"))
config.imageType = ImageType::SDF;
else if (!strcmp(arg, "psdf"))
config.imageType = ImageType::PSDF;
else if (!strcmp(arg, "msdf"))
config.imageType = ImageType::MSDF;
else if (!strcmp(arg, "mtsdf"))
config.imageType = ImageType::MTSDF;
else
ABORT("Invalid atlas type. Valid types are: hardmask, softmask, sdf, psdf, msdf, mtsdf");
++argPos;
continue;
}
ARG_CASE("-format", 1) {
arg = argv[++argPos];
if (!strcmp(arg, "png"))
config.imageFormat = ImageFormat::PNG;
else if (!strcmp(arg, "bmp"))
config.imageFormat = ImageFormat::BMP;
else if (!strcmp(arg, "tiff"))
config.imageFormat = ImageFormat::TIFF;
else if (!strcmp(arg, "text"))
config.imageFormat = ImageFormat::TEXT;
else if (!strcmp(arg, "textfloat"))
config.imageFormat = ImageFormat::TEXT_FLOAT;
else if (!strcmp(arg, "bin"))
config.imageFormat = ImageFormat::BINARY;
else if (!strcmp(arg, "binfloat"))
config.imageFormat = ImageFormat::BINARY_FLOAT;
else if (!strcmp(arg, "binfloatbe"))
config.imageFormat = ImageFormat::BINARY_FLOAT_BE;
else
ABORT("Invalid image format. Valid formats are: png, bmp, tiff, text, textfloat, bin, binfloat");
imageFormatName = arg;
++argPos;
continue;
}
ARG_CASE("-font", 1) {
fontFilename = argv[++argPos];
++argPos;
continue;
}
ARG_CASE("-charset", 1) {
charsetFilename = argv[++argPos];
++argPos;
continue;
}
ARG_CASE("-arfont", 1) {
config.arteryFontFilename = argv[++argPos];
++argPos;
continue;
}
ARG_CASE("-imageout", 1) {
config.imageFilename = argv[++argPos];
++argPos;
continue;
}
ARG_CASE("-json", 1) {
config.jsonFilename = argv[++argPos];
++argPos;
continue;
}
ARG_CASE("-csv", 1) {
config.csvFilename = argv[++argPos];
++argPos;
continue;
}
ARG_CASE("-shadronpreview", 2) {
config.shadronPreviewFilename = argv[++argPos];
config.shadronPreviewText = argv[++argPos];
++argPos;
continue;
}
ARG_CASE("-dimensions", 2) {
unsigned w, h;
if (!(parseUnsigned(w, argv[argPos+1]) && parseUnsigned(h, argv[argPos+2]) && w && h))
ABORT("Invalid atlas dimensions. Use -dimensions <width> <height> with two positive integers.");
fixedWidth = w, fixedHeight = h;
argPos += 3;
continue;
}
ARG_CASE("-pots", 0) {
atlasSizeConstraint = TightAtlasPacker::DimensionsConstraint::POWER_OF_TWO_SQUARE;
fixedWidth = -1, fixedHeight = -1;
++argPos;
continue;
}
ARG_CASE("-potr", 0) {
atlasSizeConstraint = TightAtlasPacker::DimensionsConstraint::POWER_OF_TWO_RECTANGLE;
fixedWidth = -1, fixedHeight = -1;
++argPos;
continue;
}
ARG_CASE("-square", 0) {
atlasSizeConstraint = TightAtlasPacker::DimensionsConstraint::SQUARE;
fixedWidth = -1, fixedHeight = -1;
++argPos;
continue;
}
ARG_CASE("-square2", 0) {
atlasSizeConstraint = TightAtlasPacker::DimensionsConstraint::EVEN_SQUARE;
fixedWidth = -1, fixedHeight = -1;
++argPos;
continue;
}
ARG_CASE("-square4", 0) {
atlasSizeConstraint = TightAtlasPacker::DimensionsConstraint::MULTIPLE_OF_FOUR_SQUARE;
fixedWidth = -1, fixedHeight = -1;
++argPos;
continue;
}
ARG_CASE("-size", 1) {
double s;
if (!(parseDouble(s, argv[++argPos]) && s > 0))
ABORT("Invalid EM size argument. Use -size <EM size> with a positive real number.");
config.emSize = s;
++argPos;
continue;
}
ARG_CASE("-minsize", 1) {
double s;
if (!(parseDouble(s, argv[++argPos]) && s > 0))
ABORT("Invalid minimum EM size argument. Use -minsize <EM size> with a positive real number.");
minEmSize = s;
++argPos;
continue;
}
ARG_CASE("-emrange", 1) {
double r;
if (!(parseDouble(r, argv[++argPos]) && r >= 0))
ABORT("Invalid range argument. Use -emrange <EM range> with a positive real number.");
rangeMode = RANGE_EM;
rangeValue = r;
++argPos;
continue;
}
ARG_CASE("-pxrange", 1) {
double r;
if (!(parseDouble(r, argv[++argPos]) && r >= 0))
ABORT("Invalid range argument. Use -pxrange <pixel range> with a positive real number.");
rangeMode = RANGE_PIXEL;
rangeValue = r;
++argPos;
continue;
}
ARG_CASE("-angle", 1) {
double at;
if (!parseAngle(at, argv[argPos+1]))
ABORT("Invalid angle threshold. Use -angle <min angle> with a positive real number less than PI or a value in degrees followed by 'd' below 180d.");
config.angleThreshold = at;
argPos += 2;
continue;
}
ARG_CASE("-errorcorrection", 1) {
double ect;
if (!parseDouble(ect, argv[argPos+1]) || ect < 0)
ABORT("Invalid error correction threshold. Use -errorcorrection <threshold> with a real number larger or equal to 1.");
config.generatorAttributes.errorCorrectionThreshold = ect;
argPos += 2;
continue;
}
ARG_CASE("-miterlimit", 1) {
double m;
if (!(parseDouble(m, argv[++argPos]) && m >= 0))
ABORT("Invalid miter limit argument. Use -miterlimit <limit> with a positive real number.");
config.miterLimit = m;
++argPos;
continue;
}
ARG_CASE("-nooverlap", 0) {
config.generatorAttributes.overlapSupport = false;
argPos += 1;
continue;
}
ARG_CASE("-noscanline", 0) {
config.generatorAttributes.scanlinePass = false;
argPos += 1;
continue;
}
ARG_CASE("-scanline", 0) {
config.generatorAttributes.scanlinePass = true;
argPos += 1;
continue;
}
ARG_CASE("-seed", 1) {
if (!parseUnsignedLL(config.coloringSeed, argv[argPos+1]))
ABORT("Invalid seed. Use -seed <N> with N being a non-negative integer.");
argPos += 2;
continue;
}
ARG_CASE("-help", 0) {
puts(helpText);
return 0;
}
printf("Unknown setting or insufficient parameters: %s\n", arg);
suggestHelp = true;
++argPos;
}
if (suggestHelp)
printf("Use -help for more information.\n");
// Nothing to do?
if (argc == 1) {
printf(
"Usage: msdf-atlas-gen"
#ifdef _WIN32
".exe"
#endif
" -font <filename.ttf/otf> -charset <charset> <output specification> <options>\n"
"Use -help for more information.\n"
);
return 0;
}
if (!fontFilename)
ABORT("No font specified.");
if (!(config.arteryFontFilename || config.imageFilename || config.jsonFilename || config.csvFilename || config.shadronPreviewFilename)) {
puts("No output specified.");
return 0;
}
bool layoutOnly = !(config.arteryFontFilename || config.imageFilename);
// Fix up configuration based on related values
if (!(config.imageType == ImageType::PSDF || config.imageType == ImageType::MSDF || config.imageType == ImageType::MTSDF))
config.miterLimit = 0;
if (config.emSize > minEmSize)
minEmSize = config.emSize;
if (!(fixedWidth > 0 && fixedHeight > 0) && !(minEmSize > 0)) {
puts("Neither atlas size nor glyph size selected, using default...");
minEmSize = DEFAULT_EM_SIZE;
}
if (!(config.imageType == ImageType::SDF || config.imageType == ImageType::PSDF || config.imageType == ImageType::MSDF || config.imageType == ImageType::MTSDF)) {
rangeMode = RANGE_PIXEL;
rangeValue = (double) (config.imageType == ImageType::SOFT_MASK);
} else if (rangeValue <= 0) {
rangeMode = RANGE_PIXEL;
rangeValue = DEFAULT_PIXEL_RANGE;
}
// Finalize image format
ImageFormat imageExtension = ImageFormat::UNSPECIFIED;
if (config.imageFilename) {
if (cmpExtension(config.imageFilename, ".png")) imageExtension = ImageFormat::PNG;
else if (cmpExtension(config.imageFilename, ".bmp")) imageExtension = ImageFormat::BMP;
else if (cmpExtension(config.imageFilename, ".tif") || cmpExtension(config.imageFilename, ".tiff")) imageExtension = ImageFormat::TIFF;
else if (cmpExtension(config.imageFilename, ".txt")) imageExtension = ImageFormat::TEXT;
else if (cmpExtension(config.imageFilename, ".bin")) imageExtension = ImageFormat::BINARY;
}
if (config.imageFormat == ImageFormat::UNSPECIFIED) {
config.imageFormat = ImageFormat::PNG;
imageFormatName = "png";
// If image format is not specified and -imageout is the only image output, infer format from its extension
if (imageExtension != ImageFormat::UNSPECIFIED && !config.arteryFontFilename)
config.imageFormat = imageExtension;
}
if (config.imageType == ImageType::MTSDF && config.imageFormat == ImageFormat::BMP)
ABORT("Atlas type not compatible with image format. MTSDF requires a format with alpha channel.");
if (config.arteryFontFilename && !(config.imageFormat == ImageFormat::PNG || config.imageFormat == ImageFormat::BINARY || config.imageFormat == ImageFormat::BINARY_FLOAT)) {
config.arteryFontFilename = nullptr;
result = 1;
puts("Error: Unable to create an Artery Font file with the specified image format!");
// Recheck whether there is anything else to do
if (!(config.arteryFontFilename || config.imageFilename || config.jsonFilename || config.csvFilename || config.shadronPreviewFilename))
return result;
layoutOnly = !(config.arteryFontFilename || config.imageFilename);
}
if (imageExtension != ImageFormat::UNSPECIFIED) {
// Warn if image format mismatches -imageout extension
bool mismatch = false;
switch (config.imageFormat) {
case ImageFormat::TEXT: case ImageFormat::TEXT_FLOAT:
mismatch = imageExtension != ImageFormat::TEXT;
break;
case ImageFormat::BINARY: case ImageFormat::BINARY_FLOAT: case ImageFormat::BINARY_FLOAT_BE:
mismatch = imageExtension != ImageFormat::BINARY;
break;
default:
mismatch = imageExtension != config.imageFormat;
}
if (mismatch)
printf("Warning: Output image file extension does not match the image's actual format (%s)!\n", imageFormatName);
}
imageFormatName = nullptr; // No longer consistent with imageFormat
// Load font
class FontHolder {
msdfgen::FreetypeHandle *ft;
msdfgen::FontHandle *font;
public:
explicit FontHolder(const char *fontFilename) : ft(nullptr), font(nullptr) {
if ((ft = msdfgen::initializeFreetype()))
font = msdfgen::loadFont(ft, fontFilename);
}
~FontHolder() {
if (ft) {
if (font)
msdfgen::destroyFont(font);
msdfgen::deinitializeFreetype(ft);
}
}
operator msdfgen::FontHandle *() const {
return font;
}
} font(fontFilename);
if (!font)
ABORT("Failed to load specified font file.");
msdfgen::FontMetrics fontMetrics = { };
msdfgen::getFontMetrics(fontMetrics, font);
if (fontMetrics.emSize <= 0)
fontMetrics.emSize = DEFAULT_EM_SIZE;
// Load character set
Charset charset;
if (charsetFilename) {
if (!charset.load(charsetFilename))
ABORT("Failed to load character set specification.");
} else
charset = Charset::ASCII;
if (charset.empty())
ABORT("No character set loaded.");
// Load glyphs
std::vector<GlyphGeometry> glyphs;
loadGlyphs(glyphs, font, charset);
printf("Loaded geometry of %d out of %d characters.\n", (int) glyphs.size(), (int) charset.size());
// Determine final atlas dimensions, scale and range, pack glyphs
{
double unitRange = 0, pxRange = 0;
switch (rangeMode) {
case RANGE_EM:
unitRange = rangeValue*fontMetrics.emSize;
break;
case RANGE_PIXEL:
pxRange = rangeValue;
break;
}
bool fixedDimensions = fixedWidth >= 0 && fixedHeight >= 0;
bool fixedScale = config.emSize > 0;
TightAtlasPacker atlasPacker;
if (fixedDimensions)
atlasPacker.setDimensions(fixedWidth, fixedHeight);
else
atlasPacker.setDimensionsConstraint(atlasSizeConstraint);
atlasPacker.setPadding(config.imageType == ImageType::MSDF || config.imageType == ImageType::MTSDF ? 0 : -1);
// TODO: In this case (if padding is -1), the border pixels of each glyph are black, but still computed. For floating-point output, this may play a role.
if (fixedScale)
atlasPacker.setScale(config.emSize/fontMetrics.emSize);
else
atlasPacker.setMinimumScale(minEmSize/fontMetrics.emSize);
atlasPacker.setPixelRange(pxRange);
atlasPacker.setUnitRange(unitRange);
atlasPacker.setMiterLimit(config.miterLimit);
if (int remaining = atlasPacker.pack(glyphs.data(), glyphs.size())) {
if (remaining < 0) {
ABORT("Failed to pack glyphs into atlas.");
} else {
printf("Error: Could not fit %d out of %d glyphs into the atlas.\n", remaining, (int) glyphs.size());
return 1;
}
}
atlasPacker.getDimensions(config.width, config.height);
if (!(config.width > 0 && config.height > 0))
ABORT("Unable to determine atlas size.");
config.emSize = atlasPacker.getScale()*fontMetrics.emSize;
config.pxRange = atlasPacker.getPixelRange();
if (!fixedScale)
printf("Glyph size: %.9g pixels/EM\n", config.emSize);
if (!fixedDimensions)
printf("Atlas dimensions: %d x %d\n", config.width, config.height);
}
// Generate atlas bitmap
if (!layoutOnly) {
// Edge coloring
if (config.imageType == ImageType::MSDF || config.imageType == ImageType::MTSDF) {
unsigned long long glyphSeed = config.coloringSeed;
for (GlyphGeometry &glyph : glyphs) {
glyphSeed *= MCG_MULTIPLIER;
glyph.edgeColoring(config.angleThreshold, glyphSeed);
}
}
bool floatingPoint = (
config.imageFormat == ImageFormat::TIFF ||
config.imageFormat == ImageFormat::TEXT_FLOAT ||
config.imageFormat == ImageFormat::BINARY_FLOAT ||
config.imageFormat == ImageFormat::BINARY_FLOAT_BE
);
bool success = false;
switch (config.imageType) {
case ImageType::HARD_MASK:
if (floatingPoint)
success = makeAtlas<float, float, 1, scanlineGenerator>(glyphs, font, config);
else
success = makeAtlas<byte, float, 1, scanlineGenerator>(glyphs, font, config);
break;
case ImageType::SOFT_MASK:
case ImageType::SDF:
if (floatingPoint)
success = makeAtlas<float, float, 1, sdfGenerator>(glyphs, font, config);
else
success = makeAtlas<byte, float, 1, sdfGenerator>(glyphs, font, config);
break;
case ImageType::PSDF:
if (floatingPoint)
success = makeAtlas<float, float, 1, psdfGenerator>(glyphs, font, config);
else
success = makeAtlas<byte, float, 1, psdfGenerator>(glyphs, font, config);
break;
case ImageType::MSDF:
if (floatingPoint)
success = makeAtlas<float, float, 3, msdfGenerator>(glyphs, font, config);
else
success = makeAtlas<byte, float, 3, msdfGenerator>(glyphs, font, config);
break;
case ImageType::MTSDF:
if (floatingPoint)
success = makeAtlas<float, float, 4, mtsdfGenerator>(glyphs, font, config);
else
success = makeAtlas<byte, float, 4, mtsdfGenerator>(glyphs, font, config);
break;
}
if (!success)
result = 1;
}
if (config.csvFilename) {
if (exportCSV(glyphs.data(), glyphs.size(), fontMetrics.emSize, config.csvFilename))
puts("Glyph layout written into CSV file.");
else {
result = 1;
puts("Failed to write CSV output file.");
}
}
if (config.jsonFilename) {
if (exportJSON(font, glyphs.data(), glyphs.size(), config.emSize, config.pxRange, config.width, config.height, config.imageType, config.jsonFilename))
puts("Glyph layout and metadata written into JSON file.");
else {
result = 1;
puts("Failed to write JSON output file.");
}
}
if (config.shadronPreviewFilename && config.shadronPreviewText) {
std::vector<unicode_t> previewText;
utf8Decode(previewText, config.shadronPreviewText);
previewText.push_back(0);
if (generateShadronPreview(font, glyphs.data(), glyphs.size(), config.imageType, config.width, config.height, config.pxRange, previewText.data(), config.imageFilename, config.shadronPreviewFilename))
puts("Shadron preview script generated.");
else {
result = 1;
puts("Failed to generate Shadron preview file.");
}
}
return result;
}
#endif

View File

@ -0,0 +1,41 @@
#pragma once
/*
* MULTI-CHANNEL SIGNED DISTANCE FIELD ATLAS GENERATOR v1.0 (2020-03-08)
* ---------------------------------------------------------------------
* A utility by Viktor Chlumsky, (c) 2020
*
* Generates compact bitmap font atlases using MSDFGEN.
*
*/
#include <msdfgen.h>
#include <msdfgen-ext.h>
#include "types.h"
#include "utf8.h"
#include "Rectangle.h"
#include "Charset.h"
#include "GlyphBox.h"
#include "GlyphGeometry.h"
#include "RectanglePacker.h"
#include "rectangle-packing.h"
#include "Workload.h"
#include "size-selectors.h"
#include "bitmap-blit.h"
#include "AtlasStorage.h"
#include "BitmapAtlasStorage.h"
#include "TightAtlasPacker.h"
#include "AtlasGenerator.h"
#include "ImmediateAtlasGenerator.h"
#include "DynamicAtlas.h"
#include "glyph-generators.h"
#include "image-encode.h"
#include "image-save.h"
#include "artery-font-export.h"
#include "csv-export.h"
#include "json-export.h"
#include "shadron-preview-generator.h"
#define MSDF_ATLAS_VERSION "1.0"

View File

@ -0,0 +1,19 @@
#pragma once
#include <utility>
#include "Rectangle.h"
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);
/// 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);
}
#include "rectangle-packing.hpp"

View File

@ -0,0 +1,61 @@
#include "rectangle-packing.h"
#include <vector>
#include "RectanglePacker.h"
namespace msdf_atlas {
static void copyRectanglePlacement(Rectangle &dst, const Rectangle &src) {
dst.x = src.x;
dst.y = src.y;
}
static void copyRectanglePlacement(OrientedRectangle &dst, const OrientedRectangle &src) {
dst.x = src.x;
dst.y = src.y;
dst.rotated = src.rotated;
}
template <typename RectangleType>
int packRectangles(RectangleType *rectangles, int count, int width, int height, int padding) {
if (padding)
for (int i = 0; i < count; ++i) {
rectangles[i].w += padding;
rectangles[i].h += padding;
}
int result = RectanglePacker(width+padding, height+padding).pack(rectangles, count);
if (padding)
for (int i = 0; i < count; ++i) {
rectangles[i].w -= padding;
rectangles[i].h -= padding;
}
return result;
}
template <class SizeSelector, typename RectangleType>
std::pair<int, int> packRectangles(RectangleType *rectangles, int count, int padding) {
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;
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)) {
dimensions.first = width;
dimensions.second = height;
for (int i = 0; i < count; ++i)
copyRectanglePlacement(rectangles[i], rectanglesCopy[i]);
--sizeSelector;
} else
++sizeSelector;
}
return dimensions;
}
}

View File

@ -0,0 +1,148 @@
#include "shadron-preview-generator.h"
#include <string>
#include <algorithm>
namespace msdf_atlas {
static const char * const shadronFillGlyphMask = R"(
template <ATLAS, RANGE, 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>
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 fill = clamp(sd+0.5, 0.0, 1.0);
return vec4(vec3(COLOR), fill);
}
)";
static const char * const shadronPreviewPreamble = R"(
#include <median>
glsl struct GlyphVertex {
vec2 coord;
vec2 texCoord;
};
template <TEXT_SIZE>
glsl vec4 projectVertex(out vec2 texCoord, in GlyphVertex vertex) {
vec2 coord = vertex.coord;
float scale = 2.0/max((TEXT_SIZE).x, shadron_Aspect*(TEXT_SIZE).y);
scale *= exp(0.0625*shadron_Mouse.z);
coord += vec2(-0.5, 0.5)*vec2(TEXT_SIZE);
coord *= scale*vec2(1.0, shadron_Aspect);
texCoord = vertex.texCoord;
return vec4(coord, 0.0, 1.0);
}
%s
#define PREVIEW_IMAGE(NAME, ATLAS, RANGE, 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>), \
depth(false), \
blend(transparency), \
background(vec4(vec3(COLOR), 0.0)), \
dimensions(DIMENSIONS), \
resizable(true)
)";
static std::string relativizePath(const char *base, const char *target) {
if (target[0] == '/' || (target[0] && target[1] == ':')) // absolute path?
return target;
int commonPrefix = 0;
for (int i = 0; base[i] && target[i] && base[i] == target[i]; ++i) {
if (base[i] == '/' || base[i] == '\\')
commonPrefix = i+1;
}
base += commonPrefix;
target += commonPrefix;
int baseNesting = 0;
for (int i = 0; base[i]; ++i)
if (base[i] == '/' || base[i] == '\\')
++baseNesting;
std::string output;
for (int i = 0; i < baseNesting; ++i)
output += "../";
output += 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) {
double texelWidth = 1./atlasWidth;
double texelHeight = 1./atlasHeight;
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());
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);
{
msdfgen::FontMetrics fontMetrics;
if (!msdfgen::getFontMetrics(fontMetrics, font))
return false;
double fsScale = 1/(fontMetrics.ascenderY-fontMetrics.descenderY);
fputs("vertex_list GlyphVertex textQuadVertices = {\n", file);
double x = 0, y = -fsScale*fontMetrics.ascenderY;
double textWidth = 0;
for (const unicode_t *cp = text; *cp; ++cp) {
if (*cp == '\r')
continue;
if (*cp == '\n') {
textWidth = std::max(textWidth, x);
x = 0;
y -= fsScale*fontMetrics.lineHeight;
continue;
}
for (int i = 0; i < glyphCount; ++i) {
if (glyphs[i].getCodepoint() == *cp) {
if (!glyphs[i].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);
pl *= fsScale, pb *= fsScale, pr *= fsScale, pt *= fsScale;
pl += x, pb += y, pr += x, pt += y;
il *= texelWidth, ib *= texelHeight, ir *= texelWidth, it *= texelHeight;
fprintf(file, " %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g, %.9g,\n",
pl, pb, il, ib,
pr, pb, ir, ib,
pl, pt, il, it,
pr, pt, ir, it,
pl, pt, il, it,
pr, pb, ir, ib
);
}
x += fsScale*glyphs[i].getAdvance();
double kerning;
if (msdfgen::getKerning(kerning, font, cp[0], cp[1]))
x += fsScale*kerning;
break;
}
}
}
textWidth = std::max(textWidth, x);
y += fsScale*fontMetrics.descenderY;
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("export png(Preview, \"preview.png\");\n", file);
fclose(file);
return true;
}
}

View File

@ -0,0 +1,14 @@
#pragma once
#include <msdfgen.h>
#include <msdfgen-ext.h>
#include "types.h"
#include "GlyphGeometry.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);
}

View File

@ -0,0 +1,90 @@
#include "size-selectors.h"
#include <cmath>
namespace msdf_atlas {
template <int MULTIPLE>
SquareSizeSelector<MULTIPLE>::SquareSizeSelector(int minArea) : lowerBound(0), upperBound(-1) {
if (minArea > 0)
lowerBound = int(sqrt(minArea-1))/MULTIPLE+1;
updateCurrent();
}
template <int MULTIPLE>
void SquareSizeSelector<MULTIPLE>::updateCurrent() {
if (upperBound < 0)
current = 5*lowerBound/4+16/MULTIPLE;
else
current = lowerBound+(upperBound-lowerBound)/2;
}
template <int MULTIPLE>
bool SquareSizeSelector<MULTIPLE>::operator()(int &width, int &height) const {
width = MULTIPLE*current, height = MULTIPLE*current;
return lowerBound < upperBound || upperBound < 0;
}
template <int MULTIPLE>
SquareSizeSelector<MULTIPLE> & SquareSizeSelector<MULTIPLE>::operator++() {
lowerBound = current+1;
updateCurrent();
return *this;
}
template <int MULTIPLE>
SquareSizeSelector<MULTIPLE> & SquareSizeSelector<MULTIPLE>::operator--() {
upperBound = current;
updateCurrent();
return *this;
}
template class SquareSizeSelector<1>;
template class SquareSizeSelector<2>;
template class SquareSizeSelector<4>;
SquarePowerOfTwoSizeSelector::SquarePowerOfTwoSizeSelector(int minArea) : side(1) {
while (side*side < minArea)
side <<= 1;
}
bool SquarePowerOfTwoSizeSelector::operator()(int &width, int &height) const {
width = side, height = side;
return side > 0;
}
SquarePowerOfTwoSizeSelector & SquarePowerOfTwoSizeSelector::operator++() {
side <<= 1;
return *this;
}
SquarePowerOfTwoSizeSelector & SquarePowerOfTwoSizeSelector::operator--() {
side = 0;
return *this;
}
PowerOfTwoSizeSelector::PowerOfTwoSizeSelector(int minArea) : w(1), h(1) {
while (w*h < minArea)
++*this;
}
bool PowerOfTwoSizeSelector::operator()(int &width, int &height) const {
width = w, height = h;
return w > 0 && h > 0;
}
PowerOfTwoSizeSelector & PowerOfTwoSizeSelector::operator++() {
if (w == h)
w <<= 1;
else
h = w;
return *this;
}
PowerOfTwoSizeSelector & PowerOfTwoSizeSelector::operator--() {
w = 0, h = 0;
return *this;
}
}

View File

@ -0,0 +1,54 @@
#pragma once
namespace msdf_atlas {
// The size selector classes are used to select the minimum dimensions of the atlas fitting a given constraint.
/// Selects square dimensions which are also a multiple of MULTIPLE
template <int MULTIPLE = 1>
class SquareSizeSelector {
public:
explicit SquareSizeSelector(int minArea = 0);
bool operator()(int &width, int &height) const;
SquareSizeSelector<MULTIPLE> & operator++();
SquareSizeSelector<MULTIPLE> & operator--();
private:
int lowerBound, upperBound;
int current;
void updateCurrent();
};
/// Selects square power-of-two dimensions
class SquarePowerOfTwoSizeSelector {
public:
explicit SquarePowerOfTwoSizeSelector(int minArea = 0);
bool operator()(int &width, int &height) const;
SquarePowerOfTwoSizeSelector & operator++();
SquarePowerOfTwoSizeSelector & operator--();
private:
int side;
};
/// Selects square or rectangular (2:1) power-of-two dimensions
class PowerOfTwoSizeSelector {
public:
explicit PowerOfTwoSizeSelector(int minArea = 0);
bool operator()(int &width, int &height) const;
PowerOfTwoSizeSelector & operator++();
PowerOfTwoSizeSelector & operator--();
private:
int w, h;
};
}

40
msdf-atlas-gen/types.h Normal file
View File

@ -0,0 +1,40 @@
#pragma once
#include <cstdint>
namespace msdf_atlas {
typedef unsigned char byte;
typedef uint32_t unicode_t;
/// Type of atlas image contents
enum class ImageType {
/// Rendered glyphs without anti-aliasing (two colors only)
HARD_MASK,
/// Rendered glyphs with anti-aliasing
SOFT_MASK,
/// Signed (true) distance field
SDF,
/// Signed pseudo-distance field
PSDF,
/// Multi-channel signed distance field
MSDF,
/// Multi-channel & true signed distance field
MTSDF
};
/// Atlas image encoding
enum class ImageFormat {
UNSPECIFIED,
PNG,
BMP,
TIFF,
TEXT,
TEXT_FLOAT,
BINARY,
BINARY_FLOAT,
BINARY_FLOAT_BE
};
}

38
msdf-atlas-gen/utf8.cpp Normal file
View File

@ -0,0 +1,38 @@
#include "utf8.h"
namespace msdf_atlas {
void utf8Decode(std::vector<unicode_t> &codepoints, const char *utf8String) {
bool start = true;
int rBytes = 0;
unicode_t cp = 0;
for (const char *c = utf8String; *c; ++c) {
if (rBytes > 0) {
--rBytes;
if ((*c&0xc0) == 0x80)
cp |= (*c&0x3f)<<(6*rBytes);
// else error
} else if (!(*c&0x80)) {
cp = *c;
rBytes = 0;
} else if (*c&0x40) {
int block;
for (block = 0; (*c<<block)&0x40 && block < 4; ++block);
if (block < 4) {
cp = (*c&(0x3f>>block))<<(6*block);
rBytes = block;
} else
continue; // error
} else
continue; // error
if (!rBytes) {
if (!(start && cp == 0xfeff)) // BOM
codepoints.push_back(cp);
start = false;
}
}
}
}

12
msdf-atlas-gen/utf8.h Normal file
View File

@ -0,0 +1,12 @@
#pragma once
#include <vector>
#include "types.h"
namespace msdf_atlas {
/// Decodes the UTF-8 string into an array of Unicode codepoints
void utf8Decode(std::vector<unicode_t> &codepoints, const char *utf8String);
}

BIN
resource.h Normal file

Binary file not shown.