diff --git a/MakerPrompt.Shared/BrailleRAP/Models/BrailleCell.cs b/MakerPrompt.Shared/BrailleRAP/Models/BrailleCell.cs new file mode 100644 index 0000000..c8770b4 --- /dev/null +++ b/MakerPrompt.Shared/BrailleRAP/Models/BrailleCell.cs @@ -0,0 +1,43 @@ +namespace MakerPrompt.Shared.BrailleRAP.Models +{ + /// + /// Represents a single Braille cell using Unicode Braille Patterns (U+2800 to U+28FF). + /// + public class BrailleCell + { + /// + /// The Unicode character representing this Braille cell. + /// + public char Character { get; set; } + + /// + /// Creates a Braille cell from a Unicode Braille character. + /// + public BrailleCell(char character) + { + Character = character; + } + + /// + /// Gets the value (0-255) representing the dot pattern. + /// + public int GetValue() + { + return Character - 0x2800; + } + + /// + /// Checks if a specific dot is raised in this cell. + /// + public bool HasDot(BrailleDotPosition position) + { + int value = GetValue(); + return (value & (1 << (int)position)) != 0; + } + + public override string ToString() + { + return Character.ToString(); + } + } +} diff --git a/MakerPrompt.Shared/BrailleRAP/Models/BrailleDot.cs b/MakerPrompt.Shared/BrailleRAP/Models/BrailleDot.cs new file mode 100644 index 0000000..df4011a --- /dev/null +++ b/MakerPrompt.Shared/BrailleRAP/Models/BrailleDot.cs @@ -0,0 +1,19 @@ +namespace MakerPrompt.Shared.BrailleRAP.Models +{ + /// + /// Represents the position of a dot in a Braille cell (0-7 for 8-dot Braille). + /// Standard 6-dot positions: 0-5 + /// Extended 8-dot positions: 0-7 + /// + public enum BrailleDotPosition + { + Dot1 = 0, // Top-left + Dot2 = 1, // Middle-left + Dot3 = 2, // Bottom-left + Dot4 = 3, // Top-right + Dot5 = 4, // Middle-right + Dot6 = 5, // Bottom-right + Dot7 = 6, // Extended bottom-left + Dot8 = 7 // Extended bottom-right + } +} diff --git a/MakerPrompt.Shared/BrailleRAP/Models/BraillePageLayout.cs b/MakerPrompt.Shared/BrailleRAP/Models/BraillePageLayout.cs new file mode 100644 index 0000000..e504b97 --- /dev/null +++ b/MakerPrompt.Shared/BrailleRAP/Models/BraillePageLayout.cs @@ -0,0 +1,29 @@ +namespace MakerPrompt.Shared.BrailleRAP.Models +{ + /// + /// Represents a paginated layout of Braille text. + /// + public class BraillePageLayout + { + /// + /// Pages of Braille lines. + /// Each page contains multiple lines, each line is a string of Braille characters. + /// + public List> Pages { get; set; } = new(); + + /// + /// Gets the total number of pages. + /// + public int PageCount => Pages.Count; + + /// + /// Gets a specific page by index. + /// + public List GetPage(int pageIndex) + { + if (pageIndex < 0 || pageIndex >= Pages.Count) + return new List(); + return Pages[pageIndex]; + } + } +} diff --git a/MakerPrompt.Shared/BrailleRAP/Models/GeomPoint.cs b/MakerPrompt.Shared/BrailleRAP/Models/GeomPoint.cs new file mode 100644 index 0000000..4f6d17d --- /dev/null +++ b/MakerPrompt.Shared/BrailleRAP/Models/GeomPoint.cs @@ -0,0 +1,7 @@ +namespace MakerPrompt.Shared.BrailleRAP.Models +{ + /// + /// Represents a geometric point in 2D space for BrailleRAP positioning. + /// + public record GeomPoint(double X, double Y); +} diff --git a/MakerPrompt.Shared/BrailleRAP/Models/MachineConfig.cs b/MakerPrompt.Shared/BrailleRAP/Models/MachineConfig.cs new file mode 100644 index 0000000..9852b6c --- /dev/null +++ b/MakerPrompt.Shared/BrailleRAP/Models/MachineConfig.cs @@ -0,0 +1,48 @@ +namespace MakerPrompt.Shared.BrailleRAP.Models +{ + /// + /// Configuration for BrailleRAP machine parameters. + /// + public class MachineConfig + { + /// + /// Horizontal spacing between dots in a cell (mm). + /// + public double DotPaddingX { get; set; } = 2.2; + + /// + /// Vertical spacing between dots in a cell (mm). + /// + public double DotPaddingY { get; set; } = 2.2; + + /// + /// Horizontal spacing between cells (mm). + /// + public double CellPaddingX { get; set; } = 6.0; + + /// + /// Vertical spacing between lines (mm). + /// + public double CellPaddingY { get; set; } = 12.0; + + /// + /// Feed rate for movement (mm/min). + /// + public int FeedRate { get; set; } = 6000; + + /// + /// X-axis offset from origin (mm). + /// + public double OffsetX { get; set; } = 0.0; + + /// + /// Y-axis offset from origin (mm). + /// + public double OffsetY { get; set; } = 0.0; + + /// + /// Y-coordinate for return position after printing (mm). + /// + public double ReturnPositionY { get; set; } = 300.0; + } +} diff --git a/MakerPrompt.Shared/BrailleRAP/Models/PageConfig.cs b/MakerPrompt.Shared/BrailleRAP/Models/PageConfig.cs new file mode 100644 index 0000000..6dec25f --- /dev/null +++ b/MakerPrompt.Shared/BrailleRAP/Models/PageConfig.cs @@ -0,0 +1,31 @@ +namespace MakerPrompt.Shared.BrailleRAP.Models +{ + /// + /// Configuration for Braille page layout. + /// + public class PageConfig + { + /// + /// Number of Braille cells per line. + /// + public int Columns { get; set; } = 28; + + /// + /// Maximum number of lines per page. + /// + public int Rows { get; set; } = 21; + + /// + /// Line spacing multiplier (0 = normal, 1 = double space, etc.). + /// + public int LineSpacing { get; set; } = 0; + + /// + /// Gets the computed number of rows based on spacing. + /// + public int GetComputedRows() + { + return (int)Math.Floor(Rows / ((LineSpacing * 0.5) + 1)); + } + } +} diff --git a/MakerPrompt.Shared/BrailleRAP/Services/BrailleGCodeGenerator.cs b/MakerPrompt.Shared/BrailleRAP/Services/BrailleGCodeGenerator.cs new file mode 100644 index 0000000..95d2aee --- /dev/null +++ b/MakerPrompt.Shared/BrailleRAP/Services/BrailleGCodeGenerator.cs @@ -0,0 +1,114 @@ +using MakerPrompt.Shared.BrailleRAP.Models; +using System.Text; + +namespace MakerPrompt.Shared.BrailleRAP.Services +{ + /// + /// Generates G-code for BrailleRAP embossing from geometric points. + /// Ported from AccessBrailleRAP's GeomToGCode.js + /// + public class BrailleGCodeGenerator + { + private readonly MachineConfig _config; + + public BrailleGCodeGenerator(MachineConfig config) + { + _config = config; + } + + /// + /// Generates complete G-code from geometric points. + /// + public string GenerateGCode(List points) + { + var gcode = new StringBuilder(); + + // Initialize + gcode.Append(Home()); + gcode.Append(SetSpeed(_config.FeedRate)); + gcode.Append(MoveTo(0, 0)); + + // Process each point + foreach (var point in points) + { + gcode.Append(MoveTo(point.X, point.Y)); + gcode.Append(PrintDot()); + } + + // Return to home position + gcode.Append(MoveTo(0, _config.ReturnPositionY)); + gcode.Append(MotorOff()); + + return gcode.ToString(); + } + + /// + /// Generates G-code from a Braille page layout. + /// + public string GenerateGCodeFromLayout(BraillePageLayout layout, int pageIndex = 0) + { + var page = layout.GetPage(pageIndex); + if (page.Count == 0) + return string.Empty; + + var geometry = new BrailleToGeometry(_config); + var points = geometry.BraillePageToGeom(page, _config.OffsetX, _config.OffsetY); + + return GenerateGCode(points); + } + + private string MotorOff() + { + return "M84;\r\n"; + } + + private string Home() + { + var sb = new StringBuilder(); + sb.Append("G28 X;\r\n"); + sb.Append("G28 Y;\r\n"); + return sb.ToString(); + } + + private string GCodePosition(double? x, double? y) + { + if (x == null && y == null) + { + throw new ArgumentException("At least one coordinate must be specified"); + } + + var code = new StringBuilder(); + + if (x.HasValue) + { + code.Append($" X{x.Value:F2}"); + } + + if (y.HasValue) + { + code.Append($" Y{y.Value:F2}"); + } + + code.Append(";\r\n"); + return code.ToString(); + } + + private string SetSpeed(int speed) + { + return $"G1 F{speed};\r\n"; + } + + private string MoveTo(double x, double y) + { + return "G1" + GCodePosition(x, y); + } + + private string PrintDot() + { + var sb = new StringBuilder(); + sb.Append("M3 S1;\r\n"); + sb.Append("M3 S0;\r\n"); + return sb.ToString(); + } + } +} diff --git a/MakerPrompt.Shared/BrailleRAP/Services/BraillePaginator.cs b/MakerPrompt.Shared/BrailleRAP/Services/BraillePaginator.cs new file mode 100644 index 0000000..8b6afb7 --- /dev/null +++ b/MakerPrompt.Shared/BrailleRAP/Services/BraillePaginator.cs @@ -0,0 +1,160 @@ +using MakerPrompt.Shared.BrailleRAP.Models; + +namespace MakerPrompt.Shared.BrailleRAP.Services +{ + /// + /// Paginates Braille text into pages based on column and row constraints. + /// Ported from AccessBrailleRAP's BraillePaginator.js + /// + public class BraillePaginator + { + private const char BlankBrailleCell = '\u2800'; + + private PageConfig _config; + private List _sourceLines; + private List> _pages; + private List _currentPage; + private int _computedRows; + + public BraillePaginator() + { + _config = new PageConfig(); + _sourceLines = new List(); + _pages = new List>(); + _currentPage = new List(); + _computedRows = _config.GetComputedRows(); + } + + /// + /// Sets the page configuration. + /// + public void SetConfig(PageConfig config) + { + _config = config; + ComputeRows(); + Update(); + } + + /// + /// Sets the source Braille lines to paginate. + /// + public void SetSourceLines(List lines) + { + _sourceLines = lines; + Update(); + } + + /// + /// Gets the paginated layout. + /// + public BraillePageLayout GetLayout() + { + return new BraillePageLayout { Pages = new List>(_pages) }; + } + + private void ComputeRows() + { + _computedRows = _config.GetComputedRows(); + } + + private void AddLine(string line) + { + _currentPage.Add(line); + if (_currentPage.Count >= _computedRows) + { + _pages.Add(new List(_currentPage)); + _currentPage.Clear(); + } + } + + private void FlushLine() + { + if (_currentPage.Count > 0) + { + _pages.Add(new List(_currentPage)); + _currentPage.Clear(); + } + } + + private void Update() + { + if (_sourceLines == null) + return; + + _pages.Clear(); + _currentPage.Clear(); + ComputeRows(); + + foreach (var srcLine in _sourceLines) + { + // Split by blank Braille cells (U+2800 is the blank cell used as space) + var words = srcLine.Split(BlankBrailleCell); + + var currentLine = ""; + + foreach (var word in words) + { + // Check for form feed + if (word == "\f") + { + AddLine(currentLine); + currentLine = ""; + FlushLine(); + continue; + } + + // Check if adding this word would exceed column limit + if (word.Length + currentLine.Length >= _config.Columns) + { + if (currentLine.Length > 0) + { + // Add current line and start new one + AddLine(currentLine); + + if (word.Length < _config.Columns) + { + currentLine = word + BlankBrailleCell; + } + else + { + // Word is too long, need to split it + currentLine = ""; + SplitLongWord(word); + } + } + else + { + // Need to split a long word + SplitLongWord(word); + currentLine = ""; + } + } + else + { + currentLine += word; + currentLine += BlankBrailleCell; + } + } + + if (currentLine.Length > 0) + { + AddLine(currentLine); + } + } + + FlushLine(); + } + + private void SplitLongWord(string word) + { + int start = 0; + while (start < word.Length) + { + int chunkSize = Math.Min(_config.Columns, word.Length - start); + string chunk = word.Substring(start, chunkSize); + AddLine(chunk); + start += chunkSize; + } + } + } +} diff --git a/MakerPrompt.Shared/BrailleRAP/Services/BrailleRAPService.cs b/MakerPrompt.Shared/BrailleRAP/Services/BrailleRAPService.cs new file mode 100644 index 0000000..0ec6b28 --- /dev/null +++ b/MakerPrompt.Shared/BrailleRAP/Services/BrailleRAPService.cs @@ -0,0 +1,130 @@ +using MakerPrompt.Shared.BrailleRAP.Models; + +namespace MakerPrompt.Shared.BrailleRAP.Services +{ + /// + /// Main service for BrailleRAP operations. + /// Coordinates translation, pagination, and G-code generation. + /// + public class BrailleRAPService + { + private readonly BrailleTranslator _translator; + private readonly BraillePaginator _paginator; + private PageConfig _pageConfig; + private MachineConfig _machineConfig; + private BrailleLanguage _currentLanguage; + + public BrailleRAPService() + { + _translator = new BrailleTranslator(); + _paginator = new BraillePaginator(); + _pageConfig = new PageConfig(); + _machineConfig = new MachineConfig(); + _currentLanguage = BrailleLanguage.EnglishGrade1; + } + + /// + /// Sets the Braille translation language. + /// + public void SetLanguage(BrailleLanguage language) + { + _currentLanguage = language; + _translator.SetLanguage(language); + } + + /// + /// Gets the current Braille language. + /// + public BrailleLanguage GetLanguage() => _currentLanguage; + + /// + /// Sets the page configuration. + /// + public void SetPageConfig(PageConfig config) + { + _pageConfig = config; + _paginator.SetConfig(config); + } + + /// + /// Sets the machine configuration. + /// + public void SetMachineConfig(MachineConfig config) + { + _machineConfig = config; + } + + /// + /// Gets the current page configuration. + /// + public PageConfig GetPageConfig() => _pageConfig; + + /// + /// Gets the current machine configuration. + /// + public MachineConfig GetMachineConfig() => _machineConfig; + + /// + /// Translates text to Braille and returns the layout. + /// + public BraillePageLayout TranslateAndLayout(string text) + { + // Translate text to Braille + var brailleLines = _translator.Translate(text); + + // Paginate + _paginator.SetSourceLines(brailleLines); + + return _paginator.GetLayout(); + } + + /// + /// Generates G-code from text for a specific page. + /// + public string GenerateGCode(string text, int pageIndex = 0) + { + var layout = TranslateAndLayout(text); + + if (layout.PageCount == 0 || pageIndex >= layout.PageCount) + return string.Empty; + + var generator = new BrailleGCodeGenerator(_machineConfig); + return generator.GenerateGCodeFromLayout(layout, pageIndex); + } + + /// + /// Gets a preview of the Braille text for a specific page. + /// + public List GetBraillePreview(string text, int pageIndex = 0) + { + var layout = TranslateAndLayout(text); + + if (layout.PageCount == 0 || pageIndex >= layout.PageCount) + return new List(); + + return layout.GetPage(pageIndex); + } + + /// + /// Gets statistics about the translated and paginated text. + /// + public (int PageCount, int TotalLines, int TotalCharacters) GetStatistics(string text) + { + var layout = TranslateAndLayout(text); + + int totalLines = 0; + int totalChars = 0; + + foreach (var page in layout.Pages) + { + totalLines += page.Count; + foreach (var line in page) + { + totalChars += line.Length; + } + } + + return (layout.PageCount, totalLines, totalChars); + } + } +} diff --git a/MakerPrompt.Shared/BrailleRAP/Services/BrailleToGeometry.cs b/MakerPrompt.Shared/BrailleRAP/Services/BrailleToGeometry.cs new file mode 100644 index 0000000..2e55fa6 --- /dev/null +++ b/MakerPrompt.Shared/BrailleRAP/Services/BrailleToGeometry.cs @@ -0,0 +1,137 @@ +using MakerPrompt.Shared.BrailleRAP.Models; + +namespace MakerPrompt.Shared.BrailleRAP.Services +{ + /// + /// Converts Braille cells to geometric points for embossing. + /// Ported from AccessBrailleRAP's BrailleToGeometry.js + /// + public class BrailleToGeometry + { + // Standard 8-dot Braille dot positions + private static readonly (int X, int Y)[] DotPositions = new[] + { + (0, 0), // Dot 1 + (0, 1), // Dot 2 + (0, 2), // Dot 3 + (1, 0), // Dot 4 + (1, 1), // Dot 5 + (1, 2), // Dot 6 + (0, 3), // Dot 7 + (1, 3) // Dot 8 + }; + + private readonly MachineConfig _config; + + public BrailleToGeometry(MachineConfig config) + { + _config = config; + } + + /// + /// Converts a single Braille character to geometric points. + /// + public List BrailleCharToGeom(char brailleChar, double offsetX, double offsetY) + { + var points = new List(); + int value = brailleChar - 0x2800; + + for (int i = 0; i < 8; i++) + { + if ((value & (1 << i)) != 0) + { + var dot = DotPositions[i]; + var point = new GeomPoint( + dot.X * _config.DotPaddingX + offsetX, + dot.Y * _config.DotPaddingY + offsetY + ); + points.Add(point); + } + } + + return points; + } + + /// + /// Converts a page of Braille text to geometric points. + /// + public List BraillePageToGeom(List lines, double offsetX, double offsetY) + { + var geometry = new List(); + var startY = offsetY; + + foreach (var line in lines) + { + var startX = offsetX; + + foreach (var ch in line) + { + var points = BrailleCharToGeom(ch, startX, startY); + geometry.AddRange(points); + startX += _config.CellPaddingX; + } + + startY += _config.CellPaddingY; + } + + // Sort geometry + SortGeom(geometry); + + // Apply zig-zag optimization for efficient printing + var sorted = SortGeomZigZag(geometry); + + return sorted; + } + + /// + /// Sorts geometry by Y then X coordinates. + /// + private void SortGeom(List geom) + { + geom.Sort((a, b) => + { + if (Math.Abs(a.Y - b.Y) < 0.001) + return a.X.CompareTo(b.X); + return a.Y.CompareTo(b.Y); + }); + } + + /// + /// Sorts geometry in a zig-zag pattern for efficient printing. + /// Alternates direction for each row to minimize travel distance. + /// + private List SortGeomZigZag(List geom) + { + if (geom == null || geom.Count == 0) + return new List(); + + var sorted = new List(); + int start = 0; + int end = 0; + int direction = 1; + + while (end < geom.Count) + { + // Find all points with the same Y coordinate + while (end < geom.Count && Math.Abs(geom[start].Y - geom[end].Y) < 0.001) + { + end++; + } + + // Extract this row + var row = geom.GetRange(start, end - start); + + // Sort by X, alternating direction + row.Sort((a, b) => direction * a.X.CompareTo(b.X)); + + sorted.AddRange(row); + + // Alternate direction for next row + direction = -direction; + start = end; + } + + return sorted; + } + } +} diff --git a/MakerPrompt.Shared/BrailleRAP/Services/BrailleTranslator.cs b/MakerPrompt.Shared/BrailleRAP/Services/BrailleTranslator.cs new file mode 100644 index 0000000..25fd3b1 --- /dev/null +++ b/MakerPrompt.Shared/BrailleRAP/Services/BrailleTranslator.cs @@ -0,0 +1,308 @@ +using MakerPrompt.Shared.BrailleRAP.Models; + +namespace MakerPrompt.Shared.BrailleRAP.Services +{ + /// + /// Supported Braille languages and translation tables. + /// + public enum BrailleLanguage + { + EnglishGrade1, + FrenchGrade1, + GermanGrade1, + SpanishGrade1 + } + + /// + /// Translates text to Braille using character mapping for multiple languages. + /// + public class BrailleTranslator + { + // Braille character constants + private const char CapitalIndicator = '\u2820'; + private const char BlankCell = '\u2800'; + private const char NumberIndicator = '\u283C'; + + private BrailleLanguage _currentLanguage = BrailleLanguage.EnglishGrade1; + + /// + /// Sets the translation language. + /// + public void SetLanguage(BrailleLanguage language) + { + _currentLanguage = language; + } + + /// + /// Gets the current language. + /// + public BrailleLanguage GetLanguage() => _currentLanguage; + + // Basic English Grade 1 Braille mapping (simplified) + // Unicode Braille patterns start at U+2800 + private static readonly Dictionary EnglishGrade1Map = new() + { + // Letters (a-z) + { 'a', '\u2801' }, { 'b', '\u2803' }, { 'c', '\u2809' }, { 'd', '\u2819' }, + { 'e', '\u2811' }, { 'f', '\u280B' }, { 'g', '\u281B' }, { 'h', '\u2813' }, + { 'i', '\u280A' }, { 'j', '\u281A' }, { 'k', '\u2805' }, { 'l', '\u2807' }, + { 'm', '\u280D' }, { 'n', '\u281D' }, { 'o', '\u2815' }, { 'p', '\u280F' }, + { 'q', '\u281F' }, { 'r', '\u2817' }, { 's', '\u280E' }, { 't', '\u281E' }, + { 'u', '\u2825' }, { 'v', '\u2827' }, { 'w', '\u283A' }, { 'x', '\u282D' }, + { 'y', '\u283D' }, { 'z', '\u2835' }, + + // Capital letter indicator + { 'A', CapitalIndicator }, { 'B', CapitalIndicator }, { 'C', CapitalIndicator }, { 'D', CapitalIndicator }, + { 'E', CapitalIndicator }, { 'F', CapitalIndicator }, { 'G', CapitalIndicator }, { 'H', CapitalIndicator }, + { 'I', CapitalIndicator }, { 'J', CapitalIndicator }, { 'K', CapitalIndicator }, { 'L', CapitalIndicator }, + { 'M', CapitalIndicator }, { 'N', CapitalIndicator }, { 'O', CapitalIndicator }, { 'P', CapitalIndicator }, + { 'Q', CapitalIndicator }, { 'R', CapitalIndicator }, { 'S', CapitalIndicator }, { 'T', CapitalIndicator }, + { 'U', CapitalIndicator }, { 'V', CapitalIndicator }, { 'W', CapitalIndicator }, { 'X', CapitalIndicator }, + { 'Y', CapitalIndicator }, { 'Z', CapitalIndicator }, + + // Numbers + { '1', '\u2801' }, { '2', '\u2803' }, { '3', '\u2809' }, { '4', '\u2819' }, + { '5', '\u2811' }, { '6', '\u280B' }, { '7', '\u281B' }, { '8', '\u2813' }, + { '9', '\u280A' }, { '0', '\u281A' }, + + // Common punctuation + { ' ', BlankCell }, // Blank cell for space + { ',', '\u2802' }, + { '.', '\u2832' }, + { '?', '\u2826' }, + { '!', '\u2816' }, + { '\'', '\u2804' }, + { '-', '\u2824' }, + { ':', '\u2812' }, + { ';', '\u2822' }, + { '(', '\u2823' }, + { ')', '\u281C' }, + { '\n', '\n' }, // Preserve newlines + { '\f', '\f' }, // Preserve form feeds + }; + + // French Grade 1 Braille mapping + private static readonly Dictionary FrenchGrade1Map = new() + { + // Letters (a-z) - same as English + { 'a', '\u2801' }, { 'b', '\u2803' }, { 'c', '\u2809' }, { 'd', '\u2819' }, + { 'e', '\u2811' }, { 'f', '\u280B' }, { 'g', '\u281B' }, { 'h', '\u2813' }, + { 'i', '\u280A' }, { 'j', '\u281A' }, { 'k', '\u2805' }, { 'l', '\u2807' }, + { 'm', '\u280D' }, { 'n', '\u281D' }, { 'o', '\u2815' }, { 'p', '\u280F' }, + { 'q', '\u281F' }, { 'r', '\u2817' }, { 's', '\u280E' }, { 't', '\u281E' }, + { 'u', '\u2825' }, { 'v', '\u2827' }, { 'w', '\u283A' }, { 'x', '\u282D' }, + { 'y', '\u283D' }, { 'z', '\u2835' }, + + // Capital letters + { 'A', CapitalIndicator }, { 'B', CapitalIndicator }, { 'C', CapitalIndicator }, { 'D', CapitalIndicator }, + { 'E', CapitalIndicator }, { 'F', CapitalIndicator }, { 'G', CapitalIndicator }, { 'H', CapitalIndicator }, + { 'I', CapitalIndicator }, { 'J', CapitalIndicator }, { 'K', CapitalIndicator }, { 'L', CapitalIndicator }, + { 'M', CapitalIndicator }, { 'N', CapitalIndicator }, { 'O', CapitalIndicator }, { 'P', CapitalIndicator }, + { 'Q', CapitalIndicator }, { 'R', CapitalIndicator }, { 'S', CapitalIndicator }, { 'T', CapitalIndicator }, + { 'U', CapitalIndicator }, { 'V', CapitalIndicator }, { 'W', CapitalIndicator }, { 'X', CapitalIndicator }, + { 'Y', CapitalIndicator }, { 'Z', CapitalIndicator }, + + // Numbers + { '1', '\u2801' }, { '2', '\u2803' }, { '3', '\u2809' }, { '4', '\u2819' }, + { '5', '\u2811' }, { '6', '\u280B' }, { '7', '\u281B' }, { '8', '\u2813' }, + { '9', '\u280A' }, { '0', '\u281A' }, + + // French accented characters + { 'à', '\u282F' }, { 'â', '\u2801' }, { 'ç', '\u280F' }, + { 'é', '\u283F' }, { 'è', '\u282E' }, { 'ê', '\u2811' }, + { 'ë', '\u283B' }, { 'î', '\u280A' }, { 'ï', '\u283B' }, + { 'ô', '\u2815' }, { 'ù', '\u283D' }, { 'û', '\u2825' }, + { 'ü', '\u283B' }, { 'œ', '\u283B' }, + + // Common punctuation + { ' ', BlankCell }, + { ',', '\u2802' }, + { '.', '\u2832' }, + { '?', '\u2826' }, + { '!', '\u2816' }, + { '\'', '\u2804' }, + { '-', '\u2824' }, + { ':', '\u2812' }, + { ';', '\u2822' }, + { '(', '\u2823' }, + { ')', '\u281C' }, + { '\n', '\n' }, + { '\f', '\f' }, + }; + + // German Grade 1 Braille mapping + private static readonly Dictionary GermanGrade1Map = new() + { + // Letters (a-z) - same as English + { 'a', '\u2801' }, { 'b', '\u2803' }, { 'c', '\u2809' }, { 'd', '\u2819' }, + { 'e', '\u2811' }, { 'f', '\u280B' }, { 'g', '\u281B' }, { 'h', '\u2813' }, + { 'i', '\u280A' }, { 'j', '\u281A' }, { 'k', '\u2805' }, { 'l', '\u2807' }, + { 'm', '\u280D' }, { 'n', '\u281D' }, { 'o', '\u2815' }, { 'p', '\u280F' }, + { 'q', '\u281F' }, { 'r', '\u2817' }, { 's', '\u280E' }, { 't', '\u281E' }, + { 'u', '\u2825' }, { 'v', '\u2827' }, { 'w', '\u283A' }, { 'x', '\u282D' }, + { 'y', '\u283D' }, { 'z', '\u2835' }, + + // Capital letters + { 'A', CapitalIndicator }, { 'B', CapitalIndicator }, { 'C', CapitalIndicator }, { 'D', CapitalIndicator }, + { 'E', CapitalIndicator }, { 'F', CapitalIndicator }, { 'G', CapitalIndicator }, { 'H', CapitalIndicator }, + { 'I', CapitalIndicator }, { 'J', CapitalIndicator }, { 'K', CapitalIndicator }, { 'L', CapitalIndicator }, + { 'M', CapitalIndicator }, { 'N', CapitalIndicator }, { 'O', CapitalIndicator }, { 'P', CapitalIndicator }, + { 'Q', CapitalIndicator }, { 'R', CapitalIndicator }, { 'S', CapitalIndicator }, { 'T', CapitalIndicator }, + { 'U', CapitalIndicator }, { 'V', CapitalIndicator }, { 'W', CapitalIndicator }, { 'X', CapitalIndicator }, + { 'Y', CapitalIndicator }, { 'Z', CapitalIndicator }, + + // Numbers + { '1', '\u2801' }, { '2', '\u2803' }, { '3', '\u2809' }, { '4', '\u2819' }, + { '5', '\u2811' }, { '6', '\u280B' }, { '7', '\u281B' }, { '8', '\u2813' }, + { '9', '\u280A' }, { '0', '\u281A' }, + + // German umlauts and special characters + { 'ä', '\u2831' }, { 'Ä', '\u2831' }, + { 'ö', '\u283B' }, { 'Ö', '\u283B' }, + { 'ü', '\u283C' }, { 'Ü', '\u283C' }, + { 'ß', '\u282E' }, + + // Common punctuation + { ' ', BlankCell }, + { ',', '\u2802' }, + { '.', '\u2832' }, + { '?', '\u2826' }, + { '!', '\u2816' }, + { '\'', '\u2804' }, + { '-', '\u2824' }, + { ':', '\u2812' }, + { ';', '\u2822' }, + { '(', '\u2823' }, + { ')', '\u281C' }, + { '\n', '\n' }, + { '\f', '\f' }, + }; + + // Spanish Grade 1 Braille mapping + private static readonly Dictionary SpanishGrade1Map = new() + { + // Letters (a-z) - same as English + { 'a', '\u2801' }, { 'b', '\u2803' }, { 'c', '\u2809' }, { 'd', '\u2819' }, + { 'e', '\u2811' }, { 'f', '\u280B' }, { 'g', '\u281B' }, { 'h', '\u2813' }, + { 'i', '\u280A' }, { 'j', '\u281A' }, { 'k', '\u2805' }, { 'l', '\u2807' }, + { 'm', '\u280D' }, { 'n', '\u281D' }, { 'o', '\u2815' }, { 'p', '\u280F' }, + { 'q', '\u281F' }, { 'r', '\u2817' }, { 's', '\u280E' }, { 't', '\u281E' }, + { 'u', '\u2825' }, { 'v', '\u2827' }, { 'w', '\u283A' }, { 'x', '\u282D' }, + { 'y', '\u283D' }, { 'z', '\u2835' }, + + // Capital letters + { 'A', CapitalIndicator }, { 'B', CapitalIndicator }, { 'C', CapitalIndicator }, { 'D', CapitalIndicator }, + { 'E', CapitalIndicator }, { 'F', CapitalIndicator }, { 'G', CapitalIndicator }, { 'H', CapitalIndicator }, + { 'I', CapitalIndicator }, { 'J', CapitalIndicator }, { 'K', CapitalIndicator }, { 'L', CapitalIndicator }, + { 'M', CapitalIndicator }, { 'N', CapitalIndicator }, { 'O', CapitalIndicator }, { 'P', CapitalIndicator }, + { 'Q', CapitalIndicator }, { 'R', CapitalIndicator }, { 'S', CapitalIndicator }, { 'T', CapitalIndicator }, + { 'U', CapitalIndicator }, { 'V', CapitalIndicator }, { 'W', CapitalIndicator }, { 'X', CapitalIndicator }, + { 'Y', CapitalIndicator }, { 'Z', CapitalIndicator }, + + // Numbers + { '1', '\u2801' }, { '2', '\u2803' }, { '3', '\u2809' }, { '4', '\u2819' }, + { '5', '\u2811' }, { '6', '\u280B' }, { '7', '\u281B' }, { '8', '\u2813' }, + { '9', '\u280A' }, { '0', '\u281A' }, + + // Spanish accented characters + { 'á', '\u2831' }, { 'é', '\u283F' }, { 'í', '\u280C' }, + { 'ó', '\u283B' }, { 'ú', '\u283C' }, + { 'ñ', '\u283B' }, { 'ü', '\u283C' }, + { '¿', '\u2826' }, { '¡', '\u2816' }, + + // Common punctuation + { ' ', BlankCell }, + { ',', '\u2802' }, + { '.', '\u2832' }, + { '?', '\u2826' }, + { '!', '\u2816' }, + { '\'', '\u2804' }, + { '-', '\u2824' }, + { ':', '\u2812' }, + { ';', '\u2822' }, + { '(', '\u2823' }, + { ')', '\u281C' }, + { '\n', '\n' }, + { '\f', '\f' }, + }; + + /// + /// Gets the appropriate translation map for the current language. + /// + private Dictionary GetTranslationMap() + { + return _currentLanguage switch + { + BrailleLanguage.FrenchGrade1 => FrenchGrade1Map, + BrailleLanguage.GermanGrade1 => GermanGrade1Map, + BrailleLanguage.SpanishGrade1 => SpanishGrade1Map, + _ => EnglishGrade1Map + }; + } + + /// + /// Translates plain text to Braille. + /// + /// The text to translate. + /// List of Braille lines. + public List Translate(string text) + { + if (string.IsNullOrEmpty(text)) + return new List(); + + var translationMap = GetTranslationMap(); + + // Split by newlines but preserve form feeds + var lines = text.Split(new[] { '\r', '\n' }, StringSplitOptions.None) + .Where(line => !string.IsNullOrEmpty(line) || line == string.Empty) + .ToList(); + + var brailleLines = new List(); + + foreach (var line in lines) + { + if (line.Contains('\f')) + { + brailleLines.Add("\f"); + continue; + } + + var brailleLine = new System.Text.StringBuilder(); + + foreach (var ch in line) + { + if (char.IsUpper(ch)) + { + // Add capital indicator before the letter + brailleLine.Append(CapitalIndicator); + // Then add the lowercase version + var lower = char.ToLower(ch); + if (translationMap.TryGetValue(lower, out var brailleChar)) + { + brailleLine.Append(brailleChar); + } + else + { + // Unknown character, use blank + brailleLine.Append(BlankCell); + } + } + else if (translationMap.TryGetValue(ch, out var brailleChar)) + { + if (brailleChar != '\n' && brailleChar != '\f') + brailleLine.Append(brailleChar); + } + else + { + // Unknown character, use blank cell + brailleLine.Append(BlankCell); + } + } + + brailleLines.Add(brailleLine.ToString()); + } + + return brailleLines; + } + } +} diff --git a/MakerPrompt.Shared/Layout/NavMenu.razor b/MakerPrompt.Shared/Layout/NavMenu.razor index f716897..498410f 100644 --- a/MakerPrompt.Shared/Layout/NavMenu.razor +++ b/MakerPrompt.Shared/Layout/NavMenu.razor @@ -20,6 +20,11 @@ @Resources.PageTitle_Calculators +