Blog. Just Blog

Загрузка шрифтов из памяти на Ассемблере

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Загрузка шрифтов из памяти на Ассемблере
Загрузка шрифтов из памяти на Ассемблере

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

Для начала файл шрифта надо внедрить в наш исполняемый файл. На Ассемблере это делается примерно так, как показано ниже. В дальнейшем нам понадобится размер файла, так что и его сразу посчитаем.
  1. ; Шрифт содержится в исполняемом файле
  2. font_data:
  3. file 'Horrormaster.ttf'
  4. ; Размер файла шрифта
  5. font_size=$-font_data
  6. ; Название шрифта
  7. font_name  db 'Horrormaster',0
Переходим к загрузке нужного нам шрифта из памяти. Она выполняется одной единственной функцией AddFontMemResourceEx. Особенность ее работы заключается в том, что шрифт как бы устанавливается в систему, но фактически это происходит только для вызывающего процесса и только на время его запуска. После успешной загрузки шрифт можно использовать как любой другой системный шрифт.
  1.         ; Загрузить шрифт в память для нашего процесса
  2.         invoke  AddFontMemResourceEx,font_data,font_size,0,tmp
  3.         ; Создать шрифт
  4.         invoke  CreateFont,40,20,0,0,700,FALSE,FALSE,FALSE,\
  5.                 DEFAULT_CHARSET,OUT_RASTER_PRECIS,CLIP_DEFAULT_PRECIS,\
  6.                 PROOF_QUALITY,FIXED_PITCH+FF_DONTCARE,font_name
  7.         ; Установить шрифт элементу окна
  8.         invoke  SendDlgItemMessage,[hwnddlg],ID_TEXT,WM_SETFONT,eax,0
Шрифт можно хранить не только в сегменте данных исполняемого файла, но и в ресурсах, а то и вовсе в какой-нибудь внешней dll. В этом случае придется задействовать функции работы с ресурсами: FindResource, LoadResource и LockResource, чтобы получить адрес шрифта в памяти, а также SizeofResource для получения его размера. Все последующие действия по работе со шрифтом точно такие же.

Для создания шрифта функцией CreateFont надо обязательно указать его название. В случае, когда в вашем приложении используется какой-нибудь строго определенный шрифт, то никаких проблем, вы можете сразу прописать его название в виде строки. А как быть в ситуации, когда шрифт получен из сторонних источников и никакой информации о нем нет. Для шрифтов в виде файлов все решается одной функцией GetFontResourceInfo, правда в MSDN она официально никак не документирована. Но у нас же физического файла нет, есть только данные шрифта в памяти. Значит будем парсить TTF-файлы самостоятельно. Внутренняя структура TTF-файлов отлично описана в TrueType Reference Manual или в аналогичном мануале от Microsoft. Мурзилки вы можете вдумчиво почитать в свободное время, я же сразу перейду к описанию всех необходимых структур, которые понадобятся нам для работы.
  1. ; Заголовок шрифта
  2. struct TT_OFFSET_TABLE
  3.         majorVersion  dw ?
  4.         minorVersion  dw ?
  5.         numOfTables   dw ?
  6.         searchRange   dw ?
  7.         entrySelector dw ?
  8.         rangeShift    dw ?
  9. ends
  10.  
  11. ; Заголовок коллекции шрифтов
  12. struct TTC_HEADER
  13.         ttcTag        rb 4
  14.         majorVersion  dw ?
  15.         minorVersion  dw ?
  16.         numFonts      dd ?
  17.         offsetTable   dd ?
  18. ends
  19.  
  20. ; Запись в таблице
  21. struct TT_TABLE_DIRECTORY
  22.         tag         rb 4
  23.         checkSum    dd ?
  24.         tableOffset dd ?
  25.         tableLength dd ?
  26. ends
  27.  
  28. ; Заголовок таблицы наименований
  29. struct TT_NAME_TABLE_HEADER
  30.         formatSelector  dw ?
  31.         numberOfRecords dw ?
  32.         stringOffset    dw ?
  33. ends
  34.  
  35. ; Запись о наименовании
  36. struct TT_NAME_RECORD
  37.         platformID   dw ?
  38.         encodingID   dw ?
  39.         languageID   dw ?
  40.         nameID       dw ?
  41.         stringLength dw ?
  42.         stringOffset dw ?
  43. ends
