Введение
О проекте
В данном проекте планируется создать парсер, а затем и рендер для шрифтов с расширениями TTF, OTF, TTC, OTC. Я прекрасно понимаю, что подобные проекты существуют и это будет изобретение велосипеда, однако благо, что данные инициативы не запрещены, поэтому можно заниматься этим сколь угодно много.
Документацию по данным форматам можно найти на сайте Microsoft и на сайте Apple. Мы будем их использовать как главные источники гнозиса.
Проект планируется разбить на следующие подпроекты.
Парсинг. Это чтение файла шрифта и всех входящих в него таблиц и составление на их основе описания шрифтов.
Создание векторных изображений символов и лигатур. На этом этапе можно встроить композитинг текста.
Растеризация текста. На основе полученных векторных текстур составляется растровое изображение текста. Желательно "подружить" растеризатор с наиболее распространенными API (Vulkan, OpenGL, DirectX) или библиотеками рисования.
Интеграция с композитингом текста. Здесь планируется добиться возможности правильного размещения глифов относительно друг друга. Реализация элементов текста (абзац, строчные элементы, блочные элементы).
Этот проект планируется мной как часть более крупного проекта по созданию приложений с пользовательским интерфейсом. Репозиторий на GitHub.
Описание проекта будет меняться со временем. Принимаю ваши замечания и исправления. Цикл данных статей является своеобразным влогом, но не обучающим. Наоборот я прошу об обратном отклике в виде комментариев и иных видах обратной связи.
О цикле статей
Описание проекта будет меняться со временем. Принимаю ваши замечания и исправления. Цикл данных статей является своеобразным влогом, но не обучающим. Наоборот я прошу об обратном отклике в виде комментариев.
Данный влог - это опыт создания open source проектов и изучения того как работают шрифты и как создаётся на их основе структурированный текст.
Под каждой статьёй я буду выкладывать вводную часть, в которой будет описан план работы и исправления ошибок предыдущих этапов.
Надеюсь, что чтение данного цикла статей не будет для вас утомительным. Приятного чтива!
Немного о себе
Я не создавал крупных open-source проектов, поэтому это мой первый опыт. Я являюсь самоучкой в программировании. Изучаю C++. Программирование не является моей работой, однако оно меня очень увлекает. Это также моя первая статья на habr. Я думал о том, чтобы записать видео, но текстовый формат мне понравился больше. Вдруг, кому-нибудь станет интересно, но не обещаю.
Этап вводный. Подготовка среды для проекта
Прежде чем начать писать код, необходима подготовка проекта. Начать бы проект хотелось с подготовки среды, в которой будет писаться код. Необходимы следующие вещи: среда разработки, средства создания документации кода, фреймворки для тестирования, а также, самое главное система сборки и контроля версий. Полученные шрифты нужно как-то отображать и отображать отладочную информацию о них.
Система сборки CMake
Я буду по-возможности использовать стандарт c++20. Это, на мой взгляд, самый поздний из стабильных на всех компиляторах. Будут использоваться старые добрые файлы заголовков и исходников.
CMake делится на три этапа: сборка, тестирование и установка. Мы пока остановимся на первых двух.
Так выглядит начало файла CMakeLists.txt.
#Будем использовать более-менее современную версию CMake.
cmake_minimum_required(VERSION 3.20)
#Проект будет на C/C++.
project(ttf-raster C CXX)
#Будем использовать стандарт c++20.
set(CMAKE_CXX_STANDART 20)
#c17.
set(CMAKE_C_STANDART 17)
Система сборка cmake преобразовывает файлы конфигурации в входные файлы сборки для различных генераторов. Генераторы уже собирают готовый проект.
Директории и файлы проекта
Подкаталогами данного проекта будут.
source. Там хранятся файлы исходного кода.
include. Заголовочные файлы.
test. Файлы для кода тестирования.
build. Файлы сборки.
resource. Файлы ресурсов.
install. Каталог, где будет окончательная сборка проекта для пользователя.
docs. Файлы документации.
cmake. Файлы конфигурации для Cmake.
lib. Файлы локальных библиотек.
Также в корне проекта располагается.
CMakeLists.txt для конфигурации CMake.
Doxyfile для конфигурации doxygen-а.
README.md для краткого описания проекта.
LICENSE для описания лицензионного соглашения.
.gitignore для сообщения git о том, какие файлы и директории будут игнорироваться системой контроля версий.
.gitmodule для использования одних проектов внутри других.
Для более-менее живого проекта эти файлы и каталоги необходимы. Будем надеется, что они в дальнейшем будут наполняться качественно задокументированным кодом.
Интеграция GTest в CMake
Для юнит тестирования нашего кода будем использовать небезызвестный фреймворк gtest. Его необходимо интегрировать в нашу CMake сборку. Юнит-тестирования - необходимый инструмент для разработки. Он поможет избегать фиаско с внезапными, трудно отлавливаемыми ошибками.
Для начала необходимо загрузить googletest. Делается это с помощью команды git:git submodule add https://github.com/google/googletest.git
Откроем файл .gitmodules. Там уже написаны следующие строчки
[submodule "googletest"]
path = googletest
url = https://github.com/google/googletest
Теперь googletest будет нашем подмодулем.
Затем в CMakeLists.txt добавляем директорию googletest в список поддиректорий проекта. Для этого допишем в наш основной файл.
#Включаем тестирование.
enable_testing()
#Добавляем gtest в наш проект.
add_subdirectory(googletest)
#Добавляем в наш проект файлы для тестирования
add_subdirectory(test)
Теперь напишем следующий скелет файла CMakeLists.txt в каталоге test, то бишь настроим сборку тестов.
#Будем использовать более-менее современную версию CMake.
cmake_minimum_required(VERSION 3.20)
#Проект будет на C/C++.
project(ttf-raster-test C CXX)
#Минимальная необходимая версия стандарта c++14 будем использовать её.
set(CMAKE_CXX_STANDART 14)
#c17.
set(CMAKE_C_STANDART 99)
#Задаём список файлов исходного кода для тестирования
set(SOURCE_TESTS
#Пока тут пусто(
)
#Создаём исполняемый файл для запуска тестирования
#Их может быть несколько поэтому вместо PROJECT_NAME
#стоит использовать другие имена
add_executable(${PROJECT_NAME} ${SOURCE_TESTS})
#Добавим библиотеки для тестирования
target_link_libraries(
${PROJECT_NAME} PUBLIC
gtest_main
gmock_main
)
#Добавим их в списки тестов
add_test(
NAME ${PROJECT_NAME}
COMMAND ${PROJECT_NAME}
)
Теперь, чтобы запустить все тесты, нужно запустить команду: ctest
Интеграция Doxygen в Cmake
Нужно документировать свой код, иначе можно забыть откуда что берётся и куда кладётся. Будет использоваться Doxygen. Его прелесть в том, что он преобразовывает комментарии-инструкции в коде в формат документа. Поэтому нужно при добавлении функции, класса, перечисления и всего другого, что необходимо задокументировать писать специальные комментарии, которые являются инструкциями для doxygen-а. Добавим в наш главный CMakeLists.txt строчки.
#Добавляем создание документации в наш проект
add_subdiretory(docs)
В папке docs создадим файл CMakeLists.txt и напишем там.
find_program(DOXYGEN_PATH doxygen REQUIRED)
if(DOXYGEN_PATH_NOTFOUND)
message(FATAL_ERROR "Doxygen is needed to build the documentation. Please install it on your system")
else()
message(STATUS "Doxygen found.")
add_custom_target(documentation COMMAND ${DOXYGEN_PATH} ${CMAKE_CURRENT_LIST_DIR}/Doxyfile WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
BYPRODUCTS ${CMAKE_BINARY_DIR}/html/index.html)
add_custom_command(
TARGET documentation POST_BUILD COMMAND echo "Documentation successfully generated. You can preview at: ${CMAKE_BINARY_DIR}/html/index.html")
endif()
Среда отображения
Как говорилось выше, полученные глифы нужно где-то отображать. Первое время будем использовать GDI. Будем программировать на платформе win32. Но в дальнейшем планируется поддержка других платформ. Пока давайте остановимся на этом. GDI довольно низкоуровневый интерфейс. Это то, что нам нужно. Однако, в ближайшее время нам ничего не придётся рисовать.
Для открытия файлов будем использовать стандартную библиотеку. В следующей статьёй мы подготовим поверхность, куда будут рисоваться пиксели и выводить информацию об отладке, затем мы начнём парсить шрифты.
Заключение
Зачем все эти сложности в проекте? Они необходимы для того, чтобы в дальнейшем избежать больших сложностей. Сложность написания проекта в начале пути - это автоматизация, чтобы не возиться в дальнейшем, нужно потрудиться в начале.
Это мой первый опыт. Данная статья НЕ ЯВЛЯЕТСЯ ТУТОРИАЛОМ, я никого не хочу научить. Не судите строго.
Комментарии (17)
smind
14.01.2024 10:53к сожалению шрифты слишком сложны чтобы ввязываться в это дело. но потренироваться никто ведь не может запретить. Положите на видное место ссылку на репозиторий, подпишусь на него чтобы следить за активностью
Indemsys
14.01.2024 10:53Лет 10 назад рендерил TTF шрифты с помощью исходников из проекта https://freetype.org/ на жиденьких 72 МГц микроконтроллерах STM32F1
Рекомендовал бы этот проект для изучения, чтобы не терять зря времени.
johnfound
14.01.2024 10:53Для библиотеки растеризации нет никакой нужды что-то рисовать на экране. Это даже вредно, потому что сразу ограничивает переносимость кода.
Настоятельно рекомендую ознакомиться хотя бы с API библиотеки FreeType2.
А так, проект интересный. Надо больше растеризаторов, хороших и разных.
voldemar_d
14.01.2024 10:53Можно растеризовать в какой-нибудь монохромный BMP-файл, например.
johnfound
14.01.2024 10:53FreeType так и делает, но конечно не в монохромный битмап, потому что есть субпиксельная растеризация, а она очень даже цветная. А еще есть и цветные шрифты.
shagunov Автор
14.01.2024 10:53Хотелось бы не ограничиваться одним вариантом, а дать возможность выбрать ту растеризацию, которая по душе. Например, можно придумать какие-нибудь шейдеры для ускорения отрисовки.
В шрифтах кроме контуров есть ещё и инструкции, которые можно было бы обернуть в шейдерные функции. В общем, главное, что бы я хотел - это чтобы отрисовка была на GPU. Насколько это нужно это вопрос... Дело в том, что я знаю, что для шейдеров есть определённые ограничения.
voldemar_d
14.01.2024 10:53Помнится, на защите диплома лет 10-15 назад, где один студент рассказывал о растеризации шрифтов в наладонном компьютере (Palm или что-то вроде), зашла речь об ограниченности их ресурсов. На что в зале последовали комментарии про то, как приходилось растеризовать векторные шрифты ещё в 70-х годах на тогдашних ЭВМ, где были килобайты памяти и соответствующие процессоры в единицы МГц. Вот уж правда, "жиденький" микроконтроллер у Вас на фоне этого :) А там точно 72 МГц, не 7.2?
voldemar_d
14.01.2024 10:53+2Я думал о том, чтобы записать видео, но текстовый формат мне понравился больше
Раз так, то и слово "влог" в тексте уже не нужно.
fujinon
14.01.2024 10:53+1Первое время будем использовать GDI. Будем программировать на платформе win32. Но в дальнейшем планируется поддержка других платформ. Пока давайте остановимся на этом. GDI довольно низкоуровневый интерфейс. Это то, что нам нужно.
GDI очень стар, супер стар, используйте Direct2X! Он к тому же и проще.
https://learn.microsoft.com/en-us/windows/win32/direct2d/direct2d-portal
feelamee
Мне нравится как структурирована статья.
Насчёт предложений - в современном cmake советуют использовать target_* аналоги команд. Вместо set(SOURCES...) можно использовать target_sources
Насколько я знаю, в проектах C++ принято использовать src/ для исходников и include/your_libname для публичных хедеров.
Также советую добавить отдельную директорию для зависимостей. Например thirdparty или external. А там можно добавить CMakeLists txt, в котором стоит проверить инициализированы ли сабмодули, чтобы для пользователей библиотеки выдавало понятное сообщение об ошибке.
Не совсем понимаю для чего build/, обычно его создают локально, когда собирают проект. А install/ обычно просто указывается с помощью --install-prefix. Впрочем это уже дело вкуса. Все делают как хотят - как душа ляжет.
Также я бы присмотрелся к другим системам сборки. А ещё я предпочитаю маленькие тестовые библиотеки, вроде doctest. Но о вкусах не спорят)
Удачи в начинаниях)
shagunov Автор
Благодарю за комментарий).