init
This commit is contained in:
commit
2c2202e60d
2491 files changed
+1295990
No files matched your search
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
+28
@@ -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 %>
|
||||
}
|
||||
+59
@@ -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
|
||||
+55
@@ -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
|
||||
+93
@@ -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
|
||||
+183
@@ -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
|
||||
+42
@@ -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
|
||||
+263
@@ -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
|
||||
+46
@@ -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
|
||||
+424
@@ -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(¤tFileGlyphNode, sizeof(GlyphNode));
|
||||
currentFileGlyphNumber++;
|
||||
}
|
||||
if (currentFileGlyphNode.unicode != unicode)
|
||||
{
|
||||
// GlyphNode not found
|
||||
return top;
|
||||
}
|
||||
|
||||
// GlyphNode found
|
||||
uint32_t glyphSize = getGlyphSize(¤tFileGlyphNode);
|
||||
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
|
||||
+112
@@ -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
|
||||
+310
@@ -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
|
||||
+227
@@ -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
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
/* DO NOT EDIT THIS FILE */
|
||||
/* This file is autogenerated by the text-database code generator */
|
||||
|
||||
// Empty Language file
|
||||
+48
@@ -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 %>
|
||||
+23
@@ -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 ¤tLanguagePtr[currentLanguageIndices[id]];
|
||||
}
|
||||
+138
@@ -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
|
||||
+21
@@ -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
|
||||
+236
@@ -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
|
||||
+137
@@ -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
|
||||
+24
@@ -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 %>
|
||||
+78
@@ -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
|
||||
+86
@@ -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
|
||||
+27
@@ -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
|
||||
+27
@@ -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
|
||||
+27
@@ -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
|
||||
+52
@@ -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
|
||||
+27
@@ -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
|
||||
+52
@@ -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
|
||||
+27
@@ -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
|
||||
+27
@@ -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
|
||||
+27
@@ -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
|
||||
+27
@@ -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
|
||||
+284
@@ -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
|
||||
+162
@@ -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
|
||||
|
||||
+59
@@ -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
|
||||
+185
@@ -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
|
||||
+27
@@ -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
|
||||
+563
@@ -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
|
||||
+27
@@ -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
|
||||
+27
@@ -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
|
||||
+191
@@ -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
|
||||
+175
@@ -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
|
||||
+162
@@ -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
|
||||
+470
@@ -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
|
||||
|
||||
+45
@@ -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 " "
|
||||
xml_doc.xpath("//Text/Translation").each do |translation_node|
|
||||
translation_node.native_content = translation_node.encode_special_chars(translation_node.text).gsub("\n", '& ')
|
||||
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
|
||||
Reference in new issue
Block a user