Как стать автором
Обновить

Простая отрисовка емодзи в атлас из таблицы .ttf (NotoColorEmoji)

Уровень сложностиСложный
Время на прочтение7 мин
Количество просмотров408
Стильная обложка
Стильная обложка

Всем привет. Текст - это неотьемлемая часть технологий, достаточно посмотреть сколько текста в приложениях, чтобы понять какую часть занимает текст в технологиях. Текст можно рисовать от руки кисточкой - каллиграфия, можно печатать - раньше это были печатные машинки, сегодня это ПК и смартфоны, текст есть на mp3 плеерах, вообщем везде где есть пиксели и возможность рисовать пикселями есть текст.

В этой статье рассмотрим возможно самый простой способ рисования всех емодзи из шрифта NotoColorEmoji.ttf.

Недавно мне стало интересно как можно реализовать отрисовку текста в растре для атласа. Небольшая вводная SO83KQuuZvg, реализовав отрисовку шрифта из ttf(TrueType-Reference-Manual/ - описание ситуации с форматом, svg - то чем будем сегодня пользоваться), захотелось добавить отрисовку емодзи.

Подготовка

пользуюсь системой FreeBSD14.2, clang++20, будем пользоваться форматом SVG, html/css, браузер.

Encode+Decode юникода и соответсвия кода к символу в этой статье мы не будем рассматривать.

Вводная

Используя С++ подготовим всё необходимое для отрисовки. Это будет html файл, css файл. Чтобы получить html надо прочитать таблицу SVG из шрифта .ttf. Отдельной утилитой сгенерируем отступы и запишем в файл css, далее мы его подключим к html, и далее средствами браузера достанем картинку.

Парсинг ttf для таблицы SVG

SVG таблица в шрифтах формата ttf(TrueType) это дополнение или формат OpenType(OpenType), который описан на сайте Microsoft.

SVG формат(SVG) имеет свой синтаксис он похож на xml. Взаимодействовать пока не будем с ним.

SVG группы - емодзи записаны по правилам сериализации в файл .ttf это значит, что перед прочтением мы узнаем отступы и количество. Чтобы прочитать определенную таблицу надо погрузиться в то как устроен формат на начальном этапе, тоесть надо понять как прочитать адреса, имена и длины таблиц, которые есть в файле. Воспользуемся описанием Chap6.html#ScalerTypeNote с сайта Apple, таблица 4(тут получаем адрес на начало таблицы) и 5(начальная информация).

Начнем

NotoColorEmoji-Regular.ttf предварительно скачан.

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <iostream>
#include <vector>

typedef unsigned char u8;
     
typedef unsigned short u16;
     
typedef unsigned int u32;

//таблица количество таблиц она занимает 12 байт
typedef struct OffsetTable_t
{
  u32 scaler;
  u16 nTabs;
  u16 searchRange;
  u16 entrySelector;
  u16 rangeShift;
}OffsetTable;

//таблица содержащая таблицы
typedef struct TableLocation_t//
{
  u8 tag[5];
  u32 ccks;
  u32 off;
  u32 len;
}TableLocation;

u32 readu32(const u8 *tempN)
{
  u32 t=((tempN[0] << 24)  |
	 (tempN[1] << 16)  |
	 (tempN[2] << 8)   |
	 (tempN[3]<<0));

  return t;
}

u16 readu16(u8 *tempN)
{
  u16 t=(tempN[0] << 8)  |
         tempN[1] ;

  return t;
}

u8 readu8(u8 *tempN)
{
  u8 t=tempN[0] ;

  return t;
}

//получение адреса таблицы
u8* FindTableByTag(u8* data,const u8 *sTag) {
  u8* p = data + 4;//scaler
  u16 numTables = readu16(p);//количество таблиц
     

  u32 intTag = readu32(sTag);//читаем тег
  u8* t = data + 12;//переместились к первой таблице
     
  for (u16 i = 0; i < numTables; ++i) {
    u32 thisTag =readu32(t);//tag
    if (thisTag == intTag) {
      p = t + 8;//offset
      u32 offset = readu32(p);
     
      return data + offset;//относительный адрес на начало
    }
    t += 16;//следующая таблица
  }
     
  return 0;
}
//тоже самое но вывод информации 
TableLocation* getTable(OffsetTable* oft,TableLocation* t,u8* data)
{
  oft->scaler=readu32(data);data+=4;
  
  oft->nTabs=readu16(data);data+=2;
  
  oft->searchRange=readu16(data);data+=2;
  
  oft->entrySelector=readu16(data);data+=2;
  
  oft->rangeShift=readu16(data);data+=2;
  
  printf("%d %d %d %d %d\n",oft->scaler,oft->nTabs,oft->searchRange,oft->entrySelector,oft->rangeShift);
  
  t=(TableLocation*)malloc(sizeof(TableLocation)*oft->nTabs);
  
  for(int i=0;i<oft->nTabs;i++){
    //data--;
    t[i].tag[0]=readu8(data++);
    t[i].tag[1]=readu8(data++);
    t[i].tag[2]=readu8(data++);
    t[i].tag[3]=readu8(data++);
    t[i].tag[4]='\0';
    //data++;

    t[i].ccks=(u32)readu32(data);data+=4;
    t[i].off=(u32)readu32(data);data+=4;
    t[i].len=(u32)readu32(data);data+=4;
    printf("%s %d\n",t[i].tag,t[i].off);
  }
  return t;
}

