Pull to refresh

Вёрстка Учебника (LaTeX + CPP + GNU Make + Jenkins = Учебник)

Level of difficultyEasy
Reading time12 min
Views2.7K

Вы, наверное удивитесь, но чтобы написать учебник, надо знать системы сборки из софтверного БигТеха и, как ни странно, старый добрый сишный препроцессор (cpp). Да, господа... Именно так... Сейчас объясню, почему...

В front-end разработке существует язык разметки LaTeХ. Многие про него слышали и некоторые его используют. Это язык создан для вёрстки прекрасно скомпонованных документов. Например все datasheet(ы) на культовые мировые микроконтроллеры по 5000+ страниц как раз собраны именно через утилиту LaTeX. Обычно на LaTeX профессионально работают так называемые технические писатели. В западной академической среде тоже активно применяют LaTeX для вёрстки своих публикаций на IEEE Explore и для подготовки слайдов на всяческие околонаучные конференции.

При оформлении книги в latex автор не должен думать об оформлении. Автор думает о содержании. За оформление автор ответственности не несёт. Вся ответственность за оформление страниц перекладывается на компилятор LaTeX. В этом основная концепция.

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

В этой заметке я покажу несколько трюков по работе с LaTeX.

Итак, танцуем от печки...

Что надо из софтвера (ПО)?

Название утилиты

CygWin

Пояснение

1

cpp

+

Препроцессор языка Си

2

pdf viewer

Обозреватель pdf файлов

3

pandoc

утилита для преобразования latex в docx

4

cmd

Интерпретатор языка Batch

5

make

+

Интерпретатор языка make

6

pdflatex

Интерпретатор языка LaTeX

7

Eclipse IDE

Текстовый редактор Eclipse

8

git

+

Система контроля версий для ваших текстов учебника

12

realpath

+

Вычислить абсолютный путь к папке

13

sort

+

Утилита для сортировки строк. Например для списка акронимов.

9

tree

+

Обозреватель содержимого папки

10

m4

+

Универсальный препроцессор. Целый скриптовый язык.

11

grep

+

Поиск по ключевому слову на жестком диске

Большинство этих утилит преспокойно берутся из CygWin или MinGW.

Каков план?

Я предлагаю построить и пустить вот такой конвейер метаморфоза файликов.

конвейер метаморфоза файликов
конвейер метаморфоза файликов

Зелёным цветом покрашены те файлы, которые являются для нас исходниками. Это то самое, что надо подвергать версионному контролю в git репозитории. GNU Make тут выступает дирижером оркестра и механической коробкой передач одновременно.

На каждую главу книги создается *.texi файл, *.mk файл и папка с картинками. Вот пример *.texi файла для главы:

