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 19966 days, 13 hours, 22 minutes ago.