Для определения названия шрифта надо найти в файле таблицу данных с тегом "name", а в ней найти интересующую нас строку.
  1.         ; Определить имя шрифта
  2.         mov     ebx,font_data
  3.         ; Проверить корректность заголовка шрифта
  4.         ; TTC
  5.         cmp     dword [ebx+TT_OFFSET_TABLE.majorVersion],'ttcf'
  6.         jne     @f
  7.         ; Смещение первого шрифта в коллекции
  8.         mov     eax,[ebx+TTC_HEADER.offsetTable]
  9.         bswap   eax
  10.         add     ebx,eax
  11. @@:
  12.         ; TTF
  13.         cmp     dword [ebx+TT_OFFSET_TABLE.majorVersion],0100h
  14.         je      @f
  15.         ; OTF
  16.         cmp     dword [ebx+TT_OFFSET_TABLE.majorVersion],'OTTO'
  17.         jne     .loc_error
  18. @@:
  19.         ; Количество записей
  20.         movzx   ecx,word [ebx+TT_OFFSET_TABLE.numOfTables]
  21.         xchg    cl,ch
  22.         ; Указатель на список таблиц
  23.         add     ebx,sizeof.TT_OFFSET_TABLE
  24. @@:
  25.         mov     eax,dword [ebx+TT_TABLE_DIRECTORY.tag]
  26.         or      eax,20202020h
  27.         cmp     eax,'name'
  28.         je      @f
  29.         add     ebx,sizeof.TT_TABLE_DIRECTORY
  30.         sub     ecx,1
  31.         jnz     @b
  32.         ; Таблица не найдена
  33.         jmp     .loc_error
  34. @@:
  35.         ; Указатель на таблицу и размер данных
  36.         mov     eax,dword [ebx+TT_TABLE_DIRECTORY.tableOffset]
  37.         bswap   eax
  38.  
  39.         ; Переместить указатель на заголовок таблицы наименований
  40.         mov     ebx,font_data
  41.         add     ebx,eax
  42.  
  43.         ; Количество записей
  44.         movzx   edx,word [ebx+TT_NAME_TABLE_HEADER.numberOfRecords]
  45.         xchg    dl,dh
  46.         movzx   eax,word [ebx+TT_NAME_TABLE_HEADER.stringOffset]
  47.         xchg    al,ah
  48.         lea     esi,[eax+ebx]
  49.  
  50.         ; Указатель на последнюю запись в списке
  51.         imul    eax,edx,sizeof.TT_NAME_RECORD
  52.         lea     ebx,[ebx+eax+sizeof.TT_NAME_TABLE_HEADER-sizeof.TT_NAME_RECORD]
  53. .loc_scan_name:
  54.         ; NameID должен быть Font Family - 1 (Big Endian)
  55.         cmp     word [ebx+TT_NAME_RECORD.nameID],0100h
  56.         jne     .loc_prev_name
  57.         ; Platform ID должен быть Microsoft - 3 (Big Endian)
  58.         cmp     word [ebx+TT_NAME_RECORD.platformID],0300h
  59.         jne     @f
  60.         ; с кодировкой Unicode - 1 или Symbol - 0
  61.         movzx   eax,word [ebx+TT_NAME_RECORD.encodingID]
  62.         xchg    al,ah
  63.         cmp     eax,1
  64.         jbe     .loc_string
  65.         jmp     .loc_prev_name
  66. @@:
  67.         ; или Macintosh - 1 (Big Endian)
  68.         cmp     word [ebx+TT_NAME_RECORD.platformID],0100h
  69.         jne     .loc_prev_name
  70.         ; с кодировкой Roman - 0
  71.         cmp     word [ebx+TT_NAME_RECORD.encodingID],0
  72.         je      .loc_string
  73. .loc_prev_name:
  74.         sub     ebx,sizeof.TT_NAME_RECORD
  75.         dec     edx
  76.         jnz     .loc_scan_name
  77.         ; Название шрифта не найдено
  78.         jmp     .loc_error
  79.  
  80. .loc_string:
  81.         ; Длина строки
  82.         movzx   ecx,word [ebx+TT_NAME_RECORD.stringLength]
  83.         or      ecx,ecx
  84.         jz      .loc_prev_name
  85.         xchg    cl,ch
  86.  
  87.         ; Указатель на строку
  88.         movzx   eax,word [ebx+TT_NAME_RECORD.stringOffset]
  89.         xchg    al,ah
  90.         add     esi,eax
  91.  
  92.         mov     edi,font_name
  93.  
  94.         cmp     word [ebx+TT_NAME_RECORD.platformID],0300h
  95.         je      .loc_unicode_string
  96.  
  97. .loc_ascii_string:
  98.         ; Скопировать название шрифта в буфер
  99.         xor     eax,eax
  100. @@:
  101.         lodsb
  102.         stosw
  103.         loop    @b
  104.         jmp     .loc_terminate
  105.  
  106. .loc_unicode_string:
  107.         ; Скопировать название шрифта в буфер
  108.         shr     ecx,1
  109. @@:
  110.         lodsw
  111.         xchg    al,ah
  112.         stosw
  113.         loop    @b
  114.  
  115.         ; Завершающий ноль
  116. .loc_terminate:
  117.         xor     eax,eax
  118.         stosw
  119.  
  120. .loc_done:
  121.         ; Название шрифта найдено
  122.         cmp     word [font_name],0
  123.         je      .loc_error
  124.         ; Теперь в font_name находится название шрифта
