폰트 캐싱
이 섹션에서는 TouchGFX에서 폰트 캐시를 사용해 이진 폰트를 처리하는 방법을 알아보겠습니다.
먼저 이진 폰트 섹션을 읽어보십시오.
폰트 캐싱
앞서 이진 폰트을 사용하려면 폰트 전체를 메모리에 로드해야 한다고 설명했습니다. 하지만 큰 중국어 폰트처럼 폰트가 큰 경우에는 바람직한 방법이 아닙니다.
폰트 캐싱은 애플리케이션이 외장 메모리에서 문자열을 표시하는 데 필요한 문자만 로드할 수 있는 기능입니다. 즉, 전체 폰트를 주소 지정이 가능한 플래시나 RAM에 저장하지 않고 대용량 파일 시스템에 저장할 수 있습니다.
아래 그림에서 컴파일된 폰트인 Font2가 폰트 캐시로 대체되었습니다. 그러면 TouchGFX가 Font2를 사용하는 텍스트를 그려야할 때 폰트 테이블에서 CachedFont
객체를 가리키는 포인터를 찾습니다. 이 특수 폰트는 FontCache 객체에서 해당하는 문자를 조회합니다.
CachedFont는 연결된 폰트(위의 Font2)를 가리키는 포인터와 함께 설정됩니다. TouchGFX가 CachedFont에게 특정 문자를 요청하면 CachedFont가 먼저 대체하는 일반 폰트(Font2)를 확인합니다. 이 폰트는 빈 폰트일 수 있지만, 자주 사용하는 문자가 포함된 “일반” 폰트가 될 수도 있습니다. 이 폰트에 필요한 문자가 없으면 CachedFont가 FontCache를 보고 해당 문자가 파일 시스템에서 로드되었는지 확인합니다.
일반 폰트에서 이미 찾아낸 문자는 캐싱할 필요가 없기 때문에 이러한 원리는 캐싱해야 할 문자 수를 최소화하는 효과가 있습니다.
애플리케이션 코드로 폰트 캐시 사용하기
애플리케이션에 CachedFont를 설치하려면 먼저 FontCache, 메모리 버퍼, 그리고 파일 시스템 리더 객체를 생성해야 합니다.
Screen1View.cpp
uint8_t fontdata[5120]; //Memory buffer for the font cache, 5Kb
FontCache fontCache;
CachedFont cachedFont; //Cached Font object
FileDataReader reader; //Filesystem reader object
FontCache가 버퍼와 리더에 연결되어야 합니다.
Screen1View.cpp
//setup the font cache with buffer and size; and file reader object
fontCache.setMemory(fontdata, 5120);
fontCache.setReader(&reader);
이제 애플리케이션이 폰트 캐시를 설정하고, CachedFont를 초기화하여 TouchGFX로 전달할 수 있습니다.
폰트 캐시에서 CachedFont 객체를 초기화하려면 TextId가 필요합니다. TextId는 CachedFont가 가리켜야 할 폰트를 조회하는 데 사용됩니다. 따라서 텍스트에서 사용하는 폰트를 안심하고 대체하여 디스플레이에 표시할 수 있습니다.
Screen1View.cpp
//initialize the cachedFont object to the font used by T_SINGLEUSEID1
TypedText text = TypedText(T_SINGLEUSEID1);
fontCache.initializeCachedFont(text, &cachedFont);
//replace the linked in font in TouchGFX with cachedFont
TypedTextDatabase::setFont(DEFAULT, &cachedFont);
위의 코드는 애플리케이션에서 어디든지 삽입할 수 있습니다. 예를 들어 캐싱된 폰트가 특정 View에서만 사용된다면 해당 View가 코드를 삽입하기에 적합할 수 있습니다.
문자 캐싱
폰트 캐시가 아직 빈 상태입니다. 문자를 표시하려면 먼저 해당 문자를 폰트 캐시에서 읽어와야 합니다. 그 방법은 유니코드 배열(문자열)을 폰트 캐시로 전달하는 것입니다. 아래 예제에서는 T_SINGLEUSEID1에서 텍스트를 전달합니다.
Screen1View.cpp
//cache the glyphs used by the text T_SINGLEUSEID1
Unicode::UnicodeChar* str = const_cast<Unicode::UnicodeChar*>(text.getText());
bool b = fontCache.cacheString(text, str);
폰트 캐시가 str
배열에서 찾은 문자를 리더 객체를 통해 로드합니다. 읽어온 유니코드는 TextId text
인수에서 사용하는 폰트로 연결됩니다.
애플리케이션은 정확한 파일에서 로드할 수 있도록 리더 객체를 구성해야 합니다.
합자(Ligature) 캐싱
표시하기 전에 연속되는 유니코드를 다른 유니코드로 변환하는 언어(아랍어, 힌디어 등)의 경우 위의 방법은 효과적이지 않습니다. 원본 유니코드만 캐싱할 뿐 변환 후 표시되는 유니코드는 캐싱하지 않기 때문입니다. 아래 방법을 사용하면 지정된 유니코드를 변환한 후 필요한 유니코드를 캐싱합니다(변환 후).
Screen1View.cpp
//cache the glyphs used by the text T_SINGLEUSEID1 after conversion
Unicode::UnicodeChar* str = const_cast<Unicode::UnicodeChar*>(text.getText());
bool b = fontCache.cacheLigatures(cachedFont, text, str);
메모리 사용량
폰트 캐시는 현재 사용된 메모리 용량을 계산할 수 있습니다.
Screen1View.cpp
touchgfx_printf("Memory usage %d\n", fontCache.getMemoryUsage());
GSUB 테이블 캐싱
일부 폰트는 렌더링 시 GSUB 테이블을 사용합니다. 중동 언어에 사용되는 일부 폰트(Devanagari 폰트 등)가 여기에 해당합니다. GSUB 테이블을 사용하면 폰트 시스템에서 문자 순서를 조정하여 연속되는 문자를 다른 "결합" 문자로 대체할 수 있습니다.
폰트 캐시는 파일 시스템에서 이 GSUB 테이블을 로드할 수 있습니다. 로드되지 않으면 GSUB 테이블을 텍스트 렌더링 시스템에 사용할 수 없기 때문에 폰트가 올바르게 표시되지 않습니다.
캐싱된 폰트를 초기화할 때 다음과 같이 별도의 인수를 추가하면 GSUB 테이블이 로드됩니다.
Screen1View.cpp
//initialize the cachedFont and load the GSUB table
text = TypedText(T_SINGLEUSEID1);
fontCache.initializeCachedFont(text, &cachedFont, true);
폰트 파일 리더 구현하기
위 예제 코드에서 사용되는 FileDataReader 클래스는 사용하는 운영 체제에 따라 다르기 때문에 TouchGFX에 포함되어 있지 않습니다.
다음은 일반 "stdio" 호환 파일 시스템을 위한 예제입니다.
Screen1View.cpp
class FileDataReader : public FontDataReader
{
public:
virtual ~FileDataReader() { }
virtual void open()
{
fp = fopen("Font_verdana_20_4bpp.bin", "rb");
if (!fp)
{
touchgfx_printf("Unable to open font file!!!\n");
}
}
virtual void close()
{
fclose(fp);
}
virtual void setPosition(uint32_t position)
{
fseek(fp, position, SEEK_SET);
}
virtual void readData(void* out, uint32_t numberOfBytes)
{
fread(out, numberOfBytes, 1, fp);
}
private:
FILE* fp;
};
FileDataReader 클래스는 FontCache.hpp에서 FontDataReader 인터페이스를 구현합니다.
FontCache.hpp
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;
};