Рисование SVG
Пример SVG изображения:
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<rect x="10" y="10" height="100" width="100" style="stroke:#ff0000; fill: #0000ff"/>
</svg>
Справочник по элементам и атрибутам SVG
Диаграмма классов
Это те классы, которые должны получиться в окончательной версии программы:

Условие задачи
- Вспомогательные классы для рисования
SVG
- Класс SVG, содержит открытый
PrintStream для печати SVG:
SVG svg = new SVG("a.svg", 300, 300); svg.addTag(rect1); svg.addTag(rect2); svg.close();В конце обязательно будет вызвано закрытие, чтобы закрыть PrintStream
- Класс Tag - это описание одного тега.
Tag rect1 = new Tag("rect"); rect1.set("x", "200"); rect1.set("y", "200"); rect1.set("width", "10"); rect1.set("height", "20"); rect1.set("style", "stroke:#ff0000; fill: #0000ff");
- Класс SVG, содержит открытый
PrintStream для печати SVG:
- Когда сделаете классы Tag и SVG, проверьте, что вы можете создавть картинки с их помощью.
- Измените код так, чтобы не требовалось вручную вызывать
close()в классе SVG. Для этого класс SVG должен реализовать интерфейс AutoClosable, и после этого класс можно будет использовать в конструкции try-with-resource:try (SVG svg = new SVG("a.svg", 300, 300)) { ... - Давайте реализуем еще вложенные теги. Нам это понадобиться, чтобы удобно двигать фигуры. Посмотрите на такой SVG, создайте эту
картинку и посмотрите, как она выглядит:
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300"> <rect x="10" y="10" height="100" width="100" style="stroke:#ff0000; fill: #0000ff"/> <rect x="20" y="20" height="100" width="100" style="stroke:#ff0000; fill: #00ff00"/> <g transform="translate(150, 150)"> <rect x="10" y="10" height="100" width="100" style="stroke:#ff0000; fill: #0000ff"/> <rect x="20" y="20" height="100" width="100" style="stroke:#ff0000; fill: #00ff00"/> </g> </svg>В этом примере рисуются два прямоугольника друг на друге, а потом такие же два прямоугольника помещены в группу (тэг
g), и этой группе указан атрибутtransform, который говорит, что ее содержимое надо сместить на 150 пикселей по горизонтали и вертикали.Обратите внимание, что тэги
rectоткрываются и сразу закрываются, об этом говорит слэш перед закрывающей угловой скобкой. А тэгgтолько открывается, у него нет слеша перед закрывающей угловой скобкой. Зато ниже этот тэг все же закрывается тегом, который начинается со слеша:</g>. Поэтому, предлагаю ввести три типа тэгов: открывающие, закрывающие и открывающие-закрывающие. Вот как можно описать три типа тэгов в Java с помощью нового для нас понятияenum(перечисление):public enum TagType { OPEN, CLOSE, OPEN_AND_CLOSE; }enumзадает тип данных, который может иметь только несколько возможных значений. Для ссылки на значения пишитеTagType.OPEN,TagType.Closeи т.п.Предлагаю создавать подобный SVG следующим образом:
Tag rect1 = new Tag("rect"); //заполняем rect1 как первый квадрат Tag rect2 = new Tag("rect"); //заполняем rect2 как второй квадрат Tag g = new Tag("g", TagType.OPEN); g.set("transform", "translate(150, 150)"); Tag gClose = new Tag("g", TagType.Close); try (SVG svg = new SVG("a.svg", 300, 300)) { svg.addTag(rect1); svg.addTag(rect2); svg.addTag(g); svg.addTag(rect1); svg.addTag(rect2); svg.addTag(gClose); }Вам придется добавить еще один конструктор в класс
Tag, в котором можно указать тип тэга. - Интерфейс Shape. Фигура. Изображение будет рисоваться из большого числа фигур. Какие имеено фигуры будут использоваться для рисования, будет задаваться в настройках. Этот интерфейс имеет метод getTags(), который возвращает список тегов
List<Tag>для рисования этой фигуры. В этот же интерфейс можно (но не обязательно, сделайте это только если вам интересно попробовать неизученную раньше возможность java) поместить метод draw(SVG), который имеет реализацию по-умолчанию, и передает все свои теги в SVG для рисования:public interface Shape { List<Tag> getTags(); default void draw(SVG svg) { //используйте getTags, чтобы получить тэги для фигуры и нарисовать их на svg. } }Сразу реализуйте этот интерфейс классами
SmallSquareиRedCircle, они должны возвращать по одному тэгу, которые рисуют, соответственно, маленький квадрат и красный круг. Протестируйте создание фигур, для этого добавьте в main метод рисование фигур на SVG файл. Если вы реализовали методdrawвнутри интерфейсаShape, то достаточно будет только вызвать его. Если не реализовали, реализуйте рисование фигуры прямо внутри методаmain.Замечание о координатах фигур. Рисуйте фигуры так, чтобы координата (0, 0) была примерно в середине фигуры. Реальное положение фигуры будет задаваться позже. Когда вы тестировали
SmallSquareиRedCircle, вы, наверное, указали большие значения для коориднат фигур, чтобы они оказались ближе к центру изображения. После тестирования координаты надо исправить, чтобы центр фигуры оказался в (0, 0) или примерно в (0, 0). -
Реализуйте класс
PositionedShape, он содержит в себе информацию о фигуре и ее координатах. Такой класс позволит нам создавать много одинаковых фигур в разных местах изображения. Возьмите из диаграммы классов содержимое классаPositionedShapeи реализуйте их. При реализации методаgetTagsнеобходимо вернуть закрывающий и открывающий теги<g>c атрибутомtransform, а между ними вставить те теги, которые возвращает методgetTagsдля исходной фигуры. Другими словами,PositionedShapeоборачивает теги исходной фигуры в тегg(в группу) и сдвигает эту группу.Не забудьте протестировать созданный класс, для этого создайте несколько фигур
SmallSquareиRedCircle, обернутых вPositionedShape, и сдвинутых в разные места изображения. -
При запуске программы создайте SVG размера 400 на 400, и добавьте на нее 100 фигур
SmallSquareи 150 фигурRedCircle, сдвинутых в разные места изображения. Используйте классRandomдля генерации случайных чисел. Вызывайте у него метод nextInteger(int max), чтобы получить случайное целое в нужном диапазоне. -
Далее мы будем исправлять предыдущий пункт, получая настройки из файла. Для этого создайте класс
Settings, который реализует шаблон singleton и при создании читает настройки из файлаsvg.propertiesв формате properties. Пусть для примера начальная версия файла настроек будет такой:rand_seed=52345234 background=#FFCCBB width=400 height=400 draw=red_circle:150 small_square:100 # описания фигур shape.red_circle=ru.spbu.arts.semester.iv.svg.shape.RedCircle shape.small_square=ru.spbu.arts.semester.iv.svg.shape.SmallSquareДалее будем постепенно расширять программу, добавляя в нее использование информации из этого файла.
-
Создайте в классе
SettingsметодыgetWidthиgetHeight, которые будут возвращать значения записейwidthиheightиз файла.Программа должна создать SVG изображение именно того размера, которое указано в настройках. Напомню, что раз
Settingsреализует шаблон singleton, то пользоваться им можно в любом месте программы следующим образом:Settings.getInstance().getWidth(). -
Создайте в классе
SettingsметодgetBackground, который возвращает css цвет фона. Программа должна перед рисованием фигур нарисовать на изображении фон этого цвета. -
Создайте в классе
SettingsметодgetRandSeed, который возвращает число для инициализации генератора случайных чисел. Именно это число нужно передать в качестве аргумента конструктору классаRandom. Давайте еще разрешим писать в настройкахrand_seed=auto, в этом случае методgetRandSeedдолжен возвращать 0, а генератор нужно создавать без указания seed, т.е. вызывая конструктор по-умолчаниюnew Random(). -
Создайте в классе
SettingsметодgetShapeDescription. Он должен получать название фигуры, например,red_circleи возвращать запись в файле настроек с описанием этой фигуры. Описание в файле настроек начинается с префиксаshape., например, описаниеred_circleначинается сshape.red_circle=. Фактически, в описании будет находится полное имя класса, реализующего эту фигуру. Итого,getShapeDescription("red_circle")возвращает"ru.spbu.arts.semester.iv.svg.shape.RedCircle". -
Создайте класс
ShapeFactoryи реализуйте у него методcreate. Он получает описание фигуры (см. пердыдущий пункт) и возвращает саму фигуру. Для начала считайте, что описание фигуры - это всегда имя класса, который ее реализует. Позже мы добавим более сложные описания. С помощью рефлексии создайте объект класса, имя которого вам передано. Если класс не существует, или существует, но не является фигурой, это приравнивается к ошибке в файле конфигурации. Пока мы не обрабатываем такие ошибки, позже нужно будет сделать вывод понятных сообщений пользователю о том, что конфигурация не верна. - Создайте в классе
SettingsметодgetShapsWithCount. Он должен возвращать, каких фигур и сколько нужно создать. Эта информация хранится в строкеdraw=в файле конфигурации. В приведенном примере файла конфигурации должно возвращаться red_circle -> 150, small_square -> 100.