// This is a library that converts text to Grade 1 Braille. // // US Standards for spacing, etc are given here: // https://www.loc.gov/nls/about/organization/standards-guidelines/contract-specifications/ // // Specifically these spacings are for books and pamphlets as found in // Publication 800:2014 Braille Books and Pamphlets // https://www.loc.gov/nls/wp-content/uploads/2019/09/Spec800.11October2014.final_.pdf // // ADA Accessibilty standards for signage are found in section 703.3 of this: // https://www.access-board.gov/ada/#ada-703_3 // // US ADA Signage quick reference: // https://www.accentsignage.com/wp-content/uploads/ADA-Quick-Reference.pdf // // Preview of ISO Standard 17049: // https://www.sis.se/api/document/preview/916703/ // class Braille { // A dictionary mapping single characters to their braille Unicode // equivalent. class var charDict = undef // An inverse dictionary mapping Unicode characters to their ASCII // equivalents class var reverseDict = undef // Nominal base diameter of braille dots. class var DOT_DIAMETER = 0.057 in // Nominal distance from center to center of adjacent dots in the same // cell. class var DOT_SPACING = 0.092 in // Nominal horizontal distance between corresponding dots in two cells // horizontally (empirically found to match my braille template.) // The official specification section 3.2.3 says 0.245 +0.005/-0.001 inches. class var INTERCHAR_HORIZONTAL_SPACING = 0.241589 in // Nominal vertical distance between corresponding dots in two cells class var INTERLINE_SPACING = 0.400 in // Initialize the character dictionaries. class initDict[] := { charDict = new dict charDict@"a" = "\u2801" charDict@"b" = "\u2803" charDict@"c" = "\u2809" charDict@"d" = "\u2819" charDict@"e" = "\u2811" charDict@"f" = "\u280b" charDict@"g" = "\u281b" charDict@"h" = "\u2813" charDict@"i" = "\u280a" charDict@"j" = "\u281a" charDict@"k" = "\u2805" charDict@"l" = "\u2807" charDict@"m" = "\u280d" charDict@"n" = "\u281d" charDict@"o" = "\u2815" charDict@"p" = "\u280f" charDict@"q" = "\u281f" charDict@"r" = "\u2817" charDict@"s" = "\u280e" charDict@"t" = "\u281e" charDict@"u" = "\u2825" charDict@"v" = "\u2827" charDict@"w" = "\u283a" charDict@"x" = "\u282d" charDict@"y" = "\u283d" charDict@"z" = "\u2835" charDict@"?" = "\u2826" charDict@"," = "\u2802" charDict@";" = "\u2806" charDict@":" = "\u2812" charDict@"." = "\u2832" charDict@"!" = "\u2816" charDict@"-" = "\u2824" // We invert here because numbers and letters use the same symbols. reverseDict = charDict.invert[] charDict@"1" = "\u2801" charDict@"2" = "\u2803" charDict@"3" = "\u2809" charDict@"4" = "\u2819" charDict@"5" = "\u2811" charDict@"6" = "\u280b" charDict@"7" = "\u281b" charDict@"8" = "\u2813" charDict@"9" = "\u280a" charDict@"0" = "\u281a" } // Convert the specified string to its Braille unicode equivalent. // This just uses Braille grade 1 transformations. class toBrailleGrade1[str] := { if charDict == undef initDict[] result = "" // Collapse multiple spaces into one. str =~ %s/([\s]+)/ /g // Add number sign before numbers str =~ %s/([0-9]+)/\u283c$1/g; // Add capitalization mark for next character. str =~ %s/([A-Z])/"\u2820" + lc[$1]/ge; for c = charList[str] { if charDict.containsKey[c] result = result + charDict@c else result = result + c } return result } // Draw a block of text in braille, wrapping it to fit the specified maximum // width. // // Parameters: // g : an object of type graphics that will be drawn to. // str: The string to draw. If it is not already converted to Unicode // representation, it will be automatically converted. // x,y: The coordinates where drawing is to begin (must have dimensions // of length (e.g. "0 mm, 0 mm") unless you redefine the spacing // constants. // maxWidth: The maximum width to draw before wrapping. Should have // dimensions of width unless you redefine the spacing constants. // If this is undef, then no wrapping will be performed. // hDirection: Should be 1 for drawing normally. To draw horizontally // mirrored (so you can print on the back of the paper and punch // through it,) this should be -1. // fill: true if the dots are to be filled. If false, only the outline // of the dot will be drawn. // diam: The diameter of each dot. // dotSpace: The distance between two dots in a character. // horizSpace: The horizontal distance between corresponding dots in two // different characters. // lineSpace: The vertical distance between corresponding dots in two // different lines of text. // // Spacing constants follow standard sizes for printed braille. // class drawText[g is graphics, str, x, y, maxWidth=undef, hDirection = 1, fill=true, diam = Braille.DOT_DIAMETER, dotSpace = Braille.DOT_SPACING, horizSpace = Braille.INTERCHAR_HORIZONTAL_SPACING, lineSpace = Braille.INTERLINE_SPACING] := { if ! isEncoded[str] str = toBrailleGrade1[str] if maxWidth == undef drawLine[g, str, x, y, hDirection, fill, diam, dotSpace, horizSpace] else { maxChars = maxWidth div horizSpace // println["maxChars is $maxChars"] startPos = 0 length = length[str] endPos = min[startPos + maxChars - 1, length] while startPos < length { while (endPos >= startPos) { if substrLen[str, endPos, 1] == " " { text = substr[str, startPos, endPos] // println[text] drawLine[g, text, x, y, hDirection, fill, diam, dotSpace, horizSpace] y = y + lineSpace startPos = endPos + 1 endPos = min[startPos + maxChars - 1, length] } else endPos = endPos - 1 } if (endPos == startPos-1) // Didn't find a space { text = substrLen[str, startPos, maxChars] // println[text] drawLine[g, text, x, y, hDirection, fill, diam, dotSpace, horizSpace] y = y + lineSpace startPos = startPos + maxChars endPos = min[startPos + maxChars-1, length] } } } } // Draws a line of text in Braille. You probably shouldn't call this // method directly, but rather call drawText which performs the appropriate // wrapping and encoding. class drawLine[g is graphics, str, x, y, hDirection = 1, fill=true, diam = Braille.DOT_DIAMETER, dotSpace = Braille.DOT_SPACING, horizSpace = Braille.INTERCHAR_HORIZONTAL_SPACING] := { x = x + hDirection * (horizSpace - dotSpace)/2 // Center in space for c = charList[str] { drawChar[g, c, x, y, hDirection, fill, diam, dotSpace, horizSpace] x = x + horizSpace * hDirection } } // Draws a single braille character. class drawChar[g is graphics, char, x, y, hDirection = 1, fill = true, diam = Braille.DOT_DIAMETER, dotSpace = Braille.DOT_SPACING, horizSpace = Braille.INTERCHAR_HORIZONTAL_SPACING] := { c = char[char] if c >= 0x2800 and c <= 0x28FF { c = c - 0x2800 if getBit[c, 0] == 1 drawDot[g, x, y, fill, diam] if getBit[c, 1] == 1 drawDot[g, x, y+dotSpace, fill, diam] if getBit[c, 2] == 1 drawDot[g, x, y+dotSpace*2, fill, diam] if getBit[c, 3] == 1 drawDot[g, x+dotSpace*hDirection, y, fill, diam] if getBit[c, 4] == 1 drawDot[g, x+dotSpace*hDirection, y+dotSpace, fill, diam] if getBit[c, 5] == 1 drawDot[g, x+dotSpace*hDirection, y+dotSpace*2, fill, diam] if getBit[c, 6] == 1 drawDot[g, x, y+dotSpace*3, fill, diam] if getBit[c, 7] == 1 drawDot[g, x+dotSpace*hDirection, y+dotSpace*3, fill, diam] } } // Draw a single dot centered at the specified location. class drawDot[g is graphics, x, y, fill=true, diam = Braille.DOT_DIAMETER] := { if fill g.fillEllipseCenter[x, y, diam, diam] else g.drawEllipseCenter[x, y, diam, diam] } // Returns true if the string is already encoded into Braille, (that is, // if it contains any Braille Unicode characters at all.) class isEncoded[str] := { for c = chars[str] if c >= 0x2800 and c <= 0x28ff return true return false } // This reverses class fromUnicodeBraille[str] := { result = "" if charDict == undef initDict[] for c = charList[str] if reverseDict@c == undef result = result + c else result = result + reverseDict@c return result } }