1/** 2 * Screen Reader main controller class 3 * 4 * @package SCREENREADER::plugins::system::screenreader 5 * @subpackage libraries 6 * @subpackage controller 7 * @author Joomla! Extensions Store 8 * @copyright (C) 2015 - Joomla! Extensions Store 9 * @license GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html 10 */ 11//'use strict'; 12(function($) { 13 var MainController = function(options) { 14 /** 15 * Plugin options 16 * 17 * @access private 18 * @var Object 19 */ 20 var screenReaderOptions = {}; 21 22 /** 23 * Storage used by the plugin, session or local 24 * 25 * @access private 26 * @var Object 27 */ 28 var screenReaderStorage = {}; 29 30 /** 31 * TTS instance 32 * 33 * @access private 34 * @var Object 35 */ 36 var frTTSEngineInstance = {}; 37 38 /** 39 * Font size default as starting point 40 * 41 * @access private 42 * @var Int 43 */ 44 var fontSizeDefault = 80; 45 46 /** 47 * Font size currently set 48 * 49 * @access private 50 * @var Int 51 */ 52 var fontSizeCurrent = 80; 53 54 /** 55 * Spacing size currently set 56 * 57 * @access private 58 * @var Int 59 */ 60 var spacingSizeCurrent = 0; 61 62 /** 63 * Page zoom currently set 64 * 65 * @access private 66 * @var Int 67 */ 68 var zoomSizeCurrent = 100; 69 70 /** 71 * Line height currently set 72 * 73 * @access private 74 * @var Int 75 */ 76 var lineHeightCurrent = 0; 77 78 /** 79 * Set a variable for the current state of high contrast 80 * 81 * @access private 82 * @var Int 83 */ 84 var highContrast = 0; 85 86 /** 87 * Set a variable for the current state of alternate contrast 88 * 89 * @access private 90 * @var Int 91 */ 92 var highContrastAlternate = 0; 93 94 /** 95 * Set the target element for the filter transform 96 * 97 * @access private 98 * @var Int 99 */ 100 var targetFilterElement = $('body'); 101 102 /** 103 * Set a variable for the current state of dyslexic font 104 * 105 * @access private 106 * @var Int 107 */ 108 var dyslexicFont = 0; 109 110 /** 111 * Set a variable for the current state of gray hues 112 * 113 * @access private 114 * @var Int 115 */ 116 var grayHues = 0; 117 118 /** 119 * Set a variable for the current state of big cursor 120 * 121 * @access private 122 * @var Int 123 */ 124 var bigCursor = 0; 125 126 /** 127 * Set a variable for the current state of read guides 128 * 129 * @access private 130 * @var Int 131 */ 132 var readingGuides = 0; 133 134 /** 135 * Set a variable to store the original HTML code to restore 136 * 137 * @access private 138 * @var String 139 */ 140 var originalHtml = ''; 141 142 /** 143 * Set a variable for the current state of read mode 144 * 145 * @access private 146 * @var String 147 */ 148 var readabilityMode = 0; 149 150 /** 151 * Set a variable for the current state of hide images 152 * 153 * @access private 154 * @var String 155 */ 156 var hideImages = 0; 157 158 /** 159 * Set a variable for the current state of hide images 160 * 161 * @access private 162 * @var String 163 */ 164 var hideAlsoVideosIFrames = 0; 165 166 /** 167 * Set a variable for the current state of custom background color 168 * 169 * @access private 170 * @var String 171 */ 172 var customBackgroundColor; 173 174 /** 175 * Set a variable for the current state of custom text color 176 * 177 * @access private 178 * @var String 179 */ 180 var customTextColor; 181 182 /** 183 * Set a variable to retrieve the base body font family 184 * 185 * @access private 186 * @var Int 187 */ 188 var originalBodyFontFamily = $('body').css('font-family'); 189 190 /** 191 * Keep track of the original HTML element styles if any 192 * 193 * @accee private 194 * @var String 195 */ 196 var originalHTMLStyles = null; 197 198 // Apply stylesheets programmaticallyfunction rtrim(str, ch) 199 var rtrim = function(str, ch) { 200 for (i = str.length - 1; i >= 0; i--) { 201 if (ch != str.charAt(i)) { 202 str = str.substring(0, i + 1); 203 break; 204 } 205 } 206 return str; 207 }; 208 209 /** 210 * Manage the appliance of the font resizing for headers element with an increment to preserve proportions 211 * 212 * @access private 213 * @return Void 214 */ 215 var fontResizeHeaders = function() { 216 // Filter header elements h1, h2, h3, h4 217 $(screenReaderOptions.fontsizeSelector).filter(function(index){ 218 var elementNodeName = this.nodeName.toLowerCase(); 219 var enlargedElement = false; 220 var enlargementValue = 0; 221 var headersReduction = 1; 222 switch (elementNodeName) { 223 case 'h1': 224 enlargementValue = 100; 225 enlargedElement = true; 226 break; 227 228 case 'h2': 229 enlargementValue = 80; 230 headersReduction = 0.8; 231 enlargedElement = true; 232 break; 233 234 case 'h3': 235 enlargementValue = 40; 236 headersReduction = 0.6; 237 enlargedElement = true; 238 break; 239 240 case 'h4': 241 enlargementValue = 20; 242 headersReduction = 0.4; 243 enlargedElement = true; 244 break; 245 } 246 // Found a header element to enlarge? 247 if(enlargedElement) { 248 // Append to the current inline style of the element 249 var currentInlineStyles = $(this).attr('style') || ''; 250 $(this).attr('style', currentInlineStyles + ';font-size:' + (parseInt(fontSizeCurrent) + (screenReaderOptions.fontSizeHeadersIncrement * headersReduction) + enlargementValue) + '%'); 251 } 252 }); 253 }; 254 255 /**
256 * Manage the appliance of the alternate high contrast mode 257 * 258 * @access private 259 * @param Object jqEvent 260 * @return Void 261 */ 262 var applyAlternateHighContrast = function(jqEvent){ 263 // Get alternate ig contrast value 264 highContrastAlternate = parseInt(screenReaderStorage.getItem('scr_highcontrast_alternate')) || 0; 265 266 if(jqEvent) { 267 $('#fr_screenreader_highcontrast,#fr_screenreader_highcontrast2,#fr_screenreader_highcontrast3').removeClass('active'); 268 } 269 270 // Appliance function 271 var applianceFunction = function() { 272 // Always reset previous default states 273 targetFilterElement.removeClass('scr_highcontrast'); 274 highContrast = 0; 275 screenReaderStorage.setItem('scr_highcontrast', highContrast); 276 277 // Get original styles 278 if(originalHTMLStyles === null) { 279 originalHTMLStyles = targetFilterElement.attr('style') || ''; 280 } 281 282 // Get a hue rotate value if any 283 var hueRotate = ''; 284 if($(this).data('rotate')) { 285 hueRotate = ' hue-rotate(' + parseInt($(this).data('rotate')) + 'deg)'; 286 } 287 288 // Get a brightness value if any 289 var brightness = ''; 290 if($(this).data('brightness')) { 291 brightness = ' brightness(' + parseInt($(this).data('brightness')) + ')'; 292 } 293 294 targetFilterElement.attr('style', rtrim(originalHTMLStyles, ';') + ';filter: invert(100%)' + hueRotate + brightness); 295 } 296 297 if(!highContrastAlternate) { 298 applianceFunction.bind(this)(); 299 highContrastAlternate = $(this).data('alternate'); 300 301 if(jqEvent) { 302 $(jqEvent.target).addClass('active'); 303 } 304 } else if(highContrastAlternate && typeof(jqEvent) !== 'undefined'){ 305 // Is the same alternate high contrast or the other one? 306 var currentClickedHighContrastAlternate = $(this).data('alternate'); 307 if(currentClickedHighContrastAlternate == highContrastAlternate) { 308 // Disable it, exit from state 309 // Recover original stylesheets 310 targetFilterElement.attr('style', originalHTMLStyles); 311 highContrastAlternate = 0; 312 } else { 313 highContrastAlternate = currentClickedHighContrastAlternate; 314 applianceFunction.bind(this)(); 315 316 if(jqEvent) { 317 $(jqEvent.target).addClass('active'); 318 } 319 } 320 } else if(highContrastAlternate && typeof(jqEvent) === 'undefined') { 321 applianceFunction.bind(this)(); 322 } 323 324 screenReaderStorage.setItem('scr_highcontrast_alternate', highContrastAlternate); 325 }; 326 327 /** 328 * Build Screen Reader interface 329 * 330 * @access private 331 * @param String hexnum 332 * @return String 333 */ 334 var applyColorsInversion = function(hexnum, isForeGround) { 335 336 hexnum = hexnum.toUpperCase(); 337 var splitnum = hexnum.split(""); 338 var resultnum = ""; 339 var simplenum = "FEDCBA9876".split(""); 340 var complexnum = new Array(); 341 complexnum.A = "5"; 342 complexnum.B = "4"; 343 complexnum.C = "3"; 344 complexnum.D = "2"; 345 complexnum.E = "1"; 346 complexnum.F = "0"; 347 348 for (i = 0; i < 6; i++) { 349 if (!isNaN(splitnum[i])) { 350 resultnum += simplenum[splitnum[i]]; 351 } else if (complexnum[splitnum[i]]) { 352 resultnum += complexnum[splitnum[i]]; 353 } else { 354 return false; 355 } 356 } 357 358 if(isForeGround && screenReaderOptions.ieHighContrast) { 359 var originalIntColor = parseInt(hexnum, 16); 360 var newIntColor = parseInt(resultnum, 16); 361 if(newIntColor < originalIntColor) 362 return hexnum; 363 } 364 365 return resultnum; 366 367 }; 368 369 /** 370 * Convert rgb color to hex 371 * 372 * @access private 373 * @param String 374 * $rgbcolor 375 * @return String 376 */ 377 var convertRGBDecimalToHex = function(rgb) { 378 var regex = /rgba? *\( *([0-9]{1,3}) *, *([0-9]{1,3}) *, *([0-9]{1,3})(, *[0-9]{1,3}[.0-9]*)? *\)/; 379 var values = regex.exec(rgb); 380 if(!values) { 381 return 'FFFFFF'; // fall back to white 382 } 383 var r = Math.round(parseFloat(values[1])); 384 var g = Math.round(parseFloat(values[2])); 385 var b = Math.round(parseFloat(values[3]));
386 var hexColor = (r + 0x10000).toString(16).substring(3).toUpperCase() 387 + (g + 0x10000).toString(16).substring(3).toUpperCase() 388 + (b + 0x10000).toString(16).substring(3).toUpperCase(); 389 390 return hexColor; 391 }; 392 393 /** 394 * Detect if browser has native support for CSS3 filters 395 * 396 * @access private 397 * @param String selector 398 * @return String 399 */ 400 var css3FilterFeatureDetect = function(enableWebkit) { 401 var prefixes = ' -webkit- -moz- -o- -ms- '.split(' '); 402 var el = document.createElement('div'); 403 el.style.cssText = prefixes.join('filter' + ':invert(100%); '); 404 return !!el.style.length && ((document.documentMode === undefined || document.documentMode > 9)); 405 }; 406 407 /** 408 * Start the legacy IE color inversion 409 * 410 * @access private 411 * @param String selector 412 * @return String 413 */ 414 var startLegacyHighcontrast = function(selector) { 415 var collectionOfElements = $(selector); 416 417 $.fn.skipElement = function(style) { 418 var original = this.css(style); 419 this.css(style, 'inherit'); 420 var inherited = this.css(style); 421 if (original != inherited && convertRGBDecimalToHex(inherited) != '000000') { 422 this.css(style, inherited); 423 return true; 424 } else { 425 this.css(style, original); 426 return false; 427 } 428 } 429 430 $(selector).each(function(index, HTMLElement){ 431 // Skip untraslated elements 432 if($.inArray(HTMLElement.nodeName.toLowerCase(), ['script', 'embed', 'object']) == -1) { 433 // Advanced mode for !important inline styling without the skipElemen algo 434 if(screenReaderOptions.ieHighContrastAdvanced) { 435 // Get rgb original color 436 var backgroundColor = $(HTMLElement).css('background-color'); 437 var foregroundColor = $(HTMLElement).css('color'); 438 439 if(backgroundColor && foregroundColor) { 440 // Invert to hex colors 441 var invertedBackgroundColor = applyColorsInversion(convertRGBDecimalToHex(backgroundColor)); 442 var invertedForegroundColor = applyColorsInversion(convertRGBDecimalToHex(foregroundColor), true); 443 444 // Now apply calculated inverted colors to the target element 445 var currentStyle = $(HTMLElement).attr('style') || ''; 446 var newStyle = currentStyle + ' background-color:#' + invertedBackgroundColor + ' !important; color:#' + invertedForegroundColor + ' !important'; 447 $(HTMLElement).attr('style', newStyle); 448 } 449 } else { 450 if(!$(HTMLElement).skipElement('background-color')) { 451 // Get rgb original color 452 var backgroundColor = $(HTMLElement).css('background-color'); 453 var foregroundColor = $(HTMLElement).css('color'); 454 455 if(backgroundColor && foregroundColor) { 456 // Invert to hex colors 457 var invertedBackgroundColor = applyColorsInversion(convertRGBDecimalToHex(backgroundColor)); 458 var invertedForegroundColor = applyColorsInversion(convertRGBDecimalToHex(foregroundColor), true); 459 460 // Now apply calculated inverted colors to the target element 461 $(HTMLElement).css('background-color', '#' + invertedBackgroundColor); 462 $(HTMLElement).css('color', '#' + invertedForegroundColor); 463 } 464 } 465 } 466 } 467 }); 468 }; 469 470 /** 471 * Manage the appliance of the alternate high contrast mode 472 * 473 * @access private 474 * @param Object jqEvent 475 * @return Void 476 */ 477 var skipToContents = function(jqEvent){ 478 var targetElement = $(screenReaderOptions.skipToContentsSelector); 479 if(targetElement.length) { 480 $('html, body').animate({ 481 scrollTop: targetElement.offset().top 482 }, 500); 483 } 484 }; 485 486 /** 487 * Build Screen Reader interface 488 * 489 * @access public 490 * @return Void 491 */ 492 this.buildInterface = function() { 493 // Inject del container 494 var mainContainer = $('<div/>').attr('id', 'accessibility-links') 495 .addClass(screenReaderOptions.position + ' ' + screenReaderOptions.scrolling + ' ' + screenReaderOptions.templateOrientation); 496 497 // Switch the target append mode 498 if(screenReaderOptions.targetAppendMode == 'bottom') { 499 mainContainer.appendTo(screenReaderOptions.targetAppendto); 500 }
500 else if(screenReaderOptions.targetAppendMode == 'top') { 501 mainContainer.prependTo(screenReaderOptions.targetAppendto); 502 } 503 504 // Init dark mode 505 if(screenReaderOptions.enableDarkMode && !!parseInt(screenReaderStorage.getItem('scr_toolbar_darkmode'))) { 506 $('#accessibility-links').addClass('scr-toolbar-darkmode'); 507 } 508 509 if(screenReaderOptions.template == 'accessible.css') { 510 // Inject della extra top toolbar per accessible template di chiusura e informazioni 511 var toolbarDiv = $('<div/>').attr('id', 'toolbar_plugin') 512 .html('<span class="scaccessibletoolbar-text">' + (screenReaderOptions.labeltext || fr_screenreader) + '</span>') 513 .addClass('scaccessibletoolbar'); 514 if(screenReaderOptions.screenreaderIcon == 'wheelchair') { 515 toolbarDiv.addClass('scr_wheelchair'); 516 } 517 mainContainer.append(toolbarDiv); 518 519 // Inject del toolbar closer 520 var toolbarDivCloser = $('<span/>').attr('id', 'toolbar_closer') 521 .html('❌') 522 .attr('data-title', fr_close_toolbar) 523 .attr('aria-label', fr_close_toolbar) 524 .attr('role', 'button') 525 .addClass('scaccessibletoolbarcloser'); 526 $('#toolbar_plugin').append(toolbarDivCloser); 527 } 528 529 // Inject del left container per il testo 530 var textDiv = $('<div/>').attr('id', 'text_plugin') 531 .attr('title', ' - ' + (screenReaderOptions.labeltext || fr_screenreader) + ' - ') 532 .addClass('scbasebin screenreader text'); 533 if(!screenReaderOptions.showlabel) { 534 textDiv.addClass('scr_nolabel'); 535 } 536 if(screenReaderOptions.screenreaderIcon == 'wheelchair') { 537 textDiv.addClass('scr_wheelchair'); 538 } 539 if(screenReaderOptions.screenreaderIcon == 'custom') { 540 textDiv.addClass('scr_customicon'); 541 } 542 mainContainer.append(textDiv); 543 544 // Inject del middle container per il volume controls 545 if(screenReaderOptions.screenreader) { 546 var volumeDiv = $('<div/>').attr('id', 'volume_plugin') 547 .addClass('scbasebin'); 548 mainContainer.append(volumeDiv); 549 } 550 551 // Inject del right container per lo switch speaker 552 var speakerDiv = $('<div/>').attr('id', 'speaker_plugin') 553 .addClass('scbasebin speaker'); 554 mainContainer.append(speakerDiv); 555 556 // Inject del clearer 557 var clearerDiv = $('<div/>').css('clear', 'both').attr('id', 'scr_clearer'); 558 mainContainer.append(clearerDiv); 559 560 // Label 561 if(screenReaderOptions.showlabel) { 562 var labelScreenReader = $('<span/>').addClass('fr_label startapp') 563 .text(screenReaderOptions.labeltext || fr_screenreader); 564 textDiv.append(labelScreenReader); 565 } 566 567 if(screenReaderOptions.screenreader) { 568 // Inject del play 569 var playButton = $('<button/>').attr({ 'id':'fr_screenreader_play', 570 'title': ' - ' + fr_screenreader_play + ' - ', 571 'aria-label': ' - ' + fr_screenreader_play + ' - ', 572 'role' : 'button', 573 'accesskey':screenReaderOptions.accesskey_play}) 574 .addClass('pinnable') 575 .text(fr_screenreader_play); 576 speakerDiv.append(playButton); 577 578 // Inject del pause 579 var pauseButton = $('<button/>').attr({ 'id':'fr_screenreader_pause', 580 'title': ' - ' + fr_screenreader_pause + ' - ', 581 'aria-label': ' - ' + fr_screenreader_pause + ' - ', 582 'role' : 'button', 583 'accesskey':screenReaderOptions.accesskey_pause}) 584 .addClass('pinnable') 585 .text(fr_screenreader_pause); 586 speakerDiv.append(pauseButton); 587 588 // Inject dello stop 589 var stopButton = $('<button/>').attr({ 'id':'fr_screenreader_stop', 590 'title': ' - ' + fr_screenreader_stop + ' - ', 591 'aria-label': ' - ' + fr_screenreader_stop + ' - ', 592 'role' : 'button', 593 'accesskey':screenReaderOptions.accesskey_stop}) 594 .addClass('pinnable') 595 .text(fr_screenreader_stop); 596 speakerDiv.append(stopButton); 597 } 598 599 // Rebuild the section if the accessible template is active 600 if(screenReaderOptions.template == 'accessible.css') {
601 var sectionAudioContainer = $('<div/>').attr({ 'id':'sc-section-audio' }) 602 .addClass('sc-section'); 603 604 sectionAudioContainer.append('<div class="sc-section-title" aria-label="' + fr_text_reader + '">' + fr_text_reader + '</div>'); 605 speakerDiv.append(sectionAudioContainer); 606 var audioSectionButtons = $('#fr_screenreader_play, #fr_screenreader_pause, #fr_screenreader_stop').clone(true, true).end().remove(); 607 sectionAudioContainer.append(audioSectionButtons); 608 609 var audioSectionVolumeControls = $('#volume_plugin').clone(true, true).end().remove(); 610 sectionAudioContainer.append(audioSectionVolumeControls); 611 612 if(screenReaderOptions.enableDarkMode) { 613 // Inject del dark mode 614 var toolbarDivDarkMode = $('<span/>').attr('id', 'toolbar_darkmode') 615 .html('<svg class="scr-darkmode" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.56 122.88"><defs><style>.cls-1{fill-rule:evenodd;}</style></defs><title>night</title><path class="cls-1" d="M121.85,87.3A64.31,64.31,0,1,1,36.88.4c2.94-1.37,5.92.91,4.47,4.47a56.29,56.29,0,0,0,75.75,77.4l.49-.27a3.41,3.41,0,0,1,4.61,4.61l-.35.69ZM92.46,74.67H92A16.11,16.11,0,0,0,76.2,58.93v-.52a15.08,15.08,0,0,0,11-4.72,15.19,15.19,0,0,0,4.72-11h.51a15.12,15.12,0,0,0,4.72,11,15.12,15.12,0,0,0,11,4.72v.51A16.13,16.13,0,0,0,92.46,74.67Zm10.09-46.59h-.27a7.94,7.94,0,0,0-2.49-5.81A7.94,7.94,0,0,0,94,19.78v-.27A7.94,7.94,0,0,0,99.79,17a8,8,0,0,0,2.49-5.8h.27A8,8,0,0,0,105,17a8,8,0,0,0,5.81,2.49v.27A8,8,0,0,0,105,22.27a7.94,7.94,0,0,0-2.49,5.81Zm-41.5,8h-.41a12.06,12.06,0,0,0-3.78-8.82A12.06,12.06,0,0,0,48,23.5v-.41a12.07,12.07,0,0,0,8.82-3.78,12.09,12.09,0,0,0,3.78-8.82h.41a12.08,12.08,0,0,0,3.77,8.82,12.09,12.09,0,0,0,8.83,3.78v.41a12.09,12.09,0,0,0-8.83,3.78,12.08,12.08,0,0,0-3.77,8.82Z"/></svg>') 616 .attr('data-title', fr_dark_mode) 617 .attr('aria-label', fr_dark_mode) 618 .attr('role', 'button') 619 .addClass('scaccessibletoolbarcloser'); 620 $('#sc-section-audio').append(toolbarDivDarkMode); 621 622 $('#toolbar_darkmode').on('click', function(jqEvent){ 623 var darkModeState = 0; 624 var accessibilityLinksToolbar = $('#accessibility-links'); 625 accessibilityLinksToolbar.toggleClass('scr-toolbar-darkmode'); 626 if(accessibilityLinksToolbar.hasClass('scr-toolbar-darkmode')) { 627 darkModeState = 1; 628 } 629 screenReaderStorage.setItem('scr_toolbar_darkmode', darkModeState); 630 }); 631 } 632 if(screenReaderOptions.enableAccessibilityStatement) { 633 // Inject del dark mode 634 var toolbarDivAccessibilityStatement = $('<a/>').attr('id', 'toolbar_accessibility_statement') 635 .text(screenReaderOptions.enableAccessibilityStatementText) 636 .attr('href', screenReaderOptions.enableAccessibilityStatementLink) 637 .attr('download', screenReaderOptions.enableAccessibilityStatementLink.substring(screenReaderOptions.enableAccessibilityStatementLink.lastIndexOf('/') + 1)) 638 .addClass('scaccessibletoolbarstatement'); 639 if(screenReaderOptions.enableDarkMode) { 640 toolbarDivAccessibilityStatement.addClass('darkmode'); 641 } 642 $('#sc-section-audio').append(toolbarDivAccessibilityStatement); 643 } 644 } 645 646 if(screenReaderOptions.fontsize) { 647 // Inject del font size + 648 var increaseButton = $('<button/>').attr({'id':'fr_screenreader_font_increase', 649 'title': ' - ' + fr_increase + ' - ', 650 'aria-label': ' - ' + fr_increase + ' - ', 651 'role' : 'button', 652 'accesskey':screenReaderOptions.accesskey_increase, 653 'data-value':'1'}) 654 .addClass('sizable') 655 .text(fr_increase); 656 speakerDiv.append(increaseButton); 657 658 // Inject del font size - 659 var decreaseButton = $('<button/>').attr({'id':'fr_screenreader_font_decrease', 660 'title': ' - ' + fr_decrease + ' - ', 661 'aria-label': ' - ' + fr_decrease + ' - ', 662 'role' : 'button', 663 'accesskey':screenReaderOptions.accesskey_decrease, 664 'data-value':'-1'}) 665 .addClass('sizable') 666 .text(fr_decrease); 667 speakerDiv.append(decreaseButton); 668 669 // Inject del font size reset 670 var resetButton = $('<button/>').attr({ 'id':'fr_screenreader_font_reset', 671 'title': ' - ' + fr_reset + ' - ', 672 'aria-label': ' - ' + fr_reset + ' - ', 673 'role' : 'button', 674 'accesskey':screenReaderOptions.accesskey_reset, 675 'data-value':'0'}) 676 .addClass('resizable') 677 .text(fr_reset); 678 speakerDiv.append(resetButton); 679 680 // Add extra control labels for the accessible template 681 if(screenReaderOptions.template == 'accessible.css') { 682 var fontSizeDefault = screenReaderOptions.fontsizeDefault; 683 var sessionFontSizeCurrent = screenReaderStorage.getItem('scr_fontsize'); 684 var currentCalculatedIncrement = parseInt(fontSizeCurrent - fontSizeDefault); 685 var incrementSign = currentCalculatedIncrement > 0 ? '+' : '-'; 686 if(currentCalculatedIncrement != 0) { 687 if(currentCalculatedIncrement > 0) { 688 $('#fr_screenreader_font_increase').append('<div class="scfontsizelabel">' + incrementSign + currentCalculatedIncrement + '%</div>'); 689 } else { 690 $('#fr_screenreader_font_decrease').append('<div class="scfontsizelabel">' + currentCalculatedIncrement + '%</div>'); 691 } 692 } 693 694 // Rebuild the section if the accessible template is active
695 var sectionFontsizeContainer = $('<div/>').attr({ 'id':'sc-section-fontsize' }) 696 .addClass('sc-section'); 697 698 sectionFontsizeContainer.append('<div class="sc-section-title" aria-label="' + fr_font_sizing + '">' + fr_font_sizing + '</div>'); 699 speakerDiv.append(sectionFontsizeContainer); 700 var fontsizeSectionButtons = $('#fr_screenreader_font_increase, #fr_screenreader_font_decrease, #fr_screenreader_font_reset').clone(true, true).end().remove(); 701 sectionFontsizeContainer.append(fontsizeSectionButtons); 702 } 703 } 704 705 // Append del font family toggle 706 if(screenReaderOptions.dyslexicFont) { 707 // Inject del font size reset 708 var fontFamilyButton = $('<button/>').attr({'id':'fr_screenreader_font_family', 709 'title': ' - ' + fr_dyslexic_title + ' - ', 710 'aria-label': ' - ' + fr_dyslexic_title + ' - ', 711 'role' : 'button', 712 'accesskey':screenReaderOptions.accesskey_dyslexic, 713 'data-value':'0'}) 714 .text(fr_dyslexic_title); 715 speakerDiv.append(fontFamilyButton); 716 } 717 718 // Append del gray hues button 719 if(screenReaderOptions.grayHues) { 720 // Inject del font size reset 721 var grayHuesButton = $('<button/>').attr({'id':'fr_screenreader_gray_hues', 722 'title': ' - ' + fr_gray_hues + ' - ', 723 'aria-label': ' - ' + fr_gray_hues + ' - ', 724 'role' : 'button', 725 'accesskey':screenReaderOptions.accesskey_grayhues}) 726 .text(fr_gray_hues); 727 speakerDiv.append(grayHuesButton); 728 } 729 730 // Append del big cursor 731 if(screenReaderOptions.bigCursor) { 732 var bigCursorButton = $('<button/>').attr({ 'id':'fr_screenreader_bigcursor', 733 'title': ' - ' + fr_big_cursor + ' - ', 734 'aria-label': ' - ' + fr_big_cursor + ' - ', 735 'role' : 'button', 736 'accesskey':screenReaderOptions.accesskey_bigcursor}) 737 .text(fr_big_cursor); 738 speakerDiv.append(bigCursorButton); 739 } 740 741 // Append della reading guide 742 if(screenReaderOptions.readingGuides) { 743 // Inject del font size reset 744 var readingGuidesButton = $('<button/>').attr({'id':'fr_screenreader_reading_guides', 745 'title': ' - ' + fr_reading_guides_title + ' - ', 746 'aria-label': ' - ' + fr_reading_guides_title + ' - ', 747 'role' : 'button', 748 'accesskey':screenReaderOptions.accesskey_reading_guides}) 749 .text(fr_reading_guides_title); 750 speakerDiv.append(readingGuidesButton); 751 752 // Init reading guides 753 readingGuides = !!parseInt(screenReaderStorage.getItem('scr_reading_guides')) || 0; 754 if(readingGuides) { 755 $('#fr_screenreader_reading_guides').trigger('click', 1); 756 } 757 } 758 759 // Append del read mode 760 if(screenReaderOptions.readability) { 761 // Inject del font size reset 762 var readabilityButton = $('<button/>').attr({'id':'fr_screenreader_readability', 763 'title': ' - ' + fr_readability_title + ' - ', 764 'aria-label': ' - ' + fr_readability_title + ' - ', 765 'role' : 'button', 766 'accesskey':screenReaderOptions.accesskey_readability}) 767 .text(fr_readability_title); 768 speakerDiv.append(readabilityButton); 769 } 770 771 // Append dell'hide images 772 if(screenReaderOptions.hideImages) { 773 // Inject del font size reset 774 var hideimagesButton = $('<button/>').attr({'id':'fr_screenreader_hideimages', 775 'title': ' - ' + fr_hideimages_title + ' - ', 776 'aria-label': ' - ' + fr_hideimages_title + ' - ', 777 'role' : 'button', 778 'accesskey':screenReaderOptions.accesskey_hideimages}) 779 .text(fr_hideimages_title); 780 speakerDiv.append(hideimagesButton); 781 782 // Init reading guides 783 hideImages = !!parseInt(screenReaderStorage.getItem('scr_hideimages')) || 0; 784 if(hideImages) { 785 $('#fr_screenreader_hideimages').trigger('click', 1); 786 } 787 } 788 789 // Append dello skip to contents 790 if(screenReaderOptions.showSkipToContents) { 791 // Inject del font size reset 792 var skipToContentsButton = $('<button/>').attr({'id':'fr_screenreader_skiptocontents', 793 'title': ' - ' + fr_showskiptocontents_title + ' - ', 794 'aria-label': ' - ' + fr_showskiptocontents_title + ' - ', 795 'role' : 'button', 796 'accesskey':screenReaderOptions.accesskey_skiptocontents}) 797 .text(fr_showskiptocontents_title); 798 speakerDiv.append(skipToContentsButton); 799 } 800 801 // Append dello spacing increase/decrease 802 if(screenReaderOptions.spacingSize) { 803 // Inject del font size + 804 var spacingIncreaseButton = $('<button/>').attr({ 'id':'fr_screenreader_spacing_increase', 805 'title': ' - ' + fr_spacing_increase + ' - ', 806 'aria-label': ' - ' + fr_spacing_increase + ' - ', 807 'role' : 'button', 808 'accesskey':screenReaderOptions.accesskey_spacingsize_increase, 809 'data-value':'1'}) 810 .addClass('spacingsizable') 811 .text(fr_spacing_increase); 812 speakerDiv.append(spacingIncreaseButton); 813 814 // Inject del font size - 815 var spacingDecreaseButton = $('<button/>').attr({ 'id':'fr_screenreader_spacing_decrease', 816 'title': ' - ' + fr_spacing_decrease + ' - ', 817 'aria-label': ' - ' + fr_spacing_decrease + ' - ', 818 'role' : 'button', 819 'accesskey':screenReaderOptions.accesskey_spacingsize_decrease, 820 'data-value':'-1'}) 821 .addClass('spacingsizable') 822 .text(fr_spacing_decrease); 823 speakerDiv.append(spacingDecreaseButton); 824 825 // Add extra control labels for the accessible template 826 if(screenReaderOptions.template == 'accessible.css') {
827 var sessionSpacingSizeCurrent = screenReaderStorage.getItem('scr_spacingsize'); 828 if(sessionSpacingSizeCurrent) { 829 if(sessionSpacingSizeCurrent > 0) { 830 $('#fr_screenreader_spacing_increase').append('<div class="scspacingsizelabel">+' + (sessionSpacingSizeCurrent * 10) + '%</div>'); 831 } 832 } 833 } 834 } 835 836 // Add extra control labels for the accessible template 837 if(screenReaderOptions.template == 'accessible.css') { 838 // Rebuild the section if the accessible template is active 839 var enhancementsSectionButtons = $('#fr_screenreader_font_family, #fr_screenreader_gray_hues, #fr_screenreader_bigcursor, #fr_screenreader_reading_guides, #fr_screenreader_readability, #fr_screenreader_hideimages, #fr_screenreader_skiptocontents, #fr_screenreader_spacing_increase, #fr_screenreader_spacing_decrease').clone(true, true).end().remove(); 840 if(enhancementsSectionButtons.length) { 841 var sectionEnhancementContainer = $('<div/>').attr({ 'id':'sc-section-enhancement' }) 842 .addClass('sc-section'); 843 sectionEnhancementContainer.append('<div class="sc-section-title" aria-label="' + fr_accessibility_enhancements + '">' + fr_accessibility_enhancements + '</div>'); 844 speakerDiv.append(sectionEnhancementContainer); 845 sectionEnhancementContainer.append(enhancementsSectionButtons); 846 } 847 } 848 849 // Append del page zoom 850 if(screenReaderOptions.pageZoom && screenReaderOptions.templateOrientation == 'vertical') { 851 // Increase del page zoom + 852 var pagezoomIncreaseButton = $('<button/>').attr({ 'id':'fr_screenreader_pagezoom_increase', 853 'title': ' - ' + fr_pagezoom_increase + ' - ', 854 'aria-label': ' - ' + fr_pagezoom_increase + ' - ', 855 'role' : 'button', 856 'accesskey':screenReaderOptions.accesskey_pagezoomsize_increase, 857 'data-value':'1'}) 858 .addClass('zoomsizable') 859 .text(fr_pagezoom_increase); 860 speakerDiv.append(pagezoomIncreaseButton); 861 862 // Decrease del page zoom - 863 var pagezoomDecreaseButton = $('<button/>').attr({ 'id':'fr_screenreader_pagezoom_decrease', 864 'title': ' - ' + fr_pagezoom_decrease + ' - ', 865 'aria-label': ' - ' + fr_pagezoom_decrease + ' - ', 866 'role' : 'button', 867 'accesskey':screenReaderOptions.accesskey_pagezoomsize_decrease, 868 'data-value':'-1'}) 869 .addClass('zoomsizable') 870 .text(fr_pagezoom_decrease); 871 speakerDiv.append(pagezoomDecreaseButton); 872 873 // Reset del page zoom 874 var pagezoomResetButton = $('<button/>').attr({ 'id':'fr_screenreader_pagezoom_reset', 875 'title': ' - ' + fr_pagezoom_reset + ' - ', 876 'aria-label': ' - ' + fr_pagezoom_reset + ' - ', 877 'role' : 'button', 878 'accesskey':screenReaderOptions.accesskey_pagezoomsize_reset, 879 'data-value':'0'}) 880 .addClass('zoomresizable') 881 .text(fr_pagezoom_reset); 882 speakerDiv.append(pagezoomResetButton); 883 884 // Add extra control labels for the accessible template 885 if(screenReaderOptions.template == 'accessible.css') { 886 var zoomSizeDefault = 100; 887 var sessionZoomSizeCurrent = screenReaderStorage.getItem('scr_zoomsize'); 888 var currentCalculatedIncrement = parseInt(zoomSizeCurrent - zoomSizeDefault); 889 var incrementSign = currentCalculatedIncrement > 0 ? '+' : '-'; 890 if(currentCalculatedIncrement != 0) { 891 if(currentCalculatedIncrement > 0) { 892 $('#fr_screenreader_pagezoom_increase').append('<div class="sczoomsizelabel">' + incrementSign + currentCalculatedIncrement + '%</div>'); 893 } else { 894 $('#fr_screenreader_pagezoom_decrease').append('<div class="sczoomsizelabel">' + currentCalculatedIncrement + '%</div>'); 895 } 896 } 897 898 899 var sessionPageZoomCurrent = screenReaderStorage.getItem('scr_pagezoom'); 900 if(sessionPageZoomCurrent) { 901 if(sessionPageZoomCurrent > 0) { 902 $('#fr_screenreader_pagezoom_increase').append('<div class="scspacingsizelabel">+' + (sessionPageZoomCurrent * 10) + '%</div>'); 903 } 904 } 905
906 var sectionPageZoomContainer = $('<div/>').attr({ 'id':'sc-section-pagezoom' }) 907 .addClass('sc-section'); 908 909 sectionPageZoomContainer.append('<div class="sc-section-title" aria-label="' + fr_page_zoom + '">' + fr_page_zoom + '</div>'); 910 speakerDiv.append(sectionPageZoomContainer); 911 var pagezoomSectionButtons = $('#fr_screenreader_pagezoom_increase, #fr_screenreader_pagezoom_decrease, #fr_screenreader_pagezoom_reset').clone(true, true).end().remove(); 912 sectionPageZoomContainer.append(pagezoomSectionButtons); 913 } 914 } 915 916 var subtractWidthKonst = 0; 917 if(!screenReaderOptions.screenreader) { 918 subtractWidthKonst = 100; 919 } 920 if(!screenReaderOptions.dyslexicFont) { 921 subtractWidthKonst += 35; 922 } 923 if(!screenReaderOptions.grayHues) { 924 subtractWidthKonst += 35; 925 } 926 if(!screenReaderOptions.bigCursor) { 927 subtractWidthKonst += 35; 928 } 929 if(!screenReaderOptions.readingGuides) { 930 subtractWidthKonst += 35; 931 } 932 if(!screenReaderOptions.readability) { 933 subtractWidthKonst += 35; 934 } 935 if(!screenReaderOptions.showSkipToContents) { 936 subtractWidthKonst += 35; 937 } 938 if(!screenReaderOptions.spacingSize) { 939 subtractWidthKonst += 70; 940 } 941 942 if(screenReaderOptions.highcontrast) { 943 // Inject del default high contrast 944 var highContrastHtml = $('<button/>').attr({'id':'fr_screenreader_highcontrast', 945 'title': ' - ' + fr_highcontrast + ' - ', 946 'aria-label': ' - ' + fr_highcontrast + ' - ', 947 'role' : 'button', 948 'accesskey':screenReaderOptions.accesskey_highcontrast, 949 'data-value':'0'}) 950 .text(fr_highcontrast); 951 if(highContrast) { 952 highContrastHtml.addClass('active'); 953 } 954 speakerDiv.append(highContrastHtml); 955 956 // Check if the alternate high contrast mode is enabled and supported 957 var alternateHighContrast = screenReaderOptions.highcontrastAlternate && css3FilterFeatureDetect(); 958 if(alternateHighContrast) { 959 // Inject del mode2 high contrast 960 var highContrast2 = $('<button/>').attr({'id':'fr_screenreader_highcontrast2', 961 'title': ' - ' + fr_highcontrast + ' - ', 962 'aria-label': ' - ' + fr_highcontrast + ' - ', 963 'role' : 'button', 964 'accesskey':screenReaderOptions.accesskey_highcontrast2, 965 'data-alternate':'1', 966 'data-rotate':screenReaderOptions.colorHue}) 967 .text(fr_highcontrast); 968 speakerDiv.append(highContrast2); 969 970 // Inject del mode3 high contrast 971 var highContrast3 = $('<button/>').attr({'id':'fr_screenreader_highcontrast3', 972 'title': ' - ' + fr_highcontrast + ' - ', 973 'aria-label': ' - ' + fr_highcontrast + ' - ', 974 'role' : 'button', 975 'accesskey':screenReaderOptions.accesskey_highcontrast3, 976 'data-alternate':'2', 977 'data-rotate':screenReaderOptions.colorHue, 978 'data-brightness':screenReaderOptions.colorBrightness}) 979 .text(fr_highcontrast); 980 speakerDiv.append(highContrast3); 981 982 // Init high contrast alternate 983 highContrastAlternate = parseInt(screenReaderStorage.getItem('scr_highcontrast_alternate')) || 0; 984 if(highContrastAlternate) { 985 var targetHightContrastAlternateButtonMode = $('button[data-alternate=' + highContrastAlternate + ']').addClass('active'); 986 applyAlternateHighContrast.call(targetHightContrastAlternateButtonMode); 987 } 988 } 989 990 // Rule the bar size width 991 if(screenReaderOptions.fontsize && alternateHighContrast) { 992 $('#speaker_plugin').css('width', parseInt(605 - subtractWidthKonst) + 'px'); 993 } else if(screenReaderOptions.fontsize){ 994 $('#speaker_plugin').css('width', parseInt(540 - subtractWidthKonst) + 'px'); 995 } else if(!screenReaderOptions.fontsize && alternateHighContrast) { 996 $('#speaker_plugin').css('width', parseInt(510 - subtractWidthKonst) + 'px'); 997 } 998 999 // Add extra control labels for the accessible template 1000 if(screenReaderOptions.template == 'accessible.css') { 1001 // Rebuild the section if the accessible template is active
1002 var sectionHighcontrastContainer = $('<div/>').attr({ 'id':'sc-section-highcontrast' }) 1003 .addClass('sc-section'); 1004 1005 sectionHighcontrastContainer.append('<div class="sc-section-title" aria-label="' + fr_high_contrast_color + '">' + fr_high_contrast_color + '</div>'); 1006 speakerDiv.append(sectionHighcontrastContainer); 1007 var highcontrastSectionButtons = $('#fr_screenreader_highcontrast, #fr_screenreader_highcontrast2, #fr_screenreader_highcontrast3').clone(true, true).end().remove(); 1008 sectionHighcontrastContainer.append(highcontrastSectionButtons); 1009 } 1010 } else { 1011 if(screenReaderOptions.fontsize) { 1012 $('#speaker_plugin').css('width', parseInt(425 - subtractWidthKonst) + 'px'); 1013 } else { 1014 $('#speaker_plugin').css('width', parseInt(315 - subtractWidthKonst) + 'px'); 1015 } 1016 } 1017 1018 // Add colors palette to the accessibility template 1019 if(screenReaderOptions.customColors && screenReaderOptions.template == 'accessible.css') { 1020 // Inject del font size + 1021 var changeTextColor = $('<button/>').attr({'id':'fr_screenreader_text_color', 1022 'title': ' - ' + fr_text_color + ' - ', 1023 'aria-label': ' - ' + fr_text_color + ' - ', 1024 'role' : 'button', 1025 'accesskey':screenReaderOptions.accesskey_change_text_color}) 1026 .addClass('scr-custom-color') 1027 .text(fr_text_color); 1028 speakerDiv.append(changeTextColor); 1029 1030 // Inject del font size - 1031 var changeBackgroundColor = $('<button/>').attr({'id':'fr_screenreader_background_color', 1032 'title': ' - ' + fr_background_color + ' - ', 1033 'aria-label': ' - ' + fr_background_color + ' - ', 1034 'role' : 'button', 1035 'accesskey':screenReaderOptions.accesskey_change_background_color}) 1036 .addClass('scr-custom-color') 1037 .text(fr_background_color); 1038 speakerDiv.append(changeBackgroundColor); 1039 1040 var colorsMiniButtons = ` 1041 <div class="scr-color-box"> 1042 <span data-scr-color="#0076B4" class="scr-color-selection" role="button" tabindex="0" aria-label="${fr_background_color_desc}" style="background-color: #0076B4 !important;"> </span> 1043 <span data-scr-color="#7A549C" class="scr-color-selection" role="button" tabindex="0" aria-label="${fr_background_color_desc}" style="background-color: #7A549C !important;"> </span> 1044 <span data-scr-color="#C83733" class="scr-color-selection" role="button" tabindex="0" aria-label="${fr_background_color_desc}" style="background-color: #C83733 !important;"> </span> 1045 <span data-scr-color="#D07021" class="scr-color-selection" role="button" tabindex="0" aria-label="${fr_background_color_desc}" style="background-color: #D07021 !important;"> </span> 1046 <span data-scr-color="#26999F" class="scr-color-selection" role="button" tabindex="0" aria-label="${fr_background_color_desc}" style="background-color: #26999F !important;"> </span> 1047 <span data-scr-color="#4D7831" class="scr-color-selection" role="button" tabindex="0" aria-label="${fr_background_color_desc}" style="background-color: #4D7831 !important;"> </span> 1048 <span data-scr-color="#ffffff" class="scr-color-selection" role="button" tabindex="0" aria-label="${fr_background_color_desc}" style="background-color: #ffffff !important;"> </span> 1049 <span data-scr-color="#000000" class="scr-color-selection" role="button" tabindex="0" aria-label="${fr_background_color_desc}" style="background-color: #000000 !important;"> </span> 1050 <div class="scr-color-selection-reset" role="button" tabindex="0"> ${fr_custom_color_reset} </div> 1051 </div>`; 1052 $('button.scr-custom-color').append(colorsMiniButtons); 1053 1054 // Rebuild the section if the accessible template is active
1055 var sectionCustomcolorsContainer = $('<div/>').attr({'id':'sc-section-customcolors'}) 1056 .addClass('sc-section'); 1057 1058 sectionCustomcolorsContainer.append('<div class="sc-section-title" aria-label="' + fr_custom_colors + '">' + fr_custom_colors + '</div>'); 1059 speakerDiv.append(sectionCustomcolorsContainer); 1060 var customColorsSectionButtons = $('#fr_screenreader_text_color, #fr_screenreader_background_color').clone(true, true).end().remove(); 1061 sectionCustomcolorsContainer.append(customColorsSectionButtons); 1062 1063 // Bind event change color, apply the color based on the container type 1064 $('span.scr-color-selection').on('click', function(jqEvent){ 1065 var colorButton = $(this); 1066 var parentType = colorButton.parents('button.scr-custom-color').attr('id'); 1067 var selectedColor = colorButton.data('scr-color'); 1068 1069 if(parentType == 'fr_screenreader_text_color') { 1070 if(colorButton.hasClass('scr-color-selection-enabled')) { 1071 document.querySelector('body').style.removeProperty('color'); 1072 if(screenReaderOptions.customColorsCssSelectors) { 1073 $(screenReaderOptions.customColorsCssSelectors).each(function(index, element){ 1074 element.style.removeProperty('color'); 1075 });
1076 } 1077 colorButton.removeClass('scr-color-selection-enabled'); 1078 } else { 1079 document.querySelector('body').style.setProperty('color', selectedColor, 'important'); 1080 if(screenReaderOptions.customColorsCssSelectors) { 1081 $(screenReaderOptions.customColorsCssSelectors).each(function(index, element){ 1082 element.style.setProperty('color', selectedColor, 'important'); 1083 }); 1084 } 1085 $('#fr_screenreader_text_color span.scr-color-selection').removeClass('scr-color-selection-enabled'); 1086 colorButton.addClass('scr-color-selection-enabled'); 1087 } 1088 screenReaderStorage.setItem('scr_custom_text_color', selectedColor); 1089 } else if(parentType == 'fr_screenreader_background_color') { 1090 if(colorButton.hasClass('scr-color-selection-enabled')) { 1091 document.querySelector('body').style.removeProperty('background'); 1092 if(screenReaderOptions.customColorsCssSelectors) { 1093 $(screenReaderOptions.customColorsCssSelectors).each(function(index, element){ 1094 element.style.removeProperty('background'); 1095 });
1096 } 1097 colorButton.removeClass('scr-color-selection-enabled'); 1098 } else { 1099 document.querySelector('body').style.setProperty('background', selectedColor, 'important'); 1100 if(screenReaderOptions.customColorsCssSelectors) { 1101 $(screenReaderOptions.customColorsCssSelectors).each(function(index, element){ 1102 element.style.setProperty('background', selectedColor, 'important'); 1103 }); 1104 } 1105 $('#fr_screenreader_background_color span.scr-color-selection').removeClass('scr-color-selection-enabled'); 1106 colorButton.addClass('scr-color-selection-enabled'); 1107 } 1108 screenReaderStorage.setItem('scr_custom_background_color', selectedColor); 1109 } 1110 }); 1111 1112 $('div.scr-color-selection-reset').on('click', function(jqEvent){ 1113 var resetButton = $(this); 1114 var parentType = resetButton.parents('button.scr-custom-color').attr('id'); 1115 1116 if(parentType == 'fr_screenreader_text_color') { 1117 $('span.scr-color-selection').removeClass('scr-color-selection-enabled'); 1118 document.querySelector('body').style.removeProperty('color'); 1119 if(screenReaderOptions.customColorsCssSelectors) { 1120 $(screenReaderOptions.customColorsCssSelectors).each(function(index, element){ 1121 element.style.removeProperty('color'); 1122 }); 1123 } 1124 screenReaderStorage.removeItem('scr_custom_text_color'); 1125 } else if(parentType == 'fr_screenreader_background_color') { 1126 $('span.scr-color-selection').removeClass('scr-color-selection-enabled'); 1127 document.querySelector('body').style.removeProperty('background'); 1128 if(screenReaderOptions.customColorsCssSelectors) { 1129 $(screenReaderOptions.customColorsCssSelectors).each(function(index, element){ 1130 element.style.removeProperty('background'); 1131 }); 1132 } 1133 screenReaderStorage.removeItem('scr_custom_background_color'); 1134 } 1135 }); 1136 1137 // Rotate button click 1138 $('#fr_screenreader_background_color, #fr_screenreader_text_color').on('click', function(jqEvent){ 1139 jqEvent.stopPropagation(); 1140 if(!$(jqEvent.target).hasClass('scr-custom-color') && !$(jqEvent.target).hasClass('scr-color-box')) { 1141 return false; 1142 } 1143 if($(jqEvent.target).hasClass('scr-custom-color')) { 1144 var selectedContainerButton = $(jqEvent.target); 1145 } else { 1146 var selectedContainerButton = $(jqEvent.target).parent('button.scr-custom-color'); 1147 } 1148 var selectedColorButton = $('span.scr-color-selection.scr-color-selection-enabled', selectedContainerButton); 1149 if(selectedColorButton.length) { 1150 selectedColorButton.next().trigger('click'); 1151 } else { 1152 $('div.scr-color-box > span:first-child', selectedContainerButton).trigger('click'); 1153 } 1154 }); 1155 } 1156 1157 // Manage background color for the toolbar or buttons only based on the template 1158 if(screenReaderOptions.template == 'custom.css') { 1159 $('#speaker_plugin button').css('background-color', screenReaderOptions.toolbarBgcolor); 1160 $('#accessibility-links').css('background', 'transparent'); 1161 } else { 1162 $('#accessibility-links').css('background-color', screenReaderOptions.toolbarBgcolor); 1163 } 1164 1165 if(screenReaderOptions.templateOrientation == 'vertical' && screenReaderOptions.template != 'custom.css') { 1166 $('#text_plugin').css('background-color', screenReaderOptions.toolbarBgcolor); 1167 } 1168 1169 var toolbarBgcolorCompare = screenReaderOptions.toolbarBgcolor.toLowerCase(); 1170 if(screenReaderOptions.template == 'accessible.css' && (toolbarBgcolorCompare == '#eeeeee' || toolbarBgcolorCompare == '#eee')) { 1171 $('#text_plugin').css('background-color', '#004fce'); 1172 $('#toolbar_plugin').css('background-color', '#004fce'); 1173 $('#toolbar_closer').css({'color':'transparent', 'text-shadow':'#004fce 0px 0px 0px'}); 1174 $('svg.scr-darkmode').css('fill', '#004fce'); 1175 } 1176 1177 if(screenReaderOptions.template == 'accessible.css' && toolbarBgcolorCompare != '#eeeeee' && toolbarBgcolorCompare != '#eee') { 1178 $('#accessibility-links').css('background-color', '#EEEEEE'); 1179 $('#text_plugin').css('background-color', screenReaderOptions.toolbarBgcolor); 1180 $('#toolbar_plugin').css('background-color', screenReaderOptions.toolbarBgcolor); 1181 $('#toolbar_closer').css({'color':'transparent', 'text-shadow': (screenReaderOptions.toolbarBgcolor + ' 0px 0px 0px')}); 1182 $('svg.scr-darkmode').css('fill', screenReaderOptions.toolbarBgcolor); 1183 } 1184 1185 if(screenReaderOptions.template == 'accessible.css') { 1186 $('#speaker_plugin > button').removeAttr('title'); 1187 var currentLabelTitle = $('#text_plugin').attr('title'); 1188 $('#text_plugin').removeAttr('title'); 1189 $('#text_plugin').attr('aria-label', currentLabelTitle).attr('role', 'button'); 1190 } 1191 1192 // Manage the visibility state of the toolbar 1193 if(screenReaderConfigOptions.useMinimizedToolbar && 1194 (screenReaderConfigOptions.mobileDeviceDetected || (!screenReaderConfigOptions.mobileDeviceDetected && !screenReaderConfigOptions.minimizedToolbarOnlyMobile))) { 1195 var volumeWidth = $('#volume_plugin').width(); 1196 var speakerWidth = $('#speaker_plugin').width(); 1197 var containerWidth = $('#accessibility-links').width(); 1198 var totalWidthToOffset = parseInt(volumeWidth + speakerWidth);
1199 if(screenReaderOptions.templateOrientation == 'vertical') { 1200 totalWidthToOffset = parseInt(speakerWidth); 1201 } 1202 if(screenReaderOptions.template == 'accessible.css') { 1203 totalWidthToOffset = parseInt(containerWidth); 1204 } 1205 var targetState = 0; 1206 var targetSign = '+'; 1207 var windowWidth = $(window).width(); 1208 if(windowWidth < totalWidthToOffset && screenReaderOptions.template != 'accessible.css') { 1209 totalWidthToOffset = windowWidth - 80; 1210 } 1211 1212 $('#text_plugin').attr('accesskey', screenReaderConfigOptions.accesskey_minimized); 1213 1214 $('#toolbar_closer').on('click', function(jqEvent){ 1215 $('#text_plugin').trigger('click'); 1216 }); 1217 1218 // Manage the lazy loaded fontsize when opening the minimized toolbar 1219 if(screenReaderOptions.fontsizeMinimizedToolbar && screenReaderConfigOptions.statusMinimizedToolbar == 'closed') { 1220 $('#text_plugin').on('click', function(jqEvent){ 1221 $('body').css('font-size', fontSizeCurrent + '%'); 1222 1223 if(screenReaderOptions.fontsizeSelector) { 1224 fontSizeInline = ';font-size:100%'; 1225 // Manage header resize increment 1226 fontResizeHeaders(); 1227 } 1228 1229 var fontsizingElements = $(screenReaderOptions.fontsizeSelector); 1230 $.each(fontsizingElements, function(index, elem) { 1231 if($.inArray(elem.nodeName.toLowerCase(), ['h1', 'h2', 'h3', 'h4']) != -1) { 1232 fontSizeInline = ''; 1233 } else { 1234 fontSizeInline = ';font-size:100%'; 1235 } 1236 var currentInlineStyles = $(elem).attr('style') || ''; 1237 $(elem).attr('style', rtrim(currentInlineStyles, ';') + fontSizeInline); 1238 }); 1239 }); 1240 } 1241 1242 // Identify the side to offest based on the positionment 1243 switch(screenReaderOptions.position) { 1244 case 'bottomright': 1245 case 'topright': 1246 $('#text_plugin span.fr_label').css({'cursor':'pointer'}); 1247 if(screenReaderConfigOptions.statusMinimizedToolbar == 'closed') { 1248 $('#accessibility-links').css({'right':'-' + (totalWidthToOffset + 2) + 'px'}).removeClass('screenreader-open').addClass('screenreader-closed'); 1249 1250 // Reposition the button in the accessible.css template on window resize or orientation change on mobile 1251 if(screenReaderOptions.template == 'accessible.css') { 1252 $(window).on('resize', function() { 1253 let accessibilityLinksMainContainer = $('#accessibility-links.screenreader-closed'); 1254 if(accessibilityLinksMainContainer.length) { 1255 let containerWidth = accessibilityLinksMainContainer.width(); 1256 totalWidthToOffset = parseInt(containerWidth); 1257 $('#accessibility-links').css({'right':'-' + (totalWidthToOffset + 2) + 'px'}); 1258 } 1259 }) 1260 } 1261 } else { 1262 $('#speaker_plugin, #volume_plugin').css({'opacity':1}); 1263 $('#accessibility-links').removeClass('screenreader-closed').addClass('screenreader-open'); 1264 targetState = totalWidthToOffset + 2; 1265 targetSign = '-'; 1266 } 1267 1268 $('#text_plugin').css({'cursor':'pointer'}).on('click', function(jqEvent){ 1269 if(targetState) { 1270 $('#speaker_plugin, #volume_plugin').animate({'opacity': 0}, 300); 1271 } 1272 $('#accessibility-links').animate({'right': (targetSign + targetState)}, 500, function(){ 1273 targetState = targetState ? 0 : totalWidthToOffset + 2; 1274 targetSign = '+' ? '-' : '+'; 1275 if(targetState) { 1276 $(this).removeClass('screenreader-closed').addClass('screenreader-open'); 1277 } else { 1278 $(this).removeClass('screenreader-open').addClass('screenreader-closed'); 1279 } 1280 }); 1281 if(!targetState) { 1282 setTimeout(function(){ 1283 $('#speaker_plugin, #volume_plugin').animate({'opacity': 1}, 300); 1284 }, 300); 1285 } 1286 }); 1287 break; 1288 1289 case 'topleft': 1290 case 'bottomleft' : 1291 if(screenReaderConfigOptions.statusMinimizedToolbar == 'closed') { 1292 $('#accessibility-links').removeClass('screenreader-open').addClass('screenreader-closed'); 1293 $('#volume_plugin,#speaker_plugin,#toolbar_plugin').hide(); 1294 } else { 1295 $('#accessibility-links').removeClass('screenreader-closed').addClass('screenreader-open'); 1296 } 1297 $('#text_plugin span.fr_label').css({'cursor':'pointer'}); 1298 $('#text_plugin').css({'cursor':'pointer'}).on('click', function(jqEvent){ 1299 $('#volume_plugin,#speaker_plugin,#toolbar_plugin').fadeToggle(); 1300 if($('#accessibility-links').hasClass('screenreader-open')) { 1301 $('#accessibility-links').removeClass('screenreader-open').addClass('screenreader-closed'); 1302 } else { 1303 $('#accessibility-links').removeClass('screenreader-closed').addClass('screenreader-open'); 1304 } 1305 });
1306 break; 1307 } 1308 } else { 1309 switch(screenReaderOptions.position) { 1310 case 'bottomright': 1311 case 'topright': 1312 $('#speaker_plugin, #volume_plugin').css({'opacity':1}); 1313 break; 1314 } 1315 } 1316 1317 // Manage the clone moving of controls to the bottom of the toolbar for the sidebar optimal displacement 1318 if(screenReaderOptions.template != 'accessible.css' && screenReaderOptions.templateOrientation == 'vertical') { 1319 var $originalTextPlugin = $('#text_plugin'); 1320 var $originalVolumePlugin = $('#volume_plugin'); 1321 var $clonedTextPlugin = $originalTextPlugin.clone(true, true); 1322 var $clonedVolumePlugin = $originalVolumePlugin.clone(true, true); 1323 var $targetSpeakerPlugin = $('#scr_clearer').before($clonedTextPlugin).before($clonedVolumePlugin); 1324 $originalTextPlugin.remove(); 1325 $originalVolumePlugin.remove(); 1326 } 1327 }; 1328 1329 /** 1330 * Start TTS engine 1331 * 1332 * @access public 1333 * @return Boolean 1334 */ 1335 this.startTTSEngine = function() { 1336 // Instance TTS 1337 frTTSEngineInstance = new jQuery.frTTSEngine(screenReaderOptions); 1338 1339 return true; 1340 }; 1341 1342 /** 1343 * Add plugin buttons event listeners 1344 * 1345 * @access private 1346 * @return Void 1347 */ 1348 var addListeners = function() { 1349 // Register events for sizable 1350 $(document).on('click', '.sizable', function(jqEvent){ 1351 var increment = parseInt($(this).data('value')); 1352 fontSizeCurrent = parseInt(fontSizeCurrent) + parseInt(increment * 5); 1353 1354 if(fontSizeCurrent > screenReaderOptions.fontsizeMax){ 1355 fontSizeCurrent = screenReaderOptions.fontsizeMax; 1356 } else if (fontSizeCurrent < screenReaderOptions.fontsizeMin){ 1357 fontSizeCurrent = screenReaderOptions.fontsizeMin; 1358 } 1359 1360 $('body').css('font-size', fontSizeCurrent + '%'); 1361 if(screenReaderOptions.fontsizeSelector) { 1362 // Manage header resize increment 1363 fontResizeHeaders(); 1364 } 1365 screenReaderStorage.setItem('scr_fontsize', fontSizeCurrent); 1366 1367 // Add extra control labels for the accessible template 1368 if(screenReaderOptions.template == 'accessible.css') { 1369 var currentCalculatedIncrement = parseInt(fontSizeCurrent - fontSizeDefault); 1370 var incrementSign = currentCalculatedIncrement > 0 ? '+' : '-'; 1371 if(currentCalculatedIncrement != 0) { 1372 $('div.scfontsizelabel').remove(); 1373 if(currentCalculatedIncrement > 0) { 1374 $('#fr_screenreader_font_increase').append('<div class="scfontsizelabel">' + incrementSign + currentCalculatedIncrement + '%</div>'); 1375 } else { 1376 $('#fr_screenreader_font_decrease').append('<div class="scfontsizelabel">' + currentCalculatedIncrement + '%</div>'); 1377 } 1378 } else { 1379 $('div.scfontsizelabel').remove(); 1380 } 1381 } 1382 }); 1383 1384 // Register events for reset 1385 $(document).on('click', '.resizable', function(jqEvent){ 1386 if(screenReaderOptions.resetButtonBehavior == 'all') { 1387 var resetHighContrast = !!parseInt(screenReaderStorage.getItem('scr_highcontrast')) || 0; 1388 if(resetHighContrast) { 1389 $('#fr_screenreader_highcontrast').trigger('click'); 1390 } else { 1391 var resetHighContrastAlternate = parseInt(screenReaderStorage.getItem('scr_highcontrast_alternate')) || 0; 1392 if(resetHighContrastAlternate == 1) { 1393 $('#fr_screenreader_highcontrast2').trigger('click'); 1394 } 1395 if(resetHighContrastAlternate == 2) { 1396 $('#fr_screenreader_highcontrast3').trigger('click'); 1397 } 1398 } 1399 1400 var resetDyslexicFont = !!parseInt(screenReaderStorage.getItem('scr_dyslexicfont')) || 0; 1401 if(resetDyslexicFont) { 1402 $('#fr_screenreader_font_family').trigger('click'); 1403 } 1404 1405 var resetGrayHues = !!parseInt(screenReaderStorage.getItem('scr_grayhues')) || 0; 1406 if(resetGrayHues) { 1407 $('#fr_screenreader_gray_hues').trigger('click'); 1408 } 1409 1410 var resetBigCursor = !!parseInt(screenReaderStorage.getItem('scr_bigcursor')) || 0; 1411 if(resetBigCursor) { 1412 $('#fr_screenreader_bigcursor').trigger('click'); 1413 } 1414 1415 var resetPageZoom = !!parseInt(screenReaderStorage.getItem('scr_zoomsize')) || 0; 1416 if(resetPageZoom) { 1417 $('#fr_screenreader_pagezoom_reset').trigger('click'); 1418 } 1419 } 1420 1421 // Remove extra control labels for the accessible template 1422 if(screenReaderOptions.template == 'accessible.css') { 1423 $('div.scfontsizelabel,div.scspacingsizelabel').remove(); 1424 } 1425 1426 fontSizeCurrent = fontSizeDefault; 1427 $('body').css('font-size', fontSizeDefault + '%'); 1428 1429 // Reset even text spacing if any 1430 var resetSpacingSize = !!parseInt(screenReaderStorage.getItem('scr_spacingsize')) || !!parseInt(screenReaderStorage.getItem('scr_final_lineheight')) || 0; 1431 var resetSpacingSizeInline = ''; 1432 if(resetSpacingSize) { 1433 $('body').css({'word-spacing':'inherit', 'letter-spacing':'inherit', 'line-height':screenReaderStorage.getItem('scr_initial_lineheight')}); 1434 resetSpacingSizeInline = ';word-spacing:inherit;letter-spacing:inherit;line-height:inherit'; 1435 screenReaderStorage.setItem('scr_spacingsize', 0); 1436 spacingSizeCurrent = 0; 1437 screenReaderStorage.removeItem('scr_final_lineheight'); 1438 } 1439 1440 if(screenReaderOptions.fontsizeSelector) { 1441 // Manage header resize increment 1442 fontResizeHeaders(); 1443 } 1444 screenReaderStorage.setItem('scr_fontsize', fontSizeDefault); 1445 }); 1446 1447 // Register events for zoomsizable 1448 $(document).on('click', '.zoomsizable', function(jqEvent){ 1449 var increment = parseInt($(this).data('value')); 1450 zoomSizeCurrent = parseInt(zoomSizeCurrent) + parseInt(increment * 5); 1451 1452 $('html').css('zoom', zoomSizeCurrent + '%'); 1453 screenReaderStorage.setItem('scr_zoomsize', zoomSizeCurrent); 1454 1455 // Add extra control labels for the accessible template 1456 if(screenReaderOptions.template == 'accessible.css') { 1457 var currentCalculatedIncrement = parseInt(zoomSizeCurrent - 100); 1458 var incrementSign = currentCalculatedIncrement > 0 ? '+' : '-'; 1459 if(currentCalculatedIncrement != 0) { 1460 $('div.sczoomsizelabel').remove(); 1461 if(currentCalculatedIncrement > 0) { 1462 $('#fr_screenreader_pagezoom_increase').append('<div class="sczoomsizelabel">' + incrementSign + currentCalculatedIncrement + '%</div>'); 1463 } else { 1464 $('#fr_screenreader_pagezoom_decrease').append('<div class="sczoomsizelabel">' + currentCalculatedIncrement + '%</div>'); 1465 } 1466 } else { 1467 $('div.sczoomsizelabel').remove(); 1468 } 1469 } 1470 }); 1471 1472 // Register events for reset 1473 $(document).on('click', '.zoomresizable', function(jqEvent){ 1474 // Remove extra control labels for the accessible template 1475 if(screenReaderOptions.template == 'accessible.css') { 1476 $('div.sczoomsizelabel').remove(); 1477 } 1478 1479 zoomSizeCurrent = 100;
1480 $('html').css('zoom', 100 + '%'); 1481 1482 screenReaderStorage.removeItem('scr_zoomsize', 100); 1483 }); 1484 1485 // Register events for sizable 1486 $(document).on('click', '.spacingsizable', function(jqEvent){ 1487 var increment = parseInt($(this).data('value')); 1488 var currentLineHeight = $('body').css('line-height'); 1489 spacingSizeCurrent = parseInt(spacingSizeCurrent) + parseInt(increment); 1490 if(parseFloat(currentLineHeight)) { 1491 lineHeightCurrent = parseFloat(currentLineHeight) + parseInt(increment); 1492 } else { 1493 lineHeightCurrent = 16 + parseInt(increment); 1494 } 1495 1496 if(spacingSizeCurrent > screenReaderOptions.spacingSizeMax){ 1497 spacingSizeCurrent = screenReaderOptions.spacingSizeMax; 1498 return; 1499 } else if (spacingSizeCurrent < screenReaderOptions.spacingSizeMin){ 1500 spacingSizeCurrent = screenReaderOptions.spacingSizeMin; 1501 return; 1502 } 1503 1504 // Store the initial line-height for later reset 1505 if(!screenReaderStorage.getItem('scr_initial_lineheight')) { 1506 screenReaderStorage.setItem('scr_initial_lineheight', currentLineHeight); 1507 } 1508 1509 $('body').css({'word-spacing': (spacingSizeCurrent * 2) + 'px', 'letter-spacing': spacingSizeCurrent + 'px', 'line-height': lineHeightCurrent + 'px'}); 1510 screenReaderStorage.setItem('scr_spacingsize', spacingSizeCurrent); 1511 screenReaderStorage.setItem('scr_final_lineheight', lineHeightCurrent); 1512 1513 // Add extra control labels for the accessible template 1514 if(screenReaderOptions.template == 'accessible.css') { 1515 if(spacingSizeCurrent > 0) { 1516 $('#fr_screenreader_spacing_increase').append('<div class="scspacingsizelabel">+' + (spacingSizeCurrent * 10) + '%</div>'); 1517 } else { 1518 $('div.scspacingsizelabel').remove(); 1519 } 1520 } 1521 }); 1522 1523 // Register events for high contrast button 1524 $(document).on('click', '#fr_screenreader_highcontrast', function(jqEvent){ 1525 highContrast = !!parseInt(screenReaderStorage.getItem('scr_highcontrast')) || 0; 1526 if(!highContrast) { 1527 if(highContrastAlternate) { 1528 // Recover original stylesheets 1529 targetFilterElement.attr('style', originalHTMLStyles); 1530 highContrastAlternate = 0; 1531 screenReaderStorage.setItem('scr_highcontrast_alternate', highContrastAlternate); 1532 } 1533 1534 targetFilterElement.addClass('scr_highcontrast'); 1535 1536 // If IE detected, fallback on the compatibility inversion color high contrast 1537 if("ActiveXObject" in window) { 1538 startLegacyHighcontrast('body, body *:not(#accessibility-links, #accessibility-links *, div.shapes)'); 1539 } 1540 1541 $('#fr_screenreader_highcontrast2,#fr_screenreader_highcontrast3').removeClass('active'); 1542 $(this).addClass('active'); 1543 highContrast = 1; 1544 } else { 1545 targetFilterElement.removeClass('scr_highcontrast'); 1546 1547 // If IE detected, fallback on the compatibility inversion color high contrast 1548 if("ActiveXObject" in window) { 1549 highContrast = 0; 1550 window.location.reload(); 1551 } 1552 1553 $(this).removeClass('active'); 1554 highContrast = 0; 1555 } 1556 1557 screenReaderStorage.setItem('scr_highcontrast', highContrast); 1558 }); 1559 1560 // Register events for dyslexic font 1561 $(document).on('click', '#fr_screenreader_font_family', function(jqEvent){ 1562 dyslexicFont = !!parseInt(screenReaderStorage.getItem('scr_dyslexicfont')) || 0; 1563 if(!dyslexicFont) { 1564 $('body').addClass('scr_dyslexic'); 1565 if(screenReaderOptions.fontsizeSelector) { 1566 // Append to the current inline style of the element 1567 var fontsizingElements = $(screenReaderOptions.fontsizeSelector); 1568 $.each(fontsizingElements, function(index, elem){ 1569 var currentInlineStyles = $(elem).attr('style') || ''; 1570 $(elem).attr('style', currentInlineStyles + ';font-family:OpenDyslexic'); 1571 }); 1572 } 1573 dyslexicFont = 1; 1574 } else { 1575 $('body').removeClass('scr_dyslexic'); 1576 if(screenReaderOptions.fontsizeSelector) { 1577 // Append to the current inline style of the element 1578 var fontsizingElements = $(screenReaderOptions.fontsizeSelector); 1579 $.each(fontsizingElements, function(index, elem){ 1580 var currentInlineStyles = $(elem).attr('style') || ''; 1581 $(elem).attr('style', currentInlineStyles.replace(';font-family:OpenDyslexic','')); 1582 });
1583 } 1584 dyslexicFont = 0; 1585 } 1586 1587 screenReaderStorage.setItem('scr_dyslexicfont', dyslexicFont); 1588 }); 1589 1590 // Register events for gray hues button 1591 $(document).on('click', '#fr_screenreader_gray_hues', function(jqEvent){ 1592 grayHues = !!parseInt(screenReaderStorage.getItem('scr_grayhues')) || 0; 1593 if(!grayHues) { 1594 targetFilterElement.addClass('scr_grayhues'); 1595 1596 grayHues = 1; 1597 } else { 1598 targetFilterElement.removeClass('scr_grayhues'); 1599 1600 grayHues = 0; 1601 } 1602 1603 screenReaderStorage.setItem('scr_grayhues', grayHues); 1604 }); 1605 1606 // Register events for big cursor button 1607 $(document).on('click', '#fr_screenreader_bigcursor', function(jqEvent){ 1608 bigCursor = !!parseInt(screenReaderStorage.getItem('scr_bigcursor')) || 0; 1609 if(!bigCursor) { 1610 $('body').addClass('scr_bigcursor'); 1611 1612 bigCursor = 1; 1613 } else { 1614 $('body').removeClass('scr_bigcursor'); 1615 1616 bigCursor = 0; 1617 } 1618 1619 screenReaderStorage.setItem('scr_bigcursor', bigCursor); 1620 }); 1621 1622 // Register read guides 1623 $(document).on('click', '#fr_screenreader_reading_guides', function(jqEvent, onlyTrigger){ 1624 var updateReadGuide = function(event) { 1625 var topOffset = 0; 1626 topOffset = ("touchmove" == event.type) ? event.changedTouches[0].clientY : event.y; 1627 $("#sc_read_guide_bar").css('top', topOffset - 15); 1628 } 1629 1630 readingGuides = !!parseInt(screenReaderStorage.getItem('scr_reading_guides')) || 0; 1631 if(!readingGuides || onlyTrigger) { 1632 $('body').addClass('scr_reading_guides'); 1633 1634 var readingGuideElement = document.createElement("div"); 1635 readingGuideElement.id = "sc_read_guide_bar"; 1636 readingGuideElement.classList.add("sc_read_guide_bar"); 1637 $('body').append(readingGuideElement); 1638 document.body.addEventListener("touchmove", updateReadGuide, false), 1639 document.body.addEventListener("mousemove", updateReadGuide, false); 1640 1641 readingGuides = 1; 1642 } else { 1643 $('body').removeClass('scr_reading_guides'); 1644 1645 $('#sc_read_guide_bar').remove(); 1646 1647 document.body.removeEventListener("touchmove", updateReadGuide, false); 1648 document.body.removeEventListener("mousemove", updateReadGuide, false) 1649 1650 readingGuides = 0; 1651 } 1652 1653 screenReaderStorage.setItem('scr_reading_guides', readingGuides); 1654 }); 1655 1656 // Register read mode 1657 $(document).on('click', '#fr_screenreader_readability', function(jqEvent){ 1658 if(!readabilityMode) { 1659 originalHtml = $('body').clone(); 1660 1661 $('body').addClass('scr_readability'); 1662 1663 var contentToRead = $(screenReaderOptions.readabilitySelector).html(); 1664 1665 // Only if some contents to transform are found, then go on 1666 if(contentToRead) { 1667 $('body').empty(); 1668 $('body').append('<div class="sc-readability">' + contentToRead + '</div>'); 1669 } 1670 1671 readabilityMode = 1; 1672 } else { 1673 $('body').removeClass('scr_readability'); 1674 1675 $('div.sc-readability').remove(); 1676 $('body').replaceWith(originalHtml[0]); 1677 1678 // Reassign - rebind the body element to the local private variable 1679 targetFilterElement = $('body'); 1680 1681 readabilityMode = 0; 1682 } 1683 }); 1684 1685 // Register hide images 1686 $(document).on('click', '#fr_screenreader_hideimages', function(jqEvent, onlyTrigger){ 1687 hideImages = !!parseInt(screenReaderStorage.getItem('scr_hideimages')) || 0; 1688 hideAlsoVideosIFrames = screenReaderOptions.hideAlsoVideosIframes; 1689 1690 if(!hideImages || onlyTrigger) { 1691 $('body').addClass('scr_hideimages'); 1692 if(hideAlsoVideosIFrames) { 1693 $('body').addClass('scr_hidevideosiframes'); 1694 } 1695 hideImages = 1; 1696 } else { 1697 $('body').removeClass('scr_hideimages'); 1698 if(hideAlsoVideosIFrames) { 1699 $('body').removeClass('scr_hidevideosiframes'); 1700 } 1701 hideImages = 0; 1702 } 1703 1704 screenReaderStorage.setItem('scr_hideimages', hideImages); 1705 }); 1706 1707 // Register events for alternate high contrast buttons, use only native CSS3 filters if available 1708 $(document).on('click', '#fr_screenreader_highcontrast2, #fr_screenreader_highcontrast3', applyAlternateHighContrast); 1709 1710 // Register events for skip to contents button 1711 if(screenReaderOptions.showSkipToContents) { 1712 $(document).on('click', '#fr_screenreader_skiptocontents', skipToContents); 1713 } 1714 }; 1715 1716 // Simple hash function (djb2 variant) 1717 var hashCode = function (str) { 1718 let hash = 5381; 1719 for (let i = 0; i < str.length; i++) { 1720 hash = ((hash << 5) + hash) + str.charCodeAt(i); // hash * 33 + c 1721 } 1722 // Convert to unsigned 32-bit integer and then to a base-36 string. 1723 return (hash >>> 0).toString(36); 1724 }; 1725 1726 /** 1727 * Accessibility enhancement fix 1728 * Sub-Function to call OpenAI API for alt text generation 1729 * 1730 * @access private 1731 * @return Void 1732 */ 1733 var generateAltTextAI = async function (imageUrl) { 1734 const cacheMappingKey = "screenReaderAltMapping"; 1735 1736 // Retrieve the current mapping from local storage, or use an empty object. 1737 let mapping = {};
1738 try { 1739 mapping = JSON.parse(localStorage.getItem(cacheMappingKey)) || {}; 1740 } catch (e) { 1741 mapping = {}; 1742 } 1743 1744 // Generate a hash key for the image URL. 1745 const key = hashCode(imageUrl); 1746 1747 // If a cached alt text exists, return it. 1748 if (mapping[key]) { 1749 return mapping[key]; 1750 } 1751 1752 const OPENAI_API_KEY = screenReaderOptions.generateMissingImagesAltChatgptApikey; 1753 if(!OPENAI_API_KEY.trim()) { 1754 return ''; 1755 } 1756 1757 const prompt = `Describe the content of this image in a short, clear sentence using the language matching the code '${screenReaderConfigOptions.langCode}': ${imageUrl}`; 1758 const response = await fetch("https://api.openai.com/v1/chat/completions", { 1759 method: "POST", 1760 headers: { 1761 "Content-Type": "application/json", 1762 "Authorization": `Bearer ${OPENAI_API_KEY}` 1763 }, 1764 body: JSON.stringify({ 1765 model: screenReaderOptions.generateMissingImagesAltChatgptModel, 1766 messages: [{ role: "user", content: prompt }], 1767 max_tokens: 50 1768 }) 1769 }); 1770 1771 if (!response.ok) { 1772 return ''; 1773 } 1774 1775 const data = await response.json(); 1776 const altText = data.choices[0].message.content.trim(); 1777 1778 // Save the generated alt text in our mapping and update local storage. 1779 mapping[key] = altText; 1780 localStorage.setItem(cacheMappingKey, JSON.stringify(mapping)); 1781 1782 return altText; 1783 }; 1784 1785 /** 1786 * Accessibility enhancement fix 1787 * Function to fix missing alt attributes for images using ChatGPT 1788 * 1789 * @access private 1790 * @return Void 1791 */ 1792 async function fixMissingAltText() { 1793 const images = document.querySelectorAll('img:not([alt]), img[alt=""]'); 1794 for (const img of images) { 1795 try { 1796 const altText = await generateAltTextAI(img.src); 1797 img.setAttribute('alt', altText); 1798 } catch (error) { 1799 } 1800 } 1801 }; 1802 1803 /** 1804 * Accessibility enhancement fix 1805 * Function to automatically fix heading structure issues 1806 * 1807 * @access private 1808 * @return Void 1809 */ 1810 var fixHeadingStructure = function() { 1811 const headings = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6')); 1812 let lastValidLevel = 1; // Start with H1 as the base 1813
1814 headings.forEach((heading, index) => { 1815 let currentLevel = parseInt(heading.tagName.replace('H', ''), 10); 1816 1817 if (index === 0 && currentLevel !== 1) { 1818 currentLevel = 1; 1819 } else if (currentLevel > lastValidLevel + 1) { 1820 currentLevel = lastValidLevel + 1; // Correct the level 1821 } 1822 1823 if (currentLevel !== parseInt(heading.tagName.replace('H', ''), 10)) { 1824 const newHeading = document.createElement(`h${currentLevel}`); 1825 1826 // Preserve attributes, styles, and classes 1827 Array.from(heading.attributes).forEach(attr => { 1828 newHeading.setAttribute(attr.name, attr.value); 1829 }); 1830 1831 newHeading.innerHTML = heading.innerHTML; 1832 heading.replaceWith(newHeading); 1833 } 1834 1835 lastValidLevel = currentLevel; 1836 }); 1837 }; 1838 1839 /** 1840 * Accessibility enhancement fix 1841 * Sub-Function to check if contrast is sufficient 1842 * 1843 * @access private 1844 * @return Void 1845 */ 1846 var isContrastSufficient = function(bgColor, textColor) { 1847 // Compute relative luminance for an RGB color 1848 function luminance(r, g, b) { 1849 const a = [r, g, b].map(v => { 1850 v /= 255; 1851 return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); 1852 }); 1853 return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722; 1854 } 1855 1856 // Extracts the numeric RGB values from a color string like "rgb(255, 255, 255)" 1857 function getRGB(color) { 1858 return color.match(/\d+/g).map(Number); 1859 } 1860 1861 const [r1, g1, b1] = getRGB(bgColor); 1862 const [r2, g2, b2] = getRGB(textColor); 1863 1864 const lum1 = luminance(r1, g1, b1); 1865 const lum2 = luminance(r2, g2, b2); 1866 1867 // Determine the lighter and darker luminance values 1868 const lighter = Math.max(lum1, lum2); 1869 const darker = Math.min(lum1, lum2); 1870 1871 const contrastRatio = (lighter + 0.05) / (darker + 0.05); 1872 1873 return contrastRatio >= 4.5; // WCAG standard for normal text 1874 }; 1875 1876 /** 1877 * Accessibility enhancement fix 1878 * Function to check and fix low contrast text 1879 * 1880 * @access private 1881 * @return Void 1882 */ 1883 var fixLowContrastText = function () { 1884 const textElements = document.querySelectorAll('p, h1, h2, h3, h4, h5, h6, span, div, a, li, td, th, label, button'); 1885 1886 textElements.forEach((el) => { 1887 if (el.classList.contains('screenreader_embed_play_button')) { 1888 return; // Skip this element 1889 } 1890 1891 const bgColor = window.getComputedStyle(el).backgroundColor; 1892 const textColor = window.getComputedStyle(el).color; 1893 1894 if (!isContrastSufficient(bgColor, textColor)) { 1895 el.style.color = "#000"; // Adjust to black for better contrast 1896 } 1897 }); 1898 }; 1899 1900 /** 1901 * Accessibility enhancement fix 1902 * Function to add role="main" to the main element if missing 1903 * 1904 * @access private 1905 * @return Void 1906 */ 1907 var fixMissingAriaRoles = function () { 1908 // Add role="navigation" to <nav> elements if missing 1909 document.querySelectorAll('nav:not([role])').forEach(el => { 1910 el.setAttribute('role', 'navigation'); 1911 }); 1912 1913 // Add role="main" to <main> elements if missing 1914 const mainEl = document.querySelector('main:not([role]),#content:not([role])'); 1915 if (mainEl) { 1916 mainEl.setAttribute('role', 'main'); 1917 } else { 1918 // As a fallback, use the skip to contents selector 1919 const customMainContent = $(screenReaderOptions.skipToContentsSelector); 1920 if(customMainContent.length && !customMainContent.get(0).hasAttribute('role')) { 1921 customMainContent.get(0).setAttribute('role', 'main'); 1922 } 1923 } 1924 }; 1925 1926 /** 1927 * Accessibility enhancement fix 1928 * Function to add an aria-label if missing 1929 * 1930 * @access private 1931 * @return Void 1932 */ 1933 var fixMissingFormLabels = function () { 1934 const inputs = document.querySelectorAll('input, textarea, select'); 1935
1936 inputs.forEach(input => { 1937 // Determine if a label exists by checking for an associated 1938 // <label> or if the input is wrapped in one. 1939 const id = input.getAttribute('id'); 1940 const hasAssociatedLabel = id ? document.querySelector(`label[for="${id}"]`) : null; 1941 1942 if (!hasAssociatedLabel && !input.closest('label')) { 1943 // Use the placeholder as a hint, if available. 1944 const ariaText = input.getAttribute('placeholder') || 'Input field'; 1945 input.setAttribute('aria-label', ariaText); 1946 } 1947 }); 1948 }; 1949 1950 /** 1951 * Accessibility enhancement fix 1952 * Reorder and fix the sequence of tabindex for elements 1953 * 1954 * @access private 1955 * @return Void 1956 */ 1957 var validateAndFixFocusOrder = function () { 1958 // Define a selector for common interactive elements. 1959 const focusableSelectors = ` 1960 a[href], 1961 area[href], 1962 input:not([disabled]):not([type="hidden"]), 1963 select:not([disabled]), 1964 textarea:not([disabled]), 1965 button:not([disabled]), 1966 iframe, 1967 [tabindex]:not([tabindex="-1"]), 1968 [contenteditable="true"] 1969 `; 1970 1971 // Get all matching elements in document order 1972 let focusableElements = Array.from(document.querySelectorAll(focusableSelectors)); 1973 1974 // Filter out elements that are not visible (e.g., display:none) 1975 focusableElements = focusableElements.filter(el => { 1976 const style = window.getComputedStyle(el); 1977 return style.display !== "none" && style.visibility !== "hidden"; 1978 }); 1979 1980 // Reassign tabindex values sequentially, starting at 1. 1981 // This forces the focus order to follow the DOM order. 1982 focusableElements.forEach((el, index) => { 1983 // Set both the property and attribute to ensure compatibility. 1984 const newTabIndex = index + 1; 1985 el.tabIndex = newTabIndex; 1986 el.setAttribute("tabindex", newTabIndex); 1987 }); 1988 }; 1989 1990 /** 1991 * Run the accessibility fixes 1992 * 1993 * @access private 1994 * @return Void 1995 */ 1996 var autoFixAccessibility = function () { 1997 // Fix missing alt attributes in images 1998 if(screenReaderOptions.generateMissingImagesAlt) { 1999 fixMissingAltText(); 2000 } 2001 2002 // Automatically fix heading structure issues 2003 if(screenReaderOptions.fixHeadingsStructure) { 2004 fixHeadingStructure(); 2005 } 2006 2007 // Check and fix low contrast text 2008 if(screenReaderOptions.fixLowContrastText) { 2009 fixLowContrastText(); 2010 } 2011 2012 // Add missing aria role attributes 2013 if(screenReaderOptions.fixMissingAriaRoles) { 2014 fixMissingAriaRoles(); 2015 } 2016 2017 // Add an aria-label if missing 2018 if(screenReaderOptions.fixMissingFormLabels) { 2019 fixMissingFormLabels(); 2020 } 2021 2022 // Fix tabindex order 2023 if(screenReaderOptions.validateAndFixFocusOrder) { 2024 validateAndFixFocusOrder(); 2025 } 2026 }; 2027 2028 /** 2029 * Function dummy constructor 2030 * 2031 * @access private 2032 * @param String 2033 * contextSelector 2034 * @method <<IIFE>> 2035 * @return Void 2036 */ 2037 (function __construct() { 2038 // Compatibility language translations fallback 2039 if(typeof(fr_close_toolbar) === 'undefined') { 2040 window.fr_close_toolbar="Close accessibility interface"; 2041 window.fr_text_reader="Text Reader"; 2042 window.fr_font_sizing="Font sizing"; 2043 window.fr_accessibility_enhancements="Accessibility enhancements"; 2044 window.fr_high_contrast_color="High contrast color"; 2045 window.fr_reading_guides_title="Reading guide"; 2046 } 2047 if(typeof(fr_dark_mode) === 'undefined') { 2048 window.fr_volume = 'Volume'; 2049 window.fr_dark_mode = 'Toggle dark mode'; 2050 window.fr_page_zoom="Page zoom" 2051 window.fr_pagezoom_increase="Increase zoom"; 2052 window.fr_pagezoom_decrease="Decrease zoom"; 2053 window.fr_pagezoom_reset="Reset zoom"; 2054 } 2055 2056 // Init status on load 2057 $.extend(screenReaderOptions, options); 2058 2059 // Set the preferred storage 2060 if(screenReaderOptions.selectedStorage == 'session') { 2061 screenReaderStorage = window.sessionStorage; 2062 } else if(screenReaderOptions.selectedStorage == 'local') { 2063 screenReaderStorage = window.localStorage; 2064 } 2065 2066 // Init target filter element 2067 if(screenReaderOptions.rootTarget) { 2068 targetFilterElement = $('html'); 2069 } 2070 2071 // ReInject if overridden 2072 if(jQuery.frTTSEngine === undefined) { 2073 jQuery('script[src*="tts.js"]').clone().appendTo('body'); 2074 } 2075 2076 // Ensure that the same background color is applied to both the body and html tag
2077 if(screenReaderOptions.autoBackgroundColor) { 2078 var bodyBGColor = $('body').css('background-color'); 2079 $('html').css('background-color', bodyBGColor); 2080 } 2081 2082 // Init font sizing 2083 fontSizeDefault = fontSizeCurrent = screenReaderOptions.fontsizeDefault; 2084 var sessionFontSizeCurrent = screenReaderStorage.getItem('scr_fontsize'); 2085 if(sessionFontSizeCurrent) { 2086 fontSizeCurrent = sessionFontSizeCurrent; 2087 } 2088 2089 // Init zoom size 2090 var sessionZoomSizeCurrent = screenReaderStorage.getItem('scr_zoomsize'); 2091 if(sessionZoomSizeCurrent) { 2092 zoomSizeCurrent = sessionZoomSizeCurrent; 2093 $('html').css('zoom', zoomSizeCurrent + '%'); 2094 } 2095 2096 // Only if spacing size is enabled 2097 var spacingSizeInline = ''; 2098 if(screenReaderOptions.spacingSize) { 2099 spacingSizeCurrent = parseInt(screenReaderStorage.getItem('scr_spacingsize')) || 0; 2100 if(spacingSizeCurrent > 0) { 2101 $('body').css({'word-spacing': (spacingSizeCurrent * 2) + 'px', 'letter-spacing': spacingSizeCurrent + 'px'}); 2102 2103 } 2104 2105 lineHeightCurrent = parseFloat(screenReaderStorage.getItem('scr_final_lineheight')) || 0; 2106 if(lineHeightCurrent > 0) { 2107 $('body').css({'line-height': lineHeightCurrent + 'px'}); 2108 2109 } 2110 if(screenReaderOptions.fontsizeSelector) { 2111 // Append to the current inline style of the element 2112 spacingSizeInline = ';word-spacing:inherit;letter-spacing:inherit;line-height:inherit'; 2113 } 2114 } 2115 2116 // Only if font sizing is enabled 2117 var fontSizeInline = ''; 2118 var isfontsizeMinimizedToolbar = !!screenReaderConfigOptions.useMinimizedToolbar && !!screenReaderOptions.fontsizeMinimizedToolbar && screenReaderConfigOptions.statusMinimizedToolbar == 'closed'; 2119 if(screenReaderOptions.fontsize && !isfontsizeMinimizedToolbar) { 2120 $('body').css('font-size', fontSizeCurrent + '%'); 2121 2122 if(screenReaderOptions.fontsizeSelector) { 2123 fontSizeInline = ';font-size:100%'; 2124 // Manage header resize increment 2125 fontResizeHeaders(); 2126 } 2127 } 2128 2129 // Append to the current inline style of the element 2130 if(screenReaderOptions.fontsize || screenReaderOptions.spacingSize) { 2131 var fontsizingElements = $(screenReaderOptions.fontsizeSelector); 2132 $.each(fontsizingElements, function(index, elem) { 2133 if($.inArray(elem.nodeName.toLowerCase(), ['h1', 'h2', 'h3', 'h4']) != -1 || isfontsizeMinimizedToolbar) { 2134 fontSizeInline = ''; 2135 } else { 2136 fontSizeInline = ';font-size:100%'; 2137 } 2138 var currentInlineStyles = $(elem).attr('style') || ''; 2139 $(elem).attr('style', rtrim(currentInlineStyles, ';') + fontSizeInline + spacingSizeInline); 2140 }); 2141 } 2142 2143 // Init high contrast 2144 highContrast = !!parseInt(screenReaderStorage.getItem('scr_highcontrast')) || 0; 2145 if(highContrast) { 2146 targetFilterElement.addClass('scr_highcontrast'); 2147 2148 // If IE detected, fallback on the compatibility inversion color high contrast 2149 if("ActiveXObject" in window) { 2150 startLegacyHighcontrast('body, body *:not(#accessibility-links, #accessibility-links *, div.shapes)'); 2151 } 2152 } 2153 2154 // Init dyslexic font 2155 dyslexicFont = !!parseInt(screenReaderStorage.getItem('scr_dyslexicfont')) || 0; 2156 if(dyslexicFont) { 2157 $('body').addClass('scr_dyslexic'); 2158 if(screenReaderOptions.fontsizeSelector) { 2159 // Append to the current inline style of the element 2160 var fontsizingElements = $(screenReaderOptions.fontsizeSelector); 2161 $.each(fontsizingElements, function(index, elem){ 2162 var currentInlineStyles = $(elem).attr('style') || ''; 2163 $(elem).attr('style', currentInlineStyles + ';font-family:OpenDyslexic'); 2164 }); 2165 } 2166 } 2167 2168 // Init gray hues 2169 grayHues = !!parseInt(screenReaderStorage.getItem('scr_grayhues')) || 0; 2170 if(grayHues) { 2171 targetFilterElement.addClass('scr_grayhues'); 2172 } 2173 2174 // Init big cursor 2175 bigCursor = !!parseInt(screenReaderStorage.getItem('scr_bigcursor')) || 0; 2176 if(bigCursor) { 2177 $('body').addClass('scr_bigcursor'); 2178 } 2179 2180 // Init custom text color
2181 customBackgroundColor = screenReaderStorage.getItem('scr_custom_background_color'); 2182 if(screenReaderOptions.customColors && screenReaderOptions.template == 'accessible.css' && customBackgroundColor) { 2183 document.querySelector('body').style.setProperty('background', customBackgroundColor, 'important'); 2184 if(screenReaderOptions.customColorsCssSelectors) { 2185 $(screenReaderOptions.customColorsCssSelectors).each(function(index, element){ 2186 element.style.setProperty('background', customBackgroundColor, 'important'); 2187 }); 2188 } 2189 setTimeout(function(){ 2190 $('#fr_screenreader_background_color span.scr-color-selection[data-scr-color="' + customBackgroundColor + '"]').addClass('scr-color-selection-enabled'); 2191 }, 1); 2192 } 2193 2194 // Init custom background color 2195 customTextColor = screenReaderStorage.getItem('scr_custom_text_color'); 2196 if(screenReaderOptions.customColors && screenReaderOptions.template == 'accessible.css' && customTextColor) { 2197 document.querySelector('body').style.setProperty('color', customTextColor, 'important'); 2198 if(screenReaderOptions.customColorsCssSelectors) { 2199 $(screenReaderOptions.customColorsCssSelectors).each(function(index, element){ 2200 element.style.setProperty('color', customTextColor, 'important'); 2201 }); 2202 } 2203 setTimeout(function(){ 2204 $('#fr_screenreader_text_color span.scr-color-selection[data-scr-color="' + customTextColor + '"]').addClass('scr-color-selection-enabled'); 2205 }, 1); 2206 } 2207 2208 // Remove links target if enabled 2209 if(screenReaderOptions.removeLinksTarget) { 2210 $('a').removeAttr('target'); 2211 }; 2212 2213 // Add listeners for UI 2214 addListeners(); 2215 2216 // Check if AI based auto-fixing accessibility issue is enabled and should run 2217 autoFixAccessibility(); 2218 }).call(this); 2219 }; 2220 2221 //On DOM Ready 2222 $(function() { 2223 function scrDetectMobile() { 2224 var windowOnTouch = !!('ontouchstart' in window); 2225 var msTouch = !!(navigator.msMaxTouchPoints); 2226 var hasTouch = windowOnTouch || msTouch; 2227 return hasTouch; 2228 } 2229 var isMobile = scrDetectMobile(); 2230 2231 if(screenReaderConfigOptions.hideOnMobile && isMobile) { 2232 return; 2233 } 2234 2235 if(isMobile && screenReaderConfigOptions.useMobileReaderEngine) { 2236 if(screenReaderConfigOptions.readerEngine != screenReaderConfigOptions.mobileReaderEngine) { 2237 // Override if different 2238 screenReaderConfigOptions.readerEngine = screenReaderConfigOptions.mobileReaderEngine; 2239 } 2240 2241 if(screenReaderConfigOptions.readerEngine == 'proxy' && parseInt(screenReaderConfigOptions.chunkLength) > 100) { 2242 screenReaderConfigOptions.chunkLength = 90; 2243 } 2244 if(screenReaderConfigOptions.readerEngine == 'proxy_virtual' && parseInt(screenReaderConfigOptions.chunkLength) >= 300) { 2245 screenReaderConfigOptions.chunkLength = 280; 2246 } 2247 } 2248 2249 screenReaderConfigOptions.mobileDeviceDetected = isMobile ? true : false; 2250 2251 // Instance controller 2252 window.ScreenReaderMainController = new MainController(screenReaderConfigOptions); 2253 2254 // Build user interface 2255 ScreenReaderMainController.buildInterface(); 2256 2257 // Start engine once interface rendering has been completed 2258 ScreenReaderMainController.startTTSEngine(); 2259 }); 2260})(jQuery);
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.