sinclude(`latex_m4_preproc_utils.texi')

\graphicspath{ {PATH_CHAPTER_X_DIR/pix} }


\chapter{Название Главы}


\section{Пролог}
ххххххххххххх

\section{Параграф\_1}
хххххххххх

\section{Итог}
хххххххххххх

\section{Гиперссылки}

\begin{enumerate}
  \item \href{wwwwwwwwwwwwwwwww}{zzzzzzzzzzzz}
\end{enumerate}

На вход препроцессора cpp(или m4) подаются файлы *.texi. На утилиту pdflatex мы подаем выход самого обыкновенного сишного препроцессора (консольная утилита cpp) - то есть файл *.tex.

Если проводить аналогию, то LaTex - это как компоновщик LD из разработки ПО, только не для бинарного кода, а для обыкновенного человекочитаемого текста. Вот такие вот пирожки с капустой.

Допустим, вы пишете курсовой проект, магистерский диплом, datasheet на навороченный ASIC с 270-ю SPI-регистрами или учебник по программированию. Что надо уметь делать на Latex? На самом деле достаточно много всего разного. Однако вот минимальный джентльменский набор:

  1. Вставка изображения

  2. Вставка цитаты

  3. Вставка кусков кода (так называемые листинги)

  4. Вставка таблицы

  5. Вставка перечислений

  6. Вставка гиперссылок

  7. Вставка уравнений

  8. Вставка оглавления

  9. Вставка списка литературы

    А теперь обо всем по порядку.

Вставка изображения

Этот кусок LaTeX-кода производит вставку изображения с именем arch.png


\begin{figure}[h]
    \centering
    \includegraphics[width=0.99\textwidth]{arch}
    \caption{Harvard architecture }
    \label{fig:mesh1}
\end{figure}

Можно даже создать макрофункцию для вставки картинки одной строчкой. Вот так: INSERT_PIX( arch , Harvard architecture ). LaTeX сам найдет подходящий по контексту файл изображения в проиндексированных путях.


#define INSERT_PIX(FILE_NAME,HINT )  \
  \begin{figure}[h]  \
    \centering   \
    \includegraphics[width=0.99\textwidth]{FILE_NAME}   \
    \caption{HINT }  \
    \label{fig:FILE_NAME}  \
  \end{figure}
    

Как на Latex оформить цитату?

В любом тексте от случая к случаю приходится вставлять цитаты. На LaTeX это можно сделать так:

\begin{quote}
    В любом деле важно определить приоритеты. 
    Иначе второстепенное, хотя и нужное, отнимет все силы 
    и не даст дойти до главного.
\end{quote}

Как оформить вставку куска с кодом?

Вставка листинга с исходным кодом:

\begin{lstlisting}[label=some-code,caption=Правильный if]
        
    int ret = NVRAM_Get(ID_IPv4_ROLE, tmp, 1, &tmp_len);
    if (MM_RET_CODE_OK != ret) {
        return ERROR_CODE_HARDWARE_FAULT;
    }

\end{lstlisting}

Если вы пишете учебник по Си и вставляете листинги с Си-кодом в LaTex код, то препроцессор будет пытаться вставить include-ы, которых на самом деле нет, и выдаст ошибку. Всё заклинит. Поэтому придется убирать символ # из листингов с кодом и писать справку (*), что надо подразумевать первым символом в строке символ #.

Как оформить таблицу?

Таблицы постоянно встречаются в текстах. Вот так они пишутся на LaTeX:

\begin{tabular} {ll}
    Акроним & Расшифровка \\
    \hline
    TUI & Text-based user interface \\
    CLI & command-line interface \\
    UART & universal asynchronous receiver / transmitter \\
    JTAG & Joint Test Action Group \\
    LED & light-emitting diode \\
    CIC & Cascaded integrator–comb \\
\end{tabular}

Как оформить перечисление "чиво-либо"?

В любых текстах то там то здесь постоянно появляются списки всяческих пунктиков. В LaTex это оформляется так:

\begin{enumerate}
    \item One
    \item Two
    \item Three
\end{enumerate}

Как оформить гиперссылки?

В гиперссылки можно заточить список литераторы:

\section{Гиперссылки}

\begin{enumerate}
  \item \href{https://habr.com/ru/post/673522/}{Настройка ToolChain(а) для Win10+GCC+С+Makefile+ARM Cortex-Mx+GDB}
  \item \href{https://habr.com/ru/post/111691/}{Пример Makefile}
  \item \href{https://www.opennet.ru/docs/RUS/gnumake/}{Эффективное использование GNU Make}
  \item \href{https://habr.com/ru/articles/857416}{Обновление Прошивки из Make Скрипта}
  \item \href{https://www.youtube.com/watch?v=HEEVxZ4rBCo}{CI/CD прошивок для микроконтроллеров в Wiren Board ( начало на 25:20)}
  \item \href{https://www.youtube.com/watch?v=vmuO4bHjTSo&t=7s}{Конвеерум 30: Эволюция рабочего окружения для embedded разработки}
  \item \href{https://habr.com/ru/post/47513/}{GNU Make может больше чем ты думаешь}
\end{enumerate}

Как настроить препроцессор CPP?

Как многим известно, Си-препроцессор можно преспокойно использовать для любого другого языка программирования (не только Си) или любого другого текста в общем. Разработчики препроцессора даже добавили специальные ключи для этого. Вот такой пучок опций надо подать на утилиту cpp для активации универсального препроцессора:

Опция (ключ CLI)

Пояснение

Пояснение

-E

textual output from the preprocessor will be in UTF-8.

Заставить препроцессор сохранять в кодировке UTF-8

-P

Inhibit generation of linemarkers

Убрать лишние комментарии на выходе препроцессора. Эта опция специально для не Си кода

-C

Do not discard comments

Не отбрасывать комментарии

-traditional-cpp

Traditional Mode

Не удалять на выходе последовательно идущие пробелы

-nostdinc

Do not search the standard system directories for header files

Не искать заголовочные файлы в стандартных директориях

-fexec-charset=UTF-8

Set the execution character set

Задать кодирование символов в кодировке UTF-8

-DHAS_XXX

Передать макрос HAS_XXX через командную строку

-undef

Do not predefine any system-specific or GCC-specific macros. The standard
predefined macros remain defined.

Не использовать макросы от компилятора Си кода. Они тут не нужны, так как нет самого исполняемого кода.

Препроцессором cpp можно не только собирать Си-код, но также и верстать LaTeX, компоновать графы в GraphViz, перекраивать Assembler код, перетасовывать LD скрипты компоновщика и всё, на что только вам хватит фантазии. Сишный препроцессор - это дубовая вещь.

Структура репозитория

Вот так может выглядеть папка с исходниками учебника. На каждую главу по отдельной папочке.

>    C:\cygwin64\bin\tree.exe
.
├── about_author
│   ├── about_author.mk
│   ├── about_author.texi
│   └── pix
├── attributes_of_good_firmware
│   ├── attributes_of_good_firmware.mk
│   ├── attributes_of_good_firmware.texi
│   └── pix
│       ├── free.png
│       ├── nano.png
│       └── ubolx_od.png
├── bibliography
│   ├── bibliography.mk
│   ├── bibliography.texi
│   └── pix
├── good_swc
│   ├── good_swc.mk
│   ├── good_swc.texi
│   └── pix
│       ├── arch.png
│       ├── folder.png
│       ├── list.png
│       ├── reg.png
│       └── scan.png
├── latex_misc.texi
├── library.mk
├── preamble.texi
└── why_make
    ├── pix
    │   ├── IAR.png
    │   ├── diff.png
    │   ├── pc.png
    │   ├── perf.png
    │   ├── reactor.png
    │   └── spher.png
    ├── why_make.mk
    └── why_make.texi

12 directories, 33 files

>

Корневой Latex файл

А это, господа, корневой LaTeX файл, в который, в зависимости от конфига, препроцессор и вмонтирует все кусочки глав.

\documentclass[a4paper, 16pt]{book}

#include "preamble.texi"

\begin{document}

\title{ \textbf{ Название брошюры }}
\author{aabzel}
\date{\today}
\maketitle
\tableofcontents

#ifdef HAS_ABOUT_AUTHOR
#include "about_author.texi"
#endif

#ifdef HAS_ATTRIBUTES_OF_GOOD_FIRMWARE
#include "attributes_of_good_firmware.texi"
#endif

#ifdef HAS_GOOD_SWC
#include "good_swc.texi"
#endif

#ifdef HAS_WHY_MAKE
#include "why_make.texi"
#endif

#ifdef HAS_BIBLIOGRAPHY
#include "bibliography.texi"
#endif

\end{document}

преамбула preamble.texi у меня вот такая


\usepackage[T2A]{fontenc}
\usepackage{cmap} % для копипасты из PDF
\usepackage{uarial}
\renewcommand{\familydefault}{\sfdefault}
\usepackage{hyperref}
\usepackage{graphicx}
\usepackage[utf8]{inputenc}
\usepackage[russian]{babel}
\usepackage{listings}
\usepackage{listingsutf8}
\usepackage[margin=0.6in]{geometry}
\usepackage{indentfirst} % для русских красных строк
\geometry{top=20mm} 
\setlength{\parindent}{1.25cm} 
\sffamily
\usepackage[fontsize=14.0pt]{fontsize}
\renewcommand{\sfdefault}{cmss}

Специально вынес преамбулу в отдельный *.texi файл, чтобы без причины не мозолить глаза.

Отдельный же *.texi файл для главы выглядит вот так:

\graphicspath{ {PATH_CHAPTER_X_DIR/pix} }
\chapter{Название главы}
Текст главы

Тут можно заметить, что препроцессор вставит переменную PATH_CHAPTER_X_DIR, которая укажет абсолютный путь к этой папке. Сам путь расcчитает make скрипт. Поэтому куда бы вы ни клонировали из GIT исходники LaTeX, путь к картинкам встанет в нужное значение автоматически. Вы уже любите препроцессор?

Скрипты MAKE файлов

Это корневой файл

ARTIFACT_NAME=main_generated
FINAL_LATEX_FILE =$(ARTIFACT_NAME).tex
ARTIFACT_DOCS=$(ARTIFACT_NAME).docx
ARTIFACT_PDF=$(ARTIFACT_NAME).pdf


BUILD_DIR=artifacts

CPP_OPT += -undef
CPP_OPT += -E
CPP_OPT += -P
CPP_OPT += -C
CPP_OPT += -fexec-charset=UTF-8
CPP_OPT += -traditional-cpp
CPP_OPT += -nostdinc
CPP_OPT += $(OPT)

PANDOC_OPT += -f latex 
PANDOC_OPT += -t docx

$(ARTIFACT_DOCS) : $(FINAL_LATEX_FILE)
	pandoc -s $^ $(PANDOC_OPT) -o $@

$(FINAL_LATEX_FILE):$(SOURCES_DOT) $(BUILD_DIR)
	$(info Preproc...)
	cpp main.texi  $(CPP_OPT) $(INCDIR) -E -o $@

$(ARTIFACT_PDF): $(FINAL_LATEX_FILE) $(BUILD_DIR)
	$(info generate_pdf...)
	./latex.bat $(FINAL_LATEX_FILE)

move_artifacts: $(BUILD_DIR)
	mv $(ARTIFACT_DOCS) $(BUILD_DIR)/$(ARTIFACT_DOCS)
	mv $(ARTIFACT_PDF) $(BUILD_DIR)/$(ARTIFACT_PDF)

all: $(ARTIFACT_PDF) $(ARTIFACT_DOCS) move_artifacts
	$(info All)

$(BUILD_DIR):
	mkdir -p $@
	
clean:
	$(info clean)
	rm $(ART_PDV)
	rm $(FINAL_LATEX_FILE)
	-rm -fR $(BUILD_DIR)
	
include $(DOCUMENTATION_DIR)/library/library.mk

Это файл library.mk

$(info LIBRARY_MK_INC=$(LIBRARY_MK_INC) )

ifneq ($(LIBRARY_MK_INC),Y)
    LIBRARY_MK_INC=Y

    LIBRARY_DIR=$(DOCUMENTATION_DIR)/library

    INCDIR += -I$(LIBRARY_DIR)
    OPT += -DHAS_LIBRARY

    ifeq ($(ABOUT_AUTHOR),Y)
        include $(LIBRARY_DIR)/about_author/about_author.mk
    endif

    ifeq ($(CHAPTER_1),Y)
        include $(LIBRARY_DIR)/chapter_1/chapter_1.mk
    endif
    
    ifeq ($(CHAPTER_2),Y)
        include $(LIBRARY_DIR)/chapter_2/chapter_2.mk
    endif

    ifeq ($(CHAPTER_3),Y)
        include $(LIBRARY_DIR)/chapter_3/chapter_3.mk
    endif
    
   ifeq ($(BIBLIOGRAPHY),Y)
        include $(LIBRARY_DIR)/bibliography/bibliography.mk
   endif

endif

Ну и make-файл для главы. Тут вместо CHAPTER_X и chapter_x вы просто подставите название своей главы.

$(info CHAPTER_X_MK_INC=$(CHAPTER_X_MK_INC) )

ifneq ($(CHAPTER_X_MK_INC),Y)
    CHAPTER_X_MK_INC=Y

    CHAPTER_X_DIR=$(LIBRARY_DIR)/chapter_x
    #@echo $(error CHAPTER_X_DIR=$(CHAPTER_X_DIR))

    INCDIR += -I$(CHAPTER_X_DIR)
    OPT += -DHAS_CHAPTER_X_DIR
    OPT += -DHAS_FOREIGN_AUTHORS

    OPT += -DPATH_CHAPTER_X_DIR=$(CHAPTER_X_DIR)
endif

В файле config.mk вы просто декларативно выбираете, какие главы вставлять в вашу книгу, а какие выпиливать:


ABOUT_AUTHOR=Y
CHAPTER_1=N
CHAPTER_2=Y
CHAPTER_3=N
BIBLIOGRAPHY=Y

И, наконец, сам Makefile. Как можно заметить, в языке make тоже есть свой препроцессор - команда include

PROJECT_PATH:=$(dir $(realpath $(lastword $(MAKEFILE_LIST))))
DOCUMENTATION_DIR:=$(PROJECT_PATH)../../../docs

PROJECT_PATH:=$(subst /cygdrive/c/,C:/,$(PROJECT_PATH))
DOCUMENTATION_DIR:=$(subst /cygdrive/c/,C:/,$(DOCUMENTATION_DIR))

INCDIR += -I$(PROJECT_PATH)
INCDIR += -I$(WORKSPACE_LOC)

include $(PROJECT_PATH)config.mk
include $(DOCUMENTATION_DIR)/docs.mk
include $(DOCUMENTATION_DIR)/make_scripts/typeset_book.mk
 

Теперь только остается дзыкнуть по *.bat-скрипту, который выполнит в консоли make -i all и у вас в этой папке появится *.docx и готовый для печати *.pdf файл. Easy!

Демо-версию получившегося учебника можно посмотреть тут

Важные моменты

1--Все latex исходники должны быть в формате кодирования UTF-8. Иначе утилита pandoc вам сгенерирует *.docs с кракозябрами.

Сборка учебника на CI сервере Jenkins

А теперь приятный бонус. Этот make скрипт сборки учебника, который мы написали скармливаем серверу сборки Jenkins и сервер сам нам теперь будет собирать *.pdf(ку) после каждого коммита в репозиторий. Автоматически. Здорово! Вы уже любите скрипты cборки?

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

Достоинства тандема LaTex + препроцессора

++ Вы получаете полностью конфигурируемый процесс сборки своей брошюры. Благодаря системе сборки Вы можете автоматически синтезировать множество вариаций одного и того же учебника, меняя набор глав и содержание, просто манипулируя переменными окружения в скриптах сборки make. Вам уже нравится сиcтема сборки GNU Make?...

++ Вы получаете полностью бесплатный инструментарий для вёрстки своего дока. Все консольные утилиты свободно скачиваются.

++ Благодаря скриптам Вы можете верстать учебник автоматически на серверах сборки рядом с прошивками. Всё, что от вас требуется это сделать комит в git и, вуаля, у вас новое издание книги.

Недостатки CPP

-- Если вы пишете учебник по Си и вставляете листинги с Си-кодом в LaTex код, то препроцессор будет пытаться вставить include-ы, которых на самом деле нет, и выдаст ошибку. Всё заклинит. Поэтому придется убирать символ # из листингов с кодом и писать справку, что надо подразумевать тут символ #.  

Однако это легко решается. Существует ещё более универсальный препроцессор c лаконичным названием: m4. Надо просто реинкарнировать препроцессор m4 из 197x. Тут он даже более применим нежели cpp.

Как мигрировать с препроцессора CPP на препроцессор M4?

Так происходит условная вставка содержимого заголовочного файла:

-----------------------------
CPP

#define HAS_FILE1

#ifdef HAS_FILE1
    #include "file1.txt"
#endif

----------------------------
M4

define(HAS_FILE2)

ifdef(`HAS_FILE1', `include(`file1.txt')', )

