Введение

О проекте

В данном проекте планируется создать парсер, а затем и рендер для шрифтов с расширениями TTF, OTF, TTC, OTC. Я прекрасно понимаю, что подобные проекты существуют и это будет изобретение велосипеда, однако благо, что данные инициативы не запрещены, поэтому можно заниматься этим сколь угодно много.

Документацию по данным форматам можно найти на сайте Microsoft и на сайте Apple. Мы будем их использовать как главные источники гнозиса.

Проект планируется разбить на следующие подпроекты.

  1. Парсинг. Это чтение файла шрифта и всех входящих в него таблиц и составление на их основе описания шрифтов.

  2. Создание векторных изображений символов и лигатур. На этом этапе можно встроить композитинг текста.

  3. Растеризация текста. На основе полученных векторных текстур составляется растровое изображение текста. Желательно "подружить" растеризатор с наиболее распространенными API (Vulkan, OpenGL, DirectX) или библиотеками рисования.

  4. Интеграция с композитингом текста. Здесь планируется добиться возможности правильного размещения глифов относительно друг друга. Реализация элементов текста (абзац, строчные элементы, блочные элементы).

Этот проект планируется мной как часть более крупного проекта по созданию приложений с пользовательским интерфейсом. Репозиторий на 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)


  1. feelamee
    14.01.2024 10:53
    +2

    Мне нравится как структурирована статья.

    Насчёт предложений - в современном cmake советуют использовать target_* аналоги команд. Вместо set(SOURCES...) можно использовать target_sources

    Насколько я знаю, в проектах C++ принято использовать src/ для исходников и include/your_libname для публичных хедеров.

    Также советую добавить отдельную директорию для зависимостей. Например thirdparty или external. А там можно добавить CMakeLists txt, в котором стоит проверить инициализированы ли сабмодули, чтобы для пользователей библиотеки выдавало понятное сообщение об ошибке.

    Не совсем понимаю для чего build/, обычно его создают локально, когда собирают проект. А install/ обычно просто указывается с помощью --install-prefix. Впрочем это уже дело вкуса. Все делают как хотят - как душа ляжет.

    Также я бы присмотрелся к другим системам сборки. А ещё я предпочитаю маленькие тестовые библиотеки, вроде doctest. Но о вкусах не спорят)

    Удачи в начинаниях)


    1. shagunov Автор
      14.01.2024 10:53

      Благодарю за комментарий).


  1. smind
    14.01.2024 10:53

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


    1. voldemar_d
      14.01.2024 10:53

      Кому-то и кривые Безье сложны :)


  1. Indemsys
    14.01.2024 10:53

    Лет 10 назад рендерил TTF шрифты с помощью исходников из проекта https://freetype.org/ на жиденьких 72 МГц микроконтроллерах STM32F1

    Рекомендовал бы этот проект для изучения, чтобы не терять зря времени.


    1. johnfound
      14.01.2024 10:53

      Для библиотеки растеризации нет никакой нужды что-то рисовать на экране. Это даже вредно, потому что сразу ограничивает переносимость кода.

      Настоятельно рекомендую ознакомиться хотя бы с API библиотеки FreeType2.

      А так, проект интересный. Надо больше растеризаторов, хороших и разных.


      1. voldemar_d
        14.01.2024 10:53

        Можно растеризовать в какой-нибудь монохромный BMP-файл, например.


        1. johnfound
          14.01.2024 10:53

          FreeType так и делает, но конечно не в монохромный битмап, потому что есть субпиксельная растеризация, а она очень даже цветная. А еще есть и цветные шрифты.


          1. AnSa8x
            14.01.2024 10:53

            FreeType может много как делать. Может и в монохроме, может и в обычном Antialias, может и в LCD, а может просто спаны отдавать (рисуй сам как хочешь).


            1. johnfound
              14.01.2024 10:53

              Да, я о том же. Но что примечательно, он никак не рисует на экране.


        1. shagunov Автор
          14.01.2024 10:53

          Хотелось бы не ограничиваться одним вариантом, а дать возможность выбрать ту растеризацию, которая по душе. Например, можно придумать какие-нибудь шейдеры для ускорения отрисовки.

          В шрифтах кроме контуров есть ещё и инструкции, которые можно было бы обернуть в шейдерные функции. В общем, главное, что бы я хотел - это чтобы отрисовка была на GPU. Насколько это нужно это вопрос... Дело в том, что я знаю, что для шейдеров есть определённые ограничения.


    1. voldemar_d
      14.01.2024 10:53

      Помнится, на защите диплома лет 10-15 назад, где один студент рассказывал о растеризации шрифтов в наладонном компьютере (Palm или что-то вроде), зашла речь об ограниченности их ресурсов. На что в зале последовали комментарии про то, как приходилось растеризовать векторные шрифты ещё в 70-х годах на тогдашних ЭВМ, где были килобайты памяти и соответствующие процессоры в единицы МГц. Вот уж правда, "жиденький" микроконтроллер у Вас на фоне этого :) А там точно 72 МГц, не 7.2?


  1. JerryI
    14.01.2024 10:53
    +3

    Хорошее начало, жаль Вы решили показать только первую страницу. Хотелось бы увидеть уже минимальную рабочую демку в первой части….


    1. shagunov Автор
      14.01.2024 10:53

      Уже пишу вторую статью, где попытаюсь нарисовать хотя бы одну буковку.


  1. voldemar_d
    14.01.2024 10:53
    +2

    Я думал о том, чтобы записать видео, но текстовый формат мне понравился больше

    Раз так, то и слово "влог" в тексте уже не нужно.


  1. fujinon
    14.01.2024 10:53
    +1

    Первое время будем использовать GDI. Будем программировать на платформе win32. Но в дальнейшем планируется поддержка других платформ. Пока давайте остановимся на этом. GDI довольно низкоуровневый интерфейс. Это то, что нам нужно. 

    GDI очень стар, супер стар, используйте Direct2X! Он к тому же и проще.

    https://learn.microsoft.com/en-us/windows/win32/direct2d/direct2d-portal


    1. fujinon
      14.01.2024 10:53

      опечатка, Direct2D