NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Bread // @match *://*/* // @version 1.0.5 // @author Toby // @license MIT // @description Bread (Bionic Reading) - Read text faster & easier // @require https://openuserjs.org/src/libs/sizzle/GM_config.js // @grant GM_registerMenuCommand // ==/UserScript== /* jshint esversion: 6 */ GM_config.init( { 'id': 'BreadConfig', 'title': 'Bread Configuration', 'fields': { 'MinWordLength': { 'label': 'Minimum word length', 'type': 'int', 'min': 1, 'max': 20, 'default': 4, }, 'MinTextLength': { 'label': 'Minimum text length', 'type': 'int', 'min': 1, 'max': 500, 'default': 50, }, 'BoldRatio': { 'label': 'Bold ratio', 'type': 'float', 'min': 0.1, 'max': 1, 'default': 0.4, }, 'ProcessDyn': { 'label': 'Process dynamically loaded content (may cause performance issues)', 'type': 'checkbox', 'default': true, }, } }); if (typeof GM_registerMenuCommand !== "undefined") { GM_registerMenuCommand('Configuration', function () { GM_config.open(); }); } document.addEventListener('keydown', function (event) { if (event.ctrlKey && event.key === 'b') { GM_config.open(); } }); var minWordLength = GM_config.get('MinWordLength'); var minTextLength = GM_config.get('MinTextLength'); var boldRatio = GM_config.get('BoldRatio'); var processDyn = GM_config.get('ProcessDyn'); function insertTextBefore(text, node, bold) { if (bold) { var span = document.createElement("span"); span.style.fontWeight = "bolder"; span.appendChild(document.createTextNode(text)); node.parentNode.insertBefore(span, node); } else { node.parentNode.insertBefore(document.createTextNode(text), node); } } function processNode(node) { var walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT, { acceptNode: function (node) { return ( node.parentNode.nodeName !== 'SCRIPT' && node.parentNode.nodeName !== 'NOSCRIPT' && node.parentNode.nodeName !== 'STYLE' && node.nodeValue.length >= minTextLength) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; } }); var node; while (node = walker.nextNode()) { var text = node.nodeValue; var wStart = -1, wLen = 0, eng = null; for (var i = 0; i <= text.length; i++) { // We use <= here because we want to include the last character in the loop var cEng = i < text.length ? /[\p{Letter}\p{Mark}]/u.test(text[i]) : false; if (i == text.length || eng !== cEng) { // State flipped or end of string if (eng && wLen >= minWordLength) { var word = text.substring(wStart, wStart + wLen); var numBold = Math.ceil(word.length * boldRatio); var bt = word.substring(0, numBold), nt = word.substring(numBold); insertTextBefore(bt, node, true); insertTextBefore(nt, node, false); } else if (wLen > 0) { var word = text.substring(wStart, wStart + wLen); insertTextBefore(word, node, false); } wStart = i; wLen = 1; eng = cEng; } else { wLen++; } } node.nodeValue = ""; // Can't remove the node (otherwise the tree walker will break) so just set it to empty } } window.addEventListener("load", function (event) { processNode(event.target); }, false); if (processDyn) { document.body.addEventListener('DOMNodeInserted', function (event) { processNode(event.target); }, false); }