diff --git a/libs/msdf-atlas-gen/include/LICENSE.txt b/libs/msdf-atlas-gen/include/LICENSE.txt new file mode 100644 index 0000000..3642b9f --- /dev/null +++ b/libs/msdf-atlas-gen/include/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 - 2023 Viktor Chlumsky + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/libs/msdf-atlas-gen/include/artery-font-format/LICENSE.txt b/libs/msdf-atlas-gen/include/artery-font-format/LICENSE.txt new file mode 100644 index 0000000..83edc59 --- /dev/null +++ b/libs/msdf-atlas-gen/include/artery-font-format/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Viktor Chlumsky + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/libs/msdf-atlas-gen/include/artery-font-format/README.md b/libs/msdf-atlas-gen/include/artery-font-format/README.md new file mode 100644 index 0000000..1b75fb0 --- /dev/null +++ b/libs/msdf-atlas-gen/include/artery-font-format/README.md @@ -0,0 +1,6 @@ + +# Artery Atlas Font format library + +This is a header-only C++ library that facilitates encoding and decoding of the Artery Atlas Font format – a specialized binary file format for storing fonts as bitmap atlases used by the [Artery Engine](https://www.arteryengine.com/), intended for use in video games and other hardware accelerated applications. + +An Artery Atlas font file (*.arfont) wraps together the atlas bitmap(s), which can be compressed e.g. in PNG format, the layout of the atlas, as well as the font's and the individual glyphs' metrics and positioning data, including kerning pairs. diff --git a/libs/msdf-atlas-gen/include/artery-font-format/artery-font/artery-font.h b/libs/msdf-atlas-gen/include/artery-font-format/artery-font/artery-font.h new file mode 100644 index 0000000..ec29933 --- /dev/null +++ b/libs/msdf-atlas-gen/include/artery-font-format/artery-font/artery-font.h @@ -0,0 +1,10 @@ + +#pragma once + +#include "types.h" +#include "enums.h" +#include "structures.h" +#include "serialization.h" + +// ARTERY ENGINE ATLAS FONT FORMAT LIBRARY v1.0.1 +// Author: Viktor Chlumsky (c) 2020 - 2022 diff --git a/libs/msdf-atlas-gen/include/artery-font-format/artery-font/crc32.h b/libs/msdf-atlas-gen/include/artery-font-format/artery-font/crc32.h new file mode 100644 index 0000000..a05341b --- /dev/null +++ b/libs/msdf-atlas-gen/include/artery-font-format/artery-font/crc32.h @@ -0,0 +1,13 @@ + +#pragma once + +#include "types.h" + +namespace artery_font { + +uint32 crc32Init(); +uint32 crc32Update(uint32 crc, byte x); + +} + +#include "crc32.hpp" diff --git a/libs/msdf-atlas-gen/include/artery-font-format/artery-font/crc32.hpp b/libs/msdf-atlas-gen/include/artery-font-format/artery-font/crc32.hpp new file mode 100644 index 0000000..17ab4b3 --- /dev/null +++ b/libs/msdf-atlas-gen/include/artery-font-format/artery-font/crc32.hpp @@ -0,0 +1,48 @@ + +#include "crc32.h" + +namespace artery_font { + +inline uint32 crc32Init() { + return ~0u; +} + +inline uint32 crc32Update(uint32 crc, byte x) { + static const uint32 crc32Table[256] = { + 0x00000000u, 0x77073096u, 0xee0e612cu, 0x990951bau, 0x076dc419u, 0x706af48fu, 0xe963a535u, 0x9e6495a3u, + 0x0edb8832u, 0x79dcb8a4u, 0xe0d5e91eu, 0x97d2d988u, 0x09b64c2bu, 0x7eb17cbdu, 0xe7b82d07u, 0x90bf1d91u, + 0x1db71064u, 0x6ab020f2u, 0xf3b97148u, 0x84be41deu, 0x1adad47du, 0x6ddde4ebu, 0xf4d4b551u, 0x83d385c7u, + 0x136c9856u, 0x646ba8c0u, 0xfd62f97au, 0x8a65c9ecu, 0x14015c4fu, 0x63066cd9u, 0xfa0f3d63u, 0x8d080df5u, + 0x3b6e20c8u, 0x4c69105eu, 0xd56041e4u, 0xa2677172u, 0x3c03e4d1u, 0x4b04d447u, 0xd20d85fdu, 0xa50ab56bu, + 0x35b5a8fau, 0x42b2986cu, 0xdbbbc9d6u, 0xacbcf940u, 0x32d86ce3u, 0x45df5c75u, 0xdcd60dcfu, 0xabd13d59u, + 0x26d930acu, 0x51de003au, 0xc8d75180u, 0xbfd06116u, 0x21b4f4b5u, 0x56b3c423u, 0xcfba9599u, 0xb8bda50fu, + 0x2802b89eu, 0x5f058808u, 0xc60cd9b2u, 0xb10be924u, 0x2f6f7c87u, 0x58684c11u, 0xc1611dabu, 0xb6662d3du, + 0x76dc4190u, 0x01db7106u, 0x98d220bcu, 0xefd5102au, 0x71b18589u, 0x06b6b51fu, 0x9fbfe4a5u, 0xe8b8d433u, + 0x7807c9a2u, 0x0f00f934u, 0x9609a88eu, 0xe10e9818u, 0x7f6a0dbbu, 0x086d3d2du, 0x91646c97u, 0xe6635c01u, + 0x6b6b51f4u, 0x1c6c6162u, 0x856530d8u, 0xf262004eu, 0x6c0695edu, 0x1b01a57bu, 0x8208f4c1u, 0xf50fc457u, + 0x65b0d9c6u, 0x12b7e950u, 0x8bbeb8eau, 0xfcb9887cu, 0x62dd1ddfu, 0x15da2d49u, 0x8cd37cf3u, 0xfbd44c65u, + 0x4db26158u, 0x3ab551ceu, 0xa3bc0074u, 0xd4bb30e2u, 0x4adfa541u, 0x3dd895d7u, 0xa4d1c46du, 0xd3d6f4fbu, + 0x4369e96au, 0x346ed9fcu, 0xad678846u, 0xda60b8d0u, 0x44042d73u, 0x33031de5u, 0xaa0a4c5fu, 0xdd0d7cc9u, + 0x5005713cu, 0x270241aau, 0xbe0b1010u, 0xc90c2086u, 0x5768b525u, 0x206f85b3u, 0xb966d409u, 0xce61e49fu, + 0x5edef90eu, 0x29d9c998u, 0xb0d09822u, 0xc7d7a8b4u, 0x59b33d17u, 0x2eb40d81u, 0xb7bd5c3bu, 0xc0ba6cadu, + 0xedb88320u, 0x9abfb3b6u, 0x03b6e20cu, 0x74b1d29au, 0xead54739u, 0x9dd277afu, 0x04db2615u, 0x73dc1683u, + 0xe3630b12u, 0x94643b84u, 0x0d6d6a3eu, 0x7a6a5aa8u, 0xe40ecf0bu, 0x9309ff9du, 0x0a00ae27u, 0x7d079eb1u, + 0xf00f9344u, 0x8708a3d2u, 0x1e01f268u, 0x6906c2feu, 0xf762575du, 0x806567cbu, 0x196c3671u, 0x6e6b06e7u, + 0xfed41b76u, 0x89d32be0u, 0x10da7a5au, 0x67dd4accu, 0xf9b9df6fu, 0x8ebeeff9u, 0x17b7be43u, 0x60b08ed5u, + 0xd6d6a3e8u, 0xa1d1937eu, 0x38d8c2c4u, 0x4fdff252u, 0xd1bb67f1u, 0xa6bc5767u, 0x3fb506ddu, 0x48b2364bu, + 0xd80d2bdau, 0xaf0a1b4cu, 0x36034af6u, 0x41047a60u, 0xdf60efc3u, 0xa867df55u, 0x316e8eefu, 0x4669be79u, + 0xcb61b38cu, 0xbc66831au, 0x256fd2a0u, 0x5268e236u, 0xcc0c7795u, 0xbb0b4703u, 0x220216b9u, 0x5505262fu, + 0xc5ba3bbeu, 0xb2bd0b28u, 0x2bb45a92u, 0x5cb36a04u, 0xc2d7ffa7u, 0xb5d0cf31u, 0x2cd99e8bu, 0x5bdeae1du, + 0x9b64c2b0u, 0xec63f226u, 0x756aa39cu, 0x026d930au, 0x9c0906a9u, 0xeb0e363fu, 0x72076785u, 0x05005713u, + 0x95bf4a82u, 0xe2b87a14u, 0x7bb12baeu, 0x0cb61b38u, 0x92d28e9bu, 0xe5d5be0du, 0x7cdcefb7u, 0x0bdbdf21u, + 0x86d3d2d4u, 0xf1d4e242u, 0x68ddb3f8u, 0x1fda836eu, 0x81be16cdu, 0xf6b9265bu, 0x6fb077e1u, 0x18b74777u, + 0x88085ae6u, 0xff0f6a70u, 0x66063bcau, 0x11010b5cu, 0x8f659effu, 0xf862ae69u, 0x616bffd3u, 0x166ccf45u, + 0xa00ae278u, 0xd70dd2eeu, 0x4e048354u, 0x3903b3c2u, 0xa7672661u, 0xd06016f7u, 0x4969474du, 0x3e6e77dbu, + 0xaed16a4au, 0xd9d65adcu, 0x40df0b66u, 0x37d83bf0u, 0xa9bcae53u, 0xdebb9ec5u, 0x47b2cf7fu, 0x30b5ffe9u, + 0xbdbdf21cu, 0xcabac28au, 0x53b39330u, 0x24b4a3a6u, 0xbad03605u, 0xcdd70693u, 0x54de5729u, 0x23d967bfu, + 0xb3667a2eu, 0xc4614ab8u, 0x5d681b02u, 0x2a6f2b94u, 0xb40bbe37u, 0xc30c8ea1u, 0x5a05df1bu, 0x2d02ef8du, + }; + return crc32Table[byte(x^crc)]^crc>>8; +} + +} diff --git a/libs/msdf-atlas-gen/include/artery-font-format/artery-font/enums.h b/libs/msdf-atlas-gen/include/artery-font-format/artery-font/enums.h new file mode 100644 index 0000000..979bf5f --- /dev/null +++ b/libs/msdf-atlas-gen/include/artery-font-format/artery-font/enums.h @@ -0,0 +1,66 @@ + +#pragma once + +namespace artery_font { + +enum FontFlags { + FONT_BOLD = 0x01, + FONT_LIGHT = 0x02, + FONT_EXTRA_BOLD = 0x04, + FONT_CONDENSED = 0x08, + FONT_ITALIC = 0x10, + FONT_SMALL_CAPS = 0x20, + FONT_ICONOGRAPHIC = 0x0100, + FONT_SANS_SERIF = 0x0200, + FONT_SERIF = 0x0400, + FONT_MONOSPACE = 0x1000, + FONT_CURSIVE = 0x2000 +}; + +enum CodepointType { + CP_UNSPECIFIED = 0, + CP_UNICODE = 1, + CP_INDEXED = 2, + CP_ICONOGRAPHIC = 14 +}; + +enum MetadataFormat { + METADATA_NONE = 0, + METADATA_PLAINTEXT = 1, + METADATA_JSON = 2 +}; + +enum ImageType { + IMAGE_NONE = 0, + IMAGE_SRGB_IMAGE = 1, + IMAGE_LINEAR_MASK = 2, + IMAGE_MASKED_SRGB_IMAGE = 3, + IMAGE_SDF = 4, + IMAGE_PSDF = 5, + IMAGE_MSDF = 6, + IMAGE_MTSDF = 7, + IMAGE_MIXED_CONTENT = 255 +}; + +enum PixelFormat { + PIXEL_UNKNOWN = 0, + PIXEL_BOOLEAN1 = 1, + PIXEL_UNSIGNED8 = 8, + PIXEL_FLOAT32 = 32 +}; + +enum ImageEncoding { + IMAGE_UNKNOWN_ENCODING = 0, + IMAGE_RAW_BINARY = 1, + IMAGE_BMP = 4, + IMAGE_TIFF = 5, + IMAGE_PNG = 8, + IMAGE_TGA = 9 +}; + +enum ImageOrientation { + ORIENTATION_TOP_DOWN = 1, + ORIENTATION_BOTTOM_UP = -1 +}; + +} diff --git a/libs/msdf-atlas-gen/include/artery-font-format/artery-font/serialization.h b/libs/msdf-atlas-gen/include/artery-font-format/artery-font/serialization.h new file mode 100644 index 0000000..f5cf79e --- /dev/null +++ b/libs/msdf-atlas-gen/include/artery-font-format/artery-font/serialization.h @@ -0,0 +1,21 @@ + +#pragma once + +#include "types.h" +#include "enums.h" +#include "structures.h" + +namespace artery_font { + +typedef int ReadFunction(void *dst, int limit, void *userData); +typedef int WriteFunction(const void *src, int length, void *userData); + +template class LIST, class BYTE_ARRAY, class STRING> +bool decode(ArteryFont &font, void *userData); + +template class LIST, class BYTE_ARRAY, class STRING> +bool encode(const ArteryFont &font, void *userData); + +} + +#include "serialization.hpp" diff --git a/libs/msdf-atlas-gen/include/artery-font-format/artery-font/serialization.hpp b/libs/msdf-atlas-gen/include/artery-font-format/artery-font/serialization.hpp new file mode 100644 index 0000000..ff43d3a --- /dev/null +++ b/libs/msdf-atlas-gen/include/artery-font-format/artery-font/serialization.hpp @@ -0,0 +1,394 @@ + +#include "serialization.h" + +#include +#include "crc32.h" + +namespace artery_font { + +namespace internal { + +#define ARTERY_FONT_HEADER_TAG "ARTERY/FONT\0\0\0\0\0" +#define ARTERY_FONT_HEADER_VERSION 1u +#define ARTERY_FONT_HEADER_MAGIC_NO 0x4d276a5cu +#define ARTERY_FONT_FOOTER_MAGIC_NO 0x55ccb363u + +struct ArteryFontHeader { + char tag[16]; + uint32 magicNo; + uint32 version; + uint32 flags; + uint32 realType; + uint32 reserved[4]; + + uint32 metadataFormat; + uint32 metadataLength; + uint32 variantCount; + uint32 variantsLength; + uint32 imageCount; + uint32 imagesLength; + uint32 appendixCount; + uint32 appendicesLength; + uint32 reserved2[8]; +}; + +struct ArteryFontFooter { + uint32 salt; + uint32 magicNo; + uint32 reserved[4]; + uint32 totalLength; + uint32 checksum; +}; + +template +struct FontVariantHeader { + uint32 flags; + uint32 weight; + uint32 codepointType; + uint32 imageType; + uint32 fallbackVariant; + uint32 fallbackGlyph; + uint32 reserved[6]; + REAL metrics[32]; + uint32 nameLength; + uint32 metadataLength; + uint32 glyphCount; + uint32 kernPairCount; +}; + +struct ImageHeader { + uint32 flags; + uint32 encoding; + uint32 width, height; + uint32 channels; + uint32 pixelFormat; + uint32 imageType; + uint32 rowLength; + sint32 orientation; + uint32 childImages; + uint32 textureFlags; + uint32 reserved[3]; + uint32 metadataLength; + uint32 dataLength; +}; + +struct AppendixHeader { + uint32 metadataLength; + uint32 dataLength; +}; + +template +uint32 realTypeCode(); + +template <> +inline uint32 realTypeCode() { + return 0x14u; +} +template <> +inline uint32 realTypeCode() { + return 0x18u; +} + +inline uint32 paddedLength(uint32 len) { + if (len&0x03u) + len += 0x04u-(len&0x03u); + return len; +} + +template +uint32 paddedStringLength(const STRING &str) { + uint32 len = str.length(); + return paddedLength(len+(len > 0)); +} + +} + +#ifndef __BIG_ENDIAN__ + +template class LIST, class BYTE_ARRAY, class STRING> +bool decode(ArteryFont &font, void *userData) { + uint32 totalLength = 0; + uint32 prevLength = 0; + uint32 checksum = crc32Init(); + byte dump[4]; + #define ARTERY_FONT_DECODE_READ(target, len) { \ + if (READ((target), (len), userData) != int(len)) \ + return false; \ + totalLength += (len); \ + for (int i = 0; i < int(len); ++i) \ + checksum = crc32Update(checksum, reinterpret_cast(target)[i]); \ + } + #define ARTERY_FONT_DECODE_REALIGN() { \ + if (totalLength&0x03u) { \ + uint32 len = 0x04u-(totalLength&0x03u); \ + ARTERY_FONT_DECODE_READ(dump, len); \ + } \ + } + #define ARTERY_FONT_DECODE_READ_STRING(str, len) { \ + if ((len) > 0) { \ + LIST characters((len)+1); \ + ARTERY_FONT_DECODE_READ((char *) characters, (len)+1); \ + ((char *) characters)[len] = '\0'; \ + (str) = STRING((const char *) characters, int(len)); \ + ARTERY_FONT_DECODE_REALIGN(); \ + } else \ + (str) = STRING(); \ + } + int variantCount = 0; + int imageCount = 0; + int appendixCount = 0; + uint32 variantsLength = 0; + uint32 imagesLength = 0; + uint32 appendicesLength = 0; + // Read header + { + internal::ArteryFontHeader header; + ARTERY_FONT_DECODE_READ(&header, sizeof(header)); + if (memcmp(header.tag, ARTERY_FONT_HEADER_TAG, sizeof(header.tag))) + return false; + if (header.magicNo != ARTERY_FONT_HEADER_MAGIC_NO) + return false; + if (header.realType != internal::realTypeCode()) + return false; + font.metadataFormat = (MetadataFormat) header.metadataFormat; + ARTERY_FONT_DECODE_READ_STRING(font.metadata, header.metadataLength); + variantCount = header.variantCount; + imageCount = header.imageCount; + appendixCount = header.appendixCount; + font.variants = LIST >(header.variantCount); + font.images = LIST >(header.imageCount); + font.appendices = LIST >(header.appendixCount); + variantsLength = header.variantsLength; + imagesLength = header.imagesLength; + appendicesLength = header.appendicesLength; + } + prevLength = totalLength; + // Read variants + for (int i = 0; i < variantCount; ++i) { + FontVariant &variant = font.variants[i]; + internal::FontVariantHeader header; + ARTERY_FONT_DECODE_READ(&header, sizeof(header)); + variant.flags = header.flags; + variant.weight = header.weight; + variant.codepointType = (CodepointType) header.codepointType; + variant.imageType = (ImageType) header.imageType; + variant.fallbackVariant = header.fallbackVariant; + variant.fallbackGlyph = header.fallbackGlyph; + memcpy(&variant.metrics, header.metrics, sizeof(header.metrics)); + ARTERY_FONT_DECODE_READ_STRING(variant.name, header.nameLength); + ARTERY_FONT_DECODE_READ_STRING(variant.metadata, header.metadataLength); + variant.glyphs = LIST >(header.glyphCount); + variant.kernPairs = LIST >(header.kernPairCount); + ARTERY_FONT_DECODE_READ((Glyph *) variant.glyphs, header.glyphCount*sizeof(Glyph)); + ARTERY_FONT_DECODE_READ((KernPair *) variant.kernPairs, header.kernPairCount*sizeof(KernPair)); + } + if (totalLength-prevLength != variantsLength) + return false; + prevLength = totalLength; + // Read images + for (int i = 0; i < imageCount; ++i) { + FontImage &image = font.images[i]; + internal::ImageHeader header; + ARTERY_FONT_DECODE_READ(&header, sizeof(header)); + image.flags = header.flags; + image.encoding = (ImageEncoding) header.encoding; + image.width = header.width; + image.height = header.height; + image.channels = header.channels; + image.pixelFormat = (PixelFormat) header.pixelFormat; + image.imageType = (ImageType) header.imageType; + image.rawBinaryFormat.rowLength = header.rowLength; + image.rawBinaryFormat.orientation = (ImageOrientation) header.orientation; + image.childImages = header.childImages; + image.textureFlags = header.textureFlags; + ARTERY_FONT_DECODE_READ_STRING(image.metadata, header.metadataLength); + image.data = BYTE_ARRAY(header.dataLength); + ARTERY_FONT_DECODE_READ((unsigned char *) image.data, header.dataLength); + ARTERY_FONT_DECODE_REALIGN(); + } + if (totalLength-prevLength != imagesLength) + return false; + prevLength = totalLength; + // Read appendices + for (int i = 0; i < appendixCount; ++i) { + FontAppendix &appendix = font.appendices[i]; + internal::AppendixHeader header; + ARTERY_FONT_DECODE_READ(&header, sizeof(header)); + ARTERY_FONT_DECODE_READ_STRING(appendix.metadata, header.metadataLength); + appendix.data = BYTE_ARRAY(header.dataLength); + ARTERY_FONT_DECODE_READ((unsigned char *) appendix.data, header.dataLength); + ARTERY_FONT_DECODE_REALIGN(); + } + if (totalLength-prevLength != appendicesLength) + return false; + prevLength = totalLength; + // Read footer + { + internal::ArteryFontFooter footer; + ARTERY_FONT_DECODE_READ(&footer, sizeof(footer)-sizeof(footer.checksum)); + if (footer.magicNo != ARTERY_FONT_FOOTER_MAGIC_NO) + return false; + uint32 finalChecksum = checksum; + ARTERY_FONT_DECODE_READ(&footer.checksum, sizeof(footer.checksum)); + if (footer.checksum != finalChecksum) + return false; + if (totalLength != footer.totalLength) + return false; + } + return true; + #undef ARTERY_FONT_DECODE_READ + #undef ARTERY_FONT_DECODE_REALIGN + #undef ARTERY_FONT_DECODE_READ_STRING +} + +template class LIST, class BYTE_ARRAY, class STRING> +bool encode(const ArteryFont &font, void *userData) { + uint32 totalLength = 0; + uint32 checksum = crc32Init(); + const byte padding[4] = { }; + #define ARTERY_FONT_ENCODE_WRITE(data, len) { \ + if (WRITE((data), (len), userData) != int(len)) \ + return false; \ + totalLength += (len); \ + for (int i = 0; i < int(len); ++i) \ + checksum = crc32Update(checksum, reinterpret_cast(data)[i]); \ + } + #define ARTERY_FONT_ENCODE_REALIGN() { \ + if (totalLength&0x03u) { \ + uint32 len = 0x04u-(totalLength&0x03u); \ + ARTERY_FONT_ENCODE_WRITE(padding, len); \ + } \ + } + #define ARTERY_FONT_ENCODE_WRITE_STRING(str) { \ + uint32 len = (str).length(); \ + if ((len) > 0) { \ + ARTERY_FONT_ENCODE_WRITE((const char *) (str), (len)); \ + ARTERY_FONT_ENCODE_WRITE(padding, 1) \ + ARTERY_FONT_ENCODE_REALIGN(); \ + } \ + } + int variantCount = 0; + int imageCount = 0; + int appendixCount = 0; + // Write header + { + internal::ArteryFontHeader header; + memcpy(header.tag, ARTERY_FONT_HEADER_TAG, sizeof(header.tag)); + header.magicNo = ARTERY_FONT_HEADER_MAGIC_NO; + header.version = ARTERY_FONT_HEADER_VERSION; + header.flags = 0; + header.realType = internal::realTypeCode(); + memset(header.reserved, 0, sizeof(header.reserved)); + header.metadataFormat = (uint32) font.metadataFormat; + header.metadataLength = font.metadata.length(); + header.variantCount = variantCount = font.variants.length(); + header.variantsLength = 0; + header.imageCount = imageCount = font.images.length(); + header.imagesLength = 0; + header.appendixCount = appendixCount = font.appendices.length(); + header.appendicesLength = 0; + memset(header.reserved2, 0, sizeof(header.reserved2)); + for (int i = 0; i < variantCount; ++i) { + const FontVariant &variant = font.variants[i]; + header.variantsLength += sizeof(internal::FontVariantHeader); + header.variantsLength += internal::paddedStringLength(variant.name); + header.variantsLength += internal::paddedStringLength(variant.metadata); + header.variantsLength += variant.glyphs.length()*sizeof(Glyph); + header.variantsLength += variant.kernPairs.length()*sizeof(KernPair); + } + for (int i = 0; i < imageCount; ++i) { + const FontImage &image = font.images[i]; + header.imagesLength += sizeof(internal::ImageHeader); + header.imagesLength += internal::paddedStringLength(image.metadata); + header.imagesLength += internal::paddedLength(image.data.length()); + } + for (int i = 0; i < appendixCount; ++i) { + const FontAppendix &appendix = font.appendices[i]; + header.appendicesLength += sizeof(internal::AppendixHeader); + header.appendicesLength += internal::paddedStringLength(appendix.metadata); + header.appendicesLength += internal::paddedLength(appendix.data.length()); + } + ARTERY_FONT_ENCODE_WRITE(&header, sizeof(header)); + ARTERY_FONT_ENCODE_WRITE_STRING(font.metadata); + } + // Write variants + for (int i = 0; i < variantCount; ++i) { + const FontVariant &variant = font.variants[i]; + internal::FontVariantHeader header; + header.flags = variant.flags; + header.weight = variant.weight; + header.codepointType = (uint32) variant.codepointType; + header.imageType = (uint32) variant.imageType; + header.fallbackVariant = variant.fallbackVariant; + header.fallbackGlyph = variant.fallbackGlyph; + memset(header.reserved, 0, sizeof(header.reserved)); + memcpy(header.metrics, &variant.metrics, sizeof(header.metrics)); + header.nameLength = variant.name.length(); + header.metadataLength = variant.metadata.length(); + header.glyphCount = variant.glyphs.length(); + header.kernPairCount = variant.kernPairs.length(); + ARTERY_FONT_ENCODE_WRITE(&header, sizeof(header)); + ARTERY_FONT_ENCODE_WRITE_STRING(variant.name); + ARTERY_FONT_ENCODE_WRITE_STRING(variant.metadata); + ARTERY_FONT_ENCODE_WRITE((const Glyph *) variant.glyphs, header.glyphCount*sizeof(Glyph)); + ARTERY_FONT_ENCODE_WRITE((const KernPair *) variant.kernPairs, header.kernPairCount*sizeof(KernPair)); + } + // Write images + for (int i = 0; i < imageCount; ++i) { + const FontImage &image = font.images[i]; + internal::ImageHeader header; + header.flags = image.flags; + header.encoding = (uint32) image.encoding; + header.width = image.width; + header.height = image.height; + header.channels = image.channels; + header.pixelFormat = (uint32) image.pixelFormat; + header.imageType = (uint32) image.imageType; + header.rowLength = image.rawBinaryFormat.rowLength; + header.orientation = (sint32) image.rawBinaryFormat.orientation; + header.childImages = image.childImages; + header.textureFlags = image.textureFlags; + memset(header.reserved, 0, sizeof(header.reserved)); + header.metadataLength = image.metadata.length(); + header.dataLength = image.data.length(); + ARTERY_FONT_ENCODE_WRITE(&header, sizeof(header)); + ARTERY_FONT_ENCODE_WRITE_STRING(image.metadata); + ARTERY_FONT_ENCODE_WRITE((const unsigned char *) image.data, header.dataLength); + ARTERY_FONT_ENCODE_REALIGN(); + } + // Write appendices + for (int i = 0; i < appendixCount; ++i) { + const FontAppendix &appendix = font.appendices[i]; + internal::AppendixHeader header; + header.metadataLength = appendix.metadata.length(); + header.dataLength = appendix.data.length(); + ARTERY_FONT_ENCODE_WRITE(&header, sizeof(header)); + ARTERY_FONT_ENCODE_WRITE_STRING(appendix.metadata); + ARTERY_FONT_ENCODE_WRITE((const unsigned char *) appendix.data, header.dataLength); + ARTERY_FONT_ENCODE_REALIGN(); + } + // Write footer + { + internal::ArteryFontFooter footer; + footer.salt = 0; + footer.magicNo = ARTERY_FONT_FOOTER_MAGIC_NO; + memset(footer.reserved, 0, sizeof(footer.reserved)); + footer.totalLength = totalLength+sizeof(footer); + ARTERY_FONT_ENCODE_WRITE(&footer, sizeof(footer)-sizeof(footer.checksum)); + footer.checksum = checksum; + ARTERY_FONT_ENCODE_WRITE(&footer.checksum, sizeof(footer.checksum)); + } + return true; + #undef ARTERY_FONT_ENCODE_WRITE + #undef ARTERY_FONT_ENCODE_REALIGN + #undef ARTERY_FONT_ENCODE_WRITE_STRING +} + +#endif + +#undef ARTERY_FONT_HEADER_TAG +#undef ARTERY_FONT_HEADER_VERSION +#undef ARTERY_FONT_HEADER_MAGIC_NO +#undef ARTERY_FONT_FOOTER_MAGIC_NO + +} diff --git a/libs/msdf-atlas-gen/include/artery-font-format/artery-font/std-artery-font.h b/libs/msdf-atlas-gen/include/artery-font-format/artery-font/std-artery-font.h new file mode 100644 index 0000000..273a7ee --- /dev/null +++ b/libs/msdf-atlas-gen/include/artery-font-format/artery-font/std-artery-font.h @@ -0,0 +1,39 @@ + +#pragma once + +#include +#include +#include "artery-font.h" + +namespace artery_font { + +template +class StdList : private std::vector { + +public: + inline StdList() { } + inline explicit StdList(int length) : std::vector((size_t) length) { } + inline int length() const { return (int) std::vector::size(); } + inline explicit operator T *() { return std::vector::data(); } + inline explicit operator const T *() const { return std::vector::data(); } + inline T &operator[](int index) { return std::vector::operator[](index); } + inline const T &operator[](int index) const { return std::vector::operator[](index); } + +}; + +class StdString : private std::string { + +public: + inline StdString() { } + inline StdString(const char *characters, int length) : std::string(characters, (size_t) length) { } + inline int length() const { return (int) std::string::size(); } + inline explicit operator const char *() const { return std::string::c_str(); } + +}; + +typedef StdList StdByteArray; + +template +using StdArteryFont = ArteryFont; + +} diff --git a/libs/msdf-atlas-gen/include/artery-font-format/artery-font/stdio-serialization.h b/libs/msdf-atlas-gen/include/artery-font-format/artery-font/stdio-serialization.h new file mode 100644 index 0000000..65d5245 --- /dev/null +++ b/libs/msdf-atlas-gen/include/artery-font-format/artery-font/stdio-serialization.h @@ -0,0 +1,23 @@ + +#pragma once + +#include +#include "serialization.h" + +namespace artery_font { + +template class LIST, class BYTE_ARRAY, class STRING> +bool read(ArteryFont &font, FILE *file); + +template class LIST, class BYTE_ARRAY, class STRING> +bool write(const ArteryFont &font, FILE *file); + +template class LIST, class BYTE_ARRAY, class STRING> +bool readFile(ArteryFont &font, const char *filename); + +template class LIST, class BYTE_ARRAY, class STRING> +bool writeFile(const ArteryFont &font, const char *filename); + +} + +#include "stdio-serialization.hpp" diff --git a/libs/msdf-atlas-gen/include/artery-font-format/artery-font/stdio-serialization.hpp b/libs/msdf-atlas-gen/include/artery-font-format/artery-font/stdio-serialization.hpp new file mode 100644 index 0000000..fff2561 --- /dev/null +++ b/libs/msdf-atlas-gen/include/artery-font-format/artery-font/stdio-serialization.hpp @@ -0,0 +1,48 @@ + +#include "stdio-serialization.h" + +namespace artery_font { + +namespace internal { + +inline int fileRead(void *buffer, int length, void *file) { + return fread(buffer, 1, length, reinterpret_cast(file)); +} + +inline int fileWrite(const void *buffer, int length, void *file) { + return fwrite(buffer, 1, length, reinterpret_cast(file)); +} + +} + +template class LIST, class BYTE_ARRAY, class STRING> +bool read(ArteryFont &font, FILE *file) { + return decode(font, file); +} + +template class LIST, class BYTE_ARRAY, class STRING> +bool write(const ArteryFont &font, FILE *file) { + return encode(font, file); +} + +template class LIST, class BYTE_ARRAY, class STRING> +bool readFile(ArteryFont &font, const char *filename) { + FILE *file = fopen(filename, "rb"); + if (!file) + return false; + bool result = read(font, file); + fclose(file); + return result; +} + +template class LIST, class BYTE_ARRAY, class STRING> +bool writeFile(const ArteryFont &font, const char *filename) { + FILE *file = fopen(filename, "wb"); + if (!file) + return false; + bool result = write(font, file); + fclose(file); + return result; +} + +} diff --git a/libs/msdf-atlas-gen/include/artery-font-format/artery-font/structures.h b/libs/msdf-atlas-gen/include/artery-font-format/artery-font/structures.h new file mode 100644 index 0000000..26dad79 --- /dev/null +++ b/libs/msdf-atlas-gen/include/artery-font-format/artery-font/structures.h @@ -0,0 +1,90 @@ + +#pragma once + +#include "types.h" +#include "enums.h" + +namespace artery_font { + +template +struct Glyph { + uint32 codepoint; + uint32 image; + struct { + REAL l, b, r, t; + } planeBounds, imageBounds; + struct { + REAL h, v; + } advance; +}; + +template +struct KernPair { + uint32 codepoint1, codepoint2; + struct { + REAL h, v; + } advance; +}; + +template class LIST, class STRING> +struct FontVariant { + uint32 flags; + uint32 weight; + CodepointType codepointType; + ImageType imageType; + uint32 fallbackVariant; + uint32 fallbackGlyph; + struct Metrics { + // In pixels: + REAL fontSize; + REAL distanceRange; + // Proportional to font size: + REAL emSize; + REAL ascender, descender; + REAL lineHeight; + REAL underlineY, underlineThickness; + REAL reserved[24]; + } metrics; + STRING name; + STRING metadata; + LIST > glyphs; + LIST > kernPairs; +}; + +template +struct FontImage { + uint32 flags; + ImageEncoding encoding; + uint32 width, height; + uint32 channels; + PixelFormat pixelFormat; + ImageType imageType; + struct { + uint32 rowLength; + ImageOrientation orientation; + } rawBinaryFormat; + uint32 childImages; + uint32 textureFlags; + STRING metadata; + BYTE_ARRAY data; +}; + +template +struct FontAppendix { + STRING metadata; + BYTE_ARRAY data; +}; + +template class LIST, class BYTE_ARRAY, class STRING> +struct ArteryFont { + typedef FontVariant Variant; + typedef FontImage Image; + typedef FontAppendix Appendix; + MetadataFormat metadataFormat; + STRING metadata; + LIST variants; + LIST images; + LIST appendices; +}; + +} diff --git a/libs/msdf-atlas-gen/include/artery-font-format/artery-font/types.h b/libs/msdf-atlas-gen/include/artery-font-format/artery-font/types.h new file mode 100644 index 0000000..d81db4e --- /dev/null +++ b/libs/msdf-atlas-gen/include/artery-font-format/artery-font/types.h @@ -0,0 +1,12 @@ + +#pragma once + +#include + +namespace artery_font { + +typedef unsigned char byte; +typedef int32_t sint32; +typedef uint32_t uint32; + +} diff --git a/libs/msdf-atlas-gen/include/artery-font-format/example.arfont b/libs/msdf-atlas-gen/include/artery-font-format/example.arfont new file mode 100644 index 0000000..a153043 Binary files /dev/null and b/libs/msdf-atlas-gen/include/artery-font-format/example.arfont differ diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/AtlasGenerator.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/AtlasGenerator.h new file mode 100644 index 0000000..75a17a7 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/AtlasGenerator.h @@ -0,0 +1,42 @@ + +#pragma once + +#include +#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 { + msdfgen::MSDFGeneratorConfig config; + bool scanlinePass = false; +}; + +/// A function that generates the bitmap for a single glyph +template +using GeneratorFunction = void (*)(const msdfgen::BitmapRef &, const GlyphGeometry &, const GeneratorAttributes &); + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/AtlasStorage.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/AtlasStorage.h new file mode 100644 index 0000000..a51f46a --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/AtlasStorage.h @@ -0,0 +1,37 @@ + +#pragma once + +#include +#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 + void put(int x, int y, const msdfgen::BitmapConstRef &subBitmap); + /// Retrieves a subsection at x, y from the atlas storage. May be implemented for only some T, N + template + void get(int x, int y, const msdfgen::BitmapRef &subBitmap) const; + +}; + +} + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/BitmapAtlasStorage.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/BitmapAtlasStorage.h new file mode 100644 index 0000000..f0f7c99 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/BitmapAtlasStorage.h @@ -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 +class BitmapAtlasStorage { + +public: + BitmapAtlasStorage(); + BitmapAtlasStorage(int width, int height); + explicit BitmapAtlasStorage(const msdfgen::BitmapConstRef &bitmap); + explicit BitmapAtlasStorage(msdfgen::Bitmap &&bitmap); + BitmapAtlasStorage(const BitmapAtlasStorage &orig, int width, int height); + BitmapAtlasStorage(const BitmapAtlasStorage &orig, int width, int height, const Remap *remapping, int count); + operator msdfgen::BitmapConstRef() const; + operator msdfgen::BitmapRef(); + operator msdfgen::Bitmap() &&; + template + void put(int x, int y, const msdfgen::BitmapConstRef &subBitmap); + void get(int x, int y, const msdfgen::BitmapRef &subBitmap) const; + +private: + msdfgen::Bitmap bitmap; + +}; + +} + +#include "BitmapAtlasStorage.hpp" diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/BitmapAtlasStorage.hpp b/libs/msdf-atlas-gen/include/msdf-atlas-gen/BitmapAtlasStorage.hpp new file mode 100644 index 0000000..fbfdc9b --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/BitmapAtlasStorage.hpp @@ -0,0 +1,65 @@ + +#include "BitmapAtlasStorage.h" + +#include +#include +#include "bitmap-blit.h" + +namespace msdf_atlas { + +template +BitmapAtlasStorage::BitmapAtlasStorage() { } + +template +BitmapAtlasStorage::BitmapAtlasStorage(int width, int height) : bitmap(width, height) { + memset((T *) bitmap, 0, sizeof(T)*N*width*height); +} + +template +BitmapAtlasStorage::BitmapAtlasStorage(const msdfgen::BitmapConstRef &bitmap) : bitmap(bitmap) { } + +template +BitmapAtlasStorage::BitmapAtlasStorage(msdfgen::Bitmap &&bitmap) : bitmap((msdfgen::Bitmap &&) bitmap) { } + +template +BitmapAtlasStorage::BitmapAtlasStorage(const BitmapAtlasStorage &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 +BitmapAtlasStorage::BitmapAtlasStorage(const BitmapAtlasStorage &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 +BitmapAtlasStorage::operator msdfgen::BitmapConstRef() const { + return bitmap; +} + +template +BitmapAtlasStorage::operator msdfgen::BitmapRef() { + return bitmap; +} + +template +BitmapAtlasStorage::operator msdfgen::Bitmap() && { + return (msdfgen::Bitmap &&) bitmap; +} + +template +template +void BitmapAtlasStorage::put(int x, int y, const msdfgen::BitmapConstRef &subBitmap) { + blit(bitmap, subBitmap, x, y, 0, 0, subBitmap.width, subBitmap.height); +} + +template +void BitmapAtlasStorage::get(int x, int y, const msdfgen::BitmapRef &subBitmap) const { + blit(subBitmap, bitmap, 0, 0, x, y, subBitmap.width, subBitmap.height); +} + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/Charset.cpp b/libs/msdf-atlas-gen/include/msdf-atlas-gen/Charset.cpp new file mode 100644 index 0000000..69c93ee --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/Charset.cpp @@ -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::const_iterator Charset::begin() const { + return codepoints.begin(); +} + +std::set::const_iterator Charset::end() const { + return codepoints.end(); +} + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/Charset.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/Charset.h new file mode 100644 index 0000000..d0cc719 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/Charset.h @@ -0,0 +1,35 @@ + +#pragma once + +#include +#include +#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::const_iterator begin() const; + std::set::const_iterator end() const; + + /// Load character set from a text file with the correct syntax + bool load(const char *filename, bool disableCharLiterals = false); + +private: + std::set codepoints; + +}; + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/DynamicAtlas.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/DynamicAtlas.h new file mode 100644 index 0000000..ce93e7f --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/DynamicAtlas.h @@ -0,0 +1,49 @@ + +#pragma once + +#include +#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 DynamicAtlas { + +public: + enum ChangeFlag { + NO_CHANGE = 0x00, + RESIZED = 0x01, + REARRANGED = 0x02 + }; + typedef int ChangeFlags; + + 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 + ChangeFlags add(GlyphGeometry *glyphs, int count, bool allowRearrange = false); + /// Allows access to generator. Do not add glyphs to the generator directly! + AtlasGenerator & atlasGenerator(); + const AtlasGenerator & atlasGenerator() const; + +private: + AtlasGenerator generator; + RectanglePacker packer; + int glyphCount; + int side; + std::vector rectangles; + std::vector remapBuffer; + int totalArea; + int padding; + +}; + +} + +#include "DynamicAtlas.hpp" diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/DynamicAtlas.hpp b/libs/msdf-atlas-gen/include/msdf-atlas-gen/DynamicAtlas.hpp new file mode 100644 index 0000000..a56d8ef --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/DynamicAtlas.hpp @@ -0,0 +1,78 @@ + +#include "DynamicAtlas.h" + +namespace msdf_atlas { + +template +DynamicAtlas::DynamicAtlas() : glyphCount(0), side(0), totalArea(0), padding(0) { } + +template +DynamicAtlas::DynamicAtlas(AtlasGenerator &&generator) : generator((AtlasGenerator &&) generator), glyphCount(0), side(0), totalArea(0), padding(0) { } + +template +typename DynamicAtlas::ChangeFlags DynamicAtlas::add(GlyphGeometry *glyphs, int count, bool allowRearrange) { + ChangeFlags changeFlags = 0; + int start = rectangles.size(); + for (int i = 0; i < count; ++i) { + if (!glyphs[i].isWhitespace()) { + int w, h; + glyphs[i].getBoxSize(w, h); + Rectangle rect = { 0, 0, w+padding, h+padding }; + 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 packerStart = start; + int remaining; + while ((remaining = packer.pack(rectangles.data()+packerStart, rectangles.size()-packerStart)) > 0) { + side = (side+!side)<<1; + while (side*side < totalArea) + side <<= 1; + if (allowRearrange) { + packer = RectanglePacker(side+padding, side+padding); + packerStart = 0; + } else { + packer.expand(side+padding, side+padding); + packerStart = rectangles.size()-remaining; + } + changeFlags |= RESIZED; + } + if (packerStart < start) { + for (int i = packerStart; i < start; ++i) { + Remap &remap = remapBuffer[i]; + remap.source = remap.target; + remap.target.x = rectangles[i].x; + remap.target.y = rectangles[i].y; + } + generator.rearrange(side, side, remapBuffer.data(), start); + changeFlags |= REARRANGED; + } else if (changeFlags&RESIZED) + generator.resize(side, side); + for (int i = start; i < (int) rectangles.size(); ++i) { + remapBuffer[i].target.x = rectangles[i].x; + remapBuffer[i].target.y = rectangles[i].y; + glyphs[remapBuffer[i].index-glyphCount].placeBox(rectangles[i].x, rectangles[i].y); + } + } + generator.generate(glyphs, count); + glyphCount += count; + return changeFlags; +} + +template +AtlasGenerator & DynamicAtlas::atlasGenerator() { + return generator; +} + +template +const AtlasGenerator & DynamicAtlas::atlasGenerator() const { + return generator; +} + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/FontGeometry.cpp b/libs/msdf-atlas-gen/include/msdf-atlas-gen/FontGeometry.cpp new file mode 100644 index 0000000..ad89d45 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/FontGeometry.cpp @@ -0,0 +1,185 @@ + +#include "FontGeometry.h" + +namespace msdf_atlas { + +FontGeometry::GlyphRange::GlyphRange() : glyphs(), rangeStart(), rangeEnd() { } + +FontGeometry::GlyphRange::GlyphRange(const std::vector *glyphs, size_t rangeStart, size_t rangeEnd) : glyphs(glyphs), rangeStart(rangeStart), rangeEnd(rangeEnd) { } + +size_t FontGeometry::GlyphRange::size() const { + return glyphs->size(); +} + +bool FontGeometry::GlyphRange::empty() const { + return glyphs->empty(); +} + +const GlyphGeometry * FontGeometry::GlyphRange::begin() const { + return glyphs->data()+rangeStart; +} + +const GlyphGeometry * FontGeometry::GlyphRange::end() const { + return glyphs->data()+rangeEnd; +} + +FontGeometry::FontGeometry() : geometryScale(1), metrics(), preferredIdentifierType(GlyphIdentifierType::UNICODE_CODEPOINT), glyphs(&ownGlyphs), rangeStart(glyphs->size()), rangeEnd(glyphs->size()) { } + +FontGeometry::FontGeometry(std::vector *glyphStorage) : geometryScale(1), metrics(), preferredIdentifierType(GlyphIdentifierType::UNICODE_CODEPOINT), glyphs(glyphStorage), rangeStart(glyphs->size()), rangeEnd(glyphs->size()) { } + +int FontGeometry::loadGlyphset(msdfgen::FontHandle *font, double fontScale, const Charset &glyphset, bool preprocessGeometry, bool enableKerning) { + if (!(glyphs->size() == rangeEnd && loadMetrics(font, fontScale))) + return -1; + glyphs->reserve(glyphs->size()+glyphset.size()); + int loaded = 0; + for (unicode_t index : glyphset) { + GlyphGeometry glyph; + if (glyph.load(font, geometryScale, msdfgen::GlyphIndex(index), preprocessGeometry)) { + addGlyph((GlyphGeometry &&) glyph); + ++loaded; + } + } + if (enableKerning) + loadKerning(font); + preferredIdentifierType = GlyphIdentifierType::GLYPH_INDEX; + return loaded; +} + +int FontGeometry::loadCharset(msdfgen::FontHandle *font, double fontScale, const Charset &charset, bool preprocessGeometry, bool enableKerning) { + if (!(glyphs->size() == rangeEnd && loadMetrics(font, fontScale))) + return -1; + glyphs->reserve(glyphs->size()+charset.size()); + int loaded = 0; + for (unicode_t cp : charset) { + GlyphGeometry glyph; + if (glyph.load(font, geometryScale, cp, preprocessGeometry)) { + addGlyph((GlyphGeometry &&) glyph); + ++loaded; + } + } + if (enableKerning) + loadKerning(font); + preferredIdentifierType = GlyphIdentifierType::UNICODE_CODEPOINT; + return loaded; +} + +bool FontGeometry::loadMetrics(msdfgen::FontHandle *font, double fontScale) { + if (!msdfgen::getFontMetrics(metrics, font)) + return false; + if (metrics.emSize <= 0) + metrics.emSize = MSDF_ATLAS_DEFAULT_EM_SIZE; + geometryScale = fontScale/metrics.emSize; + metrics.emSize *= geometryScale; + metrics.ascenderY *= geometryScale; + metrics.descenderY *= geometryScale; + metrics.lineHeight *= geometryScale; + metrics.underlineY *= geometryScale; + metrics.underlineThickness *= geometryScale; + return true; +} + +bool FontGeometry::addGlyph(const GlyphGeometry &glyph) { + if (glyphs->size() != rangeEnd) + return false; + glyphsByIndex.insert(std::make_pair(glyph.getIndex(), rangeEnd)); + if (glyph.getCodepoint()) + glyphsByCodepoint.insert(std::make_pair(glyph.getCodepoint(), rangeEnd)); + glyphs->push_back(glyph); + ++rangeEnd; + return true; +} + +bool FontGeometry::addGlyph(GlyphGeometry &&glyph) { + if (glyphs->size() != rangeEnd) + return false; + glyphsByIndex.insert(std::make_pair(glyph.getIndex(), rangeEnd)); + if (glyph.getCodepoint()) + glyphsByCodepoint.insert(std::make_pair(glyph.getCodepoint(), rangeEnd)); + glyphs->push_back((GlyphGeometry &&) glyph); + ++rangeEnd; + return true; +} + +int FontGeometry::loadKerning(msdfgen::FontHandle *font) { + int loaded = 0; + for (size_t i = rangeStart; i < rangeEnd; ++i) + for (size_t j = rangeStart; j < rangeEnd; ++j) { + double advance; + if (msdfgen::getKerning(advance, font, (*glyphs)[i].getGlyphIndex(), (*glyphs)[j].getGlyphIndex()) && advance) { + kerning[std::make_pair((*glyphs)[i].getIndex(), (*glyphs)[j].getIndex())] = geometryScale*advance; + ++loaded; + } + } + return loaded; +} + +void FontGeometry::setName(const char *name) { + if (name) + this->name = name; + else + this->name.clear(); +} + +double FontGeometry::getGeometryScale() const { + return geometryScale; +} + +const msdfgen::FontMetrics & FontGeometry::getMetrics() const { + return metrics; +} + +GlyphIdentifierType FontGeometry::getPreferredIdentifierType() const { + return preferredIdentifierType; +} + +FontGeometry::GlyphRange FontGeometry::getGlyphs() const { + return GlyphRange(glyphs, rangeStart, rangeEnd); +} + +const GlyphGeometry * FontGeometry::getGlyph(msdfgen::GlyphIndex index) const { + std::map::const_iterator it = glyphsByIndex.find(index.getIndex()); + if (it != glyphsByIndex.end()) + return &(*glyphs)[it->second]; + return nullptr; +} + +const GlyphGeometry * FontGeometry::getGlyph(unicode_t codepoint) const { + std::map::const_iterator it = glyphsByCodepoint.find(codepoint); + if (it != glyphsByCodepoint.end()) + return &(*glyphs)[it->second]; + return nullptr; +} + +bool FontGeometry::getAdvance(double &advance, msdfgen::GlyphIndex index1, msdfgen::GlyphIndex index2) const { + const GlyphGeometry *glyph1 = getGlyph(index1); + if (!glyph1) + return false; + advance = glyph1->getAdvance(); + std::map, double>::const_iterator it = kerning.find(std::make_pair(index1.getIndex(), index2.getIndex())); + if (it != kerning.end()) + advance += it->second; + return true; +} + +bool FontGeometry::getAdvance(double &advance, unicode_t codepoint1, unicode_t codepoint2) const { + const GlyphGeometry *glyph1, *glyph2; + if (!((glyph1 = getGlyph(codepoint1)) && (glyph2 = getGlyph(codepoint2)))) + return false; + advance = glyph1->getAdvance(); + std::map, double>::const_iterator it = kerning.find(std::make_pair(glyph1->getIndex(), glyph2->getIndex())); + if (it != kerning.end()) + advance += it->second; + return true; +} + +const std::map, double> & FontGeometry::getKerning() const { + return kerning; +} + +const char * FontGeometry::getName() const { + if (name.empty()) + return nullptr; + return name.c_str(); +} + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/FontGeometry.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/FontGeometry.h new file mode 100644 index 0000000..4ac230e --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/FontGeometry.h @@ -0,0 +1,86 @@ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include "types.h" +#include "GlyphGeometry.h" +#include "Charset.h" + +#define MSDF_ATLAS_DEFAULT_EM_SIZE 32.0 + +namespace msdf_atlas { + +/// Represents the geometry of all glyphs of a given font or font variant +class FontGeometry { + +public: + class GlyphRange { + public: + GlyphRange(); + GlyphRange(const std::vector *glyphs, size_t rangeStart, size_t rangeEnd); + size_t size() const; + bool empty() const; + const GlyphGeometry * begin() const; + const GlyphGeometry * end() const; + private: + const std::vector *glyphs; + size_t rangeStart, rangeEnd; + }; + + FontGeometry(); + explicit FontGeometry(std::vector *glyphStorage); + + /// Loads all glyphs in a glyphset (Charset elements are glyph indices), returns the number of successfully loaded glyphs + int loadGlyphset(msdfgen::FontHandle *font, double fontScale, const Charset &glyphset, bool preprocessGeometry = true, bool enableKerning = true); + /// Loads all glyphs in a charset (Charset elements are Unicode codepoints), returns the number of successfully loaded glyphs + int loadCharset(msdfgen::FontHandle *font, double fontScale, const Charset &charset, bool preprocessGeometry = true, bool enableKerning = true); + + /// Only loads font metrics and geometry scale from font + bool loadMetrics(msdfgen::FontHandle *font, double fontScale); + /// Adds a loaded glyph + bool addGlyph(const GlyphGeometry &glyph); + bool addGlyph(GlyphGeometry &&glyph); + /// Loads kerning pairs for all glyphs that are currently present, returns the number of loaded kerning pairs + int loadKerning(msdfgen::FontHandle *font); + /// Sets a name to be associated with the font + void setName(const char *name); + + /// Returns the geometry scale to be used when loading glyphs + double getGeometryScale() const; + /// Returns the processed font metrics + const msdfgen::FontMetrics & getMetrics() const; + /// Returns the type of identifier that was used to load glyphs + GlyphIdentifierType getPreferredIdentifierType() const; + /// Returns the list of all glyphs + GlyphRange getGlyphs() const; + /// Finds a glyph by glyph index or Unicode codepoint, returns null if not found + const GlyphGeometry * getGlyph(msdfgen::GlyphIndex index) const; + const GlyphGeometry * getGlyph(unicode_t codepoint) const; + /// Outputs the advance between two glyphs with kerning taken into consideration, returns false on failure + bool getAdvance(double &advance, msdfgen::GlyphIndex index1, msdfgen::GlyphIndex index2) const; + bool getAdvance(double &advance, unicode_t codepoint1, unicode_t codepoint2) const; + /// Returns the complete mapping of kerning pairs (by glyph indices) and their respective advance values + const std::map, double> & getKerning() const; + /// Returns the name associated with the font or null if not set + const char * getName() const; + +private: + double geometryScale; + msdfgen::FontMetrics metrics; + GlyphIdentifierType preferredIdentifierType; + std::vector *glyphs; + size_t rangeStart, rangeEnd; + std::map glyphsByIndex; + std::map glyphsByCodepoint; + std::map, double> kerning; + std::vector ownGlyphs; + std::string name; + +}; + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/GlyphBox.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/GlyphBox.h new file mode 100644 index 0000000..df90923 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/GlyphBox.h @@ -0,0 +1,19 @@ + +#pragma once + +#include "Rectangle.h" + +namespace msdf_atlas { + +/// The glyph box - its bounds in plane and atlas +struct GlyphBox { + int index; + double advance; + struct { + double l, b, r, t; + } bounds; + Rectangle rect; + +}; + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/GlyphGeometry.cpp b/libs/msdf-atlas-gen/include/msdf-atlas-gen/GlyphGeometry.cpp new file mode 100644 index 0000000..72684d0 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/GlyphGeometry.cpp @@ -0,0 +1,178 @@ + +#include "GlyphGeometry.h" + +#include +#include + +namespace msdf_atlas { + +GlyphGeometry::GlyphGeometry() : index(), codepoint(), geometryScale(), bounds(), advance(), box() { } + +bool GlyphGeometry::load(msdfgen::FontHandle *font, double geometryScale, msdfgen::GlyphIndex index, bool preprocessGeometry) { + if (font && msdfgen::loadGlyph(shape, font, index, &advance) && shape.validate()) { + this->index = index.getIndex(); + this->geometryScale = geometryScale; + codepoint = 0; + advance *= geometryScale; + #ifdef MSDFGEN_USE_SKIA + if (preprocessGeometry) + msdfgen::resolveShapeGeometry(shape); + #endif + shape.normalize(); + bounds = shape.getBounds(); + #ifdef MSDFGEN_USE_SKIA + if (!preprocessGeometry) + #endif + { + // Determine if shape is winded incorrectly and reverse it in that case + msdfgen::Point2 outerPoint(bounds.l-(bounds.r-bounds.l)-1, bounds.b-(bounds.t-bounds.b)-1); + if (msdfgen::SimpleTrueShapeDistanceFinder::oneShotDistance(shape, outerPoint) > 0) { + for (msdfgen::Contour &contour : shape.contours) + contour.reverse(); + } + } + return true; + } + return false; +} + +bool GlyphGeometry::load(msdfgen::FontHandle *font, double geometryScale, unicode_t codepoint, bool preprocessGeometry) { + msdfgen::GlyphIndex index; + if (msdfgen::getGlyphIndex(index, font, codepoint)) { + if (load(font, geometryScale, index, preprocessGeometry)) { + this->codepoint = codepoint; + return true; + } + } + return false; +} + +void GlyphGeometry::edgeColoring(void (*fn)(msdfgen::Shape &, double, unsigned long long), double angleThreshold, unsigned long long seed) { + fn(shape, angleThreshold, seed); +} + +void GlyphGeometry::wrapBox(double scale, double range, double miterLimit) { + scale *= geometryScale; + range /= geometryScale; + box.range = range; + box.scale = scale; + if (bounds.l < bounds.r && bounds.b < bounds.t) { + double l = bounds.l, b = bounds.b, r = bounds.r, t = bounds.t; + l -= .5*range, b -= .5*range; + r += .5*range, t += .5*range; + if (miterLimit > 0) + shape.boundMiters(l, b, r, t, .5*range, miterLimit, 1); + double w = scale*(r-l); + double h = scale*(t-b); + box.rect.w = (int) ceil(w)+1; + box.rect.h = (int) ceil(h)+1; + box.translate.x = -l+.5*(box.rect.w-w)/scale; + box.translate.y = -b+.5*(box.rect.h-h)/scale; + } 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; +} + +void GlyphGeometry::setBoxRect(const Rectangle &rect) { + box.rect = rect; +} + +int GlyphGeometry::getIndex() const { + return index; +} + +msdfgen::GlyphIndex GlyphGeometry::getGlyphIndex() const { + return msdfgen::GlyphIndex(index); +} + +unicode_t GlyphGeometry::getCodepoint() const { + return codepoint; +} + +int GlyphGeometry::getIdentifier(GlyphIdentifierType type) const { + switch (type) { + case GlyphIdentifierType::GLYPH_INDEX: + return index; + case GlyphIdentifierType::UNICODE_CODEPOINT: + return (int) codepoint; + } + return 0; +} + +const msdfgen::Shape & GlyphGeometry::getShape() const { + return shape; +} + +double GlyphGeometry::getAdvance() const { + return advance; +} + +Rectangle GlyphGeometry::getBoxRect() const { + return box.rect; +} + +void GlyphGeometry::getBoxRect(int &x, int &y, int &w, int &h) const { + x = box.rect.x, y = box.rect.y; + w = box.rect.w, h = box.rect.h; +} + +void GlyphGeometry::getBoxSize(int &w, int &h) const { + w = box.rect.w, h = box.rect.h; +} + +double GlyphGeometry::getBoxRange() const { + return box.range; +} + +msdfgen::Projection GlyphGeometry::getBoxProjection() const { + return msdfgen::Projection(msdfgen::Vector2(box.scale), box.translate); +} + +double GlyphGeometry::getBoxScale() const { + return box.scale; +} + +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) { + double invBoxScale = 1/box.scale; + l = geometryScale*(-box.translate.x+.5*invBoxScale); + b = geometryScale*(-box.translate.y+.5*invBoxScale); + r = geometryScale*(-box.translate.x+(box.rect.w-.5)*invBoxScale); + t = geometryScale*(-box.translate.y+(box.rect.h-.5)*invBoxScale); + } else + l = 0, b = 0, r = 0, t = 0; +} + +void GlyphGeometry::getQuadAtlasBounds(double &l, double &b, double &r, double &t) const { + if (box.rect.w > 0 && box.rect.h > 0) { + l = box.rect.x+.5; + b = box.rect.y+.5; + r = box.rect.x+box.rect.w-.5; + t = box.rect.y+box.rect.h-.5; + } else + l = 0, b = 0, r = 0, t = 0; +} + +bool GlyphGeometry::isWhitespace() const { + return shape.contours.empty(); +} + +GlyphGeometry::operator GlyphBox() const { + GlyphBox box; + box.index = index; + 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; +} + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/GlyphGeometry.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/GlyphGeometry.h new file mode 100644 index 0000000..f1c5d2a --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/GlyphGeometry.h @@ -0,0 +1,79 @@ + +#pragma once + +#include +#include +#include "types.h" +#include "Rectangle.h" +#include "GlyphBox.h" + +namespace msdf_atlas { + +/// Represents 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, double geometryScale, msdfgen::GlyphIndex index, bool preprocessGeometry = true); + bool load(msdfgen::FontHandle *font, double geometryScale, unicode_t codepoint, bool preprocessGeometry = true); + /// Applies edge coloring to glyph shape + void edgeColoring(void (*fn)(msdfgen::Shape &, double, unsigned long long), double angleThreshold, unsigned long long seed); + /// Computes the dimensions of the glyph's box as well as the transformation for the generator function + void wrapBox(double scale, double range, double miterLimit); + /// Sets the glyph's box's position in the atlas + void placeBox(int x, int y); + /// Sets the glyph's box's rectangle in the atlas + void setBoxRect(const Rectangle &rect); + /// Returns the glyph's index within the font + int getIndex() const; + /// Returns the glyph's index as a msdfgen::GlyphIndex + msdfgen::GlyphIndex getGlyphIndex() const; + /// Returns the Unicode codepoint represented by the glyph or 0 if unknown + unicode_t getCodepoint() const; + /// Returns the glyph's identifier specified by the supplied identifier type + int getIdentifier(GlyphIdentifierType type) const; + /// Returns the glyph's shape + const msdfgen::Shape & getShape() const; + /// Returns the glyph's advance + double getAdvance() const; + /// Returns the glyph's box in the atlas + Rectangle getBoxRect() const; + /// Outputs the position and dimensions of the glyph's box in the atlas + void getBoxRect(int &x, int &y, int &w, int &h) const; + /// Outputs the dimensions of the glyph's box in the atlas + void getBoxSize(int &w, int &h) const; + /// Returns the range needed to generate the glyph's SDF + double getBoxRange() const; + /// Returns the projection needed to generate the glyph's bitmap + msdfgen::Projection getBoxProjection() const; + /// Returns the scale needed to generate the glyph's bitmap + double getBoxScale() const; + /// Returns the translation vector needed to generate the glyph's bitmap + 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: + int index; + unicode_t codepoint; + double geometryScale; + msdfgen::Shape shape; + msdfgen::Shape::Bounds bounds; + double advance; + struct { + Rectangle rect; + double range; + double scale; + msdfgen::Vector2 translate; + } box; + +}; + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/ImmediateAtlasGenerator.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/ImmediateAtlasGenerator.h new file mode 100644 index 0000000..cdeb070 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/ImmediateAtlasGenerator.h @@ -0,0 +1,47 @@ + +#pragma once + +#include +#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 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; + /// Returns the layout of the contained glyphs as a list of GlyphBoxes + const std::vector & getLayout() const; + +private: + AtlasStorage storage; + std::vector layout; + std::vector glyphBuffer; + std::vector errorCorrectionBuffer; + GeneratorAttributes attributes; + int threadCount; + +}; + +} + +#include "ImmediateAtlasGenerator.hpp" diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/ImmediateAtlasGenerator.hpp b/libs/msdf-atlas-gen/include/msdf-atlas-gen/ImmediateAtlasGenerator.hpp new file mode 100644 index 0000000..b835b7e --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/ImmediateAtlasGenerator.hpp @@ -0,0 +1,82 @@ + +#include "ImmediateAtlasGenerator.h" + +#include + +namespace msdf_atlas { + +template GEN_FN, class AtlasStorage> +ImmediateAtlasGenerator::ImmediateAtlasGenerator() : threadCount(1) { } + +template GEN_FN, class AtlasStorage> +ImmediateAtlasGenerator::ImmediateAtlasGenerator(int width, int height) : storage(width, height), threadCount(1) { } + +template GEN_FN, class AtlasStorage> +void ImmediateAtlasGenerator::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); + if (threadCount*maxBoxArea > (int) errorCorrectionBuffer.size()) + errorCorrectionBuffer.resize(threadCount*maxBoxArea); + std::vector threadAttributes(threadCount); + for (int i = 0; i < threadCount; ++i) { + threadAttributes[i] = attributes; + threadAttributes[i].config.errorCorrection.buffer = errorCorrectionBuffer.data()+i*maxBoxArea; + } + + Workload([this, glyphs, &threadAttributes, threadBufferSize](int i, int threadNo) -> bool { + const GlyphGeometry &glyph = glyphs[i]; + if (!glyph.isWhitespace()) { + int l, b, w, h; + glyph.getBoxRect(l, b, w, h); + msdfgen::BitmapRef glyphBitmap(glyphBuffer.data()+threadNo*threadBufferSize, w, h); + GEN_FN(glyphBitmap, glyph, threadAttributes[threadNo]); + storage.put(l, b, msdfgen::BitmapConstRef(glyphBitmap)); + } + return true; + }, count).finish(threadCount); +} + +template GEN_FN, class AtlasStorage> +void ImmediateAtlasGenerator::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 GEN_FN, class AtlasStorage> +void ImmediateAtlasGenerator::resize(int width, int height) { + AtlasStorage newStorage((AtlasStorage &&) storage, width, height); + storage = (AtlasStorage &&) newStorage; +} + +template GEN_FN, class AtlasStorage> +void ImmediateAtlasGenerator::setAttributes(const GeneratorAttributes &attributes) { + this->attributes = attributes; +} + +template GEN_FN, class AtlasStorage> +void ImmediateAtlasGenerator::setThreadCount(int threadCount) { + this->threadCount = threadCount; +} + +template GEN_FN, class AtlasStorage> +const AtlasStorage & ImmediateAtlasGenerator::atlasStorage() const { + return storage; +} + +template GEN_FN, class AtlasStorage> +const std::vector & ImmediateAtlasGenerator::getLayout() const { + return layout; +} + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/Rectangle.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/Rectangle.h new file mode 100644 index 0000000..8221be4 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/Rectangle.h @@ -0,0 +1,14 @@ + +#pragma once + +namespace msdf_atlas { + +struct Rectangle { + int x, y, w, h; +}; + +struct OrientedRectangle : Rectangle { + bool rotated; +}; + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/RectanglePacker.cpp b/libs/msdf-atlas-gen/include/msdf-atlas-gen/RectanglePacker.cpp new file mode 100644 index 0000000..29a5f04 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/RectanglePacker.cpp @@ -0,0 +1,157 @@ + +#include "RectanglePacker.h" + +#include + +namespace msdf_atlas { + +#define WORST_FIT 0x7fffffff + +template +static void removeFromUnorderedVector(std::vector &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::expand(int width, int height) { + if (width > 0 && height > 0) { + int oldWidth = 0, oldHeight = 0; + for (const Rectangle &space : spaces) { + if (space.x+space.w > oldWidth) + oldWidth = space.x+space.w; + if (space.y+space.h > oldHeight) + oldHeight = space.y+space.h; + } + spaces.push_back(Rectangle { 0, 0, width, height }); + splitSpace(int(spaces.size()-1), oldWidth, oldHeight); + } +} + +void RectanglePacker::splitSpace(int index, int w, int h) { + Rectangle space = spaces[index]; + removeFromUnorderedVector(spaces, index); + Rectangle a = { space.x, space.y+h, w, space.h-h }; + Rectangle b = { space.x+w, space.y, space.w-w, h }; + if (w*(space.h-h) < h*(space.w-w)) + 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 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 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(); +} + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/RectanglePacker.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/RectanglePacker.h new file mode 100644 index 0000000..e4490ff --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/RectanglePacker.h @@ -0,0 +1,30 @@ + +#pragma once + +#include +#include "Rectangle.h" + +namespace msdf_atlas { + +/// Guillotine 2D single bin packer +class RectanglePacker { + +public: + RectanglePacker(); + RectanglePacker(int width, int height); + /// Expands the packing area - both width and height must be greater or equal to the previous value + void expand(int width, int height); + /// Packs the rectangle array, returns how many didn't fit (0 on success) + int pack(Rectangle *rectangles, int count); + int pack(OrientedRectangle *rectangles, int count); + +private: + std::vector spaces; + + static int rateFit(int w, int h, int sw, int sh); + + void splitSpace(int index, int w, int h); + +}; + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/Remap.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/Remap.h new file mode 100644 index 0000000..4a69889 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/Remap.h @@ -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; +}; + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/TightAtlasPacker.cpp b/libs/msdf-atlas-gen/include/msdf-atlas-gen/TightAtlasPacker.cpp new file mode 100644 index 0000000..5c623ea --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/TightAtlasPacker.cpp @@ -0,0 +1,168 @@ + +#include "TightAtlasPacker.h" + +#include +#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 rectangles; + std::vector 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 dimensions = std::make_pair(width, height); + switch (dimensionsConstraint) { + case DimensionsConstraint::POWER_OF_TWO_SQUARE: + dimensions = packRectangles(rectangles.data(), rectangles.size(), padding); + break; + case DimensionsConstraint::POWER_OF_TWO_RECTANGLE: + dimensions = packRectangles(rectangles.data(), rectangles.size(), padding); + break; + case DimensionsConstraint::MULTIPLE_OF_FOUR_SQUARE: + dimensions = packRectangles >(rectangles.data(), rectangles.size(), padding); + break; + case DimensionsConstraint::EVEN_SQUARE: + dimensions = packRectangles >(rectangles.data(), rectangles.size(), padding); + break; + case DimensionsConstraint::SQUARE: + dimensions = packRectangles >(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 && ((maxScale = 2*minScale), TRY_PACK(maxScale))) + minScale = maxScale; + } else { + while (minScale > 1e-32 && ((minScale = .5*maxScale), !TRY_PACK(minScale))) + 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; +} + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/TightAtlasPacker.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/TightAtlasPacker.h new file mode 100644 index 0000000..35bb2a9 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/TightAtlasPacker.h @@ -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); + +}; + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/Workload.cpp b/libs/msdf-atlas-gen/include/msdf-atlas-gen/Workload.cpp new file mode 100644 index 0000000..6050bd0 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/Workload.cpp @@ -0,0 +1,50 @@ + +#include "Workload.h" + +#include +#include +#include +#include + +namespace msdf_atlas { + +Workload::Workload() : chunks(0) { } + +Workload::Workload(const std::function &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 next(0); + std::function threadWorker = [this, &result, &next](int threadNo) { + for (int i = next++; result && i < chunks; i = next++) { + if (!workerFunction(i, threadNo)) + result = false; + } + }; + std::vector 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(std::min(threadCount, chunks)); + return false; +} + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/Workload.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/Workload.h new file mode 100644 index 0000000..cee0c9d --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/Workload.h @@ -0,0 +1,32 @@ + +#pragma once + +#include + +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 &workerFunction, int chunks); + /// Runs the process and returns true if all chunks have been processed + bool finish(int threadCount); + +private: + std::function workerFunction; + int chunks; + + bool finishSequential(); + bool finishParallel(int threadCount); + +}; + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/artery-font-export.cpp b/libs/msdf-atlas-gen/include/msdf-atlas-gen/artery-font-export.cpp new file mode 100644 index 0000000..f617a58 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/artery-font-export.cpp @@ -0,0 +1,194 @@ + +#include "artery-font-export.h" + +#ifndef MSDF_ATLAS_NO_ARTERY_FONT + +#include +#include +#include "GlyphGeometry.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; +} + +static artery_font::CodepointType convertCodepointType(GlyphIdentifierType glyphIdentifierType) { + switch (glyphIdentifierType) { + case GlyphIdentifierType::GLYPH_INDEX: + return artery_font::CP_INDEXED; + case GlyphIdentifierType::UNICODE_CODEPOINT: + return artery_font::CP_UNICODE; + } + return artery_font::CP_UNSPECIFIED; +} + +template +static bool encodeTiff(std::vector &output, const msdfgen::BitmapConstRef &atlas) { + // TODO + return false; +} + +template +static artery_font::PixelFormat getPixelFormat(); + +template <> +artery_font::PixelFormat getPixelFormat() { + return artery_font::PIXEL_UNSIGNED8; +} +template <> +artery_font::PixelFormat getPixelFormat() { + return artery_font::PIXEL_FLOAT32; +} + +template +bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties) { + artery_font::StdArteryFont arfont = { }; + arfont.metadataFormat = artery_font::METADATA_NONE; + + arfont.variants = artery_font::StdList::Variant>(fontCount); + for (int i = 0; i < fontCount; ++i) { + const FontGeometry &font = fonts[i]; + GlyphIdentifierType identifierType = font.getPreferredIdentifierType(); + const msdfgen::FontMetrics &fontMetrics = font.getMetrics(); + typename artery_font::StdArteryFont::Variant &fontVariant = arfont.variants[i] = typename artery_font::StdArteryFont::Variant(); + fontVariant.codepointType = convertCodepointType(identifierType); + fontVariant.imageType = convertImageType(properties.imageType); + fontVariant.metrics.fontSize = REAL(properties.fontSize*fontMetrics.emSize); + if (properties.imageType != ImageType::HARD_MASK) + fontVariant.metrics.distanceRange = REAL(properties.pxRange); + fontVariant.metrics.emSize = REAL(fontMetrics.emSize); + fontVariant.metrics.ascender = REAL(fontMetrics.ascenderY); + fontVariant.metrics.descender = REAL(fontMetrics.descenderY); + fontVariant.metrics.lineHeight = REAL(fontMetrics.lineHeight); + fontVariant.metrics.underlineY = REAL(fontMetrics.underlineY); + fontVariant.metrics.underlineThickness = REAL(fontMetrics.underlineThickness); + const char *name = font.getName(); + if (name) + (std::string &) fontVariant.name = name; + fontVariant.glyphs = artery_font::StdList >(font.getGlyphs().size()); + int j = 0; + for (const GlyphGeometry &glyphGeom : font.getGlyphs()) { + artery_font::Glyph &glyph = fontVariant.glyphs[j++]; + glyph.codepoint = glyphGeom.getIdentifier(identifierType); + glyph.image = 0; + double l, b, r, t; + glyphGeom.getQuadPlaneBounds(l, b, r, t); + glyph.planeBounds.l = REAL(l); + glyph.planeBounds.b = REAL(b); + glyph.planeBounds.r = REAL(r); + glyph.planeBounds.t = REAL(t); + glyphGeom.getQuadAtlasBounds(l, b, r, t); + glyph.imageBounds.l = REAL(l); + glyph.imageBounds.b = REAL(b); + glyph.imageBounds.r = REAL(r); + glyph.imageBounds.t = REAL(t); + glyph.advance.h = REAL(glyphGeom.getAdvance()); + glyph.advance.v = REAL(0); + } + switch (identifierType) { + case GlyphIdentifierType::GLYPH_INDEX: + for (const std::pair, double> &elem : font.getKerning()) { + artery_font::KernPair kernPair = { }; + kernPair.codepoint1 = elem.first.first; + kernPair.codepoint2 = elem.first.second; + kernPair.advance.h = REAL(elem.second); + ((std::vector > &) fontVariant.kernPairs).push_back((artery_font::KernPair &&) kernPair); + } + break; + case GlyphIdentifierType::UNICODE_CODEPOINT: + for (const std::pair, double> &elem : font.getKerning()) { + const GlyphGeometry *glyph1 = font.getGlyph(msdfgen::GlyphIndex(elem.first.first)); + const GlyphGeometry *glyph2 = font.getGlyph(msdfgen::GlyphIndex(elem.first.second)); + if (glyph1 && glyph2 && glyph1->getCodepoint() && glyph2->getCodepoint()) { + artery_font::KernPair kernPair = { }; + kernPair.codepoint1 = glyph1->getCodepoint(); + kernPair.codepoint2 = glyph2->getCodepoint(); + kernPair.advance.h = REAL(elem.second); + ((std::vector > &) fontVariant.kernPairs).push_back((artery_font::KernPair &&) kernPair); + } + } + break; + } + } + + arfont.images = artery_font::StdList::Image>(1); + { + typename artery_font::StdArteryFont::Image &image = arfont.images[0] = typename artery_font::StdArteryFont::Image(); + image.width = atlas.width; + image.height = atlas.height; + image.channels = N; + image.imageType = convertImageType(properties.imageType); + switch (properties.imageFormat) { + case ImageFormat::PNG: + image.encoding = artery_font::IMAGE_PNG; + image.pixelFormat = artery_font::PIXEL_UNSIGNED8; + if (!encodePng((std::vector &) image.data, atlas)) + return false; + break; + case ImageFormat::TIFF: + image.encoding = artery_font::IMAGE_TIFF; + image.pixelFormat = artery_font::PIXEL_FLOAT32; + if (!encodeTiff((std::vector &) image.data, 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()) + return false; + image.encoding = artery_font::IMAGE_RAW_BINARY; + image.rawBinaryFormat.rowLength = N*sizeof(T)*atlas.width; + image.data = artery_font::StdByteArray(N*sizeof(T)*atlas.width*atlas.height); + switch (properties.yDirection) { + case YDirection::BOTTOM_UP: + image.rawBinaryFormat.orientation = artery_font::ORIENTATION_BOTTOM_UP; + memcpy((byte *) image.data, atlas.pixels, N*sizeof(T)*atlas.width*atlas.height); + break; + case YDirection::TOP_DOWN: { + image.rawBinaryFormat.orientation = artery_font::ORIENTATION_TOP_DOWN; + byte *imageData = (byte *) image.data; + for (int y = atlas.height-1; y >= 0; --y) { + memcpy(imageData, atlas.pixels+N*atlas.width*y, N*sizeof(T)*atlas.width); + imageData += N*sizeof(T)*atlas.width; + } + break; + } + } + break; + default: + return false; + } + } + + return artery_font::writeFile(arfont, filename); +} + +template bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); +template bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); +template bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); +template bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); +template bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); +template bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); + +} + +#endif diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/artery-font-export.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/artery-font-export.h new file mode 100644 index 0000000..f61d417 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/artery-font-export.h @@ -0,0 +1,27 @@ + +#pragma once + +#ifndef MSDF_ATLAS_NO_ARTERY_FONT + +#include +#include +#include "types.h" +#include "FontGeometry.h" + +namespace msdf_atlas { + +struct ArteryFontExportProperties { + double fontSize; + double pxRange; + ImageType imageType; + ImageFormat imageFormat; + YDirection yDirection; +}; + +/// Encodes the atlas bitmap and its layout into an Artery Atlas Font file +template +bool exportArteryFont(const FontGeometry *fonts, int fontCount, const msdfgen::BitmapConstRef &atlas, const char *filename, const ArteryFontExportProperties &properties); + +} + +#endif diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/bitmap-blit.cpp b/libs/msdf-atlas-gen/include/msdf-atlas-gen/bitmap-blit.cpp new file mode 100644 index 0000000..56054ed --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/bitmap-blit.cpp @@ -0,0 +1,72 @@ + +#include "bitmap-blit.h" + +#include +#include + +namespace msdf_atlas { + +#define BOUND_AREA() { \ + if (dx < 0) w += dx, sx -= dx, dx = 0; \ + if (dy < 0) h += dy, sy -= dy, dy = 0; \ + if (sx < 0) w += sx, dx -= sx, sx = 0; \ + if (sy < 0) h += sy, dy -= sy, sy = 0; \ + w = std::max(0, std::min(w, std::min(dst.width-dx, src.width-sx))); \ + h = std::max(0, std::min(h, std::min(dst.height-dy, src.height-sy))); \ +} + +template +void blitSameType(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h) { + BOUND_AREA(); + for (int y = 0; y < h; ++y) + memcpy(dst(dx, dy+y), src(sx, sy+y), sizeof(T)*N*w); +} + +#define BLIT_SAME_TYPE_IMPL(T, N) void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &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 &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h) { + BOUND_AREA(); + for (int y = 0; y < h; ++y) { + byte *dstPixel = dst(dx, dy+y); + for (int x = 0; x < w; ++x) { + const float *srcPixel = src(sx+x, sy+y); + *dstPixel++ = msdfgen::pixelFloatToByte(*srcPixel); + } + } +} + +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h) { + BOUND_AREA(); + for (int y = 0; y < h; ++y) { + byte *dstPixel = dst(dx, dy+y); + for (int x = 0; x < w; ++x) { + 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 &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h) { + BOUND_AREA(); + for (int y = 0; y < h; ++y) { + byte *dstPixel = dst(dx, dy+y); + for (int x = 0; x < w; ++x) { + 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]); + } + } +} + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/bitmap-blit.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/bitmap-blit.h new file mode 100644 index 0000000..b186d12 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/bitmap-blit.h @@ -0,0 +1,26 @@ + +#pragma once + +#include +#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 &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); + +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); + +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); +void blit(const msdfgen::BitmapRef &dst, const msdfgen::BitmapConstRef &src, int dx, int dy, int sx, int sy, int w, int h); + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/charset-parser.cpp b/libs/msdf-atlas-gen/include/msdf-atlas-gen/charset-parser.cpp new file mode 100644 index 0000000..23c68a3 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/charset-parser.cpp @@ -0,0 +1,250 @@ + +#include "Charset.h" + +#include +#include +#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, bool disableCharLiterals) { + + if (FILE *f = fopen(filename, "rb")) { + + enum { + CLEAR, + TIGHT, + RANGE_BRACKET, + RANGE_START, + RANGE_SEPARATOR, + RANGE_END + } state = CLEAR; + + std::string buffer; + std::vector unicodeBuffer; + unicode_t rangeStart = 0; + for (int c = fgetc(f), start = true; c >= 0; start = false) { + switch (c) { + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // number + if (!(state == CLEAR || state == RANGE_BRACKET || state == RANGE_SEPARATOR)) + goto FAIL; + buffer.push_back((char) c); + c = readWord(buffer, f); + { + int cp; + if (!parseInt(cp, buffer.c_str())) + goto FAIL; + switch (state) { + case CLEAR: + if (cp >= 0) + add((unicode_t) cp); + state = TIGHT; + break; + case RANGE_BRACKET: + rangeStart = (unicode_t) cp; + state = RANGE_START; + break; + case RANGE_SEPARATOR: + for (unicode_t u = rangeStart; (int) u <= cp; ++u) + add(u); + state = RANGE_END; + break; + default:; + } + } + buffer.clear(); + continue; // next character already read + case '\'': // single UTF-8 character + if (!(state == CLEAR || state == RANGE_BRACKET || state == RANGE_SEPARATOR) || disableCharLiterals) + goto FAIL; + if (!readString(buffer, f, '\'')) + goto FAIL; + utf8Decode(unicodeBuffer, buffer.c_str()); + if (unicodeBuffer.size() == 1) { + switch (state) { + case CLEAR: + if (unicodeBuffer[0] > 0) + add(unicodeBuffer[0]); + state = TIGHT; + break; + case RANGE_BRACKET: + rangeStart = unicodeBuffer[0]; + state = RANGE_START; + break; + case RANGE_SEPARATOR: + for (unicode_t u = rangeStart; u <= unicodeBuffer[0]; ++u) + add(u); + state = RANGE_END; + break; + default:; + } + } else + goto FAIL; + unicodeBuffer.clear(); + buffer.clear(); + break; + case '"': // string of UTF-8 characters + if (state != CLEAR || disableCharLiterals) + goto FAIL; + if (!readString(buffer, f, '"')) + goto FAIL; + utf8Decode(unicodeBuffer, buffer.c_str()); + for (unicode_t cp : unicodeBuffer) + add(cp); + unicodeBuffer.clear(); + buffer.clear(); + state = TIGHT; + break; + case '[': // character range start + if (state != CLEAR) + goto FAIL; + state = RANGE_BRACKET; + break; + case ']': // character range end + if (state == RANGE_END) + state = TIGHT; + else + goto FAIL; + break; + case '@': // annotation + if (state != CLEAR) + goto FAIL; + c = readWord(buffer, f); + if (buffer == "include") { + while (c == ' ' || c == '\t' || c == '\n' || c == '\r') + c = fgetc(f); + if (c != '"') + goto FAIL; + buffer.clear(); + if (!readString(buffer, f, '"')) + goto FAIL; + load(combinePath(filename, buffer.c_str()).c_str()); + state = TIGHT; + } else + goto FAIL; + buffer.clear(); + break; + case ',': case ';': // separator + if (!(state == CLEAR || state == TIGHT)) { + if (state == RANGE_START) + state = RANGE_SEPARATOR; + else + goto FAIL; + } // else treat as whitespace + case ' ': case '\n': case '\r': case '\t': // whitespace + if (state == TIGHT) + state = CLEAR; + break; + case 0xef: // UTF-8 byte order mark + if (start) { + if (!(fgetc(f) == 0xbb && fgetc(f) == 0xbf)) + goto FAIL; + break; + } + default: // unexpected character + goto FAIL; + } + c = fgetc(f); + } + + fclose(f); + return state == CLEAR || state == TIGHT; + + FAIL: + fclose(f); + return false; + } + + return false; +} + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/csv-export.cpp b/libs/msdf-atlas-gen/include/msdf-atlas-gen/csv-export.cpp new file mode 100644 index 0000000..a85f9bc --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/csv-export.cpp @@ -0,0 +1,45 @@ + +#include "csv-export.h" + +#include +#include "GlyphGeometry.h" + +namespace msdf_atlas { + +bool exportCSV(const FontGeometry *fonts, int fontCount, int atlasWidth, int atlasHeight, YDirection yDirection, const char *filename) { + FILE *f = fopen(filename, "w"); + if (!f) + return false; + + for (int i = 0; i < fontCount; ++i) { + for (const GlyphGeometry &glyph : fonts[i].getGlyphs()) { + double l, b, r, t; + if (fontCount > 1) + fprintf(f, "%d,", i); + fprintf(f, "%d,%.17g,", glyph.getIdentifier(fonts[i].getPreferredIdentifierType()), glyph.getAdvance()); + glyph.getQuadPlaneBounds(l, b, r, t); + switch (yDirection) { + case YDirection::BOTTOM_UP: + fprintf(f, "%.17g,%.17g,%.17g,%.17g,", l, b, r, t); + break; + case YDirection::TOP_DOWN: + fprintf(f, "%.17g,%.17g,%.17g,%.17g,", l, -t, r, -b); + break; + } + glyph.getQuadAtlasBounds(l, b, r, t); + switch (yDirection) { + case YDirection::BOTTOM_UP: + fprintf(f, "%.17g,%.17g,%.17g,%.17g\n", l, b, r, t); + break; + case YDirection::TOP_DOWN: + fprintf(f, "%.17g,%.17g,%.17g,%.17g\n", l, atlasHeight-t, r, atlasHeight-b); + break; + } + } + } + + fclose(f); + return true; +} + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/csv-export.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/csv-export.h new file mode 100644 index 0000000..56d5c4c --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/csv-export.h @@ -0,0 +1,14 @@ + +#pragma once + +#include "FontGeometry.h" + +namespace msdf_atlas { + +/** + * Writes the positioning data and atlas layout of the glyphs into a CSV file + * The columns are: font variant index (if fontCount > 1), glyph identifier (index or Unicode), horizontal advance, plane bounds (l, b, r, t), atlas bounds (l, b, r, t) + */ +bool exportCSV(const FontGeometry *fonts, int fontCount, int atlasWidth, int atlasHeight, YDirection yDirection, const char *filename); + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/glyph-generators.cpp b/libs/msdf-atlas-gen/include/msdf-atlas-gen/glyph-generators.cpp new file mode 100644 index 0000000..af4a325 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/glyph-generators.cpp @@ -0,0 +1,52 @@ + +#include "glyph-generators.h" + +namespace msdf_atlas { + +void scanlineGenerator(const msdfgen::BitmapRef &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 &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) { + msdfgen::generateSDF(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), attribs.config); + if (attribs.scanlinePass) + msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxProjection(), MSDF_ATLAS_GLYPH_FILL_RULE); +} + +void psdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) { + msdfgen::generatePseudoSDF(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), attribs.config); + if (attribs.scanlinePass) + msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxProjection(), MSDF_ATLAS_GLYPH_FILL_RULE); +} + +void msdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) { + msdfgen::MSDFGeneratorConfig config = attribs.config; + if (attribs.scanlinePass) + config.errorCorrection.mode = msdfgen::ErrorCorrectionConfig::DISABLED; + msdfgen::generateMSDF(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), config); + if (attribs.scanlinePass) { + msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxProjection(), MSDF_ATLAS_GLYPH_FILL_RULE); + if (attribs.config.errorCorrection.mode != msdfgen::ErrorCorrectionConfig::DISABLED) { + config.errorCorrection.mode = attribs.config.errorCorrection.mode; + config.errorCorrection.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE; + msdfgen::msdfErrorCorrection(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), config); + } + } +} + +void mtsdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs) { + msdfgen::MSDFGeneratorConfig config = attribs.config; + if (attribs.scanlinePass) + config.errorCorrection.mode = msdfgen::ErrorCorrectionConfig::DISABLED; + msdfgen::generateMTSDF(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), config); + if (attribs.scanlinePass) { + msdfgen::distanceSignCorrection(output, glyph.getShape(), glyph.getBoxProjection(), MSDF_ATLAS_GLYPH_FILL_RULE); + if (attribs.config.errorCorrection.mode != msdfgen::ErrorCorrectionConfig::DISABLED) { + config.errorCorrection.mode = attribs.config.errorCorrection.mode; + config.errorCorrection.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE; + msdfgen::msdfErrorCorrection(output, glyph.getShape(), glyph.getBoxProjection(), glyph.getBoxRange(), config); + } + } +} + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/glyph-generators.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/glyph-generators.h new file mode 100644 index 0000000..1df7c24 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/glyph-generators.h @@ -0,0 +1,25 @@ + +#pragma once + +#include +#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 &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs); +/// Generates a true signed distance field of the glyph +void sdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs); +/// Generates a signed pseudo-distance field of the glyph +void psdfGenerator(const msdfgen::BitmapRef &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs); +/// Generates a multi-channel signed distance field of the glyph +void msdfGenerator(const msdfgen::BitmapRef &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 &output, const GlyphGeometry &glyph, const GeneratorAttributes &attribs); + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/image-encode.cpp b/libs/msdf-atlas-gen/include/msdf-atlas-gen/image-encode.cpp new file mode 100644 index 0000000..948a7be --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/image-encode.cpp @@ -0,0 +1,157 @@ + +#include "image-encode.h" + +#include + +#ifdef MSDFGEN_USE_LIBPNG + +#include + +namespace msdf_atlas { + +class PngGuard { + png_structp png; + png_infop info; + +public: + inline PngGuard(png_structp png, png_infop info) : png(png), info(info) { } + inline ~PngGuard() { + png_destroy_write_struct(&png, &info); + } + +}; + +static void pngIgnoreError(png_structp, png_const_charp) { } + +static void pngWrite(png_structp png, png_bytep data, png_size_t length) { + std::vector &output = *reinterpret_cast *>(png_get_io_ptr(png)); + output.insert(output.end(), data, data+length); +} + +static void pngFlush(png_structp) { } + +static bool pngEncode(std::vector &output, const byte *pixels, int width, int height, int channels, int colorType) { + if (!(pixels && width && height)) + return false; + png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, &pngIgnoreError, &pngIgnoreError); + if (!png) + return false; + png_infop info = png_create_info_struct(png); + PngGuard guard(png, info); + if (!info) + return false; + std::vector rows(height); + for (int y = 0; y < height; ++y) + rows[y] = pixels+channels*width*(height-y-1); + if (setjmp(png_jmpbuf(png))) + return false; + png_set_write_fn(png, &output, &pngWrite, &pngFlush); + png_set_IHDR(png, info, width, height, 8, colorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_set_compression_level(png, 9); + png_set_rows(png, info, const_cast(&rows[0])); + png_write_png(png, info, PNG_TRANSFORM_IDENTITY, NULL); + return true; +} + +static bool pngEncode(std::vector &output, const float *pixels, int width, int height, int channels, int colorType) { + if (!(pixels && width && height)) + return false; + int subpixels = channels*width*height; + std::vector bytePixels(subpixels); + for (int i = 0; i < subpixels; ++i) + bytePixels[i] = msdfgen::pixelFloatToByte(pixels[i]); + return pngEncode(output, bytePixels.data(), width, height, channels, colorType); +} + +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap) { + return pngEncode(output, bitmap.pixels, bitmap.width, bitmap.height, 1, PNG_COLOR_TYPE_GRAY); +} + +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap) { + return pngEncode(output, bitmap.pixels, bitmap.width, bitmap.height, 3, PNG_COLOR_TYPE_RGB); +} + +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap) { + return pngEncode(output, bitmap.pixels, bitmap.width, bitmap.height, 4, PNG_COLOR_TYPE_RGB_ALPHA); +} + +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap) { + return pngEncode(output, bitmap.pixels, bitmap.width, bitmap.height, 1, PNG_COLOR_TYPE_GRAY); +} + +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap) { + return pngEncode(output, bitmap.pixels, bitmap.width, bitmap.height, 3, PNG_COLOR_TYPE_RGB); +} + +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap) { + return pngEncode(output, bitmap.pixels, bitmap.width, bitmap.height, 4, PNG_COLOR_TYPE_RGB_ALPHA); +} + +} + +#endif + +#ifdef MSDFGEN_USE_LODEPNG + +#include + +namespace msdf_atlas { + +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap) { + std::vector 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 &output, const msdfgen::BitmapConstRef &bitmap) { + std::vector 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 &output, const msdfgen::BitmapConstRef &bitmap) { + std::vector 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 &output, const msdfgen::BitmapConstRef &bitmap) { + std::vector pixels(bitmap.width*bitmap.height); + std::vector::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 &output, const msdfgen::BitmapConstRef &bitmap) { + std::vector pixels(3*bitmap.width*bitmap.height); + std::vector::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 &output, const msdfgen::BitmapConstRef &bitmap) { + std::vector pixels(4*bitmap.width*bitmap.height); + std::vector::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); +} + +} + +#endif diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/image-encode.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/image-encode.h new file mode 100644 index 0000000..59a8fc5 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/image-encode.h @@ -0,0 +1,20 @@ + +#pragma once + +#include +#include +#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 &output, const msdfgen::BitmapConstRef &bitmap); +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap); +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap); +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap); +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap); +bool encodePng(std::vector &output, const msdfgen::BitmapConstRef &bitmap); + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/image-save.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/image-save.h new file mode 100644 index 0000000..0a0b37e --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/image-save.h @@ -0,0 +1,15 @@ + +#pragma once + +#include +#include "types.h" + +namespace msdf_atlas { + +/// Saves the bitmap as an image file with the specified format +template +bool saveImage(const msdfgen::BitmapConstRef &bitmap, ImageFormat format, const char *filename, YDirection outputYDirection = YDirection::BOTTOM_UP); + +} + +#include "image-save.hpp" diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/image-save.hpp b/libs/msdf-atlas-gen/include/msdf-atlas-gen/image-save.hpp new file mode 100644 index 0000000..c5d0853 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/image-save.hpp @@ -0,0 +1,172 @@ + +#include "image-save.h" + +#include +#include + +namespace msdf_atlas { + +template +bool saveImageBinary(const msdfgen::BitmapConstRef &bitmap, const char *filename, YDirection outputYDirection); +template +bool saveImageBinaryLE(const msdfgen::BitmapConstRef &bitmap, const char *filename, YDirection outputYDirection); +template +bool saveImageBinaryBE(const msdfgen::BitmapConstRef &bitmap, const char *filename, YDirection outputYDirection); + +template +bool saveImageText(const msdfgen::BitmapConstRef &bitmap, const char *filename, YDirection outputYDirection); +template +bool saveImageText(const msdfgen::BitmapConstRef &bitmap, const char *filename, YDirection outputYDirection); + +template +bool saveImage(const msdfgen::BitmapConstRef &bitmap, ImageFormat format, const char *filename, YDirection outputYDirection) { + 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, outputYDirection); + case ImageFormat::TEXT_FLOAT: + return false; + case ImageFormat::BINARY: + return saveImageBinary(bitmap, filename, outputYDirection); + case ImageFormat::BINARY_FLOAT: + case ImageFormat::BINARY_FLOAT_BE: + return false; + default:; + } + return false; +} + +template +bool saveImage(const msdfgen::BitmapConstRef &bitmap, ImageFormat format, const char *filename, YDirection outputYDirection) { + 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, outputYDirection); + case ImageFormat::BINARY: + return false; + case ImageFormat::BINARY_FLOAT: + return saveImageBinaryLE(bitmap, filename, outputYDirection); + case ImageFormat::BINARY_FLOAT_BE: + return saveImageBinaryBE(bitmap, filename, outputYDirection); + default:; + } + return false; +} + +template +bool saveImageBinary(const msdfgen::BitmapConstRef &bitmap, const char *filename, YDirection outputYDirection) { + bool success = false; + if (FILE *f = fopen(filename, "wb")) { + int written = 0; + switch (outputYDirection) { + case YDirection::BOTTOM_UP: + written = fwrite(bitmap.pixels, 1, N*bitmap.width*bitmap.height, f); + break; + case YDirection::TOP_DOWN: + for (int y = bitmap.height-1; y >= 0; --y) + written += fwrite(bitmap.pixels+N*bitmap.width*y, 1, N*bitmap.width, f); + break; + } + success = written == N*bitmap.width*bitmap.height; + fclose(f); + } + return success; +} + +template +bool + #ifdef __BIG_ENDIAN__ + saveImageBinaryBE + #else + saveImageBinaryLE + #endif + (const msdfgen::BitmapConstRef &bitmap, const char *filename, YDirection outputYDirection) { + bool success = false; + if (FILE *f = fopen(filename, "wb")) { + int written = 0; + switch (outputYDirection) { + case YDirection::BOTTOM_UP: + written = fwrite(bitmap.pixels, sizeof(float), N*bitmap.width*bitmap.height, f); + break; + case YDirection::TOP_DOWN: + for (int y = bitmap.height-1; y >= 0; --y) + written += fwrite(bitmap.pixels+N*bitmap.width*y, sizeof(float), N*bitmap.width, f); + break; + } + success = written == N*bitmap.width*bitmap.height; + fclose(f); + } + return success; +} + +template +bool + #ifdef __BIG_ENDIAN__ + saveImageBinaryLE + #else + saveImageBinaryBE + #endif + (const msdfgen::BitmapConstRef &bitmap, const char *filename, YDirection outputYDirection) { + bool success = false; + if (FILE *f = fopen(filename, "wb")) { + int written = 0; + for (int y = 0; y < bitmap.height; ++y) { + const float *p = bitmap.pixels+N*bitmap.width*(outputYDirection == YDirection::TOP_DOWN ? bitmap.height-y-1 : y); + for (int x = 0; x < bitmap.width; ++x) { + const unsigned char *b = reinterpret_cast(p++); + for (int i = sizeof(float)-1; i >= 0; --i) + written += fwrite(b+i, 1, 1, f); + } + } + success = written == sizeof(float)*N*bitmap.width*bitmap.height; + fclose(f); + } + return success; +} + + +template +bool saveImageText(const msdfgen::BitmapConstRef &bitmap, const char *filename, YDirection outputYDirection) { + bool success = false; + if (FILE *f = fopen(filename, "wb")) { + success = true; + for (int y = 0; y < bitmap.height; ++y) { + const byte *p = bitmap.pixels+N*bitmap.width*(outputYDirection == YDirection::TOP_DOWN ? bitmap.height-y-1 : y); + for (int x = 0; x < N*bitmap.width; ++x) + success &= fprintf(f, x ? " %02X" : "%02X", (unsigned) *p++) > 0; + success &= fprintf(f, "\n") > 0; + } + fclose(f); + } + return success; +} + +template +bool saveImageText(const msdfgen::BitmapConstRef &bitmap, const char *filename, YDirection outputYDirection) { + bool success = false; + if (FILE *f = fopen(filename, "wb")) { + success = true; + for (int y = 0; y < bitmap.height; ++y) { + const float *p = bitmap.pixels+N*bitmap.width*(outputYDirection == YDirection::TOP_DOWN ? bitmap.height-y-1 : y); + for (int x = 0; x < N*bitmap.width; ++x) + success &= fprintf(f, x ? " %g" : "%g", *p++) > 0; + success &= fprintf(f, "\n") > 0; + } + fclose(f); + } + return success; +} + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/json-export.cpp b/libs/msdf-atlas-gen/include/msdf-atlas-gen/json-export.cpp new file mode 100644 index 0000000..2bf24dc --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/json-export.cpp @@ -0,0 +1,186 @@ + +#include "json-export.h" + +#include +#include "GlyphGeometry.h" + +namespace msdf_atlas { + +static std::string escapeJsonString(const char *str) { + char uval[7] = "\\u0000"; + std::string outStr; + while (*str) { + switch (*str) { + case '\\': + outStr += "\\\\"; + break; + case '"': + outStr += "\\\""; + break; + case '\n': + outStr += "\\n"; + break; + case '\r': + outStr += "\\r"; + break; + case '\t': + outStr += "\\t"; + break; + case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: /* \\t */ /* \\n */ case 0x0b: case 0x0c: /* \\r */ case 0x0e: case 0x0f: + case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: + uval[4] = '0'+(*str >= 0x10); + uval[5] = "0123456789abcdef"[*str&0x0f]; + outStr += uval; + break; + default: + outStr.push_back(*str); + } + ++str; + } + return outStr; +} + +static const char * imageTypeString(ImageType type) { + switch (type) { + case ImageType::HARD_MASK: + return "hardmask"; + 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(const FontGeometry *fonts, int fontCount, double fontSize, double pxRange, int atlasWidth, int atlasHeight, ImageType imageType, YDirection yDirection, const char *filename, bool kerning) { + 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); + fprintf(f, "\"yOrigin\":\"%s\"", yDirection == YDirection::TOP_DOWN ? "top" : "bottom"); + } fputs("},", f); + + if (fontCount > 1) + fputs("\"variants\":[", f); + for (int i = 0; i < fontCount; ++i) { + const FontGeometry &font = fonts[i]; + if (fontCount > 1) + fputs(i == 0 ? "{" : ",{", f); + + // Font name + const char *name = font.getName(); + if (name) + fprintf(f, "\"name\":\"%s\",", escapeJsonString(name).c_str()); + + // Font metrics + fputs("\"metrics\":{", f); { + double yFactor = yDirection == YDirection::TOP_DOWN ? -1 : 1; + const msdfgen::FontMetrics &metrics = font.getMetrics(); + fprintf(f, "\"emSize\":%.17g,", metrics.emSize); + fprintf(f, "\"lineHeight\":%.17g,", metrics.lineHeight); + fprintf(f, "\"ascender\":%.17g,", yFactor*metrics.ascenderY); + fprintf(f, "\"descender\":%.17g,", yFactor*metrics.descenderY); + fprintf(f, "\"underlineY\":%.17g,", yFactor*metrics.underlineY); + fprintf(f, "\"underlineThickness\":%.17g", metrics.underlineThickness); + } fputs("},", f); + + // Glyph mapping + fputs("\"glyphs\":[", f); + bool firstGlyph = true; + for (const GlyphGeometry &glyph : font.getGlyphs()) { + fputs(firstGlyph ? "{" : ",{", f); + switch (font.getPreferredIdentifierType()) { + case GlyphIdentifierType::GLYPH_INDEX: + fprintf(f, "\"index\":%d,", glyph.getIndex()); + break; + case GlyphIdentifierType::UNICODE_CODEPOINT: + fprintf(f, "\"unicode\":%u,", glyph.getCodepoint()); + break; + } + fprintf(f, "\"advance\":%.17g", glyph.getAdvance()); + double l, b, r, t; + glyph.getQuadPlaneBounds(l, b, r, t); + if (l || b || r || t) { + switch (yDirection) { + case YDirection::BOTTOM_UP: + fprintf(f, ",\"planeBounds\":{\"left\":%.17g,\"bottom\":%.17g,\"right\":%.17g,\"top\":%.17g}", l, b, r, t); + break; + case YDirection::TOP_DOWN: + fprintf(f, ",\"planeBounds\":{\"left\":%.17g,\"top\":%.17g,\"right\":%.17g,\"bottom\":%.17g}", l, -t, r, -b); + break; + } + } + glyph.getQuadAtlasBounds(l, b, r, t); + if (l || b || r || t) { + switch (yDirection) { + case YDirection::BOTTOM_UP: + fprintf(f, ",\"atlasBounds\":{\"left\":%.17g,\"bottom\":%.17g,\"right\":%.17g,\"top\":%.17g}", l, b, r, t); + break; + case YDirection::TOP_DOWN: + fprintf(f, ",\"atlasBounds\":{\"left\":%.17g,\"top\":%.17g,\"right\":%.17g,\"bottom\":%.17g}", l, atlasHeight-t, r, atlasHeight-b); + break; + } + } + fputs("}", f); + firstGlyph = false; + } fputs("]", f); + + // Kerning pairs + if (kerning) { + fputs(",\"kerning\":[", f); + bool firstPair = true; + switch (font.getPreferredIdentifierType()) { + case GlyphIdentifierType::GLYPH_INDEX: + for (const std::pair, double> &kernPair : font.getKerning()) { + fputs(firstPair ? "{" : ",{", f); + fprintf(f, "\"index1\":%d,", kernPair.first.first); + fprintf(f, "\"index2\":%d,", kernPair.first.second); + fprintf(f, "\"advance\":%.17g", kernPair.second); + fputs("}", f); + firstPair = false; + } + break; + case GlyphIdentifierType::UNICODE_CODEPOINT: + for (const std::pair, double> &kernPair : font.getKerning()) { + const GlyphGeometry *glyph1 = font.getGlyph(msdfgen::GlyphIndex(kernPair.first.first)); + const GlyphGeometry *glyph2 = font.getGlyph(msdfgen::GlyphIndex(kernPair.first.second)); + if (glyph1 && glyph2 && glyph1->getCodepoint() && glyph2->getCodepoint()) { + fputs(firstPair ? "{" : ",{", f); + fprintf(f, "\"unicode1\":%u,", glyph1->getCodepoint()); + fprintf(f, "\"unicode2\":%u,", glyph2->getCodepoint()); + fprintf(f, "\"advance\":%.17g", kernPair.second); + fputs("}", f); + firstPair = false; + } + } + break; + } fputs("]", f); + } + + if (fontCount > 1) + fputs("}", f); + } + if (fontCount > 1) + fputs("]", f); + + fputs("}\n", f); + fclose(f); + return true; +} + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/json-export.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/json-export.h new file mode 100644 index 0000000..fe7bc04 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/json-export.h @@ -0,0 +1,14 @@ + +#pragma once + +#include +#include +#include "types.h" +#include "FontGeometry.h" + +namespace msdf_atlas { + +/// Writes the font and glyph metrics and atlas layout data into a comprehensive JSON file +bool exportJSON(const FontGeometry *fonts, int fontCount, double fontSize, double pxRange, int atlasWidth, int atlasHeight, ImageType imageType, YDirection yDirection, const char *filename, bool kerning); + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/main.cpp b/libs/msdf-atlas-gen/include/msdf-atlas-gen/main.cpp new file mode 100644 index 0000000..7481546 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/main.cpp @@ -0,0 +1,1064 @@ + +/* +* MULTI-CHANNEL SIGNED DISTANCE FIELD ATLAS GENERATOR - standalone console program +* -------------------------------------------------------------------------------- +* A utility by Viktor Chlumsky, (c) 2020 - 2023 +*/ + +#ifdef MSDF_ATLAS_STANDALONE + +#define _USE_MATH_DEFINES +#include +#include +#include +#include +#include +#include +#include + +#include "msdf-atlas-gen.h" + +using namespace msdf_atlas; + +#define DEFAULT_ANGLE_THRESHOLD 3.0 +#define DEFAULT_MITER_LIMIT 1.0 +#define DEFAULT_PIXEL_RANGE 2.0 +#define SDF_ERROR_ESTIMATE_PRECISION 19 +#define GLYPH_FILL_RULE msdfgen::FILL_NONZERO +#define LCG_MULTIPLIER 6364136223846793005ull +#define LCG_INCREMENT 1442695040888963407ull + +#define STRINGIZE_(x) #x +#define STRINGIZE(x) STRINGIZE_(x) +#define MSDF_ATLAS_VERSION_STRING STRINGIZE(MSDF_ATLAS_VERSION) +#define MSDFGEN_VERSION_STRING STRINGIZE(MSDFGEN_VERSION) +#ifdef MSDF_ATLAS_VERSION_UNDERLINE + #define VERSION_UNDERLINE STRINGIZE(MSDF_ATLAS_VERSION_UNDERLINE) +#else + #define VERSION_UNDERLINE "--------" +#endif + +#ifdef MSDFGEN_USE_SKIA + #define TITLE_SUFFIX " & Skia" + #define EXTRA_UNDERLINE "-------" +#else + #define TITLE_SUFFIX + #define EXTRA_UNDERLINE +#endif + +static const char * const versionText = + "MSDF-Atlas-Gen v" MSDF_ATLAS_VERSION_STRING "\n" + " with MSDFgen v" MSDFGEN_VERSION_STRING TITLE_SUFFIX "\n" + "(c) 2020 - " STRINGIZE(MSDF_ATLAS_COPYRIGHT_YEAR) " Viktor Chlumsky"; + +static const char * const helpText = R"( +MSDF Atlas Generator by Viktor Chlumsky v)" MSDF_ATLAS_VERSION_STRING R"( (with MSDFgen v)" MSDFGEN_VERSION_STRING TITLE_SUFFIX R"() +----------------------------------------------------------------)" VERSION_UNDERLINE EXTRA_UNDERLINE R"( + +INPUT SPECIFICATION + -font + Specifies the input TrueType / OpenType font file. A font specification is required. + -varfont + Specifies an input variable font file and configures its variables. + -charset + Specifies the input character set. Refer to the documentation for format of charset specification. Defaults to ASCII. + -glyphset + Specifies the set of input glyphs as glyph indices within the font file. + -fontscale + Specifies the scale to be applied to the glyph geometry of the font. + -fontname + Specifies a name for the font that will be propagated into the output files as metadata. + -and + Separates multiple inputs to be combined into a single atlas. + +ATLAS CONFIGURATION + -type + Selects the type of atlas to be generated. + -format + Selects the format for the atlas image output. Some image formats may be incompatible with embedded output formats. + -dimensions + 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 + -yorigin + Determines whether the Y-axis is oriented upwards (bottom origin, default) or downwards (top origin). + +OUTPUT SPECIFICATION - one or more can be specified + -imageout + Saves the atlas as an image file with the specified format. Layout data must be stored separately. + -json + Writes the atlas's layout data, as well as other metrics into a structured JSON file. + -csv + Writes the layout data of the glyphs into a simple CSV file.)" +#ifndef MSDF_ATLAS_NO_ARTERY_FONT +R"( + -arfont + Stores the atlas and its layout data as an Artery Font file. Supported formats: png, bin, binfloat.)" +#endif +R"( + -shadronpreview + Generates a Shadron script that uses the generated atlas to draw a sample text as a preview. + +GLYPH CONFIGURATION + -size + Specifies the size of the glyphs in the atlas bitmap in pixels per EM. + -minsize + Specifies the minimum size. The largest possible size that fits the same atlas dimensions will be used. + -emrange + Specifies the SDF distance range in EM's. + -pxrange + Specifies the SDF distance range in output pixels. The default value is 2. + -nokerning + Disables inclusion of kerning pair table in output files. + +DISTANCE FIELD GENERATOR SETTINGS + -angle + Specifies the minimum angle between adjacent edges to be considered a corner. Append D for degrees. (msdf / mtsdf only) + -coloringstrategy + Selects the strategy of the edge coloring heuristic. + -errorcorrection + Changes the MSDF/MTSDF error correction mode. Use -errorcorrection help for a list of valid modes. + -errordeviationratio + Sets the minimum ratio between the actual and maximum expected distance delta to be considered an error. + -errorimproveratio + Sets the minimum ratio between the pre-correction distance error and the post-correction distance error. + -miterlimit + Sets the miter limit that limits the extension of each glyph's bounding box due to very sharp corners. (psdf / msdf / mtsdf only))" +#ifdef MSDFGEN_USE_SKIA +R"( + -overlap + Switches to distance field generator with support for overlapping contours. + -nopreprocess + Disables path preprocessing which resolves self-intersections and overlapping contours. + -scanline + Performs an additional scanline pass to fix the signs of the distances.)" +#else +R"( + -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.)" +#endif +R"( + -seed + Sets the initial seed for the edge coloring heuristic. + -threads + Sets the number of threads for the parallel computation. (0 = auto) +)"; + +static const char *errorCorrectionHelpText = R"( +ERROR CORRECTION MODES + auto-fast + Detects inversion artifacts and distance errors that do not affect edges by range testing. + auto-full + Detects inversion artifacts and distance errors that do not affect edges by exact distance evaluation. + auto-mixed (default) + Detects inversions by distance evaluation and distance errors that do not affect edges by range testing. + disabled + Disables error correction. + distance-fast + Detects distance errors by range testing. Does not care if edges and corners are affected. + distance-full + Detects distance errors by exact distance evaluation. Does not care if edges and corners are affected, slow. + edge-fast + Detects inversion artifacts only by range testing. + edge-full + Detects inversion artifacts only by exact distance evaluation. + help + Displays this help. +)"; + +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 msdfgen::FontHandle * loadVarFont(msdfgen::FreetypeHandle *library, const char *filename) { + std::string buffer; + while (*filename && *filename != '?') + buffer.push_back(*filename++); + msdfgen::FontHandle *font = msdfgen::loadFont(library, buffer.c_str()); + if (font && *filename++ == '?') { + do { + buffer.clear(); + while (*filename && *filename != '=') + buffer.push_back(*filename++); + if (*filename == '=') { + double value = 0; + int skip = 0; + if (sscanf(++filename, "%lf%n", &value, &skip) == 1) { + msdfgen::setFontVariationAxis(library, font, buffer.c_str(), value); + filename += skip; + } + } + } while (*filename++ == '&'); + } + return font; +} + +struct FontInput { + const char *fontFilename; + bool variableFont; + GlyphIdentifierType glyphIdentifierType; + const char *charsetFilename; + double fontScale; + const char *fontName; +}; + +struct Configuration { + ImageType imageType; + ImageFormat imageFormat; + YDirection yDirection; + int width, height; + double emSize; + double pxRange; + double angleThreshold; + double miterLimit; + void (*edgeColoring)(msdfgen::Shape &, double, unsigned long long); + bool expensiveColoring; + unsigned long long coloringSeed; + GeneratorAttributes generatorAttributes; + bool preprocessGeometry; + bool kerning; + int threadCount; + const char *arteryFontFilename; + const char *imageFilename; + const char *jsonFilename; + const char *csvFilename; + const char *shadronPreviewFilename; + const char *shadronPreviewText; +}; + +template GEN_FN> +static bool makeAtlas(const std::vector &glyphs, const std::vector &fonts, const Configuration &config) { + ImmediateAtlasGenerator > generator(config.width, config.height); + generator.setAttributes(config.generatorAttributes); + generator.setThreadCount(config.threadCount); + generator.generate(glyphs.data(), glyphs.size()); + msdfgen::BitmapConstRef bitmap = (msdfgen::BitmapConstRef) generator.atlasStorage(); + + bool success = true; + + if (config.imageFilename) { + if (saveImage(bitmap, config.imageFormat, config.imageFilename, config.yDirection)) + puts("Atlas image file saved."); + else { + success = false; + puts("Failed to save the atlas as an image file."); + } + } + +#ifndef MSDF_ATLAS_NO_ARTERY_FONT + if (config.arteryFontFilename) { + ArteryFontExportProperties arfontProps; + arfontProps.fontSize = config.emSize; + arfontProps.pxRange = config.pxRange; + arfontProps.imageType = config.imageType; + arfontProps.imageFormat = config.imageFormat; + arfontProps.yDirection = config.yDirection; + if (exportArteryFont(fonts.data(), fonts.size(), bitmap, config.arteryFontFilename, arfontProps)) + puts("Artery Font file generated."); + else { + success = false; + puts("Failed to generate Artery Font file."); + } + } +#endif + + return success; +} + +int main(int argc, const char * const *argv) { + #define ABORT(msg) { puts(msg); return 1; } + + int result = 0; + std::vector fontInputs; + FontInput fontInput = { }; + Configuration config = { }; + fontInput.glyphIdentifierType = GlyphIdentifierType::UNICODE_CODEPOINT; + fontInput.fontScale = -1; + config.imageType = ImageType::MSDF; + config.imageFormat = ImageFormat::UNSPECIFIED; + config.yDirection = YDirection::BOTTOM_UP; + config.edgeColoring = msdfgen::edgeColoringInkTrap; + config.kerning = true; + const char *imageFormatName = nullptr; + int fixedWidth = -1, fixedHeight = -1; + config.preprocessGeometry = ( + #ifdef MSDFGEN_USE_SKIA + true + #else + false + #endif + ); + config.generatorAttributes.config.overlapSupport = !config.preprocessGeometry; + config.generatorAttributes.scanlinePass = !config.preprocessGeometry; + 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 = 0; + + // Parse command line + int argPos = 1; + bool suggestHelp = false; + bool explicitErrorCorrectionMode = false; + while (argPos < argc) { + const char *arg = argv[argPos]; + #define ARG_CASE(s, p) if (!strcmp(arg, s) && argPos+(p) < argc) + + // Accept arguments prefixed with -- instead of - + if (arg[0] == '-' && arg[1] == '-') + ++arg; + + 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) { + fontInput.fontFilename = argv[++argPos]; + fontInput.variableFont = false; + ++argPos; + continue; + } + ARG_CASE("-varfont", 1) { + fontInput.fontFilename = argv[++argPos]; + fontInput.variableFont = true; + ++argPos; + continue; + } + ARG_CASE("-charset", 1) { + fontInput.charsetFilename = argv[++argPos]; + fontInput.glyphIdentifierType = GlyphIdentifierType::UNICODE_CODEPOINT; + ++argPos; + continue; + } + ARG_CASE("-glyphset", 1) { + fontInput.charsetFilename = argv[++argPos]; + fontInput.glyphIdentifierType = GlyphIdentifierType::GLYPH_INDEX; + ++argPos; + continue; + } + ARG_CASE("-fontscale", 1) { + double fs; + if (!(parseDouble(fs, argv[++argPos]) && fs > 0)) + ABORT("Invalid font scale argument. Use -fontscale with a positive real number."); + fontInput.fontScale = fs; + ++argPos; + continue; + } + ARG_CASE("-fontname", 1) { + fontInput.fontName = argv[++argPos]; + ++argPos; + continue; + } + ARG_CASE("-and", 0) { + if (!fontInput.fontFilename && !fontInput.charsetFilename && fontInput.fontScale < 0) + ABORT("No font, character set, or font scale specified before -and separator."); + if (!fontInputs.empty() && !memcmp(&fontInputs.back(), &fontInput, sizeof(FontInput))) + ABORT("No changes between subsequent inputs. A different font, character set, or font scale must be set inbetween -and separators."); + fontInputs.push_back(fontInput); + fontInput.fontName = nullptr; + ++argPos; + continue; + } + #ifndef MSDF_ATLAS_NO_ARTERY_FONT + ARG_CASE("-arfont", 1) { + config.arteryFontFilename = argv[++argPos]; + ++argPos; + continue; + } + #endif + 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 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("-yorigin", 1) { + arg = argv[++argPos]; + if (!strcmp(arg, "bottom")) + config.yDirection = YDirection::BOTTOM_UP; + else if (!strcmp(arg, "top")) + config.yDirection = YDirection::TOP_DOWN; + else + ABORT("Invalid Y-axis origin. Use bottom or top."); + ++argPos; + continue; + } + ARG_CASE("-size", 1) { + double s; + if (!(parseDouble(s, argv[++argPos]) && s > 0)) + ABORT("Invalid EM size argument. Use -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 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 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 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 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) { + msdfgen::ErrorCorrectionConfig &ec = config.generatorAttributes.config.errorCorrection; + if (!strcmp(argv[argPos+1], "disabled") || !strcmp(argv[argPos+1], "0") || !strcmp(argv[argPos+1], "none")) { + ec.mode = msdfgen::ErrorCorrectionConfig::DISABLED; + ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE; + } else if (!strcmp(argv[argPos+1], "default") || !strcmp(argv[argPos+1], "auto") || !strcmp(argv[argPos+1], "auto-mixed") || !strcmp(argv[argPos+1], "mixed")) { + ec.mode = msdfgen::ErrorCorrectionConfig::EDGE_PRIORITY; + ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::CHECK_DISTANCE_AT_EDGE; + } else if (!strcmp(argv[argPos+1], "auto-fast") || !strcmp(argv[argPos+1], "fast")) { + ec.mode = msdfgen::ErrorCorrectionConfig::EDGE_PRIORITY; + ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE; + } else if (!strcmp(argv[argPos+1], "auto-full") || !strcmp(argv[argPos+1], "full")) { + ec.mode = msdfgen::ErrorCorrectionConfig::EDGE_PRIORITY; + ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE; + } else if (!strcmp(argv[argPos+1], "distance") || !strcmp(argv[argPos+1], "distance-fast") || !strcmp(argv[argPos+1], "indiscriminate") || !strcmp(argv[argPos+1], "indiscriminate-fast")) { + ec.mode = msdfgen::ErrorCorrectionConfig::INDISCRIMINATE; + ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE; + } else if (!strcmp(argv[argPos+1], "distance-full") || !strcmp(argv[argPos+1], "indiscriminate-full")) { + ec.mode = msdfgen::ErrorCorrectionConfig::INDISCRIMINATE; + ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE; + } else if (!strcmp(argv[argPos+1], "edge-fast")) { + ec.mode = msdfgen::ErrorCorrectionConfig::EDGE_ONLY; + ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE; + } else if (!strcmp(argv[argPos+1], "edge") || !strcmp(argv[argPos+1], "edge-full")) { + ec.mode = msdfgen::ErrorCorrectionConfig::EDGE_ONLY; + ec.distanceCheckMode = msdfgen::ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE; + } else if (!strcmp(argv[argPos+1], "help")) { + puts(errorCorrectionHelpText); + return 0; + } else + ABORT("Unknown error correction mode. Use -errorcorrection help for more information."); + explicitErrorCorrectionMode = true; + argPos += 2; + continue; + } + ARG_CASE("-errordeviationratio", 1) { + double edr; + if (!(parseDouble(edr, argv[argPos+1]) && edr > 0)) + ABORT("Invalid error deviation ratio. Use -errordeviationratio with a positive real number."); + config.generatorAttributes.config.errorCorrection.minDeviationRatio = edr; + argPos += 2; + continue; + } + ARG_CASE("-errorimproveratio", 1) { + double eir; + if (!(parseDouble(eir, argv[argPos+1]) && eir > 0)) + ABORT("Invalid error improvement ratio. Use -errorimproveratio with a positive real number."); + config.generatorAttributes.config.errorCorrection.minImproveRatio = eir; + argPos += 2; + continue; + } + ARG_CASE("-coloringstrategy", 1) { + if (!strcmp(argv[argPos+1], "simple")) config.edgeColoring = msdfgen::edgeColoringSimple, config.expensiveColoring = false; + else if (!strcmp(argv[argPos+1], "inktrap")) config.edgeColoring = msdfgen::edgeColoringInkTrap, config.expensiveColoring = false; + else if (!strcmp(argv[argPos+1], "distance")) config.edgeColoring = msdfgen::edgeColoringByDistance, config.expensiveColoring = true; + else + puts("Unknown coloring strategy specified."); + argPos += 2; + continue; + } + ARG_CASE("-miterlimit", 1) { + double m; + if (!(parseDouble(m, argv[++argPos]) && m >= 0)) + ABORT("Invalid miter limit argument. Use -miterlimit with a positive real number."); + config.miterLimit = m; + ++argPos; + continue; + } + ARG_CASE("-nokerning", 0) { + config.kerning = false; + ++argPos; + continue; + } + ARG_CASE("-kerning", 0) { + config.kerning = true; + ++argPos; + continue; + } + ARG_CASE("-nopreprocess", 0) { + config.preprocessGeometry = false; + ++argPos; + continue; + } + ARG_CASE("-preprocess", 0) { + config.preprocessGeometry = true; + ++argPos; + continue; + } + ARG_CASE("-nooverlap", 0) { + config.generatorAttributes.config.overlapSupport = false; + ++argPos; + continue; + } + ARG_CASE("-overlap", 0) { + config.generatorAttributes.config.overlapSupport = true; + ++argPos; + continue; + } + ARG_CASE("-noscanline", 0) { + config.generatorAttributes.scanlinePass = false; + ++argPos; + continue; + } + ARG_CASE("-scanline", 0) { + config.generatorAttributes.scanlinePass = true; + ++argPos; + continue; + } + ARG_CASE("-seed", 1) { + if (!parseUnsignedLL(config.coloringSeed, argv[argPos+1])) + ABORT("Invalid seed. Use -seed with N being a non-negative integer."); + argPos += 2; + continue; + } + ARG_CASE("-threads", 1) { + unsigned tc; + if (!parseUnsigned(tc, argv[argPos+1]) || (int) tc < 0) + ABORT("Invalid thread count. Use -threads with N being a non-negative integer."); + config.threadCount = (int) tc; + argPos += 2; + continue; + } + ARG_CASE("-version", 0) { + puts(versionText); + return 0; + } + ARG_CASE("-help", 0) { + puts(helpText); + return 0; + } + printf("Unknown setting or insufficient parameters: %s\n", argv[argPos]); + 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 -charset \n" + "Use -help for more information.\n" + ); + return 0; + } + if (!fontInput.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); + + // Finalize font inputs + const FontInput *nextFontInput = &fontInput; + for (std::vector::reverse_iterator it = fontInputs.rbegin(); it != fontInputs.rend(); ++it) { + if (!it->fontFilename && nextFontInput->fontFilename) + it->fontFilename = nextFontInput->fontFilename; + if (!it->charsetFilename && nextFontInput->charsetFilename) { + it->charsetFilename = nextFontInput->charsetFilename; + it->glyphIdentifierType = nextFontInput->glyphIdentifierType; + } + if (it->fontScale < 0 && nextFontInput->fontScale >= 0) + it->fontScale = nextFontInput->fontScale; + nextFontInput = &*it; + } + if (fontInputs.empty() || memcmp(&fontInputs.back(), &fontInput, sizeof(FontInput))) + fontInputs.push_back(fontInput); + + // 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 = MSDF_ATLAS_DEFAULT_EM_SIZE; + } + if (config.imageType == ImageType::HARD_MASK || config.imageType == ImageType::SOFT_MASK) { + rangeMode = RANGE_PIXEL; + rangeValue = 1; + } else if (rangeValue <= 0) { + rangeMode = RANGE_PIXEL; + rangeValue = DEFAULT_PIXEL_RANGE; + } + if (config.kerning && !(config.arteryFontFilename || config.jsonFilename || config.shadronPreviewFilename)) + config.kerning = false; + if (config.threadCount <= 0) + config.threadCount = std::max((int) std::thread::hardware_concurrency(), 1); + if (config.generatorAttributes.scanlinePass) { + if (explicitErrorCorrectionMode && config.generatorAttributes.config.errorCorrection.distanceCheckMode != msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE) { + const char *fallbackModeName = "unknown"; + switch (config.generatorAttributes.config.errorCorrection.mode) { + case msdfgen::ErrorCorrectionConfig::DISABLED: fallbackModeName = "disabled"; break; + case msdfgen::ErrorCorrectionConfig::INDISCRIMINATE: fallbackModeName = "distance-fast"; break; + case msdfgen::ErrorCorrectionConfig::EDGE_PRIORITY: fallbackModeName = "auto-fast"; break; + case msdfgen::ErrorCorrectionConfig::EDGE_ONLY: fallbackModeName = "edge-fast"; break; + } + printf("Selected error correction mode not compatible with scanline mode, falling back to %s.\n", fallbackModeName); + } + config.generatorAttributes.config.errorCorrection.distanceCheckMode = msdfgen::ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE; + } + + // 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."); +#ifndef MSDF_ATLAS_NO_ARTERY_FONT + 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); + } +#endif + 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 + bool floatingPointFormat = ( + config.imageFormat == ImageFormat::TIFF || + config.imageFormat == ImageFormat::TEXT_FLOAT || + config.imageFormat == ImageFormat::BINARY_FLOAT || + config.imageFormat == ImageFormat::BINARY_FLOAT_BE + ); + + // Load fonts + std::vector glyphs; + std::vector fonts; + bool anyCodepointsAvailable = false; + { + class FontHolder { + msdfgen::FreetypeHandle *ft; + msdfgen::FontHandle *font; + const char *fontFilename; + public: + FontHolder() : ft(msdfgen::initializeFreetype()), font(nullptr), fontFilename(nullptr) { } + ~FontHolder() { + if (ft) { + if (font) + msdfgen::destroyFont(font); + msdfgen::deinitializeFreetype(ft); + } + } + bool load(const char *fontFilename, bool isVarFont) { + if (ft && fontFilename) { + if (this->fontFilename && !strcmp(this->fontFilename, fontFilename)) + return true; + if (font) + msdfgen::destroyFont(font); + if ((font = isVarFont ? loadVarFont(ft, fontFilename) : msdfgen::loadFont(ft, fontFilename))) { + this->fontFilename = fontFilename; + return true; + } + this->fontFilename = nullptr; + } + return false; + } + operator msdfgen::FontHandle *() const { + return font; + } + } font; + + for (FontInput &fontInput : fontInputs) { + if (!font.load(fontInput.fontFilename, fontInput.variableFont)) + ABORT("Failed to load specified font file."); + if (fontInput.fontScale <= 0) + fontInput.fontScale = 1; + + // Load character set + Charset charset; + if (fontInput.charsetFilename) { + if (!charset.load(fontInput.charsetFilename, fontInput.glyphIdentifierType != GlyphIdentifierType::UNICODE_CODEPOINT)) + ABORT(fontInput.glyphIdentifierType == GlyphIdentifierType::GLYPH_INDEX ? "Failed to load glyph set specification." : "Failed to load character set specification."); + } else { + charset = Charset::ASCII; + fontInput.glyphIdentifierType = GlyphIdentifierType::UNICODE_CODEPOINT; + } + + // Load glyphs + FontGeometry fontGeometry(&glyphs); + int glyphsLoaded = -1; + switch (fontInput.glyphIdentifierType) { + case GlyphIdentifierType::GLYPH_INDEX: + glyphsLoaded = fontGeometry.loadGlyphset(font, fontInput.fontScale, charset, config.preprocessGeometry, config.kerning); + break; + case GlyphIdentifierType::UNICODE_CODEPOINT: + glyphsLoaded = fontGeometry.loadCharset(font, fontInput.fontScale, charset, config.preprocessGeometry, config.kerning); + anyCodepointsAvailable |= glyphsLoaded > 0; + break; + } + if (glyphsLoaded < 0) + ABORT("Failed to load glyphs from font."); + printf("Loaded geometry of %d out of %d glyphs", glyphsLoaded, (int) charset.size()); + if (fontInputs.size() > 1) + printf(" from font \"%s\"", fontInput.fontFilename); + printf(".\n"); + // List missing glyphs + if (glyphsLoaded < (int) charset.size()) { + printf("Missing %d %s", (int) charset.size()-glyphsLoaded, fontInput.glyphIdentifierType == GlyphIdentifierType::UNICODE_CODEPOINT ? "codepoints" : "glyphs"); + bool first = true; + switch (fontInput.glyphIdentifierType) { + case GlyphIdentifierType::GLYPH_INDEX: + for (unicode_t cp : charset) + if (!fontGeometry.getGlyph(msdfgen::GlyphIndex(cp))) + printf("%c 0x%02X", first ? ((first = false), ':') : ',', cp); + break; + case GlyphIdentifierType::UNICODE_CODEPOINT: + for (unicode_t cp : charset) + if (!fontGeometry.getGlyph(cp)) + printf("%c 0x%02X", first ? ((first = false), ':') : ',', cp); + break; + } + printf("\n"); + } + + if (fontInput.fontName) + fontGeometry.setName(fontInput.fontName); + + fonts.push_back((FontGeometry &&) fontGeometry); + } + } + if (glyphs.empty()) + ABORT("No glyphs loaded."); + + // Determine final atlas dimensions, scale and range, pack glyphs + { + double unitRange = 0, pxRange = 0; + switch (rangeMode) { + case RANGE_EM: + unitRange = rangeValue; + 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); + else + atlasPacker.setMinimumScale(minEmSize); + 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(); + 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) { + if (config.expensiveColoring) { + Workload([&glyphs, &config](int i, int threadNo) -> bool { + unsigned long long glyphSeed = (LCG_MULTIPLIER*(config.coloringSeed^i)+LCG_INCREMENT)*!!config.coloringSeed; + glyphs[i].edgeColoring(config.edgeColoring, config.angleThreshold, glyphSeed); + return true; + }, glyphs.size()).finish(config.threadCount); + } else { + unsigned long long glyphSeed = config.coloringSeed; + for (GlyphGeometry &glyph : glyphs) { + glyphSeed *= LCG_MULTIPLIER; + glyph.edgeColoring(config.edgeColoring, config.angleThreshold, glyphSeed); + } + } + } + + bool success = false; + switch (config.imageType) { + case ImageType::HARD_MASK: + if (floatingPointFormat) + success = makeAtlas(glyphs, fonts, config); + else + success = makeAtlas(glyphs, fonts, config); + break; + case ImageType::SOFT_MASK: + case ImageType::SDF: + if (floatingPointFormat) + success = makeAtlas(glyphs, fonts, config); + else + success = makeAtlas(glyphs, fonts, config); + break; + case ImageType::PSDF: + if (floatingPointFormat) + success = makeAtlas(glyphs, fonts, config); + else + success = makeAtlas(glyphs, fonts, config); + break; + case ImageType::MSDF: + if (floatingPointFormat) + success = makeAtlas(glyphs, fonts, config); + else + success = makeAtlas(glyphs, fonts, config); + break; + case ImageType::MTSDF: + if (floatingPointFormat) + success = makeAtlas(glyphs, fonts, config); + else + success = makeAtlas(glyphs, fonts, config); + break; + } + if (!success) + result = 1; + } + + if (config.csvFilename) { + if (exportCSV(fonts.data(), fonts.size(), config.width, config.height, config.yDirection, config.csvFilename)) + puts("Glyph layout written into CSV file."); + else { + result = 1; + puts("Failed to write CSV output file."); + } + } + if (config.jsonFilename) { + if (exportJSON(fonts.data(), fonts.size(), config.emSize, config.pxRange, config.width, config.height, config.imageType, config.yDirection, config.jsonFilename, config.kerning)) + puts("Glyph layout and metadata written into JSON file."); + else { + result = 1; + puts("Failed to write JSON output file."); + } + } + + if (config.shadronPreviewFilename && config.shadronPreviewText) { + if (anyCodepointsAvailable) { + std::vector previewText; + utf8Decode(previewText, config.shadronPreviewText); + previewText.push_back(0); + if (generateShadronPreview(fonts.data(), fonts.size(), config.imageType, config.width, config.height, config.pxRange, previewText.data(), config.imageFilename, floatingPointFormat, config.shadronPreviewFilename)) + puts("Shadron preview script generated."); + else { + result = 1; + puts("Failed to generate Shadron preview file."); + } + } else { + result = 1; + puts("Shadron preview not supported in -glyphset mode."); + } + } + + return result; +} + +#endif diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/msdf-atlas-gen.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/msdf-atlas-gen.h new file mode 100644 index 0000000..e833a43 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/msdf-atlas-gen.h @@ -0,0 +1,38 @@ + +#pragma once + +/* + * MULTI-CHANNEL SIGNED DISTANCE FIELD ATLAS GENERATOR + * --------------------------------------------------- + * A utility by Viktor Chlumsky, (c) 2020 - 2023 + * Generates compact bitmap font atlases using MSDFgen + */ + +#include +#include + +#include "types.h" +#include "utf8.h" +#include "Rectangle.h" +#include "Charset.h" +#include "GlyphBox.h" +#include "GlyphGeometry.h" +#include "FontGeometry.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" diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/rectangle-packing.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/rectangle-packing.h new file mode 100644 index 0000000..4bcc6a8 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/rectangle-packing.h @@ -0,0 +1,19 @@ + +#pragma once + +#include +#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 +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 +std::pair packRectangles(RectangleType *rectangles, int count, int padding = 0); + +} + +#include "rectangle-packing.hpp" diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/rectangle-packing.hpp b/libs/msdf-atlas-gen/include/msdf-atlas-gen/rectangle-packing.hpp new file mode 100644 index 0000000..56c7a84 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/rectangle-packing.hpp @@ -0,0 +1,61 @@ + +#include "rectangle-packing.h" + +#include +#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 +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 +std::pair packRectangles(RectangleType *rectangles, int count, int padding) { + std::vector 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 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; +} + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/shadron-preview-generator.cpp b/libs/msdf-atlas-gen/include/msdf-atlas-gen/shadron-preview-generator.cpp new file mode 100644 index 0000000..dabe66c --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/shadron-preview-generator.cpp @@ -0,0 +1,172 @@ + +#include "shadron-preview-generator.h" + +#include +#include + +namespace msdf_atlas { + +static const char * const shadronFillGlyphMask = R"( +template +glsl vec4 fillGlyph(vec2 texCoord) { + float fill = texture((ATLAS), texCoord).r; + return vec4(vec3(COLOR), fill); +} +)"; + +static const char * const shadronFillGlyphSdf = R"( +template +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 + +glsl struct GlyphVertex { + vec2 coord; + vec2 texCoord; +}; + +template +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, triangles, VERTEX_LIST), \ + fragment(fillGlyph), \ + 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; +} + +static std::string escapeString(const std::string &str) { + std::string output; + for (int i = 0; i < (int) str.size(); ++i) { + switch (str[i]) { + case '"': + output += "\\\""; + break; + case '\\': + output += "\\\\"; + break; + default: + output.push_back(str[i]); + } + } + return output; +} + +bool generateShadronPreview(const FontGeometry *fonts, int fontCount, ImageType atlasType, int atlasWidth, int atlasHeight, double pxRange, const unicode_t *text, const char *imageFilename, bool fullRange, const char *outputFilename) { + if (fontCount <= 0) + return false; + double texelWidth = 1./atlasWidth; + double texelHeight = 1./atlasHeight; + bool anyGlyphs = false; + FILE *file = fopen(outputFilename, "w"); + if (!file) + return false; + fprintf(file, shadronPreviewPreamble, atlasType == ImageType::HARD_MASK || atlasType == ImageType::SOFT_MASK ? shadronFillGlyphMask : shadronFillGlyphSdf); + if (imageFilename) + fprintf(file, "image Atlas = file(\"%s\")", escapeString(relativizePath(outputFilename, imageFilename)).c_str()); + else + fprintf(file, "image Atlas = file()"); + fprintf(file, " : %sfilter(%s), map(repeat);\n", fullRange ? "full_range(true), " : "", atlasType == ImageType::HARD_MASK ? "nearest" : "linear"); + fprintf(file, "const vec2 txRange = vec2(%.9g, %.9g);\n\n", pxRange*texelWidth, pxRange*texelHeight); + { + msdfgen::FontMetrics fontMetrics = fonts->getMetrics(); + for (int i = 1; i < fontCount; ++i) { + fontMetrics.lineHeight = std::max(fontMetrics.lineHeight, fonts[i].getMetrics().lineHeight); + fontMetrics.ascenderY = std::max(fontMetrics.ascenderY, fonts[i].getMetrics().ascenderY); + fontMetrics.descenderY = std::min(fontMetrics.descenderY, fonts[i].getMetrics().descenderY); + } + double fsScale = 1/(fontMetrics.ascenderY-fontMetrics.descenderY); + fputs("vertex_list GlyphVertex textQuadVertices = {\n", file); + double x = 0, y = -fsScale*fontMetrics.ascenderY; + 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 < fontCount; ++i) { + const GlyphGeometry *glyph = fonts[i].getGlyph(*cp); + if (glyph) { + if (!glyph->isWhitespace()) { + double pl, pb, pr, pt; + double il, ib, ir, it; + glyph->getQuadPlaneBounds(pl, pb, pr, pt); + glyph->getQuadAtlasBounds(il, ib, ir, it); + pl *= fsScale, pb *= fsScale, pr *= fsScale, pt *= fsScale; + pl += x, pb += y, pr += x, pt += y; + il *= texelWidth, ib *= texelHeight, ir *= texelWidth, it *= texelHeight; + 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 + ); + } + double advance = glyph->getAdvance(); + fonts[i].getAdvance(advance, cp[0], cp[1]); + x += fsScale*advance; + anyGlyphs = true; + 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 anyGlyphs; +} + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/shadron-preview-generator.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/shadron-preview-generator.h new file mode 100644 index 0000000..75da3eb --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/shadron-preview-generator.h @@ -0,0 +1,14 @@ + +#pragma once + +#include +#include +#include "types.h" +#include "FontGeometry.h" + +namespace msdf_atlas { + +/// Generates a Shadron script that displays a string using the generated atlas +bool generateShadronPreview(const FontGeometry *fonts, int fontCount, ImageType atlasType, int atlasWidth, int atlasHeight, double pxRange, const unicode_t *text, const char *imageFilename, bool fullRange, const char *outputFilename); + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/size-selectors.cpp b/libs/msdf-atlas-gen/include/msdf-atlas-gen/size-selectors.cpp new file mode 100644 index 0000000..ef2f21b --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/size-selectors.cpp @@ -0,0 +1,90 @@ + +#include "size-selectors.h" + +#include + +namespace msdf_atlas { + +template +SquareSizeSelector::SquareSizeSelector(int minArea) : lowerBound(0), upperBound(-1) { + if (minArea > 0) + lowerBound = int(sqrt(minArea-1))/MULTIPLE+1; + updateCurrent(); +} + +template +void SquareSizeSelector::updateCurrent() { + if (upperBound < 0) + current = 5*lowerBound/4+16/MULTIPLE; + else + current = lowerBound+(upperBound-lowerBound)/2; +} + +template +bool SquareSizeSelector::operator()(int &width, int &height) const { + width = MULTIPLE*current, height = MULTIPLE*current; + return lowerBound < upperBound || upperBound < 0; +} + +template +SquareSizeSelector & SquareSizeSelector::operator++() { + lowerBound = current+1; + updateCurrent(); + return *this; +} + +template +SquareSizeSelector & SquareSizeSelector::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; +} + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/size-selectors.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/size-selectors.h new file mode 100644 index 0000000..b75fc3a --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/size-selectors.h @@ -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 +class SquareSizeSelector { + +public: + explicit SquareSizeSelector(int minArea = 0); + bool operator()(int &width, int &height) const; + SquareSizeSelector & operator++(); + SquareSizeSelector & 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; + +}; + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/types.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/types.h new file mode 100644 index 0000000..27311e7 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/types.h @@ -0,0 +1,52 @@ + +#pragma once + +#include + +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 +}; + +/// Glyph identification +enum class GlyphIdentifierType { + GLYPH_INDEX, + UNICODE_CODEPOINT +}; + +/// Direction of the Y-axis +enum class YDirection { + BOTTOM_UP, + TOP_DOWN +}; + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/utf8.cpp b/libs/msdf-atlas-gen/include/msdf-atlas-gen/utf8.cpp new file mode 100644 index 0000000..393c5f2 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/utf8.cpp @@ -0,0 +1,38 @@ + +#include "utf8.h" + +namespace msdf_atlas { + +void utf8Decode(std::vector &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; ((unsigned char) *c<>block))<<(6*block); + rBytes = block; + } else + continue; // error + } else + continue; // error + if (!rBytes) { + if (!(start && cp == 0xfeff)) // BOM + codepoints.push_back(cp); + start = false; + } + } +} + +} diff --git a/libs/msdf-atlas-gen/include/msdf-atlas-gen/utf8.h b/libs/msdf-atlas-gen/include/msdf-atlas-gen/utf8.h new file mode 100644 index 0000000..740c2fb --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdf-atlas-gen/utf8.h @@ -0,0 +1,12 @@ + +#pragma once + +#include +#include "types.h" + +namespace msdf_atlas { + +/// Decodes the UTF-8 string into an array of Unicode codepoints +void utf8Decode(std::vector &codepoints, const char *utf8String); + +} diff --git a/libs/msdf-atlas-gen/include/msdfgen/.gitattributes b/libs/msdf-atlas-gen/include/msdfgen/.gitattributes new file mode 100644 index 0000000..176a458 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdfgen/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/libs/msdf-atlas-gen/include/msdfgen/.gitignore b/libs/msdf-atlas-gen/include/msdfgen/.gitignore new file mode 100644 index 0000000..75c2cea --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdfgen/.gitignore @@ -0,0 +1,30 @@ +/build/ +/Debug/ +/Release/ +/Release OpenMP/ +/Debug Library/ +/Release Library/ +/Release Library OpenMP/ +/x86/ +/x64/ +.vs/ +.vscode/ +.DS_Store +*.exe +*.zip +*.user +*.sdf +*.pdb +*.ipdb +*.iobj +*.suo +*.VC.opendb +*.VC.db +/bin/*.lib +/bin/msdfgen +output.png +render.png +out/ +/build_xcode/ +/cmake-gen.bat +/line-ending-check.bat diff --git a/libs/msdf-atlas-gen/include/msdfgen/CHANGELOG.md b/libs/msdf-atlas-gen/include/msdfgen/CHANGELOG.md new file mode 100644 index 0000000..1274a08 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdfgen/CHANGELOG.md @@ -0,0 +1,99 @@ + +## Version 1.10 (2023-01-15) + +- Switched to vcpkg as the primary dependency management system +- Switched to libpng as the primary PNG file encoder +- Parameters of variable fonts can be specified +- Fixed a bug that prevented glyph 0 to be specified in a glyphset + +### Version 1.9.2 (2021-12-01) + +- Improved detection of numerical errors in cubic equation solver +- Added -windingpreprocess option +- Fixed edge coloring not restored if lost during preprocessing + +### Version 1.9.1 (2021-07-09) + +- Fixed an edge case bug in the new MSDF error correction algorithm + +## Version 1.9 (2021-05-28) + +- Error correction of multi-channel distance fields has been completely reworked +- Added new edge coloring strategy that optimizes colors based on distances between edges +- Added some minor functions for the library API +- Minor code refactor and optimizations + +## Version 1.8 (2020-10-17) + +- Integrated the Skia library into the project, which is used to preprocess the shape geometry and eliminate any self-intersections and other irregularities previously unsupported by the software + - The scanline pass and overlapping contour mode is made obsolete by this step and has been disabled by default. The preprocess step can be disabled by the new `-nopreprocess` switch and the former enabled by `-scanline` and `-overlap` respectively. + - The project can be built without the Skia library, forgoing the geometry preprocessing feature. This is controlled by the macro definition `MSDFGEN_USE_SKIA` +- Significantly improved performance of the core algorithm by reusing results from previously computed pixels +- Introduced an additional error correction routine which eliminates MSDF artifacts by analytically predicting results of bilinear interpolation +- Added the possibility to load font glyphs by their index rather than a Unicode value (use the prefix `g` before the character code in `-font` argument) +- Added `-distanceshift` argument that can be used to adjust the center of the distance range in the output distance field +- Fixed several errors in the evaluation of curve distances +- Fixed an issue with paths containing convergent corners (those whose inner angle is zero) +- The algorithm for pseudo-distance computation slightly changed, fixing certain rare edge cases and improving consistency +- Added the ability to supply own `FT_Face` handle to the msdfgen library +- Minor refactor of the core algorithm + +### Version 1.7.1 (2020-03-09) + +- Fixed an edge case bug in scanline rasterization + +## Version 1.7 (2020-03-07) + +- Added `mtsdf` mode - a combination of `msdf` with `sdf` in the alpha channel +- Distance fields can now be stored as uncompressed TIFF image files with floating point precision +- Bitmap class refactor - template argument split into data type and number of channels, bitmap reference classes introduced +- Added a secondary "ink trap" edge coloring heuristic, can be selected using `-coloringstrategy inktrap` +- Added computation of estimated rendering error for a given SDF +- Added computation of bounding box that includes sharp mitered corners +- The API for bounds computation of the `Shape` class changed for clarity +- Fixed several edge case bugs + +## Version 1.6 (2019-04-08) + +- Core algorithm rewritten to split up advanced edge selection logic into modular template arguments. +- Pseudo-distance evaluation reworked to eliminate discontinuities at the midpoint between edges. +- MSDF error correction reworked to also fix distances away from edges and consider diagonal pairs. Code simplified. +- Added scanline rasterization support for `Shape`. +- Added a scanline pass in the standalone version, which corrects the signs in the distance field according to the selected fill rule (`-fillrule`). Can be disabled using `-noscanline`. +- Fixed autoframe scaling, which previously caused the output to have unnecessary empty border. +- `-guessorder` switch no longer enabled by default, as the functionality is now provided by the scanline pass. +- Updated FreeType and other libraries, changed to static linkage +- Added 64-bit and static library builds to the Visual Studio solution + +## Version 1.5 (2017-07-23) + +- Fixed rounding error in cubic curve splitting. +- SVG parser fixes and support for additional path commands. +- Added CMake build script. + +## Version 1.4 (2017-02-09) + +- Reworked contour combining logic to support overlapping contours. Original algorithm preserved in functions with `_legacy` suffix, which are invoked by the new `-legacy` switch. +- Fixed a severe bug in cubic curve distance computation, where a control point lies at the endpoint. +- Standalone version now automatically detects if the input has the wrong orientation and adjusts the distance field accordingly. Can be disabled by `-keeporder` or `-reverseorder` switch. +- SVG parser fixes and improvements. + +## Version 1.3 (2016-12-07) + +- Fixed `-reverseorder` switch. +- Fixed glyph loading to use the proper method of acquiring outlines from FreeType. + +## Version 1.2 (2016-07-20) + +- Added option to specify that shape vertices are listed in reverse order (`-reverseorder`). +- Added option to set a seed for the edge coloring heuristic (-seed \), which can be used to adjust the output. +- Fixed parsing of glyph contours that start with a curve control point. + +## Version 1.1 (2016-05-08) + +- Switched to MIT license due to popular demand. +- Fixed SDF rendering anti-aliasing when the output is smaller than the distance field. + +## Version 1.0 (2016-04-28) + +- Project published. diff --git a/libs/msdf-atlas-gen/include/msdfgen/CMakeLists.txt b/libs/msdf-atlas-gen/include/msdfgen/CMakeLists.txt new file mode 100644 index 0000000..3293c1b --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdfgen/CMakeLists.txt @@ -0,0 +1,262 @@ + +cmake_minimum_required(VERSION 3.15) +include(cmake/version.cmake) + +option(MSDFGEN_CORE_ONLY "Only build the core library with no dependencies" OFF) +option(MSDFGEN_BUILD_STANDALONE "Build the msdfgen standalone executable" ON) +option(MSDFGEN_USE_VCPKG "Use vcpkg package manager to link project dependencies" ON) +option(MSDFGEN_USE_OPENMP "Build with OpenMP support for multithreaded code" OFF) +option(MSDFGEN_USE_CPP11 "Build with C++11 enabled" ON) +option(MSDFGEN_USE_SKIA "Build with the Skia library" ON) +option(MSDFGEN_INSTALL "Generate installation target" OFF) +option(MSDFGEN_DYNAMIC_RUNTIME "Link dynamic runtime library instead of static" OFF) +option(BUILD_SHARED_LIBS "Generate dynamic library files instead of static" OFF) + +if(MSDFGEN_CORE_ONLY AND MSDFGEN_BUILD_STANDALONE) + message(WARNING "Option MSDFGEN_CORE_ONLY ignored - extensions are required for standalone executable") + set(MSDFGEN_CORE_ONLY OFF) +endif() +if(MSDFGEN_CORE_ONLY AND MSDFGEN_USE_VCPKG) + message(STATUS "Option MSDFGEN_USE_VCPKG ignored due to MSDFGEN_CORE_ONLY - core has no dependencies") + set(MSDFGEN_USE_VCPKG OFF) +endif() + +get_property(MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(NOT MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE) + message(STATUS "CMAKE_BUILD_TYPE not set, defaulting to Release") + set(CMAKE_BUILD_TYPE Release) +endif() + +if(MSDFGEN_DYNAMIC_RUNTIME) + set(MSDFGEN_MSVC_RUNTIME "MultiThreaded$<$:Debug>DLL") +else() + set(MSDFGEN_MSVC_RUNTIME "MultiThreaded$<$:Debug>") +endif() + +if(BUILD_SHARED_LIBS) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +endif() + +if(MSDFGEN_USE_VCPKG) + # Make sure that vcpkg toolchain file is set + if(NOT CMAKE_TOOLCHAIN_FILE) + if(DEFINED ENV{VCPKG_ROOT}) + set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake") + else() + message(SEND_ERROR "Vcpkg toolchain not configured. Either set VCPKG_ROOT environment variable or pass -DCMAKE_TOOLCHAIN_FILE=VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake to cmake") + endif() + endif() + # Default to statically linked vcpkg triplet on Windows + if(WIN32 AND NOT VCPKG_TARGET_TRIPLET AND NOT MSDFGEN_DYNAMIC_RUNTIME) + if(CMAKE_GENERATOR_PLATFORM MATCHES "64$" AND NOT CMAKE_GENERATOR_PLATFORM STREQUAL "ARM64") + set(VCPKG_TARGET_TRIPLET "x64-windows-static") + elseif(CMAKE_GENERATOR_PLATFORM MATCHES "32$" OR CMAKE_GENERATOR_PLATFORM STREQUAL "x86") + set(VCPKG_TARGET_TRIPLET "x86-windows-static") + else() + if(CMAKE_GENERATOR_PLATFORM) + message(WARNING "Vcpkg triplet not explicitly specified and could not be deduced. Recommend using -DVCPKG_TARGET_TRIPLET=x64-windows-static or similar") + else() + message(WARNING "Vcpkg triplet not explicitly specified and could not be deduced. Recommend using -A to explicitly select platform (Win32 or x64)") + endif() + endif() + endif() + # Select project features + if(NOT MSDFGEN_VCPKG_FEATURES_SET) + set(VCPKG_MANIFEST_NO_DEFAULT_FEATURES ON) + if(NOT MSDFGEN_CORE_ONLY) + list(APPEND VCPKG_MANIFEST_FEATURES "extensions") + endif() + if(MSDFGEN_BUILD_STANDALONE) + list(APPEND VCPKG_MANIFEST_FEATURES "standalone") + endif() + if(MSDFGEN_USE_SKIA) + list(APPEND VCPKG_MANIFEST_FEATURES "geometry-preprocessing") + endif() + if(MSDFGEN_USE_OPENMP) + list(APPEND VCPKG_MANIFEST_FEATURES "openmp") + endif() + endif() +endif() + +# Version is specified in vcpkg.json +project(msdfgen VERSION ${MSDFGEN_VERSION} LANGUAGES CXX) + +file(GLOB_RECURSE MSDFGEN_CORE_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "core/*.h" "core/*.hpp") +file(GLOB_RECURSE MSDFGEN_CORE_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "core/*.cpp") +file(GLOB_RECURSE MSDFGEN_EXT_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "ext/*.h" "ext/*.hpp") +file(GLOB_RECURSE MSDFGEN_EXT_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "ext/*.cpp" "lib/*.cpp") + +# Core library +add_library(msdfgen-core "${CMAKE_CURRENT_SOURCE_DIR}/msdfgen.h" ${MSDFGEN_CORE_HEADERS} ${MSDFGEN_CORE_SOURCES}) +add_library(msdfgen::msdfgen-core ALIAS msdfgen-core) +set_target_properties(msdfgen-core PROPERTIES PUBLIC_HEADER "${MSDFGEN_CORE_HEADERS}") +set_property(TARGET msdfgen-core PROPERTY MSVC_RUNTIME_LIBRARY "${MSDFGEN_MSVC_RUNTIME}") +target_compile_definitions(msdfgen-core PUBLIC + MSDFGEN_VERSION=${MSDFGEN_VERSION} + MSDFGEN_VERSION_MAJOR=${MSDFGEN_VERSION_MAJOR} + MSDFGEN_VERSION_MINOR=${MSDFGEN_VERSION_MINOR} + MSDFGEN_VERSION_REVISION=${MSDFGEN_VERSION_REVISION} + MSDFGEN_COPYRIGHT_YEAR=${MSDFGEN_COPYRIGHT_YEAR} +) +target_include_directories(msdfgen-core INTERFACE + $ + $ +) +set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT msdfgen-core) + +if(MSDFGEN_USE_CPP11) + target_compile_features(msdfgen-core PUBLIC cxx_std_11) + target_compile_definitions(msdfgen-core PUBLIC MSDFGEN_USE_CPP11) +endif() + +if(MSDFGEN_USE_OPENMP) + # Note: Clang doesn't support OpenMP by default... + find_package(OpenMP REQUIRED COMPONENTS CXX) + target_compile_definitions(msdfgen-core PUBLIC MSDFGEN_USE_OPENMP) + target_link_libraries(msdfgen-core PUBLIC OpenMP::OpenMP_CXX) +endif() + +if(BUILD_SHARED_LIBS AND WIN32) + target_compile_definitions(msdfgen-core PRIVATE "MSDFGEN_PUBLIC=__declspec(dllexport)") + target_compile_definitions(msdfgen-core INTERFACE "MSDFGEN_PUBLIC=__declspec(dllimport)") +else() + target_compile_definitions(msdfgen-core PUBLIC MSDFGEN_PUBLIC=) +endif() + +# Extensions library +if(NOT MSDFGEN_CORE_ONLY) + if(NOT TARGET Freetype::Freetype) + find_package(Freetype REQUIRED) + endif() + if(NOT TARGET tinyxml2::tinyxml2) + find_package(tinyxml2 REQUIRED) + endif() + if(NOT TARGET PNG::PNG) + find_package(PNG REQUIRED) + endif() + + add_library(msdfgen-ext "${CMAKE_CURRENT_SOURCE_DIR}/msdfgen-ext.h" ${MSDFGEN_EXT_HEADERS} ${MSDFGEN_EXT_SOURCES}) + add_library(msdfgen::msdfgen-ext ALIAS msdfgen-ext) + set_target_properties(msdfgen-ext PROPERTIES PUBLIC_HEADER "${MSDFGEN_EXT_HEADERS}") + set_property(TARGET msdfgen-ext PROPERTY MSVC_RUNTIME_LIBRARY "${MSDFGEN_MSVC_RUNTIME}") + target_compile_definitions(msdfgen-ext PUBLIC MSDFGEN_USE_LIBPNG) + target_link_libraries(msdfgen-ext PRIVATE msdfgen::msdfgen-core Freetype::Freetype tinyxml2::tinyxml2 PNG::PNG) + target_include_directories(msdfgen-ext + PUBLIC + $ + $ + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include + ) + set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT msdfgen-ext) + + if(MSDFGEN_USE_SKIA) + set(MSDFGEN_SKIA_LIB skia) + set(CMAKE_THREAD_PREFER_PTHREAD TRUE) + set(THREADS_PREFER_PTHREAD_FLAG TRUE) + find_package(Threads REQUIRED) + if(NOT TARGET skia) + if(MSDFGEN_USE_VCPKG) + find_package(unofficial-skia REQUIRED) + set(MSDFGEN_SKIA_LIB unofficial::skia::skia) + else() + find_package(skia REQUIRED) + endif() + endif() + target_compile_features(msdfgen-ext PUBLIC cxx_std_17) + target_compile_definitions(msdfgen-ext PUBLIC MSDFGEN_USE_SKIA) + target_link_libraries(msdfgen-ext PRIVATE Threads::Threads ${MSDFGEN_SKIA_LIB}) + endif() + + add_library(msdfgen-full INTERFACE) + add_library(msdfgen::msdfgen ALIAS msdfgen-full) + target_link_libraries(msdfgen-full INTERFACE msdfgen::msdfgen-core msdfgen::msdfgen-ext) +endif() + +# Standalone executable +if(MSDFGEN_BUILD_STANDALONE) + set(MSDFGEN_STANDALONE_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp") + if(MSVC) + set(MSDFGEN_STANDALONE_SOURCES ${MSDFGEN_STANDALONE_SOURCES} "${CMAKE_CURRENT_SOURCE_DIR}/msdfgen.rc") + endif() + add_executable(msdfgen ${MSDFGEN_STANDALONE_SOURCES}) + target_compile_definitions(msdfgen PUBLIC MSDFGEN_STANDALONE) + target_compile_definitions(msdfgen PRIVATE MSDFGEN_VERSION_UNDERLINE=${MSDFGEN_VERSION_UNDERLINE}) + set_property(TARGET msdfgen PROPERTY MSVC_RUNTIME_LIBRARY "${MSDFGEN_MSVC_RUNTIME}") + target_link_libraries(msdfgen PRIVATE msdfgen::msdfgen) + set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT msdfgen) +endif() + +# Installation +if(MSDFGEN_INSTALL) + include(GNUInstallDirs) + include(CMakePackageConfigHelpers) + set(MSDFGEN_CONFIG_PATH "lib/cmake/msdfgen") + + # install tree package config + write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/msdfgenConfigVersion.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion + ) + + configure_package_config_file( + cmake/msdfgenConfig.cmake.in + ${MSDFGEN_CONFIG_PATH}/msdfgenConfig.cmake + INSTALL_DESTINATION ${MSDFGEN_CONFIG_PATH} + NO_CHECK_REQUIRED_COMPONENTS_MACRO + ) + + # build tree package config + configure_file( + cmake/msdfgenConfig.cmake.in + msdfgenConfig.cmake + @ONLY + ) + + install(TARGETS msdfgen-core EXPORT msdfgenTargets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + FRAMEWORK DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/msdfgen/core + ) + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/msdfgen.h" DESTINATION include/msdfgen) + if(MSVC AND BUILD_SHARED_LIBS) + install(FILES $ DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL) + endif() + + if(NOT MSDFGEN_CORE_ONLY) + install(TARGETS msdfgen-ext EXPORT msdfgenTargets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + FRAMEWORK DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/msdfgen/ext + ) + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/msdfgen-ext.h" DESTINATION include/msdfgen) + if(MSVC AND BUILD_SHARED_LIBS) + install(FILES $ DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL) + endif() + install(TARGETS msdfgen-full EXPORT msdfgenTargets) + endif() + + export(EXPORT msdfgenTargets NAMESPACE msdfgen:: FILE "${CMAKE_CURRENT_BINARY_DIR}/msdfgenTargets.cmake") + install(EXPORT msdfgenTargets FILE msdfgenTargets.cmake NAMESPACE msdfgen:: DESTINATION ${MSDFGEN_CONFIG_PATH}) + + if(MSDFGEN_BUILD_STANDALONE) + install(TARGETS msdfgen EXPORT msdfgenBinaryTargets DESTINATION ${CMAKE_INSTALL_BINDIR}) + if(MSVC) + install(FILES $ DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL) + endif() + export(EXPORT msdfgenBinaryTargets NAMESPACE msdfgen-standalone:: FILE "${CMAKE_CURRENT_BINARY_DIR}/msdfgenBinaryTargets.cmake") + install(EXPORT msdfgenBinaryTargets FILE msdfgenBinaryTargets.cmake NAMESPACE msdfgen-standalone:: DESTINATION ${MSDFGEN_CONFIG_PATH}) + endif() + + install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/${MSDFGEN_CONFIG_PATH}/msdfgenConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/msdfgenConfigVersion.cmake" + DESTINATION ${MSDFGEN_CONFIG_PATH} + ) +endif() diff --git a/libs/msdf-atlas-gen/include/msdfgen/LICENSE.txt b/libs/msdf-atlas-gen/include/msdfgen/LICENSE.txt new file mode 100644 index 0000000..69054bd --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdfgen/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 - 2023 Viktor Chlumsky + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/libs/msdf-atlas-gen/include/msdfgen/README.md b/libs/msdf-atlas-gen/include/msdfgen/README.md new file mode 100644 index 0000000..485cd76 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdfgen/README.md @@ -0,0 +1,215 @@ +# Multi-channel signed distance field generator + +This is a utility for generating signed distance fields from vector shapes and font glyphs, +which serve as a texture representation that can be used in real-time graphics to efficiently reproduce said shapes. +Although it can also be used to generate conventional signed distance fields best known from +[this Valve paper](https://steamcdn-a.akamaihd.net/apps/valve/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf) +and pseudo-distance fields, its primary purpose is to generate multi-channel distance fields, +using a method I have developed. Unlike monochrome distance fields, they have the ability +to reproduce sharp corners almost perfectly by utilizing all three color channels. + +The following comparison demonstrates the improvement in image quality. + +![demo-msdf16](https://user-images.githubusercontent.com/18639794/106391899-e37ebe80-63ef-11eb-988b-4764004bb196.png) +![demo-sdf16](https://user-images.githubusercontent.com/18639794/106391905-e679af00-63ef-11eb-96c3-993176330911.png) +![demo-sdf32](https://user-images.githubusercontent.com/18639794/106391906-e7aadc00-63ef-11eb-8f84-d402d0dd9174.png) + +- To learn more about this method, you can read my [Master's thesis](https://github.com/Chlumsky/msdfgen/files/3050967/thesis.pdf). +- Check out my [MSDF-Atlas-Gen](https://github.com/Chlumsky/msdf-atlas-gen) if you want to generate entire glyph atlases for text rendering. +- See what's new in the [changelog](CHANGELOG.md). + +## Getting started + +The project can be used either as a library or as a console program. It is divided into two parts, **[core](core)** +and **[extensions](ext)**. The core module has no dependencies and only uses bare C++. It contains all +key data structures and algorithms, which can be accessed through the [msdfgen.h](msdfgen.h) header. +Extensions contain utilities for loading fonts and SVG files, as well as saving PNG images. +Those are exposed by the [msdfgen-ext.h](msdfgen-ext.h) header. This module uses +[FreeType](https://freetype.org/), +[TinyXML2](https://www.grinninglizard.com/tinyxml2/), +[libpng](http://www.libpng.org/pub/png/libpng.html), +and (optionally) [Skia](https://skia.org/). + +Additionally, there is the [main.cpp](main.cpp), which wraps the functionality into +a comprehensive standalone console program. To start using the program immediately, +there is a Windows binary available for download in the ["Releases" section](https://github.com/Chlumsky/msdfgen/releases). +To use the project as a library, you may install it via the [vcpkg](https://vcpkg.io) package manager as +``` +vcpkg install msdfgen +``` +Or, to build the project from source, you may use the included [CMake script](CMakeLists.txt). +In its default configuration, it requires [vcpkg](https://vcpkg.io) as the provider for third-party library dependencies. +If you set the environment variable `VCPKG_ROOT` to the vcpkg directory, +the CMake configuration will take care of fetching all required packages from vcpkg. + +## Console commands + +The standalone program is executed as +``` +msdfgen.exe +``` +where only the input specification is required. + +Mode can be one of: + - **sdf** – generates a conventional monochrome (true) signed distance field. + - **psdf** – generates a monochrome signed pseudo-distance field. + - **msdf** (default) – generates a multi-channel signed distance field using my new method. + - **mtsdf** – generates a combined multi-channel and true signed distance field in the alpha channel. + +The input can be specified as one of: + - **-font \ \** – to load a glyph from a font file. + Character code can be expressed as either a decimal (63) or hexadecimal (0x3F) Unicode value, or an ASCII character + in single quotes ('?'). + - **-svg \** – to load an SVG file. Note that only the last vector path in the file will be used. + - **-shapedesc \**, -defineshape \, -stdin – to load a text description of the shape + from either a file, the next argument, or the standard input, respectively. Its syntax is documented further down. + +The complete list of available options can be printed with **-help**. +Some of the important ones are: + - **-o \** – specifies the output file name. The desired format will be deduced from the extension + (png, bmp, tif, txt, bin). Otherwise, use -format. + - **-size \ \** – specifies the dimensions of the output distance field (in pixels). + - **-range \**, **-pxrange \** – specifies the width of the range around the shape + between the minimum and maximum representable signed distance in shape units or distance field pixels, respectivelly. + - **-scale \** – sets the scale used to convert shape units to distance field pixels. + - **-translate \ \** – sets the translation of the shape in shape units. Otherwise the origin (0, 0) + lies in the bottom left corner. + - **-autoframe** – automatically frames the shape to fit the distance field. If the output must be precisely aligned, + you should manually position it using -translate and -scale instead. + - **-angle \** – specifies the maximum angle to be considered a corner. + Can be expressed in radians (3.0) or degrees with D at the end (171.9D). + - **-testrender \ \ \** - tests the generated distance field by using it to render an image + of the original shape into a PNG file with the specified dimensions. Alternatively, -testrendermulti renders + an image without combining the color channels, and may give you an insight in how the multi-channel distance field works. + - **-exportshape \** - saves the text description of the shape with edge coloring to the specified file. + This can be later edited and used as input through -shapedesc. + - **-printmetrics** – prints some useful information about the shape's layout. + +For example, +``` +msdfgen.exe msdf -font C:\Windows\Fonts\arialbd.ttf 'M' -o msdf.png -size 32 32 -pxrange 4 -autoframe -testrender render.png 1024 1024 +``` + +will take the glyph capital M from the Arial Bold typeface, generate a 32×32 multi-channel distance field +with a 4 pixels wide distance range, store it into msdf.png, and create a test render of the glyph as render.png. + +**Note:** Do not use `-autoframe` to generate character maps! It is intended as a quick preview only. + +## Library API + +If you choose to use this utility inside your own program, there are a few simple steps you need to perform +in order to generate a distance field. Please note that all classes and functions are in the `msdfgen` namespace. + + - Acquire a `Shape` object. You can either load it via `loadGlyph` or `loadSvgShape`, or construct it manually. + It consists of closed contours, which in turn consist of edges. An edge is represented by a `LinearEdge`, `QuadraticEdge`, + or `CubicEdge`. You can construct them from two endpoints and 0 to 2 Bézier control points. + - Normalize the shape using its `normalize` method and assign colors to edges if you need a multi-channel SDF. + This can be performed automatically using the `edgeColoringSimple` heuristic, or manually by setting each edge's + `color` member. Keep in mind that at least two color channels must be turned on in each edge, and iff two edges meet + at a sharp corner, they must only have one channel in common. + - Call `generateSDF`, `generatePseudoSDF`, or `generateMSDF` to generate a distance field into a floating point + `Bitmap` object. This can then be worked with further or saved to a file using `saveBmp`, `savePng`, or `saveTiff`. + - You may also render an image from the distance field using `renderSDF`. Consider calling `simulate8bit` + on the distance field beforehand to simulate the standard 8 bits/channel image format. + +Example: +```c++ +#include "msdfgen.h" +#include "msdfgen-ext.h" + +using namespace msdfgen; + +int main() { + FreetypeHandle *ft = initializeFreetype(); + if (ft) { + FontHandle *font = loadFont(ft, "C:\\Windows\\Fonts\\arialbd.ttf"); + if (font) { + Shape shape; + if (loadGlyph(shape, font, 'A')) { + shape.normalize(); + // max. angle + edgeColoringSimple(shape, 3.0); + // image width, height + Bitmap msdf(32, 32); + // range, scale, translation + generateMSDF(msdf, shape, 4.0, 1.0, Vector2(4.0, 4.0)); + savePng(msdf, "output.png"); + } + destroyFont(font); + } + deinitializeFreetype(ft); + } + return 0; +} + +``` + +## Using a multi-channel distance field + +Using a multi-channel distance field generated by this program is similarly simple to how a monochrome distance field is used. +The only additional operation is computing the **median** of the three channels inside the fragment shader, +right after sampling the distance field. This signed distance value can then be used the same way as usual. + +The following is an example GLSL fragment shader with anti-aliasing: + +```glsl +in vec2 texCoord; +out vec4 color; +uniform sampler2D msdf; +uniform vec4 bgColor; +uniform vec4 fgColor; + +float median(float r, float g, float b) { + return max(min(r, g), min(max(r, g), b)); +} + +void main() { + vec3 msd = texture(msdf, texCoord).rgb; + float sd = median(msd.r, msd.g, msd.b); + float screenPxDistance = screenPxRange()*(sd - 0.5); + float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0); + color = mix(bgColor, fgColor, opacity); +} +``` + +Here, `screenPxRange()` represents the distance field range in output screen pixels. For example, if the pixel range was set to 2 +when generating a 32x32 distance field, and it is used to draw a quad that is 72x72 pixels on the screen, +it should return 4.5 (because 72/32 * 2 = 4.5). +**For 2D rendering, this can generally be replaced by a precomputed uniform value.** + +For rendering in a **3D perspective only**, where the texture scale varies across the screen, +you may want to implement this function with fragment derivatives in the following way. +I would suggest precomputing `unitRange` as a uniform variable instead of `pxRange` for better performance. + +```glsl +uniform float pxRange; // set to distance field's pixel range + +float screenPxRange() { + vec2 unitRange = vec2(pxRange)/vec2(textureSize(msdf, 0)); + vec2 screenTexSize = vec2(1.0)/fwidth(texCoord); + return max(0.5*dot(unitRange, screenTexSize), 1.0); +} +``` + +`screenPxRange()` must never be lower than 1. If it is lower than 2, there is a high probability that the anti-aliasing will fail +and you may want to re-generate your distance field with a wider range. + +## Shape description syntax + +The text shape description has the following syntax. + - Each closed contour is enclosed by braces: `{ } { }` + - Each point (and control point) is written as two real numbers separated by a comma. + - Points in a contour are separated with semicolons. + - The last point of each contour must be equal to the first, or the symbol `#` can be used, which represents the first point. + - There can be an edge segment specification between any two points, also separated by semicolons. + This can include the edge's color (`c`, `m`, `y` or `w`) and/or one or two Bézier curve control points inside parentheses. + +For example, +``` +{ -1, -1; m; -1, +1; y; +1, +1; m; +1, -1; y; # } +``` +would represent a square with magenta and yellow edges, +``` +{ 0, 1; (+1.6, -0.8; -1.6, -0.8); # } +``` +is a teardrop shape formed by a single cubic Bézier curve. diff --git a/libs/msdf-atlas-gen/include/msdfgen/core/Bitmap.h b/libs/msdf-atlas-gen/include/msdfgen/core/Bitmap.h new file mode 100644 index 0000000..14407d6 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdfgen/core/Bitmap.h @@ -0,0 +1,50 @@ + +#pragma once + +#include "BitmapRef.hpp" + +namespace msdfgen { + +/// A 2D image bitmap with N channels of type T. Pixel memory is managed by the class. +template +class Bitmap { + +public: + Bitmap(); + Bitmap(int width, int height); + Bitmap(const BitmapConstRef &orig); + Bitmap(const Bitmap &orig); +#ifdef MSDFGEN_USE_CPP11 + Bitmap(Bitmap &&orig); +#endif + ~Bitmap(); + Bitmap & operator=(const BitmapConstRef &orig); + Bitmap & operator=(const Bitmap &orig); +#ifdef MSDFGEN_USE_CPP11 + Bitmap & operator=(Bitmap &&orig); +#endif + /// Bitmap width in pixels. + int width() const; + /// Bitmap height in pixels. + int height() const; + T * operator()(int x, int y); + const T * operator()(int x, int y) const; +#ifdef MSDFGEN_USE_CPP11 + explicit operator T *(); + explicit operator const T *() const; +#else + operator T *(); + operator const T *() const; +#endif + operator BitmapRef(); + operator BitmapConstRef() const; + +private: + T *pixels; + int w, h; + +}; + +} + +#include "Bitmap.hpp" diff --git a/libs/msdf-atlas-gen/include/msdfgen/core/Bitmap.hpp b/libs/msdf-atlas-gen/include/msdfgen/core/Bitmap.hpp new file mode 100644 index 0000000..cb16cac --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdfgen/core/Bitmap.hpp @@ -0,0 +1,117 @@ + +#include "Bitmap.h" + +#include +#include + +namespace msdfgen { + +template +Bitmap::Bitmap() : pixels(NULL), w(0), h(0) { } + +template +Bitmap::Bitmap(int width, int height) : w(width), h(height) { + pixels = new T[N*w*h]; +} + +template +Bitmap::Bitmap(const BitmapConstRef &orig) : w(orig.width), h(orig.height) { + pixels = new T[N*w*h]; + memcpy(pixels, orig.pixels, sizeof(T)*N*w*h); +} + +template +Bitmap::Bitmap(const Bitmap &orig) : w(orig.w), h(orig.h) { + pixels = new T[N*w*h]; + memcpy(pixels, orig.pixels, sizeof(T)*N*w*h); +} + +#ifdef MSDFGEN_USE_CPP11 +template +Bitmap::Bitmap(Bitmap &&orig) : pixels(orig.pixels), w(orig.w), h(orig.h) { + orig.pixels = NULL; + orig.w = 0, orig.h = 0; +} +#endif + +template +Bitmap::~Bitmap() { + delete [] pixels; +} + +template +Bitmap & Bitmap::operator=(const BitmapConstRef &orig) { + if (pixels != orig.pixels) { + delete [] pixels; + w = orig.width, h = orig.height; + pixels = new T[N*w*h]; + memcpy(pixels, orig.pixels, sizeof(T)*N*w*h); + } + return *this; +} + +template +Bitmap & Bitmap::operator=(const Bitmap &orig) { + if (this != &orig) { + delete [] pixels; + w = orig.w, h = orig.h; + pixels = new T[N*w*h]; + memcpy(pixels, orig.pixels, sizeof(T)*N*w*h); + } + return *this; +} + +#ifdef MSDFGEN_USE_CPP11 +template +Bitmap & Bitmap::operator=(Bitmap &&orig) { + if (this != &orig) { + delete [] pixels; + pixels = orig.pixels; + w = orig.w, h = orig.h; + orig.pixels = NULL; + } + return *this; +} +#endif + +template +int Bitmap::width() const { + return w; +} + +template +int Bitmap::height() const { + return h; +} + +template +T * Bitmap::operator()(int x, int y) { + return pixels+N*(w*y+x); +} + +template +const T * Bitmap::operator()(int x, int y) const { + return pixels+N*(w*y+x); +} + +template +Bitmap::operator T *() { + return pixels; +} + +template +Bitmap::operator const T *() const { + return pixels; +} + +template +Bitmap::operator BitmapRef() { + return BitmapRef(pixels, w, h); +} + +template +Bitmap::operator BitmapConstRef() const { + return BitmapConstRef(pixels, w, h); +} + +} diff --git a/libs/msdf-atlas-gen/include/msdfgen/core/BitmapRef.hpp b/libs/msdf-atlas-gen/include/msdfgen/core/BitmapRef.hpp new file mode 100644 index 0000000..6f9620d --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdfgen/core/BitmapRef.hpp @@ -0,0 +1,43 @@ + +#pragma once + +#include + +namespace msdfgen { + +typedef unsigned char byte; + +/// Reference to a 2D image bitmap or a buffer acting as one. Pixel storage not owned or managed by the object. +template +struct BitmapRef { + + T *pixels; + int width, height; + + inline BitmapRef() : pixels(NULL), width(0), height(0) { } + inline BitmapRef(T *pixels, int width, int height) : pixels(pixels), width(width), height(height) { } + + inline T * operator()(int x, int y) const { + return pixels+N*(width*y+x); + } + +}; + +/// Constant reference to a 2D image bitmap or a buffer acting as one. Pixel storage not owned or managed by the object. +template +struct BitmapConstRef { + + const T *pixels; + int width, height; + + inline BitmapConstRef() : pixels(NULL), width(0), height(0) { } + inline BitmapConstRef(const T *pixels, int width, int height) : pixels(pixels), width(width), height(height) { } + inline BitmapConstRef(const BitmapRef &orig) : pixels(orig.pixels), width(orig.width), height(orig.height) { } + + inline const T * operator()(int x, int y) const { + return pixels+N*(width*y+x); + } + +}; + +} diff --git a/libs/msdf-atlas-gen/include/msdfgen/core/Contour.cpp b/libs/msdf-atlas-gen/include/msdfgen/core/Contour.cpp new file mode 100644 index 0000000..ca80d3c --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdfgen/core/Contour.cpp @@ -0,0 +1,90 @@ + +#include "Contour.h" + +#include "arithmetics.hpp" + +namespace msdfgen { + +static double shoelace(const Point2 &a, const Point2 &b) { + return (b.x-a.x)*(a.y+b.y); +} + +void Contour::addEdge(const EdgeHolder &edge) { + edges.push_back(edge); +} + +#ifdef MSDFGEN_USE_CPP11 +void Contour::addEdge(EdgeHolder &&edge) { + edges.push_back((EdgeHolder &&) edge); +} +#endif + +EdgeHolder & Contour::addEdge() { + edges.resize(edges.size()+1); + return edges.back(); +} + +static void boundPoint(double &l, double &b, double &r, double &t, Point2 p) { + if (p.x < l) l = p.x; + if (p.y < b) b = p.y; + if (p.x > r) r = p.x; + if (p.y > t) t = p.y; +} + +void Contour::bound(double &l, double &b, double &r, double &t) const { + for (std::vector::const_iterator edge = edges.begin(); edge != edges.end(); ++edge) + (*edge)->bound(l, b, r, t); +} + +void Contour::boundMiters(double &l, double &b, double &r, double &t, double border, double miterLimit, int polarity) const { + if (edges.empty()) + return; + Vector2 prevDir = edges.back()->direction(1).normalize(true); + for (std::vector::const_iterator edge = edges.begin(); edge != edges.end(); ++edge) { + Vector2 dir = -(*edge)->direction(0).normalize(true); + if (polarity*crossProduct(prevDir, dir) >= 0) { + double miterLength = miterLimit; + double q = .5*(1-dotProduct(prevDir, dir)); + if (q > 0) + miterLength = min(1/sqrt(q), miterLimit); + Point2 miter = (*edge)->point(0)+border*miterLength*(prevDir+dir).normalize(true); + boundPoint(l, b, r, t, miter); + } + prevDir = (*edge)->direction(1).normalize(true); + } +} + +int Contour::winding() const { + if (edges.empty()) + return 0; + double total = 0; + if (edges.size() == 1) { + Point2 a = edges[0]->point(0), b = edges[0]->point(1/3.), c = edges[0]->point(2/3.); + total += shoelace(a, b); + total += shoelace(b, c); + total += shoelace(c, a); + } else if (edges.size() == 2) { + Point2 a = edges[0]->point(0), b = edges[0]->point(.5), c = edges[1]->point(0), d = edges[1]->point(.5); + total += shoelace(a, b); + total += shoelace(b, c); + total += shoelace(c, d); + total += shoelace(d, a); + } else { + Point2 prev = edges.back()->point(0); + for (std::vector::const_iterator edge = edges.begin(); edge != edges.end(); ++edge) { + Point2 cur = (*edge)->point(0); + total += shoelace(prev, cur); + prev = cur; + } + } + return sign(total); +} + +void Contour::reverse() { + for (int i = (int) edges.size()/2; i > 0; --i) + EdgeHolder::swap(edges[i-1], edges[edges.size()-i]); + for (std::vector::iterator edge = edges.begin(); edge != edges.end(); ++edge) + (*edge)->reverse(); +} + +} diff --git a/libs/msdf-atlas-gen/include/msdfgen/core/Contour.h b/libs/msdf-atlas-gen/include/msdfgen/core/Contour.h new file mode 100644 index 0000000..f79b269 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdfgen/core/Contour.h @@ -0,0 +1,34 @@ + +#pragma once + +#include +#include "EdgeHolder.h" + +namespace msdfgen { + +/// A single closed contour of a shape. +class Contour { + +public: + /// The sequence of edges that make up the contour. + std::vector edges; + + /// Adds an edge to the contour. + void addEdge(const EdgeHolder &edge); +#ifdef MSDFGEN_USE_CPP11 + void addEdge(EdgeHolder &&edge); +#endif + /// Creates a new edge in the contour and returns its reference. + EdgeHolder & addEdge(); + /// Adjusts the bounding box to fit the contour. + void bound(double &l, double &b, double &r, double &t) const; + /// Adjusts the bounding box to fit the contour border's mitered corners. + void boundMiters(double &l, double &b, double &r, double &t, double border, double miterLimit, int polarity) const; + /// Computes the winding of the contour. Returns 1 if positive, -1 if negative. + int winding() const; + /// Reverses the sequence of edges on the contour. + void reverse(); + +}; + +} diff --git a/libs/msdf-atlas-gen/include/msdfgen/core/EdgeColor.h b/libs/msdf-atlas-gen/include/msdfgen/core/EdgeColor.h new file mode 100644 index 0000000..9d49a5a --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdfgen/core/EdgeColor.h @@ -0,0 +1,18 @@ + +#pragma once + +namespace msdfgen { + +/// Edge color specifies which color channels an edge belongs to. +enum EdgeColor { + BLACK = 0, + RED = 1, + GREEN = 2, + YELLOW = 3, + BLUE = 4, + MAGENTA = 5, + CYAN = 6, + WHITE = 7 +}; + +} diff --git a/libs/msdf-atlas-gen/include/msdfgen/core/EdgeHolder.cpp b/libs/msdf-atlas-gen/include/msdfgen/core/EdgeHolder.cpp new file mode 100644 index 0000000..1a8c5f6 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdfgen/core/EdgeHolder.cpp @@ -0,0 +1,77 @@ + +#include "EdgeHolder.h" + +namespace msdfgen { + +void EdgeHolder::swap(EdgeHolder &a, EdgeHolder &b) { + EdgeSegment *tmp = a.edgeSegment; + a.edgeSegment = b.edgeSegment; + b.edgeSegment = tmp; +} + +EdgeHolder::EdgeHolder() : edgeSegment(NULL) { } + +EdgeHolder::EdgeHolder(EdgeSegment *segment) : edgeSegment(segment) { } + +EdgeHolder::EdgeHolder(Point2 p0, Point2 p1, EdgeColor edgeColor) : edgeSegment(new LinearSegment(p0, p1, edgeColor)) { } + +EdgeHolder::EdgeHolder(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor) : edgeSegment(new QuadraticSegment(p0, p1, p2, edgeColor)) { } + +EdgeHolder::EdgeHolder(Point2 p0, Point2 p1, Point2 p2, Point2 p3, EdgeColor edgeColor) : edgeSegment(new CubicSegment(p0, p1, p2, p3, edgeColor)) { } + +EdgeHolder::EdgeHolder(const EdgeHolder &orig) : edgeSegment(orig.edgeSegment ? orig.edgeSegment->clone() : NULL) { } + +#ifdef MSDFGEN_USE_CPP11 +EdgeHolder::EdgeHolder(EdgeHolder &&orig) : edgeSegment(orig.edgeSegment) { + orig.edgeSegment = NULL; +} +#endif + +EdgeHolder::~EdgeHolder() { + delete edgeSegment; +} + +EdgeHolder & EdgeHolder::operator=(const EdgeHolder &orig) { + if (this != &orig) { + delete edgeSegment; + edgeSegment = orig.edgeSegment ? orig.edgeSegment->clone() : NULL; + } + return *this; +} + +#ifdef MSDFGEN_USE_CPP11 +EdgeHolder & EdgeHolder::operator=(EdgeHolder &&orig) { + if (this != &orig) { + delete edgeSegment; + edgeSegment = orig.edgeSegment; + orig.edgeSegment = NULL; + } + return *this; +} +#endif + +EdgeSegment & EdgeHolder::operator*() { + return *edgeSegment; +} + +const EdgeSegment & EdgeHolder::operator*() const { + return *edgeSegment; +} + +EdgeSegment * EdgeHolder::operator->() { + return edgeSegment; +} + +const EdgeSegment * EdgeHolder::operator->() const { + return edgeSegment; +} + +EdgeHolder::operator EdgeSegment *() { + return edgeSegment; +} + +EdgeHolder::operator const EdgeSegment *() const { + return edgeSegment; +} + +} diff --git a/libs/msdf-atlas-gen/include/msdfgen/core/EdgeHolder.h b/libs/msdf-atlas-gen/include/msdfgen/core/EdgeHolder.h new file mode 100644 index 0000000..c4c5be7 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdfgen/core/EdgeHolder.h @@ -0,0 +1,41 @@ + +#pragma once + +#include "edge-segments.h" + +namespace msdfgen { + +/// Container for a single edge of dynamic type. +class EdgeHolder { + +public: + /// Swaps the edges held by a and b. + static void swap(EdgeHolder &a, EdgeHolder &b); + + EdgeHolder(); + EdgeHolder(EdgeSegment *segment); + EdgeHolder(Point2 p0, Point2 p1, EdgeColor edgeColor = WHITE); + EdgeHolder(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor = WHITE); + EdgeHolder(Point2 p0, Point2 p1, Point2 p2, Point2 p3, EdgeColor edgeColor = WHITE); + EdgeHolder(const EdgeHolder &orig); +#ifdef MSDFGEN_USE_CPP11 + EdgeHolder(EdgeHolder &&orig); +#endif + ~EdgeHolder(); + EdgeHolder & operator=(const EdgeHolder &orig); +#ifdef MSDFGEN_USE_CPP11 + EdgeHolder & operator=(EdgeHolder &&orig); +#endif + EdgeSegment & operator*(); + const EdgeSegment & operator*() const; + EdgeSegment * operator->(); + const EdgeSegment * operator->() const; + operator EdgeSegment *(); + operator const EdgeSegment *() const; + +private: + EdgeSegment *edgeSegment; + +}; + +} diff --git a/libs/msdf-atlas-gen/include/msdfgen/core/MSDFErrorCorrection.cpp b/libs/msdf-atlas-gen/include/msdfgen/core/MSDFErrorCorrection.cpp new file mode 100644 index 0000000..7918597 --- /dev/null +++ b/libs/msdf-atlas-gen/include/msdfgen/core/MSDFErrorCorrection.cpp @@ -0,0 +1,495 @@ + +#include "MSDFErrorCorrection.h" + +#include +#include "arithmetics.hpp" +#include "equation-solver.h" +#include "EdgeColor.h" +#include "bitmap-interpolation.hpp" +#include "edge-selectors.h" +#include "contour-combiners.h" +#include "ShapeDistanceFinder.h" +#include "generator-config.h" + +namespace msdfgen { + +#define ARTIFACT_T_EPSILON .01 +#define PROTECTION_RADIUS_TOLERANCE 1.001 + +#define CLASSIFIER_FLAG_CANDIDATE 0x01 +#define CLASSIFIER_FLAG_ARTIFACT 0x02 + +const double ErrorCorrectionConfig::defaultMinDeviationRatio = 1.11111111111111111; +const double ErrorCorrectionConfig::defaultMinImproveRatio = 1.11111111111111111; + +/// The base artifact classifier recognizes artifacts based on the contents of the SDF alone. +class BaseArtifactClassifier { +public: + inline BaseArtifactClassifier(double span, bool protectedFlag) : span(span), protectedFlag(protectedFlag) { } + /// Evaluates if the median value xm interpolated at xt in the range between am at at and bm at bt indicates an artifact. + inline int rangeTest(double at, double bt, double xt, float am, float bm, float xm) const { + // For protected texels, only consider inversion artifacts (interpolated median has different sign than boundaries). For the rest, it is sufficient that the interpolated median is outside its boundaries. + if ((am > .5f && bm > .5f && xm <= .5f) || (am < .5f && bm < .5f && xm >= .5f) || (!protectedFlag && median(am, bm, xm) != xm)) { + double axSpan = (xt-at)*span, bxSpan = (bt-xt)*span; + // Check if the interpolated median's value is in the expected range based on its distance (span) from boundaries a, b. + if (!(xm >= am-axSpan && xm <= am+axSpan && xm >= bm-bxSpan && xm <= bm+bxSpan)) + return CLASSIFIER_FLAG_CANDIDATE|CLASSIFIER_FLAG_ARTIFACT; + return CLASSIFIER_FLAG_CANDIDATE; + } + return 0; + } + /// Returns true if the combined results of the tests performed on the median value m interpolated at t indicate an artifact. + inline bool evaluate(double t, float m, int flags) const { + return (flags&2) != 0; + } +private: + double span; + bool protectedFlag; +}; + +/// The shape distance checker evaluates the exact shape distance to find additional artifacts at a significant performance cost. +template