Экспорт презентаций в HTML с внешне связанными изображениями

Предыстория

Поведение по умолчанию при экспорте в HTML заключается в том, чтобы встраивать любой ресурс в HTML-файл. Такой подход приводит к созданию одного HTML-файла, который легко просматривать и распространять. Все необходимые ресурсы базируются в коде base64 внутри. Но у такого подхода есть два недостатка:

  • Размер выходного файла значительно больше из-за кодирования base64. Заменить изображения, содержащиеся в файле, сложно.

В этой статье мы рассмотрим, как мы можем изменить поведение по умолчанию с помощью Aspose.Slides для C++, чтобы ссылки на изображения были внешними, а не встроенными в HTML-файл. Мы будем использовать интерфейс ILinkEmbedController, который содержит три метода для управления процессом встраивания и сохранения ресурсов. Мы можем передать этот интерфейс в конструктор HtmlOptions, когда подготавливаем экспорт.

Следующий код представляет собой полный код класса LinkController, который реализует интерфейс ILinkEmbedController. Как уже упоминалось, LinkController должен реализовать интерфейс ILinkEmbedController. Этот интерфейс определяет три метода:

  • LinkEmbedDecision GetObjectStoringLocation(int32_t id, ArrayPtr<uint8_t> entityData, String semanticName, String contentType, String recomendedExtension) Он вызывается, когда экспортер встречает ресурс и должен решить, как его сохранить. Наиболее важные параметры – это ‘id’ – уникальный идентификатор ресурса для всей операции экспорта и ‘contentType’ – содержит MIME-тип ресурса. Если мы решим связать ресурс, мы должны вернуть LinkEmbedDecision::Link из этого метода. В противном случае следует вернуть LinkEmbedDecision::Embed, чтобы встроить ресурс.
  • String GetUrl(int32_t id, int32_t referrer) Он вызывается, чтобы получить URL ресурса в той форме, в какой он используется в результирующем файле, скажем, для тега <img src=%method_result_here%>. Ресурс идентифицируется по ‘id’.
  • SaveExternal(int32_t id, ArrayPtr<uint8_t> entityData) Заключительный метод последовательности, он вызывается, когда необходимо сохранить ресурс внешне. У нас есть идентификатор ресурса и содержимое ресурса в виде массива байтов. Решение о том, что делать с предоставленными данными ресурса, остается за нами.
/// <summary>
/// Этот класс отвечает за принятие решений о ресурсах, сохраняемых внешне.
/// Он должен реализовать интерфейс Aspose::Slides::Export::ILinkEmbedController.
/// </summary>
class LinkController : public ILinkEmbedController
{
public:
    LinkController()
    {
        m_externalImages = System::MakeObject<Dictionary<int32_t, String>>();
    }
    LinkController::LinkController(String savePath) : LinkController()
    {
        m_savePath = savePath;
    }

    LinkEmbedDecision GetObjectStoringLocation(int32_t id, ArrayPtr<uint8_t> entityData, 
        String semanticName, String contentType, String recomendedExtension) override
    {
        // Здесь мы принимаем решение о внешнем сохранении изображений.
        // id – уникальный идентификатор каждого объекта на протяжении всей операции экспорта.

        String template_;

        // Словарь s_templates содержит MIME-типы, которые мы собираемся сохранить внешне, и соответствующий шаблон имени файла.
        if (s_templates->TryGetValue(contentType, template_))
        {
            // Сохраняем этот ресурс в списке экспорта
            m_externalImages->Add(id, template_);
            return LinkEmbedDecision::Link;
        }

        // Все другие ресурсы, если они есть, будут встроены
        return LinkEmbedDecision::Embed;
    }

    String GetUrl(int32_t id, int32_t referrer) override
    {
        // Здесь мы формируем строку ссылки на ресурс для тега: <img src="%result%">
        // Нам нужно проверить словарь, чтобы отфильтровать ненужные ресурсы.
        // Параллельно с проверкой мы извлекаем соответствующий шаблон имени файла.
        String template_;
        if (m_externalImages->TryGetValue(id, template_))
        {
            // Предполагаем, что мы собираемся сохранять файлы ресурсов рядом с HTML-файлом.
            // Тег изображения будет выглядеть как <img src="image-1.png"> с соответствующим идентификатором ресурса и расширением.
            String fileUrl = String::Format(template_, id);
            return fileUrl;
        }

        // для ресурсов, остающихся встроенными, должно быть возвращено null
        return nullptr;
    }

    void SaveExternal(int32_t id, ArrayPtr<uint8_t> entityData) override
    {
        // Здесь мы на самом деле сохраняем файлы ресурсов на диск.
        // Снова проверяем словарь. Если id не найдено здесь, это признак ошибки в методах GetObjectStoringLocation или GetUrl.
        if (m_externalImages->ContainsKey(id))
        {
            // Теперь мы используем имя файла, сохраненное в словаре, и комбинируем его с путем по необходимости.

            // Создаем имя файла, используя сохраненный шаблон и Id.
            String fileName = String::Format(m_externalImages->idx_get(id), id);
            
            // Объединяем с директорией расположения
            const String savePath = m_savePath != nullptr ? m_savePath : String::Empty;
            String filePath = Path::Combine(savePath, fileName);

            auto fs = System::MakeObject<FileStream>(filePath, FileMode::Create);
            fs->Write(entityData, 0, entityData->get_Length());
        }
        else
        {
            throw Exception(u"Что-то не так");
        }
    }

private:
    String m_savePath;
    SharedPtr<Dictionary<int32_t, String>> m_externalImages;
    static SharedPtr<Dictionary<String, String>> s_templates;

    static struct __StaticConstructor__
    {
        __StaticConstructor__()
        {
            s_templates->Add(u"image/jpeg", u"image-{0}.jpg");
            s_templates->Add(u"image/png", u"image-{0}.png");
        }
    } s_constructor__;
};

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

const String templatePath = u"../templates/image.pptx";
auto pres = System::MakeObject<Presentation>(templatePath);

auto htmlOptions = System::MakeObject<HtmlOptions>(System::MakeObject<LinkController>(GetOutPath()));
htmlOptions->set_SlideImageFormat(SlideImageFormat::Svg(System::MakeObject<SVGOptions>()));
// Эта строка нужна, чтобы удалить отображение заголовка слайда в HTML.
// Закомментируйте это, если предпочитаете отображать заголовок слайда.
htmlOptions->set_HtmlFormatter(HtmlFormatter::CreateDocumentFormatter(String::Empty, false));

pres->Save(GetOutPath() + u"/output.html", SaveFormat::Html, htmlOptions);

Мы передаем SlideImageFormat::Svg в метод set_SlideImageFormat, что означает, что результирующий HTML-файл будет содержать данные SVG для отображения содержимого презентации.

Что касается MIME-типов, это зависит от фактических данных изображений, содержащихся в презентации. Если в презентации есть растровые битмап, то код класса должен быть готов обрабатывать как ‘image/jpeg’, так и ‘image/png’. Фактический MIME-тип экспортируемых растровых битмап может не совпадать с типом контента изображений, хранящихся в презентации. Внутренние алгоритмы Aspose.Slides для C++ выполняют оптимизацию размера и используют либо кодек JPG, либо PNG, в зависимости от того, какой из них генерирует меньший размер данных. Изображения, содержащие альфа-канал (прозрачность), всегда кодируются в PNG.