int main()
{
  FILE* file = fopen("NotoColorEmoji-Regular.ttf","rb");

  fseek(file, 0, SEEK_END); 
  size_t size = ftell(file);
  fseek(file, 0, SEEK_SET);

  u8* data = (u8*)malloc(size+1);
  fread(data,size,1,file);
  data[size]='\0';
  fclose(file);
  printf("1 %s\n",data);


  OffsetTable *offTable=(OffsetTable*)malloc(sizeof(OffsetTable));
  TableLocation *tLoc;

  tLoc=getTable(offTable,tLoc,data);

  free(tLoc);
  free(offTable);
  free(data);
  
  return 0;
}


вывод

./a.out
1 
65536 16 256 4 0
COLR 461536
CPAL 11048
GSUB 34748
OS/2 460
SVG  4817344
cmap 1976
glyf 1546632
head 404
hhea 332
hmtx 143240
loca 302360
maxp 268
name 556
post 300
vhea 368
vmtx 63652

Теперь прочитаем таблицу SVG (напомню она описана на сайте майкрософт spec/svg)

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <iostream>
#include <vector>

typedef unsigned char u8;
     
typedef unsigned short u16;
     
typedef unsigned int u32;

//таблица количество таблиц она занимает 12 байт
typedef struct OffsetTable_t
{
  u32 scaler;
  u16 nTabs;
  u16 searchRange;
  u16 entrySelector;
  u16 rangeShift;
}OffsetTable;

//таблица содержащая таблицы
typedef struct TableLocation_t//
{
  u8 tag[5];
  u32 ccks;
  u32 off;
  u32 len;
}TableLocation;

u32 readu32(const u8 *tempN)
{
  u32 t=((tempN[0] << 24)  |
	 (tempN[1] << 16)  |
	 (tempN[2] << 8)   |
	 (tempN[3]<<0));

  return t;
}

u16 readu16(u8 *tempN)
{
  u16 t=(tempN[0] << 8)  |
         tempN[1] ;

  return t;
}

u8 readu8(u8 *tempN)
{
  u8 t=tempN[0] ;

  return t;
}

//получение адреса таблицы
u8* FindTableByTag(u8* data,const u8 *sTag) {
  u8* p = data + 4;//scaler
  u16 numTables = readu16(p);//количество таблиц
     

  u32 intTag = readu32(sTag);//читаем тег
  u8* t = data + 12;//переместились к первой таблице
     
  for (u16 i = 0; i < numTables; ++i) {
    u32 thisTag =readu32(t);//tag
    if (thisTag == intTag) {
      p = t + 8;//offset
      u32 offset = readu32(p);
     
      return data + offset;//относительный адрес на начало
    }
    t += 16;//следующая таблица
  }
     
  return 0;
}
//тоже самое но вывод информации 
TableLocation* getTable(OffsetTable* oft,TableLocation* t,u8* data)
{
  oft->scaler=readu32(data);data+=4;
  
  oft->nTabs=readu16(data);data+=2;
  
  oft->searchRange=readu16(data);data+=2;
  
  oft->entrySelector=readu16(data);data+=2;
  
  oft->rangeShift=readu16(data);data+=2;
  
  printf("%d %d %d %d %d\n",oft->scaler,oft->nTabs,oft->searchRange,oft->entrySelector,oft->rangeShift);
  
  t=(TableLocation*)malloc(sizeof(TableLocation)*oft->nTabs);
  
  for(int i=0;i<oft->nTabs;i++){
    //data--;
    t[i].tag[0]=readu8(data++);
    t[i].tag[1]=readu8(data++);
    t[i].tag[2]=readu8(data++);
    t[i].tag[3]=readu8(data++);
    t[i].tag[4]='\0';
    //data++;

    t[i].ccks=(u32)readu32(data);data+=4;
    t[i].off=(u32)readu32(data);data+=4;
    t[i].len=(u32)readu32(data);data+=4;
    printf("%s %d\n",t[i].tag,t[i].off);
  }
  return t;
}

//структура для таблицы SVG
struct SVGDRecord_t {
    u16 start;
    u16 end;
    u32 offset;
    u32 length;
};

typedef struct SVGDRecord_t SVGDRecord;

