1$(function () 2{ 3 let $allBrochureLinks = $(".brochure-init-link"); 4 let isMobile = $(window).width() < 768; 5 let zoomLevel = isMobile 6 ? 4 7 : 2; 8 let lastTapTime = 0; 9 let doubleTapDelay = 300; 10 11 $allBrochureLinks.each(function (index, brochureLink) 12 { 13 let $brochureLink = $(brochureLink); 14 let $brochure = buildInitialHtml($brochureLink); 15 initBrochure($brochure); 16 }); 17 18 function buildInitialHtml($link) 19 { 20 // Toolbar 21 let $linkClone = $link 22 .clone() 23 .text("") 24 .attr("download", "") 25 .addClass("btn download-button") 26 .append($("<i/>").addClass("fa fa-download").attr("aria-hidden", "true")); 27 let $printButton = $("<button/>") 28 .addClass("btn print-button") 29 .append($("<i/>").addClass("fa fa-print").attr("aria-hidden", "true")); 30 let $zoomButton = $("<button/>") 31 .addClass("btn zoom-button") 32 .append($("<i/>").addClass("fa fa-search-plus").attr("aria-hidden", "true")); 33 let $toolbar = $("<div/>") 34 .addClass("col-10 d-flex justify-content-center") 35 .css("gap", "1rem") 36 .append($linkClone) 37 .append($printButton) 38 .append($zoomButton); 39 let $toolbarRow = $("<div/>") 40 .css("visibility", "hidden") 41 .addClass("row") 42 .append($("<div/>").addClass("col-1")) 43 .append($toolbar) 44 .append($("<div/>").addClass("col-1")); 45 // Flipbook 46 let $prevButton = $("<button/>") 47 .addClass("btn previous-button") 48 .text("<"); 49 let $nextButton = $("<button/>") 50 .addClass("btn next-button") 51 .text(">"); 52 let $book = $("<div/>") 53 .addClass("col-10 book-wrapper") 54 .append($("<div/>").addClass("row") 55 .append($("<div/>").addClass("col-12") 56 .append($("<div/>").addClass("zoom-layer") 57 .append($("<div/>").addClass("flipbook")) 58 ) 59 ) 60 ); 61 let $bookRow = $("<div/>") 62 .css("visibility", "hidden") 63 .addClass("row") 64 .append($("<div/>").addClass("col-1").append($prevButton)) 65 .append($book) 66 .append($("<div/>").addClass("col-1").append($nextButton)); 67 // Slider 68 let $sliderInput = $("<input/>") 69 .attr({ 70 "id": "flipbook-page-slider", 71 "type": "range", 72 "min": "1", 73 "value": "1", 74 "step": "1" 75 }) 76 .addClass("form-control form-control-range page-slider"); 77 let $sliderLabel = $("<label/>") 78 //.text("1") 79 .attr("for", "flipbook-page-slider") 80 .addClass("page-slider-label text-center d-block w-100"); 81 let $slider = $("<div/>") 82 .addClass("col-10") 83 .append($sliderInput) 84 .append($sliderLabel); 85 let $sliderRow = $("<div/>") 86 .css("visibility", "hidden") 87 .addClass("row") 88 .append($("<div/>").addClass("col-1")) 89 .append($slider) 90 .append($("<div/>").addClass("col-1")); 91 // Loading 92 let $loading = $("<div/>") 93 .addClass("alert alert-primary") 94 .append($("<span/>") 95 .addClass("spinner-border spinner-border-sm mr-2 d-inline-block") 96 .attr({ 97 "role": "status", 98 "aria-hidden": "true" 99 })) 100 .append($("<span/>").html("ÐаÑежда Ñе PDF докÑÐ¼ÐµÐ½Ñ <span class='loading-info'>...</span>")) 101 .append($("<div/>").addClass("pdf-loader-bar")); 102 // Final Container 103 let $brochure = $("<div/>") 104 .addClass("container-fluid brochure") 105 .append($loading) 106 .append($toolbarRow) 107 .append($bookRow) 108 .append($sliderRow); 109 $link.after($brochure); 110 $link.remove(); 111 return $brochure; 112 } 113 114 function initBrochure($brochure) 115 { 116 let url = $brochure.find(".download-button").attr("href"); 117 118 if (!url) 119 return; 120 121 fetch(url) 122 .then(response => 123 { 124 if (!response.ok) 125 throw new Error(`Failed to load PDF for URL ${url}`); 126 return response.blob(); 127 }) 128 .then(blob => 129 { 130 setupPrint($brochure, blob); 131 return blob.arrayBuffer(); 132 }) 133 .then(buffer => pdfjsLib.getDocument(buffer).promise) 134 .then(pdf => buildPageStructure($brochure, pdf)) 135 .then(pdf => setupFlipBook($brochure, pdf)) 136 .catch(err => console.error(err)); 137 } 138 139 function setupPrint($brochure, blob) 140 { 141 $brochure.find(".print-button").on("click", function () 142 { 143 let $printFrame = $brochure.find(".print-frame"); 144 145 if (!$printFrame.length) 146 { 147 let blobUrl = URL.createObjectURL(blob); 148 $printFrame = $("<iframe/>") 149 .attr("src", blobUrl) 150 .addClass("d-none print-frame"); 151 $printFrame.on("load", function () 152 { 153 setTimeout(function () 154 { 155 $printFrame.focus(); 156 let window = $printFrame[0].contentWindow; 157 window.print(); 158 }, 1000); 159 }); 160 $brochure.append($printFrame); 161 } 162 else 163 { 164 setTimeout(function () 165 { 166 $printFrame.focus(); 167 let window = $printFrame[0].contentWindow; 168 window.print(); 169 }, 1000); 170 } 171 }); 172 } 173 174 function buildPageStructure($brochure, pdf) 175 { 176 let $flipbook = $brochure.find(".flipbook"); 177 let chain = Promise.resolve(); 178 179 $brochure.find(".alert").fadeOut(); 180 $brochure.find(".row").css("visibility", "visible"); 181 182 for (let i = 1; i <= pdf.numPages; ++i) 183 { 184 chain = chain.then(() => pdf.getPage(i).then(function (page) 185 { 186 let pageInfo = getPageInfo(page, $flipbook); 187 188 let $canvas = $("<canvas/>") 189 .attr({ 190 "width": pageInfo.canvasHtmlWidth, 191 "height": pageInfo.canvasHtmlHeight 192 }) 193 .css({ 194 "width": pageInfo.canvasCssWidth + "px", 195 "height": pageInfo.canvasCssHeight + "px" 196 }) 197 .addClass(`original-${i}`) 198 .attr("data-original", i); 199 200 let $pageDiv = $("<div/>") 201 .addClass("cpage") 202 .css({ 203 width: pageInfo.canvasCssWidth + "px", 204 height: pageInfo.canvasCssHeight + "px", 205 overflow: "hidden" 206 }) 207 .append($canvas); 208 209 $flipbook.append($pageDiv); 210 211 if (pageInfo.isLandscape) 212 { 213 $pageDiv.addClass("double"); 214 canvasScissor($pageDiv, false); 215 } 216 })); 217 } 218 219 return chain.then(function () 220 { 221 let $doubles = $brochure.find(".double"); 222 let $pages = $brochure.find(".cpage"); 223 224 if ($doubles.length && $pages.length % 2 === 0) 225 { 226 $pages 227 .first() 228 .after($("<div/>") 229 .addClass("cpage blank") 230 .css({ 231 "width": ($flipbook.width() / 2) + "px", 232 "height": "100%", "text-align": "center", 233 "padding-top": "2rem" 234 }) 235 .text("Тази ÑÑÑаниÑа е наÑоÑно пÑазна")); 236 } 237 return pdf; 238 }); 239 } 240 241 function canvasScissor($obj, copyGraphicsData) 242 { 243 $obj.each(function () 244 { 245 let element = $(this); 246 247 if (element.is(".scissored")) 248 return; // continue 249 250 element.addClass("scissored"); 251 252 let pageProperties = 253 { 254 width: element.width() / 2, 255 height: element.height(), 256 overflow: "hidden" 257 }; 258 259 let newElement = element.clone(true); 260 let leftPage = $("<div/>", { css: pageProperties }); 261 let rightPage = $("<div/>", { css: pageProperties }); 262 let originalCanvas = element.find("canvas")[0]; 263 let clonedCanvas = cloneCanvasWithData(originalCanvas, copyGraphicsData); 264 265 newElement.find("canvas").remove(); 266 newElement.append(clonedCanvas); 267 element.after(leftPage); 268 leftPage.after(rightPage); 269 270 element.css("margin-left", 0).appendTo(leftPage); 271 newElement.css("margin-left", -pageProperties.width + "px").appendTo(rightPage); 272 }); 273 274 return $obj; 275 } 276 277 function cloneCanvasWithData(canvas, copyGfxData) 278 { 279 let clone = canvas.cloneNode(true); 280 clone.width = canvas.width; 281 clone.height = canvas.height; 282 clone.style.width = canvas.style.width; 283 clone.style.height = canvas.style.height; 284 285 if (copyGfxData) 286 { 287 let context = clone.getContext("2d"); 288 context.drawImage(canvas, 0, 0); 289 } 290 291 return clone; 292 } 293 294 function getPageInfo(page, $flipbook) 295 { 296 let viewport = page.getViewport({ scale: 1 }); 297 let isLandscape = viewport.width > viewport.height; 298 let pixelRatio = Math.max(window.devicePixelRatio || 1, 2); 299 let desiredWidth = (isMobile 300 ? $flipbook.width() 301 : $flipbook.width() / 2); 302 let scale = isLandscape
303 ? desiredWidth / (viewport.width / 2) 304 : desiredWidth / viewport.width; 305 let scaledViewport = page.getViewport({ scale: scale }); 306 let transform = [pixelRatio, 0, 0, pixelRatio, 0, 0]; 307 308 return { 309 canvasHtmlWidth: Math.floor(scaledViewport.width * pixelRatio), 310 canvasHtmlHeight: Math.floor(scaledViewport.height * pixelRatio), 311 canvasCssWidth: Math.floor(scaledViewport.width), 312 canvasCssHeight: Math.floor(scaledViewport.height), 313 transform: transform, 314 isLandscape: isLandscape, 315 scaledViewport: scaledViewport, 316 pixelRatio: pixelRatio 317 }; 318 } 319 320 function setupFlipBook($brochure, pdf) 321 { 322 let $flipbook = $brochure.find(".flipbook"); 323 let $slider = $brochure.find(".page-slider"); 324 let $pages = $brochure.find(".cpage"); 325 let $firstPage = $pages.first(); 326 327 $flipbook.turn({ 328 width: $firstPage.width() * 2, 329 height: $firstPage.height(), 330 autoCenter: true, 331 display: isMobile 332 ? "single" 333 : "double" 334 }); 335 336 $slider.attr("max", $pages.length); 337 338 setupEvents(pdf, $brochure, $flipbook, $slider); 339 340 $brochure.find(".loading-info").text(`Ñ ${pdf.numPages} ÑÑÑаниÑи. Това може да оÑнеме нÑколко ÑекÑнди...`); 341 342 renderAroundPage(pdf, 1, $flipbook); 343 } 344 345 function setupEvents(pdf, $brochure, $flipbook, $slider) 346 { 347 let $wrapper = $brochure.find(".book-wrapper"); 348 //let $sliderLabel = $brochure.find(".page-slider-label"); 349 let isDragging = false; 350 let startX, startY; 351 let scrollLeft, scrollTop; 352 353 // Shutdown their touch events so they don't interfere with ours. 354 $flipbook.off("touchstart touchmove touchend"); 355 $brochure.find(".next-button").on("click", function () 356 { 357 $flipbook.turn("next"); 358 }); 359 $brochure.find(".previous-button").on("click", function () 360 { 361 $flipbook.turn("previous"); 362 }); 363 $flipbook.bind("turned", function (event, page) 364 { 365 renderAroundPage(pdf, page, $flipbook); 366 $slider.val(page); 367 //$sliderLabel.text(page); 368 }); 369 $slider.on("input", function () 370 { 371 let pageNum = parseInt($slider.val()); 372 $flipbook.turn("page", pageNum); 373 renderAroundPage(pdf, pageNum, $flipbook); 374 //$sliderLabel.text(pageNum); 375 }); 376 $brochure.find(".zoom-button").on("click", function () 377 { 378 applyZoom($brochure, $wrapper, $flipbook); 379 }); 380 // start drag 381 $wrapper.on("mousedown touchstart", function (e) 382 { 383 if (!isZoomed($flipbook)) 384 return; 385 386 e.preventDefault(); 387 isDragging = true; 388 $wrapper.css("cursor", "grabbing"); 389 390 let coords = getEventCoords(e); 391 startX = coords.x; 392 startY = coords.y; 393 394 scrollLeft = $wrapper.scrollLeft(); 395 scrollTop = $wrapper.scrollTop(); 396 }); 397 // pan 398 $wrapper.on("mousemove touchmove", function (e) 399 { 400 if (!isDragging) 401 return; 402 403 e.preventDefault(); 404 405 let coords = getEventCoords(e); 406 let deltaX = coords.x - startX; 407 let deltaY = coords.y - startY; 408 409 $wrapper.scrollLeft(scrollLeft - deltaX); 410 $wrapper.scrollTop(scrollTop - deltaY); 411 }); 412 // stop drag 413 $wrapper.on("mouseup touchend touchcancel", function () 414 { 415 if (!isDragging) 416 return; 417 418 isDragging = false; 419 $wrapper.css("cursor", "default"); 420 }); 421 $wrapper.on("touchend", function (e) { 422 let now = new Date().getTime(); 423 424 if (now - lastTapTime < doubleTapDelay) { 425 // double tap detected 426 // get tab coords to zoom to
427 let coords = getEventCoords(e); 428 let offset = $wrapper.offset(); 429 let x = coords.x - offset.left; 430 let y = coords.y - offset.top; 431 let oldScale; 432 let newScale; 433 if (!isZoomed($flipbook)) 434 { 435 oldScale = 1; 436 newScale = zoomLevel; 437 } else { 438 oldScale = zoomLevel; 439 newScale = 1; 440 } 441 applyZoom($brochure, $wrapper, $flipbook); 442 $wrapper.scrollLeft( 443 ($wrapper.scrollLeft() + x) * newScale / oldScale - x 444 ); 445 $wrapper.scrollTop( 446 ($wrapper.scrollTop() + y) * newScale / oldScale - y 447 ); 448 e.preventDefault(); 449 } else { 450 lastTapTime = now; 451 } 452 }); 453 } 454 455 function getEventCoords(e) 456 { 457 if (e.type.startsWith("touch")) 458 { 459 return { 460 x: e.originalEvent.touches.length > 0 461 ? e.originalEvent.touches[0].pageX 462 : e.originalEvent.changedTouches[0].pageX, 463 y: e.originalEvent.touches.length > 0 464 ? e.originalEvent.touches[0].pageY 465 : e.originalEvent.changedTouches[0].pageY, 466 }; 467 } 468 else 469 { 470 return { 471 x: e.pageX, 472 y: e.pageY 473 }; 474 } 475 } 476 477 function applyZoom($brochure, $wrapper, $flipbook) 478 { 479 if (!isZoomed($flipbook)) 480 { 481 $flipbook.parent().css("transform", `scale(${zoomLevel})`); 482 $brochure.find(".zoom-button").find(".fa").removeClass("fa-search-plus").addClass("fa-search-minus"); 483 $wrapper.css("overflow", "auto"); 484 } 485 else 486 { 487 $flipbook.parent().css("transform", "none"); 488 $brochure.find(".zoom-button").find(".fa").removeClass("fa-search-minus").addClass("fa-search-plus"); 489 $wrapper.css("overflow", "hidden"); 490 } 491 } 492 493 function isZoomed($flipbook) 494 { 495 return $flipbook.parent().css("transform") !== "none"; 496 } 497 498 function renderAroundPage(pdf, pageNum, $flipbook) 499 { 500 let pageClasses = [ 501 ".p" + (pageNum - 3), 502 ".p" + (pageNum - 2), 503 ".p" + (pageNum - 1), 504 ".p" + (pageNum), 505 ".p" + (pageNum + 1), 506 ".p" + (pageNum + 2), 507 ".p" + (pageNum + 3) 508 ]; 509 let $pagesToRender = $flipbook.find(pageClasses.join(",")); 510 let chain = Promise.resolve(); 511 512 $pagesToRender.each(function (index, page) 513 { 514 chain = chain.then(() => renderPage(pdf, $(page), $flipbook)); 515 }); 516 } 517 518 function renderPage(pdf, $page, $flipbook) 519 { 520 if ($page.is(".blank")) 521 return; 522 523 let $canvas = $page.find("canvas"); 524 let originalPageIndex = $canvas.first().data("original"); 525 let $canvases = $flipbook.find(`.original-${originalPageIndex}`); 526 527 if ($canvases.length === $canvases.find(".rendered").length) 528 return; 529 530 return pdf.getPage(originalPageIndex).then(page => 531 { 532 let pageInfo = getPageInfo(page, $flipbook); 533 let tasks = []; 534 535 $canvases.each(function (index, canvas) 536 { 537 let $canvas = $(canvas); 538 $canvas.parent().css("height", pageInfo.canvasCssHeight + "px"); 539 540 if ($canvas.is(".rendered") || $canvas.is(".rendering")) 541 return; // continue 542 543 $canvas.addClass("rendering"); 544 let context = canvas.getContext("2d"); 545 let renderObject = 546 { 547 canvasContext: context, 548 viewport: pageInfo.scaledViewport, 549 transform: pageInfo.transform 550 }; 551 let task = page 552 .render(renderObject) 553 .promise 554 .then(() => $canvas.removeClass("rendering").addClass("rendered")); 555 tasks.push(task); 556 }) 557 558 return Promise.all(tasks); 559 }); 560 } 561});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.