HotPDF импортирует SVG-файл в загруженный или создаваемый PDF-документ в виде переиспользуемого Form XObject через ImportSVGFormXObject, который возвращает имя ресурса созданной формы. Размещение — отдельный вызов, DrawSVGFormXObject, поэтому один импорт можно проштамповать на множестве страниц в разных размерах, не дублируя ни единого байта графики
Это разделение важнее, чем кажется на первый взгляд. Логотип, растрированный с разрешением 300 DPI и размещённый на 400 страницах, — это 400 копий растрового изображения. Тот же логотип в виде Form XObject — это один объект, на который 400 раз ссылаются: он остаётся чётким при любом масштабе и печатается с разрешением устройства, а не с тем DPI, которое казалось достаточным на этапе сборки
Зачем нужен Form XObject вместо повторной отрисовки SVG каждый раз?
Form XObject — это собственный механизм PDF для переиспользуемого содержимого: самодостаточный поток содержимого со своим ограничивающим прямоугольником и словарём ресурсов, вызываемый по имени. Импорт SVG в такую форму даёт три свойства, которых нет у прямой отрисовки. Графика существует в файле лишь один раз. Каждое размещение — это небольшая матрица и вызов. А формы остаются векторными, поэтому при увеличении на 800 процентов программа просмотра рисует кривые, а не пиксели
Тонкость — в ресурсах. Шрифты и состояния графики формы должны находиться в собственном словаре /Resources формы, а не в ресурсах той страницы, что оказалась текущей на момент импорта. HotPDF записывает необходимые форме шрифты Base 14 и записи ExtGState прямо в саму форму, потому что форма, заимствующая имя у своей первой страницы-хозяина, корректно отрисовывается только там и выдаёт неразрешённое имя ресурса на каждой другой странице, куда её позже поместят
Два переворота: один для геометрии, другой для глифов
Система координат SVG растёт вниз от левого верхнего угла; Form XObject в PDF использует пользовательское пространство PDF, которое растёт вверх от левого нижнего угла. Преобразование корневого viewBox выполняет один переворот по оси Y, чтобы геометрия легла правильно. Тексту затем требуется второй, локальный переворот, потому что глиф, нарисованный в уже перевёрнутой системе координат, оказался бы вверх ногами
Это та же самая поправка, что появляется в обратном виде при экспорте страницы PDF в SVG, описанном в статье экспорт страниц PDF в SVG. Ошибку легко заметить в графике с текстом и почти невозможно — в графике без текста, поэтому логотип со слоганом — лучший дымовой тест, чем один только логотип
uses
HPDFDoc;
var
Pdf: THotPDF;
SvgBytes: TBytes;
FormName: AnsiString;
Stats: THPDFSVGImportStatistics;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'catalogue.pdf';
Pdf.BeginDoc;
SvgBytes := LoadFileBytes('brand-logo.svg');
FormName := Pdf.ImportSVGFormXObject(SvgBytes, Stats);
if FormName = '' then
raise Exception.Create('SVG rejected - see the import statistics');
// Один импорт, много размещений: байты графики существуют лишь один раз
for I := 1 to 40 do
begin
if I > 1 then
Pdf.AddPage;
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 780, 120, 40);
end;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Как гиперссылки переживают этот путь?
Внутри формы они выжить не могут, потому что Form XObject — это содержимое, а цели ссылок PDF — это аннотации, которые принадлежат странице. Поэтому элемент <a> в исходном SVG нельзя просто скопировать в поток содержимого формы
HotPDF решает эту задачу, сохраняя прямоугольники ссылок как метаданные в пространстве формы во время разбора и материализуя их в виде аннотаций-ссылок на уровне страницы при каждом размещении. DrawSVGFormXObject преобразует эти прямоугольники через переданный вами целевой прямоугольник, так что форма, размещённая дважды в разных размерах, получает два корректно масштабированных набора активных областей. Прямоугольники ссылок преобразуются только через размер корня и никогда не попадают в поток содержимого, благодаря чему нарисованная графика остаётся идентичной независимо от количества ссылок. Передайте AddLinks = False, если нужна графика без интерактивности — например, на странице только для печати
От чего отказывается импортёр и почему в этом весь смысл
SVG — это диалект XML со скриптами, внешними ссылками и раскрытием сущностей, из-за чего произвольный SVG-файл представляет такую же опасность, как и произвольный HTML-файл. Профиль импорта работает по принципу fail-closed для всего, что выходит за пределы документа: внешних ссылок на ресурсы, скриптов, атрибутов обработчиков событий, DTD и любых url(), не являющихся локальной ссылкой на фрагмент
Fail-closed означает, что импорт завершается ошибкой, а не молча отбрасывает проблемную конструкцию. Это различие важно для пакетного конвейера: молчаливое отбрасывание порождает документ, в котором чего-то не хватает и никто об этом не знает, тогда как отказ даёт файл, который можно поместить в карантин и изучить. Если вы принимаете SVG-файлы от пользователей, именно такое поведение и нужно, и оно хорошо сочетается с изоляцией на уровне процессов, описанной в статье изоляция кодеков в отдельном процессе для недоверенных изображений
Стоит знать об одной внутренней границе, если вы также рендерите цветные шрифты. Строгое подмножество SVG, используемое для цветных глифов OpenType SVG, проходит через отдельную точку входа и сохраняет своё исходное, более узкое поведение при ошибках. Импорт обычной графики — это отдельный путь, поэтому добавление поддержки дуг, прозрачности и CSS в импортируемую графику не расширило то, что допустимо содержать в глифе шрифта
Градиенты, маски и правило «без растра»
Градиенты импортируются как ограниченные векторные цветовые полосы, стеснённые контуром обтравки исходной геометрии, а не как растрированное изображение. Маски используют тот же подход: контур маски становится векторным контуром обтравки. В результате иллюстрация с обилием градиентов остаётся независимой от разрешения и небольшой по размеру, тогда как растрирующий импортёр создал бы крупный объект изображения с качеством, зафиксированным на момент импорта
Расплата — точность в крайних случаях. Полосовое приближение плавного градиента математически не идентично градиенту SVG, а графика с десятками перекрывающихся мягких масок будет выглядеть ближе к оригиналу, если вместо этого отрендерить её в изображение высокого разрешения. О моделях затенения, которые PDF поддерживает нативно, и о том, когда обращаться к ним напрямую, см. статью рендеринг осевых и радиальных градиентов затенения
// Статистика импорта показывает, что на самом деле прошло через импорт
Writeln(Format('nodes=%d css rules=%d gradients=%d masks=%d',
[Stats.NodeCount, Stats.CSSRuleCount, Stats.GradientCount,
Stats.MaskCount]));
Writeln(Format('text runs=%d links=%d form bytes=%d',
[Stats.TextRunCount, Stats.LinkCount, Stats.OutputBytes]));
// Размещение одной и той же формы в двух размерах даёт два корректных набора ссылок
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 700, 240, 80);
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 320, 700, 120, 40);
// Размещение только для печати: графика без активных областей
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 60, 120, 40, False);
Место импорта SVG среди других векторных путей
Существует три пути импорта векторной графики, и они не взаимозаменяемы. Импорт SVG подходит для графики, созданной инструментами дизайна, и для всего, что приходит из веб-конвейера. Импорт метафайлов, описанный в статье импорт векторной графики EMF и WMF, подходит для содержимого, создаваемого приложениями Windows и компонентами построения диаграмм, рисующими в контекст устройства. Прямая отрисовка через API страницы подходит для графики, которую вычисляет ваш собственный код, где промежуточного формата нет вообще
Довод о переиспользовании особенно выигрывает у импорта SVG для повторяющихся элементов. Фирменные бланки, логотипы, водяные знаки, знаки сертификации и декоративные линейки рисуются одинаково на множестве страниц, и каждый из них — это один объект формы плюс один вызов на страницу. Прежде чем решить, что разница в размере файла не имеет значения, измерьте её на реальном документе: в каталоге на 500 страниц с векторным логотипом на каждой разница между одной формой и 500 встроенными копиями обычно составляет несколько мегабайт
Импорт SVG, импорт метафайлов и прямая отрисовка векторной графики используют общий модуль записи потока содержимого для Delphi и C++Builder; полный список возможностей — на странице компонента HotPDF Delphi PDF Component