-------------------------------

Так происходит макро-подстановка с заменой:

---------------------------------------------
CPP

#define INSERT_PIX(FILE_NAME,HINT, TOKEN )  \
  \begin{figure}[h]  \
      \centering   \
      \includegraphics[width=0.99\textwidth]{FILE_NAME}   \
      \caption{HINT }  \
      \label{fig:TOKEN}  \
  \end{figure}

INSERT_PIX(IL_62.jpg, IL-62 cocpit, IL_62)
INSERT_PIX(cat.jpg, Изображение кота, cat)
------------------------------------
M4

define(INSERT_PIX, format(  
                         \begin{figure}[h]  
                             \centering   
                             \includegraphics[width=0.99\textwidth]{%s}   
                             \caption{%s }  
                             \label{fig:%s}  
                         \end{figure}
                         , $1, $2 , $3 
                         )     
      )

INSERT_PIX(IL_62.jpg, IL-62 cocpit, IL_62)
INSERT_PIX(cat.jpg, Изображение кота, cat)

Условная вставка кода:


----------------------------------------------------------
CPP

#ifdef  HAS_FOREIGN_AUTHORS
  \item Цифровая обработка сигналов, Стивен Смит,2018
  \item Extreme C,   Kamran Amini, 2019    
  \item Язык С в XXI веке,    Бен Клеменс ,  2015 
  \item Алгоритмические трюки для программистов,   Генри Уоррен-мл., 2014
  \item Архитектура встраиваемых систем,   Даниэле Лакамера, 2023 
