Braille.frink

Download or view Braille.frink in plain text format


// 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      
   }
}


Download or view Braille.frink in plain text format


This is a program written in the programming language Frink.
For more information, view the Frink Documentation or see More Sample Frink Programs.

Alan Eliasen was born 19944 days, 9 hours, 12 minutes ago.