pdfh5
Version:
JS plugin of preview PDF for mobile. web/h5/移动端PDF预览手势缩放插件
1,454 lines (1,258 loc) • 151 kB
JavaScript
; (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