UNPKG

pdfh5

Version:

JS plugin of preview PDF for mobile. web/h5/移动端PDF预览手势缩放插件

1,454 lines (1,258 loc) 151 kB
; (function (g, fn) { var version = "3.0.0", pdfjsVersion = "5.4.296"; console.info("pdfh5.js v" + version + " && pdf.js v" + pdfjsVersion + " https://pdfh5.gjtool.cn"); if (!g.document) { throw new Error("pdfh5 requires a window with a document"); } async function initPdfJs() { if (g.pdfjsLib) { return g.pdfjsLib; } try { let pdfjsLib = await import('./pdf.min.js'); let sandboxModule = await import('./pdf.sandbox.min.js'); let workerSrc = './js/pdf.worker.min.js'; let cMapUrl = '../cmaps/'; let standardFontDataUrl = '../standard_fonts/'; let iccUrl = '../iccs/'; let wasmUrl = '../wasm/'; // 集成沙箱功能 if (sandboxModule && sandboxModule.default) { pdfjsLib.Sandbox = sandboxModule.default; pdfjsLib.SandboxManager = sandboxModule.SandboxManager; } pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc; var resourcePaths = { workerSrc: workerSrc, cMapUrl: cMapUrl, standardFontDataUrl: standardFontDataUrl, iccUrl: iccUrl, wasmUrl: wasmUrl }; window._pdfh5ResourcePaths = resourcePaths; return pdfjsLib; } catch (error) { console.error('Failed to load PDF.js:', error); throw error; } } // 创建Pdfh5构造函数 var Pdfh5Constructor = fn(version, initPdfJs); if (typeof define === 'function' && define.amd) { define(function () { return Pdfh5Constructor; }); } else if (typeof module !== 'undefined' && module.exports) { module.exports = Pdfh5Constructor; } else { g.Pdfh5 = Pdfh5Constructor; } })(typeof window !== 'undefined' ? window : this, function (version, initPdfJs) { 'use strict'; var css = '.pdfjs {width: 100%;height: 100%;overflow: hidden;background: #fff;position: relative;}.pdfjs .viewerContainer {position: relative;width: 100%;height: 100%;overflow-y: auto;overflow-x: hidden;-webkit-overflow-scrolling: touch;transition: all .3s;}.pdfjs .pdfViewer {position: relative;top: 0;left: 0;padding: 10px 8px;}.pdfjs .pdfViewer .pageContainer {margin: 0px auto 8px auto;position: relative;overflow: visible;-webkit-box-shadow: darkgrey 0px 1px 3px 0px;-moz-box-shadow: darkgrey 0px 1px 3px 0px;box-shadow: darkgrey 0px 1px 3px 0px;background-color: white;box-sizing: border-box;}.pdfjs .pdfViewer .pageContainer img {width: 100%;height: 100%;position: relative;z-index: 100;user-select: none;pointer-events: none;}.pdfjs .pdfViewer .pageContainer canvas {width: 100%;height: 100%;position: relative;z-index: 100;user-select: none;pointer-events: none;}.pdfjs .pageNum {padding: 0px 7px;height: 26px;position: absolute;top: 20px;left: 15px;z-index: 997;border-radius: 8px;transition: all .3s;display: none;}.pdfjs .pageNum-bg, .pdfjs .pageNum-num {width: 100%;height: 100%;line-height: 26px;text-align: center;position: absolute;top: 0px;left: 0px;color: #fff;border-radius: 8px;font-size: 16px;}.pdfjs .pageNum-bg {background: rgba(0, 0, 0, 0.5);}.pdfjs .pageNum-num {position: relative;}.pdfjs .pageNum span {color: #fff;font-size: 16px;}.pdfjs .loadingBar {position: absolute;width: 100%;z-index: 1000;background: #fff !important;height: 4px;top: 0px;left: 0px;transition: all .3s;}.pdfjs .loadingBar .progress {background: #fff !important;position: absolute;top: 0;left: 0;width: 0%;height: 100%;overflow: hidden;transition: width 200ms;}.pdfjs .loadingBar .progress .glimmer {position: absolute;top: 0;left: 0;height: 100%;width: calc(100% + 150px);background: #7bcf34;}.pdfjs .backTop {width: 50px;height: 50px;line-height: 50px;text-align: center;position: absolute;bottom: 90px;right: 15px;font-size: 18px;z-index: 999;border-radius: 50%;background: rgba(0, 0, 0, 0.4) url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAAsSAAALEgHS3X78AAAA+klEQVRYw+2WUQ2DMBCG2TIBSJiESkACEpCAg83BcLBJmIQ5gClgDpiDby9tciGkoaUtZOESXuhdv7+X/pdm2dYC6IgX7Zh3THy+w9oN/rMASqBcE26iSA1XwCAEDIBKBc8F/KE/gB7IU8BbDXyJf2Z2tFFFAE8N6iRIi/jotXssuGn1FzhPrCu9BtCEhlcCrix5hbiYVSh46bKpELvcniO71Q51zWJ7ju3mUe9vzym7eR7Az57CbohTXBzAt9GknG9PoLY8KK4z6htLfeXTTXMZAfoZuWYWKC+YZWMAQuWZSP0k2wXsAnYB2xNwci1wGTKhO/COlLtu/ABVfTFsxwwYRgAAAABJRU5ErkJggg==) no-repeat center;background-size: 50% 50%;transition: all .3s;display: none;}.pdfjs .loadEffect {width: 100px;height: 100px;position: absolute;top: 50%;left: 50%;margin-top: -50px;margin-left: -50px;z-index: 103;background: url(data:image/gif;base64,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) no-repeat center;background-size: 30% 30%;transition: all .3s;display:block;}.pdfjs .pdfViewer .pageContainer img.pdfLogo {position: absolute;z-index: 101;}.pdfjs .pdfViewer .pageContainer canvas.pdfLogo {position: absolute;z-index: 101;}.pdfjs .textLayer {position: absolute;left: 0;top: 0;right: 0;bottom: 0;overflow: hidden;opacity: 0.2;line-height: 1.0;z-index: 101;user-select: text;}.pdfjs .textLayer>span {color: transparent;position: absolute;white-space: pre;cursor: text;transform-origin: 0% 0%;}.pdfjs .textLayer .highlight {margin: -1px;padding: 1px;background-color: rgba(180, 0, 170, 1);border-radius: 4px;}.pdfjs .textLayer .highlight.begin {border-radius: 4px 0px 0px 4px;}.pdfjs .textLayer .highlight.end {border-radius: 0px 4px 4px 0px;}.pdfjs .textLayer .highlight.middle {border-radius: 0px;}.pdfjs .textLayer .highlight.selected {background-color: rgba(0, 100, 0, 1);}.pdfjs .textLayer ::selection {background: rgba(0, 0, 255, 0.3); }.pdfjs .textLayer .endOfContent {display: block;position: absolute;left: 0px;top: 100%;right: 0px;bottom: 0px;z-index: -1;cursor: default;}.pdfjs .textLayer .endOfContent.active {top: 0px;}'; var buildElement = function (html) { var div = document.createElement('div'); div.innerHTML = html; return div.firstChild; }; var triggerEvent = function (el, name) { var event = document.createEvent('HTMLEvents'); event.initEvent(name, true, false); el.dispatchEvent(event); }; var definePinchZoom = function () { var PinchZoom = function (el, options, pinchParentNode) { this.el = el; this.pinchParentNode = pinchParentNode; this.zoomFactor = 1; this.lastScale = 1; this.offset = { x: 0, y: 0 }; this.options = Object.assign({}, this.defaults, options); this.options.tapZoomFactor = isNaN(options.tapZoomFactor) ? 2 : options.tapZoomFactor; this.options.zoomOutFactor = isNaN(options.zoomOutFactor) ? 1.2 : options.zoomOutFactor; this.options.animationDuration = isNaN(options.animationDuration) ? 300 : options.animationDuration; this.options.maxZoom = isNaN(options.maxZoom) ? 4 : options.maxZoom; this.options.minZoom = isNaN(options.minZoom) ? 0.5 : options.minZoom; this.options.dampingFactor = isNaN(options.dampingFactor) ? 0.85 : options.dampingFactor; this.setupMarkup(); this.bindEvents(); this.update(); this.enable(); this.height = 0; this.load = false; this.direction = null; this.clientY = null; this.lastclientY = null; this.lastOffsets = []; for (var i = 0; i < 5; i++) { this.lastOffsets.push({ x: 0, y: 0, time: Date.now() }); } }, sum = function (a, b) { return a + b; }, isCloseTo = function (value, expected) { return value > expected - 0.01 && value < expected + 0.01; }; PinchZoom.prototype = { defaults: { tapZoomFactor: 2, zoomOutFactor: 1.2, animationDuration: 300, maxZoom: 4, minZoom: 0.5, draggableUnzoomed: true, lockDragAxis: false, use2d: true, zoomStartEventName: 'pz_zoomstart', zoomEndEventName: 'pz_zoomend', dragStartEventName: 'pz_dragstart', dragEndEventName: 'pz_dragend', doubleTapEventName: 'pz_doubletap' }, handleDragStart: function (event) { triggerEvent(this.el, this.options.dragStartEventName); this.stopAnimation(); this.lastDragPosition = false; this.hasInteraction = true; this.handleDrag(event); }, handleDrag: function (event) { if (this.zoomFactor > 1.0) { var touch = this.getTouches(event)[0]; this.drag(touch, this.lastDragPosition, event); this.offset = this.sanitizeOffset(this.offset); this.lastDragPosition = touch; } }, sanitizeOffset: function (offset) { var containerWidth = this.getContainerX(); var imageWidth = this.el.offsetWidth * this.zoomFactor; var maxX = Math.max(imageWidth - containerWidth, 0); var containerHeight = this.getContainerY(); var imageHeight = this.el.offsetHeight * this.zoomFactor; var maxY = Math.max(imageHeight - containerHeight, 0); var elasticFactor = 0.5; var newX = offset.x; var newY = offset.y; if (newX < 0) { newX = newX * elasticFactor; } else if (newX > maxX) { newX = maxX + (newX - maxX) * elasticFactor; } if (newY < 0) { newY = newY * elasticFactor; } else if (newY > maxY) { newY = maxY + (newY - maxY) * elasticFactor; } return { x: newX, y: newY }; }, handleDragEnd: function () { triggerEvent(this.el, this.options.dragEndEventName); this.end(); var now = Date.now(); var validRecords = this.lastOffsets.filter(r => now - r.time < 100); var velocityX = validRecords.length > 1 ? (validRecords[validRecords.length - 1].x - validRecords[0].x) / (validRecords[validRecords.length - 1].time - validRecords[0].time) : 0; this.startInertiaAnimation(velocityX); }, startInertiaAnimation: function (initialVelocity) { var minVelocity = 0.08; var decay = 0.92; var animateFrame = () => { if (Math.abs(initialVelocity) < minVelocity) return; this.addOffset({ x: initialVelocity * 16, y: 0 }); this.offset = this.sanitizeOffset(this.offset); this.update(); initialVelocity *= decay; requestAnimationFrame(animateFrame); }; requestAnimationFrame(animateFrame); }, handleZoomStart: function (event) { triggerEvent(this.el, this.options.zoomStartEventName); this.stopAnimation(); this.lastScale = 1; this.nthZoom = 0; this.lastZoomCenter = false; this.hasInteraction = true; }, handleZoom: function (event, newScale) { var touchCenter = this.getTouchCenter(this.getTouches(event)), scale = newScale / this.lastScale; this.lastScale = newScale; this.nthZoom += 1; if (this.nthZoom > 3) { this.scale(scale, touchCenter); this.drag(touchCenter, this.lastZoomCenter); } this.lastZoomCenter = touchCenter; }, handleZoomEnd: function () { triggerEvent(this.el, this.options.zoomEndEventName); this.end(); }, handleDoubleTap: function (event) { var center = this.getTouches(event)[0], zoomFactor = this.zoomFactor > 1 ? 1 : this.options.tapZoomFactor, startZoomFactor = this.zoomFactor, updateProgress = (function (progress) { this.scaleTo(startZoomFactor + progress * (zoomFactor - startZoomFactor), center); }).bind(this); if (this.hasInteraction) { return; } if (startZoomFactor > zoomFactor) { center = this.getCurrentZoomCenter(); } this.animate(this.options.animationDuration, updateProgress, this.swing); triggerEvent(this.el, this.options.doubleTapEventName); }, scaleTo: function (zoomFactor, center) { this.scale(zoomFactor / this.zoomFactor, center); }, scale: function (scale, center) { scale = this.scaleZoomFactor(scale); this.addOffset({ x: (scale - 1) * (center.x + this.offset.x), y: (scale - 1) * (center.y + this.offset.y) }); this.done && this.done.call(this, this.getInitialZoomFactor() * this.zoomFactor); }, scaleZoomFactor: function (scale) { var originalZoomFactor = this.zoomFactor; this.zoomFactor *= scale; this.zoomFactor = Math.min(this.options.maxZoom, Math.max(this.zoomFactor, this.options.minZoom)); return this.zoomFactor / originalZoomFactor; }, canDrag: function () { return this.options.draggableUnzoomed || !isCloseTo(this.zoomFactor, 1); }, drag: function (center, lastCenter, event) { if (!lastCenter) return; var dx = -(center.x - lastCenter.x) * this.options.dampingFactor; var dy = -(center.y - lastCenter.y) * this.options.dampingFactor; this.addOffset({ x: dx, y: dy }); }, getTouchCenter: function (touches) { return this.getVectorAvg(touches); }, getVectorAvg: function (vectors) { return { x: vectors.map(function (v) { return v.x; }).reduce(sum) / vectors.length, y: vectors.map(function (v) { return v.y; }).reduce(sum) / vectors.length }; }, addOffset: function (offset) { this.offset = { x: this.offset.x + offset.x, y: this.offset.y + offset.y }; }, sanitize: function () { if (this.zoomFactor < this.options.zoomOutFactor) { this.zoomOutAnimation(); } else if (this.isInsaneOffset(this.offset)) { this.sanitizeOffsetAnimation(); } }, isInsaneOffset: function (offset) { var sanitizedOffset = this.sanitizeOffset(offset); return sanitizedOffset.x !== offset.x || sanitizedOffset.y !== offset.y; }, sanitizeOffsetAnimation: function () { var targetOffset = this.sanitizeOffset(this.offset), startOffset = { x: this.offset.x, y: this.offset.y }, updateProgress = (function (progress) { this.offset.x = startOffset.x + progress * (targetOffset.x - startOffset.x); this.offset.y = startOffset.y + progress * (targetOffset.y - startOffset.y); this.update(); }).bind(this); this.animate( this.options.animationDuration, updateProgress, this.swing ); }, zoomOutAnimation: function () { var startZoomFactor = this.zoomFactor, zoomFactor = 1, center = this.getCurrentZoomCenter(), updateProgress = (function (progress) { this.scaleTo(startZoomFactor + progress * (zoomFactor - startZoomFactor), center); }).bind(this); this.animate( this.options.animationDuration, updateProgress, this.swing ); }, updateAspectRatio: function () { this.setContainerY(this.getContainerX() / this.getAspectRatio()); }, getInitialZoomFactor: function () { if (this.pinchContainer && this.el) { return this.pinchContainer.offsetWidth / this.el.offsetWidth; } else { return 0; } }, getAspectRatio: function () { if (this.el) { return this.pinchContainer.offsetWidth / this.el.offsetHeight; } else { return 0; } }, getCurrentZoomCenter: function () { var length = this.pinchContainer.offsetWidth * this.zoomFactor, offsetLeft = this.offset.x, offsetRight = length - offsetLeft - this.pinchContainer.offsetWidth, widthOffsetRatio = offsetLeft / offsetRight, centerX = widthOffsetRatio * this.pinchContainer.offsetWidth / (widthOffsetRatio + 1), height = this.pinchContainer.offsetHeight * this.zoomFactor, offsetTop = this.offset.y, offsetBottom = height - offsetTop - this.pinchContainer.offsetHeight, heightOffsetRatio = offsetTop / offsetBottom, centerY = heightOffsetRatio * this.pinchContainer.offsetHeight / (heightOffsetRatio + 1); if (offsetRight === 0) { centerX = this.pinchContainer.offsetWidth; } if (offsetBottom === 0) { centerY = this.pinchContainer.offsetHeight; } return { x: centerX, y: centerY }; }, getTouches: function (event) { var position = this.pinchContainer.getBoundingClientRect(); var posTop = position.top; var posLeft = position.left; return Array.prototype.slice.call(event.touches).map(function (touch) { return { x: touch.pageX - posLeft, y: touch.pageY - posTop, }; }); }, animate: function (duration, framefn, timefn, callback) { var startTime = new Date().getTime(), renderFrame = (function () { if (!this.inAnimation) { return; } var frameTime = new Date().getTime() - startTime, progress = frameTime / duration; if (frameTime >= duration) { framefn(1); if (callback) { callback(); } this.update(); this.stopAnimation(); } else { if (timefn) { progress = timefn(progress); } framefn(progress); this.update(); requestAnimationFrame(renderFrame); } }).bind(this); this.inAnimation = true; requestAnimationFrame(renderFrame); }, stopAnimation: function () { this.inAnimation = false; }, swing: function (p) { return -Math.cos(p * Math.PI) / 2 + 0.5; }, getContainerX: function () { if (this.el) { return this.el.offsetWidth; } else { return 0; } }, getContainerY: function () { return this.el.offsetHeight; }, setContainerY: function (y) { y = y.toFixed(2); return this.pinchContainer.style.height = y + 'px'; }, setupMarkup: function () { this.pinchContainer = buildElement('<div class="pinch-zoom-container"></div>'); this.el.parentNode.insertBefore(this.pinchContainer, this.el); this.pinchContainer.appendChild(this.el); this.pinchContainer.style.position = 'relative'; this.el.style.webkitTransformOrigin = '0% 0%'; this.el.style.mozTransformOrigin = '0% 0%'; this.el.style.msTransformOrigin = '0% 0%'; this.el.style.oTransformOrigin = '0% 0%'; this.el.style.transformOrigin = '0% 0%'; this.el.style.position = 'relative'; }, end: function () { this.hasInteraction = false; this.sanitize(); this.update(); }, bindEvents: function () { var self = this; detectGestures(this.pinchParentNode, this); this.resizeHandler = this.update.bind(this); window.addEventListener('resize', this.resizeHandler); Array.from(this.el.querySelectorAll('canvas')).forEach(function (imgEl) { self.update.bind(self); }); }, update: function () { if (this.updatePlaned) { return; } this.updatePlaned = true; setTimeout((function () { this.updatePlaned = false; this.updateAspectRatio(); var zoomFactor = this.getInitialZoomFactor() * this.zoomFactor, offsetX = (-this.offset.x / zoomFactor).toFixed(3), offsetY = (-this.offset.y / zoomFactor).toFixed(3); this.lastclientY = offsetY; var transform3d = 'scale3d(' + zoomFactor + ', ' + zoomFactor + ',1) ' + 'translate3d(' + offsetX + 'px,' + offsetY + 'px,0px)', transform2d = 'scale(' + zoomFactor + ', ' + zoomFactor + ') ' + 'translate(' + offsetX + 'px,' + offsetY + 'px)', removeClone = (function () { if (this.clone) { this.clone.remove(); delete this.clone; } }).bind(this); if (!this.options.use2d || this.hasInteraction || this.inAnimation) { this.is3d = true; this.el.style.webkitTransform = transform3d; this.el.style.mozTransform = transform2d; this.el.style.msTransform = transform2d; this.el.style.oTransform = transform2d; this.el.style.transform = transform3d; } else { this.el.style.webkitTransform = transform2d; this.el.style.mozTransform = transform2d; this.el.style.msTransform = transform2d; this.el.style.oTransform = transform2d; this.el.style.transform = transform2d; this.is3d = false; } this.lastOffsets.shift(); this.lastOffsets.push({ x: this.offset.x, y: this.offset.y, time: Date.now() }); }).bind(this), 0); }, enable: function () { this.enabled = true; }, disable: function () { this.enabled = false; }, destroy: function () { window.removeEventListener('resize', this.resizeHandler); if (this.pinchContainer) { var parentNode = this.pinchContainer.parentNode; var childNode = this.pinchContainer.firstChild; parentNode.appendChild(childNode); parentNode.removeChild(this.pinchContainer); } } }; var detectGestures = function (el, target) { var interaction = null, fingers = 0, lastTouchStart = null, startTouches = null, lastTouchY = null, clientY = null, lastclientY = 0, lastTop = 0, setInteraction = function (newInteraction, event) { if (interaction !== newInteraction) { if (interaction && !newInteraction) { switch (interaction) { case "zoom": target.handleZoomEnd(event); break; case 'drag': target.handleDragEnd(event); break; } } switch (newInteraction) { case 'zoom': target.handleZoomStart(event); break; case 'drag': target.handleDragStart(event); break; } } interaction = newInteraction; }, updateInteraction = function (event) { if (fingers === 2) { setInteraction('zoom'); } else if (fingers === 1 && target.canDrag()) { setInteraction('drag', event); } else { setInteraction(null, event); } }, targetTouches = function (touches) { return Array.prototype.slice.call(touches).map(function (touch) { return { x: touch.pageX, y: touch.pageY }; }); }, getDistance = function (a, b) { var x, y; x = a.x - b.x; y = a.y - b.y; return Math.sqrt(x * x + y * y); }, calculateScale = function (startTouches, endTouches) { var startDistance = getDistance(startTouches[0], startTouches[1]), endDistance = getDistance(endTouches[0], endTouches[1]); return endDistance / startDistance; }, cancelEvent = function (event) { event.stopPropagation(); event.preventDefault(); }, detectDoubleTap = function (event) { var time = (new Date()).getTime(); var pageY = event.changedTouches[0].pageY; var top = el.scrollTop || 0; if (fingers > 1) { lastTouchStart = null; lastTouchY = null; cancelEvent(event); } if (time - lastTouchStart < 300 && Math.abs(pageY - lastTouchY) < 10 && Math.abs(lastTop - top) < 10) { cancelEvent(event); target.handleDoubleTap(event); switch (interaction) { case "zoom": target.handleZoomEnd(event); break; case 'drag': target.handleDragEnd(event); break; } } if (fingers === 1) { lastTouchStart = time; lastTouchY = pageY; lastTop = top; } }, firstMove = true; el.addEventListener('touchstart', function (event) { if (target.enabled) { firstMove = true; fingers = event.touches.length; detectDoubleTap(event); clientY = event.changedTouches[0].clientY; if (fingers > 1) { cancelEvent(event); } } }); el.addEventListener('touchmove', function (event) { if (target.enabled) { lastclientY = event.changedTouches[0].clientY; if (firstMove) { updateInteraction(event); startTouches = targetTouches(event.touches); } else { switch (interaction) { case 'zoom': target.handleZoom(event, calculateScale(startTouches, targetTouches(event.touches))); break; case 'drag': target.handleDrag(event); break; } if (interaction) { target.update(lastclientY); } } if (fingers > 1) { cancelEvent(event); } firstMove = false; } }); el.addEventListener('touchend', function (event) { if (target.enabled) { fingers = event.touches.length; if (fingers > 1) { cancelEvent(event); } updateInteraction(event); } }); }; return PinchZoom; }; var PinchZoom = definePinchZoom(); var Pdfh5 = function (dom, options) { this.version = version; this.container = dom; this.options = options; this.thePDF = null; this.totalNum = null; this.pages = null; this.initTime = 0; this.currentNum = 1; this.loadedCount = 0; this.endTime = 0; this.pinchZoom = null; this.timer = null; this.docWidth = document.documentElement.clientWidth; this.cache = {}; this.eventType = {}; this.cacheNum = 1; this.resizeEvent = false; this.cacheData = null; this.pdfjsLibPromise = null; this.pdfjsLib = null; this.currentEditorMode = null; this.searchResults = []; this.currentSearchIndex = 0; this.currentSearchQuery = null; this.intersectionObserver = null; this.pageRenderStartTimes = {}; this.isZooming = false; this.zoomDisabled = false; this.scrollDisabled = false; this.zoomConstraints = { minScale: 0.5, maxScale: 4.0, step: 0.1 }; // 沙箱管理 this.sandboxManager = null; this.sandboxEnabled = true; // 密码保护相关 this.passwordPrompt = null; // 分段加载配置 this.progressiveLoading = false; this.chunkSize = 65536; // 64KB分块大小 this.maxMemoryPages = 5; // 最大内存页面数 this.loadedPages = new Map(); // 已加载页面缓存 this.loadingQueue = []; // 加载队列 this.memoryUsage = 0; // 内存使用量统计 // 编辑器相关属性 this.annotationEditorMode = 'NONE'; this.editorParams = { freeTextColor: '#000000', freeTextSize: 12, inkColor: '#000000', inkThickness: 1, inkOpacity: 1, stampImage: null }; this.editorAnnotations = []; // 存储所有注释 this.isEditing = false; this.selectedAnnotation = null; // 当前选择的注释 this.currentEditingTextInput = null; // 当前编辑的文本输入框 // 墨迹绘制相关属性 this.isDrawingInk = false; this.currentInkPath = null; // 注释编辑器UI管理器 this.annotationEditorUIManager = null; this.init(options); }; Pdfh5.prototype = { init: async function (options) { try { this.pdfjsLib = await initPdfJs(); // 初始化沙箱管理器 this.initSandbox(); } catch (error) { console.error('Failed to initialize PDF.js:', error); return; } if (this.container.pdfLoaded) { this.destroy(); } var $style = document.createElement('style'); $style.type = 'text/css'; $style.textContent = css; document.head.appendChild($style); this.container.pdfLoaded = false; this.container.classList.add("pdfjs"); this.initTime = new Date().getTime(); this.options = this.options ? this.options : {}; this.options.pdfurl = this.options.pdfurl ? this.options.pdfurl : null; this.options.data = this.options.data ? this.options.data : null; this.options.scale = this.options.scale ? this.options.scale : 1; this.options.zoomEnable = this.options.zoomEnable === false ? false : true; this.options.scrollEnable = this.options.scrollEnable === false ? false : true; this.options.loadingBar = this.options.loadingBar === false ? false : true; this.options.pageNum = this.options.pageNum === false ? false : true; // 密码配置项 this.options.password = this.options.password || null; this.options.backTop = this.options.backTop === false ? false : true; this.options.resize = this.options.resize === false ? false : true; this.options.textLayer = this.options.textLayer === true ? true : false; this.options.goto = isNaN(this.options.goto) ? 0 : this.options.goto; this.progressiveLoading = this.options.progressiveLoading === true ? true : false; if (this.options.chunkSize) this.chunkSize = this.options.chunkSize; if (this.options.maxMemoryPages) this.maxMemoryPages = this.options.maxMemoryPages; if (pdfjsLib && window._pdfh5ResourcePaths) { if (this.options.workerSrc) { pdfjsLib.GlobalWorkerOptions.workerSrc = this.options.workerSrc; } if (!this.options.cMapUrl) { this.options.cMapUrl = window._pdfh5ResourcePaths.cMapUrl; } if (!this.options.standardFontDataUrl) { this.options.standardFontDataUrl = window._pdfh5ResourcePaths.standardFontDataUrl; } if (!this.options.iccUrl) { this.options.iccUrl = window._pdfh5ResourcePaths.iccUrl; } if (!this.options.wasmUrl) { this.options.wasmUrl = window._pdfh5ResourcePaths.wasmUrl; } } this.createHTML(); this.bindEvents(); this.initEditorEvents(); this.loadPDF(); }, createHTML: function () { var html = ''; if (this.options.loadingBar) { html += '<div class="loadingBar">' + '<div class="progress">' + ' <div class="glimmer">' + '</div>' + ' </div>' + '</div>'; } html += '<div class="pageNum">' + '<div class="pageNum-bg"></div>' + ' <div class="pageNum-num">' + ' <span class="pageNow">1</span>/' + '<span class="pageTotal">1</span>' + '</div>' + ' </div>' + '<div class="backTop">' + '</div>' + '<div class="loadEffect loading"></div>'; this.container.innerHTML = html; var viewer = document.createElement("div"); viewer.className = 'pdfViewer'; var viewerContainer = document.createElement("div"); viewerContainer.className = 'viewerContainer'; viewerContainer.appendChild(viewer); this.container.appendChild(viewerContainer); this.viewer = viewer; this.viewerContainer = viewerContainer; this.pageNum = this.container.querySelector('.pageNum'); this.pageNow = this.pageNum.querySelector('.pageNow'); this.pageTotal = this.pageNum.querySelector('.pageTotal'); this.loadingBar = this.container.querySelector('.loadingBar'); if (this.loadingBar) { this.progress = this.loadingBar.querySelector('.progress'); } else { this.progress = null; } this.backTop = this.container.querySelector('.backTop'); this.loading = this.container.querySelector('.loading'); }, bindEvents: function () { var self = this; if (!this.options.scrollEnable) { this.viewerContainer.style.overflow = "hidden"; } else { this.viewerContainer.style.overflow = "auto"; } this.viewerContainer.addEventListener('scroll', function () { self.handleScroll(); }); this.backTop.addEventListener("click", function () { self.scrollToTop(); }); if (this.options.resize !== false) { var resizeTimeout; this.resizeHandler = function () { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(function () { if (self.container.pdfLoaded && self.thePDF) { self.updateAllPagesScale(); } }, 100); }; window.addEventListener('resize', this.resizeHandler); } }, updateAllPagesScale: function () { var self = this; if (!self.container.pdfLoaded || !self.thePDF) { return; } for (var pageNum = 1; pageNum <= self.totalNum; pageNum++) { var pageCache = self.cache[pageNum + ""]; if (pageCache && pageCache.container && pageCache.container.querySelector('canvas')) { self.updatePageScale(pageNum); } } }, updateVisiblePagesScale: function () { var self = this; if (!self.container.pdfLoaded || !self.thePDF) { return; } if (self.intersectionObserver) { var visiblePages = []; for (var pageNum = 1; pageNum <= self.totalNum; pageNum++) { var pageCache = self.cache[pageNum + ""]; if (pageCache && pageCache.container) { var rect = pageCache.container.getBoundingClientRect(); var containerRect = self.viewerContainer.getBoundingClientRect(); if (rect.top < containerRect.bottom && rect.bottom > containerRect.top) { visiblePages.push(pageNum); } } } visiblePages.forEach(function (pageNum) { var pageCache = self.cache[pageNum + ""]; if (pageCache && pageCache.container && pageCache.container.querySelector('canvas')) { self.updatePageScale(pageNum); } }); } else { self.updateAllPagesScale(); } }, updatePageScale: function (pageNum) { var self = this; var pageCache = self.cache[pageNum + ""]; if (!pageCache || !pageCache.container) { return; } if (self.loadingPages && self.loadingPages.has(pageNum)) { return; } if (!self.loadingPages) { self.loadingPages = new Set(); } self.loadingPages.add(pageNum); self.thePDF.getPage(pageNum).then(function (page) { var userScale = self.scale || 1.0; if (userScale === 1.0) { var baseViewport = page.getViewport({ scale: 1.0 }); var pageWidth = baseViewport.width; var pageHeight = baseViewport.height; var containerWidth = self.viewer.clientWidth || self.viewer.offsetWidth; var containerHeight = self.viewer.clientHeight || self.viewer.offsetHeight; // 修复PDF容器高度异常小的问题 if (containerHeight < 100) { // 如果容器高度异常小,使用窗口高度减去一些边距 containerHeight = window.innerHeight - 200; // 减去200px给其他元素留空间 } var SCROLLBAR_PADDING = 40; var VERTICAL_PADDING = 5; var hPadding = SCROLLBAR_PADDING; var vPadding = VERTICAL_PADDING; var pageWidthScaleFactor = 1; var currentPageScale = 1.0; var pageWidthScale = (containerWidth - hPadding) / pageWidth * currentPageScale / pageWidthScaleFactor; var pageHeightScale = (containerHeight - vPadding) / pageHeight * currentPageScale; var isPortrait = pageHeight > pageWidth; var horizontalScale = isPortrait ? pageWidthScale : Math.min(pageHeightScale, pageWidthScale); userScale = Math.min(1.25, horizontalScale); } self.renderPage(page, pageNum, { scale: userScale, forceRerender: true }, 100 / self.totalNum); var textLayerDiv = pageCache.container.querySelector('.textLayer'); if (textLayerDiv && textLayerDiv.textLayer) { var PDF_TO_CSS_UNITS = 96.0 / 72.0; // 1.333... var newViewport = page.getViewport({ scale: userScale * PDF_TO_CSS_UNITS }); var pageWidth = newViewport.rawDims.pageWidth; var pageHeight = newViewport.rawDims.pageHeight; textLayerDiv.textLayer.update({ viewport: newViewport }); var w = 'var(--total-scale-factor) * ' + pageWidth + 'px'; var h = 'var(--total-scale-factor) * ' + pageHeight + 'px'; textLayerDiv.style.width = w; textLayerDiv.style.height = h; textLayerDiv.style.transform = 'none'; textLayerDiv.style.transformOrigin = '0 0'; var containerScale = pageCache.container.style.getPropertyValue('--scale-factor') || newViewport.scale; textLayerDiv.style.setProperty('--scale-factor', containerScale); textLayerDiv.style.setProperty('--user-unit', '1'); textLayerDiv.style.setProperty('--total-scale-factor', 'calc(var(--scale-factor) * var(--user-unit))'); } if (self.loadingPages) { self.loadingPages.delete(pageNum); } }).catch(function (error) { console.error('Error updating page scale:', error); if (self.loadingPages) { self.loadingPages.delete(pageNum); } }); }, getContainerWidth: function (container) { var current = container; while (current && current !== document.body) { var computedStyle = window.getComputedStyle(current); var width = computedStyle.width; var maxWidth = computedStyle.maxWidth; if (width !== 'auto' && width !== '100%' && width !== '100vw') { var widthValue = parseFloat(width); if (widthValue > 0) { return widthValue; } } if (maxWidth !== 'none' && maxWidth !== '100%' && maxWidth !== '100vw') { var maxWidthValue = parseFloat(maxWidth); if (maxWidthValue > 0) { return maxWidthValue; } } current = current.parentElement; } var docWidth = document.documentElement.clientWidth; return docWidth; }, loadPDF: function () { var url = this.options.pdfurl; var data = this.options.data; if (url) { this.renderPdf(this.options, { url: url }); } else if (data) { this.renderPdf(this.options, { data: data }); } else { console.error("Expect options.pdfurl or options.data!"); } }, renderPdf: function (options, obj) { var self = this; this.container.pdfLoaded = true; // 字体配置 obj.cMapUrl = options.cMapUrl || (window._pdfh5ResourcePaths ? window._pdfh5ResourcePaths.cMapUrl : '../cmaps/'); obj.standardFontDataUrl = options.standardFontDataUrl || (window._pdfh5ResourcePaths ? window._pdfh5ResourcePaths.standardFontDataUrl : '../standard_fonts/'); // 颜色管理 obj.iccUrl = options.iccUrl || (window._pdfh5ResourcePaths ? window._pdfh5ResourcePaths.iccUrl : '../iccs/'); // 图片渲染 obj.wasmUrl = options.wasmUrl || (window._pdfh5ResourcePaths ? window._pdfh5ResourcePaths.wasmUrl : '../wasm/'); obj.cMapPacked = options.cMapPacked !== false; // 默认启用压缩的CMap obj.disableFontFace = options.disableFontFace === true; // 默认不禁用字体 obj.enableXfa = options.enableXfa !== false; // 默认启用XFA obj.enableHWA = options.enableHWA !== false; // 默认启用硬件加速,对图片渲染很重要 obj.verbosity = options.verbosity || 1; // 设置详细程度 if (options.httpHeaders) { obj.httpHeaders = options.httpHeaders; } if (options.withCredentials) { obj.withCredentials = true; } if (options.password) { obj.password = options.password; } // 分段加载配置 if (self.progressiveLoading) { obj.disableStream = false; obj.disableAutoFetch = false; obj.rangeChunkSize = self.chunkSize; obj.maxImageSize = options.maxImageSize || 8388608; // 8388608最大图片大小,兼容iOS Safari obj.canvasMaxAreaInBytes = options.canvasMaxAreaInBytes || 8388608; // 8388608最大canvas面积 iOS Safari浏览器canvas限制约为16777216 } this.pdfjsLib.getDocument(obj).promise.then(function (pdf) { self.loading.style.display = "none"; self.thePDF = pdf; self.totalNum = pdf.numPages; if (options.limit > 0) { self.totalNum = options.limit; } self.pageTotal.innerText = self.totalNum; if (!self.eventBus) { self.eventBus = self; } // 初始化AnnotationEditorUIManager( if (self.pdfjsLib.AnnotationEditorUIManager && self.thePDF.annotationStorage && self.eventBus) { try { self.annotationEditorUIManager = new self.pdfjsLib.AnnotationEditorUIManager( self.container, self.pdfViewer, null, // viewerAlert null, // altTextManager null, // commentManager null, // signatureManager self.eventBus, self.thePDF, null, // pageColors null, // highlightColors false, // enableHighlightFloatingButton false, // enableUpdatedAddImage false, // enableNewAltTextWhenAddingImage null, // mlManager null, // editorUndoBar false // supportsPinchToZoom ); setTimeout(function () { for (var i = 1; i <= self.totalNum; i++) { self.createAnnotationEditorLayer(i); } }, 100); } catch (error) { } } if (self.thePDF && self.thePDF.annotationStorage) { self.thePDF.annotationStorage.onSetModified = function () { }; self.thePDF.annotationStorage.onResetModified = function () { }; } // 如果正在验证密码且PDF加载成功,隐藏密码框 if (self.passwordValidating) { self.hidePasswordPrompt(); self.passwordValidating = false; // 重置标志 } self.trigger('ready', { totalPages: self.totalNum }); if (self.progressiveLoading) { self.initProgressiveLoading(pdf, options); } else if (options.lazyLoad) { // 懒加载模式:只渲染可见页面 self.initLazyLoading(pdf, options); } else { // 传统模式:渲染所有页面 self.renderAllPages(pdf, options); } }).catch(function (err) { self.loading.style.display = "none"; // 处理密码错误 if (err.name === 'PasswordException') { self.handlePasswordError(err); } else { console.error('PDF loading error:', err); self.trigger('error', { message: 'PDF加载失败: ' + err.message }); } }); }, renderAllPages: function (pdf, options) { var self = this; // 初始化缓存 for (var i = 1; i <= self.totalNum; i++) { self.cache[i + ""] = { page: null, loaded: false, container: null, scaledViewport: null, canvas: null, imgWidth: null }; } // 初始化当前页码 self.currentNum = 1; // 检查是否有goto配置 if (self.options.goto && self.options.goto > 0 && self.options.goto <= self.totalNum) { self.currentNum = self.options.goto; } // 传统模式:一次性加载所有页面 var promise = Promise.resolve(); var num = Math.floor(100 / self.totalNum).toFixed(2); // 渲染所有页面 for (var i = 1; i <= self.totalNum; i++) { promise = promise.then(function (pageNum) { return pdf.getPage(pageNum).then(function (page) { return self.renderPage(page, pageNum, options, num); }); }.bind(null, i)); } return promise.then(function () { // 初始化TouchManager - 更好的手势缩放 if (self.options.zoomEnable) { self.initTouchManager(); } }); }, // 预计算所有页面尺寸 preCalculateAllPageSizes: function (pdf, options) { var self = this; // 获取pdfViewer的实际尺寸 var pdfViewerWidth = self.viewer.clientWidth || self.viewer.offsetWidth; var pdfViewerHeight = self.viewer.clientHeight || self.viewer.offsetHeight; // 修复PDF容器高度异常小的问题 if (pdfViewerHeight < 100) { pdfViewerHeight = window.innerHeight - 200; } // 计算pdfViewer的实际可用宽度(减去padding) var pdfViewerStyle = window.getComputedStyle(self.viewer); var paddingLeft = parseFloat(pdfViewerStyle.paddingLeft) || 0; var paddingRight = parseFloat(pdfViewerStyle.paddingRight) || 0; var availableWidth = pdfViewerWidth - paddingLeft - paddingRight; // 预计算所有页面的尺寸 var pageSizePromises = []; for (var i = 1; i <= self.totalNum; i++) { pageSizePromises.push( pdf.getPage(i).then(function (pageNum) { return function (page) { return self.calculatePageSize(page, pageNum, availableWidth, pdfViewerHeight, options); }; }(i)) ); } return Promise.all(pageSizePromises).then(function (pageSizes) { // 存储所有页面的预计算尺寸 self.preCalculatedSizes = pageSizes; return pageSizes; }); }, // 计算单个页面的尺寸 calculatePageSize: function (page, pageNum, availableWidth, pdfViewerHeight, options) { var self = this; var PDF_TO_CSS_UNITS = 96.0 / 72.0; var userScale = options.scale || 1.0; // 获取PDF页面的基础尺寸 var baseViewport = page.getViewport({ scale: 1.0 }); var pageWidth = baseViewport.width; var pageHeight = baseViewport.height; // 如果用户没有指定缩放值,使用与renderPage完全一致的自动缩放逻辑 if (userScale === 1.0) { // 使用与renderPage完全一致的缩放计算逻辑 var SCROLLBAR_PADDING = 40; var VERTICAL_PADDING = 5; var hPadding = SCROLLBAR_PADDING; var vPadding = VERTICAL_PADDING; // 计算页面宽度缩放 var pageWidthScale = (availableWidth - hPadding) / pageWidth; var pageHeightScale = (pdfViewerHeight - vPadding) / pageHeight; // 使用官方的"auto"模式逻辑,与renderPage保持一致 var isPortrait = pageHeight > pageWidth; var horizontalScale = isPortrait ? pageWidthScale : Math.min(pageHeightScale, pageWidthScale); userScale = Math.min(1.25, horizontalScale); // MAX_AUTO_SCALE = 1.25 // 设置合理的最小缩放限制,确保内容可见 userScale = Math.max(userScale, 0.2); // 最小20% } // 计算最终的viewport var scaledViewport = page.getViewport({ scale: userScale * PDF_TO_CSS_UNITS }); // 确保pageContainer宽度不超过pdfViewer的可用宽度 var finalWidth = Math.min(scaledViewport.width, availableWidth); var finalHeight = scaledViewport.height; // 如果宽度被限制,按比例调整高度 if (finalWidth < scaledViewport.width) { var scaleRatio = finalWidth / scaledViewport.width; finalHeight = scaledViewport.height * scaleRatio; } return { pageNum: pageNum, width: finalWidth, height: finalHeight, scale: userScale, viewport: scaledViewport }; }, // 懒加载初始化 initLazyLoading: function (pdf, options) { var self = this; // 初始化内存管理 self.loadedPages = new Map(); self.maxMemoryPages = self.options.maxMemoryPages || 5; // 默认最多保留5页 // 初始化缓存 for (var i = 1; i <= self.totalNum; i++) { self.cache[i + ""] = { page: null, loaded: false, container: null, scaledViewport: null, canvas: null, imgWidth: null, pageHeight: 0, // 存储页面实际高度 pageTop: 0 // 存储页面顶部位置 }; } // 初始化当前页码 self.currentNum = 1; // 预计算所有页面尺寸,然后创建容器 self.preCalculateAllPageSizes(pdf, options).then(function () { // 使用预计算的尺寸创建页面容器占位符 for (var i = 1; i <= self.totalNum; i++) { self.createPageContainerWithPreCalculatedSize(i, options); } // 继续原有的懒加载逻辑 self.continueLazyLoading(pdf, options); }); }, // 使用预计算尺寸创建页面容器 createPageContainerWithPreCalculatedSize: function (pageNum, options) { var self = this; var container = document.createElement('div'); container.className = 'pageContainer pageContainer' + pageNum; container.setAttribute('name', 'page=' + pageNum); container.setAttribute('data-page', pageNum); // 使用预计算的尺寸 if (self.preCalculatedSizes && self.preCalculatedSizes[pageNum - 1]) { var pageSize = self.preCalculatedSizes[pageNum - 1]; container.style.width = pageSize.width + 'px'; container.style.height = pageSize.height + 'px'; container["data-scale"] = pageSize.width / pageSize.height; // 设置CSS变量 container.style.setProperty('--scale-factor', pageSize.viewport.scale); } else { // 如果没有预计算尺寸,使用默认尺寸 container.style.width = '100%'; container.style.height = 'auto'; container["data-scale"] = 1.0; } self.cache[pageNum + ""].container = container; self.viewer.appendChild(container); }, // 继续懒加载逻辑 continueLazyLoading: function (pdf, options) { var self = this; // 立即渲染第一页(确保有内容显示) if (self.totalNum > 0) { // 检查是否有goto配置 var startPage = 1; if (self.options.goto && self.options.goto > 0 && self.options.goto <= self.totalNum) { startPage = self.options.goto; self.currentNum = startPage; } self.renderPageLazy(pdf, startPage, options); // 如果有goto配置,延迟滚动到指定页面 if (self.options.goto && self.options.goto > 1) { setTimeo