#endif


-------------------------------------------------------------------
M4

ifdef(`HAS_FOREIGN_AUTHORS',
  `
  \item Цифровая обработка сигналов, Стивен Смит,2018
  \item Extreme C,   Kamran Amini, 2019    
  \item Язык С в XXI веке,    Бен Клеменс ,  2015 
  \item Алгоритмические трюки для программистов,   Генри Уоррен-мл., 2014
  \item Архитектура встраиваемых систем,   Даниэле Лакамера, 2023 
  '
)

-------------------------------------------------------------------------------------------------------------------

Как сами видите, препроцессор M4 может всё тоже, что и препроцессор CPP, притом даже больше! M4 - это полноценный интерпретируемый язык программирования типа awk или python. Препроцессор M4 можно использовать даже как калькулятор формул.

Итоги

Удалось научиться верстать высокодобротную документацию кодом на LaTeX. Оказывается написание учебника, в общем-то, ничем не отличается от классической сборки компьютерных программ.

Вероятно такой тандемный способ верстки окажется по нутру как раз тем, кто пришел к разработке документации из какого-то программирования. Со своими погремушками.

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

Вот так, господа. Теперь и Вы умеете верстать учебники и можете спокойно учить этому других.

Словарь

Акроним

Расшифровка

CPP

C PreProcessor

