Your IP : 18.189.171.57


Current Path : /var/www/www-root/data/www/www.monolith-realty.ru/bitrix/js/ui/viewer/src/
Upload File :
Current File : /var/www/www-root/data/www/www.monolith-realty.ru/bitrix/js/ui/viewer/src/item-document.js

import {Text, Uri, Loc, Dom, Reflection, Event, Runtime, ajax as Ajax, Type} from 'main.core';
import {EventEmitter} from 'main.core.events';

const Item = Reflection.namespace('BX.UI.Viewer.Item');
const Util = Reflection.namespace('BX.util');
const BXPromise = Reflection.namespace('BX.Promise');

const DEFAULT_SCALE = 1.4;
const PAGES_TO_PRELOAD = 3;

// noinspection JSClosureCompilerSyntax
/**
 * @memberof BX.UI.Viewer
 * @extends BX.UI.Viewer.Item
 */
export class Document extends Item
{
	static #loadingLibraryPromise = null;
	#pageNumber: number = 1;
	#loadingDocumentPromise: Promise = null;

	pdfDocument;
	pdfPages: Object<number,Object> = {};
	scale: number = DEFAULT_SCALE;
	pdfRenderedPages: Object<number,Object> = {};
	lastRenderedPdfPage: number = 0;
	contentNode: Element;
	previewHtml: Element;
	disableAnnotationLayer: boolean = false;

	constructor (options)
	{
		super(options);

		options = options || {};

		this.scale = options.scale || DEFAULT_SCALE;
	}

	setPropertiesByNode(node:HTMLElement): void
	{
		super.setPropertiesByNode(node);

		this.disableAnnotationLayer = node.dataset.hasOwnProperty('disableAnnotationLayer');
	}

	applyReloadOptions(options)
	{
		this.controller.unsetCachedData(this.src);
	}

	listContainerModifiers(): Array<string>
	{
		const result = [
			'ui-viewer-document',
		];
		if (this.controller.stretch)
		{
			result.push('--stretch');
		}

		return result;
	}

	setSrc(src: string|Uri): this
	{
		this.src = src;
		this._pdfSrc = null;

		return this.#resetState();
	}

	setPdfSource(pdfSource: string|Uri|ArrayBuffer): this
	{
		this._pdfSrc = pdfSource;

		return this.#resetState();
	}

	#resetState(): this
	{
		this.pdfRenderedPages = {};
		this.lastRenderedPdfPage = null;
		this.pdfDocument = null;
		this.pdfPages = {};
		this.setPageNumber(1);
		if (this.printer)
		{
			this.hidePrintProgress();
			this.printer.destroy();
		}
	}

	loadLibrary(): Promise
	{
		if (Document.#loadingLibraryPromise !== null)
		{
			return Document.#loadingLibraryPromise;
		}
		Document.#loadingLibraryPromise = new Promise((resolve, reject) => {
			Runtime.loadExtension('ui.pdfjs').then(() => {
				if (!pdfjsLib.GlobalWorkerOptions.workerSrc)
				{
					pdfjsLib.GlobalWorkerOptions.workerSrc = '/bitrix/js/ui/pdfjs/pdf.worker.js';
				}

				Document.#loadingLibraryPromise = null;

				resolve();
			})
			.catch(reject);
		});