В интернетах в качестве примера получения названия шрифта на протяжении многих лет с сайта на сайт копируют следующий код. Действительно, в большинстве случаев он вернет правильный результат. Но в этом коде есть серьезная ошибка, из-за которой на некоторых TTF-файлах будет получено неправильное название. Дело в том, что таблица "name" может содержать два набора строк с названиями шрифта, один из которых записан в ASCII, а второй - в юникоде. Если файл шрифта сформирован корректно, то эти названия совпадают. Однако, в процессе написания статьи я нашел в своей коллекции несколько шрифтов, у которых названия отличались. Еще бывают варианты файлов, в которых содержится только один набор строк, с такими файлами некорректно работает даже системный установщик шрифтов. Код по приведенной ссылке возвращает только ASCII-строку названия шрифта, тогда как функция CreateFont при вызове ориентируется на строку из юникодного варианта написания, если таковая существует. В таких случаях, соответственно, функция не находит загруженный шрифт. В моей реализации эта ошибка устранена, в качестве названия приоритетно берется именно юникодная строка, а если юникодного блока строчек в файле нет, то возвращается строка из блока ASCII.

В приложении пример программы с исходным текстом, которая загружает шрифт из сегмента данных и назначает его элементу диалогового окна.

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

Font.from.Memory.Demo.zip (79,366 bytes)


Поделиться ссылкой ВКонтакте Поделиться ссылкой на Facebook Поделиться ссылкой на LiveJournal Поделиться ссылкой в Мой Круг Добавить в Мой мир Добавить на ЛиРу (Liveinternet) Добавить в закладки Memori Добавить в закладки Google
Просмотров: 1205 | Комментариев: 14

Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
ManHunter (24.03.2019 в 11:33):
Поправил. В очередной раз огромное спасибо за помощь.
DRON (23.03.2019 в 18:00):
Ещё парочка неработающих шрифтов: https://www105.zippyshare.com/...4i/file.html
Baghdad Regular.ttf и Geezah Regular.ttf - код использует последнее MAC-имя, но у него не та кодировка.
Pretzel.ttf - MAC-имя находится за пределами шрифта (получаем Access Violation). Система и редакторы шрифтов при этом работают нормально.
RoscherkDL.ttf - часть строк в неверной кодировке, но система с этим справляется.

Большинство проблем решатся, если учесть, что таблица имен сортирована и нужные нам строки находятся ближе к концу. В качестве бонуса, нам не надо будет временно копировать в буфер ASCII строки, так как они в начале таблицы и до них дело дойдёт только при отсутствии Юникодовых строк.

Вариант с обратным сканированием и проверкой кодировок: https://pastebin.com/1aviNJDT
ManHunter (22.03.2019 в 16:39):
Ну вообще красота!
DRON (22.03.2019 в 15:25):
Ну тогда, для полноты, и TTC можно добавить:
        ; Проверить корректность заголовка шрифта
        ; TTC
        cmp     dword [ebx+TT_OFFSET_TABLE.majorVersion],66637474h
        jne     @f
        ; Смещение первого шрифта в коллекции
        ; https://docs.microsoft.com/en-...#collections
        mov     eax,[ebx+0ch]
        bswap   eax
        add     ebx,eax
@@:
        ; TTF
ManHunter (22.03.2019 в 14:52):
Переделал на юникод, добавил OTF и DEFAULT_CHARSET. Теперь вообще красота, все грузится. И на юникоде даже не пришлось проверять languageID, все и так работает. DRON, огромное спасибо! Теперь можно рисовать свой просмотрщик для шрифтов :)
DRON (22.03.2019 в 14:17):
Ещё можно добавить поддержку OTF шрифтов (они часто идут с тем же .TTF расширением):
struct TT_OFFSET_TABLE
        version       dd ?
        numOfTables   dw ?
        searchRange   dw ?
        entrySelector dw ?
        rangeShift    dw ?