pdf

Portable Document Format

UTF

Unicode Transformation Format

CLI

Command-line interface

GNU

GNU’s Not UNIX

UNIX

Uniplexed Information and Computing System

Ссылки

Вопросы:

  1. Есть ли способ пометить участок кода так, чтобы Си препроцессор его не менял? Чтобы как встретилась строка #include "file.h" так и осталась в первозданном виде. (Спойлер: Никак, надо переходить на препроцессор M4)

  2. Как сишным препроцессрором cpp вставить кусок текста макро функцией, сохранив при этом переносы строк? (Спойлер: Никак, надо переходить на препроцессор M4)

  3. Существует ли аналог консольной утилиты clang-format только для LaTeX кода?

  4. Как сделать так, чтобы LaTeX проверял русскую грамотность?

Only registered users can participate in poll. Log in, please.
Вы применяли LaTeX при написании документации?
54.84% да17
45.16% нет14
31 users voted. Nobody abstained.
Only registered users can participate in poll. Log in, please.
В чём Вы обычно пишете доки?
40% В LaTeX14
25.71% В MS Word9
17.14% В Open Office6
20% В Google Docs7
8.57% Печатаю на механической печатной машинке3
2.86% Пишу документацию от руки синей ручкой1
35 users voted. 3 users abstained.
Only registered users can participate in poll. Log in, please.
Вы использовали систему сборки Make не для сборки исполняемого машинного кода?
40.54% да15
59.46% нет22
37 users voted. Nobody abstained.
Tags:
Hubs:
+7
Comments33

Articles