Загрузка шрифтов из памяти на Ассемблере
Загрузка шрифтов из памяти на Ассемблере
Хоть я и не сторонник всяких свистоперделок в интерфейсах, но некоторые элементы диалоговых окон выглядят лучше, если к ним применить какой-нибудь шрифт. И хорошо, если это будет стандартный шрифт, типа консольного, а как быть, если требуется использовать какой-нибудь особенный стиль? Никто не даст гарантию, что у пользователя в системе имеется этот шрифт. Принудительно устанавливать в систему шрифт, который нужен только вашему приложению, - это очень плохая практика. Сохранять файл со шрифтом на диск, даже во временный каталог, тоже так себе идея. К счастью, есть простой способ использовать шрифты вообще без установки, напрямую из памяти.
Для начала файл шрифта надо внедрить в наш исполняемый файл. На Ассемблере это делается примерно так, как показано ниже. В дальнейшем нам понадобится размер файла, так что и его сразу посчитаем.
Code (Assembler) : Убрать нумерацию
- ; Шрифт содержится в исполняемом файле
- font_data:
- file 'Horrormaster.ttf'
- ; Размер файла шрифта
- font_size=$-font_data
- ; Название шрифта
- font_name db 'Horrormaster',0
Code (Assembler) : Убрать нумерацию
- ; Загрузить шрифт в память для нашего процесса
- invoke AddFontMemResourceEx,font_data,font_size,0,tmp
- ; Создать шрифт
- invoke CreateFont,40,20,0,0,700,FALSE,FALSE,FALSE,\
- DEFAULT_CHARSET,OUT_RASTER_PRECIS,CLIP_DEFAULT_PRECIS,\
- PROOF_QUALITY,FIXED_PITCH+FF_DONTCARE,font_name
- ; Установить шрифт элементу окна
- invoke SendDlgItemMessage,[hwnddlg],ID_TEXT,WM_SETFONT,eax,0
Для создания шрифта функцией CreateFont надо обязательно указать его название. В случае, когда в вашем приложении используется какой-нибудь строго определенный шрифт, то никаких проблем, вы можете сразу прописать его название в виде строки. А как быть в ситуации, когда шрифт получен из сторонних источников и никакой информации о нем нет. Для шрифтов в виде файлов все решается одной функцией GetFontResourceInfo, правда в MSDN она официально никак не документирована. Но у нас же физического файла нет, есть только данные шрифта в памяти. Значит будем парсить TTF-файлы самостоятельно. Внутренняя структура TTF-файлов отлично описана в TrueType Reference Manual или в аналогичном мануале от Microsoft. Мурзилки вы можете вдумчиво почитать в свободное время, я же сразу перейду к описанию всех необходимых структур, которые понадобятся нам для работы.
Code (Assembler) : Убрать нумерацию
- ; Заголовок шрифта
- struct TT_OFFSET_TABLE
- majorVersion dw ?
- minorVersion dw ?
- numOfTables dw ?
- searchRange dw ?
- entrySelector dw ?
- rangeShift dw ?
- ends
- ; Заголовок коллекции шрифтов
- struct TTC_HEADER
- ttcTag rb 4
- majorVersion dw ?
- minorVersion dw ?
- numFonts dd ?
- offsetTable dd ?
- ends
- ; Запись в таблице
- struct TT_TABLE_DIRECTORY
- tag rb 4
- checkSum dd ?
- tableOffset dd ?
- tableLength dd ?
- ends
- ; Заголовок таблицы наименований
- struct TT_NAME_TABLE_HEADER
- formatSelector dw ?
- numberOfRecords dw ?
- stringOffset dw ?
- ends
- ; Запись о наименовании
- struct TT_NAME_RECORD
- platformID dw ?
- encodingID dw ?
- languageID dw ?
- nameID dw ?
- stringLength dw ?
- stringOffset dw ?
- ends
Code (Assembler) : Убрать нумерацию
- ; Определить имя шрифта
- mov ebx,font_data
- ; Проверить корректность заголовка шрифта
- ; TTC
- cmp dword [ebx+TT_OFFSET_TABLE.majorVersion],'ttcf'
- jne @f
- ; Смещение первого шрифта в коллекции
- mov eax,[ebx+TTC_HEADER.offsetTable]
- bswap eax
- add ebx,eax
- @@:
- ; TTF
- cmp dword [ebx+TT_OFFSET_TABLE.majorVersion],0100h
- je @f
- ; OTF
- cmp dword [ebx+TT_OFFSET_TABLE.majorVersion],'OTTO'
- jne .loc_done
- @@:
- ; Количество записей
- movzx ecx,word [ebx+TT_OFFSET_TABLE.numOfTables]
- xchg cl,ch
- ; Указатель на список таблиц
- add ebx,sizeof.TT_OFFSET_TABLE
- @@:
- mov eax,dword [ebx+TT_TABLE_DIRECTORY.tag]
- or eax,20202020h
- cmp eax,'name'
- je @f
- add ebx,sizeof.TT_TABLE_DIRECTORY
- sub ecx,1
- jnz @b
- ; Таблица не найдена
- jmp .loc_done
- @@:
- ; Указатель на таблицу и размер данных
- mov eax,dword [ebx+TT_TABLE_DIRECTORY.tableOffset]
- bswap eax
- ; Переместить указатель на заголовок таблицы наименований
- mov ebx,font_data
- add ebx,eax
- ; Количество записей
- movzx edx,word [ebx+TT_NAME_TABLE_HEADER.numberOfRecords]
- xchg dl,dh
- movzx eax,word [ebx+TT_NAME_TABLE_HEADER.stringOffset]
- xchg al,ah
- lea esi,[eax+ebx]
- ; Указатель на последнюю запись в списке
- imul eax,edx,sizeof.TT_NAME_RECORD
- lea ebx,[ebx+eax+sizeof.TT_NAME_TABLE_HEADER-sizeof.TT_NAME_RECORD]
- .loc_scan_name:
- ; NameID должен быть Full name of the font - 1 (Big Endian)
- cmp word [ebx+TT_NAME_RECORD.nameID],0100h
- jne .loc_prev_name
- ; Platform ID должен быть Microsoft - 3 (Big Endian)
- cmp word [ebx+TT_NAME_RECORD.platformID],0300h
- jne @f
- ; с кодировкой Unicode - 1 или Symbol - 0
- movzx eax,word [ebx+TT_NAME_RECORD.encodingID]
- cmp ah,1
- jbe .loc_string
- jmp .loc_prev_name
- @@:
- ; или Macintosh - 1 (Big Endian)
- cmp word [ebx+TT_NAME_RECORD.platformID],0100h
- jne .loc_prev_name
- ; с кодировкой Roman - 0
- cmp word [ebx+TT_NAME_RECORD.encodingID],0
- je .loc_string
- .loc_prev_name:
- sub ebx,sizeof.TT_NAME_RECORD
- dec edx
- jnz .loc_scan_name
- ; Название шрифта не найдено
- jmp .loc_done
- .loc_string:
- ; Длина строки
- movzx ecx,word [ebx+TT_NAME_RECORD.stringLength]
- or ecx,ecx
- jz .loc_prev_name
- xchg cl,ch
- ; Указатель на строку
- movzx eax,word [ebx+TT_NAME_RECORD.stringOffset]
- xchg al,ah
- add esi,eax
- mov edi,font_name
- cmp word [ebx+TT_NAME_RECORD.platformID],0300h
- je .loc_unicode_string
- .loc_ascii_string:
- ; Скопировать название шрифта в буфер
- xor eax,eax
- @@:
- lodsb
- stosw
- loop @b
- jmp .loc_terminate
- .loc_unicode_string:
- ; Скопировать название шрифта в буфер
- shr ecx,1
- @@:
- lodsw
- xchg al,ah
- stosw
- loop @b
- ; Завершающий ноль
- .loc_terminate:
- xor eax,eax
- stosw
- .loc_done:
- ; Название шрифта найдено
- cmp word [font_name],0
- je .loc_error
- ; Теперь в font_name находится название шрифта
В приложении пример программы с исходным текстом, которая загружает шрифт из сегмента данных и назначает его элементу диалогового окна.
Просмотров: 3007 | Комментариев: 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
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
; Проверить корректность заголовка шрифта
; 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).
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 под паком прям самое то.
А вот на счет названия шрифтов, дааааааааа... встречалась такая проблема, а то есть, получается как ? Название вроде правильное написал и при компиляции ошибки нет, даже шрифт внутри.. Так заводишь файл, а там стандарт висит. Винда дефолтный на отображение берет и всё.
Но опять же.. Подобрал отличный шрифт "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.
Я не стал включать множество шрифтов в имени которых присутствуют символы Юникода (обычно это символы 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)
Укажите пожалуста атора картинки КрошкиШи, которая в начале Вашего поста.
Атор, Леся Гусева, очень не приветствует использование её рисунков без согласования с ней.
Она ведь пермячка, как и Вы (Вы когда-то ведь были PermCrackLab)
ManHunter
(20.03.2019 в 10:48):
DRON, так все в порядке, именно так и делается. Проверяется NameID на соответствие Font Family, для CreateFont хватает именно этой строки. Я при написании статьи проверил точно не менее сотни разных шрифтов, юникодного Font Family для любого из них было достаточно. Шрифты, о которых я упомянул, как раз были конвертированы из каких-то других, и там действительно название в ASCII не совпадало с названием в UNICODE. Если есть какой-то шрифт, не подходящий под условие, то выложи куда-нибудь, я с удовольствием посмотрю. При необходимости сразу подкорректирую код.
CreateFont загружает шрифт по Font Family, а в параметрах выбирается кодировка, всякие болды с италиками, язык и прочая хрень. На основании этих данных система сама разберется, какой из загруженных шрифтов этого семейства задействовать.
CreateFont загружает шрифт по Font Family, а в параметрах выбирается кодировка, всякие болды с италиками, язык и прочая хрень. На основании этих данных система сама разберется, какой из загруженных шрифтов этого семейства задействовать.
DRON
(20.03.2019 в 01:02):
Я когда-то писал подобное и там всё намного сложнее. Вот кусок кода из утекших исходников винды: https://pastebin.com/Vw9er2NL
Как минимум, нужно учитывать различные типы строк (FaceName, Family, SubFamily итд), а также соответствие языка строки языку системы.
Как минимум, нужно учитывать различные типы строк (FaceName, Family, SubFamily итд), а также соответствие языка строки языку системы.
Добавить комментарий
Заполните форму для добавления комментария