		return Document.#loadingLibraryPromise;
	}

	loadData(): BXPromise
	{
		const promise = new BXPromise();

		if (this._pdfSrc)
		{
			this.loadLibrary().then(() => {
				promise.fulfill(this);
			});

			return promise;
		}

		console.log('loadData pdf');
		const ajaxPromise = Ajax.promise({
			url: Uri.addParam(this.src, {ts: 'bxviewer'}),
			method: 'GET',
			dataType: 'json',
			headers: [
				{
					name: 'BX-Viewer-src',
					value: this.src
				},
				{
					name: 'BX-Viewer',
					value: 'document'
				}
			]
		});

		ajaxPromise.then((response) => {
			if (!response || !response.data)
			{
				this.isTransforming = false;
				promise.reject({
					item: this,
					message: Loc.getMessage("JS_UI_VIEWER_ITEM_TRANSFORMATION_ERROR_1").replace('#DOWNLOAD_LINK#', this.getSrc()),
					type: 'error'
				});

				return promise;
			}

			if (response.data.hasOwnProperty('pullTag'))
			{
				if (!this.isTransforming)
				{
					this.transformationPromise = promise;
					this.registerTransformationHandler(response.data.pullTag);
				}
				this.isTransforming = true;
			}

			if (response.data.data && response.data.data.src)
			{
				this.isTransforming = false;
				this._pdfSrc = response.data.data.src;
				this.loadLibrary().then(() => {
					promise.fulfill(this);
				});
			}
		});

		return promise;
	}

	render(): HTMLDivElement
	{
		this.controller.showLoading();

		this.contentNode = Dom.create('div', {
			props: {
				className: 'ui-viewer-item-document-content',
				tabIndex: 2208
			},
		});

		Event.bind(this.contentNode, 'scroll', Runtime.throttle(this.handleScrollDocument.bind(this), 100));

		return this.contentNode;
	}

	getNakedActions(): Array
	{
		const nakedActions = super.getNakedActions();

		return this.insertPrintBeforeInfo(nakedActions);
	}

	insertPrintBeforeInfo(actions: Array): Array<{type: string, action: Function}>
	{
		actions = actions || [];

		let infoIndex = null;
		for (let i = 0; i < actions.length; i++)
		{
			if (actions[i].type === 'info')
			{
				infoIndex = i;
			}
		}

		const printAction = {
			type: 'print',
			action: this.print.bind(this)
		};

		if (infoIndex === null)
		{
			actions.push(printAction);
		}
		else
		{
			actions = Util.insertIntoArray(actions, infoIndex, printAction);
		}

		return actions;
	}

	getFirstDocumentPageHeight(): Promise<number>
	{
		if (this._height)
		{
			return Promise.resolve(this._height);
		}

		return new Promise((resolve) => {
			this.getDocumentPage(this.pdfDocument, 1).then((page) => {
				const viewport = page.getViewport(this.scale);
				this._height = viewport.height;

				resolve(this._height);
			});
		});
	}

	handleScrollDocument(event): void
	{
		this.getFirstDocumentPageHeight().then((height) => {
			const scrollBottom = this.contentNode.scrollHeight - this.contentNode.scrollTop - this.contentNode.clientHeight;
			if (scrollBottom < height * PAGES_TO_PRELOAD && this.pdfDocument.numPages > this.lastRenderedPdfPage)
			{
				for (let i = this.lastRenderedPdfPage + 1; i <= Math.min(this.pdfDocument.numPages, this.lastRenderedPdfPage + PAGES_TO_PRELOAD); i++)
				{
					this.renderDocumentPage(this.pdfDocument, i);
				}
			}

			this.setPageNumber((this.contentNode.scrollTop / height) + 1);
		});
	}

	loadDocument(): Promise<Object>
	{
		if (this.pdfDocument)
		{
			return Promise.resolve(this.pdfDocument);
		}

		if (this.#loadingDocumentPromise)
		{
			return this.#loadingDocumentPromise;
		}

		this.#loadingDocumentPromise = new Promise((resolve) => {
			this.loadData().then(() => {
				pdfjsLib.getDocument(this._pdfSrc).promise.then((pdf) => {
					this.pdfDocument = pdf;
					this.#loadingDocumentPromise = null;

					resolve(this.pdfDocument);
				});
			});
		});

		return this.#loadingDocumentPromise;
	}

	getDocumentPage(pdf, pageNumber): Promise<Object>
	{
		if (this.pdfPages[pageNumber])
		{
			return Promise.resolve(this.pdfPages[pageNumber]);
		}

		return new Promise((resolve) => {
			pdf.getPage(pageNumber).then((page) => {
				this.pdfPages[pageNumber] = page;

				resolve(this.pdfPages[pageNumber]);
			});
		});
	}

	renderDocumentPage(pdf, pageNumber): Promise<Object>
	{
		const pagePromise = this.pdfRenderedPages[pageNumber];
		if (pagePromise instanceof Promise)
		{
			return pagePromise;
		}
		else if(!!pagePromise)
		{
			return Promise.resolve(pagePromise);
		}

		this.pdfRenderedPages[pageNumber] = new Promise((resolve) => {
			this.getDocumentPage(pdf, pageNumber).then((page) =>  {
				const canvas = this.createCanvasPage();
				const viewport = page.getViewport(this.scale);
				canvas.height = viewport.height;
				canvas.width = viewport.width;
				const renderPromise = page.render({canvasContext: canvas.getContext('2d'), viewport: viewport});

				if (!this.disableAnnotationLayer)
				{
					renderPromise.then(function () {
						return page.getAnnotations();
					}).then(function (annotationData) {
						const annotationLayer = Dom.create('div', {
							props: {className: 'ui-viewer-pdf-annotation-layer'}
						});

						Dom.insertAfter(annotationLayer, canvas);
						Dom.adjust(annotationLayer, {
							style: {
								margin: '-' + canvas.offsetHeight + 'px auto 0 auto',
								height: canvas.height + 'px',
								width: canvas.width + 'px'
							}
						});

						pdfjsLib.AnnotationLayer.render({
							viewport: viewport.clone({dontFlip: true}),
							linkService: pdfjsLib.SimpleLinkService,
							div: annotationLayer,
							annotations: annotationData,
							page: page
						});
					});
				}

				renderPromise.then(function () {
					return page.getTextContent();
				}).then(function (textContent) {
					const textLayer = Dom.create('div', {
						props: {className: 'ui-viewer-pdf-text-layer'}
					});

					Dom.insertAfter(textLayer, canvas);
					Dom.adjust(textLayer, {
						style: {
							margin: '-' + canvas.offsetHeight + 'px auto 0 auto',
							height: canvas.height + 'px',
							width: canvas.width + 'px'
						}
					});

					pdfjsLib.renderTextLayer({
						textContent: textContent,
						container: textLayer,
						viewport: viewport,
						textDivs: []
					});
				});

				this.lastRenderedPdfPage = Math.max(pageNumber, this.lastRenderedPdfPage);

				if (pageNumber === 1)
				{
					this.firstWidthDocumentPage = canvas.width;
				}

				renderPromise.then(() => {
					this.controller.hideLoading();
					this.pdfRenderedPages[pageNumber] = page;

					resolve(page, canvas);
				});
			});
		});

		return this.pdfRenderedPages[pageNumber];
	}

	createCanvasPage(): HTMLCanvasElement
	{
		const canvas = document.createElement('canvas');
		canvas.className = 'ui-viewer-document-page-canvas';
		this.contentNode.appendChild(canvas);

		return canvas;
	}

	getContentWidth (): Promise<Number>
	{
		if (this.firstWidthDocumentPage)
		{
			return Promise.resolve(this.firstWidthDocumentPage);
		}

		return new Promise((resolve) => {
			this.loadDocument().then(() => {
				this.renderDocumentPage(this.pdfDocument, 1).then((page) => {
					resolve(page.getViewport(this.scale).width);
				})
			});
		});
	}

	afterRender(): void
	{
		this.loadDocument().then((pdf) => {
			for (let i = 1; i <= Math.min(pdf.numPages, PAGES_TO_PRELOAD); i++)
			{
				if (i === 1)
				{
					this._handleControls = this.controller.handleVisibleControls.bind(this.controller);
					this.controller.enableReadingMode(true);

					const printAction = this.controller.actionPanel.getItemById('print');
					if (printAction)
					{
						printAction.layout.container.classList.remove('ui-btn-disabled');
					}

					Runtime.throttle(Event.bind(window, 'mousemove', this._handleControls), 20);
				}

				this.renderDocumentPage(pdf, i);
			}
		});
	}

	beforeHide(): void
	{
		this.pdfRenderedPages = {};
		Event.unbind(window, 'mousemove', this._handleControls);
		if (this.printer)
		{
			this.hidePrintProgress();
			this.printer.destroy();
		}
	}

	updatePrintProgressMessage(index: number, total: number): void
	{
		const progress = Math.round((index / total) * 100);
		this.controller.setTextOnLoading(Loc.getMessage('JS_UI_VIEWER_ITEM_PREPARING_TO_PRINT').replace('#PROGRESS#', progress));
	}

	showPrintProgress(index: number, total: number): void
	{
		this.contentNode.style.opacity = 0.7;
		this.contentNode.style.filter = 'blur(2px)';

		this.controller.showLoading({
			zIndex: 1
		});

		this.updatePrintProgressMessage(index, total);
	}

	hidePrintProgress(): void
	{
		this.contentNode.style.opacity = null;
		this.contentNode.style.filter = null;

		this.controller.hideLoading();
	}

	print(): void
	{
		if (!this.pdfDocument)
		{
			console.warn('Where is pdf document to print?');

			return;
		}

		this.showPrintProgress(0, this.pdfDocument.numPages);

		this.printer = new PrintService({
			pdf: this.pdfDocument
		});

		this.printer.init().then(() => {
			this.printer.prepare({
				onProgress: this.updatePrintProgressMessage.bind(this)
			}).then(() => {
				this.hidePrintProgress();
				this.printer.performPrint();
			});
		});
	}

	handleKeyPress(event): void
	{
		switch (event.code)
		{
			case 'PageDown':
			case 'PageUp':
			case 'ArrowDown':
			case 'ArrowUp':
				BX.focus(this.contentNode);
				break;
		}
	}

	getScale(): number
	{
		return this.scale;
	}

	setScale(scale: number): this
	{
		this.scale = scale;

		return this;
	}

	updateScale(scale: number): Promise<void>
	{
		scale = Number(scale);
		if (this.scale === scale)
		{
			return Promise.resolve();
		}

		const ratio = scale / this.scale;

		const updatePageScale = ((
			page,
			canvases: Array<number, HTMLCanvasElement>,
			textLayers: Array<number,HTMLDivElement>
		): Promise => {
			const canvas = canvases[page.pageIndex];
			if (!canvas)
			{
				return Promise.resolve();
			}
			return new Promise((resolve) => {
				const viewport = page.getViewport(this.scale);
				canvas.width = viewport.width;
				canvas.height = viewport.height;
				page.render({
					canvasContext: canvas.getContext('2d'),
					viewport: viewport,
				}).then(() => {
					const textLayer = textLayers[page.pageIndex];
					if (textLayer)
					{
						Dom.clean(textLayer);
						Dom.adjust(textLayer, {
							style: {
								margin: '-' + canvas.offsetHeight + 'px auto 0 auto',
								height: viewport.height + 'px',
								width: viewport.width + 'px'
							}
						});

						page.getTextContent().then((textContent) => {
							pdfjsLib.renderTextLayer({
								textContent: textContent,
								container: textLayer,
								viewport: viewport,
								textDivs: []
							});

							resolve();
						});
					}
					else
					{
						resolve();
					}
				});
			});
		});

		const promises = [];
		this.scale = scale;
		const canvases = Array.from(this.contentNode.querySelectorAll('canvas[class="ui-viewer-document-page-canvas"]'));
		const textLayers = Array.from(this.contentNode.querySelectorAll('div[class="ui-viewer-pdf-text-layer"]'));
		Object.values(this.pdfRenderedPages).forEach((renderedPage) => {
			if (renderedPage instanceof Promise)
			{
				promises.push(new Promise((resolve) => {
					renderedPage.then((page) => {
						updatePageScale(page, canvases, textLayers).then(resolve);
					});
				}));
			}
			else
			{
				promises.push(updatePageScale(renderedPage, canvases, textLayers));
			}
		});

		const scrollTop = this.contentNode.scrollTop * ratio;
		this.contentNode.scrollTo(this.contentNode.scrollLeft, scrollTop);

		return Promise.all(promises);
	}

	getPagesNumber(): ?number
	{
		if (!this.pdfDocument)
		{
			return null;
		}

		return Text.toInteger(this.pdfDocument._pdfInfo.numPages);
	}

	scrollToPage(pageNumber: number): Promise<void>
	{
		const isChanged = this.setPageNumber(pageNumber) !== null;
		if (!isChanged)
		{
			return Promise.resolve();
		}

		return new Promise((resolve) => {
			const renderPromises = [];
			for (let i = 1; i < pageNumber; i++)
			{
				renderPromises.push(this.renderDocumentPage(this.pdfDocument, i));
			}
			Promise.all(renderPromises).then((pages) => {
				let height = 0;

				pages.forEach((page) => {
					const viewport = page.getViewport(this.scale);
					height += viewport.height + 7;
				});

				this.contentNode.scrollTo(this.contentNode.scrollLeft, height);

				resolve();
			});
		});
	}

	getPageNumber(): number
	{
		return this.#pageNumber;
	}

	setPageNumber(pageNumber: number): this|null
	{
		pageNumber = Text.toInteger(pageNumber);
		if (pageNumber < 0)
		{
			pageNumber = 1;
		}

		let numPages = this.getPagesNumber();
		if (!numPages)
		{
			numPages = 1;
		}

		if (pageNumber > numPages)
		{
			pageNumber = numPages;
		}

		if (this.#pageNumber !== pageNumber)
		{
			this.#pageNumber = pageNumber;
			EventEmitter.emit(this, 'BX.UI.Viewer.Item.Document:updatePageNumber');

			return this;
		}

		return null;
	}
}

