This commit is contained in:
thomas committed 2026-10-18 14:06:40 +02:00
commit 2c2202e60d
2491 files changed
+1295990

No files matched your search

@@ -0,0 +1,28 @@
/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */
#include <fonts/ApplicationFontProvider.hpp>
#include <fonts/GeneratedFont.hpp>
#include <texts/TypedTextDatabase.hpp>
<% if save_flashreader? %>
touchgfx::FlashDataReader* ApplicationFontProvider::fontFlashReader = 0;
<% end %>
touchgfx::Font* ApplicationFontProvider::getFont(touchgfx::FontId typography)
{
<% if @typographies.empty? %>
return 0;
<% else %>
switch (typography)
{
<% @typographies.each_with_index do |typography, index| %>
case Typography::<%= typography.name.upcase %>:
// <%= typography.cpp_name %>_<%= typography.font_size %>_<%= typography.bpp %>bpp
return const_cast<touchgfx::Font*>(TypedTextDatabase::getFonts()[<%= get_font_index(index) %>]);
<% end %>
default:
return 0;
}
<% end %>
}
@@ -0,0 +1,59 @@
/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */
#ifndef TOUCHGFX_APPLICATIONFONTPROVIDER_HPP
#define TOUCHGFX_APPLICATIONFONTPROVIDER_HPP
#include <touchgfx/FontManager.hpp>
namespace touchgfx
{
class FlashDataReader;
}
struct Typography
{
<% @typographies.each_with_index do |typography, index| %>
static const touchgfx::FontId <%= typography.name.upcase %> = <%= index %>;
<% end %>
};
struct TypographyFontIndex
{
<% @typographies.each_with_index do |typography, index| %>
static const touchgfx::FontId <%= typography.name.upcase %> = <%= get_font_index(index) %>; <%= get_font_comment_space(index) %>// <%= get_font_comment(index) %>
<% end %>
static const uint16_t NUMBER_OF_FONTS = <%= get_max_font_index %>;
};
class ApplicationFontProvider : public touchgfx::FontProvider
{
public:
virtual touchgfx::Font* getFont(touchgfx::FontId typography);
<% if save_flashreader? %>
static void setFlashReader(touchgfx::FlashDataReader* flashReader)
{
fontFlashReader = flashReader;
}
static touchgfx::FlashDataReader* getFlashReader()
{
return fontFlashReader;
}
private:
static touchgfx::FlashDataReader* fontFlashReader;
<% else %>
static void setFlashReader(touchgfx::FlashDataReader* /*flashReader*/)
{
}
static touchgfx::FlashDataReader* getFlashReader()
{
return 0;
}
<% end %>
};
#endif // TOUCHGFX_APPLICATIONFONTPROVIDER_HPP
@@ -0,0 +1,55 @@
/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */
#include <fonts/CachedFont.hpp>
namespace touchgfx
{
const uint8_t* CachedFont::getPixelData(const GlyphNode* glyph) const
{
// If glyph is cached, then data is present just after the GlyphNode
if (FontCache::isCached(glyph))
{
const uint8_t* data = FontCache::getPixelData(glyph);
return data;
}
return flashFont->getPixelData(glyph);
}
const GlyphNode* CachedFont::getGlyph(Unicode::UnicodeChar unicode, const uint8_t*& pixelData, uint8_t& bitsPerPixel) const
{
// Look first in internal flash font
const GlyphNode* n = flashFont->find(unicode);
if ((n == 0) && (cache != 0))
{
// Now look in FontCache table
n = cache->getGlyph(unicode, fontId);
}
// Revert to normal behaviour if still not found
if (n == 0 && unicode != 0 && unicode != '\n')
{
Unicode::UnicodeChar fallbackChar = flashFont->getFallbackChar();
n = flashFont->find(fallbackChar);
if (n == 0)
{
n = cache->getGlyph(fallbackChar, fontId);
}
}
if (n != 0)
{
pixelData = getPixelData(n);
bitsPerPixel = getBitsPerPixel();
return n;
}
return (const GlyphNode*)0;
}
int8_t CachedFont::getKerning(Unicode::UnicodeChar prevChar, const GlyphNode* glyph) const
{
// Kerning is not supported by Font Caching
return 0;
}
} // namespace touchgfx
@@ -0,0 +1,93 @@
/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */
#ifndef TOUCHGFX_CACHEDFONT_HPP
#define TOUCHGFX_CACHEDFONT_HPP
#include <fonts/FontCache.hpp>
#include <fonts/GeneratedFont.hpp>
namespace touchgfx
{
class CachedFont : public GeneratedFont
{
public:
CachedFont(const struct touchgfx::BinaryFontData* data, FontId id, FontCache* _cache, const GeneratedFont* _flashFont)
: GeneratedFont(0, // GlyphNode*
data->numberOfGlyphs,
data->fontHeight,
data->baseline,
data->pixAboveTop,
data->pixBelowBottom,
data->bitsPerPixel,
data->byteAlignRow,
data->maxLeft,
data->maxRight,
0, // glyphDataPointer
0, // Kerning table not used for cached font
data->fallbackChar,
data->ellipsisChar,
0, // lsubTablePointer
0), // contextualFormsPointer
fontId(id),
cache(_cache),
flashFont(_flashFont)
{
}
CachedFont()
: GeneratedFont()
{
}
using GeneratedFont::getGlyph;
virtual const GlyphNode* getGlyph(Unicode::UnicodeChar unicode, const uint8_t*& pixelData, uint8_t& bitsPerPixel) const;
virtual const uint8_t* getPixelData(const GlyphNode* glyph) const;
virtual int8_t getKerning(Unicode::UnicodeChar prevChar, const GlyphNode* glyph) const;
void setFontCache(FontCache& cache);
FontId getFontId() const
{
return fontId;
}
virtual const uint16_t* getGSUBTable() const
{
if (gsubTable != 0)
{
return gsubTable;
}
return flashFont->getGSUBTable();
}
virtual void setGSUBTable(const uint16_t* table)
{
gsubTable = table;
}
virtual const FontContextualFormsTable* getContextualFormsTable() const
{
if (arabicTable != 0)
{
return arabicTable;
}
return flashFont->getContextualFormsTable();
}
virtual void setContextualFormsTable(const FontContextualFormsTable* table)
{
arabicTable = table;
}
private:
FontId fontId;
FontCache* cache;
const GeneratedFont* flashFont;
};
} // namespace touchgfx
#endif // TOUCHGFX_CACHEDFONT_HPP
@@ -0,0 +1,183 @@
/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */
#include <fonts/CompressedFontCache.hpp>
#include <touchgfx/hal/HAL.hpp>
#include <touchgfx/hal/Paint.hpp>
namespace touchgfx
{
LOCATION_PRAGMA_32("TouchGFX_CompressedFontCache")
uint32_t CompressedFontCache::bitmapFontCache[cacheWords] LOCATION_ATTRIBUTE_32("TouchGFX_CompressedFontCache");
uint8_t* CompressedFontCache::pixelsTop = (uint8_t*)CompressedFontCache::bitmapFontCache;
int CompressedFontCache::glyphsAllocated = 0;
int CompressedFontCache::cacheClearCounter = 0;
namespace
{
/* Read nibbles from a compressed data array */
struct NibReader
{
NibReader(const uint8_t* buffer)
: buffer(buffer), byteIndex(0), low(true)
{}
const uint8_t* buffer;
int byteIndex;
bool low;
uint8_t getNext()
{
if (low)
{
const uint8_t val = buffer[byteIndex] & 0xF;
low = false;
return val;
}
const uint8_t val = buffer[byteIndex] >> 4;
low = true;
byteIndex++;
return val;
}
};
struct NibWriter
{
NibWriter(uint8_t* buffer, int size)
: buffer(buffer), bufferEnd(buffer + size), low(true) {}
uint8_t* buffer;
uint8_t* bufferEnd;
int low;
void put(uint8_t v)
{
if (low)
{
*buffer = v;
low = false;
return;
}
*buffer |= (v << 4);
buffer++;
low = true;
}
bool eof() { return buffer >= bufferEnd; }
};
} // anonymous namespace
uint8_t* CompressedFontCache::decompressGlyph(uint8_t* pixelsTop, const GlyphNode* glyphNode, const uint8_t* compressedData)
{
const int byteSize = (glyphNode->width() + 1) / 2 * glyphNode->height();
NibWriter writer(pixelsTop, byteSize);
NibReader reader(compressedData);
uint8_t* const pixelsEnd = pixelsTop + byteSize;
const int algorithm = glyphNode->dataOffset >> 30;
if (algorithm == 1)
{
// RLE1
while (!writer.eof())
{
const uint8_t value = reader.getNext();
if (value == 0)
{
int zeroCount = reader.getNext();
while (zeroCount)
{
writer.put(0);
zeroCount--;
}
}
writer.put(value);
}
}
else if (algorithm == 2)
{
// RLE2
while (!writer.eof())
{
const uint8_t value = reader.getNext();
if (value == 0 || value == 0xF)
{
int count = reader.getNext();
while (count)
{
writer.put(value);
count--;
}
}
writer.put(value);
}
}
else
{
// Uncompressed
while (pixelsTop < pixelsEnd)
{
*pixelsTop++ = *compressedData++;
}
}
return pixelsEnd;
}
void CompressedFontCache::clearCache()
{
// Wait for DMA2D/GPU2D to finish drawing
HAL::getInstance()->lockUnlockFrameBuffer();
glyphsAllocated = 0;
cacheClearCounter++;
pixelsTop = (uint8_t*)bitmapFontCache;
}
void CompressedFontCache::unableToCache(const GlyphNode* glyphNode, int byteSize)
{
while(1);
}
const uint8_t* CompressedFontCache::hasCachedGlyph(const GlyphNode* glyphNode)
{
const BitmapFontCacheKey* end = (const BitmapFontCacheKey*)&bitmapFontCache[cacheWords];
const BitmapFontCacheKey* first = end - glyphsAllocated;
while (first < end)
{
if (first->glyphNode == glyphNode)
{
return first->pixels;
}
first++;
}
return 0;
}
const uint8_t* CompressedFontCache::cacheGlyph(const GlyphNode* glyph, const uint8_t* compressedData)
{
const int byteSize = (glyph->width() + 1) / 2 * glyph->height();
if (byteSize + 8 > cacheSizeBytes)
{
unableToCache(glyph, byteSize);
return 0;
}
const BitmapFontCacheKey* end = (const BitmapFontCacheKey*)&bitmapFontCache[cacheWords];
if (pixelsTop + byteSize + 8 > (const uint8_t*)&end[-glyphsAllocated])
{
// Unable to fit glyph in data, clear cache
clearCache();
}
const uint8_t* glyphData = pixelsTop;
pixelsTop = decompressGlyph(pixelsTop, glyph, compressedData);
// Flush the cache lines, must be 32byte aligned
uint8_t* rowStart = reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(glyphData) & ~0x1F);
int alignmentOffset = glyphData - rowStart;
paint::flushLine((uint32_t*)rowStart, byteSize + alignmentOffset);
paint::invalidateTextureCache();
glyphsAllocated++;
BitmapFontCacheKey* key = const_cast<BitmapFontCacheKey*>(end - glyphsAllocated);
key->glyphNode = glyph;
key->pixels = glyphData;
return glyphData;
}
} // namespace touchgfx
@@ -0,0 +1,42 @@
/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */
#ifndef TOUCHGFX_COMPRESSEDFONTCACHE_HPP
#define TOUCHGFX_COMPRESSEDFONTCACHE_HPP
#include <touchgfx/hal/Types.hpp>
#include <touchgfx/Font.hpp>
namespace touchgfx
{
class CompressedFontCache
{
public:
static void clearCache();
static const uint8_t* hasCachedGlyph(const GlyphNode* glyphNode);
static void unableToCache(const GlyphNode* glyphNode, int byteSize);
static const uint8_t* cacheGlyph(const GlyphNode* glyph, const uint8_t* compressedData);
static int usedMemory()
{
return pixelsTop - (uint8_t*)bitmapFontCache + glyphsAllocated * sizeof(BitmapFontCacheKey);
}
static int cacheClearCounter;
private:
static uint8_t* decompressGlyph(uint8_t* pixelsTop, const GlyphNode* glyphNode, const uint8_t* compressedData);
struct BitmapFontCacheKey
{
const void* glyphNode;
const uint8_t* pixels;
};
static const int cacheSizeBytes = <%= get_cache_size %>;
static const int cacheWords = (cacheSizeBytes + 3) / 4;
static uint32_t bitmapFontCache[cacheWords];
static uint8_t* pixelsTop;
static int glyphsAllocated;
};
} // namespace touchgfx
#endif // TOUCHGFX_COMPRESSEDFONTCACHE_HPP
@@ -0,0 +1,263 @@
/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */
#include <fonts/CompressedUnmappedFontCache.hpp>
#include <touchgfx/hal/HAL.hpp>
#include <touchgfx/hal/Paint.hpp>
namespace touchgfx
{
LOCATION_PRAGMA_32("TouchGFX_CompressedUnmappedFontCache")
uint32_t CompressedUnmappedFontCache::bitmapFontCache[cacheWords] LOCATION_ATTRIBUTE_32("TouchGFX_CompressedUnmappedFontCache");
uint8_t* CompressedUnmappedFontCache::pixelsTop = (uint8_t*)CompressedUnmappedFontCache::bitmapFontCache;
int CompressedUnmappedFontCache::glyphsAllocated = 0;
int CompressedUnmappedFontCache::cacheClearCounter = 0;
namespace
{
/* Read nibbles from compressed data in unmapped flash. Must read through reader object */
struct NibUnmapReader
{
NibUnmapReader(const uint8_t* srcAddress, touchgfx::FlashDataReader* reader)
: address(srcAddress), reader(reader), byteIndex(0), low(true)
{
address += bufferSize;
reader->copyData(srcAddress, buffer, sizeof(buffer));
}
const uint8_t* address; //next reading address
touchgfx::FlashDataReader* reader;
static const int bufferSize = 16;
uint8_t buffer[bufferSize];
uint8_t value;
int byteIndex;
bool low;
uint8_t getNext()
{
if (low)
{
value = buffer[byteIndex];
const uint8_t val = value & 0xF;
low = false;
return val;
}
const uint8_t val = value >> 4;
low = true;
if (byteIndex == bufferSize - 1)
{
reader->copyData(address, buffer, bufferSize);
address += bufferSize;
byteIndex = 0;
}
else
{
byteIndex++;
}
return val;
}
};
struct NibWriter
{
NibWriter(uint8_t* buffer, int size)
: buffer(buffer), bufferEnd(buffer + size), low(true) {}
uint8_t* buffer;
uint8_t* bufferEnd;
int low;
void put(uint8_t v)
{
if (low)
{
*buffer = v;
low = false;
return;
}
*buffer |= (v << 4);
buffer++;
low = true;
}
bool eof() { return buffer >= bufferEnd; }
};
} // anonymous namespace
uint8_t* CompressedUnmappedFontCache::decompressGlyph(uint8_t* pixelsTop, const GlyphNode* glyphNode, const uint8_t* compressedData, touchgfx::FlashDataReader* flashReader)
{
const int byteSize = (glyphNode->width() + 1) / 2 * glyphNode->height();
NibWriter writer(pixelsTop, byteSize);
NibUnmapReader reader(compressedData, flashReader);
uint8_t* const pixelsEnd = pixelsTop + byteSize;
const int algorithm = glyphNode->dataOffset >> 30;
if (algorithm == 1)
{
// RLE1
while (!writer.eof())
{
const uint8_t value = reader.getNext();
if (value == 0)
{
int zeroCount = reader.getNext();
while (zeroCount)
{
writer.put(0);
zeroCount--;
}
}
writer.put(value);
}
}
else if (algorithm == 2)
{
// RLE2
while (!writer.eof())
{
const uint8_t value = reader.getNext();
if (value == 0 || value == 0xF)
{
int count = reader.getNext();
while (count)
{
writer.put(value);
count--;
}
}
writer.put(value);
}
}
else
{
// Uncompressed
flashReader->copyData(compressedData, pixelsTop, byteSize);
}
return pixelsEnd;
}
void CompressedUnmappedFontCache::clearCache()
{
// Wait for DMA2D/GPU2D to finish drawing
HAL::getInstance()->lockUnlockFrameBuffer();
glyphsAllocated = 0;
cacheClearCounter++;
pixelsTop = (uint8_t*)bitmapFontCache;
}
void CompressedUnmappedFontCache::unableToCache(const GlyphNode* glyphNode, int byteSize)
{
while(1);
}
const GlyphNode* CompressedUnmappedFontCache::hasCachedGlyphNode(const GlyphNode* glyphNode)
{
const BitmapFontCacheKey* end = (const BitmapFontCacheKey*)&bitmapFontCache[cacheWords];
const BitmapFontCacheKey* first = end - glyphsAllocated;
while (first < end)
{
if (first->glyphNodeSrc == glyphNode)
{
return &first->glyphNodeCopy;
}
first++;
}
return 0;
}
const GlyphNode* CompressedUnmappedFontCache::hasCachedGlyphData(const GlyphNode* glyphNode, const uint8_t*& pixelData)
{
const BitmapFontCacheKey* end = (const BitmapFontCacheKey*)&bitmapFontCache[cacheWords];
const BitmapFontCacheKey* first = end - glyphsAllocated;
while (first < end)
{
if (first->glyphNodeSrc == glyphNode)
{
//Pixel data can be zero!
pixelData = first->pixels;
return &first->glyphNodeCopy;
}
first++;
}
return 0;
}
GlyphNode* CompressedUnmappedFontCache::cacheGlyphNode(const GlyphNode* glyph)
{
const BitmapFontCacheKey* end = (const BitmapFontCacheKey*)&bitmapFontCache[cacheWords];
if (pixelsTop + sizeof(BitmapFontCacheKey) > (const uint8_t*)&end[-glyphsAllocated])
{
// Unable to fit GlyphNode in data, clear cache
clearCache();
}
glyphsAllocated++;
BitmapFontCacheKey* key = const_cast<BitmapFontCacheKey*>(end - glyphsAllocated);
key->glyphNodeSrc = glyph;
key->pixels = 0;
return &key->glyphNodeCopy;
}
const uint8_t* CompressedUnmappedFontCache::cacheGlyphData(const GlyphNode* glyph, const uint8_t* compressedData, touchgfx::FlashDataReader* reader)
{
// Search keys for glyph
BitmapFontCacheKey* end = (BitmapFontCacheKey*)&bitmapFontCache[cacheWords];
BitmapFontCacheKey* it = end - glyphsAllocated;
while (it < end)
{
if (it->glyphNodeSrc == glyph)
{
break;
}
it++;
}
if (it == end)
{
// broken invariant!
return 0;
}
const int byteSize = (it->glyphNodeCopy.width() + 1) / 2 * it->glyphNodeCopy.height();
// Check if pixels can fit in cache
if (pixelsTop + byteSize > (const uint8_t*)&end[-glyphsAllocated])
{
// Can it fit in a clean cache?
if (byteSize + sizeof(BitmapFontCacheKey) > cacheSizeBytes)
{
unableToCache(glyph, byteSize);
return 0;
}
// Unable to fit pixel in data, must clear cache
// Save address of GlyphNode in cache
const GlyphNode* cachedGn = &it->glyphNodeCopy;
// Now clear (reset pointer)
clearCache();
// Insert glyph node again
glyphsAllocated++;
it = const_cast<BitmapFontCacheKey*>(end - 1);
it->glyphNodeSrc = glyph;
it->pixels = 0;
// Copy GlyphNode content from previous position in cache
it->glyphNodeCopy = *cachedGn;
}
//Now decompress data
const uint8_t* glyphData = pixelsTop;
pixelsTop = decompressGlyph(pixelsTop, &it->glyphNodeCopy, compressedData, reader);
// Flush the cache lines, must be 32byte aligned
uint8_t* rowStart = reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(glyphData) & ~0x1F);
int alignmentOffset = glyphData - rowStart;
paint::flushLine((uint32_t*)rowStart, byteSize + alignmentOffset);
paint::invalidateTextureCache();
it->pixels = glyphData;
return glyphData;
}
} // namespace touchgfx
@@ -0,0 +1,46 @@
/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */
#ifndef TOUCHGFX_COMPRESSEDUNMAPPEDFONTCACHE_HPP
#define TOUCHGFX_COMPRESSEDUNMAPPEDFONTCACHE_HPP
#include <touchgfx/hal/FlashDataReader.hpp>
#include <touchgfx/hal/Types.hpp>
#include <touchgfx/Font.hpp>
namespace touchgfx
{
class CompressedUnmappedFontCache
{
public:
static void clearCache();
static const GlyphNode* hasCachedGlyphNode(const GlyphNode* glyphNode);
static const GlyphNode* hasCachedGlyphData(const GlyphNode* glyphNode, const uint8_t*& pixelData);
static void unableToCache(const GlyphNode* glyphNode, int byteSize);
static GlyphNode* cacheGlyphNode(const GlyphNode* glyph);
static const uint8_t* cacheGlyphData(const GlyphNode* glyph, const uint8_t* compressedData, touchgfx::FlashDataReader* reader);
static int usedMemory()
{
return pixelsTop - (uint8_t*)bitmapFontCache + glyphsAllocated * sizeof(BitmapFontCacheKey);
}
static int cacheClearCounter;
private:
static uint8_t* decompressGlyph(uint8_t* pixelsTop, const GlyphNode* glyphNode, const uint8_t* compressedData, touchgfx::FlashDataReader* reader);
struct BitmapFontCacheKey
{
const void* glyphNodeSrc;
GlyphNode glyphNodeCopy;
const uint8_t* pixels;
};
static const int cacheSizeBytes = <%= get_cache_size %>;
static const int cacheWords = (cacheSizeBytes + 3) / 4;
static uint32_t bitmapFontCache[cacheWords];
static uint8_t* pixelsTop;
static int glyphsAllocated;
};
} // namespace touchgfx
#endif // TOUCHGFX_COMPRESSEDUNMAPPEDFONTCACHE_HPP
@@ -0,0 +1,424 @@
/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */
#include <cstring>
#include <touchgfx/TextProvider.hpp>
#include <touchgfx/Utils.hpp>
#include <fonts/CachedFont.hpp>
#include <fonts/FontCache.hpp>
#include <texts/TypedTextDatabase.hpp>
namespace touchgfx
{
FontCache::FontCache()
: memorySize(0), memory(0), top(0), gsubStart(0), reader(0)
{
}
void FontCache::clear(bool keepGsubOrContextTable /* = false */)
{
memset(fontTable, 0, sizeof(fontTable));
// Top is beginning of memory, no glyphs are cached yet
top = memory;
if (!keepGsubOrContextTable)
{
// gsubStart points to end of memory (nothing loaded yet)
gsubStart = memory + memorySize;
// Round down to 32bit address
gsubStart = (uint8_t*)((uintptr_t)gsubStart & ~(sizeof(void*) - 1));
}
}
void FontCache::setMemory(uint8_t* _memory, uint32_t size)
{
memory = _memory;
memorySize = size;
clear();
}
void FontCache::setReader(FontDataReader* _reader)
{
reader = _reader;
}
const GlyphNode* FontCache::getGlyph(Unicode::UnicodeChar unicode, FontId font) const
{
GlyphNode* g = (GlyphNode*)fontTable[font].first;
while (g)
{
if (g->unicode == unicode)
{
return g;
}
GlyphNode** next = (GlyphNode**)((uint8_t*)g + SizeGlyphNode);
g = *next;
}
return 0;
}
void FontCache::open()
{
if (reader)
{
reader->open();
}
}
void FontCache::close()
{
if (reader)
{
reader->close();
}
}
void FontCache::initializeCachedFont(TypedText t, CachedFont* font, bool loadGsubOrContextTable /*= false*/)
{
// Get font index from typed text
FontId fontId = t.getFontId();
// Reset to start of file
open();
setPosition(0);
assert(sizeof(touchgfx::BinaryFontData) < MAX_BUFFER_SIZE);
readData(buffer, sizeof(touchgfx::BinaryFontData));
const struct touchgfx::BinaryFontData* binaryFontData = reinterpret_cast<const struct touchgfx::BinaryFontData*>(buffer);
const Font** flashFonts = TypedTextDatabase::getFonts();
const GeneratedFont* flashFont = static_cast<const GeneratedFont*>(flashFonts[fontId]);
*font = CachedFont(reinterpret_cast<const struct touchgfx::BinaryFontData*>(buffer), fontId, this, flashFont);
if (loadGsubOrContextTable && (binaryFontData->offsetToGSUB != 0))
{
setPosition(binaryFontData->offsetToGSUB);
const uint32_t sizeOfGSUB = (binaryFontData->offsetToArabicTable != 0 ? binaryFontData->offsetToArabicTable : binaryFontData->sizeOfFontData) - binaryFontData->offsetToGSUB;
if (top + sizeOfGSUB < gsubStart) // Room for this GSUB table
{
// Round down to aligned address
uint8_t* const gsubPosition = (uint8_t*)((uintptr_t)(gsubStart - sizeOfGSUB) & ~(sizeof(void*) - 1));
readData(gsubPosition, sizeOfGSUB);
font->setGSUBTable(reinterpret_cast<uint16_t*>(gsubPosition));
gsubStart = gsubPosition;
}
else
{
font->setGSUBTable(0);
}
}
if (loadGsubOrContextTable && (binaryFontData->offsetToArabicTable != 0))
{
setPosition(binaryFontData->offsetToArabicTable);
const uint32_t sizeTableData = binaryFontData->sizeOfFontData - binaryFontData->offsetToArabicTable;
if (top + sizeTableData + sizeof(FontContextualFormsTable) < gsubStart) // Room for the ContextualFormsTables
{
// Allocate FontContextualFormsTable first
gsubStart -= sizeof(FontContextualFormsTable);
// Round down to 32bit address
gsubStart = (uint8_t*)((uintptr_t)gsubStart & ~(sizeof(void*) - 1));
FontContextualFormsTable* table = (FontContextualFormsTable*)gsubStart;
font->setContextualFormsTable(table);
gsubStart -= sizeTableData;
readData(gsubStart, sizeTableData);
// Set pointers in table
const uint16_t* const base = (const uint16_t*)gsubStart;
// First elements in binary font are offsets to arrays in 16bit words
table->contextualForms4Long = (FontContextualFormsTable::arrayOf5UnicodesPtr)(base + base[0]);
table->contextualForms3Long = (FontContextualFormsTable::arrayOf5UnicodesPtr)(base + base[1]);
table->contextualForms2Long = (FontContextualFormsTable::arrayOf5UnicodesPtr)(base + base[2]);
table->contextualForms0621_063a = (FontContextualFormsTable::arrayOf4UnicodesPtr)(base + base[3]);
table->contextualForms0641_064a = (FontContextualFormsTable::arrayOf4UnicodesPtr)(base + base[4]);
table->contextualForms06XX = (FontContextualFormsTable::arrayOf5UnicodesPtr)(base + base[5]);
table->contextualForms4LongSize = base[6];
table->contextualForms3LongSize = base[7];
table->contextualForms2LongSize = base[8];
table->contextualForms06XXSize = base[9];
}
else
{
font->setContextualFormsTable(0);
}
}
close();
}
bool FontCache::cacheString(TypedText t, const Unicode::UnicodeChar* string)
{
open();
if (!createSortedString(string))
{
close();
return false;
}
const bool result = cacheSortedString(t);
close();
return result;
}
bool FontCache::cacheLigatures(CachedFont* font, TypedText t, const Unicode::UnicodeChar* string)
{
open();
if (!createSortedLigatures(font, t, string, 0, 0))
{
close();
return false;
}
const bool result = cacheSortedString(t);
close();
return result;
}
bool FontCache::cacheSortedString(TypedText t)
{
setPosition(8); // Skip font index and size
uint32_t glyphNodeOffset;
uint32_t dummy;
readData(&glyphNodeOffset, sizeof(uint32_t)); // offsetToTable
readData(&dummy, sizeof(uint32_t)); // offsetToKerning
readData(&glyphDataOffset, sizeof(uint32_t)); // offsetToGlyphs
readData(&dummy, sizeof(uint32_t)); // offsetToGlyphs
readData(&dummy, sizeof(uint32_t)); // offsetToArabicTable
readData(&numGlyphs, sizeof(uint16_t)); // numberOfGlyphs
FontId fontId = t.getFontId(); // Get font index from typed text
bpp = t.getFont()->getBitsPerPixel(); // Get BPP from standard font
byteAlignRow = t.getFont()->getByteAlignRow(); // Get ByteAlign from font
setPosition(glyphNodeOffset); // Go to glyph nodes for font
currentFileGlyphNumber = 0;
currentFileGlyphNode.unicode = 0; // Force reading of first glyph
const Unicode::UnicodeChar* string = sortedString;
Unicode::UnicodeChar last = 0;
GlyphNode* firstNewGlyph = 0;
bool outOfMemory = false;
while (*string)
{
Unicode::UnicodeChar ch = *string;
if (ch != last)
{
if (!contains(ch, fontId))
{
insert(ch, fontId, outOfMemory);
if (outOfMemory)
{
break;
}
if (firstNewGlyph == 0)
{
firstNewGlyph = (GlyphNode*)fontTable[fontId].last;
}
}
}
last = ch;
string++;
}
cacheData(firstNewGlyph);
return !outOfMemory;
}
bool FontCache::contains(Unicode::UnicodeChar unicode, FontId font) const
{
GlyphNode* g = (GlyphNode*)fontTable[font].first;
while (g)
{
if (g->unicode == unicode)
{
return true;
}
GlyphNode** next = (GlyphNode**)((uint8_t*)g + SizeGlyphNode);
g = *next;
}
return false;
}
void FontCache::insert(Unicode::UnicodeChar unicode, FontId font, bool& outOfMemory)
{
// Insert new glyphnode and glyph after last for font.
uint8_t* oldTop = top;
top = copyGlyph(top, unicode, font, outOfMemory);
if (top == oldTop)
{
return;
}
if (fontTable[font].last == 0)
{
// First glyph
fontTable[font].first = oldTop;
fontTable[font].last = oldTop;
}
else
{
// Set next pointer of old last glyph
uint8_t** old_next = (uint8_t**)(fontTable[font].last + SizeGlyphNode);
*old_next = oldTop;
// Save new glyph as last glyph
fontTable[font].last = oldTop;
}
}
uint8_t* FontCache::copyGlyph(uint8_t* top, Unicode::UnicodeChar unicode, FontId font, bool& outOfMemory)
{
while (currentFileGlyphNumber < numGlyphs && currentFileGlyphNode.unicode < unicode)
{
readData(&currentFileGlyphNode, sizeof(GlyphNode));
currentFileGlyphNumber++;
}
if (currentFileGlyphNode.unicode != unicode)
{
// GlyphNode not found
return top;
}
// GlyphNode found
uint32_t glyphSize = getGlyphSize(&currentFileGlyphNode);
const int alignment = sizeof(void*);
glyphSize = (glyphSize + (alignment - 1)) & ~(alignment - 1);
uint32_t requiredMem = SizeGlyphNode + sizeof(void*) + glyphSize; // GlyphNode + next ptr + glyph
// Is space available before sortedString
if (top + requiredMem > (uint8_t*)sortedString)
{
outOfMemory = true;
return top;
}
*(GlyphNode*)top = currentFileGlyphNode;
// Clear next pointer
uint8_t** next = (uint8_t**)(top + SizeGlyphNode);
*next = 0;
top += requiredMem;
return top;
}
void FontCache::cacheData(GlyphNode* first)
{
GlyphNode* gn = first;
while (gn)
{
uint8_t* p = (uint8_t*)gn;
if (gn->dataOffset != 0xFFFFFFFF)
{
p += SizeGlyphNode;
// Next pointer
p += sizeof(void*);
// Seek and copy
setPosition(glyphDataOffset + gn->dataOffset);
readData(p, getGlyphSize(gn));
// Mark glyphNode as cached
gn->dataOffset = 0xFFFFFFFF;
}
GlyphNode** next = (GlyphNode**)((uint8_t*)gn + SizeGlyphNode);
gn = *next;
}
}
bool FontCache::createSortedString(const Unicode::UnicodeChar* string)
{
int length = Unicode::strlen(string);
// Sorted string is allocated at end of buffer
sortedString = (Unicode::UnicodeChar*)(gsubStart - (length + 1) * 2);
if ((uint8_t*)sortedString < top)
{
// Unable to allocate string buffer in end of memory
return false;
}
int n = 0;
Unicode::UnicodeChar* uc = sortedString;
while (*string)
{
*uc++ = *string++;
n++;
}
*uc = 0;
return sortSortedString(n);
}
bool FontCache::createSortedLigatures(CachedFont* font, TypedText t, const Unicode::UnicodeChar* string, ...)
{
va_list pArg;
va_start(pArg, string);
TextProvider tp;
tp.initialize(string, pArg, font->getGSUBTable(), font->getContextualFormsTable());
va_end(pArg);
Unicode::UnicodeChar ligature;
sortedString = (Unicode::UnicodeChar*)(gsubStart);
if ((uint8_t*)(sortedString - 1) < top)
{
return false;
}
*--sortedString = 0;
int n = 0;
while ((ligature = tp.getNextLigature(t.getTextDirection())) != 0)
{
if ((uint8_t*)(sortedString - 1) < top)
{
return false;
}
*--sortedString = ligature;
n++;
}
return sortSortedString(n);
}
bool FontCache::sortSortedString(int n)
{
Unicode::UnicodeChar* uc = sortedString;
for (int i = 0; i < n - 1; i++)
{
bool swapped = false;
for (int j = 0; j < n - i - 1; j++)
{
if (uc[j] > uc[j + 1])
{
Unicode::UnicodeChar temp = uc[j];
uc[j] = uc[j + 1];
uc[j + 1] = temp;
swapped = true;
}
}
// If no two elements were swapped by inner loop, then break
if (!swapped)
{
break;
}
}
return true;
}
void FontCache::setPosition(uint32_t position)
{
if (reader)
{
reader->setPosition(position);
}
}
void FontCache::readData(void* out, uint32_t numberOfBytes)
{
if (reader)
{
reader->readData(out, numberOfBytes);
}
}
} // namespace touchgfx
@@ -0,0 +1,112 @@
/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */
#ifndef TOUCHGFX_FONTCACHE_HPP
#define TOUCHGFX_FONTCACHE_HPP
#include <touchgfx/Font.hpp>
#include <touchgfx/TypedText.hpp>
#include <fonts/ApplicationFontProvider.hpp>
namespace touchgfx
{
class CachedFont;
class FontDataReader
{
public:
virtual ~FontDataReader()
{
}
virtual void open() = 0;
virtual void close() = 0;
virtual void setPosition(uint32_t position) = 0;
virtual void readData(void* out, uint32_t numberOfBytes) = 0;
};
class FontCache
{
public:
FontCache();
void setReader(FontDataReader* reader);
void clear(bool keepGsubOrContextTable = false);
void setMemory(uint8_t* memory, uint32_t size);
void initializeCachedFont(TypedText t, CachedFont* font, bool loadGsubOrContextTable = false);
bool cacheString(TypedText t, const Unicode::UnicodeChar* string);
bool cacheLigatures(CachedFont* font, TypedText t, const Unicode::UnicodeChar* string);
const GlyphNode* getGlyph(Unicode::UnicodeChar unicode, FontId font) const;
uint32_t getMemoryUsage()
{
return memorySize - (gsubStart - top);
}
void open();
void close();
static inline const uint8_t* getPixelData(const GlyphNode* glyph)
{
return ((const uint8_t*)glyph) + SizeGlyphNode + sizeof(void*);
}
static inline bool isCached(const GlyphNode* g)
{
return g->dataOffset == 0xFFFFFFFF;
}
private:
static const uint32_t SizeGlyphNode = 16;
bool contains(Unicode::UnicodeChar unicode, FontId font) const;
void insert(Unicode::UnicodeChar unicode, FontId font, bool& outOfMemory);
uint8_t* copyGlyph(uint8_t* top, Unicode::UnicodeChar unicode, FontId font, bool& outOfMemory);
void cacheData(GlyphNode* first);
bool cacheSortedString(TypedText t);
bool createSortedString(const Unicode::UnicodeChar* string);
bool createSortedLigatures(CachedFont* font, TypedText t, const Unicode::UnicodeChar* string, ...);
bool sortSortedString(int n);
void setPosition(uint32_t position);
void readData(void* out, uint32_t numberOfBytes);
uint32_t getGlyphSize(const GlyphNode* gn)
{
if (byteAlignRow)
{
// Round up line width to byte boundary and multiply by height
return (gn->width() * bpp + 7) / 8 * gn->height();
}
// Calculate number of pixels and round up to byte bounday
return (gn->width() * gn->height() * bpp + 7) / 8;
}
struct
{
uint8_t* first; // First GlyphNode, glyph in cache;
uint8_t* last; // Last GlyphNode, glyph in cache;
} fontTable[MAX(TypographyFontIndex::NUMBER_OF_FONTS, 1)];
uint32_t memorySize;
uint8_t* memory; // Start of memory
uint8_t* top; // First unused byte
uint8_t* gsubStart; // First address of GSUB tables, allocated in the end of the cache
FontDataReader* reader;
Unicode::UnicodeChar* sortedString;
// Must be bigger than BinaryFontData
static const uint32_t MAX_BUFFER_SIZE = 64;
char buffer[MAX_BUFFER_SIZE];
uint32_t glyphDataOffset;
uint16_t numGlyphs;
uint16_t currentFileGlyphNumber;
uint8_t bpp;
uint8_t byteAlignRow;
GlyphNode currentFileGlyphNode;
};
} // namespace touchgfx
#endif // TOUCHGFX_FONTCACHE_HPP
@@ -0,0 +1,310 @@
/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */
#include <fonts/GeneratedFont.hpp>
#include <fonts/CompressedFontCache.hpp>
#include <math.h>
#include <stddef.h>
namespace touchgfx
{
GeneratedFont::GeneratedFont(const GlyphNode* glyphs, uint16_t numGlyphs, uint16_t height, uint16_t baseline, uint8_t pixAboveTop, uint8_t pixBelowBottom, uint8_t bitsPerPixel, uint8_t byteAlignRow, uint8_t maxLeft, uint8_t maxRight, const uint8_t* const* glyphDataInternalFlash, const KerningNode* kerningList, const Unicode::UnicodeChar fallbackChar, const Unicode::UnicodeChar ellipsisChar, const uint16_t* const gsubData, const FontContextualFormsTable* formsTable)
: ConstFont(glyphs, numGlyphs, height, baseline, pixAboveTop, pixBelowBottom, bitsPerPixel, byteAlignRow, maxLeft, maxRight, fallbackChar, ellipsisChar),
glyphData(glyphDataInternalFlash),
kerningData(kerningList),
gsubTable(gsubData),
arabicTable(formsTable)
{
}
const uint8_t* GeneratedFont::getPixelData(const GlyphNode* glyph) const
{
// Read dataOffset as 16bit, as it may be unaligned
volatile const uint16_t* dataOffset = (const uint16_t*)((const uint8_t*)glyph + offsetof(GlyphNode, dataOffset));
uint32_t offset = dataOffset[0];
offset |= dataOffset[1] << 16;
const uint8_t* const* table = (const uint8_t* const*)glyphData;
const uint8_t* pixels = table[glyph->unicode / 2048];
return pixels + offset;
}
int8_t GeneratedFont::getKerning(Unicode::UnicodeChar prevChar, const GlyphNode* glyph) const
{
if (!glyph || glyph->kerningTableSize == 0)
{
return 0;
}
const KerningNode* kerndata = kerningData + glyph->kerningTablePos();
for (uint16_t i = glyph->kerningTableSize; i > 0; i--, kerndata++)
{
if (prevChar == kerndata->unicodePrevChar)
{
return kerndata->distance;
}
if (prevChar < kerndata->unicodePrevChar)
{
break;
}
}
return 0;
}
const GlyphNode* FusedFont::getGlyph(Unicode::UnicodeChar unicode, const uint8_t*& pixelData, uint8_t& bitsPerPixel) const
{
if (unicode < 0xAC00 || unicode > 0xD7A3)
{
return GeneratedFont::getGlyph(unicode, pixelData, bitsPerPixel);
}
else
{
fusedNode.unicode = unicode;
bitsPerPixel = 1;
pixelData = 0;
return const_cast<const GlyphNode*>(&fusedNode);
}
}
GlyphNode GeneratedVectorFont::glyphNode; // Static member
GeneratedVectorFont::GeneratedVectorFont(uint16_t baseline, float scale, const VectorFontNode* vectorGlyphs, const VectorFontData& fontData,
const uint16_t* const* glyphData, const VectorKerningNode* kerning,
const uint16_t* const gsubData, const FontContextualFormsTable* formsTable)
: Font((int)ceilf((fontData.maxBelowBottom + fontData.baselineHeight) * scale), baseline,
(uint8_t)ceilf(fontData.maxAboveTop * scale),
(uint8_t)ceilf(fontData.maxBelowBottom * scale), 0 /*bitsPerPixel*/, 0 /*byteAlignRow*/,
(uint8_t)ceilf(fontData.maxLeft * scale),
(uint8_t)ceilf(fontData.maxRight * scale),
fontData.fallbackChar,
fontData.ellipsisChar),
scaleFactor(scale), vectorNodes(vectorGlyphs), vectorTable(glyphData), kerningTable(kerning),
arabicTable(formsTable), numberOfGlyphs(fontData.numberOfGlyphs)
{
if (gsubData[0] != 0)
{
gsubTable = gsubData;
}
else
{
gsubTable = 0;
}
}
const GlyphNode* GeneratedVectorFont::getGlyph(Unicode::UnicodeChar unicode) const
{
const VectorFontNode* const node = find(unicode);
if (node == 0)
{
return 0;
}
else
{
return getGlyphNode(node);
}
}
const GlyphNode* GeneratedVectorFont::getGlyph(Unicode::UnicodeChar unicode, const uint8_t*& pixelData, uint8_t& bitsPerPixel) const
{
const VectorFontNode* const node = find(unicode);
if (node == 0)
{
return 0;
}
else
{
const uint16_t* contourData = &(vectorTable[unicode / 2048][node->dataOffset]);
pixelData = (const uint8_t*)contourData;
bitsPerPixel = 0xFF;
return getGlyphNode(node);
}
}
const GlyphNode* GeneratedVectorFont::getGlyphNode(const VectorFontNode* node) const
{
glyphNode.dataOffset = 0;
uint8_t flags = 0;
//Convert data from VectorFontNode to glyphNode using scaleFactor
glyphNode.unicode = node->unicode;
const int width = (node->width == 0) ? 0 : ((int)ceilf(node->width * scaleFactor) + 1);
glyphNode._width = width; // Save low 8 bit
// Set flags bits for values above 8 bit
if (width > 255)
{
flags |= GLYPH_DATA_WIDTH_BIT8;
}
const int height = (int)ceilf(node->height * scaleFactor);
glyphNode._height = height;
if (height > 255)
{
flags |= GLYPH_DATA_HEIGHT_BIT8;
}
const int top = (int)ceilf(node->top * scaleFactor);
glyphNode._top = top;
if (top > 255)
{
flags |= ((top & 0x300) >> 3); // GLYPH_DATA_TOP_BIT8 + 9
}
const int left = (int)(node->left * scaleFactor);
glyphNode.left = left;
const int advance = (int)(node->advance * scaleFactor + 0.5f);
glyphNode._advance = advance;
if (advance > 255)
{
flags |= GLYPH_DATA_ADVANCE_BIT8;
}
glyphNode._kerningTablePos = (uint8_t)node->kerningTablePos;
if (node->kerningTablePos > 255)
{
flags |= (node->kerningTablePos >> 8) & GLYPH_DATA_KERNINGTABLEPOS_BIT8_10;
}
glyphNode.kerningTableSize = node->kerningTableSize;
glyphNode.flags = flags;
return &glyphNode;
}
int8_t GeneratedVectorFont::getKerning(Unicode::UnicodeChar prevChar, const GlyphNode* glyph) const
{
if (!glyph || glyph->kerningTableSize == 0)
{
return 0;
}
const VectorKerningNode* kerndata = kerningTable + glyph->kerningTablePos();
for (uint16_t i = glyph->kerningTableSize; i > 0; i--, kerndata++)
{
if (prevChar == kerndata->unicodePrevChar)
{
const float scaledDistance = kerndata->distance * scaleFactor;
return (int)(scaledDistance >= 0.0f ? (scaledDistance + 0.5f) : (scaledDistance - 0.5f));
}
if (prevChar < kerndata->unicodePrevChar)
{
break;
}
}
return 0;
}
const VectorFontNode* GeneratedVectorFont::find(Unicode::UnicodeChar unicode) const
{
// Some fonts does not have a glyphList. Cannot be searched...
if (vectorNodes == 0)
{
return 0;
}
int32_t min = 0;
int32_t max = numberOfGlyphs - 1;
int32_t mid = min + (unicode - vectorNodes[min].unicode); // Linear up from [min].unicode
if (mid < min)
{
// Unicode < [min].unicode => not found
return (VectorFontNode*)0;
}
if (mid > max)
{
// Linear up ends too high
mid = max - (vectorNodes[max].unicode - unicode); // Linear down from [max].unicode
if (mid > max)
{
// Unicode > [max].unicode => not found
return (VectorFontNode*)0;
}
if (mid < min)
{
// Linear down ends too low, take the middle element
mid = (min + max) / 2;
}
}
while (min <= max)
{
const Unicode::UnicodeChar midUnicode = vectorNodes[mid].unicode;
if (unicode == midUnicode)
{
// Found at [mid]
return vectorNodes + mid;
}
if (unicode < midUnicode)
{
// Unicode is in lower half
max = mid - 1;
if (max < min)
{
// Range is empty => not found
break;
}
// We adjusted max, try linear down from [max].unicode
mid = max - (vectorNodes[max].unicode - unicode);
if (mid > max)
{
// Unicode > [max].unicode => not found
break;
}
if (mid < min)
{
// Linear down ends too low, take the middle element
mid = (min + max) / 2;
}
}
else
{
// Unicode is in upper half
min = mid + 1;
if (min > max)
{
// Range is empty => not found
break;
}
// We adjusted min, try linear up from [min].unicode
mid = min + (unicode - vectorNodes[min].unicode);
if (mid < min)
{
// Unicode < [min].unicode => not found
break;
}
if (mid > max)
{
// Linear up ends too high, take the middle element
mid = (min + max) / 2;
}
}
}
return (VectorFontNode*)0;
}
CompressedMappedFont::CompressedMappedFont(const GlyphNode* glyphs, uint16_t numGlyphs, uint16_t height, uint16_t baseline, uint8_t pixAboveTop, uint8_t pixBelowBottom, uint8_t bitsPerPixel, uint8_t byteAlignRow, uint8_t maxLeft, uint8_t maxRight, const uint8_t* const* glyphDataInternalFlash, const KerningNode* kerningList, const Unicode::UnicodeChar fallbackChar, const Unicode::UnicodeChar ellipsisChar, const uint16_t* const gsubData, const FontContextualFormsTable* formsTable)
: GeneratedFont(glyphs, numGlyphs, height, baseline, pixAboveTop, pixBelowBottom, bitsPerPixel, byteAlignRow, maxLeft, maxRight, glyphDataInternalFlash, kerningList, fallbackChar, ellipsisChar, gsubData, formsTable)
{
}
const GlyphNode* CompressedMappedFont::getGlyph(Unicode::UnicodeChar unicode) const
{
return find(unicode);
}
const uint8_t* CompressedMappedFont::getPixelData(const GlyphNode* glyph) const
{
const uint8_t* pixelData = CompressedFontCache::hasCachedGlyph(glyph);
if (pixelData)
{
return pixelData;
}
const uint8_t* const* table = (const uint8_t* const*)glyphData;
const uint8_t* compressedData = &(table[glyph->unicode / 2048][glyph->dataOffset & 0x3FFFFFFF]);
return CompressedFontCache::cacheGlyph(glyph, compressedData);
}
} // namespace touchgfx
@@ -0,0 +1,227 @@
/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */
#ifndef TOUCHGFX_GENERATEDFONT_HPP
#define TOUCHGFX_GENERATEDFONT_HPP
#include <touchgfx/ConstFont.hpp>
namespace touchgfx
{
class GeneratedFont : public ConstFont
{
public:
GeneratedFont(const GlyphNode* glyphs, uint16_t numGlyphs, uint16_t height, uint16_t baseline, uint8_t pixAboveTop, uint8_t pixBelowBottom, uint8_t bitsPerPixel, uint8_t byteAlignRow, uint8_t maxLeft, uint8_t maxRight, const uint8_t* const* glyphDataInternalFlash, const KerningNode* kerningList, const Unicode::UnicodeChar fallbackChar, const Unicode::UnicodeChar ellipsisChar, const uint16_t* const gsubData, const FontContextualFormsTable* formsTable);
using ConstFont::getGlyph;
virtual const uint8_t* getPixelData(const GlyphNode* glyph) const;
virtual int8_t getKerning(Unicode::UnicodeChar prevChar, const GlyphNode* glyph) const;
virtual const uint16_t* getGSUBTable() const
{
return gsubTable;
}
virtual const FontContextualFormsTable* getContextualFormsTable() const
{
return arabicTable;
}
protected:
GeneratedFont()
: ConstFont(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), glyphData(0), kerningData(0), gsubTable(0), arabicTable(0)
{
}
const void* glyphData; ///< The glyphs
const KerningNode* kerningData; ///< The kerning
const uint16_t* gsubTable; ///< The GSUB tables
const FontContextualFormsTable* arabicTable; ///< Contextual forms
};
class FusedFont : public GeneratedFont
{
public:
FusedFont(const GlyphNode* glyphs, uint16_t numGlyphs, uint16_t height, uint16_t baseline, uint8_t pixAboveTop, uint8_t pixBelowBottom, uint8_t bitsPerPixel, uint8_t byteAlignRow, uint8_t maxLeft, uint8_t maxRight, const uint8_t* const* glyphDataInternalFlash, const KerningNode* kerningList, const Unicode::UnicodeChar fallbackChar, const Unicode::UnicodeChar ellipsisChar, const uint16_t* const gsubData, const FontContextualFormsTable* formsTable, GlyphNode& fontFusedNode)
: GeneratedFont(glyphs, numGlyphs, height, baseline, pixAboveTop, pixBelowBottom, bitsPerPixel, byteAlignRow,
maxLeft, maxRight, glyphDataInternalFlash, kerningList, fallbackChar, ellipsisChar,
gsubData, formsTable), fusedNode(fontFusedNode)
{ }
using GeneratedFont::getGlyph;
virtual const GlyphNode* getGlyph(Unicode::UnicodeChar unicode, const uint8_t*& pixelData, uint8_t& bitsPerPixel) const;
private:
GlyphNode& fusedNode;
};
struct VectorFontNode
{
uint32_t dataOffset; ///< The index to the data of this glyph in words
Unicode::UnicodeChar unicode; ///< The Unicode of this glyph.
uint16_t width; ///< Width of the actual glyph data
uint16_t height; ///< Height of the actual glyph data
int16_t top; ///< Vertical offset from baseline of the glyph
int16_t left; ///< Horizontal offset from the left of the glyph
uint16_t advance; ///< Width of the glyph (including space to the left and right)
uint16_t kerningTablePos; ///< Where are the kerning information for this glyph stored in the kerning table
uint8_t kerningTableSize; ///< How many entries are there in the kerning table (following kerningTablePos) for this glyph
};
struct VectorKerningNode
{
Unicode::UnicodeChar unicodePrevChar; ///< The Unicode for the first character in the kerning pair
int16_t distance; ///< The kerning distance
};
struct VectorFontData
{
uint16_t numberOfGlyphs;
uint16_t baselineHeight;
uint16_t maxLeft;
uint16_t maxRight;
uint16_t maxAboveTop;
uint16_t maxBelowBottom;
const Unicode::UnicodeChar fallbackChar;
const Unicode::UnicodeChar ellipsisChar;
};
class GeneratedVectorFont : public Font
{
public:
GeneratedVectorFont(uint16_t baseline, float scale, const VectorFontNode* vectorGlyphs, const VectorFontData& fontData,
const uint16_t* const* glyphData, const VectorKerningNode* kerningTable,
const uint16_t* const gsubData, const FontContextualFormsTable* formsTable);
virtual const GlyphNode* getGlyph(Unicode::UnicodeChar unicode) const;
virtual const GlyphNode* getGlyph(Unicode::UnicodeChar unicode, const uint8_t*& pixelData, uint8_t& bitsPerPixel) const;
virtual int8_t getKerning(Unicode::UnicodeChar prevChar, const GlyphNode* glyph) const;
virtual bool isVectorBasedFont() const { return true; }
virtual float getScaleFactor() const { return scaleFactor; }
virtual const uint16_t* getGSUBTable() const { return gsubTable; }
virtual const FontContextualFormsTable* getContextualFormsTable() const { return arabicTable; }
private:
const VectorFontNode* find(Unicode::UnicodeChar unicode) const;
const GlyphNode* getGlyphNode(const VectorFontNode* node) const;
float scaleFactor;
const VectorFontNode* vectorNodes;
const uint16_t* const* vectorTable;
const VectorKerningNode* kerningTable;
const uint16_t* gsubTable;
const FontContextualFormsTable* arabicTable;
uint16_t numberOfGlyphs;
static GlyphNode glyphNode;
};
struct BinaryFontData
{
uint32_t fontIndex; // The font index (as used by TypedTextDatabase)
uint32_t sizeOfFontData; // Size of the complete BinaryFont
uint32_t offsetToTable; // GlyphNode[]
uint32_t offsetToKerning; // KerningNode[]
uint32_t offsetToGlyphs; // uint8_t[]
uint32_t offsetToGSUB; // uint16_t[]
uint32_t offsetToArabicTable; // FontContextualFormsTable
uint16_t numberOfGlyphs; // Number of glyphs in Table and Glyphs
uint16_t fontHeight; // Font height
uint16_t baseline; // Distance to baseline
uint8_t pixAboveTop; // Max pixels above top
uint8_t pixBelowBottom; // Max pixels below bottom
uint8_t bitsPerPixel; // Bpp
uint8_t byteAlignRow; // A4/A2/A1
uint8_t maxLeft; // The maximum a glyph extends to the left
uint8_t maxRight; // The maximum a glyph extends to the right
Unicode::UnicodeChar fallbackChar; // Fallback Character for the font
Unicode::UnicodeChar ellipsisChar; // Ellipsis Character for the font
};
class BinaryFont : public GeneratedFont
{
public:
BinaryFont(const struct touchgfx::BinaryFontData* data)
: GeneratedFont((const GlyphNode*)((const uint8_t*)data + data->offsetToTable),
data->numberOfGlyphs,
data->fontHeight,
data->baseline,
data->pixAboveTop,
data->pixBelowBottom,
data->bitsPerPixel,
data->byteAlignRow,
data->maxLeft,
data->maxRight,
0,
(const KerningNode*)((const uint8_t*)data + data->offsetToKerning),
data->fallbackChar,
data->ellipsisChar,
(data->offsetToGSUB == 0) ? 0 : (const uint16_t*)((const uint8_t*)data + data->offsetToGSUB),
0),
glyphData((const uint8_t*)data + data->offsetToGlyphs)
{
if (data->offsetToArabicTable > 0)
{
setupContextualTable(data);
}
}
BinaryFont()
: GeneratedFont()
{
}
virtual const uint8_t* getPixelData(const GlyphNode* glyph) const
{
const uint8_t* data = (const uint8_t*)glyphData;
return &(data[glyph->dataOffset]);
}
protected:
const uint8_t* glyphData;
FontContextualFormsTable contextualForms;
private:
typedef const Unicode::UnicodeChar (*array5ptr)[5];
typedef const Unicode::UnicodeChar (*array4ptr)[4];
void setupContextualTable(const struct touchgfx::BinaryFontData* data)
{
const uint16_t* const base = (const uint16_t*)(((const uint8_t*)data) + data->offsetToArabicTable);
// First elements in binary font are offsets to arrays in 16bit words
contextualForms.contextualForms4Long = (array5ptr)(base + base[0]);
contextualForms.contextualForms3Long = (array5ptr)(base + base[1]);
contextualForms.contextualForms2Long = (array5ptr)(base + base[2]);
contextualForms.contextualForms0621_063a = (array4ptr)(base + base[3]);
contextualForms.contextualForms0641_064a = (array4ptr)(base + base[4]);
contextualForms.contextualForms06XX = (array5ptr)(base + base[5]);
contextualForms.contextualForms4LongSize = base[6];
contextualForms.contextualForms3LongSize = base[7];
contextualForms.contextualForms2LongSize = base[8];
contextualForms.contextualForms06XXSize = base[9];
arabicTable = &contextualForms;
}
};
class CompressedMappedFont : public GeneratedFont
{
public:
CompressedMappedFont(const GlyphNode* glyphs, uint16_t numGlyphs, uint16_t height, uint16_t baseline, uint8_t pixAboveTop, uint8_t pixBelowBottom, uint8_t bitsPerPixel, uint8_t byteAlignRow, uint8_t maxLeft, uint8_t maxRight, const uint8_t* const* glyphDataInternalFlash, const KerningNode* kerningList, const Unicode::UnicodeChar fallbackChar, const Unicode::UnicodeChar ellipsisChar, const uint16_t* const gsubData, const FontContextualFormsTable* formsTable);
using GeneratedFont::getGlyph;
virtual const GlyphNode* getGlyph(Unicode::UnicodeChar unicode) const;
virtual const uint8_t* getPixelData(const GlyphNode* glyph) const;
};
} // namespace touchgfx
#endif // TOUCHGFX_GENERATEDFONT_HPP
@@ -0,0 +1,4 @@
/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */
// Empty Language file
@@ -0,0 +1,48 @@
/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */
#include <stdint.h>
#include <touchgfx/Unicode.hpp>
<%= get_pragma %>
KEEP extern const uint32_t indices<%= get_language %>[]<%= get_attribute%>;
<% if remap_global? %>
// Remap all strings
TEXT_LOCATION_FLASH_PRAGMA
KEEP extern const uint32_t indices<%= get_language %>[] TEXT_LOCATION_FLASH_ATTRIBUTE = {
<% text_entries = get_text_entries %>
<% if text_entries.empty? %>
0
<% else %>
<% text_entries.each_with_index do |entry, index| %>
<%= get_string_index(entry) %><%= ((index == text_entries.length - 1) ? (index == 0 ? '' : ' '): ',') %><%= get_string_index_spaces(entry)%>// <%= entry.text_id %>: "<%= unicode_array_to_string(entry.int_array) %>"
<% end %>
<% end %>
};
<% else %>
// Remap local strings
TEXT_LOCATION_FLASH_PRAGMA
KEEP extern const touchgfx::Unicode::UnicodeChar texts<%= get_language %>[] TEXT_LOCATION_FLASH_ATTRIBUTE = {
<% substrings_and_offset = get_substrings_and_offsets(@language_index) %>
<% if substrings_and_offset.empty? %>
0
<% else %>
<% get_substrings_and_offsets(@language_index).each do |unicode_array, offset, last| %>
<%= unicode_array.map{|x|"0x%x"%x} * ', ' %><%= last ? '' : ',' %> // @<%= offset %> "<%= unicode_array_to_string(unicode_array) %>"
<% end %>
<% end %>
};
<%= get_pragma %>
KEEP extern const uint32_t indices<%= get_language %>[]<%= get_attribute%> = {
<% text_entries = get_text_entries %>
<% if text_entries.empty? %>
0
<% else %>
<% text_entries.each_with_index do |entry, index| %>
<%= get_string_index(entry) %><%= ((index == text_entries.length-1) ? ' ': ',') %><%= get_string_index_spaces(entry)%>// <%= entry.text_id %>: "<%= unicode_array_to_string(entry.int_array) %>"
<% end %>
<% end %>
};
<% end %>
@@ -0,0 +1,23 @@
/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */
#ifndef TOUCHGFX_TEXTKEYSANDLANGUAGES_HPP
#define TOUCHGFX_TEXTKEYSANDLANGUAGES_HPP
enum LANGUAGES
{
<% get_languages.each do |language| %>
<%= language %>,
<% end %>
NUMBER_OF_LANGUAGES
};
enum TEXTS
{
<% get_texts.each do |text| %>
<%= text.upcase %>,
<% end %>
NUMBER_OF_TEXT_KEYS
};
#endif // TOUCHGFX_TEXTKEYSANDLANGUAGES_HPP
@@ -0,0 +1,224 @@
/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */
#include <stdarg.h>
#include <touchgfx/TextProvider.hpp>
#include <touchgfx/Texts.hpp>
#include <touchgfx/TypedText.hpp>
#include <touchgfx/Unicode.hpp>
#include <touchgfx/hal/HAL.hpp>
#include <touchgfx/lcd/LCD.hpp>
#include <texts/TypedTextDatabase.hpp>
<% if copy_translations? %>
#include <touchgfx/hal/FlashDataReader.hpp>
#include <fonts/ApplicationFontProvider.hpp>
<% end %>
uint16_t touchgfx::Font::getStringWidth(const touchgfx::Unicode::UnicodeChar* text, ...) const
{
va_list pArg;
va_start(pArg, text);
uint16_t width = getStringWidth<%= is_rtl? ? 'RTL' : 'LTR' %>(TEXT_DIRECTION_LTR, text, pArg);
va_end(pArg);
return width;
}
uint16_t touchgfx::Font::getStringWidth(touchgfx::TextDirection textDirection, const touchgfx::Unicode::UnicodeChar* text, ...) const
{
va_list pArg;
va_start(pArg, text);
uint16_t width = getStringWidth<%= is_rtl? ? 'RTL' : 'LTR' %>(textDirection, text, pArg);
va_end(pArg);
return width;
}
touchgfx::Unicode::UnicodeChar touchgfx::TextProvider::getNextLigature(TextDirection direction)
{
<% if is_rtl? %>
nextCharacters.replaceAt0(unicodeConverter(direction));
<% end %>
if (fontGsubTable && nextCharacters.peekChar())
{
substituteGlyphs();
if (nextCharacters.peekChar(1) == 0x093F) // Hindi I-matra
{
nextCharacters.replaceAt1(nextCharacters.peekChar());
nextCharacters.replaceAt0(0x093F);
}
}
return getNextChar();
}
void touchgfx::TextProvider::initializeInternal()
{
fillInputBuffer();
<% if is_rtl? %>
unicodeConverterInit();
<% end %>
}
void touchgfx::LCD::drawString(touchgfx::Rect widgetArea, const touchgfx::Rect& invalidatedArea, const touchgfx::LCD::StringVisuals& stringVisuals, const touchgfx::Unicode::UnicodeChar* format, ...)
{
va_list pArg;
va_start(pArg, format);
drawString<%= is_rtl? ? 'RTL' : 'LTR' %>(widgetArea, invalidatedArea, stringVisuals, format, pArg);
va_end(pArg);
}
// Default TypedTextDatabase
extern const touchgfx::TypedText::TypedTextData* const typedTextDatabaseArray[];
<% if generate_binary_files? %>
extern const touchgfx::Unicode::UnicodeChar EmptyLanguageTexts[];
extern const uint32_t EmptyLanguageIndices[];
<% else %>
<% if remap_global? %>
TEXT_LOCATION_FLASH_PRAGMA
KEEP extern const touchgfx::Unicode::UnicodeChar texts_all_languages[] TEXT_LOCATION_FLASH_ATTRIBUTE = {
<% substrings_and_offsets = get_substrings_and_offsets %>
<% if substrings_and_offsets.empty? %>
0 // No texts in application
<% else %>
<% substrings_and_offsets.each do |(unicode_array, offset, last)| %>
<%= unicode_array.map{|x|"0x%x"%x} * ', ' %><%= last ? '' : ',' %> // @<%= offset %> "<%= unicode_array_to_string(unicode_array) %>"
<% end %>
<% end %>
};
<% end %>
<% get_capitalized_languages.each do |lang| %>
<%= get_pragma %>
KEEP extern const uint32_t indices<%= lang %>[]<%= get_attribute%>;
<% if !remap_global? %>
<%= get_pragma %>
KEEP extern const touchgfx::Unicode::UnicodeChar texts<%= lang %>[]<%= get_attribute%>;
<% end %>
<% end %>
<% end %>
// Array holding dynamically installed languages
struct TranslationHeader
{
uint32_t offset_to_texts;
uint32_t offset_to_indices;
uint32_t offset_to_typedtext;
};
static const TranslationHeader* languagesArray[<%= has_languages? ? get_number_of_languages : 1 %>] = { 0 };
// Compiled and linked in languages
static const uint32_t* const staticLanguageIndices[] = {
<% if generate_binary_files? %>
EmptyLanguageIndices
<% else %>
<% if !has_languages? %>
0
<% else %>
<% get_capitalized_languages.each_with_index do |lang, index| %>
indices<%= lang %><%= (index == get_number_of_languages - 1) ? '': ',' %>
<% end %>
<% end %>
<% end %>
};
<% if generate_binary_files? %>
static const touchgfx::Unicode::UnicodeChar* const staticLanguageTexts[] = {
EmptyLanguageTexts
};
<% else %>
<% if not remap_global? %>
static const touchgfx::Unicode::UnicodeChar* const staticLanguageTexts[] = {
<% if !has_languages? %>
0
<% else %>
<% get_capitalized_languages.each_with_index do |lang, index| %>
texts<%= lang %><%= (index == get_number_of_languages - 1) ? '': ',' %>
<% end %>
<% end %>
};
<% end %>
<% end %>
<% if copy_translations? %>
static const uint32_t staticLanguageSizes[] = {
<% get_capitalized_languages.each_with_index do |lang, index| %>
<%= language_length(index) %><%= (index == get_number_of_languages - 1) ? '': ',' %>
<% end %>
};
static const uint32_t maxStaticLanguageSize = <%= get_max_language_length %>;
static touchgfx::Unicode::UnicodeChar translationBuffer[maxStaticLanguageSize];
<% end %>
touchgfx::LanguageId touchgfx::Texts::currentLanguage = static_cast<touchgfx::LanguageId>(0);
static const touchgfx::Unicode::UnicodeChar* currentLanguagePtr = 0;
static const uint32_t* currentLanguageIndices = 0;
void touchgfx::Texts::setLanguage(touchgfx::LanguageId id)
{
const touchgfx::TypedText::TypedTextData* currentLanguageTypedText = 0;
if (id < <%= has_languages? ? get_number_of_languages : 1 %>)
{
if (languagesArray[id] != 0)
{
// Dynamic translation is added
const TranslationHeader* translation = languagesArray[id];
currentLanguagePtr = (const touchgfx::Unicode::UnicodeChar*)(((const uint8_t*)translation) + translation->offset_to_texts);
currentLanguageIndices = (const uint32_t*)(((const uint8_t*)translation) + translation->offset_to_indices);
currentLanguageTypedText = (const touchgfx::TypedText::TypedTextData*)(((const uint8_t*)translation) + translation->offset_to_typedtext);
}
<% if generate_binary_files? %>
else
{
// Compiled and linked empty texts and indices in typedTextData
currentLanguagePtr = staticLanguageTexts[0];
currentLanguageIndices = staticLanguageIndices[0];
currentLanguageTypedText = typedTextDatabaseArray[0];
}
<% else %>
else
{
// Compiled and linked in languages
<% if remap_global? %>
currentLanguagePtr = texts_all_languages;
currentLanguageIndices = staticLanguageIndices[id];
<% else %>
<% if not copy_translations? %>
currentLanguagePtr = staticLanguageTexts[id];
<% else %>
// Read translations from unmapped flash
#ifdef SIMULATOR
memcpy(translationBuffer, staticLanguageTexts[id], staticLanguageSizes[id] * 2);
#else
touchgfx::FlashDataReader* const flashReader = ApplicationFontProvider::getFlashReader();
if (flashReader)
{
flashReader->copyData(staticLanguageTexts[id], translationBuffer, staticLanguageSizes[id] * 2);
}
#endif
currentLanguagePtr = translationBuffer;
<% end %>
currentLanguageIndices = staticLanguageIndices[id];
<% end %>
currentLanguageTypedText = typedTextDatabaseArray[id];
}
<% end %>
}
if (currentLanguageTypedText)
{
currentLanguage = id;
touchgfx::TypedText::registerTypedTextDatabase(currentLanguageTypedText,
TypedTextDatabase::getFonts(), TypedTextDatabase::getInstanceSize());
}
}
void touchgfx::Texts::setTranslation(touchgfx::LanguageId id, const void* translation)
{
languagesArray[id] = (const TranslationHeader*)translation;
}
const touchgfx::Unicode::UnicodeChar* touchgfx::Texts::getText(TypedTextId id) const
{
return &currentLanguagePtr[currentLanguageIndices[id]];
}
@@ -0,0 +1,138 @@
/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */
#include <touchgfx/TypedText.hpp>
<% get_font_class_names.each do |name| %>
#include <fonts/<%= name %>.hpp>
<% end %>
#include <texts/TypedTextDatabase.hpp>
<% get_fonts.each do |font| %>
extern touchgfx::<%= get_font_class_name(font) %>& <%= font %>();
<% end %>
const touchgfx::Font* touchgfx_fonts[] = {
<% if get_fonts.empty? %>
0
<% else %>
<% get_fonts.each_with_index do |font, index| %>
&(<%= font %>())<%= index == get_fonts.length-1 ? '' : ',' %>
<% end %>
<% end %>
};
<% if generate_binary_files? %>
extern const touchgfx::TypedText::TypedTextData typedText_database_EMPTY[];
<% else %>
extern const touchgfx::TypedText::TypedTextData typedText_database_DEFAULT[];
<% end %>
extern const touchgfx::TypedText::TypedTextData* const typedTextDatabaseArray[];
<% if generate_binary_files? %>
TEXT_LOCATION_FLASH_PRAGMA
KEEP extern const touchgfx::Unicode::UnicodeChar EmptyLanguageTexts[] TEXT_LOCATION_FLASH_ATTRIBUTE = {
<% get_layouts.each do |layout| %>
<% get_typed_texts(layout).each_with_index do |entry, index| %>
0<%= ((index == get_typed_texts(layout).length-1) ? '': ',') %>
<% end %>
<% break %>
<% end %>
};
TEXT_LOCATION_FLASH_PRAGMA
KEEP extern const uint32_t EmptyLanguageIndices[] TEXT_LOCATION_FLASH_ATTRIBUTE = {
<% get_layouts.each do |layout| %>
<% get_typed_texts(layout).each_with_index do |entry, index| %>
<%= index %><%= ((index == get_typed_texts(layout).length-1) ? '': ',') %>
<% end %>
<% break %>
<% end %>
};
TEXT_LOCATION_FLASH_PRAGMA
const touchgfx::TypedText::TypedTextData typedText_database_EMPTY[] TEXT_LOCATION_FLASH_ATTRIBUTE = {
<% get_layouts.each do |layout| %>
<% get_typed_texts(layout).each_with_index do |entry, index| %>
<%= ('{ 0, touchgfx::LEFT, touchgfx::TEXT_DIRECTION_LTR }').to_s %><%= ((index == get_typed_texts(layout).length-1) ? '': ',') %>
<% end %>
<% break %>
<% end %>
};
<% else %>
<% get_layouts.each do |layout| %>
<%= get_pragma %>
const touchgfx::TypedText::TypedTextData typedText_database_<%= layout %>[]<%= get_attribute %> = {
<% if get_typed_texts(layout).empty? %>
{ 0, touchgfx::LEFT, touchgfx::TEXT_DIRECTION_LTR }
<% else %>
<% get_typed_texts(layout).each_with_index do |typed_text, index| %>
<% fontIdx = get_font_index(typed_text.typography) %>
<% alignment = get_touchgfx_aligment(typed_text.alignment) %>
<% direction = get_touchgfx_direction(typed_text.direction) %>
{ <%= fontIdx %>, <%= alignment %>, <%= direction %> }<%= index == get_typed_texts(layout).length-1 ? '' : ',' %>
<% end %>
<% end %>
};
<% end %>
<% end %>
const touchgfx::TypedText::TypedTextData* const typedTextDatabaseArray[] = {
<% if generate_binary_files? %>
<% get_languages.each_with_index do |entry,index| %>
typedText_database_EMPTY<%= index == get_languages.length-1 ? '' : ',' %>
<% end %>
<% elsif languages.empty? %>
typedText_database_DEFAULT
<% else %>
<% get_languages.each_with_index do |language, index| %>
typedText_database_<%= get_layouts.find { |l| l == language } || 'DEFAULT' %><%= index == get_languages.length-1 ? '' : ',' %>
<% end %>
<% end %>
};
namespace TypedTextDatabase
{
const touchgfx::TypedText::TypedTextData* getInstance(touchgfx::LanguageId id)
{
return typedTextDatabaseArray[id];
}
uint16_t getInstanceSize()
{
<% if has_text_entries? %>
<% if generate_binary_files? %>
return sizeof(typedText_database_EMPTY) / sizeof(touchgfx::TypedText::TypedTextData);
<% else %>
return sizeof(typedText_database_DEFAULT) / sizeof(touchgfx::TypedText::TypedTextData);
<% end %>
<% else %>
return 0;
<% end %>
}
const touchgfx::Font** getFonts()
{
return touchgfx_fonts;
}
const touchgfx::Font* setFont(touchgfx::FontId fontId, const touchgfx::Font* font)
{
const touchgfx::Font* old = touchgfx_fonts[fontId];
touchgfx_fonts[fontId] = font;
return old;
}
void resetFont(touchgfx::FontId fontId)
{
<% if !get_fonts.empty? %>
switch (fontId)
{
<% get_fonts.each_with_index do |font, index| %>
case <%= index %>:
touchgfx_fonts[<%= index %>] = &(<%= font %>());
break;
<% end %>
}
<% end %>
}
} // namespace TypedTextDatabase
@@ -0,0 +1,21 @@
/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */
#ifndef TOUCHGFX_TYPEDTEXTDATABASE_HPP
#define TOUCHGFX_TYPEDTEXTDATABASE_HPP
#include <touchgfx/TypedText.hpp>
#include <touchgfx/hal/Types.hpp>
namespace TypedTextDatabase
{
class TypedTextData;
const touchgfx::TypedText::TypedTextData* getInstance(touchgfx::LanguageId id);
const touchgfx::TypedText::TypedTextData* getInstance();
const touchgfx::Font** getFonts();
const touchgfx::Font* setFont(touchgfx::FontId fontId, const touchgfx::Font*);
void resetFont(touchgfx::FontId fontId);
uint16_t getInstanceSize();
} // namespace TypedTextDatabase
#endif // TOUCHGFX_TYPEDTEXTDATABASE_HPP
@@ -0,0 +1,236 @@
/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */
#include <touchgfx/hal/FlashDataReader.hpp>
#include <fonts/ApplicationFontProvider.hpp>
#include <fonts/CompressedUnmappedFontCache.hpp>
#include <fonts/UnmappedDataFont.hpp>
namespace touchgfx
{
GlyphNode UnmappedDataFont::glyphNodeBuffer;
UnmappedDataFont::UnmappedDataFont(const GlyphNode* glyphs, const uint16_t* unicodeList, uint16_t numGlyphs, uint16_t height, uint16_t baseline, uint8_t pixAboveTop, uint8_t pixBelowBottom, uint8_t bitsPerPixel, uint8_t byteAlignRow, uint8_t maxLeft, uint8_t maxRight, const uint8_t* const* glyphDataList, const KerningNode* kerningList, const Unicode::UnicodeChar fallbackChar, const Unicode::UnicodeChar ellipsisChar, const uint16_t* const gsubData, const FontContextualFormsTable* formsTable)
: Font(height, baseline, pixAboveTop, pixBelowBottom, bitsPerPixel, byteAlignRow, maxLeft, maxRight, fallbackChar, ellipsisChar),
glyphList(glyphs),
listSize(numGlyphs),
unicodes(unicodeList),
glyphDataList(glyphDataList),
kerningData(kerningList),
gsubTable(gsubData),
arabicTable(formsTable)
{
}
const GlyphNode* UnmappedDataFont::getGlyph(Unicode::UnicodeChar unicode, const uint8_t*& pixelData, uint8_t& bitsPerPixel) const
{
int index = lookupUnicode(unicode);
if (index != -1)
{
// Read glyphNode from unmapped flash
touchgfx::FlashDataReader* const flashReader = ApplicationFontProvider::getFlashReader();
if (flashReader)
{
flashReader->copyData(glyphList + index, &glyphNodeBuffer, sizeof(GlyphNode));
pixelData = getPixelData(const_cast<const GlyphNode*>(&glyphNodeBuffer));
bitsPerPixel = getBitsPerPixel();
return &glyphNodeBuffer;
}
}
return 0;
}
const uint8_t* UnmappedDataFont::getPixelData(const GlyphNode* glyph) const
{
const uint8_t* const* table = (const uint8_t* const*)glyphDataList;
return &(table[glyph->unicode / 2048][glyph->dataOffset]);
}
int8_t UnmappedDataFont::getKerning(Unicode::UnicodeChar prevChar, const GlyphNode* glyph) const
{
if (!glyph || glyph->kerningTableSize == 0)
{
return 0;
}
const KerningNode* kerndata = kerningData + glyph->kerningTablePos();
for (uint16_t i = glyph->kerningTableSize; i > 0; i--, kerndata++)
{
if (prevChar == kerndata->unicodePrevChar)
{
return kerndata->distance;
}
if (prevChar < kerndata->unicodePrevChar)
{
break;
}
}
return 0;
}
int UnmappedDataFont::lookupUnicode(uint16_t unicode) const
{
int32_t min = 0;
int32_t max = listSize - 1;
int32_t mid = min + (unicode - unicodes[min]); // Linear up from [min].unicode
if (mid < min)
{
// Unicode < unicodes[min] => not found
return -1;
}
if (mid > max)
{
// Linear up ends too high
mid = max - (unicodes[max] - unicode); // Linear down from [max].unicode
if (mid > max)
{
// Unicode > unicodes[max] => not found
return -1;
}
if (mid < min)
{
// Linear down ends too low, take the middle element
mid = (min + max) / 2;
}
}
while (min <= max)
{
if (unicode == unicodes[mid])
{
// Found at [mid]
return mid;
}
if (unicode < unicodes[mid])
{
// Unicode is in lower half
max = mid - 1;
if (max < min)
{
// Range is empty => not found
break;
}
// We adjusted max, try linear down from [max].unicode
mid = max - (unicodes[max] - unicode);
if (mid > max)
{
// Unicode > [max].unicode => not found
break;
}
if (mid < min)
{
// Linear down ends too low, take the middle element
mid = (min + max) / 2;
}
}
else
{
// Unicode is in upper half
min = mid + 1;
if (min > max)
{
// Range is empty => not found
break;
}
// We adjusted min, try linear up from [min].unicode
mid = min + (unicode - unicodes[min]);
if (mid < min)
{
// Unicode < [min].unicode => not found
break;
}
if (mid > max)
{
// Linear up ends too high, take the middle element
mid = (min + max) / 2;
}
}
}
return -1;
}
CompressedUnmappedDataFont::CompressedUnmappedDataFont(const GlyphNode* glyphs, const uint16_t* unicodes, uint16_t numGlyphs, uint16_t height, uint16_t baseline, uint8_t pixAboveTop, uint8_t pixBelowBottom, uint8_t bitsPerPixel, uint8_t byteAlignRow, uint8_t maxLeft, uint8_t maxRight, const uint8_t* const* glyphDataList, const KerningNode* kerningList, const Unicode::UnicodeChar fallbackChar, const Unicode::UnicodeChar ellipsisChar, const uint16_t* const gsubData, const FontContextualFormsTable* formsTable)
: UnmappedDataFont(glyphs, unicodes, numGlyphs, height, baseline, pixAboveTop, pixBelowBottom, bitsPerPixel, byteAlignRow, maxLeft, maxRight, glyphDataList, kerningList, fallbackChar, ellipsisChar, gsubData, formsTable)
{
}
const GlyphNode* CompressedUnmappedDataFont::getGlyph(Unicode::UnicodeChar unicode) const
{
const int index = lookupUnicode(unicode);
if (index != -1)
{
const GlyphNode* glyphNode = glyphList + index;
// Is the GlyphNode already cached
const GlyphNode* cachedNode = CompressedUnmappedFontCache::hasCachedGlyphNode(glyphNode);
if (cachedNode)
{
return cachedNode;
}
// Read glyphNode from unmapped flash
touchgfx::FlashDataReader* const flashReader = ApplicationFontProvider::getFlashReader();
if (flashReader)
{
GlyphNode* newGlyphNode = CompressedUnmappedFontCache::cacheGlyphNode(glyphNode);
flashReader->copyData(glyphList + index, (uint8_t*)newGlyphNode, sizeof(GlyphNode));
return newGlyphNode;
}
}
return 0;
}
const GlyphNode* CompressedUnmappedDataFont::getGlyph(Unicode::UnicodeChar unicode, const uint8_t*& pixelData, uint8_t& bitsPerPixel) const
{
const int index = lookupUnicode(unicode);
if (index != -1)
{
const GlyphNode* glyphNode = glyphList + index;
const uint8_t* pixels = 0;
const GlyphNode* cachedNode = CompressedUnmappedFontCache::hasCachedGlyphData(glyphNode, pixels);
if (pixels)
{
pixelData = pixels;
bitsPerPixel = 4;
return cachedNode;
}
// Cache glyphNode if not found already
if (cachedNode == 0)
{
cachedNode = getGlyph(unicode);
}
if (cachedNode->width() == 0)
{
bitsPerPixel = 4;
pixelData = 0;
return cachedNode;
}
// Read data from unmapped flash
touchgfx::FlashDataReader* const flashReader = ApplicationFontProvider::getFlashReader();
if (flashReader)
{
const uint8_t* const* table = (const uint8_t* const*)glyphDataList;
const uint32_t dataOffset = cachedNode->dataOffset & 0x3FFFFFFF;
const uint8_t* compressedData = &(table[unicode / 2048][dataOffset]);
const uint8_t* cachedData = CompressedUnmappedFontCache::cacheGlyphData(glyphNode, compressedData, flashReader);
if (cachedData)
{
bitsPerPixel = 4;
pixelData = cachedData;
return cachedNode;
}
}
}
return 0;
}
const uint8_t* CompressedUnmappedDataFont::getPixelData(const GlyphNode* glyph) const
{
return 0;
}
} // namespace touchgfx
@@ -0,0 +1,137 @@
/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */
#ifndef TOUCHGFX_UNMAPPEDDATAFONT_HPP
#define TOUCHGFX_UNMAPPEDDATAFONT_HPP
#include <touchgfx/Font.hpp>
namespace touchgfx
{
/**
* An UnmappedDataFont has both glyph table and glyph data placed in a
* flash which does not support random access read (indirect
* access). A unicode table is located in a flash with random read
* access (direct access).
*
* @see Font, ConstFont
*/
class UnmappedDataFont : public Font
{
public:
/**
* Construct the UnmappedDataFont.
*
* @param glyphs The array of glyphs known to this font (indirect).
* @param unicodes The array of unicodes known to this font (direct).
* @param numGlyphs The number of glyphs in list.
* @param height The height in pixels of the highest character in this font.
* @param baseline The height of the baseline in pixels.
* @param pixBelowBottom The maximum number of pixels that can be drawn below the
* bottom in this font.
* @param bitsPerPixel The number of bits per pixel in this font.
* @param byteAlignRow Are glyphs encoded using A4 format
* @param maxLeft The maximum a character extends to the left.
* @param maxRight The maximum a character extends to the right.
* @param glyphDataList Pointer to pointers the glyph data for the font (indirect).
* @param kerningList pointer to the kerning data for the font (direct).
* @param fallbackChar The fallback character for the typography in case no glyph is
* available.
* @param ellipsisChar The ellipsis character used for truncating long texts.
* @param gsubTable Pointer to GSUB table (direct).
*/
UnmappedDataFont(const GlyphNode* glyphs, const uint16_t* unicodes, uint16_t numGlyphs, uint16_t height, uint16_t baseline, uint8_t pixAboveTop, uint8_t pixBelowBottom, uint8_t bitsPerPixel, uint8_t byteAlignRow, uint8_t maxLeft, uint8_t maxRight, const uint8_t* const* glyphDataList, const KerningNode* kerningList, const Unicode::UnicodeChar fallbackChar, const Unicode::UnicodeChar ellipsisChar, const uint16_t* const gsubData, const FontContextualFormsTable* formsTable);
using Font::getGlyph;
/**
* Gets the glyph data associated with the specified Unicode. The
GlyphNode is allocated in the buffer passed to the constructor.
*
* Please note that in case of Thai letters and Arabic letters
* where diacritics can be placed relative to the previous
* character(s), please use TextProvider::getNextLigature()
* instead as it will create a temporary GlyphNode that will be
* adjusted with respect to X/Y position.
*
* @param unicode The character to look up.
* @param pixelData Pointer to the pixel data for the glyph if the glyph is
* found. This is set by this method.
* @param [out] bitsPerPixel Reference where to place the number of bits per pixel.
*
* @return A pointer to the glyph node or null if the glyph was not found.
*/
virtual const GlyphNode* getGlyph(Unicode::UnicodeChar unicode, const uint8_t*& pixelData, uint8_t& bitsPerPixel) const;
/**
* Obtains the address to the pixel data for the specified glyph.
*
* @param glyph The glyph to get the pixels data of.
*
* @return The address of the pixel data of the glyph.
*/
virtual const uint8_t* getPixelData(const GlyphNode* glyph) const;
/**
* Gets the kerning distance between two characters.
*
* @param prevChar The unicode value of the previous character.
* @param glyph the glyph object for the current character.
*
* @return The kerning distance between prevChar and glyph char.
*/
virtual int8_t getKerning(Unicode::UnicodeChar prevChar, const GlyphNode* glyph) const;
/**
* Gets GSUB table.
*
* @return The GSUB table or null if font has GSUB no table
*/
virtual const uint16_t* getGSUBTable() const
{
return gsubTable;
}
/**
* Gets the contextual forms table used in arabic fonts.
*
* @return The FontContextualFormsTable or null if the font has no table.
*/
virtual const FontContextualFormsTable* getContextualFormsTable() const
{
return arabicTable;
}
protected:
UnmappedDataFont()
: Font(0, 0, 0, 0, 0, 0, 0, 0, 0, 0), glyphList(0), unicodes(0), glyphDataList(0), kerningData(0), gsubTable(0)
{
}
int lookupUnicode(uint16_t unicode) const;
const GlyphNode* glyphList; ///< The list of glyphs
uint16_t listSize; ///< The size of the list of glyphs
const uint16_t* unicodes; ///< LookupTable with all unicodes in this font
const void* glyphDataList; ///< The glyphs (list of pointers)
const KerningNode* kerningData; ///< The kerning
const uint16_t* gsubTable; ///< The GSUB tables
const FontContextualFormsTable* arabicTable; ///< Contextual forms
static GlyphNode glyphNodeBuffer; ///< Buffer for GlyphNodes read from unmapped flash
};
class CompressedUnmappedDataFont : public UnmappedDataFont
{
public:
CompressedUnmappedDataFont(const GlyphNode* glyphs, const uint16_t* unicodes, uint16_t numGlyphs, uint16_t height, uint16_t baseline, uint8_t pixAboveTop, uint8_t pixBelowBottom, uint8_t bitsPerPixel, uint8_t byteAlignRow, uint8_t maxLeft, uint8_t maxRight, const uint8_t* const* glyphDataList, const KerningNode* kerningList, const Unicode::UnicodeChar fallbackChar, const Unicode::UnicodeChar ellipsisChar, const uint16_t* const gsubData, const FontContextualFormsTable* formsTable);
virtual const GlyphNode* getGlyph(Unicode::UnicodeChar unicode) const;
virtual const GlyphNode* getGlyph(Unicode::UnicodeChar unicode, const uint8_t*& pixelData, uint8_t& bitsPerPixel) const;
virtual const uint8_t* getPixelData(const GlyphNode* glyph) const;
};
} // namespace touchgfx
#endif // TOUCHGFX_UNMAPPEDDATAFONT_HPP
@@ -0,0 +1,24 @@
/* DO NOT EDIT THIS FILE */
/* This file is autogenerated by the text-database code generator */
#include <touchgfx/VectorFontRendererImpl.hpp>
<% if get_num_commands != 0 %>
static float ptArray[<%= get_num_floats %>];
static uint8_t cmdArray[<%= get_num_commands %>];
void touchgfx::VectorFontRendererImpl::getVectorFontBuffers(float*& pointArray, int& pointArraySize, uint8_t*& commandArray, int& commandArraySize)
{
pointArray = ptArray;
pointArraySize = <%= get_num_floats %>;
commandArray = cmdArray;
commandArraySize = <%= get_num_commands %>;
}
<% else %>
void touchgfx::VectorFontRendererImpl::getVectorFontBuffers(float*& pointArray, int& pointArraySize, uint8_t*& commandArray, int& commandArraySize)
{
pointArray = 0;
pointArraySize = 0;
commandArray = 0;
commandArraySize = 0;
}
<% end %>
@@ -0,0 +1,78 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
require 'json'
class ApplicationFontProviderCpp < Template
def initialize(text_entries, typographies, language, output_directory, generate_font_format)
super(text_entries, typographies, language, output_directory)
@generate_font_format = generate_font_format
@cache = {}
end
def input_path
File.join(root_dir,'Templates','ApplicationFontProvider.cpp.temp')
end
def output_path
'src/ApplicationFontProvider.cpp'
end
def cache_file
File.join(@output_directory, 'cache/ApplicationFontProvider.cache')
end
def output_filename
File.join(@output_directory, output_path)
end
def run
@cache["typographies"] = typographies.collect{|t| [t.name, t.font_file, t.font_size, t.bpp] }
@cache["generate_font_format"] = @generate_font_format
new_cache_file = false
if not File::exists?(cache_file)
new_cache_file = true
else
#cache file exists, compare data with cache file
old_cache = JSON.parse(File.read(cache_file))
new_cache_file = (old_cache != @cache)
end
if new_cache_file
#write new cache file
FileIO.write_file_silent(cache_file, @cache.to_json)
end
if !File::exists?(output_filename) || new_cache_file
#generate ApplicationFontProvider.cpp
super
end
end
def save_flashreader?
@generate_font_format == "1"
end
def get_font_index(index)
font_index[typography_name(@typographies[index])]
end
private
def typography_name(typography)
if typography.is_vector
"Vector_#{typography.cpp_name}_#{typography.font_size}"
else
"#{typography.cpp_name}_#{typography.font_size}_#{typography.bpp}bpp"
end
end
def font_index
#map typographies to index of first using same font = font index
@font_index ||= @typographies.inject(Hash.new) do |list, typography|
name = typography_name(typography)
list[name] ||= list.size
list
end
end
end
@@ -0,0 +1,86 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
class ApplicationFontProviderHpp < Template
def initialize(text_entries, typographies, languages, output_directory, generate_font_format)
super(text_entries, typographies, languages, output_directory)
@generate_font_format = generate_font_format
@cache = {}
#map typographies to index of first using same font = font index
@font_index = @typographies.inject(Hash.new) do |list, typography|
name = typography_name(typography)
list[name] ||= list.size
list
end
end
def input_path
File.join(root_dir,'Templates','ApplicationFontProvider.hpp.temp')
end
def output_path
'/include/fonts/ApplicationFontProvider.hpp'
end
def cache_file
File.join(@output_directory, 'cache/ApplicationFontProviderHpp.cache')
end
def output_filename
File.join(@output_directory, output_path)
end
def run
@cache["typographies"] = @typographies.collect{|t| [t.name, t.font_file, t.font_size, t.bpp] }
@cache["generate_font_format"] = @generate_font_format
new_cache_file = false
if not File::exists?(cache_file)
new_cache_file = true
else
#cache file exists, compare data with cache file
old_cache = JSON.parse(File.read(cache_file))
new_cache_file = (old_cache != @cache)
end
if new_cache_file
#write new cache file
FileIO.write_file_silent(cache_file, @cache.to_json)
end
if !File::exists?(output_filename) || new_cache_file
#generate ApplicationFontProvider.hpp
super
end
end
def save_flashreader?
@generate_font_format == "1"
end
def get_font_index(index)
@font_index[typography_name(@typographies[index])]
end
def get_max_font_index
@font_index.size
end
def get_font_comment_space(index)
@max_length ||= @typographies.map { |t| t.name.length }.max
' ' * (@max_length - @typographies[index].name.length)
end
def get_font_comment(index)
typography_name(@typographies[index])
end
private
def typography_name(typography)
if typography.is_vector
"Vector_#{typography.cpp_name}_#{typography.font_size}"
else
"#{typography.cpp_name}_#{typography.font_size}_#{typography.bpp}bpp"
end
end
end
@@ -0,0 +1,27 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
class CachedFontCpp < Template
def input_path
File.join(root_dir,'Templates','CachedFont.cpp.temp')
end
def output_path
'/src/CachedFont.cpp'
end
def output_filename
File.join(@output_directory, output_path)
end
def run
if !File::exists?(output_filename)
#generate CachedFont.cpp
super
end
end
end
@@ -0,0 +1,27 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
class CachedFontHpp < Template
def input_path
File.join(root_dir,'Templates','CachedFont.hpp.temp')
end
def output_path
'/include/fonts/CachedFont.hpp'
end
def output_filename
File.join(@output_directory, output_path)
end
def run
if !File::exists?(output_filename)
#generate CachedFont.hpp
super
end
end
end
@@ -0,0 +1,27 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
class CompressedFontCacheCpp < Template
def input_path
File.join(root_dir,'Templates','CompressedFontCache.cpp.temp')
end
def output_path
'/src/CompressedFontCache.cpp'
end
def output_filename
File.join(@output_directory, output_path)
end
def run
if !File::exists?(output_filename)
#generate CompressedFontCache.cpp
super
end
end
end
@@ -0,0 +1,52 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
class CompressedFontCacheHpp < Template
def initialize(text_entries, typographies, language, output_directory, compressed_font_cache_size)
super(text_entries, typographies, language, output_directory)
@compressed_font_cache_size = compressed_font_cache_size
@cache = {}
end
def input_path
File.join(root_dir,'Templates','CompressedFontCache.hpp.temp')
end
def output_path
'/include/fonts/CompressedFontCache.hpp'
end
def cache_file
File.join(@output_directory, 'cache/CompressedFontCacheHpp.cache')
end
def output_filename
File.join(@output_directory, output_path)
end
def run
@cache["cache_size"] = @compressed_font_cache_size
new_cache_file = false
if not File::exists?(cache_file)
new_cache_file = true
else
#cache file exists, compare data with cache file
old_cache = JSON.parse(File.read(cache_file))
new_cache_file = (old_cache != @cache)
end
if new_cache_file
#write new cache file
FileIO.write_file_silent(cache_file, @cache.to_json)
end
if !File::exists?(output_filename) || new_cache_file
#generate CompressedFontCache.hpp
super
end
end
def get_cache_size
@compressed_font_cache_size
end
end
@@ -0,0 +1,27 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
class CompressedUnmappedFontCacheCpp < Template
def input_path
File.join(root_dir,'Templates','CompressedUnmappedFontCache.cpp.temp')
end
def output_path
'/src/CompressedUnmappedFontCache.cpp'
end
def output_filename
File.join(@output_directory, output_path)
end
def run
if !File::exists?(output_filename)
#generate CompressedUnmappedFontCache.cpp
super
end
end
end
@@ -0,0 +1,52 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
class CompressedUnmappedFontCacheHpp < Template
def initialize(text_entries, typographies, language, output_directory, compressed_font_cache_size)
super(text_entries, typographies, language, output_directory)
@compressed_font_cache_size = compressed_font_cache_size
@cache = {}
end
def input_path
File.join(root_dir,'Templates','CompressedUnmappedFontCache.hpp.temp')
end
def output_path
'/include/fonts/CompressedUnmappedFontCache.hpp'
end
def cache_file
File.join(@output_directory, 'cache/CompressedUnmappedFontCacheHpp.cache')
end
def output_filename
File.join(@output_directory, output_path)
end
def run
@cache["cache_size"] = @compressed_font_cache_size
new_cache_file = false
if not File::exists?(cache_file)
new_cache_file = true
else
#cache file exists, compare data with cache file
old_cache = JSON.parse(File.read(cache_file))
new_cache_file = (old_cache != @cache)
end
if new_cache_file
#write new cache file
FileIO.write_file_silent(cache_file, @cache.to_json)
end
if !File::exists?(output_filename) || new_cache_file
#generate CompressedUnmappedFontCache.hpp
super
end
end
def get_cache_size
@compressed_font_cache_size
end
end
@@ -0,0 +1,27 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
class FontCacheCpp < Template
def input_path
File.join(root_dir,'Templates','FontCache.cpp.temp')
end
def output_path
'/src/FontCache.cpp'
end
def output_filename
File.join(@output_directory, output_path)
end
def run
if !File::exists?(output_filename)
#generate FontCache.cpp
super
end
end
end
@@ -0,0 +1,27 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
class FontCacheHpp < Template
def input_path
File.join(root_dir,'Templates','FontCache.hpp.temp')
end
def output_path
'/include/fonts/FontCache.hpp'
end
def output_filename
File.join(@output_directory, output_path)
end
def run
if !File::exists?(output_filename)
#generate FontCache.hpp
super
end
end
end
@@ -0,0 +1,336 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
class FontsCpp
def self.font_convert=(font_convert)
@@font_convert = font_convert
end
def initialize(text_entries, typographies, languages, output_directory, font_asset_path, autohint_setting, data_format, generate_binary_fonts, generate_font_format, korean_fusion_fonts, compressed_font_cache_size)
@typographies = typographies
@languages = languages
@output_directory = output_directory
@font_asset_path = font_asset_path
@autohint_setting = autohint_setting
@data_format = data_format
@generate_binary_fonts = generate_binary_fonts
@generate_font_format = generate_font_format
@korean_fusion_fonts = korean_fusion_fonts
@compressed_font_cache_size = compressed_font_cache_size
end
def run
context_tables_is_generated = {}
#find unique typographies, but ignore all vector typographies
unique_typographies = @typographies.select{|t| not t.is_vector}.map{ |t| Typography.new("", t.font_file, t.font_size, t.bpp, t.is_vector, t.is_compressed, t.fallback_character, t.ellipsis_character) }.uniq
#remove old unused Table, Kerning, Font files for non-vector typographies
#1. Create a list of font names
font_names = unique_typographies.collect do |typography|
"#{typography.cpp_name}_#{typography.font_size}_#{typography.bpp}bpp"
end
local_path = "#{@output_directory}/src".gsub('\\','/')
#2, scan for Kerning files, delete files not using a font in font_names
Dir["#{local_path}/Kerning_*.cpp"].each do |kerning_file|
if font_names.none? {|font_name| kerning_file == "#{local_path}/Kerning_#{font_name}.cpp" }
FileUtils.rm(kerning_file)
end
end
#3, scan for Table files
Dir["#{local_path}/Table_*.cpp"].each do |table_file|
if font_names.none? {|font_name| table_file == "#{local_path}/Table_#{font_name}.cpp" }
FileUtils.rm(table_file)
end
end
#4, scan for Font files, remove unused
Dir["#{local_path}/Font_*.cpp"].each do |font_file|
if font_names.none? {|font_name| font_file.match /#{local_path}\/Font_#{font_name}_\d+.cpp/ }
FileUtils.rm(font_file)
end
end
#5, scan for cache files
local_path = "#{@output_directory}/cache".gsub('\\','/')
Dir["#{local_path}/Font_*Cpp.cache"].each do |cache_file|
if font_names.none? {|font_name| cache_file == "#{local_path}/Font_#{font_name}Cpp.cache" }
FileUtils.rm(cache_file)
end
end
Dir["#{local_path}/CompressedFontBuffer_*.cache"].each do |cache_file|
if font_names.none? {|font_name| cache_file == "#{local_path}/CompressedFontBuffer_#{font_name}.cache" }
FileUtils.rm(cache_file)
end
end
generate_contextual_table = false
unique_typographies.sort_by { |t| sprintf("%s %04d %d",t.font_file,t.font_size,t.bpp) }.each do |typography|
fonts_directory = @output_directory
font_file = "#{@font_asset_path}/#{typography.font_file}"
font_index = get_font_index(typography)
fallback_char = typography[:fallback_character]
fallback_char ||= 0
ellipsis_char = typography[:ellipsis_character]
ellipsis_char ||= 0
autohint = @autohint_setting == "no" ? "-nah" : @autohint_setting == "force" ? "-fah" : ""
byte_align = @data_format.match("A#{typography.bpp}") ? "-ba" : ""
compression = typography.is_compressed ? "-z" : ""
#generate contextual forms table for font if not already done
generate_contextual_table = context_tables_is_generated[typography.cpp_name] ? "no" : "yes"
context_tables_is_generated[typography.cpp_name] = true #set done for next font with this name
fontname = "#{typography.cpp_name}_#{typography.font_size}_#{typography.bpp}bpp"
fusion = "0"
@korean_fusion_fonts.each do |fontmap|
if fontmap.keys.any?(fontname)
width = fontmap[fontname]["width"]
height = fontmap[fontname]["height"]
baseline = fontmap[fontname]["baseline"]
advance = fontmap[fontname]["advance"]
fail "ERROR: \"width\" is unspecified for fused font #{fontname}" if width.nil?
fail "ERROR: \"height\" is unspecified for fused font #{fontname}" if height.nil?
fail "ERROR: \"baseline\" is unspecified for fused font #{fontname}" if baseline.nil?
fail "ERROR: \"advance\" is unspecified for fused font #{fontname}" if advance.nil?
fusion = "1 #{width} #{height} #{baseline} #{advance}"
end
end
cmd = "\"#{@@font_convert}\" \
-f \"#{font_file}\" \
-i #{font_index} \
-w #{typography.font_size} \
-r #{typography.font_size} \
-o \"#{fonts_directory}\" \
-c \"#{@output_directory}/UnicodeList#{typography.cpp_name}_#{typography.font_size}_#{typography.bpp}.txt\" \
-n \"#{typography.cpp_name}\" \
-b #{typography.bpp} \
-v 0 \
-d #{fallback_char} \
-e #{ellipsis_char} \
-ct #{generate_contextual_table} \
-bf #{@generate_binary_fonts} \
-ff #{@generate_font_format} \
-ffu #{fusion} \
#{compression} \
#{autohint} \
#{byte_align}"
output = `#{cmd}`.force_encoding('iso-8859-1')
if !$?.success?
puts "Command: #{cmd}"
puts output
fail "ERROR: While generating font from #{font_file}"
else
puts "Command: #{cmd}" if ENV['DEBUG']
puts output if output.length > 0
end
end
#loop through generated/fonts/cache/CompressedFontBuffer_verdana_140_4bpp.cache files, find the max numbers
@max_size = 0
@max_font = ""
local_path = "#{@output_directory}/cache".gsub('\\','/')
Dir["#{local_path}/CompressedFontBuffer_*.cache"].each do |cache_file|
match = /CompressedFontBuffer_(.*).cache/.match(cache_file)
font_name = match[1]
value = File.read(cache_file)
buffer_size = value.to_i
if buffer_size > @max_size
@max_size = buffer_size
@max_font = font_name
end
end
if @max_size > @compressed_font_cache_size
fail "\nERROR: Compressed Font Cache Size is too small. Font #{@max_font} requires a cache of minimum #{@max_size} bytes.\n\n"
end
############# VECTOR FONTS NOW #############
#find unique vector typographies, but only vector typographies
unique_typographies = @typographies.select{|t| t.is_vector}.map{ |t| Typography.new("", t.font_file, t.font_size, t.bpp, t.is_vector, t.is_compressed, t.fallback_character, t.ellipsis_character) }.uniq
did_generate_vector_font_contour_table = {} #true if we have generated the vector font contour for this ttf
#remove old unused Table, Kerning, Font files for vector typographies
#1. Create a list of font names and font_size names
font_names = unique_typographies.collect{ |typography| "#{typography.cpp_name}" }.uniq
font_size_names = unique_typographies.collect { |typography| "#{typography.cpp_name}_#{typography.font_size}" }.uniq
local_path = "#{@output_directory}/src".gsub('\\','/')
#2, scan for Kerning files, delete files not using a font in font_names
Dir["#{local_path}/Vector_Kerning_*.cpp"].each do |kerning_file|
if font_names.none? {|font_name| kerning_file == "#{local_path}/Vector_Kerning_#{font_name}.cpp" }
FileUtils.rm(kerning_file)
end
end
#3, scan for Table files for base font or specific sizes
base_and_typos = font_names + font_size_names
Dir["#{local_path}/Vector_Table_*.cpp"].each do |table_file|
if base_and_typos.none? {|name| table_file == "#{local_path}/Vector_Table_#{name}.cpp" }
FileUtils.rm(table_file)
end
end
#4, scan for Font files, remove unused
Dir["#{local_path}/Vector_Font_*.cpp"].each do |font_file|
if font_names.none? {|font_name| font_file.match /#{local_path}\/Vector_Font_#{font_name}_\d+.cpp/ }
FileUtils.rm(font_file)
end
end
#5, scan for cache files
local_path = "#{@output_directory}/cache".gsub('\\','/')
Dir["#{local_path}/Vector_Font_*Cpp.cache"].each do |cache_file|
if font_names.none? {|font_name| cache_file == "#{local_path}/Vector_Font_#{font_name}Cpp.cache" }
FileUtils.rm(cache_file)
end
end
Dir["#{local_path}/Vector_Font_*_buffer.cache"].each do |cache_file|
if font_names.none? {|font_name| cache_file == "#{local_path}/Vector_Font_#{font_name}_buffer.cache" }
FileUtils.rm(cache_file)
end
end
#reset list of generated contextual tables, we need them again for vector
context_tables_is_generated = {}
unique_typographies.sort_by { |t| sprintf("%s %04d %d",t.font_file,t.font_size,t.bpp) }.each do |typography|
fonts_directory = @output_directory
font_file = "#{@font_asset_path}/#{typography.font_file}"
font_index = get_font_index(typography)
fallback_char = typography[:fallback_character]
fallback_char ||= 0
ellipsis_char = typography[:ellipsis_character]
ellipsis_char ||= 0
autohint = @autohint_setting == "no" ? "-nah" : @autohint_setting == "force" ? "-fah" : ""
#generate contextual forms table for font if not already done
generate_contextual_table = context_tables_is_generated[typography.cpp_name] ? "no" : "yes"
context_tables_is_generated[typography.cpp_name] = true #set done for next font with this name
if did_generate_vector_font_contour_table[typography.cpp_name] #test if we asked for the vector font contour table
unicode_list_name = "none"
else
unicode_list_name = "#{@output_directory}/VectorUnicodeList#{typography.cpp_name}.txt"
did_generate_vector_font_contour_table[typography.cpp_name] = true
end
cmd = "\"#{@@font_convert}\" \
-f \"#{font_file}\" \
-i #{font_index} \
-w #{typography.font_size} \
-r #{typography.font_size} \
-o \"#{fonts_directory}\" \
-c \"#{unicode_list_name}\" \
-n \"#{typography.cpp_name}\" \
-v 1 \
-d #{fallback_char} \
-e #{ellipsis_char} \
-ct #{generate_contextual_table} \
#{autohint}"
output = `#{cmd}`.force_encoding('iso-8859-1')
if !$?.success?
puts "Command: #{cmd}"
puts output
fail "ERROR: While generating font from #{font_file}"
else
puts "Command: #{cmd}" if ENV['DEBUG']
puts output if output.length > 0
end
end
#loop through Vector_Font_Verdata_buffer.cache files, find the max numbers
@max_floats = 0
@max_commands = 0
@max_unicode = 0
@max_font = ""
local_path = "#{@output_directory}/cache".gsub('\\','/')
Dir["#{local_path}/Vector_Font_*_buffer.cache"].each do |cache_file|
match = /Vector_Font_(.*)_buffer.cache/.match(cache_file)
font_name = match[1]
values = File.read(cache_file)
match = /(\d*) (\d*) unicode: (\d*)/.match(values)
floats = match[1].to_i
commands = match[2].to_i
unicode = match[3].to_i
if floats > @max_floats
@max_floats = floats
@max_unicode = unicode
@max_font = font_name
end
if commands > @max_commands
@max_commands = commands
@max_unicode = unicode
@max_font = font_name
end
end
#compare values to cache and write VectorFontRendererBuffers.cpp if not equal
@cache = {}
@cache["max_floats"] = @max_floats
@cache["max_commands"] = @max_commands
@cache["max_font"] = @max_font
new_cache_file = false
if not File::exists?(cache_file)
new_cache_file = true
else
#cache file exists, compare data to cache file
old_cache = JSON.parse(File.read(cache_file))
new_cache_file = (old_cache != @cache)
end
if new_cache_file
#write new cache file
FileIO.write_file_silent(cache_file, @cache.to_json)
end
if !File::exists?(output_filename) || new_cache_file
#generate VectorFontRendererBuffers.cpp
result = ERB.new(File.read(input_path).gsub(WINDOWS_LINE_ENDINGS, UNIX_LINE_ENDINGS),0,"<>").
result(binding).
gsub(WINDOWS_LINE_ENDINGS, UNIX_LINE_ENDINGS).
gsub(UNIX_LINE_ENDINGS, LINE_ENDINGS)
FileIO.write_file(File.join(@output_directory, output_path), result)
end
end
def input_path
File.join(root_dir,'Templates','VectorFontRendererBuffers.cpp.temp')
end
def output_path
'src/VectorFontRendererBuffers.cpp'
end
def output_filename
File.join(@output_directory, output_path)
end
def cache_file
File.join(@output_directory, 'cache/VectorFontRendererBuffers.cache')
end
def get_num_floats
@max_floats
end
def get_num_commands
@max_commands
end
def fonts
@cached_fonts ||=
@typographies.map{ |t| Typography.new("", t.font_file, t.font_size, t.bpp, t.is_vector) }.uniq.collect { |t| get_getFont_name(t) }
end
def fontmap
@cached_fontmap ||=
fonts.each_with_index.inject(Hash.new) { |map, (f, i)| map[f] = i; map }
end
def get_font_index(typography)
fontmap[get_getFont_name(typography)]
end
end
@@ -0,0 +1,27 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
class GeneratedFontCpp < Template
def input_path
File.join(root_dir,'Templates','GeneratedFont.cpp.temp')
end
def output_path
'/src/GeneratedFont.cpp'
end
def output_filename
File.join(@output_directory, output_path)
end
def run
if !File::exists?(output_filename)
#generate GeneratedFont.cpp
super
end
end
end
@@ -0,0 +1,27 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
class GeneratedFontHpp < Template
def input_path
File.join(root_dir,'Templates','GeneratedFont.hpp.temp')
end
def output_path
'/include/fonts/GeneratedFont.hpp'
end
def output_filename
File.join(@output_directory, output_path)
end
def run
if !File::exists?(output_filename)
#generate GeneratedFont.hpp
super
end
end
end
@@ -0,0 +1,284 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
require 'json'
class LanguagesBin
def initialize(text_entries, typographies, languages, output_directory)
@text_entries = text_entries
@typographies = typographies
@languages = languages
@output_directory = output_directory
end
def run
#remove_old_binary_files
@languages.each do |language|
LanguageXxBin.new(@text_entries, @typographies, @output_directory, @languages, language).run
end
end
private
def remove_old_binary_files
# Remove any old bin file
local_path = @output_directory.gsub('\\','/')
Dir["#{local_path}/binary/Language*.bin"].each do |language_bin_file|
puts "Deleting #{language_bin_file}"
FileUtils.rm(language_bin_file)
end
end
end
class LanguageXxBin < Template
Presenter = Struct.new(:text_id, :unicodes)
LanguageHeader = Struct.new(:offset_to_texts, :offset_to_indices, :offset_to_typedtext)
TypedTextPresenter = Struct.new(:alignment, :direction, :typography)
ALIGNMENT = { "LEFT" => 0, "CENTER" => 1, "RIGHT" => 2 }
TEXT_DIRECTION = { "LTR" => 0, "RTL" => 1 }
def initialize(text_entries, typographies, output_directory, languages, language)
@languages = languages
@language = language
@typographies = typographies
@text_entries = text_entries
@output_directory = output_directory
@cache = {}
end
def cache_file
File.join(@output_directory, "cache/LanguageBin_#{@language.capitalize}.cache")
end
def alignment_to_value(alignment)
ALIGNMENT[alignment.to_s]
end
def text_direction_to_value(direction)
TEXT_DIRECTION[direction.to_s]
end
def language
@language
end
def entries
#only generate entries once
@cached_entries ||=
begin
entries = text_entries
entries = handle_no_entries(entries, "DO_NOT_USE")
present(entries)
end
end
def entries_texts_const_initialization
entries.map { |entry| " #{entry.text_id}_#{language.capitalize}" }.join(",\n")
end
def input_path
File.join(@output_directory, output_path)
end
def output_path
"binary/Language#{language.capitalize}.bin"
end
def typed_texts(language)
text_entries.collect do |entry|
typography_name = entry.typographies[language] || entry.default_typography
typography = typographies.find { |t| t.name == typography_name }
alignment = entry.alignments[language] || entry.default_alignment
direction = entry.directions[language] || entry.default_direction
TypedTextPresenter.new(alignment, direction, typography);
end
end
def typed_texts_(language)
typed_text_str = typed_texts(language)
puts "typed_text_str = #{typed_text_str}"
end
def fonts
typographies.map{ |t| Typography.new("", t.font_file, t.font_size, t.bpp, t.is_vector) }.uniq.collect do |t|
get_getFont_name(t)
end
end
def get_font_index(typography)
fontmap[get_getFont_name(typography)]
end
def fontmap
fontmap = Hash.new
fonts.each_with_index do |f, i|
fontmap[f] = i
end
fontmap
end
def header(entries)
nb_entries = 0
header_struct_size = 12
header_unicodes_size = 0;
offset_to_texts = 0
offset_to_indices = 0
offset_to_typedtext = 0
entries.each do |entry|
nb_entries += 1
entry.unicodes.split(', ').each { |c|
header_unicodes_size += 2
}
end
offset_to_texts = header_struct_size
offset_to_indices = ((offset_to_texts + header_unicodes_size + 3) &~ 0x3)
offset_to_typedtext = ((offset_to_indices + (4 * nb_entries) + 3) &~ 0x3)
#puts "Number of Entries = #{nb_entries}"
#puts "Header size = #{header_struct_size}"
#puts "Unicodes size = #{header_unicodes_size}"
#puts "Text Offset = #{offset_to_texts}"
#puts "Indices Offset = #{offset_to_indices}"
#puts "TypedText Offset = #{offset_to_typedtext}"
LanguageHeader.new('0x' + offset_to_texts.to_s(16), '0x' + offset_to_indices.to_s(16), '0x' + offset_to_typedtext.to_s(16))
end
def output_filename
File.join(@output_directory, output_path)
end
def run
#build cache dictionary
@cache["typographies"] = typographies.collect{|t| [t.name, t.font_file, t.font_size, t.bpp] }
@cache["language"] = @language
@cache["language_index"] = @languages.index(@language)
list = [] #list of index,textid
entries.each_with_index do |entry, index|
list[index] = [entry.unicodes, entry.text_id]
end
@cache["indices"] = list
new_cache_file = false
if not File::exists?(cache_file)
new_cache_file = true
else
#cache file exists, compare data with cache file
old_cache = JSON.parse(File.read(cache_file))
new_cache_file = (old_cache != @cache)
end
if new_cache_file
#write new cache file
FileIO.write_file_silent(cache_file, @cache.to_json)
end
if !File::exists?(output_filename) || new_cache_file
#generate LanguageXX.bin
FileUtils.mkdir_p(File.dirname(input_path))
callingPath = Pathname.new($calling_path)
filePath = Pathname.new(input_path)
puts "Generating #{filePath.relative_path_from(callingPath)}"
File.open(input_path,'wb') do |f|
# create indices array
indices_arr = []
# Writing Language Header
lang_header = header(entries)
lang_header.each { |c|
f.write [c.to_i(16)].pack("L")
}
# Writing Texts data
indices_arr.clear
indices_arr << 0 # first element is @ offset zero
nb_data_in_entry = 0
entries.each do |entry|
#puts "All Unicodes #{entry.unicodes}"
entry.unicodes.split(', ').each { |c|
f.write [c.to_i(16)].pack("S")
nb_data_in_entry += 1
}
indices_arr << nb_data_in_entry #populate the indices array
end
# Add padding to align on word size for next indeces data writing
loop do
if Integer(f.pos) == Integer(lang_header.offset_to_indices)
break
end
f.write ["0x00".to_i(16)].pack("S")
end
# Remove last indice
indices_arr.pop
# Writing Indices
indices_arr.each { |idx| f.write [idx].pack("L") }
# Add padding to align on word size for next typed_text data writing
loop do
if Integer(f.pos) == Integer(lang_header.offset_to_typedtext)
break
end
f.write ["0x00".to_i(16)].pack("S")
end
# Create and Fill TypedTextsData Array
typed_text_arr = []
if typed_texts(language).empty?
#puts " { #{0}, #{alignment_to_value("LEFT")}, #{text_direction_to_value("LTR")} }"
typed_text_arr << 0 << alignment_to_value("LEFT") << text_direction_to_value("LTR")
else
typed_texts(language).map do |typed_text|
fontIdx = get_font_index(typed_text.typography)
alignment = alignment_to_value(typed_text.alignment.upcase)
direction = text_direction_to_value(typed_text.direction.upcase)
#puts "Font Index --> #{fontIdx}"
#puts "Alignment --> #{typed_text.alignment.upcase}"
#puts "Text Direction --> #{typed_text.direction.upcase}"
#puts " { #{fontIdx}, #{alignment_to_value(typed_text.alignment.upcase)}, #{text_direction_to_value(typed_text.direction.upcase)} }"
combined = direction.to_s(2).to_i(2) * 4 + alignment.to_s(2).to_i(2)
typed_text_arr << fontIdx << combined
end
end
# Writing TypedTextsData
typed_text_arr.each do |idx|
f.write [idx].pack("C")
end
# # Add padding to align the binary file size on word size
loop do
if (f.pos & 0x3) == 0
break
end
f.write ["0x00".to_i(16)].pack("C")
end
end
end
end
private
def handle_no_entries(entries, text)
if entries.empty?
empty_entry = TextEntry.new(text, "typography", "left", "ltr")
empty_entry.add_translation(language, "")
[empty_entry]
else
entries
end
end
def present(entries)
entries.map do |entry|
Presenter.new(entry.cpp_text_id, ( entry.translation_in(language).unicodes.map { |u| '0x' + u.to_s(16) } ).join(', ') )
end
end
end
@@ -0,0 +1,162 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
require 'json'
require 'lib/string_collector'
class LanguagesCpp
def initialize(string_indices, characters, text_entries, languages, output_directory, remap_global, generate_binary_translations, copy_translations_to_ram)
@string_indices = string_indices #dictionary of all string indices into the characters array
@characters = characters
@text_entries = text_entries
@languages = languages
@output_directory = output_directory
@remap_global = remap_global
@generate_binary_translations = generate_binary_translations
@copy_translations_to_ram = copy_translations_to_ram
end
def run
# First remove any unused LanguageXX.cpp files (ie. remove
# LanguageGB.cpp before creating LanguageGb.cpp on windows which
# ignores case on filenames)
Dir.glob("#{@output_directory}/src/Language*.cpp").each do |file|
m = /Language(.*).cpp/.match(file)
xx = m[1]
if !@languages.any? { |l| l.capitalize == xx }
File.delete(file) if File.exist?(file)
end
end
@languages.each_with_index do |language, language_index|
language_index = 0 if @remap_global=="yes"
LanguageXxCpp.new(@string_indices, language_index, @characters, @text_entries, @output_directory, @remap_global, @generate_binary_translations, @copy_translations_to_ram, language).run
end
end
end
class LanguageXxCpp < Template
Presenter = Struct.new(:text_id, :int_array)
def initialize(string_indices, language_index, characters, text_entries, output_directory, remap_global, generate_binary_translations, copy_translations_to_ram, language)
@string_indices = string_indices #dictionary of all string indices into the characters array
@characters = characters
@remap_global = remap_global
@generate_binary_translations = generate_binary_translations
@copy_translations_to_ram = copy_translations_to_ram
@language = language
@language_index = language_index
super(text_entries, [], [], output_directory)
@cache = {}
end
def cache_file
File.join(@output_directory, "cache/LanguageCpp_#{@language.capitalize}.cache")
end
def input_path
File.join(root_dir, 'Templates', generate_binary_files? ? 'LanguageXX.cpp.bin.temp' : 'LanguageXX.cpp.temp')
end
def output_path
"src/Language#{get_language}.cpp"
end
def output_filename
File.join(@output_directory, output_path)
end
def texts
@text_entries.entries.map(&:cpp_text_id)
end
def run
#build cache dictionary
@cache["remap"] = @remap_global
@cache["language"] = @language
@cache["language_index"] = @language_index
@cache["copy_translations"] = @copy_translations_to_ram
#save text ids and index
@cache["indices"] = get_text_entries.collect do |entry|
[get_string_index(entry), entry.text_id]
end #list of index,textid
if @remap_global!="yes"
@cache["characters"] = @characters[@language_index]
end
@cache["generate_binary_translations"] = @generate_binary_translations
new_cache_file = false
if not File::exists?(cache_file)
new_cache_file = true
else
#cache file exists, compare data with cache file
old_cache = JSON.parse(File.read(cache_file))
new_cache_file = (old_cache != @cache)
end
if new_cache_file
#write new cache file
FileIO.write_file_silent(cache_file, @cache.to_json)
end
if !File::exists?(output_filename) || new_cache_file
#generate LanguageXX.cpp
super
end
end
private
def remap_global?
@remap_global=="yes"
end
def get_language
@language.capitalize
end
def get_text_entries
#only generate entries once
@cached_entries ||=
begin
present(@text_entries)
end
end
def get_string_index_spaces(entry)
last = get_text_entries.length-1
@max_length ||= get_text_entries.each_with_index.map { |e,i| get_string_index(e).length + (i < last || last == 0 ? 1 : 0) }.max
' ' * (@max_length - get_string_index(entry).to_s.length)
end
def get_string_index(entry)
index = @string_indices[@language_index][entry.int_array]
index ||= 0
index.to_s
end
def generate_binary_files?
@generate_binary_translations=="yes"
end
def present(entries)
entries.map do |entry|
Presenter.new(entry.cpp_text_id, entry.translation_in(@language).unicodes)
end
end
def get_substrings_and_offsets(lang_index)
unicode_array_to_hex_offset_comment(@characters[lang_index])
end
#indeces arrays are in internal flash when copying, but texts are in TEXT_LOCATION
def get_pragma
@pragma ||= @copy_translations_to_ram=="yes" ? "" : "TEXT_LOCATION_FLASH_PRAGMA\r\n"
end
def get_attribute
@attribute ||= @copy_translations_to_ram=="yes" ? "" : " TEXT_LOCATION_FLASH_ATTRIBUTE"
end
end
@@ -0,0 +1,33 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
require 'erb'
require 'lib/file_io'
class Template
attr_accessor :text_entries
attr_accessor :typographies
attr_accessor :languages
def initialize(text_entries, typographies, languages, output_directory)
@text_entries = text_entries
@typographies = typographies
@languages = languages
@output_directory = output_directory
end
def run
result = ERB.new(File.read(input_path).gsub(WINDOWS_LINE_ENDINGS, UNIX_LINE_ENDINGS),0,"<>").
result(binding).
gsub(WINDOWS_LINE_ENDINGS, UNIX_LINE_ENDINGS).
gsub(UNIX_LINE_ENDINGS, LINE_ENDINGS)
FileIO.write_file(File.join(@output_directory, output_path), result)
end
end
@@ -0,0 +1,59 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
require 'json'
class TextKeysAndLanguages < Template
def initialize(text_entries, typographies, languages, output_directory)
super(text_entries, typographies, languages, output_directory)
@cache = {}
end
def get_languages
@languages.map(&:upcase)
end
def get_texts
@text_entries.entries.map(&:cpp_text_id)
end
def input_path
File.join(root_dir,'Templates','TextKeysAndLanguages.hpp.temp')
end
def output_path
'include/texts/TextKeysAndLanguages.hpp'
end
def cache_file
File.join(@output_directory, 'cache/TextKeysAndLanguages.cache')
end
def output_filename
File.join(@output_directory, output_path)
end
def run
@cache["languages"] = languages
@cache["textids"] = get_texts
new_cache_file = false
if not File::exists?(cache_file)
new_cache_file = true
else
#cache file exists, compare data with cache file
old_cache = JSON.parse(File.read(cache_file))
new_cache_file = (old_cache != @cache)
end
if new_cache_file
#write new cache file
FileIO.write_file_silent(cache_file, @cache.to_json)
end
if !File::exists?(output_filename) || new_cache_file || $Force_Generate_TextKeysAndLanguages
#generate TextKeysAndLanguages.hpp
super
end
end
end
@@ -0,0 +1,118 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
require 'lib/string_collector'
class TextsCpp < Template
def initialize(characters, text_entries, typographies, languages, output_directory, remap_global, generate_binary_translations, copy_translations_to_ram)
@characters = characters # one array (per language, or all at index 0 if global remapping) of the needed strings in optimal order
@remap_global = remap_global
@generate_binary_translations = generate_binary_translations
@copy_translations_to_ram = copy_translations_to_ram
super(text_entries, typographies, languages, output_directory)
@cache = {}
end
def cache_file
File.join(@output_directory, 'cache/TextsCpp.cache')
end
def input_path
File.join(root_dir,'Templates','Texts.cpp.temp')
end
def output_path
'src/Texts.cpp'
end
def output_filename
File.join(@output_directory, output_path)
end
def run
#build @cache and compare with file if exists
@cache["remap"] = @remap_global
@cache["languages"] = get_capitalized_languages
if @remap_global=="yes"
@cache["characters"] = @characters.flatten
end
@cache["copy_translations_to_ram"] = @copy_translations_to_ram
if @copy_translations_to_ram=="yes"
lengths = {}
@languages.each_with_index do |l, i|
lengths[l] = language_length(i)
end
@cache["lengths"] = lengths
end
@cache["generate_binary_translations"] = @generate_binary_translations
new_cache_file = false
if not File::exists?(cache_file)
new_cache_file = true
else
#cache file exists, compare data with cache file
old_cache = JSON.parse(File.read(cache_file))
new_cache_file = (old_cache != @cache)
end
if new_cache_file
#write new cache file
FileIO.write_file_silent(cache_file, @cache.to_json)
end
if !File::exists?(output_filename) || new_cache_file
#generate TypedTextDatabase.cpp
super
end
end
def remap_global?
@remap_global=="yes"
end
def generate_binary_files?
@generate_binary_translations=="yes"
end
def get_capitalized_languages
@languages.map(&:capitalize)
end
def get_number_of_languages
@languages.count
end
def has_languages?
!@languages.empty?
end
def is_rtl?
text_entries.is_rtl
end
def get_substrings_and_offsets
unicode_array_to_hex_offset_comment(@characters.flatten)
end
def language_length(l)
@characters[l].length
end
def get_max_language_length
@characters.inject(0){|m,c| c.length > m ? c.length : m }
end
def copy_translations?
@copy_translations_to_ram=="yes"
end
def get_pragma
@pragma ||= @copy_translations_to_ram=="yes" ? "" : "TEXT_LOCATION_FLASH_PRAGMA\r\n"
end
def get_attribute
@attribute ||= @copy_translations_to_ram=="yes" ? "" : " TEXT_LOCATION_FLASH_ATTRIBUTE"
end
end
@@ -0,0 +1,185 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
class TypedTextDatabaseCpp < Template
TypedTextPresenter = Struct.new(:alignment, :direction, :typography)
def initialize(text_entries, typographies, languages, output_directory, generate_binary_translations, generate_font_format, copy_translations_to_ram)
super(text_entries, typographies, languages, output_directory)
@generate_binary_translations = generate_binary_translations
@generate_font_format = generate_font_format
@copy_translations_to_ram = copy_translations_to_ram
@cache = {}
end
def cache_file
File.join(@output_directory, 'cache/TypedTextDatabaseCpp.cache')
end
def input_path
File.join(root_dir,'Templates','TypedTextDatabase.cpp.temp')
end
def output_path
'src/TypedTextDatabase.cpp'
end
def output_filename
File.join(@output_directory, output_path)
end
def run
#compute the typed text table once
compute_typed_texts
#calculate the cache map
#first the layout databases
databases = {}
get_layouts.each do |layout|
typed_texts = get_typed_texts(layout)
typed_text_db = []
typed_texts.inject(typed_text_db) do |a, typed_text|
#each text has a font index, alignment and direction
fontIdx = get_font_index(typed_text.typography)
a << [fontIdx, typed_text.alignment.upcase, typed_text.direction.upcase]
end
databases[layout] = typed_text_db
end
#now the list of typed text databases
language_db_list = []
@languages.inject(language_db_list) do |list, lang|
list << (get_layouts.find{|l|l==lang}||'DEFAULT');list
end
@cache["databases"] = databases
@cache["database_list"]=language_db_list
@cache["fonts"] = fontmap
@cache["generate_font_format"] = @generate_font_format
@cache["generate_binary_translations"] = @generate_binary_translations
@cache["copy_translations_to_ram"] = @copy_translations_to_ram
new_cache_file = false
if not File::exists?(cache_file)
new_cache_file = true
else
#cache file exists, compare data with cache file
old_cache = JSON.parse(File.read(cache_file))
new_cache_file = (old_cache != @cache)
end
if new_cache_file
#write new cache file
FileIO.write_file_silent(cache_file, @cache.to_json)
end
if !File::exists?(output_filename) || new_cache_file
#generate TypedTextDatabase.cpp
super
end
end
def has_text_entries?
!text_entries.empty?
end
def get_font_class_names
includes = []
if @generate_font_format == "1"
includes << "UnmappedDataFont"
#see if we have any vector fonts, then we also need the GeneratedVectorFont.hpp include
if typographies.any?{|t| t.is_vector }
includes << "GeneratedFont"
end
else
includes << "GeneratedFont"
end
includes
end
def get_font_class_name(font)
if font=~/^getFont_vector_/
"GeneratedVectorFont"
else
@generate_font_format == "1" ? "UnmappedDataFont" : "GeneratedFont"
end
end
def get_touchgfx_aligment(alignment)
"touchgfx::#{alignment.upcase}"
end
def get_touchgfx_direction(direction)
"touchgfx::TEXT_DIRECTION_#{direction.upcase}"
end
def get_font_index(typography)
fontmap[get_getFont_name(typography)]
end
def get_fonts
@cached_fonts ||=
begin
typographies.map{ |t| Typography.new("", t.font_file, t.font_size, t.bpp, t.is_vector) }.uniq.collect do |t|
get_getFont_name(t)
end
end
end
def get_typed_texts(layout)
@typed_texts[layout]
end
def get_layouts
@cached_layouts ||=
begin
if text_entries.empty?
["DEFAULT"]
else
(text_entries.languages_with_specific_settings << "DEFAULT").uniq
end
end
end
def get_languages
@languages
end
def compute_typed_texts
@typed_texts = {}
get_layouts.each do |layout|
@typed_texts[layout] = text_entries.collect do |entry|
typography_name = entry.typographies[layout] || entry.default_typography
typography = typographies.find { |t| t.name == typography_name }
alignment = entry.alignments[layout] || entry.default_alignment
direction = entry.directions[layout] || entry.default_direction
TypedTextPresenter.new(alignment, direction, typography);
end
end
end
def generate_binary_files?
@generate_binary_translations=="yes"
end
def fontmap
@fontmap ||=
begin
@fontmap = Hash.new
get_fonts.each_with_index do |f, i|
fontmap[f] = i
end
fontmap
end
end
def get_pragma
@pragma ||= @copy_translations_to_ram=="yes" ? "" : "TEXT_LOCATION_FLASH_PRAGMA\r\n"
end
def get_attribute
@attribute ||= @copy_translations_to_ram=="yes" ? "" : " TEXT_LOCATION_FLASH_ATTRIBUTE"
end
end
@@ -0,0 +1,27 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
class TypedTextDatabaseHpp < Template
def input_path
File.join(root_dir,'Templates','TypedTextDatabase.hpp.temp')
end
def output_path
'include/texts/TypedTextDatabase.hpp'
end
def output_filename
File.join(@output_directory, output_path)
end
def run
if !File::exists?(output_filename)
#generate TypedTextDatabase.hpp
super
end
end
end
@@ -0,0 +1,563 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
class UnicodesTxt
def initialize(text_entries, typographies, languages, output_directory)
@text_entries = text_entries
@typographies = typographies
@languages = languages
@output_directory = output_directory
end
def run
#write UnicodeList for non-vector typographies
unique_typographies = @typographies.select{ |t| not t.is_vector}.map{ |t| Typography.new("", t.font_file, t.font_size, t.bpp, t.is_vector) }.uniq.sort_by { |t| sprintf("%s %04d %d",t.font_file,t.font_size,t.bpp) }
unique_typographies.each do |unique_typography|
UnicodeForTypographyTxt.new(@text_entries, @typographies, @output_directory).run_bitmap(unique_typography)
end
#write UnicodeList for vector fonts
unique_fonts = @typographies.select{ |t| t.is_vector}.map{|t| t.font_file}.uniq.sort
unique_fonts.each do |font|
UnicodeForTypographyTxt.new(@text_entries, @typographies, @output_directory).run_vector(font)
end
end
end
class UnicodeForTypographyTxt
def initialize(text_entries, typographies, output_directory)
@text_entries = text_entries
@typographies = typographies
@output_directory = output_directory
end
def convert_to_contextual_forms(unicodes)
unicodes.sort!
[
[[0x0621],[0xFE80]], # ARABIC LETTER HAMZA
[[0x0622],[0xFE81,0xFE82]], # ARABIC LETTER ALEF WITH MADDA ABOVE
[[0x0622,0x0644],[0xFEF5,0xFEF6]], # ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE
[[0x0623],[0xFE83,0xFE84]], # ARABIC LETTER ALEF WITH HAMZA ABOVE
[[0x0623,0x0644],[0xFEF7,0xFEF8]], # ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE
[[0x0624],[0xFE85,0xFE86]], # ARABIC LETTER WAW WITH HAMZA ABOVE
[[0x0625],[0xFE87,0xFE88]], # ARABIC LETTER ALEF WITH HAMZA BELOW
[[0x0625,0x0644],[0xFEF9,0xFEFA]], # ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW
[[0x0626],[0xFE89,0xFE8A,0xFE8B,0xFE8C]], # ARABIC LETTER YEH WITH HAMZA ABOVE
[[0x0626,0x0627],[0xFBEA,0xFBEB]], # ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ALEF
[[0x0626,0x062C],[0xFC00,0xFC97]], # ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH JEEM
[[0x0626,0x062D],[0xFC01,0xFC98]], # ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH HAH
[[0x0626,0x062E],[0xFC99]], # ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH KHAH
[[0x0626,0x0631],[0xFC64]], # ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH REH
[[0x0626,0x0632],[0xFC65]], # ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ZAIN
[[0x0626,0x0645],[0xFC02,0xFC66,0xFC9A,0xFCDF]], # ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH MEEM
[[0x0626,0x0646],[0xFC67]], # ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH NOON
[[0x0626,0x0647],[0xFC9B,0xFCE0]], # ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH HEH
[[0x0626,0x0648],[0xFBEE,0xFBEF]], # ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH WAW
[[0x0626,0x0649],[0xFBF9,0xFBFA,0xFBFB,0xFC03,0xFC68]], # ARABIC LIGATURE UIGHUR KIRGHIZ YEH WITH HAMZA ABOVE WITH ALEF MAKSURA / ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ALEF MAKSURA
[[0x0626,0x064A],[0xFC04,0xFC69]], # ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH YEH
[[0x0626,0x06C6],[0xFBF2,0xFBF3]], # ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH OE
[[0x0626,0x06C7],[0xFBF0,0xFBF1]], # ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH U
[[0x0626,0x06C8],[0xFBF4,0xFBF5]], # ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH YU
[[0x0626,0x06D0],[0xFBF6,0xFBF7,0xFBF8]], # ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH E
[[0x0626,0x06D5],[0xFBEC,0xFBED]], # ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH AE
[[0x0627],[0xFE8D,0xFE8E]], # ARABIC LETTER ALEF
[[0x0627,0x0628,0x0631,0x0643],[0xFDF3]], # ARABIC LIGATURE AKBAR
[[0x0627,0x0631,0x0644,0x06CC],[0xFDFC]], # RIAL SIGN
[[0x0627,0x0643],[0xFC37,0xFC80]], # ARABIC LIGATURE KAF WITH ALEF
[[0x0627,0x0644],[0xFEFB,0xFEFC]], # ARABIC LIGATURE LAM WITH ALEF
[[0x0627,0x0644,0x0647],[0xFDF2]], # ARABIC LIGATURE ALLAH
[[0x0627,0x0645],[0xFC88]], # ARABIC LIGATURE MEEM WITH ALEF
[[0x0627,0x064B],[0xFD3C,0xFD3D]], # ARABIC LIGATURE ALEF WITH FATHATAN
[[0x0628],[0xFE8F,0xFE90,0xFE91,0xFE92]], # ARABIC LETTER BEH
[[0x0628,0x062C],[0xFC05,0xFC9C]], # ARABIC LIGATURE BEH WITH JEEM
[[0x0628,0x062D],[0xFC06,0xFC9D]], # ARABIC LIGATURE BEH WITH HAH
[[0x0628,0x062D,0x064A],[0xFDC2]], # ARABIC LIGATURE BEH WITH HAH WITH YEH
[[0x0628,0x062E],[0xFC07,0xFC9E]], # ARABIC LIGATURE BEH WITH KHAH
[[0x0628,0x062E,0x064A],[0xFD9E]], # ARABIC LIGATURE BEH WITH KHAH WITH YEH
[[0x0628,0x0631],[0xFC6A]], # ARABIC LIGATURE BEH WITH REH
[[0x0628,0x0632],[0xFC6B]], # ARABIC LIGATURE BEH WITH ZAIN
[[0x0628,0x0645],[0xFC08,0xFC6C,0xFC9F,0xFCE1]], # ARABIC LIGATURE BEH WITH MEEM
[[0x0628,0x0646],[0xFC6D]], # ARABIC LIGATURE BEH WITH NOON
[[0x0628,0x0647],[0xFCA0,0xFCE2]], # ARABIC LIGATURE BEH WITH HEH
[[0x0628,0x0649],[0xFC09,0xFC6E]], # ARABIC LIGATURE BEH WITH ALEF MAKSURA
[[0x0628,0x064A],[0xFC0A,0xFC6F]], # ARABIC LIGATURE BEH WITH YEH
[[0x0629],[0xFE93,0xFE94]], # ARABIC LETTER TEH MARBUTA
[[0x062A],[0xFE95,0xFE96,0xFE97,0xFE98]], # ARABIC LETTER TEH
[[0x062A,0x062C],[0xFC0B,0xFCA1]], # ARABIC LIGATURE TEH WITH JEEM
[[0x062A,0x062C,0x062D],[0xFD51,0xFD52]], # ARABIC LIGATURE TEH WITH HAH WITH JEEM
[[0x062A,0x062C,0x0645],[0xFD50,0xFD55]], # ARABIC LIGATURE TEH WITH JEEM WITH MEEM / ARABIC LIGATURE TEH WITH MEEM WITH JEEM
[[0x062A,0x062C,0x0649],[0xFDA0]], # ARABIC LIGATURE TEH WITH JEEM WITH ALEF MAKSURA
[[0x062A,0x062C,0x064A],[0xFD9F]], # ARABIC LIGATURE TEH WITH JEEM WITH YEH
[[0x062A,0x062D],[0xFC0C,0xFCA2]], # ARABIC LIGATURE TEH WITH HAH
[[0x062A,0x062D,0x0645],[0xFD53,0xFD56]], # ARABIC LIGATURE TEH WITH HAH WITH MEEM / ARABIC LIGATURE TEH WITH MEEM WITH HAH
[[0x062A,0x062E],[0xFC0D,0xFCA3]], # ARABIC LIGATURE TEH WITH KHAH
[[0x062A,0x062E,0x0645],[0xFD54,0xFD57]], # ARABIC LIGATURE TEH WITH KHAH WITH MEEM / ARABIC LIGATURE TEH WITH MEEM WITH KHAH
[[0x062A,0x062E,0x0649],[0xFDA2]], # ARABIC LIGATURE TEH WITH KHAH WITH ALEF MAKSURA
[[0x062A,0x062E,0x064A],[0xFDA1]], # ARABIC LIGATURE TEH WITH KHAH WITH YEH
[[0x062A,0x0631],[0xFC70]], # ARABIC LIGATURE TEH WITH REH
[[0x062A,0x0632],[0xFC71]], # ARABIC LIGATURE TEH WITH ZAIN
[[0x062A,0x0645],[0xFC0E,0xFC72,0xFCA4,0xFCE3]], # ARABIC LIGATURE TEH WITH MEEM
[[0x062A,0x0645,0x0649],[0xFDA4]], # ARABIC LIGATURE TEH WITH MEEM WITH ALEF MAKSURA
[[0x062A,0x0645,0x064A],[0xFDA3]], # ARABIC LIGATURE TEH WITH MEEM WITH YEH
[[0x062A,0x0646],[0xFC73]], # ARABIC LIGATURE TEH WITH NOON
[[0x062A,0x0647],[0xFCA5,0xFCE4]], # ARABIC LIGATURE TEH WITH HEH
[[0x062A,0x0649],[0xFC0F,0xFC74]], # ARABIC LIGATURE TEH WITH ALEF MAKSURA
[[0x062A,0x064A],[0xFC10,0xFC75]], # ARABIC LIGATURE TEH WITH YEH
[[0x062B],[0xFE99,0xFE9A,0xFE9B,0xFE9C]], # ARABIC LETTER THEH
[[0x062B,0x062C],[0xFC11]], # ARABIC LIGATURE THEH WITH JEEM
[[0x062B,0x0631],[0xFC76]], # ARABIC LIGATURE THEH WITH REH
[[0x062B,0x0632],[0xFC77]], # ARABIC LIGATURE THEH WITH ZAIN
[[0x062B,0x0645],[0xFC12,0xFC78,0xFCA6,0xFCE5]], # ARABIC LIGATURE THEH WITH MEEM
[[0x062B,0x0646],[0xFC79]], # ARABIC LIGATURE THEH WITH NOON
[[0x062B,0x0647],[0xFCE6]], # ARABIC LIGATURE THEH WITH HEH
[[0x062B,0x0649],[0xFC13,0xFC7A]], # ARABIC LIGATURE THEH WITH ALEF MAKSURA
[[0x062B,0x064A],[0xFC14,0xFC7B]], # ARABIC LIGATURE THEH WITH YEH
[[0x062C],[0xFE9D,0xFE9E,0xFE9F,0xFEA0]], # ARABIC LETTER JEEM
[[0x062C,0x062D],[0xFC15,0xFC17,0xFCA7,0xFCA9]], # ARABIC LIGATURE JEEM WITH HAH / ARABIC LIGATURE HAH WITH JEEM
[[0x062C,0x062D,0x0633],[0xFD5C,0xFD5D]], # ARABIC LIGATURE SEEN WITH HAH WITH JEEM / ARABIC LIGATURE SEEN WITH JEEM WITH HAH
[[0x062C,0x062D,0x0645],[0xFD58,0xFD59,0xFD89,0xFD8C]], # ARABIC LIGATURE JEEM WITH MEEM WITH HAH / ARABIC LIGATURE MEEM WITH HAH WITH JEEM / ARABIC LIGATURE MEEM WITH JEEM WITH HAH
[[0x062C,0x062D,0x0646],[0xFDB8,0xFDBD]], # ARABIC LIGATURE NOON WITH JEEM WITH HAH
[[0x062C,0x062D,0x0649],[0xFDA6]], # ARABIC LIGATURE JEEM WITH HAH WITH ALEF MAKSURA
[[0x062C,0x062D,0x064A],[0xFDBE,0xFDBF]], # ARABIC LIGATURE JEEM WITH HAH WITH YEH / ARABIC LIGATURE HAH WITH JEEM WITH YEH
[[0x062C,0x062E],[0xFC19,0xFCAB]], # ARABIC LIGATURE KHAH WITH JEEM
[[0x062C,0x062E,0x0645],[0xFD8E,0xFD92]], # ARABIC LIGATURE MEEM WITH KHAH WITH JEEM / ARABIC LIGATURE MEEM WITH JEEM WITH KHAH
[[0x062C,0x0633],[0xFC1C,0xFCAD,0xFD34]], # ARABIC LIGATURE SEEN WITH JEEM
[[0x062C,0x0633,0x0645],[0xFD61]], # ARABIC LIGATURE SEEN WITH MEEM WITH JEEM
[[0x062C,0x0633,0x0649],[0xFD5E]], # ARABIC LIGATURE SEEN WITH JEEM WITH ALEF MAKSURA
[[0x062C,0x0634],[0xFD09,0xFD25,0xFD2D,0xFD37]], # ARABIC LIGATURE SHEEN WITH JEEM
[[0x062C,0x0634,0x064A],[0xFD69]], # ARABIC LIGATURE SHEEN WITH JEEM WITH YEH
[[0x062C,0x0636],[0xFC22,0xFCB4]], # ARABIC LIGATURE DAD WITH JEEM
[[0x062C,0x0639],[0xFC29,0xFCBA]], # ARABIC LIGATURE AIN WITH JEEM
[[0x062C,0x0639,0x0645],[0xFD75,0xFDC4]], # ARABIC LIGATURE AIN WITH JEEM WITH MEEM
[[0x062C,0x063A],[0xFC2B,0xFCBC]], # ARABIC LIGATURE GHAIN WITH JEEM
[[0x062C,0x0641],[0xFC2D,0xFCBE]], # ARABIC LIGATURE FEH WITH JEEM
[[0x062C,0x0643],[0xFC38,0xFCC4]], # ARABIC LIGATURE KAF WITH JEEM
[[0x062C,0x0644],[0xFC3F,0xFCC9,0xFD83,0xFD84]], # ARABIC LIGATURE LAM WITH JEEM / ARABIC LIGATURE LAM WITH JEEM WITH JEEM
[[0x062C,0x0644,0x0645],[0xFDBA,0xFDBC]], # ARABIC LIGATURE LAM WITH JEEM WITH MEEM
[[0x062C,0x0644,0x064A],[0xFDAC]], # ARABIC LIGATURE LAM WITH JEEM WITH YEH
[[0x062C,0x0645],[0xFC16,0xFC45,0xFCA8,0xFCCE,0xFD8D]], # ARABIC LIGATURE JEEM WITH MEEM / ARABIC LIGATURE MEEM WITH JEEM / ARABIC LIGATURE MEEM WITH JEEM WITH MEEM
[[0x062C,0x0645,0x0646],[0xFD97,0xFD98]], # ARABIC LIGATURE NOON WITH JEEM WITH MEEM
[[0x062C,0x0645,0x0647],[0xFD93]], # ARABIC LIGATURE HEH WITH MEEM WITH JEEM
[[0x062C,0x0645,0x0649],[0xFDA7]], # ARABIC LIGATURE JEEM WITH MEEM WITH ALEF MAKSURA
[[0x062C,0x0645,0x064A],[0xFDA5,0xFDC0]], # ARABIC LIGATURE JEEM WITH MEEM WITH YEH / ARABIC LIGATURE MEEM WITH JEEM WITH YEH
[[0x062C,0x0646],[0xFC4B,0xFCD2]], # ARABIC LIGATURE NOON WITH JEEM
[[0x062C,0x0646,0x0649],[0xFD99]], # ARABIC LIGATURE NOON WITH JEEM WITH ALEF MAKSURA
[[0x062C,0x0646,0x064A],[0xFDC7]], # ARABIC LIGATURE NOON WITH JEEM WITH YEH
[[0x062C,0x0647],[0xFC51,0xFCD7]], # ARABIC LIGATURE HEH WITH JEEM
[[0x062C,0x0649],[0xFD01,0xFD1D]], # ARABIC LIGATURE JEEM WITH ALEF MAKSURA
[[0x062C,0x064A],[0xFC55,0xFCDA,0xFD02,0xFD1E,0xFDAF]], # ARABIC LIGATURE YEH WITH JEEM / ARABIC LIGATURE JEEM WITH YEH / ARABIC LIGATURE YEH WITH JEEM WITH YEH
[[0x062D],[0xFEA1,0xFEA2,0xFEA3,0xFEA4]], # ARABIC LETTER HAH
[[0x062D,0x062E],[0xFC1A]], # ARABIC LIGATURE KHAH WITH HAH
[[0x062D,0x062F,0x0645],[0xFDF4]], # ARABIC LIGATURE MOHAMMAD
[[0x062D,0x0633],[0xFC1D,0xFCAE,0xFD35]], # ARABIC LIGATURE SEEN WITH HAH
[[0x062D,0x0633,0x0645],[0xFD5F,0xFD60]], # ARABIC LIGATURE SEEN WITH MEEM WITH HAH
[[0x062D,0x0634],[0xFD0A,0xFD26,0xFD2E,0xFD38]], # ARABIC LIGATURE SHEEN WITH HAH
[[0x062D,0x0634,0x0645],[0xFD67,0xFD68]], # ARABIC LIGATURE SHEEN WITH HAH WITH MEEM
[[0x062D,0x0634,0x064A],[0xFDAA]], # ARABIC LIGATURE SHEEN WITH HAH WITH YEH
[[0x062D,0x0635],[0xFC20,0xFCB1,0xFD64,0xFD65]], # ARABIC LIGATURE SAD WITH HAH / ARABIC LIGATURE SAD WITH HAH WITH HAH
[[0x062D,0x0635,0x064A],[0xFDA9]], # ARABIC LIGATURE SAD WITH HAH WITH YEH
[[0x062D,0x0636],[0xFC23,0xFCB5]], # ARABIC LIGATURE DAD WITH HAH
[[0x062D,0x0636,0x0649],[0xFD6E]], # ARABIC LIGATURE DAD WITH HAH WITH ALEF MAKSURA
[[0x062D,0x0636,0x064A],[0xFDAB]], # ARABIC LIGATURE DAD WITH HAH WITH YEH
[[0x062D,0x0637],[0xFC26,0xFCB8]], # ARABIC LIGATURE TAH WITH HAH
[[0x062D,0x0637,0x0645],[0xFD71,0xFD72]], # ARABIC LIGATURE TAH WITH MEEM WITH HAH
[[0x062D,0x0641],[0xFC2E,0xFCBF]], # ARABIC LIGATURE FEH WITH HAH
[[0x062D,0x0642],[0xFC33,0xFCC2]], # ARABIC LIGATURE QAF WITH HAH
[[0x062D,0x0642,0x0645],[0xFD7E,0xFDB4]], # ARABIC LIGATURE QAF WITH MEEM WITH HAH
[[0x062D,0x0643],[0xFC39,0xFCC5]], # ARABIC LIGATURE KAF WITH HAH
[[0x062D,0x0644],[0xFC40,0xFCCA]], # ARABIC LIGATURE LAM WITH HAH
[[0x062D,0x0644,0x0645],[0xFD80,0xFD87,0xFD88,0xFDB5]], # ARABIC LIGATURE LAM WITH HAH WITH MEEM / ARABIC LIGATURE LAM WITH MEEM WITH HAH
[[0x062D,0x0644,0x0649],[0xFD82]], # ARABIC LIGATURE LAM WITH HAH WITH ALEF MAKSURA
[[0x062D,0x0644,0x064A],[0xFD81]], # ARABIC LIGATURE LAM WITH HAH WITH YEH
[[0x062D,0x0645],[0xFC18,0xFC46,0xFCAA,0xFCCF,0xFD8A]], # ARABIC LIGATURE HAH WITH MEEM / ARABIC LIGATURE MEEM WITH HAH / ARABIC LIGATURE MEEM WITH HAH WITH MEEM
[[0x062D,0x0645,0x0646],[0xFD95]], # ARABIC LIGATURE NOON WITH HAH WITH MEEM
[[0x062D,0x0645,0x0649],[0xFD5B]], # ARABIC LIGATURE HAH WITH MEEM WITH ALEF MAKSURA
[[0x062D,0x0645,0x064A],[0xFD5A,0xFD8B]], # ARABIC LIGATURE HAH WITH MEEM WITH YEH / ARABIC LIGATURE MEEM WITH HAH WITH YEH
[[0x062D,0x0646],[0xFC4C,0xFCD3]], # ARABIC LIGATURE NOON WITH HAH
[[0x062D,0x0646,0x0649],[0xFD96]], # ARABIC LIGATURE NOON WITH HAH WITH ALEF MAKSURA
[[0x062D,0x0646,0x064A],[0xFDB3]], # ARABIC LIGATURE NOON WITH HAH WITH YEH
[[0x062D,0x0649],[0xFCFF,0xFD1B]], # ARABIC LIGATURE HAH WITH ALEF MAKSURA
[[0x062D,0x064A],[0xFC56,0xFCDB,0xFD00,0xFD1C,0xFDAE]], # ARABIC LIGATURE YEH WITH HAH / ARABIC LIGATURE HAH WITH YEH / ARABIC LIGATURE YEH WITH HAH WITH YEH
[[0x062E],[0xFEA5,0xFEA6,0xFEA7,0xFEA8]], # ARABIC LETTER KHAH
[[0x062E,0x0633],[0xFC1E,0xFCAF,0xFD36]], # ARABIC LIGATURE SEEN WITH KHAH
[[0x062E,0x0633,0x0649],[0xFDA8]], # ARABIC LIGATURE SEEN WITH KHAH WITH ALEF MAKSURA
[[0x062E,0x0633,0x064A],[0xFDC6]], # ARABIC LIGATURE SEEN WITH KHAH WITH YEH
[[0x062E,0x0634],[0xFD0B,0xFD27,0xFD2F,0xFD39]], # ARABIC LIGATURE SHEEN WITH KHAH
[[0x062E,0x0634,0x0645],[0xFD6A,0xFD6B]], # ARABIC LIGATURE SHEEN WITH MEEM WITH KHAH
[[0x062E,0x0635],[0xFCB2]], # ARABIC LIGATURE SAD WITH KHAH
[[0x062E,0x0636],[0xFC24,0xFCB6]], # ARABIC LIGATURE DAD WITH KHAH
[[0x062E,0x0636,0x0645],[0xFD6F,0xFD70]], # ARABIC LIGATURE DAD WITH KHAH WITH MEEM
[[0x062E,0x0641],[0xFC2F,0xFCC0]], # ARABIC LIGATURE FEH WITH KHAH
[[0x062E,0x0641,0x0645],[0xFD7C,0xFD7D]], # ARABIC LIGATURE FEH WITH KHAH WITH MEEM
[[0x062E,0x0643],[0xFC3A,0xFCC6]], # ARABIC LIGATURE KAF WITH KHAH
[[0x062E,0x0644],[0xFC41,0xFCCB]], # ARABIC LIGATURE LAM WITH KHAH
[[0x062E,0x0644,0x0645],[0xFD85,0xFD86]], # ARABIC LIGATURE LAM WITH KHAH WITH MEEM
[[0x062E,0x0645],[0xFC1B,0xFC47,0xFCAC,0xFCD0,0xFD8F]], # ARABIC LIGATURE KHAH WITH MEEM / ARABIC LIGATURE MEEM WITH KHAH / ARABIC LIGATURE MEEM WITH KHAH WITH MEEM
[[0x062E,0x0645,0x064A],[0xFDB9]], # ARABIC LIGATURE MEEM WITH KHAH WITH YEH
[[0x062E,0x0646],[0xFC4D,0xFCD4]], # ARABIC LIGATURE NOON WITH KHAH
[[0x062E,0x0649],[0xFD03,0xFD1F]], # ARABIC LIGATURE KHAH WITH ALEF MAKSURA
[[0x062E,0x064A],[0xFC57,0xFCDC,0xFD04,0xFD20]], # ARABIC LIGATURE YEH WITH KHAH / ARABIC LIGATURE KHAH WITH YEH
[[0x062F],[0xFEA9,0xFEAA]], # ARABIC LETTER DAL
[[0x0630],[0xFEAB,0xFEAC]], # ARABIC LETTER THAL
[[0x0630,0x0670],[0xFC5B]], # ARABIC LIGATURE THAL WITH SUPERSCRIPT ALEF
[[0x0631],[0xFEAD,0xFEAE]], # ARABIC LETTER REH
[[0x0631,0x0633],[0xFD0E,0xFD2A]], # ARABIC LIGATURE SEEN WITH REH
[[0x0631,0x0633,0x0644,0x0648],[0xFDF6]], # ARABIC LIGATURE RASOUL
[[0x0631,0x0634],[0xFD0D,0xFD29]], # ARABIC LIGATURE SHEEN WITH REH
[[0x0631,0x0635],[0xFD0F,0xFD2B]], # ARABIC LIGATURE SAD WITH REH
[[0x0631,0x0636],[0xFD10,0xFD2C]], # ARABIC LIGATURE DAD WITH REH
[[0x0631,0x0646],[0xFC8A]], # ARABIC LIGATURE NOON WITH REH
[[0x0631,0x064A],[0xFC91]], # ARABIC LIGATURE YEH WITH REH
[[0x0631,0x0670],[0xFC5C]], # ARABIC LIGATURE REH WITH SUPERSCRIPT ALEF
[[0x0632],[0xFEAF,0xFEB0]], # ARABIC LETTER ZAIN
[[0x0632,0x0646],[0xFC8B]], # ARABIC LIGATURE NOON WITH ZAIN
[[0x0632,0x064A],[0xFC92]], # ARABIC LIGATURE YEH WITH ZAIN
[[0x0633],[0xFEB1,0xFEB2,0xFEB3,0xFEB4]], # ARABIC LETTER SEEN
[[0x0633,0x0644,0x0645,0x0648],[0xFDF8]], # ARABIC LIGATURE WASALLAM
[[0x0633,0x0645],[0xFC1F,0xFCB0,0xFCE7,0xFD62,0xFD63]], # ARABIC LIGATURE SEEN WITH MEEM / ARABIC LIGATURE SEEN WITH MEEM WITH MEEM
[[0x0633,0x0647],[0xFCE8,0xFD31]], # ARABIC LIGATURE SEEN WITH HEH
[[0x0633,0x0649],[0xFCFB,0xFD17]], # ARABIC LIGATURE SEEN WITH ALEF MAKSURA
[[0x0633,0x064A],[0xFCFC,0xFD18]], # ARABIC LIGATURE SEEN WITH YEH
[[0x0634],[0xFEB5,0xFEB6,0xFEB7,0xFEB8]], # ARABIC LETTER SHEEN
[[0x0634,0x0645],[0xFCE9,0xFD0C,0xFD28,0xFD30,0xFD6C,0xFD6D]], # ARABIC LIGATURE SHEEN WITH MEEM / ARABIC LIGATURE SHEEN WITH MEEM WITH MEEM
[[0x0634,0x0647],[0xFCEA,0xFD32]], # ARABIC LIGATURE SHEEN WITH HEH
[[0x0634,0x0649],[0xFCFD,0xFD19]], # ARABIC LIGATURE SHEEN WITH ALEF MAKSURA
[[0x0634,0x064A],[0xFCFE,0xFD1A]], # ARABIC LIGATURE SHEEN WITH YEH
[[0x0635],[0xFEB9,0xFEBA,0xFEBB,0xFEBC]], # ARABIC LETTER SAD
[[0x0635,0x0639,0x0644,0x0645],[0xFDF5]], # ARABIC LIGATURE SALAM
[[0x0635,0x0644,0x0649],[0xFDF9]], # ARABIC LIGATURE SALLA
[[0x0635,0x0644,0x06D2],[0xFDF0]], # ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN
[[0x0635,0x0645],[0xFC21,0xFCB3,0xFD66,0xFDC5]], # ARABIC LIGATURE SAD WITH MEEM / ARABIC LIGATURE SAD WITH MEEM WITH MEEM
[[0x0635,0x0649],[0xFD05,0xFD21]], # ARABIC LIGATURE SAD WITH ALEF MAKSURA
[[0x0635,0x064A],[0xFD06,0xFD22]], # ARABIC LIGATURE SAD WITH YEH
[[0x0636],[0xFEBD,0xFEBE,0xFEBF,0xFEC0]], # ARABIC LETTER DAD
[[0x0636,0x0645],[0xFC25,0xFCB7]], # ARABIC LIGATURE DAD WITH MEEM
[[0x0636,0x0649],[0xFD07,0xFD23]], # ARABIC LIGATURE DAD WITH ALEF MAKSURA
[[0x0636,0x064A],[0xFD08,0xFD24]], # ARABIC LIGATURE DAD WITH YEH
[[0x0637],[0xFEC1,0xFEC2,0xFEC3,0xFEC4]], # ARABIC LETTER TAH
[[0x0637,0x0645],[0xFC27,0xFD33,0xFD3A,0xFD73]], # ARABIC LIGATURE TAH WITH MEEM / ARABIC LIGATURE TAH WITH MEEM WITH MEEM
[[0x0637,0x0645,0x064A],[0xFD74]], # ARABIC LIGATURE TAH WITH MEEM WITH YEH
[[0x0637,0x0649],[0xFCF5,0xFD11]], # ARABIC LIGATURE TAH WITH ALEF MAKSURA
[[0x0637,0x064A],[0xFCF6,0xFD12]], # ARABIC LIGATURE TAH WITH YEH
[[0x0638],[0xFEC5,0xFEC6,0xFEC7,0xFEC8]], # ARABIC LETTER ZAH
[[0x0638,0x0645],[0xFC28,0xFCB9,0xFD3B]], # ARABIC LIGATURE ZAH WITH MEEM
[[0x0639],[0xFEC9,0xFECA,0xFECB,0xFECC]], # ARABIC LETTER AIN
[[0x0639,0x0644,0x0647,0x064A],[0xFDF7]], # ARABIC LIGATURE ALAYHE
[[0x0639,0x0645],[0xFC2A,0xFCBB,0xFD76,0xFD77]], # ARABIC LIGATURE AIN WITH MEEM / ARABIC LIGATURE AIN WITH MEEM WITH MEEM
[[0x0639,0x0645,0x0649],[0xFD78]], # ARABIC LIGATURE AIN WITH MEEM WITH ALEF MAKSURA
[[0x0639,0x0645,0x064A],[0xFDB6]], # ARABIC LIGATURE AIN WITH MEEM WITH YEH
[[0x0639,0x0649],[0xFCF7,0xFD13]], # ARABIC LIGATURE AIN WITH ALEF MAKSURA
[[0x0639,0x064A],[0xFCF8,0xFD14]], # ARABIC LIGATURE AIN WITH YEH
[[0x063A],[0xFECD,0xFECE,0xFECF,0xFED0]], # ARABIC LETTER GHAIN
[[0x063A,0x0645],[0xFC2C,0xFCBD,0xFD79]], # ARABIC LIGATURE GHAIN WITH MEEM / ARABIC LIGATURE GHAIN WITH MEEM WITH MEEM
[[0x063A,0x0645,0x0649],[0xFD7B]], # ARABIC LIGATURE GHAIN WITH MEEM WITH ALEF MAKSURA
[[0x063A,0x0645,0x064A],[0xFD7A]], # ARABIC LIGATURE GHAIN WITH MEEM WITH YEH
[[0x063A,0x0649],[0xFCF9,0xFD15]], # ARABIC LIGATURE GHAIN WITH ALEF MAKSURA
[[0x063A,0x064A],[0xFCFA,0xFD16]], # ARABIC LIGATURE GHAIN WITH YEH
[[0x0640,0x064B],[0xFE71]], # ARABIC TATWEEL WITH FATHATAN ABOVE
[[0x0640,0x064E],[0xFE77]], # ARABIC FATHA
[[0x0640,0x064E,0x0651],[0xFCF2]], # ARABIC LIGATURE SHADDA WITH FATHA
[[0x0640,0x064F],[0xFE79]], # ARABIC DAMMA
[[0x0640,0x064F,0x0651],[0xFCF3]], # ARABIC LIGATURE SHADDA WITH DAMMA
[[0x0640,0x0650],[0xFE7B]], # ARABIC KASRA
[[0x0640,0x0650,0x0651],[0xFCF4]], # ARABIC LIGATURE SHADDA WITH KASRA
[[0x0640,0x0651],[0xFE7D]], # ARABIC SHADDA
[[0x0640,0x0652],[0xFE7F]], # ARABIC SUKUN
[[0x0641],[0xFED1,0xFED2,0xFED3,0xFED4]], # ARABIC LETTER FEH
[[0x0641,0x0645],[0xFC30,0xFCC1]], # ARABIC LIGATURE FEH WITH MEEM
[[0x0641,0x0645,0x064A],[0xFDC1]], # ARABIC LIGATURE FEH WITH MEEM WITH YEH
[[0x0641,0x0649],[0xFC31,0xFC7C]], # ARABIC LIGATURE FEH WITH ALEF MAKSURA
[[0x0641,0x064A],[0xFC32,0xFC7D]], # ARABIC LIGATURE FEH WITH YEH
[[0x0642],[0xFED5,0xFED6,0xFED7,0xFED8]], # ARABIC LETTER QAF
[[0x0642,0x0644,0x06D2],[0xFDF1]], # ARABIC LIGATURE QALA USED AS KORANIC STOP SIGN
[[0x0642,0x0645],[0xFC34,0xFCC3,0xFD7F]], # ARABIC LIGATURE QAF WITH MEEM / ARABIC LIGATURE QAF WITH MEEM WITH MEEM
[[0x0642,0x0645,0x064A],[0xFDB2]], # ARABIC LIGATURE QAF WITH MEEM WITH YEH
[[0x0642,0x0649],[0xFC35,0xFC7E]], # ARABIC LIGATURE QAF WITH ALEF MAKSURA
[[0x0642,0x064A],[0xFC36,0xFC7F]], # ARABIC LIGATURE QAF WITH YEH
[[0x0643],[0xFED9,0xFEDA,0xFEDB,0xFEDC]], # ARABIC LETTER KAF
[[0x0643,0x0644],[0xFC3B,0xFC81,0xFCC7,0xFCEB]], # ARABIC LIGATURE KAF WITH LAM
[[0x0643,0x0645],[0xFC3C,0xFC82,0xFCC8,0xFCEC,0xFDBB,0xFDC3]], # ARABIC LIGATURE KAF WITH MEEM / ARABIC LIGATURE KAF WITH MEEM WITH MEEM
[[0x0643,0x0645,0x064A],[0xFDB7]], # ARABIC LIGATURE KAF WITH MEEM WITH YEH
[[0x0643,0x0649],[0xFC3D,0xFC83]], # ARABIC LIGATURE KAF WITH ALEF MAKSURA
[[0x0643,0x064A],[0xFC3E,0xFC84]], # ARABIC LIGATURE KAF WITH YEH
[[0x0644],[0xFEDD,0xFEDE,0xFEDF,0xFEE0]], # ARABIC LETTER LAM
[[0x0644,0x0645],[0xFC42,0xFC85,0xFCCC,0xFCED]], # ARABIC LIGATURE LAM WITH MEEM
[[0x0644,0x0645,0x064A],[0xFDAD]], # ARABIC LIGATURE LAM WITH MEEM WITH YEH
[[0x0644,0x0647],[0xFCCD]], # ARABIC LIGATURE LAM WITH HEH
[[0x0644,0x0649],[0xFC43,0xFC86]], # ARABIC LIGATURE LAM WITH ALEF MAKSURA
[[0x0644,0x064A],[0xFC44,0xFC87]], # ARABIC LIGATURE LAM WITH YEH
[[0x0645],[0xFC48,0xFC89,0xFCD1,0xFEE1,0xFEE2,0xFEE3,0xFEE4]], # ARABIC LIGATURE MEEM WITH MEEM / ARABIC LETTER MEEM
[[0x0645,0x0646],[0xFC4E,0xFC8C,0xFCD5,0xFCEE]], # ARABIC LIGATURE NOON WITH MEEM
[[0x0645,0x0646,0x0649],[0xFD9B]], # ARABIC LIGATURE NOON WITH MEEM WITH ALEF MAKSURA
[[0x0645,0x0646,0x064A],[0xFD9A]], # ARABIC LIGATURE NOON WITH MEEM WITH YEH
[[0x0645,0x0647],[0xFC52,0xFCD8,0xFD94]], # ARABIC LIGATURE HEH WITH MEEM / ARABIC LIGATURE HEH WITH MEEM WITH MEEM
[[0x0645,0x0649],[0xFC49]], # ARABIC LIGATURE MEEM WITH ALEF MAKSURA
[[0x0645,0x064A],[0xFC4A,0xFC58,0xFC93,0xFCDD,0xFCF0,0xFD9C,0xFD9D,0xFDB0,0xFDB1]], # ARABIC LIGATURE MEEM WITH YEH / ARABIC LIGATURE YEH WITH MEEM / ARABIC LIGATURE YEH WITH MEEM WITH MEEM / ARABIC LIGATURE YEH WITH MEEM WITH YEH / ARABIC LIGATURE MEEM WITH MEEM WITH YEH
[[0x0646],[0xFC8D,0xFEE5,0xFEE6,0xFEE7,0xFEE8]], # ARABIC LIGATURE NOON WITH NOON / ARABIC LETTER NOON
[[0x0646,0x0647],[0xFCD6,0xFCEF]], # ARABIC LIGATURE NOON WITH HEH
[[0x0646,0x0649],[0xFC4F,0xFC8E]], # ARABIC LIGATURE NOON WITH ALEF MAKSURA
[[0x0646,0x064A],[0xFC50,0xFC8F,0xFC94]], # ARABIC LIGATURE NOON WITH YEH / ARABIC LIGATURE YEH WITH NOON
[[0x0647],[0xFEE9,0xFEEA,0xFEEB,0xFEEC]], # ARABIC LETTER HEH
[[0x0647,0x0649],[0xFC53]], # ARABIC LIGATURE HEH WITH ALEF MAKSURA
[[0x0647,0x064A],[0xFC54,0xFCDE,0xFCF1]], # ARABIC LIGATURE HEH WITH YEH / ARABIC LIGATURE YEH WITH HEH
[[0x0647,0x0670],[0xFCD9]], # ARABIC LIGATURE HEH WITH SUPERSCRIPT ALEF
[[0x0648],[0xFEED,0xFEEE]], # ARABIC LETTER WAW
[[0x0649],[0xFBE8,0xFBE9,0xFEEF,0xFEF0]], # ARABIC LETTER UIGHUR KAZAKH KIRGHIZ ALEF MAKSURA / ARABIC LETTER ALEF MAKSURA
[[0x0649,0x064A],[0xFC59,0xFC95]], # ARABIC LIGATURE YEH WITH ALEF MAKSURA
[[0x0649,0x0670],[0xFC5D,0xFC90]], # ARABIC LIGATURE ALEF MAKSURA WITH SUPERSCRIPT ALEF
[[0x064A],[0xFC5A,0xFC96,0xFEF1,0xFEF2,0xFEF3,0xFEF4]], # ARABIC LIGATURE YEH WITH YEH / ARABIC LETTER YEH
[[0x064B],[0xFE70]], # ARABIC FATHATAN
[[0x064C],[0xFE72]], # ARABIC DAMMATAN
[[0x064C,0x0651],[0xFC5E]], # ARABIC LIGATURE SHADDA WITH DAMMATAN
[[0x064D],[0xFE74]], # ARABIC KASRATAN
[[0x064D,0x0651],[0xFC5F]], # ARABIC LIGATURE SHADDA WITH KASRATAN
[[0x064E],[0xFE76]], # ARABIC FATHA
[[0x064E,0x0651],[0xFC60]], # ARABIC LIGATURE SHADDA WITH FATHA
[[0x064F],[0xFE78]], # ARABIC DAMMA
[[0x064F,0x0651],[0xFC61]], # ARABIC LIGATURE SHADDA WITH DAMMA
[[0x0650],[0xFE7A]], # ARABIC KASRA
[[0x0650,0x0651],[0xFC62]], # ARABIC LIGATURE SHADDA WITH KASRA
[[0x0651],[0xFE7C]], # ARABIC SHADDA
[[0x0651,0x0670],[0xFC63]], # ARABIC LIGATURE SHADDA WITH SUPERSCRIPT ALEF
[[0x0652],[0xFE7E]], # ARABIC SUKUN
[[0x0671],[0xFB50,0xFB51]], # ARABIC LETTER ALEF WASLA
[[0x0677],[0xFBDD]], # ARABIC LETTER U WITH HAMZA ABOVE
[[0x0679],[0xFB66,0xFB67,0xFB68,0xFB69]], # ARABIC LETTER TTEH
[[0x067A],[0xFB5E,0xFB5F,0xFB60,0xFB61]], # ARABIC LETTER TTEHEH
[[0x067B],[0xFB52,0xFB53,0xFB54,0xFB55]], # ARABIC LETTER BEEH
[[0x067E],[0xFB56,0xFB57,0xFB58,0xFB59]], # ARABIC LETTER PEH
[[0x067F],[0xFB62,0xFB63,0xFB64,0xFB65]], # ARABIC LETTER TEHEH
[[0x0680],[0xFB5A,0xFB5B,0xFB5C,0xFB5D]], # ARABIC LETTER BEHEH
[[0x0683],[0xFB76,0xFB77,0xFB78,0xFB79]], # ARABIC LETTER NYEH
[[0x0684],[0xFB72,0xFB73,0xFB74,0xFB75]], # ARABIC LETTER DYEH
[[0x0686],[0xFB7A,0xFB7B,0xFB7C,0xFB7D]], # ARABIC LETTER TCHEH
[[0x0687],[0xFB7E,0xFB7F,0xFB80,0xFB81]], # ARABIC LETTER TCHEHEH
[[0x0688],[0xFB88,0xFB89]], # ARABIC LETTER DDAL
[[0x068C],[0xFB84,0xFB85]], # ARABIC LETTER DAHAL
[[0x068D],[0xFB82,0xFB83]], # ARABIC LETTER DDAHAL
[[0x068E],[0xFB86,0xFB87]], # ARABIC LETTER DUL
[[0x0691],[0xFB8C,0xFB8D]], # ARABIC LETTER RREH
[[0x0698],[0xFB8A,0xFB8B]], # ARABIC LETTER JEH
[[0x06A4],[0xFB6A,0xFB6B,0xFB6C,0xFB6D]], # ARABIC LETTER VEH
[[0x06A6],[0xFB6E,0xFB6F,0xFB70,0xFB71]], # ARABIC LETTER PEHEH
[[0x06A9],[0xFB8E,0xFB8F,0xFB90,0xFB91]], # ARABIC LETTER KEHEH
[[0x06AD],[0xFBD3,0xFBD4,0xFBD5,0xFBD6]], # ARABIC LETTER NG
[[0x06AF],[0xFB92,0xFB93,0xFB94,0xFB95]], # ARABIC LETTER GAF
[[0x06B1],[0xFB9A,0xFB9B,0xFB9C,0xFB9D]], # ARABIC LETTER NGOEH
[[0x06B3],[0xFB96,0xFB97,0xFB98,0xFB99]], # ARABIC LETTER GUEH
[[0x06BA],[0xFB9E,0xFB9F]], # ARABIC LETTER NOON GHUNNA
[[0x06BB],[0xFBA0,0xFBA1,0xFBA2,0xFBA3]], # ARABIC LETTER RNOON
[[0x06BE],[0xFBAA,0xFBAB,0xFBAC,0xFBAD]], # ARABIC LETTER HEH DOACHASHMEE
[[0x06C0],[0xFBA4,0xFBA5]], # ARABIC LETTER HEH WITH YEH ABOVE
[[0x06C1],[0xFBA6,0xFBA7,0xFBA8,0xFBA9]], # ARABIC LETTER HEH GOAL
[[0x06C5],[0xFBE0,0xFBE1]], # ARABIC LETTER KIRGHIZ OE
[[0x06C6],[0xFBD9,0xFBDA]], # ARABIC LETTER OE
[[0x06C7],[0xFBD7,0xFBD8]], # ARABIC LETTER U
[[0x06C8],[0xFBDB,0xFBDC]], # ARABIC LETTER YU
[[0x06C9],[0xFBE2,0xFBE3]], # ARABIC LETTER KIRGHIZ YU
[[0x06CB],[0xFBDE,0xFBDF]], # ARABIC LETTER VE
[[0x06CC],[0xFBFC,0xFBFD,0xFBFE,0xFBFF]], # ARABIC LETTER FARSI YEH
[[0x06D0],[0xFBE4,0xFBE5,0xFBE6,0xFBE7]], # ARABIC LETTER E
[[0x06D2],[0xFBAE,0xFBAF]], # ARABIC LETTER YEH BARREE
[[0x06D3],[0xFBB0,0xFBB1]], # ARABIC LETTER YEH BARREE WITH HAMZA ABOVE
[[0],[0]]
].each do |u,l|
if (unicodes & u) == u
unicodes += l
end
end
unicodes.uniq
end
def mirror_brackes(unicodes)
[['<','>'],['(',')'],['[',']'],['{','}']].each do |l,r|
has_l = unicodes.include?(l.ord)
has_r = unicodes.include?(r.ord)
unicodes.push(r.ord) if has_l && !has_r
unicodes.push(l.ord) if has_r && !has_l
end
unicodes
end
def add_thai(unicodes)
# Add Thai unicodes which are used to calculate diacritic positions when the Thai characters have special shapes.
[[0x0E09, 0x0E08],[0x0E13,0x0E0C],[0x0E19,0x0E18],[0x0E1B, 0x0E1A],[0x0E1D,0x0E1C],[0x0E1F,0x0E1E],[0x0E33,0x0E32],[0x0E33,0x0E4D]].each do |has,need|
has_unicode = unicodes.include?(has)
has_needed = unicodes.include?(need)
unicodes.push(need) if has_unicode && !has_needed
end
unicodes
end
def check_for_rtl(unicodes)
return if @text_entries.is_rtl # No need to look for unicode if RTL already detected
# Look for hebrew (0x0590-0x05ff) or arabic (0x0600-0x06ff) + arabic ligatures (0xFE70-0xFEFF)
@text_entries.unicode_uses_rtl if unicodes.any?{|u| u.between?(0x0590, 0x05FF) || u.between?(0x0600, 0x06FF) || u.between?(0xFE70, 0xFEFE) }
end
def decode_ranges(str)
result = []
while str.length > 0
char_range = str.match(/^(.)-(.)(.*)$/)
if char_range
first_char = char_range[1]
last_char = char_range[2]
result += (first_char.ord .. last_char.ord).to_a
str = char_range[3]
else
num_range = str.match(/^(0[xX][0-9a-fA-F]+|\d+)(?:\.0+)?-(0[xX][0-9a-fA-F]+|\d+)(?:\.0+)?(.*)$/)
if num_range
first_num = Integer(num_range[1])
last_num = Integer(num_range[2])
result += (first_num..last_num).to_a
str = num_range[3]
else
num = str.match(/^(0[xX][0-9a-fA-F]+|\d+)(?:\.0+)?(.*)/)
if num
# Check for typo such as 0,2-9
if num[1].length == 1
result += [ num[1].ord ]
else
result += [ Integer(num[1]) ]
end
str = num[2]
else
abort "Unexpected character at #{str}"
end
end
end
if str.length > 0
if str[0] == ','
str = str[1..-1]
else
abort "Please separate wildcard ranges with ','"
end
end
end
result
end
def calculate_unicodes(typographies_input)
typography_names = typographies_input.map(&:name).uniq
# Find a typography with a fallback character
typography_with_fallback_character = typographies_input.find { |t| t.fallback_character }
if typography_with_fallback_character
# Now get the actual fallback character (or 'skip')
typography_fallback_character = typography_with_fallback_character.fallback_character
# Check to see if one of the other typographes has a different fallback character
index = typographies_input.find_index{ |t| t.fallback_character && t.fallback_character != typography_fallback_character }
if index
abort "The fallback character differs for typography \"#{typography_with_fallback_character.name}\" and typography \"#{typographies_input[index].name}\""
end
# set all fallback characters to the same character
typographies_input.each { |t| t.fallback_character = typography_fallback_character }
end
# Find a typography with a ellipsis character
typography_with_ellipsis_character = typographies_input.find { |t| t.ellipsis_character }
if typography_with_ellipsis_character
# Now get the actual ellipsis character (or 'skip')
typography_ellipsis_character = typography_with_ellipsis_character.ellipsis_character
# Check to see if one of the other typographes has a different ellipsis character
index = typographies_input.find_index{ |t| t.ellipsis_character && t.ellipsis_character != typography_ellipsis_character }
if index
abort "The ellipsis character differs for typography \"#{typography_with_ellipsis_character.name}\" and typography \"#{typographies_input[index].name}\""
end
# set all ellipsis characters to the same character
typographies_input.each { |t| t.ellipsis_character = typography_ellipsis_character }
end
all_translations = typography_names.map{ |typography_name| @text_entries.collect{ |entry| entry.translations_with_typography(typography_name) }.flatten }.flatten
unicodes = all_translations.map(&:unicodes).flatten.uniq.sort
typographies_input.each do |t|
fbc = t.fallback_character
fbcUnicode = 0
if fbc
if fbc.downcase == 'skip'
fbcUnicode = 0xFEFF
elsif fbc.length == 1
fbcUnicode = fbc[0].ord
else
begin
fbcUnicode = Integer(fbc.gsub(/\.0*$/,''))
rescue
fail "ERROR: Please only specify one character or ('skip') as Fallback Character, typography \"#{typography_with_fallback_character.name}\" has Fallback Character \"#{typography_with_fallback_character.fallback_character}\""
end
end
unicodes += [ fbcUnicode ]
end
t.fallback_character = fbcUnicode
tec = t.ellipsis_character
tecUnicode = 0
if tec
if tec.length == 1
tecUnicode = tec[0].ord
else
begin
tecUnicode = Integer(tec.gsub(/\.0*$/,''))
rescue
fail "ERROR: Please only specify one character as Ellipsis Character for typography \"#{typography_with_fallback_character.name}\""
end
end
unicodes += [ tecUnicode ]
end
t.ellipsis_character = tecUnicode
end
typographies_input.each{ |t|
if t.wildcard_characters
t.wildcard_characters.to_s.split('').each { |c|
unicodes += [ c[0].ord ]
}
end
if t.widget_wildcard_characters
t.widget_wildcard_characters.to_s.split('').each { |c|
unicodes += [ c[0].ord ]
}
end
if t.wildcard_ranges
unicodes += decode_ranges(t.wildcard_ranges)
end
}
unicodes = convert_to_contextual_forms(unicodes)
unicodes = mirror_brackes(unicodes)
unicodes = add_thai(unicodes)
unicodes.delete(0x0000) # Zero termination of strings
unicodes.delete(0x0002) # TouchGFX wildcard character
unicodes.delete(0x200B) # ZERO WIDTH SPACE
unicodes.delete(0xFEFF) # ZERO WIDTH NO-BREAK SPACE
unicodes = unicodes.uniq.sort
check_for_rtl(unicodes)
#return the unicodes
unicodes
end
def run_bitmap(unique_typography)
typographies_identical = @typographies.select{ |t| t.font_file == unique_typography.font_file &&
t.font_size == unique_typography.font_size &&
t.bpp == unique_typography.bpp &&
t.is_vector == false}
unicodes = calculate_unicodes(typographies_identical)
FileIO.write_file_silent(File.join(@output_directory, "UnicodeList#{unique_typography.cpp_name}_#{unique_typography.font_size}_#{unique_typography.bpp}.txt"), unicodes.join(LINE_ENDINGS) )
end
def run_vector(font_file)
typographies_font = @typographies.select{ |t| t.font_file == font_file && t.is_vector }
unicodes = calculate_unicodes(typographies_font)
font_cpp_name = font_file.gsub(/\.ttf$/,"").gsub(/[^0-9a-zA-Z]/, "_")
FileIO.write_file_silent(File.join(@output_directory, "VectorUnicodeList#{font_cpp_name}.txt"), unicodes.join(LINE_ENDINGS) )
end
end
@@ -0,0 +1,27 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
class UnmappedDataFontCpp < Template
def input_path
File.join(root_dir,'Templates','UnmappedDataFont.cpp.temp')
end
def output_path
'/src/UnmappedDataFont.cpp'
end
def output_filename
File.join(@output_directory, output_path)
end
def run
if !File::exists?(output_filename)
#generate UnmappedDataFont.cpp
super
end
end
end
@@ -0,0 +1,27 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
class UnmappedDataFontHpp < Template
def input_path
File.join(root_dir,'Templates','UnmappedDataFont.hpp.temp')
end
def output_path
'/include/fonts/UnmappedDataFont.hpp'
end
def output_filename
File.join(@output_directory, output_path)
end
def run
if !File::exists?(output_filename)
#generate GeneratedFont.hpp
super
end
end
end
@@ -0,0 +1,93 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
require 'roo'
require 'lib/text_entries'
class ExcelRow
def initialize(excel_file, header, row_number, first_column)
@excel_file = excel_file
@header = header
@row_number = row_number
@first_column = first_column
end
def [](column_header)
value_at(@row_number, column_number(column_header.to_s))
end
def exists?(name)
!@header[name.to_s.downcase].nil?
end
private
def column_number(name)
column_index = @header[name.downcase]
fail "ERROR: #{name} column not found in excel file" if column_index.nil?
column_index + @first_column
end
def value_at(row, col)
value = @excel_file.cell(row,col).to_s
if value.empty?
nil
else
check_encoding(value)
value
end
end
def check_encoding(value)
puts value if value.force_encoding("UTF-8").valid_encoding? == false
end
end
class ExcelReader
def initialize(file_name, sheet, header_row, first_column)
@excel_file = Roo::Excelx.new file_name
@sheet = sheet
@excel_file.default_sheet = sheet
@header_row = header_row
@first_column = first_column
@header = {}
header.each_with_index do |cell, ix|
@header[cell.downcase] = ix
end
end
def read_header
yield header
end
def read_rows
(@header_row + 1).upto(last_row_number) do |row_number|
yield row(row_number)
end
end
private
def last_row_number
@excel_file.last_row
end
def header
@excel_file.row(@header_row).compact.map(&:strip)
end
def row(row_number)
ExcelRow.new(@excel_file, @header, row_number, @first_column)
end
end
@@ -0,0 +1,29 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
require 'rubygems'
require 'erb'
require 'fileutils'
require 'pathname'
class FileIO
def self.write_file(file_name, contents)
callingPath = Pathname.new($calling_path)
filePath = Pathname.new(file_name)
puts "Generating #{filePath.relative_path_from(callingPath)}"
write_file_silent(file_name, contents)
end
def self.write_file_silent(file_name, contents)
FileUtils.mkdir_p(File.dirname(file_name))
unless File.exist?(file_name) && contents == File.open(file_name, 'r') { |f| f.read() }
File.open(file_name, 'w') { |f| f.write(contents) }
end
end
end
@@ -0,0 +1,27 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
require 'lib/outputter'
require 'lib/sanitizer'
require 'lib/string_collector'
require 'lib/text_database_parser'
require 'lib/xml_reader'
require 'lib/xml_validator'
class Generator
def run(xml_file_name, output_path, text_output_path, font_asset_path, data_format, remap_global, autohint_setting, generate_binary_translations, generate_binary_fonts, framebuffer_bpp, generate_font_format, korean_fusion_fonts, copy_translations_to_ram, compressed_font_cache_size)
xml_doc = XMLReader.new.read(xml_file_name)
XMLValidator.new.validate(xml_file_name)
languages, typographies, text_entries = TextDatabaseParser.new(xml_doc).run
Sanitizer.new(text_entries, typographies, languages, framebuffer_bpp).run
string_indices, characters = StringCollector.new(text_entries, languages, typographies).run(remap_global == "yes")
Outputter.new(string_indices, characters, text_entries, typographies, languages, text_output_path, output_path, font_asset_path, data_format, remap_global, autohint_setting, generate_binary_translations, generate_binary_fonts, generate_font_format, korean_fusion_fonts, copy_translations_to_ram, compressed_font_cache_size).run
end
end
@@ -0,0 +1,91 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
require 'lib/file_io'
require 'lib/emitters/template'
require 'lib/emitters/text_keys_and_languages_hpp'
require 'lib/emitters/texts_cpp'
require 'lib/emitters/languages_cpp'
require 'lib/emitters/languages_bin'
require 'lib/emitters/unicodes_txt'
require 'lib/emitters/fonts_cpp'
require 'lib/emitters/generated_font_cpp'
require 'lib/emitters/generated_font_hpp'
require 'lib/emitters/unmapped_data_font_cpp'
require 'lib/emitters/unmapped_data_font_hpp'
require 'lib/emitters/cached_font_cpp'
require 'lib/emitters/cached_font_hpp'
require 'lib/emitters/font_cache_cpp'
require 'lib/emitters/font_cache_hpp'
require 'lib/emitters/compressed_font_cache_hpp'
require 'lib/emitters/compressed_font_cache_cpp'
require 'lib/emitters/compressed_unmapped_font_cache_hpp'
require 'lib/emitters/compressed_unmapped_font_cache_cpp'
require 'lib/emitters/application_font_provider_hpp'
require 'lib/emitters/application_font_provider_cpp'
require 'lib/emitters/typed_text_database_hpp'
require 'lib/emitters/typed_text_database_cpp'
class Outputter
def initialize(string_indices, characters, text_entries, typographies, languages, localization_output_directory, fonts_output_directory, font_asset_path, data_format, remap_global, autohint_setting, generate_binary_translations, generate_binary_fonts, generate_font_format, korean_fusion_fonts, copy_translations_to_ram, compressed_font_cache_size)
@string_indices = string_indices #dictionary of all string indices into the characters array
@characters = characters #one array of the needed strings in optimal order
@text_entries = text_entries
@typographies = typographies
@languages = languages
@localization_output_directory = localization_output_directory
@fonts_output_directory = fonts_output_directory
@font_asset_path = font_asset_path
@data_format = data_format
@remap_global = remap_global
@autohint_setting = autohint_setting
@generate_binary_translations = generate_binary_translations
@generate_binary_fonts = generate_binary_fonts
@generate_font_format = generate_font_format
@korean_fusion_fonts = korean_fusion_fonts
@copy_translations_to_ram = copy_translations_to_ram
@compressed_font_cache_size = compressed_font_cache_size
end
def run
[ GeneratedFontHpp,
GeneratedFontCpp,
UnmappedDataFontHpp,
UnmappedDataFontCpp,
CachedFontHpp,
CachedFontCpp,
FontCacheHpp,
FontCacheCpp,
CompressedFontCacheCpp,
CompressedUnmappedFontCacheCpp,
UnicodesTxt ].each { |template| template.new(@text_entries, @typographies, @languages, @fonts_output_directory).run }
[ CompressedFontCacheHpp, CompressedUnmappedFontCacheHpp ].each { |template| template.new(@text_entries, @typographies, @languages, @fonts_output_directory, @compressed_font_cache_size).run }
[ ApplicationFontProviderCpp,
ApplicationFontProviderHpp ].each { |template| template.new(@text_entries, @typographies, @languages, @fonts_output_directory, @generate_font_format).run }
[ TextKeysAndLanguages,
TypedTextDatabaseHpp].each { |template| template.new(@text_entries, @typographies, @languages, @localization_output_directory).run }
TypedTextDatabaseCpp.new(@text_entries, @typographies, @languages, @localization_output_directory, @generate_binary_translations, @generate_font_format, @copy_translations_to_ram).run
TextsCpp.new(@characters, @text_entries, @typographies, @languages, @localization_output_directory, @remap_global, @generate_binary_translations, @copy_translations_to_ram).run
LanguagesCpp.new(@string_indices, @characters, @text_entries, @languages, @localization_output_directory, @remap_global, @generate_binary_translations, @copy_translations_to_ram).run
FontsCpp.new(@text_entries, @typographies, @languages, @fonts_output_directory, @font_asset_path, @autohint_setting, @data_format, @generate_binary_fonts, @generate_font_format, @korean_fusion_fonts, @compressed_font_cache_size).run
if @generate_binary_translations.downcase == 'yes'
[ LanguagesBin ].each { |template| template.new(@text_entries, @typographies, @languages, @localization_output_directory).run }
end
end
end
@@ -0,0 +1,175 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
TextEntrySanitizer = Struct.new(:text_entries, :typographies, :languages, :framebuffer_bpp)
class Sanitizer < TextEntrySanitizer
def run
[ RemoveDuplicateKeys,
# RemoveIncompleteLanguages,
RemoveKeysWithMoreThanTwoSubstitutions,
RemoveKeysWithDifferentNumberOfSubstitutions,
RemoveTextEntriesWithInvalidTypography,
RemoveTextEntriesWithInvalidAlignment,
RemoveTextEntriesWithInvalidDirection,
CheckSizeAndBpp,
DowngradeFontsBitDepth
].each do |sanitizer|
sanitizer.new(text_entries, typographies, languages, framebuffer_bpp).run
end
end
end
class RemoveDuplicateKeys < TextEntrySanitizer
def run
counts = Hash.new(0)
counts = text_entries.inject(Hash.new(0)) do |h, entry|
h[entry.cpp_text_id.upcase] = h[entry.cpp_text_id.upcase] + 1
h
end
text_entries.each do |text_entry|
if counts[text_entry.cpp_text_id.upcase] > 1
fail "ERROR: Duplicate key removed: #{text_entry.text_id}, yields cpp identifier #{text_entry.cpp_text_id.upcase}"
text_entries.remove(text_entry)
end
end
end
end
class RemoveIncompleteLanguages < TextEntrySanitizer
def run
languages.each do |language|
text_entries_with_missing_translations = text_entries.select do |text_entry|
text_entry.translation_in(language).empty?
end
text_entries_with_missing_translations.each do |text_entry|
fail "ERROR: Language #{language} is missing translation for #{text_entry.text_id}"
end
if text_entries_with_missing_translations.any?
text_entries.remove_language(language)
end
end
end
end
class RemoveKeysWithMoreThanTwoSubstitutions < TextEntrySanitizer
def run
languages.each do |language|
text_entries_with_more_than_two_substitutions = text_entries.select do |text_entry|
text_entry.number_of_substitutions_in(language) > 2
end
text_entries_with_more_than_two_substitutions.each do |text_entry|
fail "ERROR: Text Id #{text_entry.text_id} has #{text_entry.number_of_substitutions_in(language)} substitutions"
#text_entries.remove(text_entry)
end
end if languages
end
end
class RemoveKeysWithDifferentNumberOfSubstitutions < TextEntrySanitizer
def run
text_entries.each do |text_entry|
translations = text_entry.translations
number_of_substitutions_per_translation = translations.collect { |translation| translation.number_of_substitutions }
if number_of_substitutions_per_translation.uniq.count > 1
fail "ERROR: Text Id #{text_entry.text_id} has different number of substitutions for some languages"
#text_entries.remove(text_entry)
end
end
end
end
class RemoveTextEntriesWithInvalidTypography < TextEntrySanitizer
def run
text_entries.each do |text_entry|
non_existing_typographies = (text_entry.get_all_typographies - typographies.map( &:name )).compact;
if non_existing_typographies.any?
fail "ERROR: Text Id #{text_entry.text_id} uses unknown typographies #{non_existing_typographies}"
#text_entries.remove(text_entry)
end
end
end
end
class RemoveTextEntriesWithInvalidAlignment < TextEntrySanitizer
def run
text_entries.each do |text_entry|
alignments = text_entry.get_all_alignments_as_string
illegal_alignments = alignments.uniq - ['LEFT', 'RIGHT', 'CENTER']
if illegal_alignments.any?
fail "ERROR: Text Id #{text_entry.text_id} uses unknown alignments #{illegal_alignments}"
#text_entries.remove(text_entry)
end
end
end
end
class RemoveTextEntriesWithInvalidDirection < TextEntrySanitizer
def run
text_entries.each do |text_entry|
directions = text_entry.get_all_directions_as_string
illegal_directions = directions.uniq - ['LTR', 'RTL']
if illegal_directions.any?
fail "ERROR: Text Id '#{text_entry.text_id}' uses unknown directions #{illegal_directions}"
#text_entries.remove(text_entry)
end
end
end
end
class CheckSizeAndBpp < TextEntrySanitizer
def run
typographies.each do |typography|
if ![1, 2, 4, 8].include?(typography.bpp)
fail "ERROR: Typography named '#{typography.name}' has bpp value '#{typography.bpp}', which is not a valid value"
end
if !typography.font_size.integer? || typography.font_size < 1
fail "ERROR: Typography named '#{typography.name}' has font size value '#{typography.font_size}', which is not a valid value"
end
end
end
end
class DowngradeFontsBitDepth < TextEntrySanitizer
def run
if !framebuffer_bpp.nil?
m = framebuffer_bpp.match(/BPP(\d+)/)
bpp = m.nil? ? 24 : m[1].to_i
typographies.each do |typography|
case bpp
when 8
if typography.bpp > 2
puts "Downgrading typography #{typography.name} from #{typography.bpp.to_s}bpp to 2bpp"
typography.bpp = 2
end
when 4
if typography.bpp > 4
puts "Downgrading typography #{typography.name} from #{typography.bpp.to_s}bpp to 4bpp"
typography.bpp = 4
end
when 2
if typography.bpp > 2
puts "Downgrading typography #{typography.name} from #{typography.bpp.to_s}bpp to 2bpp"
typography.bpp = 2
end
when 1
if typography.bpp > 1
puts "Downgrading typography #{typography.name} from #{typography.bpp.to_s}bpp to 1bpp"
typography.bpp = 1
end
end
end
end
end
end
@@ -0,0 +1,98 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
CollectorStruct = Struct.new(:text_entries, :languages, :typographies)
def get_getFont_name(typography)
if typography.is_vector
"getFont_vector_#{typography.cpp_name}_#{typography.font_size}"
else
"getFont_#{typography.cpp_name}_#{typography.font_size}_#{typography.bpp}bpp"
end
end
def unicode_array_to_hex_offset_comment(array)
strings = array.slice_after(0).to_a
# Inject ([], 0) and on each interation insert
# * the substring
# * the offset
# * a flag for "last element"
# in the [] array + update the offset for next substring
# Return [0] which is the array (discarding the offset placed at [1] in the injected ([],0)
strings.each_with_index.inject([Array.new,0]) { |(result, offset), (string, index)| [ result.push([string, offset, index==strings.length-1]), offset + string.length ] }[0]
end
def unicode_array_to_string(array)
array.inject('') do |txt, unicode|
if unicode==2
txt << "<>"
elsif unicode>=32 && unicode<127
txt << unicode.chr
elsif unicode==0
return txt
elsif unicode==13
txt << '\r'
elsif unicode==10
txt << '\n'
else
txt << '?'
end
txt
end
end
class StringCollector < CollectorStruct
def run(remap_global)
#collect all strings for sorting
all_strings = Array.new(languages.count, Array.new)
text_entries.each do |text_entry|
text_entry.translations.each_with_index do |translation, lang_index|
# Fold all languages into one entry if global remapping
all_strings[remap_global ? 0 : lang_index] += [translation]
end
end
# sort by: Same length => sort by text, otherwise reverse sort on length
# [ 'Slide','Yes','Cover','None' ] => [ 'Cover', 'Slide', 'None', 'Yes' ]
all_strings = all_strings.map do |strings|
strings.sort{|x,y| x.length == y.length ? x.text <=> y.text : y.length <=> x.length }
end
#collect all string indeces, and add to characters array
string_mapping = Array.new
characters_lists = Array.new
all_strings.each_with_index do |strings, lang_index|
string_indices = Hash.new
characters = Array.new
strings.each do |translation|
#lookup translation in hash
#if not found, add to characters and insert index in hash for translation and all suffices
#if found, do nothing
unicodes = translation.unicodes # This includes a terminating zero character
index = string_indices[unicodes]
if !index
new_index = characters.length
#puts "new string: #{translation.to_cpp} index: #{new_index}"
characters.concat(unicodes)
for start in 0 .. unicodes.length-1
sub_string = unicodes[start..-1]
# if the substring is present, all shorter substrings are also present, so do not add again
break if string_indices[sub_string]
string_indices[sub_string] = (new_index + start)
end
else
#puts "existing string: #{translation.to_cpp} index: #{index}"
end
end
string_mapping[lang_index] = string_indices
characters_lists[lang_index] = characters
end
[string_mapping, characters_lists]
end
end
@@ -0,0 +1,191 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
require 'nokogiri'
require 'lib/text_entries'
require 'lib/typographies'
class ParserBase
def initialize(xml_doc)
@xml_doc = xml_doc
end
protected
def empty_to_nil(str)
str ? str.empty? ? nil : str : nil
end
def yes_to_true(str)
empty_to_nil(str)
!str.nil? && str.downcase == "yes" ? true : false
end
end
class TextDatabaseParser < ParserBase
def run
languages = LanguageParser.new(@xml_doc).run
typographies = TypographyParser.new(@xml_doc, languages).run
text_entries = TextParser.new(@xml_doc, languages, typographies).run
return languages, typographies.get_typographies(), text_entries
end
end
class LanguageParser < ParserBase
def run
languages = []
@xml_doc.xpath("/TextDatabase/Languages/Language").each do |lang_node|
language = empty_to_nil(lang_node["Id"])
fail "ERROR: Attribute 'Id' not specified in line #{lang_node.line} in #{lang_node.to_s}" if language.nil?
if !language.match(/^([0-9a-zA-Z_])*$/)
fail "ERROR: Illegal characters found in line #{lang_node.line} for <Language Id=\"#{language}\">"
end
languages.push(language)
end
return languages
end
end
class TypographyParser < ParserBase
def initialize(xml_doc, languages)
super(xml_doc)
@languages = languages
@typographies = Typographies.new
end
def run
@xml_doc.xpath("/TextDatabase/Typographies/Typography").each do |typo_node|
typo_id = empty_to_nil(typo_node["Id"])
font = empty_to_nil(typo_node["Font"])
size = empty_to_nil(typo_node["Size"])
bpp = empty_to_nil(typo_node["Bpp"])
direction = empty_to_nil(typo_node["Direction"])
is_vector = yes_to_true(typo_node["IsVector"])
is_compressed = yes_to_true(typo_node["IsCompressed"])
fallback_character = empty_to_nil(typo_node["FallbackCharacter"])
wildcard_characters = empty_to_nil(typo_node["WildcardCharacters"])
wildcard_widget_characters = empty_to_nil(typo_node["WidgetWildcardCharacters"])
wildcard_character_ranges = empty_to_nil(typo_node["WildcardCharacterRanges"])
ellipsis_character = empty_to_nil(typo_node["EllipsisCharacter"])
fail "ERROR: Attribute 'Id' not specified in line #{typo_node.line} in #{typo_node.to_s}" if typo_id.nil?
fail "ERROR: Attribute 'Font' not specified in line #{typo_node.line} for <Typography Id=\"#{typo_id}\">" if font.nil?
fail "ERROR: Attribute 'Size' not specified in line #{typo_node.line} for <Typography Id=\"#{typo_id}\">" if size.nil?
fail "ERROR: Attribute 'Bpp' not specified in line #{typo_node.line} for <Typography Id=\"#{typo_id}\">" if bpp.nil?
fail "ERROR: Attribute 'Direction' not specified in line #{typo_node.line} for <Typography Id=\"#{typo_id}\">" if direction.nil?
fail "ERROR: Attribute 'IsVector' not specified in line #{typo_node.line} for <Typography Id=\"#{typo_id}\">" if is_vector.nil?
fail "ERROR: Attribute 'IsVector' and Attribute 'IsCompressed' can not both be true for <Typography Id=\"#{typo_id}\">" if is_vector and is_compressed
if !typo_id.match(/^([0-9a-zA-Z_])*$/)
fail "ERROR: Illegal characters found in line #{typo_node.line} for <Typography Id=\"#{typo_id}\">"
end
# Default typography
@typographies.add(typo_id, "", font, size, bpp, is_vector, is_compressed, fallback_character, ellipsis_character, wildcard_characters, wildcard_widget_characters, wildcard_character_ranges, direction)
typo_node.xpath('./LanguageSetting').each do |language_setting|
language = empty_to_nil(language_setting["Language"])
font = empty_to_nil(language_setting["Font"])
size = empty_to_nil(language_setting["Size"])
bpp = empty_to_nil(language_setting["Bpp"])
direction = empty_to_nil(language_setting["Direction"])
is_vector = yes_to_true(language_setting["IsVector"])
is_compressed = yes_to_true(typo_node["IsCompressed"])
fallback_character = empty_to_nil(language_setting["FallbackCharacter"])
wildcard_characters = empty_to_nil(language_setting["WildcardCharacters"])
wildcard_widget_characters = empty_to_nil(language_setting["WidgetWildcardCharacters"])
wildcard_character_ranges = empty_to_nil(language_setting["WildcardCharacterRanges"])
ellipsis_character = empty_to_nil(language_setting["EllipsisCharacter"])
fail "ERROR: Attribute 'Language' not specified in line #{language_setting.line} in #{language_setting.to_s}" if language.nil?
fail "ERROR: Attribute 'Font' not specified in line #{language_setting.line} for <LanguageSetting Language=\"#{language}\">" if font.nil?
fail "ERROR: Attribute 'Size' not specified in line #{language_setting.line} for <LanguageSetting Language=\"#{language}\">" if size.nil?
fail "ERROR: Attribute 'Bpp' not specified in line #{language_setting.line} for <LanguageSetting Language=\"#{language}\">" if bpp.nil?
fail "ERROR: Attribute 'Direction' not specified in line #{language_setting.line} for <LanguageSetting Language=\"#{language}\">" if direction.nil?
if !language.match(/^([0-9a-zA-Z_])*$/)
fail "ERROR: Illegal characters found in line #{language_setting.line} for <LanguageSetting Language=\"#{language}\">"
end
fail "ERROR: Unknown language '#{language}'" if !@languages.include?(language)
# Language specific typography
@typographies.add(typo_id, language, font, size, bpp, is_vector, is_compressed, fallback_character, ellipsis_character, wildcard_characters, wildcard_widget_characters, wildcard_character_ranges, direction)
end
end
# See if we have the same font file and size with both 4bpp and 4bpp-compressed. This is not legal!
@typographies.get_typographies.select{|t| not t.is_vector and t.bpp==4}.group_by{|t| t.font_file+t.font_size.to_s}.each do |k, a|
if a.count{|t|t.is_compressed}>0 and a.count{|t|not t.is_compressed}>0
t = a[0]
fail "ERROR: Font file #{t.font_file} is used in size #{t.font_size} in both 4Bpp and 4Bpp Compressed. This is not supported!"
end
end
return @typographies
end
end
class TextParser < ParserBase
def initialize(xml_doc, languages, typographies)
super(xml_doc)
@languages = languages
@typographies = typographies
@text_entries = TextEntries.new
end
def run
@xml_doc.xpath("/TextDatabase/Texts/TextGroup/Text").each do |text_node|
text_id = empty_to_nil(text_node["Id"])
default_typography_id = empty_to_nil(text_node["TypographyId"])
default_alignment = empty_to_nil(text_node["Alignment"])
fail "ERROR: Attribute 'Id' not specified in line #{text_node.line} in #{text_node.to_s}" if text_id.nil?
fail "ERROR: Attribute 'TypographyId' not specified in line #{text_node.line} for <Text Id=\"#{text_id}\">" if default_typography_id.nil?
fail "ERROR: Attribute 'Alignment' not specified in line #{text_node.line} for <Text Id=\"#{text_id}\">" if default_alignment.nil?
if !text_id.match(/^([0-9a-zA-Z_])*$/)
fail "ERROR: Illegal characters found in line #{text_node.line} for <Text Id=\"#{text_id}\">"
end
default_typography_name = @typographies.get_typography_name(default_typography_id, "")
default_direction = @typographies.get_direction(default_typography_id, "")
text_entry = TextEntry.new(text_id, default_typography_name, default_alignment, default_direction);
fail "ERROR: Translation not specified in line #{text_node.line} for <Text Id=\"#{text_id}\">" if !text_node.at("Translation")
text_node.xpath('./Translation').each do |translation|
language = empty_to_nil(translation["Language"])
specific_alignment = empty_to_nil(translation["Alignment"])
fail "ERROR: Attribute 'Language' not specified in line #{translation.line} for Translation" if language.nil?
if !language.match(/^([0-9a-zA-Z_])*$/)
fail "ERROR: Illegal characters found in line #{translation.line} for <Translation Language=\"#{language}\">"
end
fail "ERROR: Unknown language '#{language}'" if !@languages.include?(language)
if translation.text.match(/\n\t|\n /)
puts "WARNING: Text in line #{translation.line} for <Text Id=\"#{text_id}\"> with Language '#{language}' contains tabs or whitespace indentation"
end
specific_typography_name = @typographies.get_typography_name(default_typography_id, language)
specific_direction = @typographies.get_direction(default_typography_id, language)
specific_typography_name = nil if specific_typography_name == default_typography_name
specific_direction = nil if specific_direction == default_direction
specific_alignment = nil if specific_alignment == default_alignment
text_entry.add_translation(language, translation.text)
text_entry.add_typography(language, specific_typography_name) if !specific_typography_name.nil?
text_entry.add_direction(language, specific_direction) if !specific_direction.nil?
text_entry.add_alignment(language, specific_alignment) if !specific_alignment.nil?
end
@text_entries.add(text_entry)
end
return @text_entries
end
end
@@ -0,0 +1,175 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
require 'lib/excel_reader'
require 'lib/text_entries'
require 'lib/typographies'
class TextDatabaseParser_4_17
def initialize(file_name)
@file_name = file_name
end
def run
typographies = TypographyParser_4_17.new(@file_name).run
text_entries = TextParser_4_17.new(@file_name).run
return typographies, text_entries
end
end
class TypographyParser_4_17
def initialize(file_name)
header_row_number = 3
header_column_number = 2
@reader = ExcelReader.new(file_name, 'Typography', header_row_number, header_column_number)
end
def run
typographies = []
@reader.read_rows do |row|
name = row[:'Typography Name']
font = row[:font]
size = row[:size]
bpp = row[:bpp]
if row.exists?(:'Fallback Character')
fallback_character = row[:'Fallback Character']
end
if row.exists?(:'Wildcard Characters')
wildcard_characters = row[:'Wildcard Characters']
end
if row.exists?(:'Widget Wildcard Characters')
widget_wildcard_characters = row[:'Widget Wildcard Characters']
end
if row.exists?(:'Character Ranges') # New name
wildcard_ranges = row[:'Character Ranges']
elsif row.exists?(:'Wildcard Ranges') # Old name
wildcard_ranges = row[:'Wildcard Ranges']
end
if row.exists?(:'Ellipsis Character')
ellipsis_character = row[:'Ellipsis Character']
end
if name
name.strip!
unless name.match(/^([0-9a-zA-Z_])*$/)
fail "ERROR: Illegal characters found in Text ID '#{name}'"
end
end
font = font.strip if font
size = size.strip if size
bpp = bpp.strip if bpp
fallback_character = fallback_character.strip if fallback_character
wildcard_characters = wildcard_characters.strip if wildcard_characters
widget_wildcard_characters = widget_wildcard_characters.strip if widget_wildcard_characters
wildcard_ranges = wildcard_ranges.strip if wildcard_ranges
ellipsis_character = ellipsis_character.strip if ellipsis_character
if name && font && size && bpp
typographies.push Typography.new(name, font, size.to_i, bpp.to_i, false, false, fallback_character, ellipsis_character, wildcard_characters, widget_wildcard_characters, wildcard_ranges)
end
end
typographies
end
end
class TextParser_4_17
def initialize(file_name)
header_row_number = 3
header_column_number = 2
@reader = ExcelReader.new(file_name, "Translation", header_row_number, header_column_number)
end
def language_capitalization(lang)
lang_upcase = lang.upcase
@languages.find { |l| l.upcase == lang_upcase }
end
def run
@reader.read_header do |header|
@alignments = header.select { |column| column.match(/^.*-ALIGNMENT$/i) }
@directions = header.select { |column| column.match(/^.*-DIRECTION$/i) }
@typographies = header.select { |column| column.match(/^.*-TYPOGRAPHY$/i) }
@languages = header.select { |column| column.match(/^(\w{1,3})$/i ) }
end
# Check for undefined languages in language specific typographies
@typographies.each do |typography|
language, _ = typography.upcase.split('-')
fail "ERROR: Unknown language in column #{language}-TYPOGRAPHY" if !@languages.any?{ |lang| lang.upcase == language }
end
# Check for undefined languages in language specific alignments
@alignments.each do |alignment|
language, _ = alignment.upcase.split('-')
fail "ERROR: Unknown language in column #{language}-ALIGNMENT" if not @languages.any?{ |lang| lang.upcase == language }
end
# Check for undefined languages in language specific directions
@directions.each do |direction|
language, _ = direction.upcase.split('-')
fail "ERROR: Unknown language in column #{language}-DIRECTION" if not @languages.any?{ |lang| lang.upcase == language }
end
text_entries = TextEntries.new
@reader.read_rows do |row|
text_id = row[:"Text ID"]
default_typography = row[:"Typography Name"]
default_alignment = row[:Alignment]
if row.exists?(:Direction)
default_direction = row[:Direction]
end
text_id = text_id.strip if text_id
default_typography = default_typography.strip if default_typography
default_alignment = default_alignment.strip if default_alignment
default_direction = default_direction.strip if default_direction
if text_id && default_typography
unless text_id.match(/^([0-9a-zA-Z_])*$/)
fail "ERROR: Illegal characters found in Text ID '#{text_id}'"
end
text_entry = TextEntry.new(text_id, default_typography, default_alignment, default_direction)
@typographies.each do |typography|
language, _ = typography.split('-')
language = language_capitalization(language)
t = row[typography]
t = t.strip if t
text_entry.add_typography(language, t)
end
@alignments.each do |alignment|
language, _ = alignment.split('-')
language = language_capitalization(language)
a = row[alignment]
a = a.strip if a
text_entry.add_alignment(language, a)
end
@directions.each do |direction|
language, _ = direction.split('-')
language = language_capitalization(language)
d = row[direction]
d = d.strip if d
text_entry.add_direction(language, d)
end
@languages.each do |language|
# Do *not* strip leading/trailing whitespace from translations.
text_entry.add_translation(language, row[language])
end
text_entries.add(text_entry)
end
end
text_entries
end
end
@@ -0,0 +1,162 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
require 'nokogiri'
require 'lib/text_entries'
require 'lib/typographies'
class TextDatabaseParser_4_18
def initialize(file_name)
@file_name = file_name
end
def run
typographies = TypographyParser_4_18.new(@file_name).run
text_entries = TextParser_4_18.new(@file_name).run
return typographies, text_entries
end
end
class TypographyParser_4_18
def empty_to_nil(str)
str ? str.strip.empty? ? nil : str.strip : nil
end
def initialize(xml_doc)
@xml_doc = xml_doc
@typographies = []
end
def run
@xml_doc.xpath("/TextDatabase/Typographies/Typography").each do |typo_node|
typo_id = empty_to_nil(typo_node["Id"])
font = empty_to_nil(typo_node["Font"])
size = empty_to_nil(typo_node["Size"])
bpp = empty_to_nil(typo_node["Bpp"])
fallback_character = empty_to_nil(typo_node["FallbackCharacter"])
wildcard_characters = empty_to_nil(typo_node["WildcardCharacters"])
wildcard_widget_characters = empty_to_nil(typo_node["WidgetWildcardCharacters"])
wildcard_character_ranges = empty_to_nil(typo_node["WildcardCharacterRanges"])
ellipsis_character = empty_to_nil(typo_node["EllipsisCharacter"])
fail "ERROR: Attribute 'Id' not specified in line #{typo_node.line} in #{typo_node.to_s}" if typo_id.nil?
fail "ERROR: Attribute 'Font' not specified in line #{typo_node.line} for <Typography Id=\"#{typo_id}\">" if font.nil?
fail "ERROR: Attribute 'Size' not specified in line #{typo_node.line} for <Typography Id=\"#{typo_id}\">" if size.nil?
fail "ERROR: Attribute 'Bpp' not specified in line #{typo_node.line} for <Typography Id=\"#{typo_id}\">" if bpp.nil?
if !typo_id.match(/^([0-9a-zA-Z_])*$/)
fail "ERROR: Illegal characters found in line #{typo_node.line} for <Typography Id=\"#{typo_id}\">"
end
@typographies.push Typography.new(typo_id, font, size.to_i, bpp.to_i, false, false, fallback_character, ellipsis_character, wildcard_characters, wildcard_widget_characters, wildcard_character_ranges)
end
@typographies
end
end
class TextParser_4_18
def initialize(xml_doc)
@xml_doc = xml_doc
@text_entries = TextEntries.new
end
def empty_to_nil(str)
str ? str.strip.empty? ? nil : str.strip : nil
end
def get_value_or_nil(a)
a.nil? ? nil : (a.value.nil? ? nil : a.value.strip)
end
def run
texts = @xml_doc.xpath("/TextDatabase/Texts/Text").map do |text|
text_id = empty_to_nil(text["Id"])
default_typography = empty_to_nil(text["TypographyId"])
default_alignment = empty_to_nil(text["Alignment"])
default_direction = empty_to_nil(text["Direction"])
fail "ERROR: Attribute 'Id' not specified in line #{text.line} in #{text.to_s}" if text_id.nil?
fail "ERROR: Attribute 'TypographyId' not specified in line #{text.line} for <Text Id=\"#{text_id}\">" if default_typography.nil?
fail "ERROR: Attribute 'Alignment' not specified in line #{text.line} for <Text Id=\"#{text_id}\">" if default_alignment.nil?
fail "ERROR: Attribute 'Direction' not specified in line #{text.line} for <Text Id=\"#{text_id}\">" if default_direction.nil?
if !text_id.match(/^([0-9a-zA-Z_])*$/)
fail "ERROR: Illegal characters found in line #{text.line} for <Text Id=\"#{text_id}\">"
end
fail "ERROR: Translation not specified in line #{text.line} for <Text Id=\"#{text_id}\">" if !text.at("Translation")
{
:id => text_id, :typography => default_typography, :alignment => default_alignment, :direction => default_direction,
:translations => text.xpath('./Translation').inject({}) do |result, translation|
fail "ERROR: Attribute 'Language' not specified in line #{translation.line} for Translation" if translation.attributes["Language"].nil?
language = translation.attributes["Language"].value
if translation.text.match(/\n\t|\n /)
puts "WARNING: Text in line #{translation.line} for <Text Id=\"#{text_id}\"> with Language '#{language}' contains tabs or whitespace indentation"
end
result[ language.strip ] = {
:value => translation.text,
:typography => get_value_or_nil(translation.attributes["TypographyId"]),
:alignment => get_value_or_nil(translation.attributes["Alignment"]),
:direction => get_value_or_nil(translation.attributes["Direction"])
}; result
end
}
end
languages_with_specific_typographies = texts.map do |text|
translations = text[:translations]
translations.keys.select { |language| translations[language][:typography] }
end.flatten.uniq
languages_with_specific_alignments = texts.map do |text|
translations = text[:translations]
translations.keys.select { |language| translations[language][:alignment] }
end.flatten.uniq
languages_with_specific_directions = texts.map do |text|
translations = text[:translations]
translations.keys.select { |language| translations[language][:direction] }
end.flatten.uniq
languages = texts.map { |text| text[:translations].keys }.flatten.uniq
texts.each do |text|
text_entry = TextEntry.new(text[:id], text[:typography], text[:alignment], text[:direction])
languages_with_specific_typographies.each do |language|
typography = text[:translations][language][:typography]
text_entry.add_typography(language, typography)
end
languages_with_specific_alignments.each do |language|
alignment = text[:translations][language][:alignment]
text_entry.add_alignment(language, alignment)
end
languages_with_specific_directions.each do |language|
direction = text[:translations][language][:direction]
text_entry.add_direction(language, direction)
end
languages.each do |language|
translation = text[:translations][language].nil? ? "" : text[:translations][language][:value]
text_entry.add_translation(language, translation)
end
@text_entries.add(text_entry)
end
@text_entries
end
end
@@ -0,0 +1,470 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
require 'nokogiri'
require 'rubygems/version'
require 'lib/text_database_parser_4_17'
require 'lib/sanitizer'
require 'lib/version'
require 'lib/xml_reader'
require 'lib/xml_writer'
class TextDatabaseUpgrader
def initialize(file_name, upgrade_version)
@file_name = file_name
@upgrade_version = Gem::Version.new(upgrade_version)
file_ext = File.extname(@file_name)
case file_ext
when '.xlsx'
@xml_doc = Nokogiri::XML::Document.new
@intermediate_version = Gem::Version.new('4.17.0') # or earlier
when '.xml'
@xml_doc = XMLReader.new.read(@file_name)
@intermediate_version = Gem::Version.new(@xml_doc.at('TextDatabase')['Version'])
else
fail "ERROR: Unsupported text database file extension: #{file_ext}"
end
end
def run
# For each version where an upgrade of the text database is required,
# a code snippet, like the 4 lines below, must be added, including
# implementation of the actual UpgradeTo_X_Y class.
# Note! In case of versions where the text database doesn't change,
# nothing has to be done. The text database will automatically be
# updated with the new @upgrade_version.
version_4_18 = Gem::Version.new('4.18.0')
if @intermediate_version < version_4_18 && @upgrade_version >= version_4_18
@xml_doc, @intermediate_version = UpgradeTo_4_18.new(@file_name).run
end
# Now @xml_doc contains 4.18.0 xml data
version_4_19 = Gem::Version.new('4.19.0')
if @intermediate_version < version_4_19 && @upgrade_version >= version_4_19
@xml_doc, @intermediate_version = UpgradeTo_4_19.new(@xml_doc).run
end
# Now @xml_doc contains 4.19.0 xml data
version_4_23 = Gem::Version.new('4.23.0')
if @intermediate_version < version_4_23 && @upgrade_version >= version_4_23
@xml_doc, @intermediate_version = UpgradeTo_4_23.new(@xml_doc).run
end
# Now @xml_doc contains 4.23.0 xml data
if @xml_doc.at('//TextDatabase')
@xml_doc.at('TextDatabase')['Version'] = @upgrade_version.version
xml_file_name = @file_name.gsub(/\.xlsx$/, '.xml')
XMLWriter.new.write(xml_file_name, @xml_doc)
else
fail "ERROR: Unsupported upgrade version: #{@upgrade_version.version}"
end
end
end
class UpgradeTo_4_23
def initialize(xml_doc)
@xml_doc = xml_doc
end
def run
# Add <IsVector> inside <Typography>
@xml_doc.xpath("//Typography").each do |typography_node|
if !typography_node.has_attribute?('IsVector')
typography_node['IsVector'] = 'no'
end
end
# Add <IsVector> inside <LanguageSetting>
@xml_doc.xpath("//LanguageSetting").each do |language_setting_node|
if !language_setting_node.has_attribute?('IsVector')
language_setting_node['IsVector'] = 'no'
end
end
return @xml_doc, Gem::Version.new('4.23.0')
end
end
class UpgradeTo_4_19
def initialize(xml_doc)
@xml_doc = xml_doc
@languages = []
@typographies = []
@texts = []
end
def run
xml_doc = Nokogiri::XML::Document.new
xml_doc.encoding = 'utf-8'
create_data_structures
# Create <TextDatabase>
textdatabase_node = xml_doc.add_child(Nokogiri::XML::Node.new('TextDatabase', xml_doc))
textdatabase_node['xmlns:xsi'] = 'http://www.w3.org/2001/XMLSchema-instance'
textdatabase_node['xsi:noNamespaceSchemaLocation'] = 'texts.xsd'
textdatabase_node['Version'] = @intermediate_version.to_s
# Create <Languages> inside <TextDatabase>
languages_node = textdatabase_node.add_child(Nokogiri::XML::Node.new('Languages', xml_doc))
@languages.each do |language|
# Add <Text> with required attributes inside <Texts>
language_node = languages_node.add_child(Nokogiri::XML::Node.new('Language', xml_doc))
language_node['Id'] = language
end
# Create <Texts> inside <TextDatabase>
texts_node = textdatabase_node.add_child(Nokogiri::XML::Node.new('Texts', xml_doc))
# Create <TextGroup> inside <Texts>
text_group_node = texts_node.add_child(Nokogiri::XML::Node.new('TextGroup', xml_doc))
text_group_node['Id'] = "Group1"
@texts.each do |text|
# Add <Text> with required attributes inside <Texts>
text_node = text_group_node.add_child(Nokogiri::XML::Node.new('Text', xml_doc))
text_node['Id'] = text.id
text_node['TypographyId'] = text.typography_id
text_node['Alignment'] = text.alignment
text.translations.each do |translation|
# Add <Translation> with required attribute
translation_node = text_node.add_child(Nokogiri::XML::Node.new('Translation', xml_doc))
translation_node['Language'] = translation.language
# Add optional attribute
translation_node['Alignment'] = translation.alignment if translation.alignment
# Add actual text
translation_node.add_child(Nokogiri::XML::Text.new(translation.text, xml_doc))
end
end
# Add <Typographies> inside <TextDatabase>
typographies_node = textdatabase_node.add_child(Nokogiri::XML::Node.new('Typographies', xml_doc))
@typographies.each do |typography|
# Add <Typography> with required attributes inside <Typographies>
typography_node = typographies_node.add_child(Nokogiri::XML::Node.new('Typography', xml_doc))
typography_node['Id'] = typography.id
typography_node['Font'] = typography.font_file
typography_node['Size'] = typography.font_size
typography_node['Bpp'] = typography.bpp
typography_node['Direction'] = typography.direction
# Add optional attributes
typography_node['FallbackCharacter'] = typography.fallback_character if typography.fallback_character
typography_node['WildcardCharacters'] = typography.wildcard_characters if typography.wildcard_characters
typography_node['WidgetWildcardCharacters'] = typography.widget_wildcard_characters if typography.widget_wildcard_characters
typography_node['WildcardCharacterRanges'] = typography.wildcard_ranges if typography.wildcard_ranges
typography_node['EllipsisCharacter'] = typography.ellipsis_character if typography.ellipsis_character
# Add <LanguageSetting> with required attributes inside <Typography>
typography.language_settings.each do |language_setting|
language_setting_node = typography_node.add_child(Nokogiri::XML::Node.new('LanguageSetting', xml_doc))
language_setting_node['Language'] = language_setting.language
language_setting_node['Font'] = language_setting.font_file
language_setting_node['Size'] = language_setting.font_size
language_setting_node['Bpp'] = language_setting.bpp
language_setting_node['Direction'] = language_setting.direction
# Add optional attributes
language_setting_node['FallbackCharacter'] = language_setting.fallback_character if language_setting.fallback_character
language_setting_node['WildcardCharacters'] = language_setting.wildcard_characters if language_setting.wildcard_characters
language_setting_node['WidgetWildcardCharacters'] = language_setting.widget_wildcard_characters if language_setting.widget_wildcard_characters
language_setting_node['WildcardCharacterRanges'] = language_setting.wildcard_ranges if language_setting.wildcard_ranges
language_setting_node['EllipsisCharacter'] = language_setting.ellipsis_character if language_setting.ellipsis_character
end
end
return xml_doc, Gem::Version.new('4.19.0')
end
private
class Typography
attr_reader :id, :font_file, :font_size, :bpp, :direction, :fallback_character, :wildcard_characters, :widget_wildcard_characters, :wildcard_ranges, :ellipsis_character, :language_settings, :text_ids
attr_writer :id
def initialize(id, font_file, font_size, bpp, direction, fallback_character, wildcard_characters, widget_wildcard_characters, wildcard_ranges, ellipsis_character)
@id = id
@font_file = font_file
@font_size = font_size
@bpp = bpp
@direction = direction
@fallback_character = fallback_character
@wildcard_characters = wildcard_characters
@widget_wildcard_characters = widget_wildcard_characters
@wildcard_ranges = wildcard_ranges
@ellipsis_character = ellipsis_character
@direction = direction
@language_settings = []
@text_ids = []
end
def add_language_setting(setting)
@language_settings.push(setting)
end
def add_text_id(id)
@text_ids.push(id)
end
def ==(other)
self.id == other.id &&
self.font_file == other.font_file &&
self.font_size == other.font_size &&
self.bpp == other.bpp &&
self.direction == other.direction &&
self.fallback_character == other.fallback_character &&
self.wildcard_characters == other.wildcard_characters &&
self.widget_wildcard_characters == other.widget_wildcard_characters &&
self.wildcard_ranges == other.wildcard_ranges &&
self.ellipsis_character == other.ellipsis_character &&
self.language_settings == other.language_settings
end
alias eql? ==
class LanguageSetting
attr_reader :language, :font_file, :font_size, :bpp, :direction, :fallback_character, :wildcard_characters, :widget_wildcard_characters, :wildcard_ranges, :ellipsis_character
def initialize(language, font_file, font_size, bpp, direction, fallback_character, wildcard_characters, widget_wildcard_characters, wildcard_ranges, ellipsis_character)
@language = language
@font_file = font_file
@font_size = font_size
@bpp = bpp
@direction = direction
@fallback_character = fallback_character
@wildcard_characters = wildcard_characters
@widget_wildcard_characters = widget_wildcard_characters
@wildcard_ranges = wildcard_ranges
@ellipsis_character = ellipsis_character
end
def ==(other)
self.language == other.language &&
self.font_file == other.font_file &&
self.font_size == other.font_size &&
self.bpp == other.bpp &&
self.direction == other.direction &&
self.fallback_character == other.fallback_character &&
self.wildcard_characters == other.wildcard_characters &&
self.widget_wildcard_characters == other.widget_wildcard_characters &&
self.wildcard_ranges == other.wildcard_ranges &&
self.ellipsis_character == other.ellipsis_character
end
alias eql? ==
end
end
class Text
attr_reader :id, :typography_id, :alignment, :translations
attr_writer :typography_id
def initialize(id, typography_id, alignment)
@id = id
@typography_id = typography_id
@alignment = alignment
@translations = []
end
def add_translation(translation)
@translations.push(translation)
end
class Translation
attr_reader :language, :alignment, :text
def initialize(language, alignment, text)
@language = language
@alignment = alignment
@text = text
end
end
end
# Objective for create_data_structure:
# Create data structures that fits the new xml layout and make it easier
# to identify and resolve any upgrading conflicts.
# How it is done:
# Iterate text nodes and create data structures for @languages, @typographies and @texts.
# Note! Unused typographies will bee added at the end.
def create_data_structures
@xml_doc.xpath('/TextDatabase/Texts/Text').each do |text_node|
text_id = text_node['Id']
typo_id = text_node['TypographyId']
alignment = text_node['Alignment']
typography_node = @xml_doc.xpath("/TextDatabase/Typographies/Typography[@Id=\"#{typo_id}\"]")
font_file = typography_node.attr('Font'); font_file = font_file.value if font_file
font_size = typography_node.attr('Size'); font_size = font_size.value if font_size
bpp = typography_node.attr('Bpp'); bpp = bpp.value if bpp
direction = text_node['Direction']
fallback_character = typography_node.attr('FallbackCharacter'); fallback_character = fallback_character.value if fallback_character
wildcard_characters = typography_node.attr('WildcardCharacters'); wildcard_characters = wildcard_characters.value if wildcard_characters
widget_wildcard_characters = typography_node.attr('WidgetWildcardCharacters'); widget_wildcard_characters = widget_wildcard_characters.value if widget_wildcard_characters
wildcard_ranges = typography_node.attr('WildcardCharacterRanges'); wildcard_ranges = wildcard_ranges.value if wildcard_ranges
ellipsis_character = typography_node.attr('EllipsisCharacter'); ellipsis_character = ellipsis_character.value if ellipsis_character
typography = Typography.new(typo_id, font_file, font_size, bpp, direction, fallback_character, wildcard_characters, widget_wildcard_characters, wildcard_ranges, ellipsis_character)
typography.add_text_id(text_id)
text = Text.new(text_id, typo_id, alignment)
text_node.xpath('./Translation').each do |translation_node|
language = translation_node['Language']
alignment = translation_node['Alignment']
typo_id = translation_node['TypographyId']
typography_node = @xml_doc.xpath("/TextDatabase/Typographies/Typography[@Id=\"#{typo_id}\"]")
font_file = typography_node.attr('Font'); font_file = font_file.value if font_file
font_size = typography_node.attr('Size'); font_size = font_size.value if font_size
bpp = typography_node.attr('Bpp'); bpp = bpp.value if bpp
direction = translation_node['Direction']
fallback_character = typography_node.attr('FallbackCharacter'); fallback_character = fallback_character.value if fallback_character
wildcard_characters = typography_node.attr('WildcardCharacters'); wildcard_characters = wildcard_characters.value if wildcard_characters
widget_wildcard_characters = typography_node.attr('WidgetWildcardCharacters'); widget_wildcard_characters = widget_wildcard_characters.value if widget_wildcard_characters
wildcard_ranges = typography_node.attr('WildcardCharacterRanges'); wildcard_ranges = wildcard_ranges.value if wildcard_ranges
ellipsis_character = typography_node.attr('EllipsisCharacter'); ellipsis_character = ellipsis_character.value if ellipsis_character
# Only add language setting if translation specific typography or direction is present
# Note! Default values are used if no specific values exists
if typo_id || direction
language_setting = Typography::LanguageSetting.new(
language,
font_file ? font_file : typography.font_file,
font_size ? font_size : typography.font_size,
bpp ? bpp : typography.bpp,
direction ? direction : typography.direction,
fallback_character ? fallback_character : typography.fallback_character,
wildcard_characters ? wildcard_characters : typography.wildcard_characters,
widget_wildcard_characters ? widget_wildcard_characters : typography.widget_wildcard_characters,
wildcard_ranges ? wildcard_ranges : typography.wildcard_ranges,
ellipsis_character ? ellipsis_character : typography.ellipsis_character,
)
typography.add_language_setting(language_setting)
end
text.add_translation(Text::Translation.new(language, alignment, translation_node.text))
# Only add language if not included in @languages
@languages.push(language) if !@languages.include?(language)
end
# Only add typography if not included in @typographies
if @typographies.include?(typography)
index = @typographies.index(typography)
# Move text_ids from duplicate
# Note! text_ids represents all the texts that uses this specific typography
(@typographies[index].text_ids << typography.text_ids).flatten!
else
@typographies.push(typography)
end
@texts.push(text)
end
# Add unused typographies and give them direction "LTR"
@xml_doc.xpath('/TextDatabase/Typographies/Typography').each do |typo_node|
typo_id = typo_node['Id']
if !@typographies.any? { |typography| typography.id == typo_id}
@typographies.push(Typography.new(typo_id, typo_node['Font'], typo_node['Size'], typo_node['Bpp'], 'LTR', typo_node['FallbackCharacter'], typo_node['WildcardCharacters'], typo_node['WidgetWildcardCharacters'], typo_node['WildcardCharacterRanges'], typo_node['EllipsisCharacter']))
end
end
resolve_any_typography_naming_conflict
end
def resolve_any_typography_naming_conflict
used_typography_ids = @typographies.collect{|typography| typography.id}.uniq
# Group typographies according to id and resolve naming conflicts
@typographies.group_by {|typography| typography.id}.each do |id, group|
group.sort! do |a,b|
# Most used first, or LTR before RTL, or first id occurrence
a.text_ids.length != b.text_ids.length ? b.text_ids.length <=> a.text_ids.length : a.direction != b.direction ? a.direction <=> b.direction : a.text_ids <=> b.text_ids
end
# More than one typography with the same id
if group.length > 1
puts "WARNING: Resolving typography naming conflict for #{id}!"
end
# Do not rename first element, it is already reserved in used_typography_ids
group[1..].each do |typography|
# Rename typography
new_id = id
if typography.direction != group[0].direction
new_id = "#{new_id}_#{typography.direction}"
end
if used_typography_ids.include?(new_id)
suffix = 'A'
while used_typography_ids.include?("#{new_id}_#{suffix}")
suffix.next!
end
new_id = "#{new_id}_#{suffix}"
end
typography_index = @typographies.index(typography)
@typographies[typography_index].id = new_id
# Update texts with new typography ids
@typographies[typography_index].text_ids.each do |text_id|
text_index = @texts.index {|text| text.id == text_id}
@texts[text_index].typography_id = @typographies[typography_index].id
end
used_typography_ids.push(new_id)
end
end
end
end
class UpgradeTo_4_18
def initialize(xlsx_file_name)
@typographies, @text_entries = TextDatabaseParser_4_17.new(xlsx_file_name).run
end
def run
xml_doc = Nokogiri::XML::Document.new
xml_doc.encoding = 'utf-8'
# Create <TextDatabase>
textdatabase_node = xml_doc.add_child(Nokogiri::XML::Node.new('TextDatabase', xml_doc))
textdatabase_node['xmlns:xsi'] = 'http://www.w3.org/2001/XMLSchema-instance'
textdatabase_node['xsi:noNamespaceSchemaLocation'] = 'texts.xsd'
textdatabase_node['Version'] = @intermediate_version.to_s
# Create <Texts> inside <TextDatabase>
texts_node = textdatabase_node.add_child(Nokogiri::XML::Node.new('Texts', xml_doc))
@text_entries.each do |entry|
# Add <Text> with required attributes inside <Texts>
text_node = texts_node.add_child(Nokogiri::XML::Node.new('Text', xml_doc))
text_node['Id'] = entry.text_id
text_node['TypographyId'] = entry.default_typography
text_node['Alignment'] = entry.default_alignment.capitalize
text_node['Direction'] = entry.default_direction
@text_entries.entries.first.languages.each do |lang|
# Add <Translation> with required attribute
translation_node = text_node.add_child(Nokogiri::XML::Node.new('Translation', xml_doc))
translation_node['Language'] = lang
# Add optional attributes
translation_node['TypographyId'] = entry.typographies[lang] if entry.typographies[lang]
translation_node['Alignment'] = entry.alignments[lang].capitalize if entry.alignments[lang]
translation_node['Direction'] = entry.directions[lang] if entry.directions[lang]
# Add actual text
translation_node.add_child(Nokogiri::XML::Text.new(entry.translation_in(lang).text, xml_doc))
end
end
# Add <Typographies> inside <TextDatabase>
typographies_node = textdatabase_node.add_child(Nokogiri::XML::Node.new('Typographies', xml_doc))
@typographies.each do |typo|
# Add <Typography> with required attributes inside <Typographies>
typography_node = typographies_node.add_child(Nokogiri::XML::Node.new('Typography', xml_doc))
typography_node['Id'] = typo.name
typography_node['Font'] = typo.font_file
typography_node['Size'] = typo.font_size
typography_node['Bpp'] = typo.bpp
# Add optional attributes
typography_node['FallbackCharacter'] = typo.fallback_character if typo.fallback_character
typography_node['WildcardCharacters'] = typo.wildcard_characters if typo.wildcard_characters
typography_node['WidgetWildcardCharacters'] = typo.widget_wildcard_characters if typo.widget_wildcard_characters
typography_node['WildcardCharacterRanges'] = typo.wildcard_ranges if typo.wildcard_ranges
typography_node['EllipsisCharacter'] = typo.ellipsis_character if typo.ellipsis_character
end
return xml_doc, Gem::Version.new('4.18.0')
end
end
@@ -0,0 +1,45 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
require 'lib/sanitizer'
require 'lib/text_database_parser'
require 'lib/text_database_parser_4_17'
require 'lib/text_database_parser_4_18'
require 'lib/xml_reader'
class TextDatabaseValidator
def validate(file_name)
@typographies
@text_entries
@languages
file_ext = File.extname(file_name)
if file_ext == '.xlsx'
@typographies, @text_entries = TextDatabaseParser_4_17.new(file_name).run
elsif file_ext == '.xml'
xml_doc = XMLReader.new.read(file_name)
if xml_doc.at("TextDatabase")
version = xml_doc.at("TextDatabase")["Version"]
case version
when '4.18.0', '4.18.1'
@typographies, @text_entries = TextDatabaseParser_4_18.new(xml_doc).run
when '4.19.0', '4.19.1', '4.20.0', '4.21.0', '4.21.1', '4.21.2', '4.21.3', '4.21.4', '4.22.0', '4.22.1', '4.23.0', '4.23.1', '4.23.2', '4.24.0', '4.24.1', '4.24.2', '4.25.0', '4.26.0', '4.26.1'
@languages, @typographies, @text_entries = TextDatabaseParser.new(xml_doc).run
else
fail "ERROR: Unknown text database version: #{version}"
end
else
fail "ERROR: Invalid text database: #{file_name}"
end
else
fail "ERROR: Unsupported text database file extension: #{file_ext}"
end
Sanitizer.new(@text_entries, @typographies, @languages, nil).run
end
end
@@ -0,0 +1,242 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
class TextEntries
include Enumerable
def initialize
@entries = []
@unicode_is_rtl = false
end
def each(&block)
@entries.each(&block)
end
def remove(entry)
@entries.delete(entry)
end
def add(entry)
@entries.push(entry)
end
def empty?
@entries.empty?
end
def different_typographies
[ar,default]
end
def different_alignments
[ar,default]
end
def languages_with_specific_settings
@entries.collect { |entry| entry.typographies.keys + entry.alignments.keys + entry.directions.keys }.flatten.uniq
end
def remove_language(language)
@entries.each { |entry| entry.remove_translation_in(language) }
end
def typographies
@entries.map(&:default_typography).uniq
end
def entries
@entries
end
def with_typography(typography)
@entries.select { |entry| entry.typography == typography }
end
def text_id(text_id)
@entries.find { |entry| entry.text_id == text_id }
end
def all_text_ids
@entries.collect { |entry| entry.text_id }
end
def include?(text_entry)
@entries.find { |entry| entry.text_id == text_entry.text_id || entry.cpp_text_id == text_entry.cpp_text_id }
end
def unicode_uses_rtl
@unicode_is_rtl = true
end
def is_rtl
@unicode_is_rtl || @entries.any? { |entry| entry.is_rtl }
end
end
class TextEntry
attr_reader :text_id
attr_reader :typographies
attr_reader :alignments
attr_reader :directions
attr_reader :default_typography
attr_reader :default_alignment
attr_reader :default_direction
def initialize(text_id, default_typography, default_alignment, default_direction)
@text_id = text_id
@typographies = {}
@alignments = {}
@directions = {}
@translations = {}
# default typography
@default_typography = default_typography
# default alignment
@default_alignment = get_alignment_as_string(default_alignment)
# default direction
@default_direction = get_direction_as_string(default_direction)
end
def add_typography(language, typography)
@typographies[language] = typography
end
def add_alignment(language, alignment)
@alignments[language] = alignment
end
def add_direction(language, direction)
@directions[language] = direction
end
def add_translation(language, text)
translation = Translation.new(text)
@translations[language] = translation
end
def remove_translation_in(language)
@translations.delete(language)
end
def translations
@translations.values
end
def translation_in(language)
@translations[language]
end
def translations_with_typography(typography)
languages_with_typography = languages.select do |language|
if @typographies[language].nil?
@default_typography == typography
else
@typographies[language] == typography
end
end
languages_with_typography.collect{ |language| translation_in(language) }
end
def languages
@translations.keys
end
def number_of_substitutions_in(language)
@translations[language].number_of_substitutions
end
def cpp_text_id
cppify(text_id)
end
# includes the default typography
def get_all_typographies
@typographies.values.compact.insert(0, @default_typography)
end
# includes the default alignment
def get_all_alignments_as_string
@alignments.values.compact.collect{ |a| get_alignment_as_string(a) }.insert(0, @default_alignment)
end
# includes the default direction
def get_all_directions_as_string
@directions.values.compact.collect{ |a| get_direction_as_string(a) }.insert(0, @default_direction)
end
def is_rtl
get_all_directions_as_string.any? { |dir| dir == 'RTL' }
end
private
def get_alignment_as_string(a)
a.to_s.empty? ? 'LEFT' : a.to_s.upcase
end
def get_direction_as_string(d)
d.to_s.empty? ? 'LTR' : d.to_s.upcase
end
def cppify(text)
t_type = "T_" + text.upcase
# strip the keys for characters, that can not be used in C++
t_type = t_type.to_ascii
t_type.gsub!(" ", "_")
t_type.gsub!(/[^0-9a-zA-Z_]/, '')
t_type
end
end
class Translation
attr_reader :text
def initialize(text)
@text = text
end
def empty?
@text.nil? || @text.empty?
end
def length
@text.length
end
def number_of_substitutions
to_cpp.count("\2")
end
def unicodes
# Collect all unicodes and add a terminating zero, which is also part of the string
@unicodes ||=
begin
numbers.map { |number| number.to_s.gsub(/\[|\]/,'').to_i } + [0]
end
end
def to_cpp
cpp_text = @text.gsub("\2", '') # Remove all existing placeholders
regex = Regexp.new(/([^\\]|^)<(|.*?[^\\])>/) # Avoid matching \< and \>
while cpp_text.match(regex)
cpp_text.gsub!(regex, '\1'+"\2")
end
cpp_text.gsub('\\<', '<').gsub('\\>', '>') # Remove \ before < and >
end
private
def numbers
to_cpp.unpack('U*')
end
end
class String
def to_ascii
# modernized version of http://craigjolicoeur.com/blog/ruby-iconv-to-the-rescue
self.encode("ASCII", "UTF-8", :undef => :replace, :invalid => :replace, :replace => '').
unpack('U*').select { |cp| cp < 127 }.pack('U*')
end
end
@@ -0,0 +1,286 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
# coding: iso-8859-1
require 'nokogiri'
require 'rubyxl'
require 'rubyXL/convenience_methods'
require 'lib/text_entries'
require 'lib/xml_reader'
require 'lib/xml_writer'
# From https://github.com/weshatheleopard/rubyXL/wiki/How-to
class RubyXL::Cell
def unlock
xf = get_cell_xf.dup
xf.protection = xf.protection&.dup || RubyXL::Protection.new
xf.protection.locked = false
xf.apply_protection = true
self.style_index = workbook.register_new_xf(xf)
end
end
class RubyXL::Worksheet
def add_frozen_split(row:, column:)
worksheetview = RubyXL::WorksheetView.new
worksheetview.pane = RubyXL::Pane.new(:top_left_cell => RubyXL::Reference.new(row,column),
:y_split => row,
:x_split => column,
:state => 'frozenSplit',
:activePane => 'bottomRight')
worksheetviews = RubyXL::WorksheetViews.new
worksheetviews << worksheetview
self.sheet_views = worksheetviews
end
end
class TranslationIO
def initialize(file_name, translation_name)
@xml_doc = XMLReader.new.read(file_name)
@translation_name = translation_name
@file_name = file_name
@xml_file_version = @xml_doc.at("TextDatabase")["Version"]
end
WHITE = 'FFFFFF'
BLACK = '000000'
HEADER_BACKGROUND = '4472C4'
HEADER_FOREGROUND = WHITE
FIRST_ROW_BACKGROUND = 'B4C6E7'
FIRST_ROW_FOREGROUND = BLACK
SECOND_ROW_BACKGROUND = 'D9E1F2'
SECOND_ROW_FOREGROUND = BLACK
SHEET_NAME = 'TouchGFX Translation'
TEXT_ID = 'Text Id'
def exportExcel(languages)
workbook = RubyXL::Workbook.new
worksheet = workbook[0]
worksheet.sheet_name = SHEET_NAME
existing_languages = get_languages
if languages.empty?
# Empty string means "put TextID in this column"
languages = [ '' ] + existing_languages
else
# Empty string means "put TextID in this column"
languages = [''] + languages.map do |lang|
lang_upcase = lang.upcase
actual_language = existing_languages.find { |l| l.upcase == lang_upcase }
fail "ERROR: Unknown export language: #{lang}" if !actual_language
actual_language
end
end
languages.uniq!
languages.each_with_index do |lang, column|
cell = worksheet.add_cell(0, column, lang.empty? ? TEXT_ID : lang)
cell.change_font_color(HEADER_FOREGROUND)
cell.change_fill(HEADER_BACKGROUND)
cell.change_font_bold(true)
worksheet.change_column_width(column, lang.empty? ? 20 : 30)
end
# This line is only needed if the font_size of each cell is to be updated (inside the loop below)
# typography_map = @typographies.inject({}) { |map,typo| map[typo.name] = typo; map }
get_text_entries.each_with_index do |text, row|
languages.each_with_index do |lang, column|
cell = worksheet.add_cell(row+1, column, lang.empty? ? text.text_id : text.translation_in(lang))
cell.change_vertical_alignment('top')
if (row % 2) == 0
cell.change_font_color(FIRST_ROW_FOREGROUND)
cell.change_fill(FIRST_ROW_BACKGROUND)
else
cell.change_font_color(SECOND_ROW_FOREGROUND)
cell.change_fill(SECOND_ROW_BACKGROUND)
end
cell.change_font_bold(lang.empty?)
cell.change_border(:bottom, 'thin')
if !lang.empty?
cell.change_border(:right, 'thin')
cell.change_text_wrap(true)
# Lines only needed if the font size of each cell is to be updated
#typography_name = text.typographies[lang] || text.typography
#cell.change_font_size((typography_map[typography_name].font_size / 1.5).to_i)
alignment = text.alignment_in(lang) || text.alignment
cell.change_horizontal_alignment(alignment.downcase)
cell.unlock
end
end
end
worksheet.add_frozen_split(:row => 1, :column => 1)
worksheet.sheet_protection = RubyXL::WorksheetProtection.new(sheet: true, format_columns: false, format_rows: false)
workbook.write(@translation_name)
end
def importExcel(languages)
require 'lib/xml_writer'
workbook = RubyXL::Parser.parse(@translation_name)
worksheet = workbook.worksheets.find { |sheet| sheet.sheet_name == SHEET_NAME }
fail "ERROR: \"#{@translation_name}\" does not contain a sheet called \"#{SHEET_NAME}\"" if !worksheet
existing_languages = get_languages
header = [] # Collect the header with correctly capitalized languages
text_id_column = nil # Which column contains the TEXT_ID
import_columns = [] # Which columns to import
column = 0
while column < worksheet[0].size
if worksheet[0][column]
lang_cell = worksheet[0][column].value
if !lang_cell.empty?
lang_upcase = lang_cell.upcase
if lang_upcase == TEXT_ID.upcase
text_id_column = column
fail "ERROR: Multiple columns contain \"#{TEXT_ID}\"" if header.include?('')
header << ''
else
# Find the language with the correct capitalization
orig_lang = existing_languages.find { |l| l.upcase == lang_upcase }
# Fail if all languages should be imported AND the language from the spreadsheet is illegal
fail "ERROR: Text Database does not contain language \"#{lang_cell}\", create the language in the TouchGFX Designer" if languages.empty? && !orig_lang && text_id_column
# if no languages specified, import all. Otherwise only import if language is wanted
if languages.empty? || languages.any? { |l| l.upcase == lang_upcase }
import_columns += [ column ]
end
fail "ERROR: Multiple columns contain translations for language \"#{orig_lang}\"" if header.include?(orig_lang)
if not orig_lang.nil?
header << orig_lang # Append language name to header array
else
header << "#{column}" # Append unique number otherwise, will not be used, but keeps length of header array correct
end
end
end
end
column += 1
end
upper_languages = languages.map(&:upcase)
upper_existing_languages = existing_languages.map(&:upcase)
# Did we ask to import a language (on the command line) which does not exist in the spreadsheet?
fail "ERROR: Unknown language(s) #{(upper_languages - upper_existing_languages)*','}" if !(upper_languages - upper_existing_languages).empty?
fail "ERROR: Missing column \"#{TEXT_ID}\"" if !text_id_column
text_nodes = get_text_nodes_map
# Row 0 is the header
row = 1
all_text_ids = []
while row < worksheet.sheet_data.rows.size
if worksheet[row] && worksheet[row][text_id_column]
text_id = worksheet[row][text_id_column].value
if text_id && !text_id.empty?
fail "ERROR: Extra translations of Text Id \"#{text_id}\" given in line #{row}" if all_text_ids.include?(text_id)
import_columns.each do |column|
text_node = text_nodes[text_id]
fail "ERROR: The Text Id \"#{text_id}\" in line #{row} does not exist in the database" if !text_node
cell = worksheet[row][column]
cell_text = cell ? cell.value.to_s : ''
set_text_node_translation(text_node, header[column], cell_text)
#puts "Setting #{text_id}.#{header[column]} = #{worksheet[row][column].value}"
end
all_text_ids << text_id
end
end
row += 1
end
all_predefined_ids = get_text_ids
if !(all_predefined_ids - all_text_ids).empty?
puts "WARNING: \"#{@translation_name}\" does not contain the following Text Id's: #{(all_predefined_ids - all_text_ids)*', '}"
end
XMLWriter.new.write(@file_name, @xml_doc)
end
protected
def empty_to_nil(str)
str ? str.strip.empty? ? nil : str.strip : nil
end
#Get array of all Languages in XML
def get_languages
@xml_doc.xpath("/TextDatabase/Languages/Language").inject([]) do |languages, lang_node|
language = empty_to_nil(lang_node["Id"])
languages.push(language)
end
end
#Get array of all text IDs in XML
def get_text_ids
@xml_doc.xpath("/TextDatabase/Texts/TextGroup/Text").map{ |text_node| text_node["Id"] }
end
#Map of textId to XML node
def get_text_nodes_map
@xml_doc.xpath("/TextDatabase/Texts/TextGroup/Text").inject({}) do |nodes, text_node|
nodes[text_node["Id"]] = text_node
nodes
end
end
#Update the translation of a text
def set_text_node_translation(text_node, language, new_translation)
text_node.xpath("./Translation").each do |translation_node|
if translation_node["Language"] == language
#Remove old translation
translation_node.child.remove if translation_node.child
translation_node.add_child(Nokogiri::XML::Text.new(new_translation, @xml_doc))
end
end
end
#Class holding single text with ID, default alignment, translations, and alignments
class TextEntry
def initialize(text_id, alignment)
@text_id = text_id
@alignment = alignment
@alignments = {} # Language -> alignment
@translations = {} # Language -> text (translation)
end
def text_id
@text_id
end
def translation_in(language)
@translations[language]
end
def alignment
@alignment
end
def alignment_in(language)
@alignments[language] || alignment
end
def set_alignment(language, alignment)
@alignments[language] = alignment
end
def set_translation(language, text)
@translations[language] = text
end
end
#Compute array of TextEntry objects
def get_text_entries
texts = []
@xml_doc.xpath("/TextDatabase/Texts/TextGroup/Text").each do |text_node|
text = TextEntry.new(text_node["Id"], text_node["Alignment"])
text_node.search("Translation").each do |translation|
language = translation["Language"]
alignment = translation["Alignment"]
text.set_alignment(language, alignment) unless alignment.nil?
text.set_translation(language, translation.text)
end
texts << text
end
texts
end
end
@@ -0,0 +1,113 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
class Typographies
def initialize()
@entries = {}
end
def add(default_typography, language, font_file, font_size, bpp, is_vector, is_compressed, fallback_character, ellipsis_character, wildcard_characters, widget_wildcard_characters, wildcard_ranges, direction)
name = "#{default_typography}"
name += "_AUTO_GENERATED_FOR_#{language}" if !language.empty?
typography = Typography.new(name, font_file, font_size.to_i, bpp.to_i, is_vector, is_compressed, fallback_character, ellipsis_character, wildcard_characters, widget_wildcard_characters, wildcard_ranges)
index = Index.new(default_typography, language)
@entries[index] = {:typography => typography, :direction => direction}
resolve_any_typography_naming_conflicts
end
def get_typographies
@entries.values.map { |entry| entry[:typography] }
end
def get_typography_name(default_typography, language)
index = lookup_index(default_typography, language)
@entries[index][:typography].name
end
def get_direction(default_typography, language)
index = lookup_index(default_typography, language)
@entries[index][:direction]
end
private
def lookup_index(default_typography, language)
# First lookup language specific, if not present, then lookup default
index = Index.new(default_typography, language)
if @entries[index].nil?
index = Index.new(default_typography, "")
end
if @entries[index].nil?
language_error_text = " with language setting for '#{language}'" if !language.empty?
fail "ERROR: Unknown typography '#{default_typography}'#{language_error_text}"
end
index
end
def is_all_typography_names_uniq
original = @entries.values.map { |entry| entry[:typography].name}
original == original.uniq
end
def rename_typography_at(index)
id = 1
suffix = @entries[index][:typography].name.match(/_(\d+$)/)
if !suffix.nil?
@entries[index][:typography].name.delete_suffix!(suffix[0])
id += suffix[1].to_i
end
@entries[index][:typography].name += "_#{id}"
end
def resolve_any_typography_naming_conflicts
# Objective:
# Do not change default names
# Change language specific typography names if conflicts
default_typographies = @entries.select { |index, entry| index.language.empty? }
while !is_all_typography_names_uniq
language_specific_typographies = @entries.select { |index, entry| !index.language.empty? }
language_specific_typographies.each do |index, lang_spec_entry|
if default_typographies.values.any? { |def_entry| def_entry[:typography].name == lang_spec_entry[:typography].name }
# Rename language specific typography
rename_typography_at(index)
end
end
end
end
end
class Typography < Struct.new(:name, :font_file, :font_size, :bpp, :is_vector, :is_compressed, :fallback_character, :ellipsis_character, :wildcard_characters, :widget_wildcard_characters, :wildcard_ranges)
def cpp_name
font_file.gsub(/\.ttf$/,"").gsub(/[^0-9a-zA-Z]/, "_")
end
end
# Inspired by class Book example: https://ruby-doc.org/core-3.1.0/Hash.html
class Index
attr_reader :default_typography, :language
def initialize(default_typography, language)
@default_typography = default_typography
@language = language
end
def ==(other)
self.class === other &&
other.default_typography == @default_typography &&
other.language == @language
end
alias eql? ==
def hash
@default_typography.hash ^ @language.hash # XOR
end
end
@@ -0,0 +1,13 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
# Current version
TOUCHGFX_VERSION = "4.26.1"
@@ -0,0 +1,22 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
require 'nokogiri'
class XMLReader
def read(file_name)
xml_doc = Nokogiri::XML::Document.parse(File.new(file_name))
if !xml_doc.errors.none?
fail (["ERROR: Malformed xml in #{file_name}"] + xml_doc.errors.map { |err| "Line #{err.line}: #{err.message}" }) * "\n"
end
xml_doc
end
end
@@ -0,0 +1,28 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
require 'nokogiri'
class XMLValidator
def validate(xml_file_name)
xml_doc = Nokogiri::XML(File.read(xml_file_name))
schema_file_name = xml_file_name.gsub(/\.xml$/, '.xsd')
if File.exists?(schema_file_name)
xsd = Nokogiri::XML::Schema(File.read(schema_file_name))
result = xsd.validate(xml_doc)
if !result.empty?
fail (["ERROR: Invalid \"#{xml_file_name}\""] + result.map { |err| "Line #{err.line}: #{err.message}" }) * "\n"
end
else
fail "ERROR: Schema file \"#{schema_file_name}\" not found."
end
end
end
@@ -0,0 +1,32 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
require 'nokogiri'
require 'fileutils'
class XMLWriter
def write(file_name, xml_doc)
# Convert newlines to "&#10;"
xml_doc.xpath("//Text/Translation").each do |translation_node|
translation_node.native_content = translation_node.encode_special_chars(translation_node.text).gsub("\n", '&&#10;')
end
# Create a formatter for nice output before writing to file
content = xml_doc.to_xml
content.gsub!('/>', ' />')
if File.exist?(file_name)
old_content = File.read(file_name)
return if content == old_content
end
File.write(file_name, content)
end
end
@@ -0,0 +1,315 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
$:.unshift File.dirname(__FILE__)
require 'lib/version'
WINDOWS_LINE_ENDINGS = "\r\n"
UNIX_LINE_ENDINGS = "\n"
#on windows/mingw file.write will it self translate \n to \r\n, on linux not
LINE_ENDINGS = RUBY_PLATFORM.match(/linux/) ? WINDOWS_LINE_ENDINGS : UNIX_LINE_ENDINGS
def root_dir
# Get the dirname of this (main.rb) file:
@root_dir ||= File.dirname(__FILE__)
end
class Main
def self.banner
<<-BANNER
Create binary and cpp text files from Text Database
Usage: #{File.basename($0)} file.xml path/to/fontconvert.out path/to/fonts_output_dir path/to/localization_output_dir path/to/font/asset calling_path {remap|yes|no} {A1|A2|A4|A8} {binary_translations} {binary_fonts} {RGB565|RGB888|BW|GRAY2|GRAY4|ARGB2222|ABGR2222|RGBA2222|BGRA2222}
Where 'remap'/'yes' will map identical texts to the same memory area to save space
'A1'/'A2'/'A4'/'A8' will generate fonts in the given format
'binary_translations' will generate binary translations instead of cpp files
'binary_fonts' will generate binary font files instead of cpp files
last argument is the framebuffer format (used to limit the bit depth of the generated fonts)
Configuration specified in the application.config file take precedence over the command line arguments
BANNER
end
def self.upgrade
<<-UPGRADE
---------------------------------------------------------------------------
Your TouchGFX Environment is using an old Ruby version (#{RUBY_VERSION}).
TouchGFX #{TOUCHGFX_VERSION} uses Ruby version 3.
Please use the new TouchGFX Environment.
---------------------------------------------------------------------------
UPGRADE
end
def self.missing_files
return !File.exists?("#{@fonts_output_path}/include/fonts/ApplicationFontProvider.hpp") ||
!File.exists?("#{@localization_output_path}/include/texts/TextKeysAndLanguages.hpp")
end
if Integer(RUBY_VERSION.match(/\d+/)[0]) < 3 && !RUBY_PLATFORM.match(/linux/)
puts self.upgrade
end
if __FILE__ == $0
if ARGV.count < 6
abort self.banner
end
file_name = ARGV.shift
font_convert_path = ARGV.shift
@fonts_output_path = ARGV.shift
@localization_output_path = ARGV.shift
font_asset_path = ARGV.shift
$calling_path = ARGV.shift
#optional arguments
remap_global = ARGV.include?("yes") || ARGV.include?("remap") ? "yes" : "no"
autohint_setting = "default"
#check gpu2d
require 'json'
target_config = File.join($calling_path, "target.config")
target_config_mod_time = nil
isGPU2D = false
if File.file?(target_config)
target_config_mod_time = [File.mtime(target_config), File.ctime(target_config)].max
tar_conf = JSON.parse(File.read(target_config))["target_configuration"] || {}
optional_components = tar_conf["optional_components"]
if optional_components
isGPU2D = optional_components.include?("GPU2D")
end
end
#data format
data_format_a1 = ARGV.include?("A1") || isGPU2D ? "A1" : ""
data_format_a2 = ARGV.include?("A2") || isGPU2D ? "A2" : ""
data_format_a4 = ARGV.include?("A4") || isGPU2D ? "A4" : ""
data_format_a8 = ARGV.include?("A8") || isGPU2D ? "A8" : ""
generate_binary_translations = ARGV.include?("binary_translations") ? "yes" : "no"
generate_binary_fonts = ARGV.include?("binary_fonts") ? "yes" : "no"
framebuffer_bpp = ""
["BPP32", "BPP24", "BPP16", "BPP8", "BPP4", "BPP2", "BPP1"].each do |format|
if ARGV.include?(format)
framebuffer_bpp = format
end
end
generate_font_format = "0" # 0 = normal font format, 1 = unmapped_flash_font_format
korean_fusion_fonts = []
copy_translations_to_ram = "no"
compressed_font_cache_size = 4096
application_config = File.join($calling_path, "application.config")
application_config_mod_time = nil
if File.file?(application_config)
application_config_mod_time = [File.mtime(application_config), File.ctime(application_config)].max
text_conf = JSON.parse(File.read(application_config))["text_configuration"] || {}
cache_size = text_conf["cache_size"]
if cache_size
compressed_font_cache_size = cache_size
end
remap = text_conf["remap"]
if remap
remap_global = remap == "yes" ? "yes" : "no"
end
autohint = text_conf["autohint"]
if autohint
autohint_setting = (autohint == "no" || autohint == "force") ? autohint : "default"
end
a1 = text_conf["a1"]
if a1
data_format_a1 = a1 == "yes" ? "A1" : ""
end
a2 = text_conf["a2"]
if a2
data_format_a2 = a2 == "yes" ? "A2" : ""
end
a4 = text_conf["a4"]
if a4
data_format_a4 = a4 == "yes" ? "A4" : ""
end
a8 = text_conf["a8"]
if a8
data_format_a8 = a8 == "yes" ? "A8" : ""
end
binary_translations = text_conf["binary_translations"]
if binary_translations
generate_binary_translations = binary_translations == "yes" ? "yes" : "no"
end
binary_fonts = text_conf["binary_fonts"]
if binary_fonts
generate_binary_fonts = binary_fonts == "yes" ? "yes" : "no"
end
bpp = text_conf["framebuffer_bpp"]
if bpp
framebuffer_bpp = "BPP" + bpp
end
font_format = text_conf["font_format"]
if font_format
values = ["0", "1"]
if values.include? font_format
generate_font_format = font_format
else
puts "Font format #{font_format} not correct, using default: \"0\""
end
end
fusion_fonts = text_conf["korean_fusion_fonts"]
if fusion_fonts
korean_fusion_fonts = fusion_fonts
end
translations_to_ram = text_conf["copy_translations_to_ram"]
if translations_to_ram
copy_translations_to_ram = translations_to_ram == "yes" ? "yes" : "no"
end
end
remap_global ||= "no"
data_format = "#{data_format_a1}#{data_format_a2}#{data_format_a4}#{data_format_a8}"
if generate_binary_translations == "yes" && remap_global == "yes"
puts "Disabling global remapping of identical texts, because binary language files are generated"
remap_global = "no"
end
if copy_translations_to_ram == "yes" && remap_global == "yes"
puts "Disabling global remapping of identical texts, because translations (a language) are copied to RAM"
remap_global = "no"
end
begin
# 0. check text database file extension. Allow texts.xlsx as parameter, but require a texts.xml to be present
# 1. if text_converter/font_converter is newer than compile_time.cache, remove all files under generated/texts and generated/fonts
# 1b if generated/fonts/include/fonts/ApplicationFontProvider.hpp is missing, force generation of TextKeysAndLanguages.hpp
# 1c if generated/texts/cache/options.cache contents differ from supplies arguments, force run
# 2. if generated/texts/cache/compile_time.cache is newer than (xml file and application.config file) and fonts/ApplicationFontProvider.hpp exists then stop now
# 3. remove UnicodeList*.txt and CharSizes*.csv
# 4. create #{@localization_output_path}/include/texts/ and #{@fonts_output_path}/include/fonts/
require 'fileutils'
# 0:
if file_name.match(/\.xlsx$/)
xml_file_name = file_name.gsub(/\.xlsx$/, '.xml')
if File.exists?(xml_file_name)
if File.exists?(file_name)
puts "WARNING: Using \"#{xml_file_name}\" instead of \"#{file_name}\""
end
else
fail "ERROR: #{xml_file_name} not found"
end
file_name = xml_file_name
end
# 1:
text_converter_time = Dir[File.join(__dir__,'**','*'), font_convert_path].collect{|f| [File.mtime(f), File.ctime(f)]}.flatten.max
if ((compile_time_exists = File.exists?("#{@localization_output_path}/cache/compile_time.cache")) && text_converter_time > File.mtime("#{@localization_output_path}/cache/compile_time.cache")) || !compile_time_exists
#remove all files, as text converter is newer (probably upgraded to new TouchGFX)
puts "Cleaning generated files from #{@localization_output_path} and #{@fonts_output_path}."
if @localization_output_path.match /generated\/texts$/
local_path = @localization_output_path.gsub('\\','/')
FileUtils.rm_rf("#{local_path}")
end
if @fonts_output_path.match /generated\/fonts$/
local_path = @fonts_output_path.gsub('\\','/')
FileUtils.rm_rf("#{local_path}")
end
end
# 1b:
$Force_Generate_TextKeysAndLanguages = self.missing_files
# 1c:
force_run = false
options_file = "#{@localization_output_path}/cache/options.cache"
options = File.exists?(options_file) && File.read(options_file)
new_options = { :remap => remap_global,
:autohint => autohint_setting,
:data_format => data_format,
:binary_translations => generate_binary_translations,
:binary_fonts => generate_binary_fonts,
:font_format => generate_font_format,
:framebuffer_bpp => framebuffer_bpp,
:fusion_fonts => fusion_fonts }.to_json
if (options != new_options)
force_run = true
require 'lib/file_io'
FileIO.write_file_silent(options_file, new_options)
end
# 2:
if File.exists?("#{@localization_output_path}/cache/compile_time.cache") && !self.missing_files && !force_run
mod_time = [File.mtime(file_name), File.ctime(file_name)].max
cache_mod_time = File.mtime("#{@localization_output_path}/cache/compile_time.cache")
# exit if cache is newer than input file and cache is also newer than application.config and target.config
# No input changed since last run, so nothing to do...
if mod_time < cache_mod_time &&
(application_config_mod_time.nil? or application_config_mod_time < cache_mod_time) &&
(target_config_mod_time.nil? or target_config_mod_time < cache_mod_time)
exit
end
end
# 3:
Dir["#{@fonts_output_path}/UnicodeList*.txt"].each do |text_file|
FileUtils.rm_f(text_file)
end
Dir["#{@fonts_output_path}/VectorUnicodeList*.txt"].each do |text_file|
FileUtils.rm_f(text_file)
end
Dir["#{@fonts_output_path}/CharSizes*.csv"].each do |text_file|
FileUtils.rm_f(text_file)
end
# 4:
FileUtils.mkdir_p("#{@localization_output_path}/include/texts/")
FileUtils.mkdir_p("#{@fonts_output_path}/include/fonts")
require 'lib/emitters/fonts_cpp'
require 'lib/generator'
FontsCpp.font_convert = font_convert_path
Generator.new.run(file_name, @fonts_output_path, @localization_output_path, font_asset_path, data_format, remap_global, autohint_setting, generate_binary_translations, generate_binary_fonts, framebuffer_bpp, generate_font_format, korean_fusion_fonts, copy_translations_to_ram, compressed_font_cache_size)
#touch the cache compile time that we rely on in the makefile
FileUtils.touch "#{@localization_output_path}/cache/compile_time.cache"
rescue SystemExit => e
if e.status != 0
abort "" #Do not print anything, already done in original abort call
end
rescue Exception => e
STDERR.puts e
STDERR.puts e.backtrace if ENV['DEBUG']
abort "An error occurred during text conversion"
end
end
end
@@ -0,0 +1,8 @@
# Touchgfx text database
## texts.xml
- An example of a touchgfx text database
## texts.xsd
- The XML schema used for validating the texts.xml
The texts.xml has a reference to the texts.xsd, so when opening the texts.xml in an XML
editor (e.g. visual studio) your texts.xml will be validated according to the texts.xsd.
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<TextDatabase xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="texts.xsd" Version="4.20.0">
<Languages>
<Language Id="GB" />
<Language Id="AR" />
</Languages>
<Texts>
<TextGroup Id="Texts1">
<Text Id="Id1" TypographyId="Default" Alignment="Left">
<Translation Language="GB">main page</Translation>
<Translation Language="AR" Alignment="Right">الصفحة الرئيسية</Translation>
</Text>
<Text Id="Id2" TypographyId="Default" Alignment="Left">
<Translation Language="GB">title</Translation>
<Translation Language="AR" Alignment="Right">لقب</Translation>
</Text>
</TextGroup>
</Texts>
<Typographies>
<Typography Id="Default" Font="verdana.ttf" Size="20" Bpp="4" IsVector="no" Direction="LTR" FallbackCharacter="?">
<LanguageSetting Language="AR" Font="helvetica.ttf" Size="14" Bpp="4" Direction="RTL" />
</Typography>
</Typographies>
</TextDatabase>
@@ -0,0 +1,168 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!--Types-->
<xs:simpleType name="direction" final="restriction" >
<xs:restriction base="xs:string">
<xs:enumeration value="LTR" />
<xs:enumeration value="RTL" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="alignment" final="restriction" >
<xs:restriction base="xs:string">
<xs:enumeration value="Left" />
<xs:enumeration value="LEFT" />
<xs:enumeration value="left" />
<xs:enumeration value="Center" />
<xs:enumeration value="CENTER" />
<xs:enumeration value="center" />
<xs:enumeration value="Right" />
<xs:enumeration value="RIGHT" />
<xs:enumeration value="right" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="bpp" final="restriction" >
<xs:restriction base="xs:string">
<xs:enumeration value="1" />
<xs:enumeration value="2" />
<xs:enumeration value="4" />
<xs:enumeration value="8" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="fallbackCharacter" final="restriction" >
<xs:restriction base="xs:string">
<xs:pattern value="|.|skip|(0[xX][0-9a-fA-F]{1,4})" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="ellipsisCharacter" final="restriction" >
<xs:restriction base="xs:string">
<xs:pattern value="|.|(0[xX][0-9a-fA-F]{1,4})" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="wildcardCharacterRanges" final="restriction" >
<xs:restriction base="xs:string">
<xs:pattern value="|(.-.|(0[xX][0-9a-fA-F]{1,4}|[0-9]{1,5})(-(0[xX][0-9a-fA-F]{1,4}|[0-9]{1,5}))?)(,(.-.|(0[xX][0-9a-fA-F]{1,4}|[0-9]{1,5})(-(0[xX][0-9a-fA-F]{1,4}|[0-9]{1,5}))?))*" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="validCppIdentifier" final="restriction" >
<xs:restriction base="xs:string">
<xs:pattern value="[a-zA-Z_][a-zA-Z0-9_]*" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="validIdentifier" final="restriction" >
<xs:restriction base="xs:string">
<xs:pattern value="[a-zA-Z0-9_]+" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="fontSize" final="restriction">
<xs:restriction base="xs:unsignedInt">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="1000"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="isVector" final="restriction" >
<xs:restriction base="xs:string">
<xs:enumeration value="yes" />
<xs:enumeration value="no" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="isCompressed" final="restriction" >
<xs:restriction base="xs:string">
<xs:enumeration value="yes" />
<xs:enumeration value="no" />
</xs:restriction>
</xs:simpleType>
<!--TextDatabase specification-->
<xs:element name="TextDatabase">
<xs:complexType>
<xs:sequence>
<xs:element name="Languages">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" name="Language">
<xs:complexType>
<xs:attribute name="Id" type="validCppIdentifier" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:sequence>
<xs:element name="Texts">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" name="TextGroup">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" name="Text">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="unbounded" name="Translation">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="Language" type="validCppIdentifier" use="required" />
<xs:attribute name="Alignment" type="alignment" use="optional" />
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="Id" type="validIdentifier" use="required" />
<xs:attribute name="Alignment" type="alignment" use="required" />
<xs:attribute name="TypographyId" type="validCppIdentifier" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="Id" type="validIdentifier" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:element name="Typographies">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" name="Typography">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" name="LanguageSetting">
<xs:complexType>
<xs:attribute name="Language" type="validCppIdentifier" use="required" />
<xs:attribute name="Font" type="xs:string" use="required" />
<xs:attribute name="Size" type="fontSize" use="required" />
<xs:attribute name="Bpp" type="bpp" use="required" />
<xs:attribute name="IsVector" type="isVector" use="required" />
<xs:attribute name="Direction" type="direction" use="required" />
<xs:attribute name="IsCompressed" type="isCompressed" use="optional" />
<xs:attribute name="FallbackCharacter" type="fallbackCharacter" use="optional" />
<xs:attribute name="WildcardCharacters" type="xs:string" use="optional" />
<xs:attribute name="WidgetWildcardCharacters" type="xs:string" use="optional" />
<xs:attribute name="WildcardCharacterRanges" type="wildcardCharacterRanges" use="optional" />
<xs:attribute name="EllipsisCharacter" type="ellipsisCharacter" use="optional" />
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="Id" type="validCppIdentifier" use="required" />
<xs:attribute name="Font" type="xs:string" use="required" />
<xs:attribute name="Size" type="fontSize" use="required" />
<xs:attribute name="Bpp" type="bpp" use="required" />
<xs:attribute name="IsVector" type="isVector" use="required" />
<xs:attribute name="Direction" type="direction" use="required" />
<xs:attribute name="IsCompressed" type="isCompressed" use="optional" />
<xs:attribute name="FallbackCharacter" type="fallbackCharacter" use="optional" />
<xs:attribute name="WildcardCharacters" type="xs:string" use="optional" />
<xs:attribute name="WidgetWildcardCharacters" type="xs:string" use="optional" />
<xs:attribute name="WildcardCharacterRanges" type="wildcardCharacterRanges" use="optional" />
<xs:attribute name="EllipsisCharacter" type="ellipsisCharacter" use="optional" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="Version" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
</xs:schema>
@@ -0,0 +1,72 @@
#!env ruby
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
$:.unshift File.dirname(__FILE__)
require 'lib/text_database_validator'
require 'lib/translation_io'
require 'lib/version'
WINDOWS_LINE_ENDINGS = "\r\n"
UNIX_LINE_ENDINGS = "\n"
#on windows/mingw file.write will it self translate \n to \r\n, on linux not
LINE_ENDINGS = RUBY_PLATFORM.match(/linux/) ? WINDOWS_LINE_ENDINGS : UNIX_LINE_ENDINGS
def root_dir
# Get the dirname of this (main.rb) file:
@root_dir ||= File.dirname(__FILE__)
end
class Main
def self.banner
<<-BANNER
Export and import the text database for translation using e.g. Excel
Usage: #{File.basename($0)} {export|import} assets/texts/texts.xml translations.xlsx [language]*
Export: Will create the "translations.xlsx" by extracting the texts for
all languages from the given assets/texts/texts.xml
Import: Will merge the text translations from "translations.xlsx" into
the given assets/texts/texts.xml. Only the actual texts are
imported, not alignment and text direction.
If no languages are specified, all languages will be handled,
otherwise only the specified languages are imported/exported.
BANNER
end
abort self.banner if ARGV.count < 3
option = ARGV.shift
abort self.banner if option!='import' && option!='export'
file_name = ARGV.shift # texts.xml
translation_name = ARGV.shift # translations.xlsx
languages = ARGV # Remaining arguments are the languages
begin
TextDatabaseValidator.new.validate(file_name)
translation_io = TranslationIO.new(file_name, translation_name)
if option == 'export'
translation_io.exportExcel(languages)
else option=='import'
translation_io.importExcel(languages)
end
rescue SystemExit => e
rescue Exception => e
STDERR.puts e
STDERR.puts e.backtrace if ENV['DEBUG']
abort "An error occurred during translations #{option}!"
end
end
@@ -0,0 +1,55 @@
#!env ruby
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
$:.unshift File.dirname(__FILE__)
require 'rubygems/version'
require 'lib/text_database_upgrader'
require 'lib/text_database_validator'
require 'lib/version'
def root_dir
# Get the dirname of this (upgrader.rb) file:
@root_dir ||= File.dirname(__FILE__)
end
class Main
def self.banner
<<-BANNER
Upgrade the text database to a specific version or the latest TouchGFX version #{TOUCHGFX_VERSION}
Usage: #{File.basename($0)} path/to/text_database [version]
Note! If no version is specified the text database will be upgraded to TouchGFX version #{TOUCHGFX_VERSION}
BANNER
end
abort self.banner if ARGV.count < 1
file_name = ARGV.shift # texts.xml or texts.xlsx
version = ARGV.shift # Version to upgrade to, if not the latest TouchGFX version
version ||= TOUCHGFX_VERSION
fail "ERROR: Version #{version} is greater than latest TouchGFX version #{TOUCHGFX_VERSION}" if Gem::Version.new(version) > Gem::Version.new(TOUCHGFX_VERSION)
begin
TextDatabaseValidator.new.validate(file_name)
TextDatabaseUpgrader.new(file_name, version).run
rescue SystemExit => e
rescue Exception => e
STDERR.puts e
STDERR.puts e.backtrace if ENV['DEBUG']
abort "An error occurred during text database upgrading!"
end
end
@@ -0,0 +1,50 @@
#!env ruby
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
require 'fileutils'
###### SETUP ###############
input = ARGV[0]
output = ARGV[1]
bins = Dir[input + "/bin/*.bin"]
FileUtils.mkdir_p("#{output}/obj") #Create obj folder
symbol_file_name = "#{output}/obj/rename_symbols.txt"
###############################################
############### Process files ########################
bins.each do |bin|
bin_file_name = File.basename(bin)
puts "Generating elf32 object from binary file #{input}/bin/#{bin_file_name}..."
`arm-none-eabi-objcopy.exe -B arm --rename-section .data=ExtFlashSection,contents,alloc,load,readonly,data -I binary -O elf32-littlearm #{input}/bin/#{bin_file_name} #{output}/obj/#{bin_file_name}.o`
#Rename the symbols
output_folder_name_subst = "#{output+'/bin'}".gsub('.', '_').gsub('/', '_')
bin_file_name_subst = File.basename(bin).gsub('.', '_').gsub('/', '_')
str = output_folder_name_subst + "_" + bin_file_name_subst
content =
"_binary_#{str}_start video_#{bin_file_name_subst}_start\n" +
"_binary_#{str}_end video_#{bin_file_name_subst}_end\n" +
"_binary_#{str}_size video_#{bin_file_name_subst}_size\n"
puts "Renaming symbols..."
FileUtils.mkdir_p(File.dirname(symbol_file_name))
unless File.exist?(symbol_file_name) && content == File.open(symbol_file_name, 'r') { |f| f.read() }
File.open(symbol_file_name, 'w') { |f| f.write(content) }
end
# Bulk rename using objcopy --redefine-syms
`arm-none-eabi-objcopy.exe --redefine-syms #{output}/obj/rename_symbols.txt #{output}/obj/#{bin_file_name}.o`
end
#Remove temp file
FileUtils.rm(symbol_file_name)
@@ -0,0 +1,324 @@
# Copyright (c) 2018(-2026) STMicroelectronics.
# All rights reserved.
#
# This file is part of the TouchGFX 4.26.1 distribution.
#
# This software is licensed under terms that can be found in the LICENSE file in
# the root directory of this software component.
# If no LICENSE file comes with this software, it is provided AS-IS.
#
###############################################################################/
require 'fileutils'
require 'nokogiri'
def root_dir
# Get the dirname of this (videoconvert.rb) file:
@root_dir ||= File.dirname(__FILE__)
end
class Main
def self.banner
<<-BANNER
Convert video files.
Usage: #{File.basename($0)} {root_folder} asset_folder generated_folder
Example: #{File.basename($0)} assets/videos generated/videos
will process files in assets/videos and place the result in generated/videos
Example: #{File.basename($0)} /c/Project assets/videos generated/videos
will process files in /c/Project/assets/videos and place the result in /c/Project/generated/videos
BANNER
end
def self.write_file(file_name, content)
FileUtils.mkdir_p(File.dirname(file_name))
unless File.exist?(file_name) && content == File.open(file_name, 'r') { |f| f.read() }
puts "Generating #{file_name}"
File.open(file_name, 'w') { |f| f.write(content) }
end
end
def self.check_compiler(project_file, dual_core)
project_file_contents = File.read(project_file, :encoding=>'utf-8')
project_file_xml ||= Nokogiri::XML(project_file_contents) do |config|
config.default_xml.noblanks
end
project_node = project_file_xml.at_xpath('./Project')
#Check compiler version
ac6_node = project_node.xpath("//uAC6")
armclang_enabled = 0
armclang_enabled = ac6_node.first.inner_html.to_i if !ac6_node.nil? && !ac6_node.empty?
if dual_core && armclang_enabled != 1
armclang_enabled = ac6_node[1].inner_html.to_i if !ac6_node.nil? && !ac6_node.empty?
end
if armclang_enabled == 1
return "ARMCLANG"
else
return "ARMCC"
end
end
def self.bin_to_obj(bin, out_dir)
symbol_file_name = "#{out_dir}/obj/rename_symbols.txt"
bin_file_name = File.basename(bin)
puts "Generating elf32 object from binary file #{out_dir}/bin/#{bin_file_name}..."
`arm-none-eabi-objcopy.exe -B arm --rename-section .data=ExtFlashSection,contents,alloc,load,readonly,data -I binary -O elf32-littlearm \"#{out_dir}/bin/#{bin_file_name}\" \"#{out_dir}/obj/#{bin_file_name}.o\"`
#Rename the symbols
output_folder_name_subst = "#{out_dir+'/bin'}".gsub('.', '_').gsub('/', '_')
bin_file_name_subst = File.basename(bin).gsub('.', '_').gsub('/', '_')
str = output_folder_name_subst + "_" + bin_file_name_subst
content =
"_binary_#{str}_start video_#{bin_file_name_subst}_start\n" +
"_binary_#{str}_end video_#{bin_file_name_subst}_end\n" +
"_binary_#{str}_size video_#{bin_file_name_subst}_size\n"
puts "Renaming symbols..."
File.open(symbol_file_name, 'w') { |f| f.write(content) }
# Bulk rename using objcopy --redefine-syms
`arm-none-eabi-objcopy.exe --redefine-syms #{symbol_file_name} \"#{out_dir}/obj/#{bin_file_name}.o\"`
#Remove temp file
FileUtils.rm(symbol_file_name)
end
root_dir = '.'
if ARGV.count == 3
root_dir = ARGV.shift
end
if ARGV.count != 2
abort self.banner
end
Dir.chdir(root_dir) do
avi_dir = ARGV.shift.gsub('\\', '/')
out_dir = ARGV.shift.gsub('\\', '/')
bin_dir = File.join(out_dir, 'bin')
obj_dir = File.join(out_dir, 'obj')
avis = Dir[File.join(avi_dir, '**', '*.avi')]
# Create the output folder:
FileUtils.mkdir_p(bin_dir)
# Create mapping from existing .avi files to the .bin files
avi2bin = {}
avi2obj = {}
obj2avi = {}
bin2avi = {}
avis.each do |avi|
bin = File.join(bin_dir, File.basename(avi.gsub(/avi$/i,'bin')))
obj = File.join(obj_dir, File.basename(avi.gsub(/avi$/i,'bin.o')))
avi2bin[avi] = bin
avi2obj[avi] = obj
if bin2avi[bin]
abort "Duplication avi files: #{bin2avi[bin]} and #{avi}"
end
bin2avi[bin] = avi
obj2avi[obj] = avi
end
# Remove bin files that have no corresponding avi file
Dir[File.join(bin_dir, '*.bin')].each do |bin|
if !bin2avi[bin]
puts "Removing #{bin}"
FileUtils.rm_f(bin)
end
end
# Remove obj files that have no corresponding avi file
Dir[File.join(obj_dir, '*.bin.o')].each do |obj|
if !obj2avi[obj]
puts "Removing #{obj}"
FileUtils.rm_f(obj)
end
end
# Now process each avi file
avi2bin.each do |avi,bin|
if !File.exist?(bin) || (File.mtime(avi) > File.mtime(bin))
puts "Generating #{bin}"
FileUtils.cp(avi, bin)
end
end
# Convert .bin to .obj
FileUtils.mkdir_p("#{out_dir}/obj")
avi2obj.each do |avi, obj|
bin = avi2bin[avi]
if !File.exist?(obj) || (File.mtime(bin) > File.mtime(obj))
puts "Converting #{bin} to #{obj}"
bin_to_obj(bin, out_dir)
end
end
# Locate .ioc file
project_file = Dir['../*.ioc'].first
if project_file.nil? # search in next parent directory if .ioc file not found
project_file = Dir['../../*.ioc'].first
end
# Determine if keil is toolchain and structure of project
compiler = "ARMCC"
project_name_suffix = ""
sub_folder = ""
prefix = ".."
keil_project_file = ""
touchgfx_folder_path = "../TouchGFX"
toolchain_is_keil = false # Assume it is not Keil
dual_core = false # Assume it is not dual core
context_enabled = false # Assume not context enabled
trust_zone = false # Assume not trust zone
if project_file
# From cubemx_project_selector.rb in touchgfx-cli:
content = File.read(project_file, :encoding=>'utf-8')
target_toolchain = content.match(/ProjectManager.TargetToolchain=([\w\s\d\.\-]+)/) || abort("Unable to parse #{File.expand_path(@project_file)}")
toolchain = target_toolchain.captures.first
toolchain_is_keil = toolchain.match(/MDK-ARM/) # "ProjectManager.TargetToolchain=MDK-ARM..." is Keil
# Determine if dual core
# From cubemx_project_selector.rb in touchgfx-cli:
content = File.read(project_file, :encoding=>'utf-8')
dual_core = content.match(/Mcu.ThirdParty\d+_Instance=STMicroelectronics.X-CUBE-TOUCHGFX.\d+.\d+.\d+_M(\d+)/)
if dual_core then
touchgfx_folder_path = "../CM#{$1}/TouchGFX"
prefix = "../.."
end
# Determine if context enabled project
# From cubemx_project_selector.rb in touchgfx-cli:
content = File.read(project_file, :encoding=>'utf-8')
context_enabled = content.match(/Mcu.ThirdParty\d+_Instance=STMicroelectronics.X-CUBE-TOUCHGFX.\d+.\d+.\d+_App/)
if context_enabled then
touchgfx_folder_path = "../../Appli/TouchGFX"
prefix = "../.."
project_name_suffix << "Appli"
sub_folder << "Appli"
end
# Determine if trust zone (non secure only) project
# From cubemx_project_selector.rb in touchgfx-cli:
content = File.read(project_file, :encoding=>'utf-8')
trust_zone = content.match(/Mcu.ThirdParty\d+_Instance=STMicroelectronics.X-CUBE-TOUCHGFX.\d+.\d+.\d+_(M\d+NS|M\d+S|AppNS|AppS)/)
if trust_zone
# match expression is for dual is also true for trust zone, so we must disable dual core
dual_core = false
prefix = "../.."
if content.match(/Mcu.ThirdParty\d+_Instance=STMicroelectronics.X-CUBE-TOUCHGFX.\d+.\d+.\d+_AppNS/)
touchgfx_folder_path = "../../AppliNonSecure/TouchGFX"
project_name_suffix << "NonSecure"
sub_folder << "NonSecure"
end
if content.match(/Mcu.ThirdParty\d+_Instance=STMicroelectronics.X-CUBE-TOUCHGFX.\d+.\d+.\d+_AppS/)
touchgfx_folder_path = "../../AppliSecure/TouchGFX"
project_name_suffix << "Secure"
sub_folder << "Secure"
end
if content.match(/Mcu.ThirdParty\d+_Instance=STMicroelectronics.X-CUBE-TOUCHGFX.\d+.\d+.\d+_M\d+NS/)
touchgfx_folder_path = "../NonSecure/TouchGFX"
project_name_suffix << "NS"
end
if content.match(/Mcu.ThirdParty\d+_Instance=STMicroelectronics.X-CUBE-TOUCHGFX.\d+.\d+.\d+_M\d+S/)
touchgfx_folder_path = "../Secure/TouchGFX"
project_name_suffix << "S"
end
end
sub_folder.prepend("/") if sub_folder != ""
if toolchain_is_keil
if context_enabled
keil_project_file = "#{prefix}/MDK-ARM#{sub_folder}/#{File.basename(project_file, ".ioc")}_#{project_name_suffix}.uvprojx"
elsif trust_zone
keil_project_file = "#{prefix}/MDK-ARM/#{File.basename(project_file, ".ioc")}_#{project_name_suffix}.uvprojx"
else
keil_project_file = "#{prefix}/MDK-ARM/#{File.basename(project_file, ".ioc")}.uvprojx"
end
compiler = check_compiler(keil_project_file, dual_core)
end
end
# Generate header file
hpp_content = ""
keil_export = ""
keil_incbin = ""
avi2bin.each do |avi,bin|
name = File.basename(bin).gsub('.', '_').gsub('/', '_')
length = File.size(avi);
symbol = "video_#{name}"
start_symbol = "#{symbol}_start"
length_symbol = "#{symbol}_length"
hpp_content += "const uint32_t #{length_symbol} = #{length};\n"
hpp_content += "#ifdef SIMULATOR\n"
hpp_content += "extern const uint8_t* #{start_symbol};\n"
hpp_content += "#else\n"
hpp_content += "extern const uint8_t #{start_symbol}[];\n"
hpp_content += "#endif\n\n"
if compiler == "ARMCLANG"
keil_export += ".global #{start_symbol}\n"
keil_incbin += "\n#{start_symbol}:\n\t.incbin \"#{touchgfx_folder_path}/#{bin}\"\n"
else
keil_export += "\tEXPORT\t#{start_symbol}\n"
keil_incbin += "\n#{start_symbol}\n\tINCBIN\t#{touchgfx_folder_path}/#{bin}\n"
end
end
hpp_file = File.join(out_dir, 'include', 'videos', 'VideoDatabase.hpp')
write_file(hpp_file,
"// Generated by videoconvert. Please, do not edit!\n\n"+
"#ifndef TOUCHGFX_VIDEODATABASE_HPP\n"+
"#define TOUCHGFX_VIDEODATABASE_HPP\n"+
"\n#include <touchgfx/hal/Types.hpp>\n\n"+
hpp_content+
"#endif // TOUCHGFX_VIDEODATABASE_HPP\n")
keil_path = [out_dir, 'src', 'keil', 'Videos.s']
keil_file = File.join(keil_path)
# Only generate Videos.s if there are video files AND we are using Keil:
if avi2bin.empty? || !toolchain_is_keil
# Try to remove existing Videos.s file and folder structure (if it is empty)
begin
FileUtils.rm(keil_file) if File.exist?(keil_file)
keil_folder = File.join(keil_path[0...-1])
FileUtils.rmdir(keil_folder) if File.directory?(keil_folder)
src_folder = File.join(keil_path[0...-2])
FileUtils.rmdir(File.join(keil_path[0...-2])) if File.directory?(src_folder);
rescue
end
else
# There are videos and we are on Keil. Generate Videos.s
if compiler == "ARMCLANG"
write_file(keil_file,
"/* Generated by videoconvert. Please, do not edit! */\n\n"+
".section ExtFlashSection, \"a\"\n\n"+
keil_export+
keil_incbin)
else
write_file(keil_file,
"; Generated by videoconvert. Please, do not edit!\n\n"+
"\tAREA\tExtFlashSection, DATA, READONLY\n\n"+
keil_export+
keil_incbin+
"\n\tEND\n")
end
end
end
end