int main()
{
  FILE* file = fopen("NotoColorEmoji-Regular.ttf","rb");

  fseek(file, 0, SEEK_END); 
  size_t size = ftell(file);
  fseek(file, 0, SEEK_SET);

  u8* data = (u8*)malloc(size+1);
  fread(data,size,1,file);
  data[size]='\0';
  fclose(file);
  printf("1 %s\n",data);

  
  OffsetTable *offTable=(OffsetTable*)malloc(sizeof(OffsetTable));
  TableLocation *tLoc;

  tLoc=getTable(offTable,tLoc,data);
  
  u8* SVG=FindTableByTag(data,(const u8*)"SVG ");//4 байта
  //читаем
  printf("\n\n\n%16s %d\n","SVGv            ",readu16(SVG));SVG=SVG+2;
  printf("%16s %d\n","SVGDocListOffset",readu32(SVG));SVG+=4;
  printf("%16s %d\n","SVGvReserved    ",readu32(SVG));SVG+=4;

  //количество
  u32 nt=readu16(SVG);SVG=SVG+2;

  //начало
  u8* tempSVG=SVG-2;

  //количество
  printf("\n\n\n%16s %d\n","NumEntries      ",nt);

  //
  SVGDRecord svgDRecord[nt];
  
  FILE *sFile = 0;
  
  sFile = fopen("htmltest1.html", "wb");

  //определим начало html
  char *tttemp="<!DOCTYPE html><html><head><title>Заголовок</title><link rel=\"stylesheet\" href=\"style.css\"></head><body><div class=\"center\">";
  fwrite(tttemp,strlen(tttemp), 1, sFile);

  //по количеству групп
  for(int i=0;i<nt;i++){
    
    svgDRecord[i].start=readu16(SVG);SVG=SVG+2;
    
    //printf("\n\n\n%16s %d\n","start      ",svgDRecord[i].start);
    
    svgDRecord[i].end=readu16(SVG);SVG=SVG+2;
    
    //printf("\n\n\n%16s %d\n","end      ",svgDRecord[i].end);
    
    svgDRecord[i].offset=readu32(SVG);SVG=SVG+4;
    
    //printf("\n\n\n%16s %d\n","Offset      ",svgDRecord[i].offset);
    
    svgDRecord[i].length=readu32(SVG);SVG=SVG+4;
    
    //пишем по относительному отступу данные определенной длинны
    fwrite(tempSVG+svgDRecord[i].offset,svgDRecord[i].length, 1, sFile);

  }

  //закроем
  fwrite("</div></body></html>",strlen("</div></body></html>"), 1, sFile);
  fclose(sFile);

  free(tLoc);
  free(offTable);
  free(data);

  return 0;
}

Далее надо написать утилиту, которая сгенерирует стили css файл - style.css

//main1.cpp
#include <iostream>
#include <fstream>
#include <string>
int main(){
  std::string t="style.css";
  std::ofstream file(t,std::ios::binary);
  std::string gMO(".center {\n  margin: auto;\n  width: 60%;\n  text-align: left;\n  width: 4250px;\n  height: 16150px;\n  overflow-y: scroll;\n}\nsvg {\n  width: 100px;\n  height: 150px;\n  overflow: visible !important;\n  transform:scale(0.1,0.1);\n  position: absolute;\n}\n");
  std::string glyph("#glyph");
  std::string glyphN1(" {\n\t\tmargin: auto;\n\t\toverflow:visible !important;\n\t\ttransform:translate(");
  std::string glyphN2("px, ");
  std::string glyphN3("px);\n}\n ");
  std::string glyphNEqual("");

  glyphNEqual+=gMO;
  uint startTX=80;
  uint startTY=500;

  int paddingTX=1300;
  int paddingTY=1300;
  
  for(int i=2;i<=3846;i++){
    glyphNEqual+=glyph;
    std::string tf = std::to_string(i);
    glyphNEqual+=tf;
    glyphNEqual+=glyphN1;
    glyphNEqual+=std::to_string(startTX);
    glyphNEqual+=glyphN2;
    glyphNEqual+=std::to_string(startTY);
    glyphNEqual+=glyphN3;
    file.write(glyphNEqual.c_str(), glyphNEqual.size());
    if(i>=4&&i<=2799){
      if(startTX >= 39080){startTX=80;startTY+=1300;}
      else startTX+=1300;
    }
    else if(i>=2800)
      {
	if(startTX >= 39080){startTX=0;startTY+=1300;}
	else startTX+=1300;
      }
    
    glyphNEqual="";
    glyphNEqual.clear();
    
  }
  file.close();
  return 0;
}

всё далее компилируем эти 2 файла и средствами браузера берем картинку из .center )

пример части емодзи
пример части емодзи
Теги:
Хабы:
+8
Комментарии2

Публикации

Работа

QT разработчик
8 вакансий
Программист C++
100 вакансий

Ближайшие события