const PRINT_SCALE = 1;

export class PrintService
{
	constructor(options)
	{
		options = options || {};
		this.pdf = options.pdf;
		this.iframe = null;
		this.documentOverview = {};
	}

	init()
	{
		if (this.documentOverview)
		{
			return Promise.resolve(this.documentOverview);
		}

		return new Promise((resolve) => {
			this.pdf.getPage(1).then((page) => {
				const viewport = page.getViewport(PRINT_SCALE);

				this.documentOverview = {
					width: viewport.width, height: viewport.height, rotation: viewport.rotation
				};

				resolve(this.documentOverview);
			});
		});
	}

	/**
	 * @param {?Object} options
	 * @param {Function} [options.onProgress]
	 * @return {BXPromise}
	 */
	prepare(options)
	{
		options = options || {};
		const pageCount = this.pdf.numPages;
		let currentPage = -1;
		const promise = new BXPromise();
		let onProgress = null;
		if (Type.isFunction(options.onProgress))
		{
			onProgress = options.onProgress;
		}

		this.frame = this.createIframe();

		const process = () => {
			if (++currentPage >= pageCount)
			{
				console.log('finish', this.frame.contentWindow.document);

				setTimeout(() => {
					promise.fulfill();
				}, 1000);

				return;
			}

			this.renderPage(currentPage + 1).then(function () {
				if (onProgress)
				{
					onProgress(currentPage + 1, pageCount);
				}
				process();
			});
		};

		process();

		return promise;
	}

