Коллеги, с внедрением движка 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, чтобы вывести документ в виде обычной картинки.
Одним из наиболее мощных и гибких решений является использование JavaScript-библиотеки PDF.js от Mozilla. Эта библиотека позволяет нам рендерить PDF-документы на HTML-элемент <canvas>. Таким образом, PDF фактически преобразуется в растровое изображение, которое мы можем отобразить и манипулировать им. Если вы хотите избежать сложностей с интеграцией JavaScript на форме, можно задействовать обработку для постраничной конвертации PDF в формат JPEG для отображения страниц.
Прежде всего, нам необходимо получить содержимое PDF-файла в виде двоичных данных и преобразовать их в строку в формате Base64. Этот шаг выполняется на стороне 1С.
СтрокаPDFФайла = ПолучитьBase64СтрокуИзДвоичныхДанных(ПолучитьИзВременногоХранилища(АдресФайла));
// Или, если у вас уже есть ДвоичныеДанные:
// ДД = Новый ДвоичныеДанные(Результат.ПолноеИмяФайла);
// СтрокаPDFФайла = ПолучитьBase64СтрокуИзДвоичныхДанных(ДД);
Здесь АдресФайла – это адрес временного хранилища, куда мы предварительно поместили наш PDF-файл. Функция ПолучитьBase64СтрокуИзДвоичныхДанных() преобразует двоичные данные в строку Base64, готовую для передачи в JavaScript.
Теперь мы можем встроить 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> документа. Таким образом, вы получаете прокручиваемый многостраничный документ.
Для полноценной работы с 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>
Страница: <span id=\"page_num\"></span> из <span id=\"page_count\"></span>
<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 позволяет скрыть эти элементы при печати документа, чтобы они не попадали на лист.
Мы также можем управлять масштабом 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-поля без полной перезагрузки.
<iframe>Этот метод является более простым и "нативным" для современных браузерных движков, включая WebKit. Он полагается на то, что браузер может самостоятельно отображать PDF-документы, если ему предоставить их в виде "объектного URL" (Blob URL). Такой подход часто оказывается быстрее и может поддерживать функции выделения текста и стандартной печати, если эти функции встроены в просмотрщик PDF самого браузера или движка.
Нам также потребуется строка Base64, полученная из двоичных данных PDF.
СтрокаPDFФайлаBase64 = ПолучитьBase64СтрокуИзДвоичныхДанных(ПолучитьОбщийМакет("PDF")); // Или из файла, как в предыдущих примерах
<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С он часто работает "из коробки".
pdf.js, pdf.worker.js) должны быть скачаны и размещены локально. Вы можете хранить их в текстовых или двоичных макетах конфигурации 1С. При этом учитывайте, что очень большие файлы в текстовых макетах могут вызывать зависания при редактировании, а в двоичных макетах — проблемы с XDTO-символами при клиент-серверном взаимодействии.pdf.worker.js не подгружается сразу через <script> тег, а путь к нему указывается через pdfjsLib.GlobalWorkerOptions.workerSrc. Это позволяет WebKit асинхронно загружать воркер и улучшает производительность.<canvas>), печать будет выводить то, что отображается на холсте. Если вам нужно скрыть кнопки управления при печати, используйте CSS-стили:
<style>
@media print {
.noprint { display:none; }
}
</style>
noprint элементам, которые не должны печататься.
<iframe> и Blob URL, печать обычно выполняется средствами самого браузера и может быть более полной, включая возможность выделения текста.user-scalable=no, которые могут запрещать масштабирование.<canvas> (метод с PDF.js), текст отображается как часть растрового изображения, и его прямое выделение и копирование обычно невозможно без дополнительной реализации текстового слоя PDF.js. Метод с <iframe> может позволять выделение текста, если это поддерживается встроенным просмотрщиком браузера.Надеемся, эти подходы помогут вам успешно отображать PDF-документы в поле HTML на WebKit в 1С:Предприятии, открывая новые возможности для взаимодействия с документами в ваших конфигурациях.