Created
July 18, 2022 13:54
-
-
Save nftchance/05ee244b8b466aac636bc8f323a53445 to your computer and use it in GitHub Desktop.
Bionic Reading
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Markdown extension for utilizing Bionic Reading. | |
Author: nftchance | |
License: MIT | |
Developer notice: The official accessibility tool for Bionic Reading | |
is locked behind a $99/month plan for what is ~75 lines of code. | |
Feel free to yoink this all you want. (Still maintain route of | |
monetization but if you put this behind a paid wall I am coming for you.) | |
Eg: | |
Markdown: | |
The cat jumped over the brown fox. | |
Output without extension: | |
<p>The cat jumped over the brown fox.</p> | |
Output with extension: | |
<p><b>Th</b>e <b>c</b>at <b>jum</b>ped <b>ov</b>er <b>t</b>he <b>bro<//b>wn <b>f</b>ox. | |
""" | |
from __future__ import absolute_import | |
from __future__ import unicode_literals | |
import math | |
import re | |
from markdown import Extension | |
from markdown.treeprocessors import Treeprocessor | |
from markdown.util import etree | |
class BionicReadingTreeProcessor(Treeprocessor): | |
""" | |
Goes through the paragraph tags and combines them into a string | |
allowing a single call to be made instead of a bunch of 'em. | |
* Has issues with special characters. If using with SmartyPants/ | |
something that messes with special characters, Bionic Reading needs | |
to be processed first as special characters struggle. | |
""" | |
FIXATION_TOP = 6 | |
FIXATION_POINTS = 10 | |
SPACE = " " | |
PREV_ELEMENT = None | |
def buildBionicWord(self, p, word, saccade_skip): | |
# if a space is needed add it to the previous element | |
if word == self.SPACE and self.PREV_ELEMENT: | |
self.PREV_ELEMENT.tail += self.SPACE | |
return | |
# calculate the fixation length | |
blen = math.ceil(len(word) * ( | |
self.FIXATION_TOP - self.config['fixation'] | |
) / self.FIXATION_POINTS) | |
element_type = 'b' if not saccade_skip else 'span' | |
# wrap the text in a bold element | |
b_element = etree.SubElement( | |
p, element_type, attrib={'class': 'b bionic'} | |
) | |
# add the bolded text | |
b_element.text = word[:blen] | |
# add the tail text | |
b_element.tail = word[blen:] | |
self.PREV_ELEMENT = b_element | |
return None | |
def buildBionicText(self, text, p): | |
saccade_length = 0 | |
for word in re.split(r'(\s+)', text): # loop through every word | |
saccade_length -= len(word) | |
saccade_skip = saccade_length > 0 | |
# building bionic word | |
self.buildBionicWord( | |
p, | |
word, | |
saccade_skip | |
) | |
# if we bioniced a piece of text, pad the coming | |
if not saccade_skip: | |
saccade_length = self.config['saccade'] - 10 | |
return None | |
def run(self, root): | |
content_types = self.config['content_types'] | |
contents = [] | |
for content_type in content_types: | |
contents += root.findall(f".//{content_type}") | |
for p in contents: # loop through very element | |
text = p.text # copy the text value as we need it | |
# if we shouldn't format this text, skip | |
if not text or len(text) == 1: continue | |
# ignores the editing of KaTeX rendering pieces | |
if "wzxhzdk" in text: continue | |
p.text = "" # clear existing text we copied | |
# convert to bionic text | |
self.buildBionicText(text, p) | |
return None | |
class BionicReadingExtension(Extension): | |
def __init__(self, **kwargs): | |
# initialize configuration | |
self.config = { | |
'content_types': [ | |
[ | |
'h1', | |
'h2', | |
'h3', | |
'h4', | |
'h5', | |
'a', | |
'em', | |
'p', | |
'figcaption', | |
], | |
"The types of DOM elements that will be Bionic formatted. (h1,h2,h3...,a,em,p)" | |
], | |
'fixation': [ | |
1, | |
"With the Fixation you define the expression of the letter combinations. You can choose a value between 1 and 5 (1,2,3, 4 or 5)" | |
], | |
'saccade': [ | |
30, | |
"Controls the number of un-bolded characters between fixation points when not 10." | |
] | |
} | |
super(BionicReadingExtension, self).__init__(**kwargs) | |
def extendMarkdown(self, md, md_globals): | |
# add BionicReadingExtension into markdown instance | |
md.registerExtension(self) | |
extension = BionicReadingTreeProcessor(md) | |
extension.config = self.getConfigs() | |
md.treeprocessors.add('bionic_reading', extension, '_end') | |
def makeExtension(**kwargs): | |
return BionicReadingExtension(**kwargs) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment