# Структурирование программ

## Цель работы

1. Уметь определять структуры.
1. Уметь структурировать программу при помощи функций.
1. Уметь писать программы из нескольких единиц трансляции.
1. Уметь писать модульные тесты.

## Задание

Работа ведется на основе кода лабораторной работы № 1.

1. Структурировать программу при помощи функций:

    * Определить структуру `Input` для хранения исходных данных.
    * Вынести ввод данных в функцию `input_data()`.
    * Вынести поиск минимума и максимума в функцию `find_minmax()`.
    * Вынести расчет количества чисел в корзинах в функцию `make_histogram()`.
    * Вынести отображение гистограммы в функцию `show_histogram_text()`.

1. Разделить программу на единицы трансляции:

    * `main.cpp`: основная программа;
    * `histogram.cpp`: функции для расчетов;
    * `text.cpp`: отображение гистограммы в виде текста.

1. Написать программу с модульными тестами функции `find_minmax()`.

1. Перевести программу на отображение гистограммы в формате SVG.

В результате должно быть две программы с частично общим кодом.
Основная программа при запуске без параметров работает так же, как ЛР № 1
в базовом виде (не своего варианта), но выводит гистограмму в формате SVG.
Вторая программа выполняет модульные тесты функции `find_minmax()`.

Код должен быть загружен в репозитарий `cs-lab34`.
Начальный коммит должен содержать код ЛР № 1 без изменений.
Должны быть коммиты, фиксирующие выполнение пунктов задания,
с номером и описанием пункта в первой строке сообщения к коммиту.
Можно делать больше промежуточных коммитов на свое усмотрение.

Отчета не нужно.

## Указания к выполнению

### 1. Импорт кода ЛР № 1 в Git

1. В каталоге с файлами ЛР № 1 инициализируйте репозитарий Git.
1. Настройте свое имя пользователя и почту МЭИ.
1. Закоммитьте файл исходного кода (`*.cpp`) и проекта (`*.cbp` для CodeBlocks).
1. Настройте игнорирование артефактов сборки (`bin/` и `obj/` для CodeBlocks).
1. Создайте пустой репозитарий `cs-lab34` на Git УИТ.
1. Загрузите код на сервер (при необходимости настройте доступ по SSH).

### 2. Структурирование программы при помощи функций

Программа для построения гистограммы из ЛР № 1 состоит из одной функции
`main()` на более чем 100 строк, из-за чего в ней неудобно ориентироваться.

#### Структура для входных данных

Входные данные включают вектор чисел и количество корзин:

```cpp
struct Input {
    vector<double> numbers;
    size_t bin_count{};
};
```

Если ваш вариант ЛР № 1 требует ввода дополнительных данных,
на этом этапе удаляйте весь специфичный для варианта код.
Когда будете выполнять индивидуальный вариант к этой ЛР,
добавьте в эту структуру дополнительные поля входных данных,
если вариант того требует.

Поля примитивных типов (не `vector` и не `string`) рекомендуется
инициализировать нулевыми значениями, как это сделано для `bin_count`.
Тогда при объявлении переменной типа `Input` без инициализации
у полей будут удобные нулевые значения по умолчанию.

#### Функция ввода данных

Код для ввода данных в базовом варианте:

``` cpp
size_t number_count;
cin >> number_count;

vector<double> numbers(number_count);
for (size_t i = 0; i < number_count; i++) {
    cin >> numbers[i];
}

size_t bin_count;
cin >> bin_count;
```

Требуется вынести его в функцию `input_data()`,
чтобы в `main()` вместо строк выше осталась одна:

```cpp
Input in = input_data();
```

Функция не принимает параметров, а возвращает структуру `Input`:

```cpp
Input
input_data() {
```

Предлагается в ЛР писать тип возвращаемого значения на отдельной строке.
В общем случае это регламентируется стилем кодирования в проекте.

Количество чисел не входит напрямую в `Input` — входит вектор чисел,
который уже несет в себе количество элементов.
Тем не менее, ввод количества нужен для того, чтобы задать размер вектора.
Поэтому `number_count` объявляется как локальная переменная
и вводится так же, как вводилась в функции `main()`:

```cpp
    size_t number_count;
    cin >> number_count;
```

Далее вводятся те данные, которые нужно будет возвращать из функции.
Это поля структуры `Input`, поэтому нужно объявить переменную-экземпляр:

```cpp
    Input in;
```

По умолчанию вектор чисел `in.numbers` инициализируется пустым,
а количество корзин `in.bin_count` инициализируется нулем.
Нужно изменить размер вектора на введенный:

```cpp
    in.numbers.resize(number_count);
```

Элементы `in.numbers` вводятся так же,
как элементы `numbers` вводились в `main()`:

```cpp
    for (size_t i = 0; i < number_count; i++) {
        cin >> in.numbers[i];
    }
```

**Самостоятельно.**
Напишите ввод `in.bin_count`.

Если ваш вариант этой ЛР потребует ввода дополнительных данных,
нужно будет добавлять его в эту часть, когда будете делать индивидуальную часть.

В конце функции нужно вернуть результат:

```cpp
    return in;
}
```

На этом этапе структура кода должна получаться такой:

``` cpp
#include ...

struct Input {
    ...
};

Input
input_data() {
    ...
}

int
main() {
    Input in = input_data();
    ...
}
```

#### Функция поиска минимума и максимума

При поиске минимума и максимума результатов два.
Оператор `return` не позволяет вернуть два значения,
если только они не упакованы в одно составное, например, в структуру.
Однако для тренировки используем выходные параметры:

``` cpp
double min, max;
find_minmax(in.numbers, min, max);
```

Нужно сделать так, чтобы этот код работал следующим образом: в теле функции
`find_minmax()` изменялись бы аргументы `min` и `max`, и эти изменения
должны отразиться на переменных `min` и `max` в функции `main()`.

Предположим, функция определена так:

``` cpp
void
find_minmax(vector<double> numbers, double min, double max) {
    min = numbers[0];
    // (здесь код поиска минимума и максимума)
}
```

Желаемого поведения не будет: параметры `min` и `max` в функции — это отдельные
переменные, хотя они и называются так же, как переменные в `main()`.  Когда
функция `find_minmax()` вызывается, параметры `min` и `max` получают *значения*,
которым были равны второй и третий аргумент (говорят: передаются по значению).
Любые изменения `min` и `max` в теле функции не отразятся на аргументах.

Чтобы из `find_minmax()` менять внешние переменные,
можно использовать указатели или ссылки.
С точки зрения организации выходных параметров, указатели отличаются тем,
что у них есть особое значение, нулевой указатель (`nullptr`/`0`/`NULL`/`{}`).
Однако в данном случае мы не заинтересованы в том, чтобы такое значение было:
функция всегда ищет минимум и максимум и всегда должна мочь их записать.
Используем ссылки (см. амперсанды после `double`):

``` cpp
void
find_minmax(vector<double> numbers, double& min, double& max) {
    min = numbers[0];
    // (здесь код поиска минимума и максимума)
}
```

Выделите `find_minmax()`, замените код в `main()` её вызовом.
Добейтесь компиляции программы и проверьте её работу.

Оптимально ли выбран тип входного параметра `numbers`?
Во-первых, изменять его не требуется, он может быть константным.
Во-вторых, поскольку он не объявлен ссылкой или указателем,
это отдельная переменная, которая получает значением копию вектора,
который передается в функцию.
Однако копия не нужна.
Итого выгоднее использовать ссылку, чтобы не было копирования,
но константную, чтобы пользователь функции (программист) понимал,
что она не изменяет этот вектор: `const vector<double>& numbers`.

Измените тип параметра `numbers` функции `find_minmax()`.
Проверьте, что программа компилируется, а ее поведение не изменилось.

#### Функция расчета гистограммы

Функция `make_histogram()` принимает вектор чисел и количество корзин.
Она возвращает количества чисел в корзинах, то есть вектор `bins`.
В своей работе она вызывает `find_minmax()`.

#### Прочие функции

В итоге структура кода в `main()` должна получиться такой:

```cpp
int
main() {
    auto in = input_data();
    auto bins = make_histogram(in.numbers, in.bin_count);
    show_histogram_text(bins, ...);
}
```

Если ваш вариант ЛР № 1 требует дополнительных данных для вывода,
на этом этапе удаляйте весь специфичный для варианта код.

Добейтесь корректной работы программы и сделайте коммит.

### 4. Разделение программы на файлы

Хорошей практикой является отделять часть программы, которая выполняет расчеты,
от частей, которые занимаются вводом и выводом.
Разделим программу на файлы так:

```
       +-----------------------------+
       |          histogram.h        |
       +-----------------------------+
       | объявление make_histogram() |
       +-----------------------------+
          ↑                        ↑
   включает файл,            включает файл,
  вызывает функцию         реализует функцию
          ↑                        ↑
+------------------+     +------------------+
|     main.cpp     |     |  histogram.cpp   |
+------------------+     +------------------+
| вызов            |     |   определение    |
| make_histogram() |     | make_histogram() |
+------------------+     +------------------+
```

#### Создание заголовочного файла

При помощи меню *File → New → File...*
добавьте к проекту заголовочный файл *(C/C++ header).*
Он должен быть расположен в каталоге проекта:

*Filename with full path:* `C:\cs-lab34\histogram.h` \
*Add file to active project:* (отмечена)

**Примечание.**
Здесь и далее каталог проекта обозначен как `C:\cs-lab34`.
Всюду нужно заменять его на тот, который реально используется.

Заготовка файла включает «стража включения» (см. лекцию):

```cpp
#ifndef HISTOGRAM_H_INCLUDED
#define HISTOGRAM_H_INCLUDED

#endif // HISTOGRAM_H_INCLUDED
```

Можно заменить его на более простой вариант `#pragma once`
или писать код в этом файле между `#define` и `#endif`.

Добавьте в этот файл объявление `make_histogram()`:

```cpp
#include <vector>

std::vector<size_t>
make_histogram(const std::vector<double>& numbers, size_t bin_count);
```

В заголовочных файлах не рекомендуется писать `using namespace std;`,
потому что в файле реализации, куда этот заголовочный файл подключается,
нет возможности отменить эту директиву, а она нужна на всегда.
Поэтому в примере `std::vector<T>` используется с указанием пространства имен.

#### Создание файла реализации

1. При помощи меню *File → New → File...*
    добавьте к проекту файл реализации *(C/C++ source).*
    Необходимые настройки:

    *Filename with full path:* `C:\cs-lab34\histogram.cpp` \
    *Add file to active project in build target(s)*:
    нажать *All*, либо проставить флажки в пунктах *Debug* и *Release*

1. Перенесите определения `find_minmax()` и `make_histogram()`
    в `histogram.cpp`.

1. Сделайте функцию `find_minmax()` статической,
    так как она не нужна за пределами `histogram.cpp`.

1. Подключите `histogram.h` в `main.cpp` и `histogram.cpp`:

    ```cpp
    #include "histogram.h"
    ```

    Нужно использовать двойные кавычки, так как файл находится
    не в стандартном каталоге, а ищется относительно файла `*.cpp`.

1. Убедитесь, что проект собирается и запускается.

**Самостоятельно.**
Выделите `show_histogram_text()` в `text.h` и `text.cpp`.

### 5. Модульные тесты

Ранее для тестирования программы применялось перенаправление ввода и вывода.
Такое тестирование называется функциональным.
Однако функционального тестирования недостаточно для больших программ.
Предположим, большая программа перестала выдавать правильный результат —
как найти место ошибки?
Необязательно это последнее изменение: возможно, другая часть и ранее работала
неверно в некоторых случаях, и последняя правка лишь обнажила проблему.

Модульное тестирование (unit testing) проверяет не работу всей программы,
а работу отдельных ее компонент, например, отдельных функций.
Модульные тесты пишутся программистами для собственного кода.
Функциональные тесты могут писаться или проводится вручную другими специалистами.

Модульный тест — это отдельная программа, которая изолированно проверяет
части кода основной программы.
Если желательно протестировать части сложного алгоритма,
эти части должны быть оформлены в виде отдельных функций
(говорят: код должен быть *тестируемым).*

#### Как сделать код тестируемым?

Функция `find_minmax()`, которую нужно протестировать, скрыта в `histogram.cpp`.
Даже если разместить в другом файле её объявление, компоновка не прошла бы,
потому что эта функция статическая.
Это довольно типичная ситуация, когда желательно протестировать функции,
которые нет смысла описывать в интерфейсе модуля.
Она означает, что модуль достаточно сложный,
и имеет смысл выделить его части в подмодули.
С другой стороны, если перенести `find_minmax()` в отдельный файл,
читать код станет только сложнее — придется переходить по файлам.
Поэтому можно только объявление тестируемой функции описать в отдельном файле.
Подключив его, к ней можно будет обращаться из теста,
а сама функция будет в `histogram.cpp`, чтобы код оставался понятным.

1. Сделайте функцию `find_minmax()` нестатической.

1. Добавьте файл `histogram_internal.h` с объявлением `find_minmax()`.

#### Создание проекта для модульных тестов

1. При помощи меню *File → New → Project...*
    создайте новый проект типа *Empty project.*

    **Важно.** Не выбирайте *Console application,* иначе содержимое `main.cpp`
    будет заменено на пустую заготовку программы.

    Необходимые настройки:

    *Project title:* `unittest` \
    *Folder to create project in:* `C:\cs-lab34` \
    *Project filename:* `unittest.cbp` \
    *Resulting filename:* `C:\cs-lab34\unittest.cbp`

    В последнем параметре подкаталога `unittest` быть не должно.

1. Дважды щелкните по проекту `unittest`, чтобы сделать его активным.

1. Из контекстного меню проекта `unittest` выберите пункт *Add files...*
    и добавьте к проекту `histogram_internal.h` и `histogram.cpp`.
    В открывшемся диалоге *Select the targets this file should belong to:*
    проставьте все флажки.

1. При помощи меню *File → New → File...* добавьте к проекту `unittest`
    файл реализации *(C/C++ source)* `unittest.cpp`.
    Необходимые настройки:

    *Filename with full path:* `C:\cs-lab34\unittest.cpp` \
    *Add file to active project in build target(s)* — нажать *All*,
	либо проставить флажки в пунктах *Debug* и *Release*

#### Подключение библиотеки для модульного тестирования

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

1. Готовит условия, например, формирует исходные данные:

    ```cpp
    vector<double> numbers{1, 2};
    ```

2. Вызывает тестируемый код, например:

    ```cpp
    double min, max;
    find_minmax(numbers, min, max);
    ```

3. Выполняет ряд проверок, например:

    ```cpp
    if (min != 1) { puts("min != 1"); }
    if (max != 2) { puts("max != 2"); }
    ```

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

Мы предлагаем использовать библиотеку `doctest`,
потому что её очень просто подключить к проекту — это один заголовочный файл.
На практике самой популярной является Google Test, но её сложнее подключать.
В целом, все такие библиотеки похожи, но отличаются продвинутыми возможностями.

Загрузите [doctest.h][doctest/doctest.h] и сохраните в каталоге проекта.
Закоммитьте этот файл.

[doctest/doctest.h]: https://raw.githubusercontent.com/doctest/doctest/master/doctest/doctest.h

#### Написание модульных тестов

Напишем тест для простейшего случая в файле `unittest.cpp`:

```cpp
#define DOCTEST_CONFIG_NO_MULTITHREADING
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest.h"
#include "histogram_internal.h"

TEST_CASE("distinct positive numbers") {
    double min = 0;
    double max = 0;
    find_minmax({1, 2}, min, max);
    CHECK(min == 1);
    CHECK(max == 2);
}
```

Функцию `main()` писать не нужно — doctest автоматически определит её.

Каждый тест должен определяться через макрос `TEST_CASE()`, как показано выше.
В скобках описывается смысл теста, doctest будет показывать его в сообщениях.
Тест всегда включает один или несколько вызовов макроса `CHECK()`.
При ошибках doctest покажет, какая из проверок не сработала,
и всю дополнительную информацию.
[Вводная документация doctest.][doctest/tutorial]

[doctest/tutorial]: https://github.com/doctest/doctest/blob/master/doc/markdown/tutorial.md

Соберите проект модульных тестов, запустите его
и убедитесь, что он работает без ошибок.

Проверим, что будет, если тест выявляет ошибку.
Замените `CHECK(min == 1)` на `CHECK(min == 3)`,
соберите проект и запустите его.
Верните код к правильному варианту.

**Самостоятельно.**
Придумайте больше тестовых случаев и напишите для них тесты.
В частности, можно проверить случаи:

* пустого вектора
* вектора с одним элементом
* вектора отрицательных элементов
* вектора из одинаковых элементов

### 6. Вывод гистограммы как изображения в формате SVG

Требуется вместо текстовой гистограммы рисовать картинку, например:

![Пример изображения-результата.](assets/histogram.svg)

Это изображение описывается таким текстом, который должна выводить программа:

```xml
<?xml version='1.0' encoding='UTF-8' ?>
<svg width='300' height='100' viewBox='0 0 300 100' xmlns='http://www.w3.org/2000/svg'>
    <text x='20' y='20'>2</text>
    <rect x='50' y='0' width='100' height='30' fill='#ffaaaa' />
    <text x='20' y='50'>5</text>
    <rect x='50' y='30' width='250' height='30' fill='#aaffaa' />
    <text x='20' y='80'>3</text>
    <rect x='50' y='60' width='150' height='30' fill='#aaaaff' />
</svg>
```

Первые две строки и последняя строка — фиксированные.
Строки с отступом — это подписи к столбцам (элементы `text`)
и столбцы в виде цветных прямоугольников (**rect**angle, элементы `rect`).
На каждый столбец программа должна печатать строку `<text...`
и строку `<rect...` со значениями `x`, `y`, `width` и `height`,
вычисленными в зависимости от номера столбца и количества попавших в него чисел.

#### Форматы разметки

Возьмем любую страницу в интернете, например, эту.
На ней присутствует визуальное оформление: заголовки крупные и жирные,
ссылки подчеркнуты и по ним можно перейти, между абзацами есть отступы.
Можно догадаться, что изображение на экране не приходит с сервера в готовом виде,
потому что страница выглядит по-разному на телефоне и на компьютере, например.
Страница передается в виде разметки — текста со специальными элементами —
на основании которой браузер рисует на экране один текст как заголовок,
второй — как ссылку, третий — отдельным абзацем.

Например, возьмем такой абзац:

> Текст **с полужирными** словами и [ссылкой](#) в никуда.

Бразуер отображает его из следующего фрагмента разметки
(язык разметки называется HTML, hyper-text markup language):

```html
<p>Текст <strong>с полужирными</strong> словами и <a href="#">ссылкой</a> в никуда.</p>
```

Всегда можно нажать *Ctrl+U* и увидеть исходный код страницы (разметку).

SVG работает так же, только разметка описывает не текст с оформлением,
а изображение. HTML указывает, как отображать определенные блоки текста,
а SVG указывает, какие геометрические фигуры нарисовать.

Если щелкнуть правой кнопкой мыши по изображению выше
и выбрать «Показать изображение», оно откроется отдельно.
Тем же *Ctrl+U* можно посмотреть код разметки гистограммы,
который в итоге будет выводить программа.

Если если программа будет печатать размету SVG вместо звездочек,
можно перенаправить вывод программы в файл с расширением `*.svg`
и открыть его браузером — будет нарисовано изображение.
В случае ошибки оно нарисовано не будет.
В этом случае можно *Ctrl+U* посмотреть, что программа вывела не так.
Как правило, в окне кода, которое открывается по *Ctrl+U,*
красным цветом выделяются синтаксические ошибки, то есть некорректная разметка.

## Формат SVG

Разберем заголовок SVG:

```xml
<?xml version='1.0' encoding='UTF-8' ?>
<svg width='300' height='100' viewBox='0 0 300 100' xmlns='http://www.w3.org/2000/svg'>
```

Так описывается изображение размером 300×100 точек.

Система координат SVG отличается от математической:
ось Y направлена вниз, то есть точка *(0,0)* находится в верхнем левом углу.
Координаты в SVG действительные, то есть может быть точка *(0.5, 3.14).*

Фрагменты вида `<svg>`, `<text>` или `<rect>` называются *элементами.*
Параметры вида `height='300'` называются *атрибутами* элементов.
Элементы имеют открывающий тэг (`<svg>`), закрывающий тэг (`</svg>`)
с косой чертой и содержимое между ними (`<text>содержимое</text>`).
Если содержимого нет, то тэг может быть сразу закрыт: `<rect ... />`.

Подробнее об SVG можно прочитать
[в спецификации](https://developer.mozilla.org/en-US/docs/Web/SVG/Element),
но все нужные для ЛР сведения даны в этих указаниях.

SVG поддерживает множество элементов, среди них:

* `<text x='20' y='35'>anything you want</text>`:
    текст «anything you want», левый нижний угол которого в точке *(20,35);*
* `<rect x='0' y='0' width='100' height='200' />`:
    прямоугольник 100×200 с верхним левым углом в точке *(0,0)*.

#### Цикл отладки функций вывода SVG

Вывод отдельных элементов SVG (заголовка, окончания, текста, прямоугольника)
стоит оформить в виде функций, который будут принимать параметры геометрии
и печатать соответствующий текст.
Функция `show_histogram_svg()` будет выводить гистограмму в формате SVG.
Функции для работы с SVG рекомендуется выделить в модуль `svg.h`/`svg.cpp`.

Отработаем цикл модификации и проверки программы.
Функции вывода заголовка и окончания SVG:

``` cpp
void
svg_begin(double width, double height) {
    cout << "<?xml version='1.0' encoding='UTF-8'?>\n";
    cout << "<svg ";
    cout << "width='" << width << "' ";
    cout << "height='" << height << "' ";
    cout << "viewBox='0 0 " << width << " " << height << "' ";
    cout << "xmlns='http://www.w3.org/2000/svg'>\n";
}

void
svg_end() {
    cout << "</svg>\n";
}
```

Обратите внимание на пробелы в строках, например, перед закрывающей кавычкой
во фрагменте `"viewBox='0 0 "`.
Они необходимы между атрибутами.
Также обратите внимание на использование одинарных кавычек.
Двойные кавычки ограничивают в C++ строковые литералы;
одинарные кавычки внутри них выводятся «как есть» в результирующую строку.
Путаница кавычек или отсутствие некоторых пробелов сделает SVG некорректным.

Пусть начальная реализация графического вывода гистограммы всегда выводит
пустое изображение фиксированного размера:

``` cpp
void
show_histogram_svg(const vector<size_t>& bins) {
    svg_begin(400, 300);
    svg_end();
}
```

Заменим вызов `show_histogram_text(bins)` вызовом `show_histogram_svg(bins)`.

Создать файл изображения можно так
(`marks.txt` можно [скачать](assets/marks.txt) или создать):

``` sh
C:\cs-lab34\bin\Debug> cs-lab34.exe <marks.txt >marks.svg
```

Эту команду нужно вводить в консоли, открытой в папке с exe-файлом проекта.
Если проект называется не `cs-lab34`, название файла нужно написать свое.
Просмотреть, какие файлы есть в текущем каталоге, можно командой `dir`.

Чтобы быстро просмотреть из консоли содержимое `marks.svg` (разметку),
можно использовать команду:

```sh
C:\cs-lab34\bin\Debug> type marks.svg
```

В браузере `marks.svg` открывается из меню *Файл → Открыть...*
Отобразится пустая страница (пустой рисунок).
Можно нажать *Ctrl+U* или пункт *«Исходный код страницы»*
в контекстном меню любого места страницы, чтобы увидеть результирующий код SVG
(контекстное меню — это то, что появляется по щелчку
правой кнопкой мыши в любом пустом месте страницы).

#### Функции вывода элементов SVG

Для вывода подписей к столбцам напишем функцию вывода текста в SVG, которая
принимает координату по горизонтали (`left`), координату нижнего края текста
по вертикали (`baseline`) и сам текст:

``` cpp
void svg_text(double left, double baseline, string text);
```

Из введения в SVG выше известно,
что она должна выводить строку такого формата:

``` xml
<text x='20' y='35'>anything you want</text>
```

На C++ точно такую строку можно вывести следующим образом (`svg_text()`)
должна размещаться выше `show_histogram_svg()`:

``` cpp
void
svg_text(double left, double baseline, string text) {
    cout << "<text x='20' y='35'>anything you want</text>";
}
```

Чтобы вместо координаты `x` (числа 20) выводить значение `left`:

``` cpp
    cout << "<text x='" << left << "' y='35'>anything you want</text>";
```

Обратите внимание на то, что сохранены одинарные кавычки внутри строк C++
(в двойных кавычках) — они необходимы по правилам SVG.

**Самостоятельно.**
Закончите реализацию `svg_text()`,
чтобы подставлять значение координаты `baseline` и текст надписи `text`.

Для проверки выведем высоту первого столбца гистограммы:

``` cpp
void
show_histogram_svg(const vector<size_t>& bins) {
    svg_begin(400, 300);
    svg_text(20, 20, to_string(bins[0]));
    svg_end();
}
```

Функция `to_string()` преобразует значения разных типов в строки.

Чтобы проверить модифицированную программу, её нужно запустить повторно:

``` sh
C:\cs-lab34\bin\Debug> cs-lab34.exe <marks.txt >marks.svg
```

После повторного запуска программы
открытый в браузере файл можно обновить клавишей *F5.*
На странице должен быть виден текст «2».
Если в файле ошибка, он не отобразится.
В этом случае нужно просмотреть код страницы на предмет ошибок в SVG,
исправить код программы, перезапустить ее и проверить результат.

**Самостоятельно.**
Напишите функцию для вывода прямоугольника в SVG:

``` cpp
void svg_rect(double x, double y, double width, double height);
```

Для проверки выведем первый столбец гистограммы справа от подписи к нему:

``` cpp
    svg_rect(50, 0, bins[0] * 10, 30);
```

Убедитесь, что вывод первого столбца гистограммы работает.

#### Исключение «магических констант»

Текст выводится в координатах *(20, 20)*, смещение левого края столбца *50*
(оно же — ширина подписей), а высота столбца *30.*
Эти числа подобраны так, чтобы элементы гистограммы не накладывались друг на друга,
но они разбросаны по коду и их смысл неясен.

Прямо внутри функции `show_histogram_svg()` стоит завести константы,
чтобы пользоваться ими при выводе:

``` cpp
const auto IMAGE_WIDTH = 400;
const auto IMAGE_HEIGHT = 300;
const auto TEXT_LEFT = 20;
const auto TEXT_BASELINE = 20;
const auto TEXT_WIDTH = 50;
const auto BIN_HEIGHT = 30;
const auto BLOCK_WIDTH = 10;
```

#### Вывод гистограммы

Логика вывода гистограммы следующая: каждая корзина выводится так же,
как первая, но к вертикальной координате добавляется смещение — высота столбца:

``` cpp
double top = 0;
for (size_t bin : bins) {
    const double bin_width = BLOCK_WIDTH * bin;
    svg_text(TEXT_LEFT, top + TEXT_BASELINE, to_string(bin));
    svg_rect(TEXT_WIDTH, top, bin_width, BIN_HEIGHT);
    top += BIN_HEIGHT;
}
```

### Оформление гистограммы. Значения параметров по умолчанию

Черная гистограмма не слишком эстетична или экономична при печати.
За цвет линий в SVG отвечает атрибут `stroke`, а за цвет заливки — `fill`.
Можно задать один из [стандартных цветов][svg/colors] или выбрать цвет
в формате `#RRGGBB` из [палитры][svg/rgb].  Пример прямоугольника с красными
границами и бледно-розовой заливкой:

``` xml
<rect x='50' y='0' width='30' height='30' stroke='red' fill='#ffeeee'/>
```

[svg/colors]: https://www.december.com/html/spec/colorsvghex.html
[svg/rgb]: http://getcolor.ru

Функция `svg_rect()` может быть доработана для указания цвета линий и заливки:

    ``` cpp
    void svg_rect(double x, double y, double width, double height,
            string stroke, string fill);
    ```

**Самостоятельно.**
Измените цвета вывода и проверьте работу программы.

#### Значения параметров по умолчанию

Цвета элементов нужно задавать не всегда.
В текущей реализации у функции `svg_rect()` шесть параметров,
два из которых отвечают за цвет.
Можно было бы перегрузить функцию:

``` cpp
void svg_rect(double x, double y, double width, double height);

void svg_rect(double x, double y, double width, double height,
        string stroke, string fill);
```

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

``` cpp
void svg_rect(double x, double y, double width, double height,
        string stroke = "black", string fill = "black");
```

Значения по умолчанию указываются только один раз в объявлении функции
(если объявление и определение отделены).
Новая версия `svg_rect()` работает так:

``` cpp
svg_rect(0, 0, 100, 200);                    // svg_rect(0, 0, 100, 200, "black", "black");
svg_rect(0, 0, 100, 200, "red");             // svg_rect(0, 0, 100, 200, "red",   "black");
svg_rect(0, 0, 100, 200, "blue", "#aaffaa");
```

**Самостоятельно.**
Реализуйте для гистограммы в SVG масштабирование:
самый длинный столбец должен занимать `(IMAGE_WIDTH - TEXT_WIDTH)` точек.

## Индивидуальные задания

Результат выполнения нужно оформить отдельным коммитом на GitHub.
Доработку следует делать с использованием функции, для который нужно добавить
unit-тесты из не менее двух существенно отличающихся случаев.

**Пример 1.**
По заданию нужно запросить у пользователя цвет заливки столбцов,
а если введен красный, отказаться его использовать и запросить повторно.
Можно выделить функцию `bool check_color(string color)`, которая принимает
введенную пользователем строку цвета и возвращает `true`, если цвет
подходящий (не красный). Для нее можно придумать положительный тест,
что `check_color("blue") == true` и отрицательный, что `check_color("red") ==
false`.

**Пример 2.**
По заданию требуется добавить гистограмме заголовок *Гистограмма* по центру.
Можно выделить функцию, которая принимает ширину и высоту изображения
и вычисляет координаты надписи. Тестовые случаи — горизонтальное
и вертикальное изображение (ширина больше или меньше высоты соответственно).

В вариантах примеры приведены для текстовых гистограмм,
но решение должно выдавать SVG.

#### Вариант 1

Дайте пользователю возможность задавать произвольную ширину столбца
гистограммы вместо 400. Считайте некорректной ширину менее 70, более 800
или менее трети количества чисел, умноженных на ширину блока (`BLOCK_WIDTH`) —
предлагайте пользователю ввести ширину заново с указанием причины.

#### Вариант 2

Задавать автоматически яркость заливки каждого столбца гистограммы в градациях
серого в зависимости от высоты столбца. Чем больше столбец, тем темнее
заливка.

Сделать это можно, передавая цвет в параметр fill в формате `"#RGB"` ([red,
green, blue](http://htmlbook.ru/html/value/color)). `"#111"` — самый темный,
`"#222"` — чуть менее темный, ..., `"#EEE"` — практически белый, `"#FFF"` —
белый. В лабораторной работе использовать диапазон цветов от `"#111"`
для самого большого столбца до `"#999"` для самого маленького столбца.
Поскольку используются градации серого, расчет сводится к вычислению только
одного значения и дублированию этого значения в качестве цвета каждого
из каналов (Red, Green, Blue). Для расчета цвета *i*-го столбца `bins[i]`
использовать формулу `(10 - (bins[i] * 9) / max_count)`. По ней мы получаем
значение цвета одного канала (от 1 до 9), который затем записываем три раза.

Пример с текстом вместо SVG:

`1|`<font color="#999">`▮`</font> — цвет `#999` \
`5|`<font color="#111">`▮▮▮▮▮`</font> — цвет `#111` \
`3|`<font color="#555">`▮▮▮`</font> — цвет `#555`

#### Вариант 3

Измените высоту изображения `IMAGE_HEIGHT = 700`. Дайте пользователю
возможность задавать высоту столбца гистограммы. Если итоговая высота
гистограммы больше `IMAGE_HEIGHT`, рассчитывать высоту столбца как
`(IMAGE_HEIGHT / bins_count)`.

#### Вариант 4

Подсчитайте процент элементов, попавших в столбец, как целое двузначное число
с `%` в конце и отображайте этот процент после столбца гистограммы
с выравниванием:

```
 3|***    25%
 5|*****  42%
 4|****   33%
```

#### Вариант 5

Отображайте гистограмму зеркально по аналогии с заданием этого варианта
в лабораторной работе № 1.

#### Вариант 6

После запроса количества столбцов запросите цвет для каждого столбца.

#### Вариант 7

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

#### Вариант 8

Запрашивать у пользователя размер шрифта. За размер шрифта отвечает атрибут
`font-size`. Считать 12 значением по умолчанию. Не позволять вводить
значения менее 8 и более 32. В этом случае предлагайте пользователю ввести
значение заново с указанием причины.

#### Вариант 9

Запросите у пользователя ширину одного «блока» гистограммы `BLOCK_WIDTH`.
Не позволяйте вводить ширину блока менее 3px и более 30px. В этом случае
предлагайте пользователю ввести ее заново с указанием причины.

#### Вариант 10

Отображайте гистограмму вертикально, с подписями сверху, по аналогии
с заданием этого варианта в лабораторной работе 1. Предусмотреть расчет
`IMAGE_HEIGHT` таким образом, чтобы вся гистограмма вмещалась в область
рисунка.

#### Вариант 11

Добавьте рамку вокруг гистограммы, используя пунктирные линии. Для отрисовки
пунктирной линии можно использовать стандартный элемент `<line>`, установив
в нем атрибут `stroke-dasharray = '10 10'`

#### Вариант 12

Добавьте на ось подписей границы столбцов по аналогии с заданием этого
варианта в лабораторной работе 1.

#### Вариант 13

Отображайте гистограмму вертикально с подписями снизу.

```
  *
  *
* *
* *
* * *
_ _ _
3 5 1
```

Предусмотреть расчет `IMAGE_HEIGHT` таким образом, чтобы вся гистограмма
вмещалась в область рисунка.

#### Вариант 14

Разделяйте каждый столбец пунктирными линиями длиной `IMAGE_WIDTH`.
Запрашивайте у пользователя шаблон пунктира. Шаблон пунктира задается
в атрибуте `stroke-dasharray` блока `<line>` в виде `stroke-dasharray = '20
10'`, где 20 означает длину черточки, 10 - длину промежутка. У пользователя
нужно запросить как длину черточки, так и длину промежутка.

#### Вариант 15

Добавьте горизонтальную шкалу под гистограммой по аналогии с заданием
лабораторной работы 1. Шкалу нужно разбить на интервалы, размер которых вводит
пользователь. Допустимы размеры от 2 до 9 `BLOCK_WIDTH`, при некорректном
вводе печатайте сообщение со словом «ERROR» и завершайте работу программы.
Под нулевой, первой и последней отметкой шкалы требуется напечатать
соответствующие числа. Шкала должна быть во всю ширину гистограммы.

#### Вариант 16

После запроса количества столбцов запросить цвет линий для каждого столбца.
Проверять ввод: цвет должен либо начинаться с `#`, либо не иметь внутри
пробелов.

#### Вариант 17

Задавать автоматически прозрачность заливки каждого столбца гистограммы
в зависимости от высоты столбца. Чем больше столбец, тем темнее заливка.
Сделать это можно, передавая процент прозрачности в параметр `fill-opacity`
в формате `"0.7"`. 1 соответствует отсутствию прозрачности, 0 соответствует
полной прозрачности (отсутствию цвета)

Для расчета прозрачности каждого i-го столбца `bins[i]` использовать формулу
`(bins[i]) / max_count)`.

Пример:

`1|`<span style="opacity: 0.2">`▮`</span> — прозрачность 0.2 \
`5|`<span style="opacity: 1.0">`▮▮▮▮▮`</span> — прозрачность 1.0 \
`3|`<span style="opacity: 0.6">`▮▮▮`</span> — прозрачность 0.6

#### Вариант 18

Позволять пользователю делать оформление текста -
<span style="text-decoration: underline">подчеркивание</span>,
<span style="text-decoration: overline">надчеркивание</span>, ~~зачеркивание~~
текста. За оформление шрифта отвечает атрибут `text-decoration`. Сделать
`'none'` значением по умолчанию. Допустимые значения: `none`, `underline`,
`overline`, `line-through`. Проверять введенное пользователем значение, и если
оно не одно из допустимых, запрашивать значение заново, выдавая предупреждение.