1const {PDFDocument, StandardFonts} = PDFLib; 2 3const $ = (...args) => document.querySelector(...args); 4const $$ = (...args) => [...document.querySelectorAll(...args)]; 5let profil = {}; 6 7// Changement langue 8function changeLangue(langue) { 9 for (const _ of document.getElementsByClassName('langue')) { 10 _.classList.remove('actif'); 11 } 12 document.getElementById(langue).classList.add('actif'); 13 for (const champ of document.getElementsByClassName('texte')) { 14 if (Object.keys(langues).indexOf(langue) > -1) { 15 let idChamp = champ.id, 16 traductionChamp = langues[langue][idChamp]; 17 champ.innerHTML = traductionChamp; 18 // Change aussi le placeholder s'il existe et n'est pas une date 19 if (idChamp.includes('label') && 20 champ.parentElement.nextElementSibling.hasAttribute('placeholder') && 21 champ.parentElement.nextElementSibling.getAttribute('type') != 'date') { 22 champ.parentElement.nextElementSibling.setAttribute('placeholder', traductionChamp); 23 } 24 } 25 } 26 localStorage.setItem('langue', langue); 27 28} 29for (const el of document.getElementsByClassName('langue')) { 30 el.addEventListener('click', () => changeLangue(el.id)); 31} 32// Changement automatique de la langue 33if (localStorage.getItem('langue')) { 34 changeLangue(localStorage.getItem('langue')); 35} else { 36 // Détection de la langue du navigateur 37 // https://stackoverflow.com/questions/1043339/javascript-for-detecting-browser-language-preference 38 let lang = window.navigator.languages ? window.navigator.languages[0] : null; 39 lang = lang || window.navigator.language || window.navigator.browserLanguage || window.navigator.userLanguage; 40 let shortLang = lang; 41 if (shortLang.indexOf('-') !== -1) 42 shortLang = shortLang.split('-')[0]; 43 if (shortLang.indexOf('_') !== -1) 44 shortLang = shortLang.split('_')[0]; 45 if (Object.keys(langues).indexOf(shortLang) > -1) 46 changeLangue(shortLang); 47} 48 49// Formattage Date 50function pad (str) { 51 return String(str).padStart(2, '0'); 52} 53 54function getFormattedDate (date) { 55 const year = date.getFullYear();
56 const month = pad(date.getMonth() + 1); 57 const day = pad(date.getDate()); 58 return `${year}-${month}-${day}`; 59} 60document.addEventListener('DOMContentLoaded', setReleaseDateTime); 61function setReleaseDateTime () { 62 const releaseDateInput = $('#date_sortie'); 63 const loadedDate = new Date(); 64 releaseDateInput.value = getFormattedDate(loadedDate); 65 66 const hour = pad(loadedDate.getHours()); 67 const minute = pad(loadedDate.getMinutes()); 68 69 const releaseTimeInput = $('#heure_sortie'); 70 releaseTimeInput.value = `${hour}:${minute}`; 71} 72 73// QR Code 74const generateQR = async (text) => { 75 try { 76 const opts = { 77 errorCorrectionLevel: 'M', 78 type: 'image/png', 79 quality: 0.92, 80 margin: 1, 81 } 82 return await QRCode.toDataURL(text, opts); 83 } catch (err) { 84 console.error(err); 85 } 86} 87 88// Création PDF 89function idealFontSize (font, text, maxWidth, minSize, defaultSize) { 90 let currentSize = defaultSize; 91 let textWidth = font.widthOfTextAtSize(text, defaultSize); 92 while (textWidth > maxWidth && currentSize > minSize) { 93 textWidth = font.widthOfTextAtSize(text, --currentSize); 94 } 95 return textWidth > maxWidth ? null : currentSize; 96} 97async function generatePdf (profile, reasons) { 98 const creationInstant = new Date(); 99 const creationDate = creationInstant.toLocaleDateString('fr-FR'); 100 const creationHour = creationInstant 101 .toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' }) 102 .replace(':', 'h'); 103 104 const { 105 adresse, 106 code_postal, 107 date_naissance, 108 date_sortie, 109 heure_sortie, 110 lieu_naissance, 111 nom, 112 prenom, 113 ville, 114 } = profile; 115 const releaseHours = String(heure_sortie).substring(0, 2); 116 const releaseMinutes = String(heure_sortie).substring(3, 5); 117 118 const data = [ 119 `Cree le: ${creationDate} a ${creationHour}`, 120 `Nom: ${nom}`, 121 `Prenom: ${prenom}`, 122 `Naissance: ${date_naissance} a ${lieu_naissance}`, 123 `Adresse: ${adresse} ${code_postal} ${ville}`, 124 `Sortie: ${date_sortie} a ${releaseHours}h${releaseMinutes}`, 125 `Motifs: ${reasons}`, 126 ].join('; '); 127 128 const existingPdfBytes = await fetch('./certificate.pdf').then((res) => res.arrayBuffer()) 129 130 const pdfDoc = await PDFDocument.load(existingPdfBytes); 131 const page1 = pdfDoc.getPages()[0]; 132 133 const font = await pdfDoc.embedFont(StandardFonts.Helvetica); 134 const drawText = (text, x, y, size = 11) => { 135 page1.drawText(text, { x, y, size, font }); 136 }; 137 138 drawText(`${prenom} ${nom}`, 123, 686); 139 drawText(date_naissance, 123, 661); 140 drawText(lieu_naissance, 92, 638); 141 drawText(`${adresse} ${code_postal} ${ville}`, 134, 613); 142 143 if (reasons.includes('travail')) { 144 drawText('x', 76, 527, 19); 145 } 146 if (reasons.includes('courses')) { 147 drawText('x', 76, 478, 19); 148 } 149 if (reasons.includes('sante')) { 150 drawText('x', 76, 436, 19); 151 } 152 if (reasons.includes('famille')) { 153 drawText('x', 76, 400, 19); 154 } 155 if (reasons.includes('sport')) { 156 drawText('x', 76, 345, 19); 157 } 158 if (reasons.includes('judiciaire')) { 159 drawText('x', 76, 298, 19); 160 } 161 if (reasons.includes('missions')) { 162 drawText('x', 76, 260, 19); 163 } 164 let locationSize = idealFontSize(font, ville, 83, 7, 11); 165 166 if (!locationSize) { 167 alert( 168 'Le nom de la ville risque de ne pas être affiché correctement en raison de sa longueur. ' + 169 'Essayez d\'utiliser des abréviations ("Saint" en "St." par exemple) quand cela est possible.', 170 ); 171 locationSize = 7; 172 } 173 drawText(ville, 111, 226, locationSize); 174 if (reasons !== '') { 175 // Date sortie 176 drawText(`${date_sortie}`, 92, 200); 177 drawText(releaseHours, 200, 201); 178 drawText(releaseMinutes, 220, 201); 179 } 180 181 // Date création 182 drawText('Date de création:', 464, 150, 7); 183 drawText(`${creationDate} à ${creationHour}`, 455, 144, 7); 184 185 const generatedQR = await generateQR(data); 186 187 const qrImage = await pdfDoc.embedPng(generatedQR); 188 189 page1.drawImage(qrImage, { 190 x: page1.getWidth() - 170, 191 y: 155, 192 width: 100, 193 height: 100, 194 }); 195 196 pdfDoc.addPage() 197 const page2 = pdfDoc.getPages()[1]; 198 page2.drawImage(qrImage, { 199 x: 50, 200 y: page2.getHeight() - 350, 201 width: 300, 202 height: 300, 203 }); 204 205 const pdfBytes = await pdfDoc.save(); 206 207 return new Blob([pdfBytes], { type: 'application/pdf' }); 208} 209 210 211// Télécharger attestation 212function downloadBlob (blob, fileName) { 213 const link = document.createElement('a'); 214 const url = URL.createObjectURL(blob); 215 link.href = url; 216 link.download = fileName; 217 document.body.appendChild(link); 218 link.click(); 219} 220 221function getMotifs () { 222 return $$('input[name="motif"]:checked') 223 .map((x) => x.value) 224 .join('-'); 225} 226function saveProfil () { 227 const champs = {}; 228 for (const field of $$('#champs-profil input')) { 229 if (field.id === 'date_sortie-label') { 230 const dateSortie = field.value.split('-'); 231 champs[dateSortie] = `${dateSortie[2]}/${dateSortie[1]}/${dateSortie[0]}`; 232 } else if (! field.id.includes('checkbox')) { 233 champs[field.id] = field.value; 234 } 235 } 236 localStorage.setItem("profil",JSON.stringify(champs)); 237 return champs; 238} 239// Sauvegarder Profile 240// Générer attestation 241$('#generer-attestation').addEventListener('click', async (event) => { 242 event.preventDefault(); 243 244 profil = saveProfil(); 245 246 const reasons = getMotifs(); 247 const pdfBlob = await generatePdf(profil, reasons); 248 249 const creationInstant = new Date(); 250 const creationDate = creationInstant.toLocaleDateString('fr-CA'); 251 const creationHour = creationInstant 252 .toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' }) 253 .replace(':', '-'); 254 downloadBlob(pdfBlob, `attestation-${creationDate}_${creationHour}.pdf`); 255 256}); 257 258if (localStorage.getItem('profil')) { 259 profil = JSON.parse(localStorage.getItem('profil')); 260 for (const champ of Object.keys(profil)) { 261 $(`#${champ}`).value = profil[champ]; 262 } 263}
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.