1/* global gettext */ 2 3var ExifRestorer = (function() 4{ 5 // from http://www.perry.cz/files/ExifRestorer.js 6 // based on MinifyJpeg 7 // http://elicon.blog57.fc2.com/blog-entry-206.html 8 9 var ExifRestorer = {}; 10 11 ExifRestorer.KEY_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 12 13 ExifRestorer.encode64 = function(input) 14 { 15 var output = "", 16 chr1, chr2, chr3 = "", 17 enc1, enc2, enc3, enc4 = "", 18 i = 0; 19 20 do { 21 chr1 = input[i++]; 22 chr2 = input[i++]; 23 chr3 = input[i++]; 24 25 enc1 = chr1 >> 2; 26 enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 27 enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 28 enc4 = chr3 & 63; 29 30 if (isNaN(chr2)) { 31 enc3 = enc4 = 64; 32 } else if (isNaN(chr3)) { 33 enc4 = 64; 34 } 35 36 output = output + 37 this.KEY_STR.charAt(enc1) + 38 this.KEY_STR.charAt(enc2) + 39 this.KEY_STR.charAt(enc3) + 40 this.KEY_STR.charAt(enc4); 41 chr1 = chr2 = chr3 = ""; 42 enc1 = enc2 = enc3 = enc4 = ""; 43 } while (i < input.length); 44 45 return output; 46 }; 47 48 ExifRestorer.restore = function(origFileBase64, resizedFileBase64) 49 { 50 if (!origFileBase64.match("data:image/jpeg;base64,")) 51 { 52 return resizedFileBase64; 53 } 54 55 var rawImage = this.decode64(origFileBase64.replace("data:image/jpeg;base64,", "")); 56 var segments = this.slice2Segments(rawImage); 57 58 var image = this.exifManipulation(resizedFileBase64, segments); 59 60 return this.encode64(image); 61 }; 62 63 ExifRestorer.restore_as_blob = function(origFileBase64, resizedFileBase64) { 64 var b64Data = ExifRestorer.restore(origFileBase64, resizedFileBase64); 65 contentType = 'image/jpeg'; 66 sliceSize = 512; 67 68 var byteCharacters = atob(b64Data); 69 var byteArrays = []; 70 71 for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) { 72 var slice = byteCharacters.slice(offset, offset + sliceSize); 73 var byteNumbers = new Array(slice.length); 74 for (var i = 0; i < slice.length; i++) { 75 byteNumbers[i] = slice.charCodeAt(i); 76 } 77 var byteArray = new Uint8Array(byteNumbers); 78 byteArrays.push(byteArray); 79 } 80 return new Blob(byteArrays, {type: contentType}); 81 }; 82 83 ExifRestorer.exifManipulation = function(resizedFileBase64, segments) 84 { 85 var exifArray = this.getExifArray(segments), 86 newImageArray = this.insertExif(resizedFileBase64, exifArray), 87 aBuffer = new Uint8Array(newImageArray); 88 89 return aBuffer; 90 }; 91 92 ExifRestorer.getExifArray = function(segments) 93 { 94 var seg; 95 for (var x = 0; x < segments.length; x++) 96 { 97 seg = segments[x]; 98 if (seg[0] == 255 & seg[1] == 225) //(ff e1) 99 { 100 return seg; 101 } 102 } 103 return []; 104 }; 105 106 ExifRestorer.insertExif = function(resizedFileBase64, exifArray) 107 { 108 var imageData = resizedFileBase64.replace("data:image/jpeg;base64,", ""), 109 buf = this.decode64(imageData), 110 separatePoint = buf.indexOf(255,3), 111 mae = buf.slice(0, separatePoint), 112 ato = buf.slice(separatePoint), 113 array = mae; 114 115 array = array.concat(exifArray); 116 array = array.concat(ato); 117 return array; 118 }; 119 120 121 ExifRestorer.slice2Segments = function(rawImageArray) 122 { 123 var head = 0, 124 segments = []; 125 126 while (1) 127 { 128 if (rawImageArray[head] == 255 & rawImageArray[head + 1] == 218){break;} 129 if (rawImageArray[head] == 255 & rawImageArray[head + 1] == 216) 130 { 131 head += 2; 132 } 133 else 134 { 135 var length = rawImageArray[head + 2] * 256 + rawImageArray[head + 3], 136 endPoint = head + length + 2, 137 seg = rawImageArray.slice(head, endPoint); 138 segments.push(seg); 139 head = endPoint; 140 } 141 if (head > rawImageArray.length){break;} 142 } 143 144 return segments; 145 }; 146 147 ExifRestorer.decode64 = function(input) 148 { 149 var output = "", 150 chr1, chr2, chr3 = "", 151 enc1, enc2, enc3, enc4 = "", 152 i = 0, 153 buf = []; 154 155 // remove all characters that are not A-Z, a-z, 0-9, +, /, or = 156 var base64test = /[^A-Za-z0-9\+\/\=]/g; 157 if (base64test.exec(input)) { 158 alert("There were invalid base64 characters in the input text.\n" + 159 "Valid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\n" + 160 "Expect errors in decoding."); 161 } 162 input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); 163 164 do { 165 enc1 = this.KEY_STR.indexOf(input.charAt(i++)); 166 enc2 = this.KEY_STR.indexOf(input.charAt(i++)); 167 enc3 = this.KEY_STR.indexOf(input.charAt(i++)); 168 enc4 = this.KEY_STR.indexOf(input.charAt(i++)); 169 170 chr1 = (enc1 << 2) | (enc2 >> 4); 171 chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 172 chr3 = ((enc3 & 3) << 6) | enc4; 173 174 buf.push(chr1); 175 176 if (enc3 != 64) { 177 buf.push(chr2); 178 } 179 if (enc4 != 64) { 180 buf.push(chr3); 181 } 182 183 chr1 = chr2 = chr3 = ""; 184 enc1 = enc2 = enc3 = enc4 = ""; 185 186 } while (i < input.length); 187 188 return buf; 189 }; 190 191 return ExifRestorer; 192})(); 193 194$.WcsFileUpload = { 195 prepare: function() { 196 var base_widget = $(this); 197 var image_resize = $(this).find('.file-button').data('image-resize'); 198 if (typeof(FileReader) === "undefined") { 199 image_resize = false;
200 } 201 if ($(base_widget).find('input[type=text]').val()) { 202 $(base_widget).find('input[type=file]').hide(); 203 $(base_widget).addClass('has-file'); 204 } else { 205 $(base_widget).find('.fileinfo').hide(); 206 $(base_widget).addClass('has-no-file'); 207 } 208 $(this).find('input[type=file]').fileupload({ 209 dropZone: base_widget, 210 pasteZone: base_widget, 211 dataType: 'json', 212 add: function (e, data) { 213 var accepted_mimetypes = $(this).attr('accept'); 214 if (accepted_mimetypes) { 215 accepted_mimetypes = accepted_mimetypes.split(','); 216 var file_mimetype = data.files[0].type; 217 var valid_mimetype = false; 218 for (var i in accepted_mimetypes) { 219 var mime_type = accepted_mimetypes[i]; 220 if (mime_type.substring(mime_type.length-2, mime_type.length) == '/*') { 221 if (file_mimetype.substring(0, mime_type.length-1) == mime_type.substring(0, mime_type.length-1)) { 222 valid_mimetype = true; 223 break; 224 } 225 } else { 226 if (file_mimetype == mime_type) { 227 valid_mimetype = true; 228 break; 229 } 230 } 231 } 232 if (!valid_mimetype) { 233 $(base_widget).find('.fileprogress').addClass('upload-error'); 234 $(base_widget).find('.fileprogress .bar').text(gettext('Invalid file type')); 235 $(base_widget).find('.fileprogress .bar').attr('aria-label', gettext('Invalid file type')); 236 $(base_widget).find('.fileprogress').show().focus(); 237 return; 238 } 239 } 240 241 if (image_resize && ( 242 data.files[0].type == 'image/jpeg' || 243 data.files[0].type == 'image/png')) { 244 245 $(base_widget).find('.fileprogress')[0].style.setProperty('--upload-progression', '0%'); 246 $(base_widget).find('.fileprogress .bar').text( 247 $(base_widget).find('.fileprogress .bar').data('resize')); 248 $(base_widget).find('.fileprogress .bar').attr('aria-label', 249 $(base_widget).find('.fileprogress .bar').text()); 250 $(base_widget).find('.fileprogress').show(); 251 $(base_widget).find('.fileprogress .bar').show().focus(); 252 253 var reader = new FileReader(); 254 reader.onload = function(e) { 255 var original_image_64 = e.target.result; 256 var img = document.createElement("img"); 257 img.onload = function() { 258 var adapt_image = function(orientation) { 259 /* 260 * 1 = Horizontal (normal) 261 * 2 = Mirror horizontal 262 * 3 = Rotate 180 263 * 4 = Mirror vertical 264 * 5 = Mirror horizontal and rotate 270 CW 265 * 6 = Rotate 90 CW 266 * 7 = Mirror horizontal and rotate 90 CW 267 * 8 = Rotate 270 CW */ 268 var canvas = document.createElement("canvas"); 269 var ctx = canvas.getContext('2d'); 270 ctx.drawImage(img, 0, 0); 271 272 var MAX_WIDTH = 2000; 273 var MAX_HEIGHT = 2000; 274 var width = img.width; 275 var height = img.height; 276 277 if (width > height && width > MAX_WIDTH) { 278 height *= MAX_WIDTH / width; 279 width = MAX_WIDTH; 280 } else if (height > MAX_HEIGHT) { 281 width *= MAX_HEIGHT / height; 282 height = MAX_HEIGHT; 283 } 284 if (img.width != width || img.height != height) { 285 canvas.width = width; 286 canvas.height = height; 287 var ctx = canvas.getContext('2d'); 288 switch (orientation) { 289 case 1: 290 break; 291 case 2: 292 ctx.translate(width, 0); 293 ctx.scale(-1, 1); 294 break; 295 case 3: 296 ctx.translate(width, height); 297 ctx.rotate(180 / 180 * Math.PI); 298 break; 299 case 4: 300 ctx.translate(0, height); 301 ctx.scale(1, -1); 302 break; 303 case 5: 304 canvas.width = height; 305 canvas.height = width; 306 ctx.rotate(90 / 180 * Math.PI); 307 ctx.scale(1, -1); 308 break; 309 case 6: 310 canvas.width = height; 311 canvas.height = width; 312 ctx.rotate(-90 / 180 * Math.PI); 313 ctx.translate(-width, 0); 314 break; 315 case 7: 316 canvas.width = height; 317 canvas.height = width; 318 ctx.rotate(270 / 180 * Math.PI); 319 ctx.translate(-width, height); 320 ctx.scale(1, -1); 321 break; 322 case 8: 323 canvas.width = height; 324 canvas.height = width; 325 ctx.translate(height, 0); 326 ctx.rotate(-270 / 180 * Math.PI); 327 break; 328 } 329 ctx.drawImage(img, 0, 0, width, height); 330 var new_image_64 = canvas.toDataURL('image/jpeg', 0.95); 331 var blob = null; 332 if (data.files[0].type == 'image/jpeg') { 333 blob = ExifRestorer.restore_as_blob(original_image_64, new_image_64); 334 blob.name = data.files[0].name; 335 } else { 336 // adapted from dataURItoBlob, from 337 // https://stackoverflow.com/questions/12168909/blob-from-dataurl#12300351 338 var byteString = atob(new_image_64.split(',')[1]); 339 var mimeString = new_image_64.split(',')[0].split(':')[1].split(';')[0] 340 var ab = new ArrayBuffer(byteString.length); 341 var ia = new Uint8Array(ab); 342 for (var i = 0; i < byteString.length; i++) { 343 ia[i] = byteString.charCodeAt(i); 344 } 345 blob = new Blob([ab], {type: mimeString}); 346 blob.name = data.files[0].name + '.jpg'; 347 } 348 data.files[0] = blob; 349 } 350 $(base_widget).find('.file-button').trigger('wcs:image-blob', data.files[0]); 351 return $.WcsFileUpload.upload(base_widget, data); 352 }; 353 if (data.files[0].type == 'image/jpeg') { 354 EXIF.getData(img, function () { 355 var orientation = +EXIF.getTag(this, "Orientation");
356 adapt_image(orientation); 357 }); 358 } else { 359 adapt_image(0); 360 } 361 } 362 img.src = e.target.result; 363 } 364 reader.readAsDataURL(data.files[0]); 365 } else { 366 return $.WcsFileUpload.upload(base_widget, data); 367 } 368 }, 369 done: function(e, data) { 370 if (data.result[0].error) { 371 $(base_widget).find('.fileprogress').addClass('upload-error'); 372 $(base_widget).find('.fileprogress .bar').text(data.result[0].error); 373 $(base_widget).find('.fileprogress .bar').attr('aria-label', 374 $(base_widget).find('.fileprogress .bar').text()); 375 $(base_widget).find('.fileprogress .bar').focus(); 376 return; 377 } 378 $(base_widget).find('.fileprogress').hide(); 379 $.WcsFileUpload.set_file(base_widget, data.result[0]); 380 $(base_widget).find('[type=file]').trigger('change'); 381 $(base_widget).find('input[type=text]').trigger('change'); 382 var $remove_button = $(base_widget).find('.remove'); 383 $remove_button.find('.remove').focus(); 384 }, 385 fail: function(e, data) { 386 $(base_widget).find('.fileprogress').addClass('upload-error'); 387 $(base_widget).find('.fileprogress .bar').text( 388 $(base_widget).find('.fileprogress .bar').data('error')); 389 $(base_widget).find('.fileprogress .bar').attr('aria-label', 390 $(base_widget).find('.fileprogress .bar').text()); 391 $(base_widget).find('.fileprogress .bar').focus(); 392 }, 393 progress: function (e, data) { 394 var progress = parseInt(data.loaded / data.total * 100, 10); 395 $(base_widget).find('.fileprogress')[0].style.setProperty('--upload-progression', progress + '%'); 396 $(base_widget).find('.fileprogress .bar').attr('aria-valuenow', progress).attr('aria-valuetext', progress + '%'); 397 } 398 }); 399 $(this).find('.remove').click(function() { 400 $(base_widget).find('input[type=text]').val(''); 401 $(base_widget).find('.fileinfo').hide(); 402 $(base_widget).find('input[type=file]').show(); 403 $(base_widget).removeClass('has-file').addClass('has-no-file'); 404 $(base_widget).find('input[type=file]').trigger('change'); 405 $(base_widget).find('input[type=text]').trigger('change'); 406 $(base_widget).find('input[type=file]').focus(); 407 return false; 408 }); 409 $(this).find('a.change').click(function() { 410 $(base_widget).find('input[type=file]').click(); 411 return false; 412 }); 413 }, 414 415 upload: function(base_widget, data) { 416 var max_file_size = $(base_widget).find('input[type=file]').data('max-file-size'); 417 if (max_file_size && data.files[0].size > max_file_size) { 418 var message = gettext('File size exceeds limits') 419 var max_file_size_human = $(base_widget).find('input[type=file]').data('max-file-size-human'); 420 if (max_file_size_human) { 421 message = message + ' (' + max_file_size_human + ')'; 422 } 423 $(base_widget).find('.fileprogress').addClass('upload-error'); 424 $(base_widget).find('.fileprogress .bar').text(message); 425 $(base_widget).find('.fileprogress').show(); 426 return; 427 } 428 $(base_widget).find('.fileprogress').removeClass('upload-error'); 429 $(base_widget).find('.fileprogress .bar').text( 430 $(base_widget).find('.fileprogress .bar').data('upload')); 431 $(base_widget).find('.fileprogress')[0].style.setProperty('--upload-progression', '0%'); 432 $(base_widget).find('.fileprogress').show(); 433 $(base_widget).find('.fileinfo').hide(); 434 $(base_widget).parents('form').find('div.buttons button').prop('disabled', true); 435 var jqXHR = data.submit(); 436 }, 437 438 set_file: function(base_widget, data) { 439 if (! data) return; 440 var base_file_name = data.name; 441 if (data.url) { 442 $(base_widget).find('.filename').empty().append( 443 $('<a>', {href: data.url, download: base_file_name, text: base_file_name})); 444 } else { 445 $(base_widget).find('.filename').text(base_file_name); 446 } 447 var $remove_button = $(base_widget).find('.remove'); 448 if ($remove_button.length) { 449 $remove_button.attr('title', $remove_button[0].dataset.titlePrefix + ' ' + base_file_name); 450 } 451 $(base_widget).find('.fileinfo').show(); 452 $(base_widget).find('input[type=text]').val(data.token); 453 $(base_widget).parents('form').find('div.buttons button').prop('disabled', false); 454 $(base_widget).addClass('has-file').removeClass('has-no-file'); 455 456 $.WcsFileUpload.image_preview(base_widget, data.token); 457 }, 458 459 image_preview: function(base_widget, img_token) { 460 var file_button = $(base_widget).find('.file-button'); 461 if(file_button.hasClass("file-image")) { 462 file_button[0].style.setProperty('--image-preview-url', `url(${window.location.protocol}//${window.location.host}${window.location.pathname}tempfile?t=${img_token}&thumbnail=1)`); 463 } 464 } 465}
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.