1import * as pdfjsLib from "https://cdn.jsdelivr.net/npm/[email protected]/build/pdf.mjs"; 2 3pdfjsLib.GlobalWorkerOptions.workerSrc = "https://cdn.jsdelivr.net/npm/[email protected]/build/pdf.worker.mjs"; 4 5console.log( 6 "%cMANUAL DE OPERAÃÃES ANONIMAS", 7 "color:#ffffff;background:#06111f;font-size:28px;font-weight:900;letter-spacing:2px;padding:10px 14px;border:1px solid #38e8ff;border-radius:6px;text-shadow:0 0 12px rgba(56,232,255,.75);", 8); 9 10const pdfUrl = document.body.dataset.pdf; 11const canvas = document.querySelector("#pdf-canvas"); 12const contexto = canvas.getContext("2d"); 13const area = document.querySelector("#canvas-area"); 14const estado = document.querySelector("#estado-pdf"); 15const campoPagina = document.querySelector("#pagina-atual"); 16const totalPaginas = document.querySelector("#total-paginas"); 17const zoomValor = document.querySelector("#zoom-valor"); 18const botoes = document.querySelectorAll("[data-acao]"); 19const elementosSemSelecao = document.querySelectorAll(".leitor-topo, .visualizador, #pdf-canvas"); 20const detalhesSobreManual = document.querySelector("[data-sobre-manual-detalhes]"); 21const menuContexto = document.createElement("div"); 22let alvoContexto = null; 23 24let pdf = null; 25let paginaAtual = 1; 26let zoom = 0.45; 27let escala = 1; 28let renderTask = null; 29let distanciaPinch = null; 30let zoomPinch = zoom; 31let rafPinch = null; 32 33function limitar(valor, minimo, maximo) { 34 return Math.min(Math.max(valor, minimo), maximo); 35} 36 37function somenteDigitos(valor) { 38 return String(valor).replace(/\D/g, ""); 39} 40 41function obterNumeroPagina(valor, paginaReserva = paginaAtual) { 42 const digitos = somenteDigitos(valor); 43 44 if (!digitos) { 45 return limitar(paginaReserva, 1, pdf ? pdf.numPages : paginaReserva); 46 } 47 48 const numero = Number.parseInt(digitos, 10); 49 50 if (!Number.isSafeInteger(numero)) { 51 return limitar(paginaReserva, 1, pdf ? pdf.numPages : paginaReserva); 52 } 53 54 return limitar(numero, 1, pdf ? pdf.numPages : numero); 55} 56 57function atualizarControles() { 58 campoPagina.value = paginaAtual; 59 campoPagina.maxLength = pdf ? String(pdf.numPages).length : "5"; 60 totalPaginas.textContent = pdf ? pdf.numPages : "--"; 61 zoomValor.textContent = `${Math.round(zoom * 100)}%`; 62} 63 64function obterEscalaAjustada(pagina) { 65 const viewportBase = pagina.getViewport({ scale: 1 }); 66 const estilos = getComputedStyle(area); 67 const paddingHorizontal = parseFloat(estilos.paddingLeft) + parseFloat(estilos.paddingRight); 68 const larguraDisponivel = Math.max(area.clientWidth - paddingHorizontal, 320); 69 const escalaBase = larguraDisponivel / viewportBase.width; 70 71 return limitar(escalaBase * zoom, 0.35, 4.5); 72} 73 74async function renderizarPagina(numero) { 75 if (!pdf) { 76 return; 77 } 78 79 if (renderTask) { 80 renderTask.cancel(); 81 } 82 83 estado.textContent = "Renderizando página..."; 84 85 try { 86 const centroAnterior = area.scrollWidth > area.clientWidth 87 ? (area.scrollLeft + area.clientWidth / 2) / area.scrollWidth 88 : 0.5; 89 const pagina = await pdf.getPage(numero); 90 escala = obterEscalaAjustada(pagina); 91 const viewport = pagina.getViewport({ scale: escala }); 92 const proporcao = window.devicePixelRatio || 1; 93 94 canvas.width = Math.floor(viewport.width * proporcao); 95 canvas.height = Math.floor(viewport.height * proporcao); 96 canvas.style.width = `${Math.floor(viewport.width)}px`; 97 canvas.style.height = `${Math.floor(viewport.height)}px`; 98 area.classList.toggle("canvas-area--rolagem-x", viewport.width > area.clientWidth); 99 100 const transform = proporcao !== 1 ? [proporcao, 0, 0, proporcao, 0, 0] : null; 101 renderTask = pagina.render({ canvasContext: contexto, viewport, transform }); 102 await renderTask.promise; 103 renderTask = null; 104 area.scrollLeft = Math.max(0, area.scrollWidth * centroAnterior - area.clientWidth / 2); 105 estado.textContent = ""; 106 atualizarControles(); 107 } catch (erro) { 108 if (erro?.name === "RenderingCancelledException") { 109 return; 110 } 111 112 estado.textContent = "Não foi possÃvel renderizar esta página."; 113 } 114} 115 116function irParaPagina(numero) { 117 if (!pdf) { 118 return; 119 } 120 121 paginaAtual = obterNumeroPagina(numero); 122 renderizarPagina(paginaAtual); 123} 124 125function ajustarZoom(valor) { 126 zoom = limitar(Number((zoom + valor).toFixed(2)), 0.25, 3.2); 127 renderizarPagina(paginaAtual); 128} 129 130function distanciaEntreToques(toques) { 131 const [primeiro, segundo] = toques; 132 return Math.hypot(primeiro.clientX - segundo.clientX, primeiro.clientY - segundo.clientY); 133} 134 135function aplicarZoomPinch(proximoZoom) { 136 zoom = limitar(Number(proximoZoom.toFixed(2)), 0.25, 3.2); 137 138 if (rafPinch) { 139 cancelAnimationFrame(rafPinch); 140 } 141 142 rafPinch = requestAnimationFrame(() => { 143 renderizarPagina(paginaAtual); 144 rafPinch = null; 145 }); 146} 147 148function abrirSobreManual() { 149 if (!detalhesSobreManual) { 150 return; 151 } 152 153 detalhesSobreManual.open = true; 154 detalhesSobreManual.scrollIntoView({ behavior: "smooth", block: "start" }); 155 detalhesSobreManual.querySelector("summary")?.focus({ preventScroll: true }); 156} 157 158function ocultarMenuContexto() { 159 menuContexto.classList.remove("ativo"); 160 menuContexto.setAttribute("aria-hidden", "true"); 161 alvoContexto = null; 162} 163 164function criarItemContexto(rotulo, acao) { 165 const botao = document.createElement("button"); 166 botao.type = "button"; 167 botao.textContent = rotulo; 168 botao.dataset.contextoAcao = acao; 169 botao.setAttribute("role", "menuitem"); 170 171 return botao; 172} 173 174function criarSeparadorContexto() { 175 const separador = document.createElement("span"); 176 separador.className = "menu-contexto__separador"; 177 separador.setAttribute("aria-hidden", "true"); 178 179 return separador; 180} 181 182function posicionarMenuContexto(evento) { 183 const margem = 10; 184 const retangulo = menuContexto.getBoundingClientRect(); 185 const esquerda = Math.min(evento.clientX, window.innerWidth - retangulo.width - margem); 186 const topo = Math.min(evento.clientY, window.innerHeight - retangulo.height - margem); 187 188 menuContexto.style.left = `${Math.max(margem, esquerda)}px`; 189 menuContexto.style.top = `${Math.max(margem, topo)}px`; 190} 191 192function mostrarMenuContexto(evento) { 193 const sobrePdf = evento.target === canvas; 194 const sobreDownload = evento.target.closest("a[download]"); 195 const sobreCampoPagina = evento.target === campoPagina;
196 const sobreControle = evento.target.closest(".toolbar"); 197 198 evento.preventDefault(); 199 alvoContexto = { sobrePdf, sobreDownload, sobreCampoPagina, sobreControle }; 200 menuContexto.replaceChildren(); 201 202 if (sobrePdf) { 203 menuContexto.append( 204 criarItemContexto("Página anterior", "pagina-anterior"), 205 criarItemContexto("Próxima página", "pagina-proxima"), 206 criarSeparadorContexto(), 207 criarItemContexto("Aumentar zoom", "zoom-mais"), 208 criarItemContexto("Diminuir zoom", "zoom-menos"), 209 criarSeparadorContexto(), 210 criarItemContexto("Salvar página como imagem", "salvar-imagem"), 211 criarItemContexto("Copiar imagem da página", "copiar-imagem"), 212 criarSeparadorContexto(), 213 criarItemContexto("Baixar manual", "baixar"), 214 criarItemContexto("Copiar endereço desta página", "copiar-pagina"), 215 ); 216 } else if (sobreDownload) { 217 menuContexto.append( 218 criarItemContexto("Baixar manual", "baixar"), 219 criarItemContexto("Abrir PDF em nova aba", "abrir-pdf"), 220 criarItemContexto("Copiar link do PDF", "copiar-pdf"), 221 ); 222 } else if (sobreCampoPagina) { 223 menuContexto.append( 224 criarItemContexto("Focar campo da página", "focar-pagina"), 225 criarItemContexto("Ir para primeira página", "primeira-pagina"), 226 criarItemContexto("Ir para última página", "ultima-pagina"), 227 ); 228 } else if (sobreControle) { 229 menuContexto.append( 230 criarItemContexto("Página anterior", "pagina-anterior"), 231 criarItemContexto("Próxima página", "pagina-proxima"), 232 criarSeparadorContexto(), 233 criarItemContexto("Aumentar zoom", "zoom-mais"), 234 criarItemContexto("Diminuir zoom", "zoom-menos"), 235 criarItemContexto("Baixar manual", "baixar"), 236 criarItemContexto("Voltar ao site", "voltar-site"), 237 ); 238 } else { 239 menuContexto.append( 240 criarItemContexto("Página anterior", "pagina-anterior"), 241 criarItemContexto("Próxima página", "pagina-proxima"), 242 criarSeparadorContexto(), 243 criarItemContexto("Baixar manual", "baixar"), 244 criarItemContexto("Voltar ao site", "voltar-site"), 245 criarItemContexto("Copiar endereço desta página", "copiar-pagina"), 246 ); 247 } 248 249 menuContexto.classList.add("ativo"); 250 menuContexto.setAttribute("aria-hidden", "false"); 251 posicionarMenuContexto(evento); 252} 253 254async function copiarTexto(texto) { 255 if (!texto) { 256 return; 257 } 258 259 try { 260 await navigator.clipboard.writeText(texto); 261 } catch (erro) { 262 const campo = document.createElement("textarea"); 263 campo.value = texto; 264 campo.style.position = "fixed"; 265 campo.style.opacity = "0"; 266 document.body.appendChild(campo); 267 campo.select(); 268 document.execCommand("copy"); 269 campo.remove(); 270 } 271} 272 273function baixarImagemPagina() { 274 const link = document.createElement("a"); 275 link.download = `manual-pagina-${paginaAtual}.png`; 276 link.href = canvas.toDataURL("image/png"); 277 link.click(); 278} 279 280async function copiarImagemPagina() { 281 if (!("ClipboardItem" in window) || !navigator.clipboard?.write) { 282 copiarTexto(`Página ${paginaAtual} do manual`); 283 return; 284 } 285 286 canvas.toBlob(async (blob) => { 287 if (!blob) { 288 return; 289 } 290 291 await navigator.clipboard.write([ 292 new ClipboardItem({ [blob.type]: blob }), 293 ]); 294 }, "image/png"); 295} 296 297menuContexto.className = "menu-contexto"; 298menuContexto.setAttribute("role", "menu"); 299menuContexto.setAttribute("aria-hidden", "true"); 300document.body.appendChild(menuContexto); 301 302botoes.forEach((botao) => { 303 botao.addEventListener("click", () => { 304 const acao = botao.dataset.acao; 305 306 if (acao === "anterior") { 307 irParaPagina(paginaAtual - 1); 308 } 309 310 if (acao === "proxima") { 311 irParaPagina(paginaAtual + 1); 312 } 313 314 if (acao === "menos-zoom") { 315 ajustarZoom(-0.1); 316 } 317 318 if (acao === "mais-zoom") { 319 ajustarZoom(0.1); 320 } 321 }); 322}); 323 324if (window.location.hash === "#sobre-manual") { 325 abrirSobreManual(); 326} 327 328menuContexto.addEventListener("click", (evento) => { 329 const item = evento.target.closest("[data-contexto-acao]"); 330 331 if (!item) { 332 return; 333 } 334 335 const acao = item.dataset.contextoAcao; 336 337 if (acao === "pagina-anterior") { 338 irParaPagina(paginaAtual - 1); 339 } 340 341 if (acao === "pagina-proxima") { 342 irParaPagina(paginaAtual + 1); 343 } 344 345 if (acao === "zoom-mais") { 346 ajustarZoom(0.1); 347 } 348 349 if (acao === "zoom-menos") { 350 ajustarZoom(-0.1); 351 } 352 353 if (acao === "salvar-imagem") { 354 baixarImagemPagina(); 355 } 356 357 if (acao === "copiar-imagem") { 358 copiarImagemPagina(); 359 } 360 361 if (acao === "baixar") { 362 document.querySelector("a[download]")?.click(); 363 } 364 365 if (acao === "abrir-pdf") { 366 window.open(pdfUrl, "_blank", "noopener"); 367 } 368 369 if (acao === "copiar-pdf") { 370 copiarTexto(new URL(pdfUrl, window.location.href).href); 371 } 372 373 if (acao === "focar-pagina") { 374 campoPagina.focus(); 375 campoPagina.select(); 376 } 377 378 if (acao === "primeira-pagina") { 379 irParaPagina(1); 380 } 381 382 if (acao === "ultima-pagina" && pdf) { 383 irParaPagina(pdf.numPages); 384 } 385 386 if (acao === "copiar-pagina") { 387 copiarTexto(window.location.href); 388 } 389 390 if (acao === "voltar-site") { 391 window.location.href = "/"; 392 } 393 394 ocultarMenuContexto(); 395}); 396 397document.addEventListener("contextmenu", mostrarMenuContexto); 398document.addEventListener("click", ocultarMenuContexto); 399window.addEventListener("blur", ocultarMenuContexto); 400window.addEventListener("resize", ocultarMenuContexto); 401window.addEventListener("scroll", ocultarMenuContexto, { passive: true }); 402window.addEventListener("keydown", (evento) => { 403 if (evento.key === "Escape") { 404 ocultarMenuContexto(); 405 } 406}); 407
408elementosSemSelecao.forEach((elemento) => { 409 elemento.addEventListener("selectstart", (evento) => { 410 if (evento.target === campoPagina) { 411 return; 412 } 413 414 evento.preventDefault(); 415 }); 416 417 elemento.addEventListener("dragstart", (evento) => { 418 evento.preventDefault(); 419 }); 420}); 421 422campoPagina.addEventListener("beforeinput", (evento) => { 423 if (evento.inputType.startsWith("delete") || !evento.data) { 424 return; 425 } 426 427 if (/\D/.test(evento.data)) { 428 evento.preventDefault(); 429 } 430}); 431 432campoPagina.addEventListener("input", () => { 433 const valorSeguro = somenteDigitos(campoPagina.value); 434 435 if (campoPagina.value !== valorSeguro) { 436 campoPagina.value = valorSeguro; 437 } 438}); 439 440campoPagina.addEventListener("paste", (evento) => { 441 evento.preventDefault(); 442 443 const texto = evento.clipboardData?.getData("text") ?? ""; 444 const valorSeguro = somenteDigitos(texto).slice(0, Number(campoPagina.maxLength) || 5); 445 const inicio = campoPagina.selectionStart ?? campoPagina.value.length; 446 const fim = campoPagina.selectionEnd ?? campoPagina.value.length; 447 448 campoPagina.setRangeText(valorSeguro, inicio, fim, "end"); 449 campoPagina.dispatchEvent(new Event("input", { bubbles: true })); 450}); 451 452campoPagina.addEventListener("change", () => { 453 irParaPagina(campoPagina.value); 454}); 455 456campoPagina.addEventListener("blur", () => { 457 campoPagina.value = obterNumeroPagina(campoPagina.value); 458}); 459 460window.addEventListener("wheel", (evento) => { 461 if (!evento.ctrlKey && !evento.metaKey) { 462 return; 463 } 464 465 evento.preventDefault(); 466 ajustarZoom(evento.deltaY > 0 ? -0.1 : 0.1); 467}, { passive: false }); 468 469area.addEventListener("touchstart", (evento) => { 470 if (evento.touches.length !== 2) { 471 distanciaPinch = null; 472 return; 473 } 474 475 distanciaPinch = distanciaEntreToques(evento.touches); 476 zoomPinch = zoom; 477}, { passive: true }); 478 479area.addEventListener("touchmove", (evento) => { 480 if (evento.touches.length !== 2 || !distanciaPinch) { 481 return; 482 } 483 484 evento.preventDefault(); 485 const distanciaAtual = distanciaEntreToques(evento.touches); 486 const fator = distanciaAtual / distanciaPinch; 487 aplicarZoomPinch(zoomPinch * fator); 488}, { passive: false }); 489 490area.addEventListener("touchend", (evento) => { 491 if (evento.touches.length < 2) { 492 distanciaPinch = null; 493 zoomPinch = zoom; 494 } 495}); 496 497window.addEventListener("keydown", (evento) => { 498 const alvo = evento.target; 499 const editandoTexto = alvo instanceof HTMLInputElement 500 || alvo instanceof HTMLTextAreaElement 501 || alvo instanceof HTMLSelectElement 502 || alvo?.isContentEditable; 503 504 if (editandoTexto) { 505 return; 506 } 507 508 if (evento.key === "ArrowLeft") { 509 irParaPagina(paginaAtual - 1); 510 } 511 512 if (evento.key === "ArrowRight") { 513 irParaPagina(paginaAtual + 1); 514 } 515 516 if ((evento.ctrlKey || evento.metaKey) && (evento.key === "+" || evento.key === "=")) { 517 evento.preventDefault(); 518 ajustarZoom(0.1); 519 } 520 521 if ((evento.ctrlKey || evento.metaKey) && evento.key === "-") { 522 evento.preventDefault(); 523 ajustarZoom(-0.1); 524 } 525}); 526 527window.addEventListener("resize", () => { 528 renderizarPagina(paginaAtual); 529}); 530 531async function iniciar() { 532 try { 533 estado.textContent = "Carregando manual..."; 534 pdf = await pdfjsLib.getDocument(pdfUrl).promise; 535 atualizarControles(); 536 await renderizarPagina(paginaAtual); 537 } catch (erro) { 538 estado.textContent = "Não foi possÃvel carregar o manual."; 539 } 540} 541 542iniciar();
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.