ends
 
; Проверить корректность заголовка шрифта
        cmp     [ebx+TT_OFFSET_TABLE.version],00000100h
        je      @f
        cmp     [ebx+TT_OFFSET_TABLE.version],4F54544Fh
        jne     .loc_done
@@:           

и стоит заменить в CreateFont ANSI_CHARSET на DEFAULT_CHARSET иначе некоторые шрифты (в основном полноюникодовые) не рендерят даже латиницу.

https://www84.zippyshare.com/v...GI/file.html
В архиве есть OTF (IrinaC.ttf), юникод в имени (IRON.TTF, aggstock.ttf, &arabia.ttf, SLAINE.TTF), шрифт в котором нужно проверять languageID (Namu R.ttf), шрифты работающий только с DEFAULT_CHARSET (keyboard.ttf и Namu R.ttf).
ManHunter (22.03.2019 в 11:12):
Откорректировал код, чтобы он работал и с одиночными блоками ASCII-строк. Теперь корректно грузятся все шрифты из подборки DRONа. Архив обновлен, статья дополнена. Еще осталось проверить шрифты с юникодом в названии. DRON, поделишься? Я у себя что-то не нашел.
Алексей (22.03.2019 в 01:45):
В шрифтах есть один минус,а это его размер. Бывает, что размер шрифта в 50 раз больше самой программы :) Мне как то 500kb шрифт попался. 
Но опять же.. Подобрал отличный шрифт "Fixedsys" от терминала.. Размер 6kb под паком прям самое то.
А вот на счет названия шрифтов, дааааааааа... встречалась такая проблема, а то есть, получается как ? Название вроде правильное написал и при компиляции ошибки нет, даже шрифт внутри.. Так заводишь файл, а там стандарт висит. Винда дефолтный на отображение берет и всё.
ManHunter (21.03.2019 в 18:05):
Во, спасибо. Мельком глянул, у этих шрифтов нет юникодного блока названия. Из-за этого обламывается не только моя программа, а также плагин-гляделка шрифтов для Total Commander, и даже виндовый установщик шрифтов не показывает название. Про "TM" знаю, у меня код под A-функции.
DRON (21.03.2019 в 17:56):
Вот несколько не определяемых шрифтов: https://www43.zippyshare.com/v...6m/file.html
Я не стал включать множество шрифтов в имени которых присутствуют символы Юникода (обычно это символы TM=Trade Mark), так как с ними можно работать только через Wide API функции. В архиве только те шрифты для которых имя вообще не определяется.
Все шрифты я проверял простой заменой font.ttf, исходники не менялись, за исключением временного добавления строки "invoke lstrcpy,font_name,'Правильное имя'", чтобы быть уверенным что шрифт вообще нормально загружается и отображается. Проверялось на Win7-x64.
ManHunter (21.03.2019 в 01:27):
Земляков обижать не буду. Заменил.
aarn (20.03.2019 в 23:41):
ManHunter !
Укажите пожалуста атора картинки КрошкиШи, которая в начале Вашего поста. 
Атор, Леся Гусева, очень не приветствует использование её рисунков без согласования с ней.
Она ведь пермячка, как и Вы (Вы когда-то ведь были PermCrackLab)
ManHunter (20.03.2019 в 10:48):
DRON, так все в порядке, именно так и делается. Проверяется NameID на соответствие Font Family, для CreateFont хватает именно этой строки. Я при написании статьи проверил точно не менее сотни разных шрифтов, юникодного Font Family для любого из них было достаточно. Шрифты, о которых я упомянул, как раз были конвертированы из каких-то других, и там действительно название в ASCII не совпадало с названием в UNICODE. Если есть какой-то шрифт, не подходящий под условие, то выложи куда-нибудь, я с удовольствием посмотрю. При необходимости сразу подкорректирую код.

CreateFont загружает шрифт по Font Family, а в параметрах выбирается кодировка, всякие болды с италиками, язык и прочая хрень. На основании этих данных система сама разберется, какой из загруженных шрифтов этого семейства задействовать.
DRON (20.03.2019 в 01:02):
Я когда-то писал подобное и там всё намного сложнее. Вот кусок кода из утекших исходников винды: https://pastebin.com/Vw9er2NL
Как минимум, нужно учитывать различные типы строк (FaceName, Family, SubFamily итд), а также соответствие языка строки языку системы.

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2020
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.08 сек. / MySQL: 2 (0.0065 сек.) / Память: 4.75 Mb
Наверх