Как отобразить PDF-документ в новом поле HTML на WebKit в 1С:Предприятии?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Коллеги, с внедрением движка WebKit в поле HTML документа в платформе 1С:Предприятие 8.3.14 и последующих версиях, мы столкнулись с новыми возможностями, но одновременно и с тем, что старые методы отображения PDF перестали быть актуальными или работать корректно. Например, ранее используемый код с тегом <embed>, который полагался на плагины браузера (вроде Adobe Acrobat Reader для Internet Explorer), теперь не работает. Давайте разберем, как теперь отобразить PDF на поле HTML документа, желательно без сохранения файла, используя современные подходы и возможности WebKit.

Первоначальная проблема заключается в том, что сам по себе движок WebKit не поддерживает работу с PDF-файлами напрямую, как это делали некоторые браузеры с плагинами. В веб-клиенте возможность просмотра PDF часто зависит от самого браузера (например, Chrome имеет встроенный просмотрщик), но во встроенном тонком клиенте 1С нам требуется иное решение. Мы рассмотрим два основных пути обхода этой проблемы: использование JavaScript-библиотеки PDF.js и метод с применением Blob URL и <iframe>. В качестве альтернативного обхода ограничений платформы можно использовать готовое решение для программной конвертации PDF в формат PNG, чтобы вывести документ в виде обычной картинки.

Метод 1: Использование библиотеки PDF.js для рендеринга на Canvas

Одним из наиболее мощных и гибких решений является использование JavaScript-библиотеки PDF.js от Mozilla. Эта библиотека позволяет нам рендерить PDF-документы на HTML-элемент <canvas>. Таким образом, PDF фактически преобразуется в растровое изображение, которое мы можем отобразить и манипулировать им. Если вы хотите избежать сложностей с интеграцией JavaScript на форме, можно задействовать обработку для постраничной конвертации PDF в формат JPEG для отображения страниц.

Шаг 1: Подготовка данных PDF

Прежде всего, нам необходимо получить содержимое PDF-файла в виде двоичных данных и преобразовать их в строку в формате Base64. Этот шаг выполняется на стороне 1С.


СтрокаPDFФайла = ПолучитьBase64СтрокуИзДвоичныхДанных(ПолучитьИзВременногоХранилища(АдресФайла));
// Или, если у вас уже есть ДвоичныеДанные:
// ДД = Новый ДвоичныеДанные(Результат.ПолноеИмяФайла);
// СтрокаPDFФайла = ПолучитьBase64СтрокуИзДвоичныхДанных(ДД);

Здесь АдресФайла – это адрес временного хранилища, куда мы предварительно поместили наш PDF-файл. Функция ПолучитьBase64СтрокуИзДвоичныхДанных() преобразует двоичные данные в строку Base64, готовую для передачи в JavaScript.

Шаг 2: Встраивание PDF.js и рендеринг первой страницы