	renderPage(pageNumber)
	{
		return this.pdf.getPage(pageNumber).then(function (page) {
			const scratchCanvas = document.createElement('canvas');
			const viewport = page.getViewport(1);
			// The size of the canvas in pixels for printing.
			const PRINT_RESOLUTION = 150;
			const PRINT_UNITS = PRINT_RESOLUTION / 72.0;
			scratchCanvas.width = Math.floor(viewport.width * PRINT_UNITS);
			scratchCanvas.height = Math.floor(viewport.height * PRINT_UNITS);

			// The physical size of the img as specified by the PDF document.
			const CSS_UNITS = 96.0 / 72.0;
			const width = Math.floor(viewport.width * CSS_UNITS) + 'px';
			const height = Math.floor(viewport.height * CSS_UNITS) + 'px';

			const ctx = scratchCanvas.getContext('2d');
			ctx.save();
			ctx.fillStyle = 'rgb(255, 255, 255)';
			ctx.fillRect(0, 0, scratchCanvas.width, scratchCanvas.height);
			ctx.restore();

			const renderContext = {
				canvasContext: ctx,
				transform: [PRINT_UNITS, 0, 0, PRINT_UNITS, 0, 0],
				viewport: page.getViewport(1, viewport.rotation),
				intent: 'print'
			};

			return page.render(renderContext).promise.then(function () {
				return {
					scratchCanvas: scratchCanvas, width: width, height: height
				}
			});
		}).then((printItem) => {

			const img = document.createElement('img');
			img.style.width = printItem.width;
			img.style.height = printItem.height;

			const scratchCanvas = printItem.scratchCanvas;
			if (('toBlob' in scratchCanvas) && !this.disableCreateObjectURL)
			{
				scratchCanvas.toBlob(function (blob) {
					img.src = URL.createObjectURL(blob);
				});
			}
			else
			{
				img.src = scratchCanvas.toDataURL();
			}

			const wrapper = document.createElement('div');
			wrapper.appendChild(img);

			this.frame.contentWindow.document.body.appendChild(wrapper);
		});
	}

	destroy()
	{
		if (this.frame)
		{
			Dom.remove(this.frame);
		}
	}

	createIframe()
	{
		const frame = document.createElement("iframe");
		frame.src = "about:blank";
		frame.name = "document-print-frame";
		frame.style.display = "none";
		document.body.appendChild(frame);

		const frameWindow = frame.contentWindow;
		const frameDoc = frameWindow.document;
		frameDoc.open();
		frameDoc.write('<html><head>');

		const pageSize = this.getDocumentOverview();
		let headTags = "<style>";
		headTags += "html, body { background: #fff !important; height: 100%; }";
		headTags += '@supports ((size:A4) and (size:1pt 1pt)) {' + '@page { size: ' + pageSize.width + 'pt ' + pageSize.height + 'pt;}' + '}';
		headTags += '#ad{ display:none;}';
		headTags += '#leftbar{ display:none;}';
		headTags += "</style>";

		frameDoc.write(headTags);

		frameDoc.write('</head><body>');
		frameDoc.write('</body></html>');
		frameDoc.close();

		return frame;
	}

	performPrint()
	{
		this.frame.contentWindow.focus();
		this.frame.contentWindow.print();
	}

	getDocumentOverview()
	{
		return this.documentOverview;
	}
}