Теперь мы можем встроить JavaScript-код с использованием PDF.js в поле HTML-документа. Для начала рассмотрим базовый пример, который отображает только первую страницу PDF. Важно отметить, что при передаче строки Base64 в JavaScript-функцию atob() необходимо использовать обратные кавычки (`), а не обычные апострофы, для корректной обработки многострочных и специальных символов. Также, для работы PDF.js, необходимо подключить саму библиотеку pdf.js и её компонент pdf.worker.js. Лучше всего скачать эти файлы и разместить их локально (например, в макетах конфигурации 1С или на веб-сервере, если это веб-клиент), чтобы обеспечить офлайн-работу и стабильность. В данном примере мы используем ссылку на CDN, но для продакшн-решений рекомендуется локальное размещение.


HTMLДокумент = "
<!DOCTYPE html>
<html>
<head>
    <meta charset=\"utf-8\">
    <title>Просмотр PDF</title>
    <script src=\"//mozilla.github.io/pdf.js/build/pdf.js\"></script>
</head>
<body>
    <script>
        var currPage = 1; // Страницы нумеруются с 1, а не с 0
        var numPages = 0;
        var thePDF = null;

        var loadingTask = pdfjsLib.getDocument({data: atob(`" + СтрокаPDFФайла + "`)});
        loadingTask.promise.then(function(pdf) {
            thePDF = pdf;
            numPages = pdf.numPages;
            pdf.getPage(1).then(handlePages);

            function handlePages(page) {
                var scale = 1.5; // Масштаб отображения

                var viewport = page.getViewport({scale: scale});

                var canvas = document.createElement( ""canvas"" );
                canvas.style.display = ""block"";
                var context = canvas.getContext('2d');
                canvas.height = viewport.height;
                canvas.width = viewport.width;

                var renderContext = {
                    canvasContext: context,
                    viewport: viewport
                };
                var renderTask = page.render(renderContext);

                document.body.appendChild( canvas );

                currPage++;
                if ( thePDF !== null && currPage <= numPages ) {
                    thePDF.getPage( currPage ).then( handlePages );
                }
            }
        });
    </script>
</body>
</html>
";
Показать

Этот код инициализирует PDF.js, загружает PDF из Base64-строки, а затем в цикле рендерит каждую страницу на отдельный элемент <canvas>, добавляя их в <body> документа. Таким образом, вы получаете прокручиваемый многостраничный документ.

Шаг 3: Добавление элементов управления (пагинация, масштабирование)

Для полноценной работы с PDF нам могут потребоваться кнопки для переключения страниц и изменения масштаба. Давайте расширим предыдущий пример, добавив кнопки "Предыдущая", "Следующая", "Увеличить", "Уменьшить", а также отображение текущего номера страницы и общего количества страниц.

Важное замечание по подключению `pdf.worker.js`: Для оптимизации работы в веб-клиенте и более корректного функционирования, pdf.worker.js не следует загружать напрямую через тег <script>, а достаточно указать путь к нему через pdfjsLib.GlobalWorkerOptions.workerSrc. Движок WebKit сам подгрузит его, когда это потребуется.

Также учтем важный момент: совместимость версий PDF.js. Встроенный WebKit в 1С не всегда обновляется до последних версий, поэтому новые версии библиотеки PDF.js могут работать некорректно. Опыт показывает, что версии pdf.js 2.2.2, 2.4.199 или 2.4.456 (в зависимости от версии платформы 1С) часто работают стабильнее, чем самые последние. Рекомендуется экспериментировать с версиями, если возникают проблемы. Для офлайн-работы все скрипты необходимо сохранить локально (например, в текстовых макетах 1С).


HTMLДокумент = "
<!DOCTYPE html>
<html>
<head>
    <meta charset=\"utf-8\">
    <title>Просмотр PDF</title>
    <style>
        @media print {
            .noprint { display:none; }
        }
    </style>
    <script src=\"//mozilla.github.io/pdf.js/build/pdf.js\"></script>
</head>
<body>
    <div class=\"noprint\">
        <button id=\"prev\">Предыдущая</button>
        <button id=\"next\">Следующая</button>
        &nbsp;Страница: <span id=\"page_num\"></span> из <span id=\"page_count\"></span>
        &nbsp;<button id=\"ZoomOut\">-</button>
        <button id=\"ZoomIn\">+</button>
    </div>
    <canvas id=\"the-canvas\"></canvas>

    <script>
        addEventListener('load', e => {
            var BinData = atob(`" + СтрокаPDFФайла + "`);
            var pdfjsLib = window['pdfjs-dist/build/pdf'];

            // Указываем путь к pdf.worker.js
            // Для офлайн-работы замените на локальный путь, например:
            // pdfjsLib.GlobalWorkerOptions.workerSrc = 'путь/к/вашему/pdf.worker.js';
            pdfjsLib.GlobalWorkerOptions.workerSrc = '//mozilla.github.io/pdf.js/build/pdf.worker.js';

            var pdfDoc = null,
                pageNum = 1,
                pageRendering = false,
                pageNumPending = null,
                scale = 1, // Начальный масштаб
                canvas = document.getElementById('the-canvas'),
                ctx = canvas.getContext('2d');

            function renderPage(num) {
                pageRendering = true;
                pdfDoc.getPage(num).then(function(page) {
                    var viewport = page.getViewport({scale: scale});
                    canvas.height = viewport.height;
                    canvas.width = viewport.width;

                    var renderContext = {
                        canvasContext: ctx,
                        viewport: viewport
                    };
                    var renderTask = page.render(renderContext);

                    renderTask.promise.then(function() {
                        pageRendering = false;
                        if (pageNumPending !== null) {
                            renderPage(pageNumPending);
                            pageNumPending = null;
                        }
                    });
                });
                document.getElementById('page_num').textContent = num;
            }

            function queueRenderPage(num) {
                if (pageRendering) {
                    pageNumPending = num;
                } else {
                    renderPage(num);
                }
            }

            function onPrevPage() {
                if (pageNum <= 1) {
                    return;
                }
                pageNum--;
                queueRenderPage(pageNum);
            }
            document.getElementById('prev').addEventListener('click', onPrevPage);

            function onNextPage() {
                if (pageNum >= pdfDoc.numPages) {
                    return;
                }
                pageNum++;
                queueRenderPage(pageNum);
            }
            document.getElementById('next').addEventListener('click', onNextPage);

            function onZoomOut() {
                scale = Math.max(0.2, scale - 0.2); // Минимальный масштаб 0.2
                queueRenderPage(pageNum);
            }
            document.getElementById('ZoomOut').addEventListener('click', onZoomOut);

            function onZoomIn() {
                scale = scale + 0.2;
                queueRenderPage(pageNum);
            }
            document.getElementById('ZoomIn').addEventListener('click', onZoomIn);

            pdfjsLib.getDocument({data: BinData}).promise.then(function(pdfDoc_) {
                pdfDoc = pdfDoc_;
                document.getElementById('page_count').textContent = pdfDoc.numPages;
                renderPage(pageNum);
            });
        });
    </script>
</body>
</html>
";
Показать

В этом примере мы добавили элементы <button> и <span> для навигации и отображения информации. Стиль @media print с классом noprint позволяет скрыть эти элементы при печати документа, чтобы они не попадали на лист.

Шаг 4: Динамическое изменение масштаба из формы 1С

Мы также можем управлять масштабом PDF не только кнопками на HTML-странице, но и, например, через реквизит на управляемой форме 1С. Для этого мы можем при изменении реквизита "Масштаб" в 1С перестроить или модифицировать HTML-код, отправляемый в поле HTML документа.


&НаКлиенте
Процедура ИзменитьМасштаб(Команда)
    Если МасштабДокументаPDF = 0 Тогда
        МасштабДокументаPDF = 150; // Начальное значение
    КонецЕсли;

    // Ищем текущее значение масштаба в HTML-коде
    СтрокаПоиска = "var scale = " + МасштабСтрокой; // МасштабСтрокой - реквизит формы, хранящий предыдущее значение масштаба

    // Форматируем новое значение масштаба
    МасштабСтрокой = Формат(МасштабДокументаPDF / 100, "ЧРД=."); 
    СтрокаЗамены = "var scale = " + МасштабСтрокой;

    // Заменяем строку с масштабом в текущем HTML-документе
    Элементы.ПолеХТМЛДокумента.УстановитьHTML(СтрЗаменить(Элементы.ПолеХТМЛДокумента.ПолучитьHTML(), СтрокаПоиска, СтрокаЗамены));
КонецПроцедуры

Здесь МасштабДокументаPDF - это числовой реквизит формы 1С (например, 100 для 100%), а МасштабСтрокой - вспомогательный строковый реквизит, в котором мы храним текущее значение масштаба в формате JavaScript, чтобы корректно найти и заменить его в HTML. Метод УстановитьHTML() позволяет нам динамически обновлять содержимое HTML-поля без полной перезагрузки.

Метод 2: Использование Blob URL и <iframe>

Этот метод является более простым и "нативным" для современных браузерных движков, включая WebKit. Он полагается на то, что браузер может самостоятельно отображать PDF-документы, если ему предоставить их в виде "объектного URL" (Blob URL). Такой подход часто оказывается быстрее и может поддерживать функции выделения текста и стандартной печати, если эти функции встроены в просмотрщик PDF самого браузера или движка.

Шаг 1: Подготовка данных PDF (аналогично первому методу)

Нам также потребуется строка Base64, полученная из двоичных данных PDF.


СтрокаPDFФайлаBase64 = ПолучитьBase64СтрокуИзДвоичныхДанных(ПолучитьОбщийМакет("PDF")); // Или из файла, как в предыдущих примерах

Шаг 2: Создание Blob URL и встраивание в <iframe>

Мы создадим JavaScript-код, который преобразует Base64-строку в массив байтов, затем в объект Blob, а из него — в временный URL. Этот URL затем присваивается атрибуту src элемента <iframe>.


HTMLДокумент = "
<!DOCTYPE html>
<html>
<head>
    <meta charset=\"utf-8\">
    <title>Просмотр PDF</title>
</head>
<body style='margin:0px;padding:0px;overflow:hidden;'>
    <iframe id='framefile' style='width:100%;height:100%;border:none;'></iframe>
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            var blob = new Blob([Uint8Array.from(atob(`" + СтрокаPDFФайлаBase64 + "`), c => c.charCodeAt(0))], {type: 'application/pdf'});
            var url = URL.createObjectURL(blob);
            var iframe = document.getElementById('framefile');
            iframe.src = url;
        });
    </script>
</body>
</html>
";
Показать

Этот код создает <iframe>, который занимает всю доступную область HTML-поля. После загрузки документа (событие DOMContentLoaded), JavaScript создает Blob из Base64-строки и генерирует уникальный URL для этого Blob. Затем этот URL присваивается src <iframe>, и браузер рендерит PDF внутри него. Важно убедиться, что теги <script> и <iframe> присутствуют и правильно закрыты.

Примечание: Этот метод может потребовать установки сторонних программ (например, Adobe Acrobat Reader) или плагинов в более старых версиях операционных систем или браузеров, чтобы движок WebKit мог корректно отобразить PDF. Однако в современных версиях WebKit и 1С он часто работает "из коробки".

Общие рекомендации и решение распространённых проблем

  1. Лишние пробелы в коде: При копировании HTML-кода из форумов или других источников в конфигуратор 1С, могут появиться невидимые или лишние пробелы, которые нарушают синтаксис JavaScript. Тщательно проверяйте код на их отсутствие, особенно при ошибках типа "неожиданный токен" или "синтаксическая ошибка".
  2. Совместимость версий PDF.js и 1С: Если возникают проблемы с отображением, попробуйте использовать разные версии библиотеки PDF.js. Например, если последняя версия не работает, протестируйте с 2.2.2, 2.4.199 или 2.4.456. Проблема часто кроется в том, что встроенный WebKit в 1С может быть не самым свежим и не поддерживать все новейшие возможности JavaScript.
  3. Офлайн-работа: Для работы без доступа к интернету, все скрипты (pdf.js, pdf.worker.js) должны быть скачаны и размещены локально. Вы можете хранить их в текстовых или двоичных макетах конфигурации 1С. При этом учитывайте, что очень большие файлы в текстовых макетах могут вызывать зависания при редактировании, а в двоичных макетах — проблемы с XDTO-символами при клиент-серверном взаимодействии.
  4. Производительность в веб-клиенте: При использовании PDF.js в веб-клиенте, особенно с большими файлами, убедитесь, что pdf.worker.js не подгружается сразу через <script> тег, а путь к нему указывается через pdfjsLib.GlobalWorkerOptions.workerSrc. Это позволяет WebKit асинхронно загружать воркер и улучшает производительность.
  5. Печать:
    • При использовании PDF.js (рендеринг на <canvas>), печать будет выводить то, что отображается на холсте. Если вам нужно скрыть кнопки управления при печати, используйте CSS-стили:
      
      <style>
          @media print {
              .noprint { display:none; }
          }
      </style>
      
      И присваивайте класс noprint элементам, которые не должны печататься.
    • При использовании метода с <iframe> и Blob URL, печать обычно выполняется средствами самого браузера и может быть более полной, включая возможность выделения текста.
  6. Масштабирование через Ctrl + колесо мыши: Если вы хотите, чтобы пользователь мог масштабировать страницу (включая PDF и элементы управления) с помощью стандартных комбинаций браузера (например, Ctrl + колесо мыши), убедитесь, что в мета-тегах нет указаний типа user-scalable=no, которые могут запрещать масштабирование.
  7. Выделение и копирование текста: При рендеринге PDF на <canvas> (метод с PDF.js), текст отображается как часть растрового изображения, и его прямое выделение и копирование обычно невозможно без дополнительной реализации текстового слоя PDF.js. Метод с <iframe> может позволять выделение текста, если это поддерживается встроенным просмотрщиком браузера.
  8. Клиент-серверная архитектура: В клиент-серверных базах могут возникать особенности при передаче данных. Если клиент и сервер 1С находятся на разных машинах, убедитесь, что передача Base64-строки происходит корректно и нет проблем с доступом к временным файлам или ресурсам.

Надеемся, эти подходы помогут вам успешно отображать PDF-документы в поле HTML на WebKit в 1С:Предприятии, открывая новые возможности для взаимодействия с документами в ваших конфигурациях.

← На главную