diff --git a/.gitignore b/.gitignore index 86c063c..cedb7b8 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ runtime.*.liblouis/runtimes/ # Rider / ReSharper per-user settings *.DotSettings.user + +# Worktrees created by spawned Claude Code sessions +.claude/worktrees/ diff --git a/LibLouis.NET.Test/AssemblyInfo.cs b/LibLouis.NET.Test/AssemblyInfo.cs new file mode 100644 index 0000000..dffc93b --- /dev/null +++ b/LibLouis.NET.Test/AssemblyInfo.cs @@ -0,0 +1,7 @@ +using Xunit; + +// liblouis keeps global state (compiled table cache, log callback, data path) and is explicitly +// not thread safe - LibLouis serialises its own calls behind a lock for exactly that reason. +// xunit parallelises across test classes by default, which lets unsynchronised native calls race +// and produce spurious "could not be compiled" failures. Run the whole assembly serially. +[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/LibLouis.NET.Test/BrailleSpec.cs b/LibLouis.NET.Test/BrailleSpec.cs new file mode 100644 index 0000000..a3e944e --- /dev/null +++ b/LibLouis.NET.Test/BrailleSpec.cs @@ -0,0 +1,398 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; + +using YamlDotNet.Core; +using YamlDotNet.Core.Events; + +namespace LibLouis.NET.Test; + +/// +/// One translation case from an upstream braille spec. +/// +public sealed record BrailleSpecCase( + string SpecFile, + int Line, + string TableQuery, + string? AssertMatch, + string DisplayTable, + string Input, + string Expected, + TestDirection Direction, + bool ExpectedToFail) +{ + public override string ToString() => + $"{SpecFile}:{Line} {Direction} {Describe(Input)} -> {Describe(Expected)}"; + + // Braille output is mostly U+28xx, which is unreadable in a test runner's output, so show the + // code points for anything outside printable ASCII. + private static string Describe(string value) => + value.All(c => c is >= ' ' and <= '~') + ? $"\"{value}\"" + : string.Concat(value.Select(c => c is >= ' ' and <= '~' ? c.ToString() : $"\\u{(int)c:X4}")); +} + +public enum TestDirection +{ + Forward, + Backward, +} + +/// +/// How a spec entry's two values are used. The distinction matters: the backward leg of +/// bothDirections swaps input and expected (lou_checkyaml.c:900-902), while an explicit +/// backward testmode does not (lou_checkyaml.c:892-895). +/// +public enum TestMode +{ + Forward, + Backward, + BothDirections, +} + +/// +/// Reads liblouis braille spec files. +/// +/// +/// These files are not YAML mappings and cannot be deserialised. A single document repeats +/// table, flags and tests at the same level, which duplicate key handling +/// would collapse or reject. lou_checkyaml treats the file as an event stream where each key +/// mutates parser state, and tests executes against whatever is current +/// (tools/lou_checkyaml.c:1087-1139), so this reader does the same over YamlDotNet's IParser. +/// +/// Consecutive table keys accumulate rather than replace: the following tests block +/// runs once per accumulated table. flags persists until the next flags. +/// +/// Only the constructs the Danish specs actually use are supported. Anything else throws rather +/// than being skipped, so a spec using a feature this reader does not model fails loudly instead +/// of silently testing less than it appears to. +/// +public static class BrailleSpecReader +{ + public static IReadOnlyList Read(string path) + { + string specFile = Path.GetFileName(path); + var cases = new List(); + + using var reader = new StreamReader(path); + var parser = new Parser(reader); + + parser.Consume(); + parser.Consume(); + parser.Consume(); + + string displayTable = string.Empty; + var tables = new List<(string Query, string? AssertMatch)>(); + TestMode mode = TestMode.Forward; + + // Consecutive table keys accumulate, but the first one after a tests block starts a fresh + // set rather than adding to the one just used. + bool tablesUsed = false; + + while (parser.Current is not MappingEnd) + { + string key = parser.Consume().Value; + + switch (key) + { + case "display": + displayTable = ReadTableValue(parser).Query; + break; + + case "table": + if (tablesUsed) + { + tables.Clear(); + tablesUsed = false; + } + + tables.Add(ReadTableValue(parser)); + break; + + case "flags": + mode = ReadFlags(parser); + break; + + case "tests": + ReadTests(parser, specFile, tables, displayTable, mode, cases); + tablesUsed = true; + break; + + default: + throw new NotSupportedException( + $"{specFile}: unsupported top level key '{key}'. This reader models only the " + + "constructs the Danish specs use; see lou_checkyaml.c for the full format."); + } + + } + + return cases; + } + + /// + /// A table value is either a file name or a query mapping. Queries are passed to lou_findTable + /// as "key:value key:value"; __assert-match is a harness directive, not part of the query. + /// + private static (string Query, string? AssertMatch) ReadTableValue(IParser parser) + { + if (parser.Current is Scalar scalar) + { + parser.MoveNext(); + return (scalar.Value, null); + } + + parser.Consume(); + + var terms = new List(); + string? assertMatch = null; + + while (parser.Current is not MappingEnd) + { + string key = parser.Consume().Value; + string value = parser.Consume().Value; + + if (key == "__assert-match") + { + assertMatch = value; + } + else + { + terms.Add($"{key}:{value}"); + } + } + + parser.Consume(); + + return (string.Join(' ', terms), assertMatch); + } + + private static TestMode ReadFlags(IParser parser) + { + parser.Consume(); + + TestMode mode = TestMode.Forward; + + while (parser.Current is not MappingEnd) + { + string key = parser.Consume().Value; + string value = parser.Consume().Value; + + if (key != "testmode") + { + throw new NotSupportedException($"unsupported flag '{key}'"); + } + + mode = ParseTestMode(value); + } + + parser.Consume(); + + return mode; + } + + private static TestMode ParseTestMode(string value) => value switch + { + "forward" => TestMode.Forward, + "backward" => TestMode.Backward, + "bothDirections" => TestMode.BothDirections, + _ => throw new NotSupportedException($"unsupported testmode '{value}'"), + }; + + private static void ReadTests( + IParser parser, + string specFile, + List<(string Query, string? AssertMatch)> tables, + string displayTable, + TestMode mode, + List cases) + { + parser.Consume(); + + while (parser.Current is not SequenceEnd) + { + SequenceStart entryStart = parser.Consume(); + int line = (int)entryStart.Start.Line; + + string input = Unescape(parser.Consume().Value); + string expected = Unescape(parser.Consume().Value); + + var xfail = XFail.None; + TestMode entryMode = mode; + bool skip = false; + + if (parser.Current is MappingStart) + { + (xfail, entryMode, skip) = ReadTestOptions(parser, mode); + } + + parser.Consume(); + + if (skip) + { + continue; + } + + foreach ((string query, string? assertMatch) in tables) + { + // Forward compares translate(input) with expected. An explicit backward testmode + // means the entry is already written braille-first, so it is not swapped. The + // backward leg of bothDirections is: the expected braille is the input, and the + // original text is what back translation should produce. + if (entryMode is TestMode.Forward or TestMode.BothDirections) + { + cases.Add(new BrailleSpecCase( + specFile, line, query, assertMatch, displayTable, + input, expected, TestDirection.Forward, xfail.HasFlag(XFail.Forward))); + } + + if (entryMode == TestMode.Backward) + { + cases.Add(new BrailleSpecCase( + specFile, line, query, assertMatch, displayTable, + input, expected, TestDirection.Backward, xfail.HasFlag(XFail.Backward))); + } + else if (entryMode == TestMode.BothDirections) + { + cases.Add(new BrailleSpecCase( + specFile, line, query, assertMatch, displayTable, + expected, input, TestDirection.Backward, xfail.HasFlag(XFail.Backward))); + } + } + } + + parser.Consume(); + } + + [Flags] + private enum XFail + { + None = 0, + Forward = 1, + Backward = 2, + Both = Forward | Backward, + } + + private static (XFail XFail, TestMode Mode, bool Skip) ReadTestOptions( + IParser parser, TestMode mode) + { + parser.Consume(); + + var xfail = XFail.None; + bool skip = false; + + while (parser.Current is not MappingEnd) + { + string key = parser.Consume().Value; + + switch (key) + { + case "xfail": + xfail = ReadXFail(parser); + break; + + case "testmode": + mode = ParseTestMode(parser.Consume().Value); + break; + + // Emphasis is applied through typeform, which this prototype does not drive yet. + // Skip the value so the rest of the file still parses, and drop the case: silently + // running it without the typeform would compare against the wrong expectation. + case "typeform": + parser.SkipThisAndNestedEvents(); + skip = true; + break; + + default: + throw new NotSupportedException($"unsupported test option '{key}'"); + } + } + + parser.Consume(); + + return (xfail, mode, skip); + } + + /// + /// xfail is either a scalar, where only "false" and "off" are falsy + /// (tools/lou_checkyaml.c:379-389), or a mapping naming the failing directions. + /// + private static XFail ReadXFail(IParser parser) + { + if (parser.Current is Scalar scalar) + { + parser.MoveNext(); + return scalar.Value is "false" or "off" ? XFail.None : XFail.Both; + } + + parser.Consume(); + + var xfail = XFail.None; + + while (parser.Current is not MappingEnd) + { + string key = parser.Consume().Value; + string value = parser.Consume().Value; + bool set = value is not ("false" or "off"); + + if (set) + { + xfail |= key switch + { + "forward" => XFail.Forward, + "backward" => XFail.Backward, + _ => throw new NotSupportedException($"unsupported xfail direction '{key}'"), + }; + } + } + + parser.Consume(); + + return xfail; + } + + /// + /// The specs use single quoted scalars, where YAML performs no escape processing at all, and + /// rely on liblouis to interpret the escapes itself. Only the forms the Danish specs actually + /// use are handled: \xNNNN and \uNNNN code points, and \\ for a literal backslash. + /// Without the backslash case, 'at\\bliver' parses as two backslashes and translates to two + /// cells where upstream expects one. + /// + private static string Unescape(string value) + { + if (!value.Contains('\\', StringComparison.Ordinal)) + { + return value; + } + + var builder = new StringBuilder(value.Length); + + for (int i = 0; i < value.Length; i++) + { + if (value[i] == '\\' && i + 1 < value.Length) + { + if (value[i + 1] is 'x' or 'y' or 'u' && i + 5 < value.Length && + ushort.TryParse( + value.AsSpan(i + 2, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ushort code)) + { + builder.Append((char)code); + i += 5; + continue; + } + + if (value[i + 1] is '\\' or '"') + { + builder.Append(value[i + 1]); + i++; + continue; + } + } + + builder.Append(value[i]); + } + + return builder.ToString(); + } +} diff --git a/LibLouis.NET.Test/BrailleSpecTests.cs b/LibLouis.NET.Test/BrailleSpecTests.cs new file mode 100644 index 0000000..3a8dc95 --- /dev/null +++ b/LibLouis.NET.Test/BrailleSpecTests.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +using Xunit; + +namespace LibLouis.NET.Test; + +/// +/// Runs the upstream braille specs for Danish through the managed wrapper. +/// +/// +/// These are liblouis's own expectations, so they check the wrapper end to end against thousands of +/// cases nobody here had to invent: not just that a translation succeeds, but that it produces the +/// characters upstream says it should, in both directions. +/// +/// The specs live in braille-specs/ and are copied verbatim from +/// upstream/liblouis-<version>/tests/braille-specs/. Re-copy them when the upstream version is +/// bumped; the diff is the set of expectations that changed. +/// +/// One test per spec file rather than per case. Ten thousand xunit cases makes discovery slow and +/// buries a real regression in an unreadable log; a single failure listing every mismatch is more +/// use than ten thousand separate red entries. +/// +public class BrailleSpecTests +{ + private static readonly string SpecDirectory = + Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "braille-specs"); + + // tables/ is the upstream set. Nota's own tables live in nota-tables/ and no longer shadow it, + // so the specs can be checked against the tables they were actually written for. + private static readonly string TableDirectory = + Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tables"); + + public static TheoryData SpecFiles() + { + var data = new TheoryData(); + + foreach (string path in Directory.EnumerateFiles(SpecDirectory, "*.yaml").OrderBy(f => f, StringComparer.Ordinal)) + { + data.Add(Path.GetFileName(path)); + } + + return data; + } + + [Theory] + [MemberData(nameof(SpecFiles))] + public void MatchesUpstreamExpectations(string specFile) + { + IReadOnlyList cases = BrailleSpecReader.Read(Path.Combine(SpecDirectory, specFile)); + + IndexUpstreamTables(); + + var mismatches = new List(); + var unexpectedPasses = new List(); + int checkedCount = 0; + + foreach (BrailleSpecCase testCase in cases) + { + + string table = ResolveTable(testCase, TableCache.Value); + string? actual = Run(testCase, table); + bool matched = actual == testCase.Expected; + checkedCount++; + + if (testCase.ExpectedToFail) + { + // Upstream reports an unexpected pass as a warning rather than an error: the case + // is known-broken, and it passing usually means the expectation moved. + if (matched) + { + unexpectedPasses.Add(testCase.ToString()); + } + } + else if (!matched) + { + mismatches.Add($"{testCase}\n actual: {Describe(actual)}"); + } + } + + + Assert.True( + mismatches.Count == 0, + $"{specFile}: {mismatches.Count} of {checkedCount} cases did not match upstream " + + $"({unexpectedPasses.Count} xfail cases passed unexpectedly).\n " + + string.Join("\n ", mismatches.Take(25)) + + (mismatches.Count > 25 ? $"\n ... and {mismatches.Count - 25} more" : string.Empty)); + } + + private static string? Run(BrailleSpecCase testCase, string table) + { + string[] tables = [ResolveDisplayTable(testCase.DisplayTable), table]; + int outputLength = Math.Max(testCase.Input.Length, testCase.Expected.Length) * 4; + + try + { + return testCase.Direction == TestDirection.Forward + ? LibLouis.Instance.Translate(tables, testCase.Input, outputLength, null, null, TranslationMode.Regular) + : LibLouis.Instance.BackTranslate(tables, testCase.Input, outputLength, null, null, TranslationMode.Regular); + } + catch (LibLouisException ex) + { + // A translation that fails outright is a mismatch, not an error: upstream marks some of + // these xfail, and letting it throw would stop the whole file at the first one. The + // message is carried into the comparison so a failure says why, rather than only that + // nothing came back. + return $""; + } + } + + // Every query from every spec is resolved once, up front, before any translation runs. + // lou_findTable's return value is freed by the marshaller with the wrong allocator (P/Invoke + // audit item 3), so interleaving these calls with translations corrupts the native heap. + private static readonly Lazy> TableCache = new(() => + { + LibLouis.Instance.IndexTables( + Directory.EnumerateFiles(TableDirectory) + .Where(f => Path.GetExtension(f) is ".ctb" or ".utb" or ".uti" or ".dis" or ".cti" or ".dic")); + + var resolved = new Dictionary(StringComparer.Ordinal); + + foreach (string query in Directory.EnumerateFiles(SpecDirectory, "*.yaml") + .SelectMany(BrailleSpecReader.Read) + .Select(c => c.TableQuery) + .Distinct(StringComparer.Ordinal)) + { + resolved[query] = LibLouis.Instance.FindTable(query) ?? string.Empty; + } + + return resolved; + }); + + private static void IndexUpstreamTables() => _ = TableCache.Value; + + /// + /// Resolving a table query is what lou_findTable does, and the specs assert which file a query + /// should select, so this covers table resolution as well as translation. + /// + private static string ResolveTable(BrailleSpecCase testCase, Dictionary cache) + { + if (!cache.TryGetValue(testCase.TableQuery, out string? resolved)) + { + resolved = LibLouis.Instance.FindTable(testCase.TableQuery) ?? string.Empty; + cache[testCase.TableQuery] = resolved; + } + + Assert.False( + string.IsNullOrEmpty(resolved), + $"No table matched the query '{testCase.TableQuery}' from {testCase.SpecFile}:{testCase.Line}"); + + if (testCase.AssertMatch is not null) + { + Assert.True( + string.Equals(Path.GetFileName(resolved), testCase.AssertMatch, StringComparison.Ordinal), + $"Query '{testCase.TableQuery}' resolved to {Path.GetFileName(resolved)}, " + + $"but {testCase.SpecFile}:{testCase.Line} asserts {testCase.AssertMatch}"); + } + + return resolved; + } + + /// + /// A spec's display table is usually a file name, but it can also be an inline table written as + /// a YAML block scalar, for instance to include the standard one and then override a character. + /// liblouis only takes paths, so the inline form is written out next to the tables, where the + /// includes inside it resolve. + /// + private static string ResolveDisplayTable(string display) + { + // A file name never contains a newline, so this distinguishes the two forms. + if (!display.Contains('\n', StringComparison.Ordinal)) + { + return Path.Combine(TableDirectory, display); + } + + string name = $"inline-{Convert.ToHexString(System.Security.Cryptography.MD5.HashData(System.Text.Encoding.UTF8.GetBytes(display)))[..8]}.dis"; + string path = Path.Combine(TableDirectory, name); + + if (!File.Exists(path)) + { + File.WriteAllText(path, display); + } + + return path; + } + + private static string Describe(string? value) => + value is null + ? "" + : string.Concat(value.Select(c => c is >= ' ' and <= '~' ? c.ToString() : $"\\u{(int)c:X4}")); +} diff --git a/LibLouis.NET.Test/CollectingLogger.cs b/LibLouis.NET.Test/CollectingLogger.cs new file mode 100644 index 0000000..9049579 --- /dev/null +++ b/LibLouis.NET.Test/CollectingLogger.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; + +using Microsoft.Extensions.Logging; + +namespace LibLouis.NET.Test; + +/// +/// Captures everything liblouis logs, so tests can assert on what the native side reported. +/// +internal sealed class CollectingLogger : ILogger +{ + public List Messages { get; } = []; + + public IDisposable? BeginScope(TState state) where TState : notnull => null; + + // LogLevel is qualified throughout: in this namespace the unqualified name binds to + // LibLouis.NET.LogLevel, the native enum, not the Microsoft.Extensions.Logging one. + public bool IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel) => true; + + public void Log( + Microsoft.Extensions.Logging.LogLevel logLevel, + EventId eventId, + TState state, + Exception? exception, + Func formatter) + { + Messages.Add(formatter(state, exception)); + } +} diff --git a/LibLouis.NET.Test/HyphenateTests.cs b/LibLouis.NET.Test/HyphenateTests.cs new file mode 100644 index 0000000..2da22eb --- /dev/null +++ b/LibLouis.NET.Test/HyphenateTests.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + +using Xunit; + +namespace LibLouis.NET.Test; + +/// +/// lou_hyphenate takes a caller-allocated char *hyphens buffer and writes inlen + 1 bytes +/// into it: '0' or '1' per character, plus a terminator (lou_translateString.c:4080). +/// +public class HyphenateTests +{ + private const string Word = "bogstaver"; + + private static readonly string[] Tables = ["da-dk-braillo.dis", "da-dk-g26.ctb"]; + + private static string[] TablePaths() => + [.. Tables.Select(t => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "nota-tables", t))]; + + [Fact] + public void Hyphenate_ReturnsOneHyphenationFlagPerCharacter() + { + string hyphens = LibLouis.Instance.Hyphenate(TablePaths(), Word, TranslationMode.Regular); + + Assert.Equal(Word.Length, hyphens.Length); + Assert.Matches("^[012]+$", hyphens); + + // "bog-sta-ver": the table has to find at least one break, otherwise this test is not + // exercising hyphenation at all. + Assert.Contains('1', hyphens); + } + + /// + /// The result must describe the word that was passed in, not a NUL terminator the wrapper + /// added. Unlike lou_translateString, lou_hyphenate does not clamp inlen at the first NUL: + /// it memcpy's exactly inlen characters, so an inflated inlen hyphenates the terminator too. + /// + [Fact] + public void Hyphenate_DoesNotIncludeTheNulTerminator() + { + foreach (string word in new[] { "a", "bo", "bogstaver", "hyphenation" }) + { + string hyphens = LibLouis.Instance.Hyphenate(TablePaths(), word, TranslationMode.Regular); + + Assert.Equal(word.Length, hyphens.Length); + } + } + + /// + /// inlen is a widechar count. On a UCS-4 build a non-BMP character is one widechar but two + /// chars, so passing string.Length claims the buffer is longer than it is - and lou_hyphenate + /// memcpy's exactly inlen widechars out of it, with no terminator to stop at. + /// + [Theory] + [InlineData("bogstaver\U0001D11E")] // one flag too many + [InlineData("bogstaver\U0001D11E\U0001D11E")] // and reads past the input buffer + public void Hyphenate_ReturnsOneFlagPerWidecharNotPerCodeUnit(string word) + { + int expected = SafeNativeMethods.lou_charSize() == 4 + ? word.EnumerateRunes().Count() + : word.Length; + + string hyphens = LibLouis.Instance.Hyphenate(TablePaths(), word, TranslationMode.Regular); + + Assert.Equal(expected, hyphens.Length); + } +} diff --git a/LibLouis.NET.Test/IndexTablesTests.cs b/LibLouis.NET.Test/IndexTablesTests.cs new file mode 100644 index 0000000..7bfac4e --- /dev/null +++ b/LibLouis.NET.Test/IndexTablesTests.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using Microsoft.Extensions.Logging; + +using Xunit; + +namespace LibLouis.NET.Test; + +/// +/// lou_indexTables walks its argument until it hits a NULL pointer +/// (for (table = tables; *table; table++), metadata.c:905). A managed string[] marshals to +/// exactly Length pointers with no terminator, so liblouis reads past the end of the array. +/// +public class IndexTablesTests +{ + private static readonly string[] Tables = ["da-dk-g26.ctb", "da-dk-g16-markers.ctb"]; + + private static string[] TablePaths() => + [.. Tables.Select(t => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "nota-tables", t))]; + + /// + /// liblouis logs one "Analyzing table <name>" line per array entry it walks, so the number + /// of those lines is a direct measure of how far it read. + /// + /// + /// Without the terminator this does not fail, it hangs: liblouis reads the managed memory + /// following the array as a char* and _lou_logMessage formats it with %s until it runs out of + /// readable memory. A regression here shows up as a test run that never finishes. + /// + [Fact] + public void IndexTables_DoesNotReadPastTheEndOfTheArray() + { + string[] paths = TablePaths(); + + CollectingLogger logger = new(); + ILogger previous = LibLouis.Instance.Logger; + LibLouis.Instance.Logger = logger; + + try + { + LibLouis.Instance.IndexTables(paths); + } + finally + { + LibLouis.Instance.Logger = previous; + } + + List analyzed = [.. logger.Messages + .Where(m => m.StartsWith("Analyzing table ", StringComparison.Ordinal)) + .Select(m => m["Analyzing table ".Length..])]; + + Assert.Equal(paths, analyzed); + } + +} diff --git a/LibLouis.NET.Test/InputLengthTests.cs b/LibLouis.NET.Test/InputLengthTests.cs new file mode 100644 index 0000000..605a37e --- /dev/null +++ b/LibLouis.NET.Test/InputLengthTests.cs @@ -0,0 +1,82 @@ +using System; +using System.IO; +using System.Linq; + +using Xunit; + +namespace LibLouis.NET.Test; + +/// +/// inlen is a widechar count that excludes the NUL terminator, matching the header and what +/// upstream callers pass. These tests pin down the two properties that depend on it: +/// +/// * The terminator is not translated as if it were text. The buffer stays NUL terminated +/// (PrepareUCSInputBuffer's job) and lou_translateString clamps at the first NUL +/// (while (k < *inlen && inbufx[k]) k++;, lou_translateString.c:1191), so an +/// embedded NUL still ends the input. +/// * Nothing is written past the position arrays the argument checks demand. liblouis +/// overwrites *inlen with the number of characters actually consumed +/// (lou_translateString.c:1354) before computing outputPos. +/// +/// The wrapper previously passed input.Length + 1 here. That was safe - the clamp at :1191 and +/// the overwrite at :1354 between them made the extra count unreachable - but it left +/// correctness resting on two undocumented internals instead of the documented contract. +/// +public class InputLengthTests +{ + private const string Input = "Første linje. Anden linje, med kursiveret tekst. Tredje linje."; + + private static readonly string[] Tables = ["da-dk-braillo.dis", "da-dk-g26.ctb"]; + + private static string[] TablePaths() => + [.. Tables.Select(t => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "nota-tables", t))]; + + /// + /// Translate() only requires outputPosition to hold input.Length entries, so liblouis must + /// not write beyond that. The array is deliberately oversized and sentinel filled, so an + /// out-of-bounds write would be observable here instead of corrupting the heap. + /// + [Fact] + public void Translate_DoesNotWriteOutputPositionsPastInputLength() + { + // Not -1: liblouis pre-fills outputPos with -1 for the characters it owns, so -1 could + // not tell an untouched entry apart from one liblouis had written. + const int sentinel = int.MinValue; + const int slack = 8; + + int outputLength = Input.Length * 4; + + int[] outputPosition = new int[Input.Length + slack]; + Array.Fill(outputPosition, sentinel); + + LibLouis.Instance.Translate( + TablePaths(), + Input, + outputLength, + null, + null, + outputPosition, + new int[outputLength], + 0, + TranslationMode.Regular); + + int firstUntouched = Array.FindIndex(outputPosition, p => p == sentinel); + + Assert.Equal(Input.Length, firstUntouched); + } + + /// + /// The NUL terminator is not translated as if it were input text. + /// + [Fact] + public void Translate_DoesNotTranslateTheNulTerminator() + { + const string input = "abc"; + + string translated = LibLouis.Instance.Translate( + TablePaths(), input, input.Length * 4, null, null, TranslationMode.Regular); + + Assert.DoesNotContain('\0', translated); + Assert.Equal(input, translated); + } +} diff --git a/LibLouis.NET.Test/LibLouis.NET.Test.csproj b/LibLouis.NET.Test/LibLouis.NET.Test.csproj index 8dee374..1e8ca27 100644 --- a/LibLouis.NET.Test/LibLouis.NET.Test.csproj +++ b/LibLouis.NET.Test/LibLouis.NET.Test.csproj @@ -5,6 +5,8 @@ enable false true + + true @@ -15,6 +17,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -27,7 +30,40 @@ - + + + PreserveNewest + + + + + PreserveNewest + + + + PreserveNewest diff --git a/LibLouis.NET.Test/LogCallbackTests.cs b/LibLouis.NET.Test/LogCallbackTests.cs new file mode 100644 index 0000000..507c7bc --- /dev/null +++ b/LibLouis.NET.Test/LogCallbackTests.cs @@ -0,0 +1,70 @@ +using System; + +using Microsoft.Extensions.Logging; + +using Xunit; + +namespace LibLouis.NET.Test; + +/// +/// liblouis keeps the function pointer it is handed by lou_registerLogCallback and calls it for +/// the rest of the process's life. The managed delegate behind that pointer therefore has to stay +/// alive for just as long: the marshalling stub only keeps it alive for the duration of the +/// registration call itself. +/// +public class LogCallbackTests +{ + /// + /// Forces collections between registering the callback and provoking a native log message. + /// If nothing roots the delegate, the pointer liblouis holds is dangling by then. + /// + [Fact] + public void Logger_StillReceivesMessagesAfterGarbageCollection() + { + CollectingLogger logger = new(); + LibLouis.Instance.Logger = logger; + + for (int i = 0; i < 3; i++) + { + GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, blocking: true, compacting: true); + GC.WaitForPendingFinalizers(); + } + + // Any failing call makes liblouis log; a table that cannot be compiled is the simplest. + Assert.Throws( + () => LibLouis.Instance.Translate( + ["no-such-table-at-all.ctb"], "x", 8, null, null, TranslationMode.Regular)); + + Assert.NotEmpty(logger.Messages); + } + + /// + /// The callback runs on a native stack. An exception thrown out of it cannot be handled by + /// liblouis and tears the process down, so an unmapped level must not throw. + /// + [Fact] + public void LogCallback_SurvivesALevelItDoesNotKnow() + { + CollectingLogger logger = new(); + LibLouis.Instance.Logger = logger; + + // 12345 is not one of the logLevels values liblouis defines. + NativeMethods.LoggingCallback callback = GetRegisteredCallback(); + + callback((LogLevel)12345, "message at an unknown level"); + } + + /// + /// Reaches the delegate the wrapper registered, so the test calls exactly what liblouis calls. + /// + private static NativeMethods.LoggingCallback GetRegisteredCallback() + { + object? field = typeof(LibLouis) + .GetField("_logCallback", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + ?.GetValue(LibLouis.Instance); + + Assert.NotNull(field); + + return (NativeMethods.LoggingCallback)field; + } +} diff --git a/LibLouis.NET.Test/NativeLockTests.cs b/LibLouis.NET.Test/NativeLockTests.cs new file mode 100644 index 0000000..48ce8b8 --- /dev/null +++ b/LibLouis.NET.Test/NativeLockTests.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +using Xunit; + +namespace LibLouis.NET.Test; + +/// +/// liblouis is not thread safe and its state is process-global, so every native call in the +/// assembly has to serialise on one lock - including the ones that do not obviously touch shared +/// state. +/// +/// +/// Holding the lock is not directly observable: lou_version returns a static string and the +/// Logging setters are single pointer-sized writes, so an unsynchronised build does not reliably +/// misbehave. These tests therefore guard the two things that are observable - that no native +/// entry point was left outside the lock, and that adding the lock did not introduce a deadlock +/// or change behaviour. +/// +public class NativeLockTests +{ + private static readonly string[] Tables = ["da-dk-braillo.dis", "da-dk-g26.ctb"]; + + private static string[] TablePaths() => + [.. Tables.Select(t => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "nota-tables", t))]; + + [Fact] + public void VersionIsReported() + { + Assert.False(string.IsNullOrWhiteSpace(LibLouis.Instance.Version)); + } + + [Fact] + public void LogLevelRoundTrips() + { + LogLevel previous = Logging.LogLevel; + + try + { + Logging.LogLevel = LogLevel.Warning; + Assert.Equal(LogLevel.Warning, Logging.LogLevel); + } + finally + { + Logging.LogLevel = previous; + } + } + + /// + /// The lock is shared between LibLouis and the static Logging helper, and Monitor is + /// reentrant, so hammering all three from several threads must neither deadlock nor produce a + /// wrong translation. + /// + [Fact] + public async Task ConcurrentUseDoesNotDeadlockOrCorrupt() + { + const string input = "Første linje"; + const string expected = "@fze linje"; + + LogLevel previous = Logging.LogLevel; + + using CancellationTokenSource cts = new(TimeSpan.FromSeconds(5)); + + ConcurrentBag failures = []; + + try + { + Task[] workers = + [ + .. Enumerable.Range(0, 4).Select(_ => Task.Run(() => + { + while (!cts.IsCancellationRequested) + { + string result = LibLouis.Instance.Translate( + TablePaths(), input, 64, null, null, TranslationMode.Regular); + + if (result != expected) + { + failures.Add($"translation returned '{result}'"); + return; + } + } + })), + Task.Run(() => + { + while (!cts.IsCancellationRequested) + { + _ = LibLouis.Instance.Version; + Logging.LogLevel = LogLevel.Error; + } + }), + ]; + + Task all = Task.WhenAll(workers); + + Assert.Same( + all, + await Task.WhenAny(all, Task.Delay(TimeSpan.FromSeconds(30)))); + + await all; + } + finally + { + Logging.LogLevel = previous; + } + + Assert.Empty(failures); + } + + /// + /// Catches a native call added later without the lock. Deliberately source-based: there is no + /// runtime signal for "this P/Invoke ran unsynchronised". + /// + /// + /// A call that genuinely does not need the lock has to say so, by carrying an "unlocked:" + /// comment giving the reason. That keeps the exemptions few and explains each one, instead of + /// letting the test quietly special-case whole methods. + /// + [Theory] + [InlineData("LibLouis.cs")] + [InlineData("Logging.cs")] + public void EveryNativeCallSiteIsLockedOrJustified(string fileName) + { + string[] lines = ReadLibrarySource(fileName).Split('\n'); + + int depth = 0; + int lockDepth = -1; + bool pendingLock = false; + + for (int i = 0; i < lines.Length; i++) + { + string line = lines[i].Trim(); + + // The body starts at the brace on the next line, so record the depth once we are + // actually inside it rather than on the "lock (" line itself. + if (line.StartsWith("lock (", StringComparison.Ordinal)) + { + pendingLock = true; + } + + bool isNativeCall = line.Contains("NativeMethods.", StringComparison.Ordinal) + && !line.StartsWith("//", StringComparison.Ordinal) + && !line.StartsWith("///", StringComparison.Ordinal) + && !line.Contains("NativeMethods.LoggingCallback", StringComparison.Ordinal); + + if (isNativeCall && lockDepth < 0) + { + bool justified = lines + .Take(i) + .Reverse() + .TakeWhile(l => l.Trim().StartsWith("//", StringComparison.Ordinal)) + .Any(l => l.Contains("unlocked:", StringComparison.Ordinal)); + + Assert.True(justified, $"{fileName}: native call outside a lock: {line}"); + } + + int updated = depth + lines[i].Count(c => c == '{') - lines[i].Count(c => c == '}'); + + if (pendingLock && updated > depth) + { + lockDepth = updated; + pendingLock = false; + } + + depth = updated; + + if (lockDepth >= 0 && depth < lockDepth) + { + lockDepth = -1; + } + } + } + + private static string ReadLibrarySource(string fileName) + { + DirectoryInfo? directory = new(AppContext.BaseDirectory); + + while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "LibLouis.NET.sln"))) + { + directory = directory.Parent; + } + + Assert.NotNull(directory); + + string path = Path.Combine(directory.FullName, "LibLouis.NET", fileName); + + Assert.True(File.Exists(path), $"could not locate {path}"); + + return File.ReadAllText(path); + } +} diff --git a/LibLouis.NET.Test/NativeMethodsTests.cs b/LibLouis.NET.Test/NativeMethodsTests.cs index a203486..0bb2032 100644 --- a/LibLouis.NET.Test/NativeMethodsTests.cs +++ b/LibLouis.NET.Test/NativeMethodsTests.cs @@ -23,7 +23,7 @@ public void SingleMode() Array.Fill(modes, TypeForm.ForeignLanguage); string resultString = LibLouis.Instance.Translate( - tables.Select(t => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tables", t)), + tables.Select(t => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "nota-tables", t)), input, outputLength, modes, @@ -48,7 +48,7 @@ public void NestedMode_EmphasisInForeignLanguage() modes[6] = TypeForm.ForeignLanguage | TypeForm.Emphasis; string resultString = LibLouis.Instance.Translate( - tables.Select(t => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tables", t)), + tables.Select(t => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "nota-tables", t)), input, outputLength, modes, @@ -89,7 +89,7 @@ public void NestedMode_ForeignLanguageInEmphasis() Assert.Equal(inputLength, modes.Length); string resultString = LibLouis.Instance.Translate( - tables.Select(t => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tables", t)), + tables.Select(t => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "nota-tables", t)), input, outputLength, modes, @@ -105,7 +105,7 @@ public void TestPositionResults() const string input = "Første linje. Anden linje, med kursiveret tekst. Tredje linje."; const string expected = "@fze linje. @anç linje, m kursi#rò ükz. @tàdje linje."; - string[] tables = ["tables/da-dk-braillo.dis", "tables/da-dk-g26.ctb"]; + string[] tables = ["nota-tables/da-dk-braillo.dis", "nota-tables/da-dk-g26.ctb"]; int outputLength = input.Length * 4; int cursorPosition = 0; @@ -116,7 +116,10 @@ public void TestPositionResults() Assert.Equal(expected, translated.Output); - Assert.Equal(inputPosition, translated.InputPosition); + // The returned arrays are sized to the strings they index rather than to the scratch + // buffers passed in, so InputPosition covers the output and no slicing is needed to use + // it. For this BMP input the values are unchanged from what liblouis wrote. + Assert.Equal(inputPosition[..translated.Output.Length], translated.InputPosition); Assert.Equal(outputPosition, translated.OutputPosition); Assert.Equal('A', input[inputPosition[12]]); diff --git a/LibLouis.NET.Test/NonBmpTests.cs b/LibLouis.NET.Test/NonBmpTests.cs new file mode 100644 index 0000000..18a5999 --- /dev/null +++ b/LibLouis.NET.Test/NonBmpTests.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; +using System.Linq; + +using Xunit; + +namespace LibLouis.NET.Test; + +/// +/// On a UCS-4 build a liblouis widechar holds a whole Unicode character, so a non-BMP character +/// occupies one widechar but two chars of a .NET string. Passing string.Length as a widechar +/// count therefore overstates the length of the buffer. +/// +/// lou_translateString survives that, because it clamps at the NUL terminator. lou_dotsToChar and +/// lou_charToDots do not clamp: they read and write exactly the count they are given +/// (lou_translateString.c:4142), so a count in the wrong unit reads past the input buffer. +/// +public class NonBmpTests +{ + /// U+1D11E MUSICAL SYMBOL G CLEF - one character, two UTF-16 code units. + private const string NonBmp = "\U0001D11E\U0001D11E"; + + private static readonly string[] Tables = ["da-dk-braillo.dis", "da-dk-g26.ctb"]; + + private static string[] TablePaths() => + [.. Tables.Select(t => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "nota-tables", t))]; + + /// + /// How many widechars liblouis sees for : whole characters on a UCS-4 + /// build, UTF-16 code units on a UCS-2 one. + /// + private static int ExpectedCells(string value) + { + return SafeNativeMethods.lou_charSize() == 4 + ? value.EnumerateRunes().Count() + : value.Length; + } + + [Fact] + public void CharactersToDots_ProducesOneCellPerWidecharNotPerCodeUnit() + { + string dots = LibLouis.Instance.CharactersToDots(TablePaths(), NonBmp); + + Assert.Equal(ExpectedCells(NonBmp), dots.Length); + } + + [Fact] + public void DotsToCharacters_ProducesOneCharacterPerCell() + { + string dots = LibLouis.Instance.CharactersToDots(TablePaths(), NonBmp); + + string roundTripped = LibLouis.Instance.DotsToCharacters(TablePaths(), dots); + + Assert.Equal(dots.Length, roundTripped.Length); + } + + /// + /// BMP text must keep behaving exactly as before: there string.Length and the widechar count + /// agree, so this guards the common case against the fix. + /// + [Fact] + public void CharactersToDots_IsUnchangedForBmpText() + { + const string input = "abc"; + + string dots = LibLouis.Instance.CharactersToDots(TablePaths(), input); + + Assert.Equal(input.Length, dots.Length); + } +} diff --git a/LibLouis.NET.Test/NotaTablesTests.cs b/LibLouis.NET.Test/NotaTablesTests.cs new file mode 100644 index 0000000..dc9d247 --- /dev/null +++ b/LibLouis.NET.Test/NotaTablesTests.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + +using Xunit; + +namespace LibLouis.NET.Test; + +/// +/// Guards the nota-tables/ directory, which the tests use in place of the upstream set. +/// +public class NotaTablesTests +{ + private static readonly string NotaTableDirectory = + Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "nota-tables"); + + private static readonly string UpstreamTableDirectory = + Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tables"); + + /// + /// liblouis resolves an include relative to the directory of the table doing the including, so + /// every table nota-tables/ pulls in has to be there too. Most are Nota's own; the handful of + /// general upstream tables they need are copied in by an explicit list in the csproj, and an + /// explicit list is exactly the kind of thing that goes stale the moment a table gains an + /// include. + /// + [Fact] + public void NotaTablesAreSelfContained() + { + var present = TableFilesIn(NotaTableDirectory).ToHashSet(StringComparer.Ordinal); + var missing = new SortedSet(StringComparer.Ordinal); + + foreach (string file in present) + { + foreach (string included in IncludesOf(Path.Combine(NotaTableDirectory, file))) + { + if (!present.Contains(included)) + { + missing.Add($"{included} (included by {file})"); + } + } + } + + Assert.True( + missing.Count == 0, + "nota-tables/ is missing tables it includes, so those cannot be compiled from that " + + "directory alone. Add them to the upstream copy list in the csproj:\n " + + string.Join("\n ", missing)); + } + + /// + /// The separate directory exists so that nothing of Nota's shadows the upstream set. Sharing a + /// file name across the two directories is expected and fine - twenty two of Nota's tables are + /// forks of an upstream table of the same name. What must never happen is Nota's tables being + /// copied into tables/ as well, which is what the old single directory did. + /// + /// The tables that exist nowhere upstream are the reliable canary: if one of them turns up in + /// tables/, the copy is misconfigured, whatever the file names say. + /// + [Fact] + public void NotaTablesDoNotLeakIntoTheUpstreamDirectory() + { + Assert.True(Directory.Exists(UpstreamTableDirectory), "the upstream tables were not staged"); + + string[] notaOnly = + [ + "da-dk-g16-markers.ctb", + "da-dk-g16_1993-markers.ctb", + "da-dk-braillo.dis", + "da-dk-g16-crossword.ctb", + "da-dk-g26l.ctb", + "da-dk-g26l-lit.ctb", + "da-dk-g28l.ctb", + "da-dk-g2_1993.dic", + ]; + + var upstream = TableFilesIn(UpstreamTableDirectory).ToHashSet(StringComparer.Ordinal); + var leaked = notaOnly.Where(upstream.Contains).OrderBy(f => f, StringComparer.Ordinal).ToList(); + + Assert.True( + leaked.Count == 0, + "tables/ should hold only the upstream set, but these tables of Nota's are in it, so " + + "the two are being copied to the same place again:\n " + string.Join("\n ", leaked)); + } + + private static IEnumerable TableFilesIn(string directory) => + Directory.EnumerateFiles(directory) + .Select(Path.GetFileName) + .Where(f => f is not null && !f.EndsWith(".md", StringComparison.OrdinalIgnoreCase))!; + + private static IEnumerable IncludesOf(string path) => + File.ReadLines(path) + .Select(line => Regex.Match(line, @"^\s*include\s+(\S+)")) + .Where(m => m.Success) + .Select(m => m.Groups[1].Value); +} diff --git a/LibLouis.NET.Test/OutputDotsTests.cs b/LibLouis.NET.Test/OutputDotsTests.cs new file mode 100644 index 0000000..6ce3373 --- /dev/null +++ b/LibLouis.NET.Test/OutputDotsTests.cs @@ -0,0 +1,101 @@ +using System; +using System.IO; +using System.Linq; + +using Xunit; + +namespace LibLouis.NET.Test; + +/// +/// On a successful forward translation liblouis reports, per output cell, whether the cell +/// contains dot 7 or dot 8 (lou_translateString.c:1330). It writes that into the typeform +/// buffer - which is why the buffer has to be output-sized, and why the caller's input-sized +/// array must not receive it. The wrapper surfaces the information on TranslatedString instead, +/// so callers get it without the write-past-the-end hazard. +/// +public class OutputDotsTests +{ + private static readonly string[] Tables = ["da-dk-braillo.dis", "da-dk-g08.ctb"]; + + private static string[] EightDotTables() => + [.. Tables.Select(t => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "nota-tables", t))]; + + private static TranslatedString TranslateWithTypeForm(string input) + { + TypeForm[] typeform = new TypeForm[input.Length]; + + return LibLouis.Instance.Translate( + EightDotTables(), + input, + input.Length * 4, + typeform, + null, + new int[input.Length], + new int[input.Length * 4], + 0, + TranslationMode.Regular); + } + + /// + /// Danish 8-dot braille marks a capital with dot 7 on the letter's own cell, so casing gives + /// a per-cell pattern we can predict: the flag must differ between the capital and the small + /// letters. + /// + [Fact] + public void ReportsDot7OnCapitalCells() + { + TranslatedString result = TranslateWithTypeForm("Abc"); + + Assert.NotNull(result.OutputDots78); + Assert.Equal(result.Output.Length, result.OutputDots78.Length); + + Assert.True(result.OutputDots78[0], "capital A should carry dot 7 in an 8-dot table"); + Assert.All(result.OutputDots78.Skip(1), d => Assert.False(d, "small letters should not")); + } + + /// + /// liblouis only computes the information when a typeform buffer is supplied, so without one + /// the property must be null rather than a fabricated all-false array. + /// + [Fact] + public void IsNullWhenNoTypeFormWasPassed() + { + TranslatedString result = LibLouis.Instance.Translate( + EightDotTables(), + "Abc", + 16, + null, + null, + new int[3], + new int[16], + 0, + TranslationMode.Regular); + + Assert.Null(result.OutputDots78); + } + + /// + /// The safety half of the contract, restated from the caller's side: surfacing the output + /// information must not bring back the write-back into the caller's array. + /// + [Fact] + public void CallersArrayStaysUntouched() + { + TypeForm[] typeform = new TypeForm[3]; + Array.Fill(typeform, TypeForm.Italic); + + TranslatedString result = LibLouis.Instance.Translate( + EightDotTables(), + "Abc", + 16, + typeform, + null, + new int[3], + new int[16], + 0, + TranslationMode.Regular); + + Assert.NotNull(result.OutputDots78); + Assert.All(typeform, t => Assert.Equal(TypeForm.Italic, t)); + } +} diff --git a/LibLouis.NET.Test/PositionMappingTests.cs b/LibLouis.NET.Test/PositionMappingTests.cs new file mode 100644 index 0000000..9ec1d80 --- /dev/null +++ b/LibLouis.NET.Test/PositionMappingTests.cs @@ -0,0 +1,181 @@ +using System; +using System.IO; +using System.Linq; + +using Xunit; + +namespace LibLouis.NET.Test; + +/// +/// liblouis indexes its position arrays in widechars - whole Unicode characters on our UCS-4 +/// builds. .NET callers read them as indices into a string, which is UTF-16. The two agree for +/// BMP text and diverge on the first non-BMP character, so the wrapper translates them. +/// +/// +/// The invariant that matters is not "the numbers look right" but that every value is directly +/// usable as a string index: Output[result.InputPosition[k]] must address the character +/// liblouis meant, and must never land on the trailing half of a surrogate pair. +/// +public class PositionMappingTests +{ + private static readonly string[] Tables = ["da-dk-braillo.dis", "da-dk-g26.ctb"]; + + private static string[] TablePaths() => + [.. Tables.Select(t => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "nota-tables", t))]; + + private static TranslatedString Translate(string input) + { + int outputLength = Math.Max(16, input.Length * 4); + + return LibLouis.Instance.Translate( + TablePaths(), + input, + outputLength, + null, + null, + new int[input.Length], + new int[outputLength], + 0, + TranslationMode.Regular); + } + + /// + /// Sized to the strings they index, so no slicing guesswork is needed. + /// + [Theory] + [InlineData("Første linje. Anden linje.")] + [InlineData("bogstaver")] + [InlineData("a\U0001D11Eb")] + [InlineData("\U0001D11E\U0001D11E")] + [InlineData("😀 hej")] + public void ArraysAreSizedToTheStringsTheyIndex(string input) + { + TranslatedString result = Translate(input); + + Assert.Equal(input.Length, result.OutputPosition.Length); + Assert.Equal(result.Output.Length, result.InputPosition.Length); + } + + /// + /// Every InputPosition value must be a usable index into the input string, and must address + /// the start of a character rather than the low half of a surrogate pair. + /// + [Theory] + [InlineData("Første linje. Anden linje.")] + [InlineData("a\U0001D11Eb")] + [InlineData("\U0001D11E\U0001D11E")] + [InlineData("😀 hej")] + public void InputPositionsAddressWholeCharactersOfTheInput(string input) + { + TranslatedString result = Translate(input); + + foreach (int position in result.InputPosition) + { + Assert.InRange(position, 0, input.Length - 1); + Assert.False( + char.IsLowSurrogate(input[position]), + $"position {position} lands on the trailing half of a surrogate pair"); + } + } + + /// + /// The same, in the other direction. + /// + [Theory] + [InlineData("Første linje. Anden linje.")] + [InlineData("a\U0001D11Eb")] + [InlineData("😀 hej")] + public void OutputPositionsAddressWholeCharactersOfTheOutput(string input) + { + TranslatedString result = Translate(input); + + foreach (int position in result.OutputPosition) + { + Assert.InRange(position, 0, result.Output.Length - 1); + Assert.False( + char.IsLowSurrogate(result.Output[position]), + $"position {position} lands on the trailing half of a surrogate pair"); + } + } + + /// + /// Both halves of a surrogate pair belong to the same character, so both must report the same + /// braille cell. + /// + [Fact] + public void SurrogatePairHalvesShareAnOutputPosition() + { + const string input = "a\U0001D11Eb"; + + TranslatedString result = Translate(input); + + // index 1 and 2 are the two halves of U+1D11E + Assert.Equal(result.OutputPosition[1], result.OutputPosition[2]); + + // and the surrounding BMP characters map elsewhere + Assert.NotEqual(result.OutputPosition[0], result.OutputPosition[1]); + } + + /// + /// BMP text must be completely unaffected: widechar and UTF-16 indices coincide there, so the + /// values have to match what liblouis wrote into the caller's scratch array. + /// + [Fact] + public void BmpTextIsUnchanged() + { + const string input = "Første linje. Anden linje, med kursiveret tekst. Tredje linje."; + + int outputLength = input.Length * 4; + + int[] scratchOutput = new int[input.Length]; + int[] scratchInput = new int[outputLength]; + + TranslatedString result = LibLouis.Instance.Translate( + TablePaths(), input, outputLength, null, null, scratchOutput, scratchInput, 0, TranslationMode.Regular); + + Assert.Equal(scratchOutput, result.OutputPosition); + Assert.Equal(scratchInput[..result.Output.Length], result.InputPosition); + } + + /// + /// The pattern that consumer code actually uses, which was correct only for BMP text. + /// + [Theory] + [InlineData("Første linje")] + [InlineData("a\U0001D11Eb")] + public void ConsumerSlicePatternStaysInBounds(string input) + { + TranslatedString result = Translate(input); + + int[] sliced = result.InputPosition[..result.Output.Length]; + + Assert.Equal(result.InputPosition.Length, sliced.Length); + Assert.All(sliced, p => Assert.InRange(p, 0, input.Length - 1)); + } + + /// + /// The cursor comes back as an index into the braille output, so it has to be translated too. + /// + [Fact] + public void CursorPositionIsAnIndexIntoTheOutput() + { + const string input = "a\U0001D11Ebc"; + + int outputLength = input.Length * 4; + + // Cursor on 'b', which sits after the surrogate pair. + TranslatedString result = LibLouis.Instance.Translate( + TablePaths(), + input, + outputLength, + null, + null, + new int[input.Length], + new int[outputLength], + input.IndexOf('b', StringComparison.Ordinal), + TranslationMode.Regular); + + Assert.InRange(result.CursorPosition, 0, result.Output.Length - 1); + Assert.False(char.IsLowSurrogate(result.Output[result.CursorPosition])); + } +} diff --git a/LibLouis.NET.Test/ReturnedStringOwnershipTests.cs b/LibLouis.NET.Test/ReturnedStringOwnershipTests.cs new file mode 100644 index 0000000..4c3a811 --- /dev/null +++ b/LibLouis.NET.Test/ReturnedStringOwnershipTests.cs @@ -0,0 +1,41 @@ +using System; +using System.IO; + +using Xunit; + +namespace LibLouis.NET.Test; + +/// +/// liblouis owns every string it returns, so the wrapper must not hand those pointers to the +/// marshaller's Free. +/// +/// * lou_setDataPath / lou_getDataPath return a pointer into a static char[MAXSTRING] inside +/// liblouis (compileTranslationTable.c:59-73). Passing that to free() is undefined behaviour +/// on every platform. +/// * lou_findTable returns malloc'd memory. Our Windows binaries are built with mingw-w64 and +/// allocate from msvcrt.dll, while .NET frees through ucrtbase.dll - different heaps, so +/// freeing it from managed code corrupts the heap there. +/// +public class ReturnedStringOwnershipTests +{ + /// + /// Setting the data path returns the static buffer, which the marshaller would then free. + /// + /// + /// The path is the test output directory rather than something arbitrary, because the data + /// path takes part in resolving relative table names and other tests rely on that. + /// + [Fact] + public void DataPath_RoundTripsWithoutFreeingLiblouisMemory() + { + string path = Path.TrimEndingDirectorySeparator(AppContext.BaseDirectory); + + LibLouis.Instance.DataPath = path; + + Assert.Equal(path, LibLouis.Instance.DataPath); + + // Reading it again returns the same static buffer; a stale free shows up here as a crash + // or as garbage. + Assert.Equal(path, LibLouis.Instance.DataPath); + } +} diff --git a/LibLouis.NET.Test/SafeNativeMethods.cs b/LibLouis.NET.Test/SafeNativeMethods.cs new file mode 100644 index 0000000..0bcf339 --- /dev/null +++ b/LibLouis.NET.Test/SafeNativeMethods.cs @@ -0,0 +1,41 @@ +using System.Runtime.InteropServices; +using System.Text; + +namespace LibLouis.NET.Test; + +/// +/// Raw P/Invoke used to characterise native behaviour without going through the wrapper. +/// +/// +/// Strings are passed as pre-encoded NUL terminated UTF-8 rather than as managed strings, so +/// there is no marshalling behaviour of our own between the test and liblouis. +/// +internal static class SafeNativeMethods +{ + /// + /// Bytes per liblouis widechar: 2 for a UCS-2 build, 4 for UCS-4. + /// + [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] + [DllImport("liblouis", EntryPoint = "lou_charSize")] + internal static extern int lou_charSize(); + + [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] + [DllImport("liblouis", EntryPoint = "lou_translateString")] + internal static extern int lou_translateString( + byte[] tableList, + byte[] inbuf, + ref int inlen, + byte[] outbuf, + ref int outlen, + ushort[]? typeform, + byte[]? spacing, + int mode); + + /// + /// Encodes a string the way liblouis expects a const char *. + /// + internal static byte[] Utf8(string value) + { + return Encoding.UTF8.GetBytes(value + "\0"); + } +} diff --git a/LibLouis.NET.Test/ShutdownTests.cs b/LibLouis.NET.Test/ShutdownTests.cs new file mode 100644 index 0000000..f7ccec0 --- /dev/null +++ b/LibLouis.NET.Test/ShutdownTests.cs @@ -0,0 +1,170 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; + +using Xunit; + +namespace LibLouis.NET.Test; + +/// +/// Shutdown frees liblouis's translation and display table chains, which are global to the +/// process. It is deliberately not IDisposable: nothing here is owned by a single caller, so +/// there is no "done with it" moment to hang disposal off, and an accidental +/// using (LibLouis.Instance) would tear liblouis down for everything else in the process. +/// +/// +/// These tests set the flag directly instead of calling Shutdown. LibLouis is a process-wide +/// singleton and the suite runs serially in one process, so really shutting it down would fail +/// every test that ran afterwards. The flag is restored in a finally for the same reason. +/// End-to-end shutdown is exercised out of process. +/// +public class ShutdownTests +{ + private static readonly string[] Tables = ["da-dk-braillo.dis", "da-dk-g26.ctb"]; + + private static string[] TablePaths() => + [.. Tables.Select(t => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "nota-tables", t))]; + + private static FieldInfo ShutDownField => + typeof(LibLouis).GetField("_shutDown", BindingFlags.NonPublic | BindingFlags.Static) + ?? throw new InvalidOperationException("_shutDown field not found"); + + /// + /// The shape change itself: an accidental using statement must not compile. + /// + [Fact] + public void LibLouisIsNotDisposable() + { + Assert.False(typeof(IDisposable).IsAssignableFrom(typeof(LibLouis))); + } + + /// + /// Shutdown is process-wide teardown, so it belongs on the type, not on an instance nobody + /// exclusively owns. + /// + [Fact] + public void ShutdownIsStatic() + { + MethodInfo? shutdown = typeof(LibLouis).GetMethod( + "Shutdown", BindingFlags.Public | BindingFlags.Static, Type.EmptyTypes); + + Assert.NotNull(shutdown); + Assert.Equal(typeof(void), shutdown.ReturnType); + } + + [Fact] + public void UsingTheInstanceAfterShutdownThrows() + { + WhileMarkedShutDown(() => + { + Assert.Throws( + () => LibLouis.Instance.Translate(TablePaths(), "abc", 16, null, null, TranslationMode.Regular)); + + Assert.Throws( + () => LibLouis.Instance.Translate( + TablePaths(), "abc", 16, null, null, new int[16], new int[16], 0, TranslationMode.Regular)); + + Assert.Throws( + () => LibLouis.Instance.BackTranslate(TablePaths(), "abc", 16, null, null, TranslationMode.Regular)); + + Assert.Throws( + () => LibLouis.Instance.CharactersToDots(TablePaths(), "abc")); + + Assert.Throws( + () => LibLouis.Instance.DotsToCharacters(TablePaths(), "abc")); + + Assert.Throws( + () => LibLouis.Instance.Hyphenate(TablePaths(), "bogstaver", TranslationMode.Regular)); + + Assert.Throws( + () => LibLouis.Instance.IndexTables(TablePaths())); + + Assert.Throws( + () => LibLouis.Instance.FindTable("type:literary")); + }); + } + + /// + /// The message has to say what happened: "cannot access a disposed object" would be a lie for + /// a type that is not disposable. + /// + [Fact] + public void TheFailureExplainsItself() + { + WhileMarkedShutDown(() => + { + InvalidOperationException e = Assert.Throws( + () => LibLouis.Instance.CharactersToDots(TablePaths(), "abc")); + + Assert.Contains("shut down", e.Message, StringComparison.OrdinalIgnoreCase); + }); + } + + /// + /// Diagnostics stay available: neither touches anything lou_free released. + /// + [Fact] + public void VersionAndLoggerSurviveShutdown() + { + WhileMarkedShutDown(() => + { + Assert.False(string.IsNullOrWhiteSpace(LibLouis.Instance.Version)); + + LibLouis.Instance.Logger = new CollectingLogger(); + }); + } + + /// + /// The instance works again once the flag is cleared, so the guard is the only thing stopping + /// it - the test is not just observing a broken singleton. + /// + [Fact] + public void TheGuardIsWhatBlocksUse() + { + WhileMarkedShutDown(() => + Assert.Throws( + () => LibLouis.Instance.CharactersToDots(TablePaths(), "abc"))); + + Assert.Equal(3, LibLouis.Instance.CharactersToDots(TablePaths(), "abc").Length); + } + + /// + /// lou_free is process-global, but a finalizer is per managed instance. In a collectible + /// AssemblyLoadContext that would free the tables of every other context still using them, + /// and it would do it on the finalizer thread, outside the lock. + /// + [Fact] + public void LibLouisHasNoFinalizer() + { + MethodInfo? finalizer = typeof(LibLouis) + .GetMethod("Finalize", BindingFlags.NonPublic | BindingFlags.Instance); + + Assert.Equal(typeof(object), finalizer?.DeclaringType); + } + + [Fact] + public void ShutDownFlagIsVolatile() + { + // Read outside the lock by the guards, written under it by Shutdown. + Assert.Contains( + ShutDownField.GetRequiredCustomModifiers(), + m => m == typeof(System.Runtime.CompilerServices.IsVolatile)); + } + + private static void WhileMarkedShutDown(Action body) + { + FieldInfo field = ShutDownField; + + field.SetValue(null, true); + + try + { + body(); + } + finally + { + field.SetValue(null, false); + } + } +} diff --git a/LibLouis.NET.Test/TypeFormBufferTests.cs b/LibLouis.NET.Test/TypeFormBufferTests.cs new file mode 100644 index 0000000..3d01cf2 --- /dev/null +++ b/LibLouis.NET.Test/TypeFormBufferTests.cs @@ -0,0 +1,129 @@ +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +using Xunit; + +namespace LibLouis.NET.Test; + +/// +/// liblouis writes the typeform array back for every *output* cell, not for every input +/// character. Passing a typeform array sized to the input therefore lets native code write +/// past the end of a managed array whenever the translation grows the text - which the +/// marker tables in this repository do routinely. +/// +public class TypeFormBufferTests +{ + private const string Input = "This is a test."; + + /// Translation of with the marker tables, 20 cells for 15 characters. + private const string ExpectedOutput = "`,@this is a test.`,"; + + private static readonly string[] Tables = ["da-dk-braillo.dis", "da-dk-g16-markers.ctb"]; + + private static string[] TablePaths() => + [.. Tables.Select(t => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "nota-tables", t))]; + + /// + /// Documents the native contract that makes the overrun possible, independent of the wrapper: + /// lou_translateString writes one typeform entry per output cell. Uses a deliberately + /// oversized buffer so nothing is corrupted while we measure how far native code writes. + /// + [Fact] + public void Native_WritesOneTypeformEntryPerOutputCell() + { + int charSize = SafeNativeMethods.lou_charSize(); + Encoding encoder = charSize == 4 ? Encoding.UTF32 : Encoding.Unicode; + + int inputLength = Input.Length; + int outputLength = ExpectedOutput.Length; + + byte[] inputBuffer = encoder.GetBytes(Input + "\0"); + byte[] outputBuffer = new byte[(outputLength + 1) * charSize]; + + // Far larger than either length, so native writes stay in bounds and are observable. + // typeform is in/out: the first inputLength entries are real input (foreign language, + // matching the other tests), the rest are plain text. Neither value collides with the + // ASCII '0' / '8' that liblouis writes back, so any such entry marks a native write. + ushort[] typeform = new ushort[outputLength * 4]; + Array.Fill(typeform, (ushort)TypeForm.ForeignLanguage, 0, inputLength); + + int inLen = inputLength; + int outLen = outputLength; + + int result = SafeNativeMethods.lou_translateString( + SafeNativeMethods.Utf8(string.Join(',', TablePaths())), + inputBuffer, + ref inLen, + outputBuffer, + ref outLen, + typeform, + null, + 0); + + Assert.NotEqual(0, result); + Assert.Equal(ExpectedOutput, encoder.GetString(outputBuffer, 0, outLen * charSize)); + + // liblouis writes the ASCII characters '0' / '8' per output cell. + int lastWritten = Array.FindLastIndex(typeform, t => t == '0' || t == '8'); + + Assert.Equal(outLen - 1, lastWritten); + + // The point of the test: native wrote beyond the input length, so an input-sized + // managed array would have been overrun by exactly this many entries. + Assert.True( + lastWritten >= inputLength, + $"Expected native writes past input length {inputLength}, but last write was at {lastWritten}."); + } + + /// + /// The wrapper must not let native code write into - let alone past - the caller's typeform + /// array. The public contract sizes typeform to the input, so the wrapper owes the caller a + /// buffer big enough for the output. + /// + [Fact] + public void Translate_DoesNotWriteIntoCallersTypeformArray() + { + TypeForm[] typeform = new TypeForm[Input.Length]; + Array.Fill(typeform, TypeForm.ForeignLanguage); + + TypeForm[] untouched = (TypeForm[])typeform.Clone(); + + string output = LibLouis.Instance.Translate( + TablePaths(), Input, Input.Length * 2, typeform, null, TranslationMode.Regular); + + Assert.Equal(ExpectedOutput, output); + Assert.Equal(untouched, typeform); + } + + /// + /// The same overrun through the position-reporting overload. + /// + [Fact] + public void TranslateWithPositions_DoesNotWriteIntoCallersTypeformArray() + { + int outputLength = Input.Length * 2; + + TypeForm[] typeform = new TypeForm[Input.Length]; + Array.Fill(typeform, TypeForm.ForeignLanguage); + + TypeForm[] untouched = (TypeForm[])typeform.Clone(); + + TranslatedString translated = LibLouis.Instance.Translate( + TablePaths(), + Input, + outputLength, + typeform, + null, + new int[Input.Length], + new int[outputLength], + 0, + TranslationMode.Regular); + + Assert.Equal(ExpectedOutput, translated.Output); + Assert.Equal(untouched, typeform); + } + +} diff --git a/LibLouis.NET.Test/UTF8StringNoFreeMarshallerTests.cs b/LibLouis.NET.Test/UTF8StringNoFreeMarshallerTests.cs new file mode 100644 index 0000000..eea4522 --- /dev/null +++ b/LibLouis.NET.Test/UTF8StringNoFreeMarshallerTests.cs @@ -0,0 +1,66 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +using Xunit; + +namespace LibLouis.NET.Test; + +/// +/// The marshaller used for strings liblouis owns. It only converts inbound: not freeing is +/// correct for memory liblouis allocated, and would be a leak for buffers we allocate ourselves, +/// so it is restricted to return values. +/// +public unsafe class UTF8StringNoFreeMarshallerTests +{ + [Theory] + [InlineData("")] + [InlineData("a")] + [InlineData("nota-tables/da-dk-g26.ctb")] + [InlineData("Første linje")] // multi-byte UTF-8 + [InlineData("\U0001D11E")] // non-BMP, surrogate pair on the managed side + public void ConvertToManaged_ReadsNulTerminatedUtf8(string value) + { + byte[] utf8 = Encoding.UTF8.GetBytes(value + "\0"); + + fixed (byte* unmanaged = utf8) + { + Assert.Equal(value, UTF8StringNoFreeMarshaller.ConvertToManaged(unmanaged)); + } + } + + /// + /// The string must stop at the terminator, not run on into whatever follows it. + /// + [Fact] + public void ConvertToManaged_StopsAtTheTerminator() + { + byte[] utf8 = Encoding.UTF8.GetBytes("abc\0trailing garbage"); + + fixed (byte* unmanaged = utf8) + { + Assert.Equal("abc", UTF8StringNoFreeMarshaller.ConvertToManaged(unmanaged)); + } + } + + [Fact] + public void ConvertToManaged_MapsNullPointerToNull() + { + Assert.Null(UTF8StringNoFreeMarshaller.ConvertToManaged(null)); + } + + /// + /// Free must leave the memory alone. If it released it, the allocator would abort on the + /// second release here. + /// + [Fact] + public void Free_DoesNotReleaseTheMemory() + { + byte* buffer = (byte*)NativeMemory.Alloc(4); + + UTF8StringNoFreeMarshaller.Free(buffer); + + // Ours to release, and still ours after Free. + NativeMemory.Free(buffer); + } +} diff --git a/LibLouis.NET.Test/braille-specs/da-dk-6dot.yaml b/LibLouis.NET.Test/braille-specs/da-dk-6dot.yaml new file mode 100644 index 0000000..c9a79a3 --- /dev/null +++ b/LibLouis.NET.Test/braille-specs/da-dk-6dot.yaml @@ -0,0 +1,1124 @@ +display: unicode-without-blank.dis + +# ------------- +# Grade 1 and 2 +# ------------- + +# Round trip tests +# Commented tests currently fail backwards but should be fixed. + +table: {language: da, grade: 1, dots: 6, version: 2022, __assert-match: da-dk-g16.ctb} +table: {language: da, grade: 2, dots: 6, version: 2022, __assert-match: da-dk-g26.ctb} +flags: {testmode: bothDirections} +tests: + +# Characters + + - [" ", " "] + - ["\"", "⠶"] + - ["\u0023", "⠘⠼"] + - ["$", "⠘⠙"] + - ["%", "⠚⠴"] + - ["'", "⠈"] + - ["+", "⠘⠖"] + - [",", "⠂"] + - ["-", "⠤⠤"] + - [".", "⠄"] + - ["0", "⠼⠚"] + - ["1", "⠼⠁"] + - ["2", "⠼⠃"] + - ["3", "⠼⠉"] + - ["4", "⠼⠙"] + - ["5", "⠼⠑"] + - ["6", "⠼⠋"] + - ["7", "⠼⠛"] + - ["8", "⠼⠓"] + - ["9", "⠼⠊"] + - [":", "⠒"] + - [";", "⠆"] + - ["<", "⠘⠍"] + - ["=", "⠘⠶"] + - [">", "⠘⠎"] + - ["?", "⠢"] + - ["@", "⠘⠁"] + - ["[", "⠐⠦"] + - ["]", "⠐⠴"] + - ["^", "⠘⠬"] + - ["_", "⠘⠤"] + - ["{", "⠘⠪"] + - ["}", "⠘⠕"] + - ["~", "⠘⠆"] + - ["€", "⠘⠑"] + - ["ƒ", "⠘⠋"] + - ["‰", "⠚⠴⠴"] + - ["Š", "⠠⠐⠎"] + - ["Ž", "⠠⠐⠵"] + - ["•", "⠘⠄"] + - ["™", "⠘⠞"] + - ["š", "⠐⠎"] + - ["©", "⠘⠉"] + - ["®", "⠘⠗"] + - ["°", "⠘⠴"] + - ["µ", "⠐⠍"] + - ["Ä", "⠠⠐⠜"] + - ["Ç", "⠠⠐⠉"] + - ["Î", "⠠⠐⠊"] + - ["Ð", "⠠⠐⠙"] + - ["Ñ", "⠠⠐⠝"] + - ["Ô", "⠠⠐⠕"] + - ["Ö", "⠠⠐⠪"] + - ["Û", "⠠⠐⠥"] + - ["Ý", "⠠⠐⠽"] + - ["Þ", "⠠⠐⠞"] + - ["ç", "⠐⠉"] + - ["î", "⠐⠊"] + - ["ð", "⠐⠙"] + - ["ñ", "⠐⠝"] + - ["ô", "⠐⠕"] + - ["ö", "⠐⠪"] + - ["û", "⠐⠥"] + - ["ý", "⠐⠽"] + - ["þ", "⠐⠞"] + +# Misc. Unicode (most cannot be back-translated) +# Some tests may be repititions of tests above, since +# some characters can occur both inside and outside the 8 bit range. +# for accented letters in the range u+0080 - u+00ff: the letters +# that are thought to occur most frequently in Danish texts are used +# for back-translation. +# For all accented letters above u+00ff, the first occurring instance is chosen for back-translation. +# There are no rules about this in the specs of Danish Braille, +# So the choice is arbitrary and may change over time. + + - ["\u0134\u0135", "⠠⠐⠚⠐⠚"] + - ["\u0138", "⠐⠟"] + - ["\u0192", "⠘⠋"] + +# Greek letters (with dot 5 as prefix) + + - ["\u0392\u03b2", "⠠⠐⠃⠐⠃"] + - ["\u0393\u03b3", "⠠⠐⠛⠐⠛"] + - ["\u0397\u03b7", "⠠⠐⠱⠐⠱"] + - ["\u0398\u03b8", "⠠⠐⠹⠐⠹"] + - ["\u039a\u03ba", "⠠⠐⠅⠐⠅"] + - ["\u039b\u03bb", "⠠⠐⠇⠐⠇"] + - ["\u039e\u03be", "⠠⠐⠭⠐⠭"] + - ["\u03a0\u03c0", "⠠⠐⠏⠐⠏"] + - ["\u03a1\u03c1", "⠠⠐⠗⠐⠗"] + - ["\u03a6\u03c6", "⠠⠐⠋⠐⠋"] + - ["\u03a7\u03c7", "⠠⠐⠓⠐⠓"] + - ["\u03a9\u03c9", "⠠⠐⠺⠐⠺"] + +# Arrows (only a few in 6 dots) + + - ["\u2190", "⠘⠺"] + - ["\u2191", "⠘⠷"] + - ["\u2193", "⠘⠟"] + +# Section sign + + - ["§ 3", "⠬⠼⠉"] + - ["§ 3:", "⠬⠼⠉⠒"] + - ["§§ 3-5", "⠬⠬⠼⠉⠤⠼⠑"] + +# Caps and mixed case + + - ["Foobar", "⠠⠋⠕⠕⠃⠁⠗"] + - ["FOOBAR", "⠸⠋⠕⠕⠃⠁⠗"] + - ["FOObar", "⠸⠋⠕⠕⠰⠃⠁⠗"] + - ["FOO-barfOObar", "⠸⠋⠕⠕⠤⠃⠁⠗⠋⠸⠕⠕⠰⠃⠁⠗"] + +# Times vs. bullit + + - ["\u2022Bullit", "⠘⠄⠠⠃⠥⠇⠇⠊⠞"] +# - ["2 \u00d7 2 = 4", "⠼⠃ ⠘⠄ ⠼⠃ ⠘⠶ ⠼⠙"] + +# Numbers and punctuation + + - ["1,2", "⠼⠁⠂⠃"] + - ["123.456", "⠼⠁⠃⠉⠄⠙⠑⠋"] + - ["3-4", "⠼⠉⠤⠼⠙"] + - ["56-", "⠼⠑⠋⠤"] + - ["1/2", "⠼⠁⠌⠃"] + - ["12:34", "⠼⠁⠃⠒⠉⠙"] + - ["2^10", "⠼⠃⠘⠬⠁⠚"] + - ["-dashes-", "⠤⠙⠁⠎⠓⠑⠎⠤"] + - ["---", "⠤⠤⠤"] + - ["-", "⠤⠤"] +# - [" -", " ⠤⠤"] + - [" - ", " ⠤⠤ "] + +# Digits and letters + + - ["1a", "⠼⠁⠰⠁"] + - ["1.,-a", "⠼⠁⠄⠂⠤⠰⠁"] + +# Characters and constructs which cannot be properly back-translated + +flags: {testmode: forward} +tests: + +# Single characters + + - ['\x0009', ' '] + - ['\x000a', ' '] + - ['\x000b', ' '] + - ['\x000c', ' '] + - ['\x000d', ' '] + - ["`", "⠈"] + - ["‚", "⠈"] + - ["„", "⠶"] + - ["…", "⠄⠄⠄"] + - ["‹", "⠈"] + - ["‘", "⠈"] + - ["’", "⠈"] + - ["“", "⠶"] + - ["”", "⠶"] + - ["Œ", "⠠⠕⠑"] + - ["–", "⠤⠤"] + - ["—", "⠤⠤"] + - ["˜", "⠘⠆"] + - ["›", "⠈"] + - ["œ", "⠕⠑"] + - ["Ÿ", "⠠⠐⠽"] + - ['\x00a0', ' '] + - ["«", "⠶"] + - ["±", "⠘⠖⠤"] + - ["´", "⠈"] + - ["»", "⠶"] + - ["¼", "⠼⠁⠌⠙"] + - ["½", "⠼⠁⠌⠃"] + - ["¾", "⠼⠉⠌⠙"] + - ["Á", "⠠⠐⠁"] + - ["Â", "⠠⠐⠁"] + - ["Ã", "⠠⠐⠁"] + - ["Ë", "⠠⠐⠑"] + - ["Ì", "⠠⠐⠊"] + - ["Í", "⠠⠐⠊"] + - ["Ï", "⠠⠐⠊"] + - ["Ò", "⠠⠐⠕"] + - ["Ó", "⠠⠐⠕"] + - ["Õ", "⠠⠐⠕"] + - ["Ù", "⠠⠐⠥"] + - ["Ú", "⠠⠐⠥"] + - ["ß", "⠎⠎"] + - ["á", "⠐⠁"] + - ["â", "⠐⠁"] + - ["ã", "⠐⠁"] + - ["ë", "⠐⠑"] + - ["ì", "⠐⠊"] + - ["í", "⠐⠊"] + - ["ï", "⠐⠊"] + - ["ò", "⠐⠕"] + - ["ó", "⠐⠕"] + - ["õ", "⠐⠕"] + - ["ù", "⠐⠥"] + - ["ú", "⠐⠥"] + - ["ÿ", "⠐⠽"] + +# Latin Extended-A + + - ["\u0100\u0101", "⠠⠐⠁⠐⠁"] + - ["\u0102\u0103", "⠠⠐⠁⠐⠁"] + - ["\u0104\u0105", "⠠⠐⠁⠐⠁"] + - ["\u0106\u0107", "⠠⠐⠉⠐⠉"] + - ["\u0108\u0109", "⠠⠐⠉⠐⠉"] + - ["\u010a\u010b", "⠠⠐⠉⠐⠉"] + - ["\u010c\u010d", "⠠⠐⠉⠐⠉"] + - ["\u010e\u010f", "⠠⠐⠙⠐⠙"] + - ["\u0110\u0111", "⠠⠐⠙⠐⠙"] + - ["\u0112\u0113", "⠠⠐⠑⠐⠑"] + - ["\u0114\u0115", "⠠⠐⠑⠐⠑"] + - ["\u0116\u0117", "⠠⠐⠑⠐⠑"] + - ["\u0118\u0119", "⠠⠐⠑⠐⠑"] + - ["\u011a\u011b", "⠠⠐⠑⠐⠑"] + - ["\u011c\u011d", "⠠⠐⠛⠐⠛"] + - ["\u011e\u011f", "⠠⠐⠛⠐⠛"] + - ["\u0120\u0121", "⠠⠐⠛⠐⠛"] + - ["\u0122\u0123", "⠠⠐⠛⠐⠛"] + - ["\u0124\u0125", "⠠⠐⠓⠐⠓"] + - ["\u0126\u0127", "⠠⠐⠓⠐⠓"] + - ["\u0128\u0129", "⠠⠐⠊⠐⠊"] + - ["\u012a\u012b", "⠠⠐⠊⠐⠊"] + - ["\u012c\u012d", "⠠⠐⠊⠐⠊"] + - ["\u012e\u012f", "⠠⠐⠊⠐⠊"] + - ["\u0130\u0131", "⠠⠐⠊⠐⠊"] + - ["\u0132\u0133", "⠠⠊⠚⠊⠚"] + - ["\u0136\u0137", "⠠⠐⠅⠐⠅"] + - ["\u0139\u013a", "⠠⠐⠇⠐⠇"] + - ["\u013b\u013c", "⠠⠐⠇⠐⠇"] + - ["\u013d\u013e", "⠠⠐⠇⠐⠇"] + - ["\u013f\u0140", "⠠⠐⠇⠐⠇"] + - ["\u0141\u0142", "⠠⠐⠇⠐⠇"] + - ["\u0143\u0144", "⠠⠐⠝⠐⠝"] + - ["\u0145\u0146", "⠠⠐⠝⠐⠝"] + - ["\u0147\u0148", "⠠⠐⠝⠐⠝"] + - ["\u0149", "⠈⠝"] + - ["\u014a\u014b", "⠠⠐⠝⠐⠝"] + - ["\u014c\u014d", "⠠⠐⠕⠐⠕"] + - ["\u014e\u014f", "⠠⠐⠕⠐⠕"] + - ["\u0150\u0151", "⠠⠐⠕⠐⠕"] + - ["\u0152\u0153", "⠠⠕⠑⠕⠑"] + - ["\u0154\u0155", "⠠⠐⠗⠐⠗"] + - ["\u0156\u0157", "⠠⠐⠗⠐⠗"] + - ["\u0158\u0159", "⠠⠐⠗⠐⠗"] + - ["\u015a\u015b", "⠠⠐⠎⠐⠎"] + - ["\u015c\u015d", "⠠⠐⠎⠐⠎"] + - ["\u015e\u015f", "⠠⠐⠎⠐⠎"] + - ["\u0160\u0161", "⠠⠐⠎⠐⠎"] + - ["\u0162\u0163", "⠠⠐⠞⠐⠞"] + - ["\u0164\u0165", "⠠⠐⠞⠐⠞"] + - ["\u0166\u0167", "⠠⠐⠞⠐⠞"] + - ["\u0168\u0169", "⠠⠐⠥⠐⠥"] + - ["\u016a\u016b", "⠠⠐⠥⠐⠥"] + - ["\u016c\u016d", "⠠⠐⠥⠐⠥"] + - ["\u016e\u016f", "⠠⠐⠥⠐⠥"] + - ["\u0170\u0171", "⠠⠐⠥⠐⠥"] + - ["\u0172\u0173", "⠠⠐⠥⠐⠥"] + - ["\u0174\u0175", "⠠⠐⠺⠐⠺"] + - ["\u0176\u0177", "⠠⠐⠽⠐⠽"] + - ["\u0178\u00ff", "⠠⠐⠽⠐⠽"] + - ["\u0179\u017a", "⠠⠐⠵⠐⠵"] + - ["\u017b\u017c", "⠠⠐⠵⠐⠵"] + - ["\u017d\u017e", "⠠⠐⠵⠐⠵"] + - ["\u017f", "⠐⠎"] + +# Latin Extended-B + + - ["\u02dc", "⠘⠆"] + +# Greek letters (with dot 5 as prefix) + + - ["\u0386\u03ac", "⠠⠐⠁⠐⠁"] + - ["\u0388\u03ad", "⠠⠐⠑⠐⠑"] + - ["\u0389\u03ae", "⠠⠐⠱⠐⠱"] + - ["\u038a\u03af", "⠠⠐⠊⠐⠊"] + - ["\u038c\u03cc", "⠠⠐⠕⠐⠕"] + - ["\u038e\u03cd", "⠠⠐⠥⠐⠥"] + - ["\u038f\u03ce", "⠠⠐⠺⠐⠺"] + - ["\u0391\u03b1", "⠠⠐⠁⠐⠁"] + - ["\u0394\u03b4", "⠠⠐⠙⠐⠙"] + - ["\u0395\u03b5", "⠠⠐⠑⠐⠑"] + - ["\u0396\u03b6", "⠠⠐⠵⠐⠵"] + - ["\u0399\u03b9", "⠠⠐⠊⠐⠊"] + - ["\u039c\u03bc", "⠠⠐⠍⠐⠍"] + - ["\u039d\u03bd", "⠠⠐⠝⠐⠝"] + - ["\u039f\u03bf", "⠠⠐⠕⠐⠕"] + - ["\u03a3\u03c3", "⠠⠐⠎⠐⠎"] + - ["\u03a4\u03c4", "⠠⠐⠞⠐⠞"] + - ["\u03a5\u03c5", "⠠⠐⠥⠐⠥"] + - ["\u03a8\u03c8", "⠠⠐⠽⠐⠽"] + +# Punctuation and bullits + + - ["\u2000", " "] + - ["\u2001", " "] + - ["\u2002", " "] + - ["\u2003", " "] + - ["\u2004", " "] + - ["\u2005", " "] + - ["\u2006", " "] + - ["\u2007", " "] + - ["\u2008", " "] + - ["\u2009", " "] + - ["\u200a", " "] + - ["\u2010", "⠤"] + - ["\u2011", "⠤"] + - ["\u2012", "⠤"] + - ["\u2013", "⠤⠤"] + - ["\u2014", "⠤⠤"] + - ["\u2018", "⠈"] + - ["\u2019", "⠈"] + - ["\u201a", "⠈"] + - ["\u201b", "⠈"] + - ["\u201c", "⠶"] + - ["\u201d", "⠶"] + - ["\u201e", "⠶"] + - ["\u201f", "⠶"] + - ["\u2023", "⠘⠄"] + - ["\u2026", "⠄⠄⠄"] + - ["\u202f", " "] + - ["\u2030", "⠚⠴⠴"] + - ["\u2039", "⠈"] + - ["\u203a", "⠈"] + - ["\u203c", "⠖⠖"] + - ["\u203d", "⠢⠖"] + - ["\u2043", "⠘⠄"] + - ["\u2047", "⠢⠢"] + - ["\u2048", "⠢⠖"] + - ["\u2049", "⠖⠢"] + - ["\u204c", "⠘⠄"] + - ["\u204d", "⠘⠄"] + - ["\u20ac", "⠘⠑"] + - ["\u2122", "⠘⠞"] + +# Arrows (only a few in 6 dots) + + - ["\u2192", "⠘⠗"] # back-translates as "registered". + +# Geometrical shapes + + - ["\u25e6", "⠘⠄"] + +# Extra digits + + - ["1\u00bc + 1\u00bd = 2\u00be", "⠼⠁⠼⠁⠌⠙ ⠘⠖ ⠼⠁⠼⠁⠌⠃ ⠘⠶ ⠼⠃⠼⠉⠌⠙"] + +# Dashes + + - ["\u2014 \u0096 \u0097 \u00ad", "⠤⠤ ⠤⠤ ⠤⠤ ⠤⠤"] + +# Quotes + + - ["\u201e \u0084 \u201c \u0093 \u201d \u0094 \u00ab \u00bb", "⠶ ⠶ ⠶ ⠶ ⠶ ⠶ ⠶ ⠶"] + +# Apostrophes + + - ["` \u201a \u0082 \u2039 \u008b \u2018 \u0091 \u2019 \u0092 \u203a \u009b \u00b4", "⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈"] + +# Characters and constructs which cannot be properly back-translated in grade 2 + +table: {language: da, grade: 1, dots: 6, version: 2022} +flags: {testmode: bothDirections} +tests: + + - ["²", "⠼⠬⠃"] + - ["³", "⠼⠬⠉"] + - ["¹", "⠼⠬⠁"] + - ["Ê", "⠠⠐⠑"] + - ["×", "⠘⠔"] + - ["ê", "⠐⠑"] + - ["÷", "⠘⠲"] + +# same tests but forward direction only +table: {language: da, grade: 2, dots: 6, version: 2022} +flags: {testmode: forward} +tests: + + - ["²", "⠼⠬⠃"] + - ["³", "⠼⠬⠉"] + - ["¹", "⠼⠬⠁"] + - ["Ê", "⠠⠐⠑"] + - ["×", "⠘⠔"] + - ["ê", "⠐⠑"] + - ["÷", "⠘⠲"] + +# Braille patterns which back-translate to something other than the original +# Typically, single character representations back-translating to strings with more than one character + +table: {language: da, grade: 1, dots: 6, version: 2022} +table: {language: da, grade: 2, dots: 6, version: 2022} +flags: {testmode: backward} +tests: + +# Characters + + - ["⠄⠄⠄", "..."] + - ["⠠⠕⠑", "Oe"] + - ["⠕⠑", "oe"] + - ["⠘⠖⠤", "±", {xfail: true}] + - ["⠼⠁⠌⠙", "1/4"] + - ["⠼⠁⠌⠃", "1/2"] + - ["⠼⠉⠌⠙", "3/4"] + +# Extra digits + + - ["⠼⠁⠼⠁⠌⠙ ⠘⠖ ⠼⠁⠼⠁⠌⠃ ⠘⠶ ⠼⠃⠼⠉⠌⠙", "1\u00bc + 1\u00bd = 2\u00be", {xfail: true}] + +# ------- +# Grade 1 +# ------- + +# Round trip tests +# Commented tests currently fail backwards but should be fixed. + +table: {language: da, grade: 1, dots: 6, version: 2022} +flags: {testmode: bothDirections} +tests: + +# Characters + + - ["&", "⠯"] + - ["(", "⠦"] + - [")", "⠴"] + - ["*", "⠔"] + - ["/", "⠌"] + - ["A", "⠠⠁"] + - ["B", "⠠⠃"] + - ["C", "⠠⠉"] + - ["D", "⠠⠙"] + - ["E", "⠠⠑"] + - ["F", "⠠⠋"] + - ["G", "⠠⠛"] + - ["H", "⠠⠓"] + - ["I", "⠠⠊"] + - ["J", "⠠⠚"] + - ["K", "⠠⠅"] + - ["L", "⠠⠇"] + - ["M", "⠠⠍"] + - ["N", "⠠⠝"] + - ["O", "⠠⠕"] + - ["P", "⠠⠏"] + - ["Q", "⠠⠟"] + - ["R", "⠠⠗"] + - ["S", "⠠⠎"] + - ["T", "⠠⠞"] + - ["U", "⠠⠥"] + - ["V", "⠠⠧"] + - ["W", "⠠⠺"] + - ["X", "⠠⠭"] + - ["Y", "⠠⠽"] + - ["Z", "⠠⠵"] + - ["\u005c\u005c", "⠘⠌"] + - ["a", "⠁"] + - ["b", "⠃"] + - ["c", "⠉"] + - ["d", "⠙"] + - ["e", "⠑"] + - ["f", "⠋"] + - ["g", "⠛"] + - ["h", "⠓"] + - ["i", "⠊"] + - ["j", "⠚"] + - ["k", "⠅"] + - ["l", "⠇"] + - ["m", "⠍"] + - ["n", "⠝"] + - ["o", "⠕"] + - ["p", "⠏"] + - ["q", "⠟"] + - ["r", "⠗"] + - ["s", "⠎"] + - ["t", "⠞"] + - ["u", "⠥"] + - ["v", "⠧"] + - ["w", "⠺"] + - ["x", "⠭"] + - ["y", "⠽"] + - ["z", "⠵"] + - ["|", "⠘⠧"] + - ["ž", "⠐⠵"] + - ["¢", "⠘⠒"] + - ["£", "⠘⠇"] + - ["¥", "⠘⠽"] + - ["§", "⠬"] + - ["À", "⠠⠷"] + - ["Å", "⠠⠡"] + - ["Æ", "⠠⠜"] + - ["È", "⠠⠮"] + - ["É", "⠠⠿"] + - ["Ø", "⠠⠪"] + - ["Ü", "⠠⠳"] + - ["à", "⠷"] + - ["å", "⠡"] + - ["ä", "⠐⠜"] + - ["æ", "⠜"] + - ["è", "⠮"] + - ["é", "⠿"] + - ["ø", "⠪"] + - ["ü", "⠳"] + +# Pangram + + - - "Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Walther spillede på xylofon." + - "⠠⠟⠥⠊⠵⠙⠑⠇⠞⠁⠛⠑⠗⠝⠑ ⠎⠏⠊⠎⠞⠑ ⠚⠕⠗⠙⠃⠜⠗ ⠍⠑⠙ ⠋⠇⠪⠙⠑⠂ ⠍⠑⠝⠎ ⠉⠊⠗⠅⠥⠎⠅⠇⠕⠧⠝⠑⠝ ⠠⠺⠁⠇⠞⠓⠑⠗ ⠎⠏⠊⠇⠇⠑⠙⠑ ⠏⠡ ⠭⠽⠇⠕⠋⠕⠝⠄" + +# Numbers and punctuation + + - ["J) j) %) ') \u2030)", "⠠⠚⠰⠴ ⠚⠰⠴ ⠚⠴⠰⠴ ⠈⠴ ⠚⠴⠴⠰⠴"] + - ["\"quotes\"", "⠶⠟⠥⠕⠞⠑⠎⠶"] + - [":-) :-(", "⠒⠤⠴ ⠒⠤⠦"] + - [";-) ;-(", "⠆⠤⠴ ⠆⠤⠦"] + - [" a-", " ⠁⠤"] + - [" -a-", " ⠤⠁⠤"] + - ["(parentheses)", "⠦⠏⠁⠗⠑⠝⠞⠓⠑⠎⠑⠎⠴"] + +# Multi-pass tests + +# - ["~x |z", "⠘⠰⠭ ⠘⠸⠵"] +# - ["~X |Z", "⠘⠰⠠⠭ ⠘⠸⠠⠵"] + - ["5É", "⠼⠑⠠⠿"] + +# Characters and constructs which cannot be properly back-translated + +flags: {testmode: forward} +tests: + +# Single characters + + - ["¡", "⠲"] + - ["­", "⠤⠤"] + - ["¯", "⠢"] + +# Apostrophes + + - ["\u0089) \u201a) \u0082) \u2039) \u009b) \u2018) \u0091) \u2019) \u0092) \u203a) \u009b)", "⠚⠴⠴⠰⠴ ⠈⠴ ⠈⠴ ⠈⠴ ⠈⠴ ⠈⠴ ⠈⠴ ⠈⠴ ⠈⠴ ⠈⠴ ⠈⠴"] + +# Emphasis + + - # Bold line + - En linje med fed. + - ⠨⠠⠑⠝ ⠇⠊⠝⠚⠑ ⠍⠑⠙ ⠋⠑⠙⠄⠨ + - typeform: + bold: '+++++++++++++++++' + - # Bold word + - Et ord med fed. + - ⠠⠑⠞ ⠨⠕⠗⠙⠨ ⠍⠑⠙ ⠋⠑⠙⠄ + - typeform: + bold: ' +++ ' + - # Bold letter + - Et bogstav med fed. + - ⠠⠑⠞ ⠃⠕⠛⠨⠎⠨⠞⠁⠧ ⠍⠑⠙ ⠋⠑⠙⠄ + - typeform: + bold: ' + ' + + - # Italic line + - En linje med kursiv. + - ⠨⠠⠑⠝ ⠇⠊⠝⠚⠑ ⠍⠑⠙ ⠅⠥⠗⠎⠊⠧⠄⠨ + - typeform: + italic: '++++++++++++++++++++' + - # Italic word + - Et ord med kursiv. + - ⠠⠑⠞ ⠨⠕⠗⠙⠨ ⠍⠑⠙ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' +++ ' + - # Italic letter + - Et bogstav med kursiv. + - ⠠⠑⠞ ⠃⠕⠛⠨⠎⠨⠞⠁⠧ ⠍⠑⠙ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' + ' + + - # Underlined line + - En linje med understreget. + - ⠨⠠⠑⠝ ⠇⠊⠝⠚⠑ ⠍⠑⠙ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄⠨ + - typeform: + underline: '++++++++++++++++++++++++++' + - # Underlined word + - Et ord med understreget. + - ⠠⠑⠞ ⠨⠕⠗⠙⠨ ⠍⠑⠙ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄ + - typeform: + underline: ' +++ ' + - # Underlined letter + - Et bogstav med understreget. + - ⠠⠑⠞ ⠃⠕⠛⠨⠎⠨⠞⠁⠧ ⠍⠑⠙ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄ + - typeform: + underline: ' + ' + + - # Bold, italic and underlined line + - En linje med alt. + - ⠨⠠⠑⠝ ⠇⠊⠝⠚⠑ ⠍⠑⠙ ⠁⠇⠞⠄⠨ + - typeform: + bold: '+++++++++++++++++' + italic: '+++++++++++++++++' + underline: '+++++++++++++++++' + - # Bold, italic and underlined word + - Et ord med alt. + - ⠠⠑⠞ ⠨⠕⠗⠙⠨ ⠍⠑⠙ ⠁⠇⠞⠄ + - typeform: + bold: ' +++ ' + italic: ' +++ ' + underline: ' +++ ' + - # Bold, italic and underlined letter + - Et bogstav med alt. + - ⠠⠑⠞ ⠃⠕⠛⠨⠎⠨⠞⠁⠧ ⠍⠑⠙ ⠁⠇⠞⠄ + - typeform: + bold: ' + ' + italic: ' + ' + underline: ' + ' + +# ------- +# Grade 2 +# ------- + +# Round trip tests +# Commented tests currently fail backwards but should be fixed. + +table: {language: da, grade: 2, dots: 6, version: 2022} +flags: {testmode: bothDirections} +tests: + +# Characters + + - ["&", "⠰⠯"] + - ["(", "⠰⠦"] + - [")", "⠰⠴"] + - ["*", "⠰⠔"] + - ["/", "⠰⠌", {xfail: {forward: true}}] + - ["A", "⠰⠠⠁"] + - ["B", "⠰⠠⠃"] + - ["C", "⠰⠠⠉"] + - ["D", "⠰⠠⠙"] + - ["E", "⠰⠠⠑"] + - ["F", "⠰⠠⠋"] + - ["G", "⠰⠠⠛"] + - ["H", "⠰⠠⠓"] + - ["I", "⠠⠊"] + - ["J", "⠰⠠⠚"] + - ["K", "⠰⠠⠅"] + - ["L", "⠰⠠⠇"] + - ["M", "⠰⠠⠍"] + - ["N", "⠰⠠⠝"] + - ["O", "⠰⠠⠕"] + - ["P", "⠰⠠⠏"] + - ["Q", "⠰⠠⠟"] + - ["R", "⠰⠠⠗"] + - ["S", "⠰⠠⠎"] + - ["T", "⠰⠠⠞"] + - ["U", "⠰⠠⠥"] + - ["V", "⠰⠠⠧"] + - ["W", "⠰⠠⠺"] + - ["X", "⠰⠠⠭"] + - ["Y", "⠰⠠⠽"] + - ["Z", "⠰⠠⠵"] + - ["\u005c\u005c", "⠘⠡"] + - ["a", "⠰⠁"] + - ["b", "⠰⠃"] + - ["c", "⠰⠉"] + - ["d", "⠰⠙"] + - ["e", "⠰⠑"] + - ["f", "⠰⠋"] + - ["g", "⠰⠛"] + - ["h", "⠰⠓"] + - ["i", "⠊"] + - ["j", "⠰⠚"] + - ["k", "⠰⠅"] + - ["l", "⠰⠇"] + - ["m", "⠰⠍"] + - ["n", "⠰⠝"] + - ["o", "⠰⠕"] + - ["p", "⠰⠏"] + - ["q", "⠰⠟"] + - ["r", "⠰⠗"] + - ["s", "⠰⠎"] + - ["t", "⠰⠞"] + - ["u", "⠰⠥"] + - ["v", "⠰⠧"] + - ["w", "⠰⠺"] + - ["x", "⠰⠭"] + - ["y", "⠰⠽"] + - ["z", "⠰⠵"] + - ["|", "⠘⠸"] + - ["ž", "⠐⠵"] + - ["¡", "⠰⠲"] + - ["¢", "⠘⠒"] + - ["£", "⠘⠇"] + - ["¥", "⠘⠽"] + - ["À", "⠰⠠⠷"] + - ["Å", "⠰⠠⠡"] + - ["Æ", "⠰⠠⠜"] + - ["È", "⠰⠠⠮"] + - ["É", "⠰⠠⠿"] + - ["Ø", "⠰⠠⠪"] + - ["Ü", "⠰⠠⠳"] + - ["à", "⠰⠷"] + - ["ä", "⠐⠜"] + - ["å", "⠰⠡"] + - ["æ", "⠰⠜"] + - ["è", "⠰⠮"] + - ["é", "⠰⠿"] + - ["ø", "⠰⠪"] + - ["ü", "⠰⠳"] + +# Pangram + + - - "Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Walther spillede på xylofon." + - "⠰⠠⠟⠥⠊⠰⠵⠹⠇⠞⠁⠛⠱⠫ ⠎⠏⠊⠵⠑ ⠚⠭⠙⠃⠜⠗ ⠍ ⠋⠇⠪⠹⠂ ⠍⠣⠎ ⠉⠊⠗⠅⠥⠎⠅⠇⠕⠧⠝⠣ ⠰⠠⠺⠁⠇⠞⠓⠱ ⠎⠏⠊⠇⠇⠑⠹ ⠏ ⠰⠭⠽⠇⠕⠋⠕⠝⠄" + +# Percent and permille + + - ["1%", "⠼⠁ ⠚⠴"] + +# Numbers and punctuation + +# - ["2\u00d72", "⠼⠃⠘⠄⠼⠃"] + - ["\"quotes\"", "⠶⠰⠟⠥⠕⠳⠎⠶"] + - [":-) :-(", "⠒⠤⠰⠴ ⠒⠤⠰⠦"] + - [";-) ;-(", "⠆⠤⠰⠴ ⠆⠤⠰⠦"] + - [" a-", " ⠰⠁⠤"] + - [" -a-", " ⠤⠰⠁⠤"] + - ["(parentheses)", "⠦⠏⠁⠗⠣⠞⠓⠑⠎⠑⠎⠴"] + +# Exclamation + + - ["\u00a1Que lastima!", "⠰⠲⠰⠠⠟⠥⠑ ⠇⠁⠵⠊⠍⠁⠖"] + +# URLs emails and file names + + - ["$at", "⠘⠙⠁⠞"] + - ["\u005c\u005cat\u005c\u005cbliver", "⠘⠡⠁⠞⠘⠡⠃⠇⠊⠧⠑⠗"] + - ["at@bliver.og", "⠁⠞⠘⠁⠃⠇⠊⠧⠑⠗⠄⠕⠛"] + - ["http://at.bliver.og", "⠓⠞⠞⠏⠒⠌⠌⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠕⠛"] + - ["www.at.bliver.og", "⠺⠺⠺⠄⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠕⠛"] + - ["www.at.bliver.com", "⠺⠺⠺⠄⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠉⠕⠍"] + - ["www.a.b.c", "⠺⠺⠺⠄⠰⠁⠄⠰⠃⠄⠰⠉"] + +# Word contractions + + - ["At", "⠠⠁"] + - ["at", "⠁"] + - ["Aldrig", "⠠⠁⠔"] + - ["aldrig", "⠁⠔"] + - ["aig", "⠁⠊⠛"] + - ["Alle", "⠠⠁⠑"] + - ["alle", "⠁⠑"] + - ["ae", "⠰⠁⠑"] + - ["Allerede", "⠠⠁⠇⠗"] + - ["allerede", "⠁⠇⠗"] + - ["alr", "⠰⠁⠇⠗"] + - ["Alligevel", "⠠⠁⠇⠧"] + - ["alligevel", "⠁⠇⠧"] + - ["alv", "⠰⠁⠇⠧"] + - ["Altid", "⠠⠁⠞⠙"] + - ["altid", "⠁⠞⠙"] + - ["atd", "⠰⠁⠞⠙"] + - ["Altså", "⠠⠁⠡"] + - ["altså", "⠁⠡"] + - ["aå", "⠰⠁⠡"] + - ["Bliver", "⠠⠃"] + - ["bliver", "⠃"] + - ["Og", "⠠⠉"] + - ["og", "⠉"] + - ["Deres", "⠠⠲"] + - ["deres", "⠲"] + - ["Du", "⠠⠙"] + - ["du", "⠙"] + - ["Eller", "⠠⠑"] + - ["eller", "⠑"] + - ["For", "⠠⠋"] + - ["for", "⠋"] + - ["Gør", "⠠⠛"] + - ["gør", "⠛"] + - ["Har", "⠠⠓"] + - ["har", "⠓"] + - ["Jeg", "⠠⠚"] + - ["jeg", "⠚"] + - ["Kan", "⠠⠅"] + - ["kan", "⠅"] + - ["Lige", "⠠⠇"] + - ["lige", "⠇"] + - ["Med", "⠠⠍"] + - ["med", "⠍"] + - ["Når", "⠠⠝"] + - ["når", "⠝"] + - ["Op", "⠠⠕"] + - ["op", "⠕"] + - ["På", "⠠⠏"] + - ["på", "⠏"] + - ["Under", "⠠⠟"] + - ["under", "⠟"] + - ["Rigtig", "⠠⠗"] + - ["rigtig", "⠗"] + - ["Som", "⠠⠎"] + - ["som", "⠎"] + - ["Til", "⠠⠞"] + - ["til", "⠞"] + - ["Hun", "⠠⠥"] + - ["hun", "⠥"] + - ["Ved", "⠠⠧"] + - ["ved", "⠧"] + - ["Hvad", "⠠⠺"] + - ["hvad", "⠺"] + - ["Over", "⠠⠭"] + - ["over", "⠭"] + - ["Han", "⠠⠽"] + - ["han", "⠽"] + - ["Efter", "⠠⠵"] + - ["efter", "⠵"] + - ["Være", "⠠⠜"] + - ["være", "⠜"] + - ["Før", "⠠⠪"] + - ["før", "⠪"] + - ["Så", "⠠⠡"] + - ["så", "⠡"] + - ["Den", "⠠⠯"] + - ["den", "⠯"] + - ["Der", "⠠⠾"] + - ["der", "⠾"] + - ["Det", "⠠⠮"] + - ["det", "⠮"] + - ["De", "⠠⠹"] + - ["de", "⠹"] + - ["En", "⠠⠣"] + - ["en", "⠣"] + - ["Er", "⠠⠱"] + - ["er", "⠱"] + - ["Et", "⠠⠬"] + - ["et", "⠬"] + - ["Gennem", "⠠⠻"] + - ["gennem", "⠻"] + - ["Hvor", "⠠⠌"] + - ["hvor", "⠌"] + - ["Men", "⠠⠩"] + - ["men", "⠩"] + - ["Ned", "⠠⠫"] + - ["ned", "⠫"] + - ["Ret", "⠠⠷"] + - ["ret", "⠷"] + - ["Skal", "⠠⠿"] + - ["skal", "⠿"] + - ["Te", "⠠⠳"] + - ["te", "⠳"] + - ["Var", "⠠⠼"] + - ["var", "⠼"] + - ["Ve", "⠠⠧⠑"] + - ["ve", "⠧⠑"] + +# Partword/nocross + + - ["Denne", "⠠⠯⠫"] + - ["denne", "⠯⠫"] + - ["Mændene", "⠠⠍⠜⠝⠹⠫"] + - ["mændene", "⠍⠜⠝⠹⠫"] + - ["Derhos", "⠠⠾⠓⠕⠎"] + - ["derhos", "⠾⠓⠕⠎"] + - ["Hunderace", "⠠⠓⠥⠝⠹⠗⠁⠉⠑"] + - ["hunderace", "⠓⠥⠝⠹⠗⠁⠉⠑"] + - ["Dette", "⠠⠮⠳"] + - ["dette", "⠮⠳"] + - ["Detalje", "⠠⠹⠞⠁⠇⠚⠑"] + - ["detalje", "⠹⠞⠁⠇⠚⠑"] + +# Nocross multiple cells + + - ["Endda", "⠠⠑⠟⠙⠁"] + - ["endda", "⠑⠟⠙⠁"] + - ["Morgendag", "⠠⠍⠭⠛⠣⠙⠁⠛"] + - ["morgendag", "⠍⠭⠛⠣⠙⠁⠛"] + - ["Gendanne", "⠠⠛⠣⠙⠁⠝⠫"] + - ["gendanne", "⠛⠣⠙⠁⠝⠫"] + - ["Generelt", "⠠⠻⠫⠷⠇⠞"] + - ["generelt", "⠻⠫⠷⠇⠞"] + + - ["Fra!", "⠠⠋⠗⠁⠖"] + - ["fra!", "⠋⠗⠁⠖"] + - ["!Fra", "⠰⠖⠠⠖"] + - ["!fra", "⠖⠋⠗⠁"] + - ["'Af", "⠈⠠⠴"] + - ["'af", "⠈⠁⠋"] + +# No single cell contractions before or after dashes + + - ["at-bliver", "⠁⠞⠤⠃"] + - ["d-d-du", "⠰⠙⠤⠰⠙⠤⠙⠥"] + +# Combinations with slashes and other punctuation signs + + - ["at!", "⠁⠖"] + - ["bliver!", "⠃⠖"] + - ["og!", "⠉⠖"] + - ["Han/hun", "⠠⠽⠌⠥"] + - ["han/hun", "⠽⠌⠥"] + - ["Over/under", "⠠⠭⠌⠟"] + - ["over/under", "⠭⠌⠟"] + - ["Til/fra", "⠠⠞⠌⠖"] + - ["til/fra", "⠞⠌⠖"] + +# Combinations which require letsign + + - ["1st", "⠼⠁⠰⠎⠞"] + - ["2nd", "⠼⠃⠰⠝⠙"] + - ["1A", "⠼⠁⠰⠠⠁"] + - ["1a", "⠼⠁⠰⠁"] + - ["2B", "⠼⠃⠰⠠⠃"] + - ["2b", "⠼⠃⠰⠃"] + +# Multi-pass te⠄sts + + - ["~x |z", "⠘⠆⠰⠭ ⠘⠸⠰⠵"] + - ["~X |Z", "⠘⠆⠰⠠⠭ ⠘⠸⠰⠠⠵"] + - ["5É", "⠼⠑⠰⠠⠿"] + +# Examples from "Den danske punktskrift 2021" + +# Section 5.3.1 Syllables + + - [Anders, ⠠⠁⠝⠾⠎] + - [banegården, ⠃⠁⠫⠛⠡⠗⠯] + - [banegårdene, ⠃⠁⠫⠛⠡⠗⠹⠫] + - [penge, ⠏⠣⠻] + - [ringe, ⠗⠊⠝⠻] + - [skriveregel, ⠿⠗⠊⠼⠷⠻⠇] + - [Roskilde, ⠠⠗⠕⠿⠊⠇⠹, {xfail: {forward: true}}] + - [hviske, ⠺⠊⠿⠑] + - [taske, ⠞⠁⠿⠑] + - [taste, ⠞⠁⠵⠑] + - [vristen, ⠧⠗⠊⠵⠣] + +# Section 5.3.3 Use last contraction + + - [bager, ⠃⠁⠛⠱] + - [haner, ⠓⠁⠝⠱] + +# Section 5.3.4 "St" and "hv" + + - [stedet, ⠵⠑⠮] + - [stene, ⠵⠑⠫] + - [hvede, ⠺⠑⠹] + +# Section 5.3.5 "Ve" as begword + + - [vej, ⠧⠑⠚] + - [veje, ⠧⠑⠚⠑] + - [vejene, ⠼⠚⠑⠫] + - [veg, ⠧⠑⠛] + - [veda, ⠧⠑⠙⠁] + +# Section 5.3.6 Multiple cell partword + + - [danskvand, ⠙⠿⠧⠁⠟] + - [danskhed, ⠙⠿⠓⠑⠙] + - [gangbro, ⠛⠛⠃⠗⠕] + - [gangbar, ⠛⠛⠃⠁⠗] + - [kvindelig, ⠅⠧⠹⠇⠔] + - [kvindekamp, ⠅⠧⠹⠅⠁⠍⠏] + - [menneskelig, ⠩⠿⠑⠇⠔] + - [problemformulering, ⠏⠃⠋⠭⠍⠥⠇⠑⠗⠊⠝⠛] + - [virkelighedsflugt, ⠧⠗⠅⠓⠑⠙⠎⠋⠇⠥⠛⠞] + +# Section 6.1 Period + + - ["St. St. Blicher", "⠰⠠⠎⠞⠄ ⠰⠠⠎⠞⠄ ⠠⠃⠇⠊⠉⠓⠱"] + - ["Skt. Hans", "⠠⠿⠞⠄ ⠠⠽⠎"] + +# Section 6.4 Hyphens + + - ["ret- og vrang-strikning", "⠗⠬⠤ ⠉ ⠧⠗⠁⠝⠛⠤⠵⠗⠊⠅⠝⠊⠝⠛"] + - [over-komme, ⠕⠧⠱⠤⠅⠕⠍⠩, {xfail: {forward: true}}] + +# Section 7.1 Capsletter + + - - Han åbnede døren. + - ⠠⠽ ⠡⠃⠫⠹ ⠙⠪⠗⠣⠄ + - - Jeg hilste på Søren og hans datter Xenia. + - ⠠⠚ ⠓⠊⠇⠵⠑ ⠏ ⠠⠎⠪⠗⠣ ⠉ ⠽⠎ ⠙⠁⠞⠞⠱ ⠰⠠⠭⠑⠝⠊⠁⠄ + - - Jeg læser gerne bøger af H. C. Andersen og St. St. Blicher. + - ⠠⠚ ⠇⠜⠎⠱ ⠛⠱⠫ ⠃⠪⠛⠱ ⠴ ⠰⠠⠓⠄ ⠰⠠⠉⠄ ⠠⠁⠝⠾⠎⠣ ⠉ ⠰⠠⠎⠞⠄ ⠰⠠⠎⠞⠄ ⠠⠃⠇⠊⠉⠓⠱⠄ + - - Læs afsnit A underafsnit a + - ⠠⠇⠜⠎ ⠁⠋⠎⠝⠊⠞ ⠰⠠⠁ ⠥⠝⠾⠁⠋⠎⠝⠊⠞ ⠰⠁ + +# Section 7.2 Capsword + + - [DBS, ⠸⠙⠃⠎] + - ["DEN GAMLE MAND OG HAVET", "⠸⠯ ⠸⠛⠁⠍⠇⠑ ⠸⠍⠁⠟ ⠸⠉ ⠸⠓⠁⠧⠬"] + - ["Din kode er DSB345Qz", "⠠⠙⠝ ⠅⠕⠹ ⠱ ⠸⠙⠎⠃⠰⠼⠉⠙⠑⠰⠠⠟⠰⠵"] + - ["NATOs", "⠸⠝⠁⠞⠕⠰⠎"] + - ["DBS's forretningsudvalg.", "⠸⠙⠃⠎⠈⠰⠎ ⠋⠭⠗⠬⠝⠊⠝⠛⠎⠥⠙⠧⠁⠇⠛⠄"] + - ["TiBS", "⠠⠞⠊⠸⠃⠎"] + +# Section 7.3 Letsign + + - - Skjern Å blev rettet ud. + - ⠠⠿⠚⠱⠝ ⠰⠠⠡ ⠃⠧ ⠗⠬⠞⠬ ⠥⠙⠄ + - [jazz, ⠚⠁⠰⠵⠰⠵] + - - han løb fx 2 km. + - ⠽ ⠇⠪⠃ ⠋⠰⠭ ⠼⠃ ⠰⠅⠍⠄ + - - han fik 5 kr. (1%). Sidste år var det mindre (1‰). + - ⠽ ⠋⠅ ⠼⠑ ⠅⠗⠄ ⠦⠼⠁ ⠚⠴⠰⠴⠄ ⠠⠎⠵⠑ ⠡⠗ ⠼ ⠮ ⠍⠊⠝⠙⠷ ⠦⠼⠁ ⠚⠴⠴⠰⠴⠄ + +# Section 7.4 Emphasis + +flags: {testmode: forward} +tests: + - - Du spørger, hvad jeg laver. Hvad laver du? + - ⠠⠙ ⠎⠏⠪⠗⠛⠱⠂ ⠺ ⠨⠚⠨ ⠇⠁⠧⠱⠄ ⠠⠺ ⠇⠁⠧⠱ ⠨⠙⠨⠢ + - typeform: + italic: " +++ ++ " + - - Hvad du gør nu, lige nu i dette øjeblik, får betydning for dig resten af dit liv. + - ⠠⠺ ⠙ ⠛ ⠝⠥⠂ ⠨⠇ ⠝⠥ ⠊ ⠮⠳ ⠪⠚⠃⠂⠨ ⠋⠡⠗ ⠃⠑⠞⠽⠙⠝⠊⠝⠛ ⠋ ⠨⠙⠔⠨ ⠷⠵⠣ ⠴ ⠙⠞ ⠇⠊⠧⠄ + - typeform: + italic: " ++++++++++++++++++++++++ +++ " + - - Den gamle stavemåde var Revshaleøen. + - ⠠⠯ ⠛⠁⠍⠇⠑ ⠵⠁⠼⠍⠡⠹ ⠼ ⠠⠷⠨⠧⠨⠎⠓⠁⠇⠑⠪⠣⠄ + - typeform: + italic: " + " + +# Section 7.5.2 Misc letters with accents + +flags: {testmode: bothDirections} +tests: + - ["Antonín Dvořák", "⠠⠁⠝⠞⠕⠝⠐⠊⠝ ⠠⠙⠧⠕⠐⠗⠐⠁⠅"] + - [Lübeck, ⠠⠇⠰⠳⠃⠑⠉⠅] + - [Göteborg, ⠠⠛⠐⠪⠳⠃⠭⠛] + - [Malmö, ⠠⠍⠁⠇⠍⠐⠪] + - [Gävle, ⠠⠛⠐⠜⠧⠇⠑] + +# Characters and constructs which cannot be properly back-translated + +flags: {testmode: forward} +tests: + +# Single characters + + - ["§", "⠬"] + +# Parentheses and misc + + - ["J) j) %) ') \u2030) \u0089) \u201a) \u0082) \u2039) \u009b) \u2018) \u0091) \u2019) \u0092) \u203a) \u009b)", "⠰⠠⠚⠰⠴ ⠰⠚⠰⠴ ⠚⠴⠰⠴ ⠈⠰⠴ ⠚⠴⠴⠰⠴ ⠚⠴⠴⠰⠴ ⠈⠰⠴ ⠈⠰⠴ ⠈⠰⠴ ⠈⠰⠴ ⠈⠰⠴ ⠈⠰⠴ ⠈⠰⠴ ⠈⠰⠴ ⠈⠰⠴ ⠈⠰⠴"] + +# Emphasis +# Single letter emphasis currently fails due to contraction across emphasis. + + - # Bold line + - En linje med fed. + - ⠨⠠⠣ ⠇⠊⠝⠚⠑ ⠍ ⠋⠑⠙⠄⠨ + - typeform: + bold: '+++++++++++++++++' + - # Bold word + - Et ord med fed. + - ⠠⠬ ⠨⠭⠙⠨ ⠍ ⠋⠑⠙⠄ + - typeform: + bold: ' +++ ' + - # Bold letter + - Et bogstav med fed. + - ⠠⠬ ⠃⠕⠛⠨⠎⠨⠞⠁⠧ ⠍ ⠋⠑⠙⠄ + - {typeform: {bold: ' + '}, xfail: true} + + - # Italic line + - En linje med kursiv. + - ⠨⠠⠣ ⠇⠊⠝⠚⠑ ⠍ ⠅⠥⠗⠎⠊⠧⠄⠨ + - typeform: + italic: '++++++++++++++++++++' + - # Italic word + - Et ord med kursiv. + - ⠠⠬ ⠨⠭⠙⠨ ⠍ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' +++ ' + - # Italic letter + - Et bogstav med kursiv. + - ⠠⠬ ⠃⠕⠛⠨⠎⠨⠞⠁⠧ ⠍ ⠅⠥⠗⠎⠊⠧⠄ + - {typeform: {italic: ' + '}, xfail: true} + + - # Underlined line + - En linje med understreget. + - ⠨⠠⠣ ⠇⠊⠝⠚⠑ ⠍ ⠥⠝⠾⠵⠷⠛⠬⠄⠨ + - typeform: + underline: '++++++++++++++++++++++++++' + - # Underlined word + - Et ord med understreget. + - ⠠⠬ ⠨⠭⠙⠨ ⠍ ⠥⠝⠾⠵⠷⠛⠬⠄ + - typeform: + underline: ' +++ ' + - # Underlined letter + - Et bogstav med understreget. + - ⠠⠬ ⠃⠕⠛⠨⠎⠨⠞⠁⠧ ⠍ ⠥⠝⠾⠵⠷⠛⠬⠄ + - {typeform: {underline: ' + '}, xfail: true} + + - # Bold, italic and underlined line + - En linje med alt. + - ⠨⠠⠣ ⠇⠊⠝⠚⠑ ⠍ ⠁⠇⠞⠄⠨ + - typeform: + bold: '+++++++++++++++++' + italic: '+++++++++++++++++' + underline: '+++++++++++++++++' + - # Bold, italic and underlined word + - Et ord med alt. + - ⠠⠬ ⠨⠭⠙⠨ ⠍ ⠁⠇⠞⠄ + - typeform: + bold: ' +++ ' + italic: ' +++ ' + underline: ' +++ ' + - # Bold, italic and underlined letter + - Et bogstav med alt. + - ⠠⠬ ⠃⠕⠛⠨⠎⠨⠞⠁⠧ ⠍ ⠁⠇⠞⠄ + - {typeform: {bold: ' + ', italic: ' + ', underline: ' + '}, xfail: true} + diff --git a/LibLouis.NET.Test/braille-specs/da-dk-8dot.yaml b/LibLouis.NET.Test/braille-specs/da-dk-8dot.yaml new file mode 100644 index 0000000..4c0e058 --- /dev/null +++ b/LibLouis.NET.Test/braille-specs/da-dk-8dot.yaml @@ -0,0 +1,1003 @@ +display: unicode-without-blank.dis + +# ---------------- +# Grade 0, 1 and 2 +# ---------------- + +table: {language: da, grade: 0, dots: 8, version: 2022, __assert-match: da-dk-g08.ctb} +table: {language: da, grade: 1, dots: 8, version: 2022, __assert-match: da-dk-g18.ctb} +table: {language: da, grade: 2, dots: 8, version: 2022, __assert-match: da-dk-g28.ctb} +flags: {testmode: bothDirections} +tests: + +# Characters from 0x21 to 0xff + + - [' ', ' '] + - ['"', '⠶'] + - ['$', '⣙'] + - ['%', '⣠'] + - ['&', '⢯'] + - ['''', '⠈'] + - ['(', '⢦'] + - [')', '⢴'] + - ['+', '⢖'] + - [',', '⠂'] + - ['-', '⠤'] + - ['.', '⠄'] + - ['/', '⢌'] + - ['0', '⢚'] + - ['1', '⢁'] + - ['2', '⢃'] + - ['3', '⢉'] + - ['4', '⢙'] + - ['5', '⢑'] + - ['6', '⢋'] + - ['7', '⢛'] + - ['8', '⢓'] + - ['9', '⢊'] + - [':', '⠒'] + - [';', '⠆'] + - ['<', '⢔'] + - ['=', '⢶'] + - ['>', '⡢'] + - ['?', '⠢'] + - ['@', '⣁'] + - ['[', '⣦'] + - [']', '⣴'] + - ['^', '⢏'] + - ['_', '⣤'] + - ['`', '⠐'] + - ['{', '⣧'] + - ['|', '⢸'] + - ['}', '⣼'] + - ['~', '⣆'] + - ['€', '⣑'] + - ['‚', '⡘'] + - ['ƒ', '⢐'] + - ['„', '⡨'] + - ['†', '⠘'] + - ['‡', '⠸'] + - ['ˆ', '⣰'] + - ['‰', '⣺'] + - ['‹', '⣖'] + - ['‘', '⡈'] + - ['’', '⢈'] + - ['“', '⡆'] + - ['”', '⢰'] + - ['•', '⡄'] + - ['–', '⢤'] + - ['—', '⡤'] + - ['˜', '⣎'] + - ['™', '⣞'] + - ['›', '⡸'] + - ['¢', '⣒'] + - ['£', '⣇'] + - ['¥', '⣽'] + - ['¦', '⣌'] + - ['§', '⣐'] + - ['¨', '⠰'] + - ['©', '⣉'] + - ['«', '⡐'] + - ['®', '⣗'] + - ['¯', '⡶'] + - ['±', '⣋'] + - ['²', '⢆'] + - ['³', '⢒'] + - ['´', '⢘'] + - ['¶', '⢿'] + - ['·', '⢄'] + - ['¸', '⣨'] + - ['¹', '⢂'] + - ['»', '⡰'] + - ['¼', '⢥'] + - ['½', '⢨'] + - ['¾', '⢭'] + - ['÷', '⢲'] + +# ------------- +# Grade 0 and 1 +# ------------- + +table: {language: da, grade: 0, dots: 8, version: 2022} +table: {language: da, grade: 1, dots: 8, version: 2022} +flags: {testmode: bothDirections} +tests: + +# Characters from 0x21 to 0xff + + - ['*', '⠔'] + - ['A', '⡁'] + - ['B', '⡃'] + - ['C', '⡉'] + - ['D', '⡙'] + - ['E', '⡑'] + - ['F', '⡋'] + - ['G', '⡛'] + - ['H', '⡓'] + - ['I', '⡊'] + - ['J', '⡚'] + - ['K', '⡅'] + - ['L', '⡇'] + - ['M', '⡍'] + - ['N', '⡝'] + - ['O', '⡕'] + - ['P', '⡏'] + - ['Q', '⡟'] + - ['R', '⡗'] + - ['S', '⡎'] + - ['T', '⡞'] + - ['U', '⡥'] + - ['V', '⡧'] + - ['W', '⡺'] + - ['X', '⡭'] + - ['Y', '⡽'] + - ['Z', '⡵'] + - ['\\', '⡌'] + - ['a', '⠁'] + - ['b', '⠃'] + - ['c', '⠉'] + - ['d', '⠙'] + - ['e', '⠑'] + - ['f', '⠋'] + - ['g', '⠛'] + - ['h', '⠓'] + - ['i', '⠊'] + - ['j', '⠚'] + - ['k', '⠅'] + - ['l', '⠇'] + - ['m', '⠍'] + - ['n', '⠝'] + - ['o', '⠕'] + - ['p', '⠏'] + - ['q', '⠟'] + - ['r', '⠗'] + - ['s', '⠎'] + - ['t', '⠞'] + - ['u', '⠥'] + - ['v', '⠧'] + - ['w', '⠺'] + - ['x', '⠭'] + - ['y', '⠽'] + - ['z', '⠵'] + - ['…', '⠠'] + - ['Œ', '⣕'] + - ['Ž', '⡬'] + - ['œ', '⢕'] + - ['ž', '⠬'] + - ['Ÿ', '⣾'] + - ['¡', '⠲'] + - ['¤', '⡦'] + - ['ª', '⣮'] + - ['°', '⠴'] + - ['µ', '⠦'] + - ['º', '⣿'] + - ['À', '⡷'] + - ['Á', '⣷'] + - ['Â', '⣡'] + - ['Ã', '⣩'] + - ['Ä', '⣜'] + - ['Å', '⡡'] + - ['Æ', '⡜'] + - ['Ç', '⡯'] + - ['È', '⡮'] + - ['É', '⡿'] + - ['Ê', '⡣'] + - ['Ë', '⡫'] + - ['Ì', '⣱'] + - ['Í', '⣣'] + - ['Î', '⡩'] + - ['Ï', '⡻'] + - ['Ð', '⡠'] + - ['Ñ', '⣻'] + - ['Ò', '⣫'] + - ['Ó', '⣬'] + - ['Ô', '⡹'] + - ['Õ', '⣹'] + - ['Ö', '⣪'] + - ['×', '⠼'] + - ['Ø', '⡪'] + - ['Ù', '⡾'] + - ['Ú', '⣳'] + - ['Û', '⡱'] + - ['Ü', '⡳'] + - ['Ý', '⣶'] + - ['Þ', '⣅'] + - ['ß', '⢮'] + - ['à', '⠷'] + - ['á', '⢷'] + - ['â', '⢡'] + - ['ã', '⢩'] + - ['ä', '⢜'] + - ['å', '⠡'] + - ['æ', '⠜'] + - ['ç', '⠯'] + - ['è', '⠮'] + - ['é', '⠿'] + - ['ê', '⠣'] + - ['ë', '⠫'] + - ['ì', '⢱'] + - ['í', '⢣'] + - ['î', '⠩'] + - ['ï', '⠻'] + - ['ð', '⢽'] + - ['ñ', '⢻'] + - ['ò', '⢫'] + - ['ó', '⢬'] + - ['ô', '⠹'] + - ['õ', '⢹'] + - ['ö', '⢪'] + - ['ø', '⠪'] + - ['ù', '⠾'] + - ['ú', '⢳'] + - ['û', '⠱'] + - ['ü', '⠳'] + - ['ý', '⢍'] + - ['þ', '⢅'] + - ['ÿ', '⢾'] + +# Pangram + + - - 'Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Walther spillede på xylofon.' + - '⡟⠥⠊⠵⠙⠑⠇⠞⠁⠛⠑⠗⠝⠑ ⠎⠏⠊⠎⠞⠑ ⠚⠕⠗⠙⠃⠜⠗ ⠍⠑⠙ ⠋⠇⠪⠙⠑⠂ ⠍⠑⠝⠎ ⠉⠊⠗⠅⠥⠎⠅⠇⠕⠧⠝⠑⠝ ⡺⠁⠇⠞⠓⠑⠗ ⠎⠏⠊⠇⠇⠑⠙⠑ ⠏⠡ ⠭⠽⠇⠕⠋⠕⠝⠄' + +# ------- +# Grade 0 +# ------- + +table: {language: da, grade: 0, dots: 8, version: 2022} +flags: {testmode: bothDirections} +tests: + + - ['\x0009', '⣊'] + - ['\x000a', '⣚'] + - ['\x000b', '⢝'] + - ['\x000c', '⢇'] + - ['\x000d', '⡒'] + +# Characters from 0x21 to 0xff + + - ['Š', '⠨'] + - ['š', '⢎'] + - [' ', '⢞'] + +# ------------- +# Grade 1 and 2 +# ------------- + +table: {language: da, grade: 1, dots: 8, version: 2022} +table: {language: da, grade: 2, dots: 8, version: 2022} +flags: {testmode: bothDirections} +tests: + +# Misc Unicode chars +# For each accented letter, the first occurring instance is chosen for back-translation. +# There are no rules about this in the specs of Danish Braille, +# So the choice is arbitrary and may change over time. + + - ['\x0100\x0101', '⠐⡁⠐⠁'] + - ['\x0106\x0107', '⠐⡉⠐⠉'] + - ['\x010e\x010f', '⠐⡙⠐⠙'] + - ['\x0112\x0113', '⠐⡑⠐⠑'] + - ['\x011c\x011d', '⠐⡛⠐⠛'] + - ['\x0124\x0125', '⠐⡓⠐⠓'] + - ['\x0128\x0129', '⠐⡊⠐⠊'] + - ['\x0134\x0135', '⠐⡚⠐⠚'] + - ['\x0136\x0137', '⠐⡅⠐⠅'] + - ['\x0138', '⠐⠟'] + - ['\x0139\x013a', '⠐⡇⠐⠇'] + - ['\x0143\x0144', '⠐⡝⠐⠝'] + - ['\x014c\x014d', '⠐⡕⠐⠕'] + - ['\x0154\x0155', '⠐⡗⠐⠗'] + - ['\x0162\x0163', '⠐⡞⠐⠞'] + - ['\x0168\x0169', '⠐⡥⠐⠥'] + - ['\x0174\x0175', '⠐⡺⠐⠺'] + - ['\x0176\x0177', '⠐⡽⠐⠽'] + - ['\x0179\x017a', '⠐⡵⠐⠵'] + +# Punctuation and bullits + + - ['\x2016', '⠘⢸'] + - ['\x2017', '⠘⣤'] + +# Arrows + + - ['\x2190', '⠘⠳⠪'] + - ['\x2191', '⠘⠳⠬'] + - ['\x2192', '⠘⠳⠕'] + - ['\x2193', '⠘⠳⠩'] + - ['\x2194', '⠘⠳⠺⠗⠕'] + - ['\x2196', '⠘⠳⠱'] + - ['\x2197', '⠘⠳⠎'] + - ['\x2198', '⠘⠳⠣'] + - ['\x2199', '⠘⠳⠜'] + - ['\x21D4', '⠘⠳⠺⠶⠗⠕'] + +# Math signs (experimental) + + - ['\x2200', '⠘⠁'] + - ['\x2208', '⠘⠑'] + - ['\x2213', '⠸⠤'] + - ['\x221d', '⠸⠐⢶'] + - ['\x2229', '⠨⠦'] + - ['\x222a', '⠨⠖'] + - ['\x2243', '⠸⠔'] + - ['\x2245', '⠐⠘⠔'] + - ['\x2248', '⠘⠔'] + - ['\x224f', '⠘⠐⢶'] + - ['\x2251', '⠨⠐⢶'] + - ['\x2260', '⠐⢶⠈⠱'] + - ['\x2261', '⠸⠿'] + - ['\x2264', '⠸⢔'] + - ['\x2265', '⠸⡢'] + - ['\x226a', '⠨⢔'] + - ['\x226b', '⠨⡢'] + - ['\x22c5', '⠐⠲'] + +# Section sign + + - ["§ 3", "⣐⢉"] + - ["§ 3:", "⣐⢉⠒"] + - ["§§ 3-5", "⣐⣐⢉⠤⢑"] + +# Characters and constructs which cannot be properly back-translated + +flags: {testmode: forward} +tests: + + - ['\x0009', ' '] + - ['\x000a', ' '] + - ['\x000b', ' '] + - ['\x000c', ' '] + - ['\x000d', ' '] + - ['\x00a0', ' '] + - ['\x0102\x0103', '⠐⡁⠐⠁'] + - ['\x0104\x0105', '⠐⡁⠐⠁'] + - ['\x0108\x0109', '⠐⡉⠐⠉'] + - ['\x010a\x010b', '⠐⡉⠐⠉'] + - ['\x010c\x010d', '⠐⡉⠐⠉'] + - ['\x0110\x0111', '⠐⡙⠐⠙'] + - ['\x0114\x0115', '⠐⡑⠐⠑'] + - ['\x0116\x0117', '⠐⡑⠐⠑'] + - ['\x0118\x0119', '⠐⡑⠐⠑'] + - ['\x011a\x011b', '⠐⡑⠐⠑'] + - ['\x011e\x011f', '⠐⡛⠐⠛'] + - ['\x0120\x0121', '⠐⡛⠐⠛'] + - ['\x0122\x0123', '⠐⡛⠐⠛'] + - ['\x0126\x0127', '⠐⡓⠐⠓'] + - ['\x012a\x012b', '⠐⡊⠐⠊'] + - ['\x012c\x012d', '⠐⡊⠐⠊'] + - ['\x012e\x012f', '⠐⡊⠐⠊'] + - ['\x0130\x0131', '⠐⡊⠐⠊'] + - ['\x0132\x0133', '⡊⠚⠊⠚'] + - ['\x013b\x013c', '⠐⡇⠐⠇'] + - ['\x013d\x013e', '⠐⡇⠐⠇'] + - ['\x013f\x0140', '⠐⡇⠐⠇'] + - ['\x0141\x0142', '⠐⡇⠐⠇'] + - ['\x0145\x0146', '⠐⡝⠐⠝'] + - ['\x0147\x0148', '⠐⡝⠐⠝'] + - ['\x0149', '⠈⠝'] + - ['\x014a\x014b', '⠐⡝⠐⠝'] + - ['\x014e\x014f', '⠐⡕⠐⠕'] + - ['\x0150\x0151', '⠐⡕⠐⠕'] + - ['\x0156\x0157', '⠐⡗⠐⠗'] + - ['\x0158\x0159', '⠐⡗⠐⠗'] + - ['\x015a\x015b', '⠐⡎⠐⠎'] + - ['\x015c\x015d', '⠐⡎⠐⠎'] + - ['\x015e\x015f', '⠐⡎⠐⠎'] + - ['\x0164\x0165', '⠐⡞⠐⠞'] + - ['\x0166\x0167', '⠐⡞⠐⠞'] + - ['\x016a\x016b', '⠐⡥⠐⠥'] + - ['\x016c\x016d', '⠐⡥⠐⠥'] + - ['\x016e\x016f', '⠐⡥⠐⠥'] + - ['\x0170\x0171', '⠐⡥⠐⠥'] + - ['\x0172\x0173', '⠐⡥⠐⠥'] + - ['\x017b\x017c', '⠐⡵⠐⠵'] + - ['\x017f', '⠐⠎'] + +# Punctuation and bullits + + - ['\x2000', ' '] + - ['\x2001', ' '] + - ['\x2002', ' '] + - ['\x2003', ' '] + - ['\x2004', ' '] + - ['\x2005', ' '] + - ['\x2006', ' '] + - ['\x2007', ' '] + - ['\x2008', ' '] + - ['\x2009', ' '] + - ['\x200a', ' '] + - ['\x2010', '⠤'] + - ['\x2011', '⠤'] + - ['\x2012', '⠤'] + - ['\x201b', '⠈'] + - ['\x201f', '⠶'] + - ['\x2023', '⡄'] + - ['\x202f', ' '] + - ['\x203c', '⠖⠖'] + - ['\x203d', '⠢⠖'] + - ['\x2043', '⡄'] + - ['\x2047', '⠢⠢'] + - ['\x2048', '⠢⠖'] + - ['\x2049', '⠖⠢'] + - ['\x204c', '⡄'] + - ['\x204d', '⡄'] + +# Geometrical shapes + + - ['\x25e6', '⡄'] + +# Tests that apply for grade 1 and 2 but currently fail for grade 2 + +table: {language: da, grade: 1, dots: 8, version: 2022} +flags: {testmode: bothDirections} +tests: + +# Greek letters (with dots 458 as prefix) + + - ['\x0386\x03ac', '⢘⠐⡁⢘⠐⠁'] + - ['\x0388\x03ad', '⢘⠐⡑⢘⠐⠑'] + - ['\x0389\x03ae', '⢘⠐⡱⢘⠐⠱'] + - ['\x038a\x03af', '⢘⠐⡊⢘⠐⠊'] + - ['\x038c\x03cc', '⢘⠐⡕⢘⠐⠕'] + - ['\x038e\x03cd', '⢘⠐⡥⢘⠐⠥'] + - ['\x038f\x03ce', '⢘⠐⡺⢘⠐⠺'] + - ['\x0391\x03b1', '⢘⡁⢘⠁'] + - ['\x0392\x03b2', '⢘⡃⢘⠃'] + - ['\x0393\x03b3', '⢘⡛⢘⠛'] + - ['\x0394\x03b4', '⢘⡙⢘⠙'] + - ['\x0395\x03b5', '⢘⡑⢘⠑'] + - ['\x0396\x03b6', '⢘⡵⢘⠵'] + - ['\x0397\x03b7', '⢘⡱⢘⠱'] + - ['\x0398\x03b8', '⢘⡹⢘⠹'] + - ['\x0399\x03b9', '⢘⡊⢘⠊'] + - ['\x039a\x03ba', '⢘⡅⢘⠅'] + - ['\x039b\x03bb', '⢘⡇⢘⠇'] + - ['\x039c\x03bc', '⢘⡍⢘⠍'] + - ['\x039d\x03bd', '⢘⡝⢘⠝'] + - ['\x039e\x03be', '⢘⡭⢘⠭'] + - ['\x039f\x03bf', '⢘⡕⢘⠕'] + - ['\x03a0\x03c0', '⢘⡏⢘⠏'] + - ['\x03a1\x03c1', '⢘⡗⢘⠗'] + - ['\x03a3\x03c3', '⢘⡎⢘⠎'] + - ['\x03a4\x03c4', '⢘⡞⢘⠞'] + - ['\x03a5\x03c5', '⢘⡥⢘⠥'] + - ['\x03a6\x03c6', '⢘⡋⢘⠋'] + - ['\x03a7\x03c7', '⢘⡯⢘⠯'] + - ['\x03a8\x03c8', '⢘⡽⢘⠽'] + - ['\x03a9\x03c9', '⢘⡺⢘⠺'] + +# same tests but with xfail +table: {language: da, grade: 2, dots: 8, version: 2022} +flags: {testmode: bothDirections} +tests: + - ['\x0386\x03ac', '⢘⠐⡁⢘⠐⠁', {xfail: {forward: true}}] + - ['\x0388\x03ad', '⢘⠐⡑⢘⠐⠑', {xfail: {forward: true}}] + - ['\x0389\x03ae', '⢘⠐⡱⢘⠐⠱', {xfail: {forward: true}}] + - ['\x038a\x03af', '⢘⠐⡊⢘⠐⠊', {xfail: {forward: true}}] + - ['\x038c\x03cc', '⢘⠐⡕⢘⠐⠕', {xfail: {forward: true}}] + - ['\x038e\x03cd', '⢘⠐⡥⢘⠐⠥', {xfail: {forward: true}}] + - ['\x038f\x03ce', '⢘⠐⡺⢘⠐⠺', {xfail: {forward: true}}] + - ['\x0391\x03b1', '⢘⡁⢘⠁', {xfail: {forward: true}}] + - ['\x0392\x03b2', '⢘⡃⢘⠃', {xfail: {forward: true}}] + - ['\x0393\x03b3', '⢘⡛⢘⠛', {xfail: {forward: true}}] + - ['\x0394\x03b4', '⢘⡙⢘⠙', {xfail: {forward: true}}] + - ['\x0395\x03b5', '⢘⡑⢘⠑', {xfail: {forward: true}}] + - ['\x0396\x03b6', '⢘⡵⢘⠵', {xfail: {forward: true}}] + - ['\x0397\x03b7', '⢘⡱⢘⠱', {xfail: {forward: true}}] + - ['\x0398\x03b8', '⢘⡹⢘⠹', {xfail: {forward: true}}] + - ['\x0399\x03b9', '⢘⡊⢘⠊', {xfail: {forward: true}}] + - ['\x039a\x03ba', '⢘⡅⢘⠅', {xfail: {forward: true}}] + - ['\x039b\x03bb', '⢘⡇⢘⠇', {xfail: {forward: true}}] + - ['\x039c\x03bc', '⢘⡍⢘⠍', {xfail: {forward: true}}] + - ['\x039d\x03bd', '⢘⡝⢘⠝', {xfail: {forward: true}}] + - ['\x039e\x03be', '⢘⡭⢘⠭', {xfail: {forward: true}}] + - ['\x039f\x03bf', '⢘⡕⢘⠕', {xfail: {forward: true}}] + - ['\x03a0\x03c0', '⢘⡏⢘⠏', {xfail: {forward: true}}] + - ['\x03a1\x03c1', '⢘⡗⢘⠗', {xfail: {forward: true}}] + - ['\x03a3\x03c3', '⢘⡎⢘⠎', {xfail: {forward: true}}] + - ['\x03a4\x03c4', '⢘⡞⢘⠞', {xfail: {forward: true}}] + - ['\x03a5\x03c5', '⢘⡥⢘⠥', {xfail: {forward: true}}] + - ['\x03a6\x03c6', '⢘⡋⢘⠋', {xfail: {forward: true}}] + - ['\x03a7\x03c7', '⢘⡯⢘⠯', {xfail: {forward: true}}] + - ['\x03a8\x03c8', '⢘⡽⢘⠽', {xfail: {forward: true}}] + - ['\x03a9\x03c9', '⢘⡺⢘⠺', {xfail: {forward: true}}] + +# ------- +# Grade 1 +# ------- + +table: {language: da, grade: 1, dots: 8, version: 2022} +flags: {testmode: bothDirections} +tests: + +# Characters from 0x21 to 0xff + + - ['Š', '⠐⡎'] + - ['š', '⠐⠎'] + +# Emphasis (cannot be back-translated) + +flags: {testmode: forward} +tests: + + - # Bold line + - En linje med fed. + - ⠨⡑⠝ ⠇⠊⠝⠚⠑ ⠍⠑⠙ ⠋⠑⠙⠄⠨ + - typeform: + bold: '+++++++++++++++++' + - # Bold word + - Et ord med fed. + - ⡑⠞ ⠨⠕⠗⠙⠨ ⠍⠑⠙ ⠋⠑⠙⠄ + - typeform: + bold: ' +++ ' + - # Bold letter + - Et bogstav med fed. + - ⡑⠞ ⠃⠕⠛⠨⠎⠨⠞⠁⠧ ⠍⠑⠙ ⠋⠑⠙⠄ + - typeform: + bold: ' + ' + + - # Italic line + - En linje med kursiv. + - ⠨⡑⠝ ⠇⠊⠝⠚⠑ ⠍⠑⠙ ⠅⠥⠗⠎⠊⠧⠄⠨ + - typeform: + italic: '++++++++++++++++++++' + - # Italic word + - Et ord med kursiv. + - ⡑⠞ ⠨⠕⠗⠙⠨ ⠍⠑⠙ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' +++ ' + - # Italic letter + - Et bogstav med kursiv. + - ⡑⠞ ⠃⠕⠛⠨⠎⠨⠞⠁⠧ ⠍⠑⠙ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' + ' + + - # Underlined line + - En linje med understreget. + - ⠨⡑⠝ ⠇⠊⠝⠚⠑ ⠍⠑⠙ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄⠨ + - typeform: + underline: '++++++++++++++++++++++++++' + - # Underlined word + - Et ord med understreget. + - ⡑⠞ ⠨⠕⠗⠙⠨ ⠍⠑⠙ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄ + - typeform: + underline: ' +++ ' + - # Underlined letter + - Et bogstav med understreget. + - ⡑⠞ ⠃⠕⠛⠨⠎⠨⠞⠁⠧ ⠍⠑⠙ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄ + - typeform: + underline: ' + ' + + - # Bold, italic and underlined line + - En linje med alt. + - ⠨⡑⠝ ⠇⠊⠝⠚⠑ ⠍⠑⠙ ⠁⠇⠞⠄⠨ + - typeform: + bold: '+++++++++++++++++' + italic: '+++++++++++++++++' + underline: '+++++++++++++++++' + - # Bold, italic and underlined word + - Et ord med alt. + - ⡑⠞ ⠨⠕⠗⠙⠨ ⠍⠑⠙ ⠁⠇⠞⠄ + - typeform: + bold: ' +++ ' + italic: ' +++ ' + underline: ' +++ ' + - # Bold, italic and underlined letter + - Et bogstav med alt. + - ⡑⠞ ⠃⠕⠛⠨⠎⠨⠞⠁⠧ ⠍⠑⠙ ⠁⠇⠞⠄ + - typeform: + bold: ' + ' + italic: ' + ' + underline: ' + ' + +# ------- +# Grade 2 +# ------- + +table: {language: da, grade: 2, dots: 8, version: 2022} +flags: {testmode: bothDirections} +tests: + +# Characters from 0x21 to 0xff + + - ['*', '⠰⠔'] + - ['A', '⠰⡁'] + - ['B', '⠰⡃'] + - ['C', '⠰⡉'] + - ['D', '⠰⡙'] + - ['E', '⠰⡑'] + - ['F', '⠰⡋'] + - ['G', '⠰⡛'] + - ['H', '⠰⡓'] + - ['I', '⡊'] + - ['J', '⠰⡚'] + - ['K', '⠰⡅'] + - ['L', '⠰⡇'] + - ['M', '⠰⡍'] + - ['N', '⠰⡝'] + - ['O', '⠰⡕'] + - ['P', '⠰⡏'] + - ['Q', '⠰⡟'] + - ['R', '⠰⡗'] + - ['S', '⠰⡎'] + - ['T', '⠰⡞'] + - ['U', '⠰⡥'] + - ['V', '⠰⡧'] + - ['W', '⠰⡺'] + - ['X', '⠰⡭'] + - ['Y', '⠰⡽'] + - ['Z', '⠰⡵'] + - ['\\', '⠰⡌'] + - ['a', '⠰⠁'] + - ['b', '⠰⠃'] + - ['c', '⠰⠉'] + - ['d', '⠰⠙'] + - ['e', '⠰⠑'] + - ['f', '⠰⠋'] + - ['g', '⠰⠛'] + - ['h', '⠰⠓'] + - ['i', '⠊'] + - ['j', '⠰⠚'] + - ['k', '⠰⠅'] + - ['l', '⠰⠇'] + - ['m', '⠰⠍'] + - ['n', '⠰⠝'] + - ['o', '⠰⠕'] + - ['p', '⠰⠏'] + - ['q', '⠰⠟'] + - ['r', '⠰⠗'] + - ['s', '⠰⠎'] + - ['t', '⠰⠞'] + - ['u', '⠰⠥'] + - ['v', '⠰⠧'] + - ['w', '⠰⠺'] + - ['x', '⠰⠭'] + - ['y', '⠰⠽'] + - ['z', '⠰⠵'] + - ['…', '⠰⠄⠄⠄'] + - ['Š', '⠐⡎'] + - ['Œ', '⠰⣕'] + - ['Ž', '⠰⡬'] + - ['š', '⠐⠎'] + - ['œ', '⠰⢕'] + - ['ž', '⠰⠬'] + - ['Ÿ', '⠰⣾'] + - ['¡', '⠰⠲'] + - ['¤', '⠰⡦'] + - ['ª', '⠰⣮'] + - ['¬', '⠰⡼'] + - ['­', '⠰⣄'] + - ['°', '⠰⠴'] + - ['µ', '⠰⠦'] + - ['º', '⠰⣿'] + - ['À', '⠰⡷'] + - ['Á', '⠰⣷'] + - ['Â', '⠰⣡'] + - ['Ã', '⠰⣩'] + - ['Ä', '⠰⣜'] + - ['Å', '⠰⡡'] + - ['Æ', '⠰⡜'] + - ['Ç', '⠰⡯'] + - ['È', '⠰⡮'] + - ['É', '⠰⡿'] + - ['Ê', '⠰⡣'] + - ['Ë', '⠰⡫'] + - ['Ì', '⠰⣱'] + - ['Í', '⠰⣣'] + - ['Î', '⠰⡩'] + - ['Ï', '⠰⡻'] + - ['Ð', '⠰⡠'] + - ['Ñ', '⠰⣻'] + - ['Ò', '⠰⣫'] + - ['Ó', '⠰⣬'] + - ['Ô', '⠰⡹'] + - ['Õ', '⠰⣹'] + - ['Ö', '⠰⣪'] + - ['×', '⠰⠼'] + - ['Ø', '⠰⡪'] + - ['Ù', '⠰⡾'] + - ['Ú', '⠰⣳'] + - ['Û', '⠰⡱'] + - ['Ü', '⠰⡳'] + - ['Ý', '⠰⣍'] + - ['Þ', '⠰⣅'] + - ['ß', '⠰⢮'] + - ['à', '⠰⠷'] + - ['á', '⠰⢷'] + - ['â', '⠰⢡'] + - ['ã', '⠰⢩'] + - ['ä', '⠰⢜'] + - ['å', '⠰⠡'] + - ['æ', '⠰⠜'] + - ['ç', '⠰⠯'] + - ['è', '⠰⠮'] + - ['é', '⠰⠿'] + - ['ê', '⠰⠣'] + - ['ë', '⠰⠫'] + - ['ì', '⠰⢱'] + - ['í', '⠰⢣'] + - ['î', '⠰⠩'] + - ['ï', '⠰⠻'] + - ['ð', '⠰⢽'] + - ['ñ', '⠰⢻'] + - ['ò', '⠰⢫'] + - ['ó', '⠰⢬'] + - ['ô', '⠰⠹'] + - ['õ', '⠰⢹'] + - ['ö', '⠰⢪'] + - ['ø', '⠰⠪'] + - ['ù', '⠰⠾'] + - ['ú', '⠰⢳'] + - ['û', '⠰⠱'] + - ['ü', '⠰⠳'] + - ['ý', '⠰⢍'] + - ['þ', '⠰⢅'] + - ['ÿ', '⠰⢾'] + +# Pangram + + - - 'Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Walther spillede på xylofon.' + - '⠰⡟⠥⠊⠰⠵⠹⠇⠞⠁⠛⠱⠫ ⠎⠏⠊⠵⠑ ⠚⠭⠙⠃⠜⠗ ⠍ ⠋⠇⠪⠹⠂ ⠍⠣⠎ ⠉⠊⠗⠅⠥⠎⠅⠇⠕⠧⠝⠣ ⠰⡺⠁⠇⠞⠓⠱ ⠎⠏⠊⠇⠇⠑⠹ ⠏ ⠰⠭⠽⠇⠕⠋⠕⠝⠄' + +# Inverted exclamation + + - ['¡Que lastima!', '⠰⠲⠰⡟⠥⠑ ⠇⠁⠵⠊⠍⠁⠖'] + +# No letsign before numbers + + - ['v8', '⠰⠧⢓'] + - ['A4', '⠰⡁⢙'] + - ['Eleva2ren', '⡑⠇⠑⠧⠁⢃⠗⠣'] + +# URLs emails and file names + + - ['$at', '⣙⠁⠞'] + - ['\\at\\bliver', '⠰⡌⠁⠞⠰⡌⠃⠇⠊⠧⠑⠗'] + - ['at@bliver.og', '⠁⠞⣁⠃⠇⠊⠧⠑⠗⠄⠕⠛'] + - ['http://at.bliver.og', '⠓⠞⠞⠏⠒⢌⢌⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠕⠛'] + - ['www.at.bliver.og', '⠺⠺⠺⠄⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠕⠛'] + - ['www.at.bliver.com', '⠺⠺⠺⠄⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠉⠕⠍'] + - ['www.a.b.c', '⠺⠺⠺⠄⠰⠁⠄⠰⠃⠄⠰⠉'] + +# Word contractions + + - ['At', '⡁'] + - ['at', '⠁'] + - ['Aldrig', '⡁⠔'] + - ['aldrig', '⠁⠔'] + - ['aig', '⠁⠊⠛'] + - ['Alle', '⡁⠑'] + - ['alle', '⠁⠑'] + - ['ae', '⠰⠁⠑'] + - ['Allerede', '⡁⠇⠗'] + - ['allerede', '⠁⠇⠗'] + - ['alr', '⠰⠁⠇⠗'] + - ['Alligevel', '⡁⠇⠧'] + - ['alligevel', '⠁⠇⠧'] + - ['alv', '⠰⠁⠇⠧'] + - ['Altid', '⡁⠞⠙'] + - ['altid', '⠁⠞⠙'] + - ['atd', '⠰⠁⠞⠙'] + - ['Altså', '⡁⠡'] + - ['altså', '⠁⠡'] + - ['aå', '⠰⠁⠡'] + - ['Bliver', '⡃'] + - ['bliver', '⠃'] + - ['Og', '⡉'] + - ['og', '⠉'] + - ['Deres', '⡲'] + - ['deres', '⠲'] + - ['Du', '⡙'] + - ['du', '⠙'] + - ['Eller', '⡑'] + - ['eller', '⠑'] + - ['For', '⡋'] + - ['for', '⠋'] + - ['Gør', '⡛'] + - ['gør', '⠛'] + - ['Har', '⡓'] + - ['har', '⠓'] + - ['Jeg', '⡚'] + - ['jeg', '⠚'] + - ['Kan', '⡅'] + - ['kan', '⠅'] + - ['Lige', '⡇'] + - ['lige', '⠇'] + - ['Med', '⡍'] + - ['med', '⠍'] + - ['Når', '⡝'] + - ['når', '⠝'] + - ['Op', '⡕'] + - ['op', '⠕'] + - ['På', '⡏'] + - ['på', '⠏'] + - ['Under', '⡟'] + - ['under', '⠟'] + - ['Rigtig', '⡗'] + - ['rigtig', '⠗'] + - ['Som', '⡎'] + - ['som', '⠎'] + - ['Til', '⡞'] + - ['til', '⠞'] + - ['Hun', '⡥'] + - ['hun', '⠥'] + - ['Ved', '⡧'] + - ['ved', '⠧'] + - ['Hvad', '⡺'] + - ['hvad', '⠺'] + - ['Over', '⡭'] + - ['over', '⠭'] + - ['Han', '⡽'] + - ['han', '⠽'] + - ['Efter', '⡵'] + - ['efter', '⠵'] + - ['Være', '⡜'] + - ['være', '⠜'] + - ['Før', '⡪'] + - ['før', '⠪'] + - ['Så', '⡡'] + - ['så', '⠡'] + - ['Den', '⡯'] + - ['den', '⠯'] + - ['Der', '⡾'] + - ['der', '⠾'] + - ['Det', '⡮'] + - ['det', '⠮'] + - ['De', '⡹'] + - ['de', '⠹'] + - ['En', '⡣'] + - ['en', '⠣'] + - ['Er', '⡱'] + - ['er', '⠱'] + - ['Et', '⡬'] + - ['et', '⠬'] + - ['Gennem', '⡻'] + - ['gennem', '⠻'] + - ['Hvor', '⡌'] + - ['hvor', '⠌'] + - ['Men', '⡩'] + - ['men', '⠩'] + - ['Ned', '⡫'] + - ['ned', '⠫'] + - ['Ret', '⡷'] + - ['ret', '⠷'] + - ['Skal', '⡿'] + - ['skal', '⠿'] + - ['Te', '⡳'] + - ['te', '⠳'] + - ['Var', '⡼'] + - ['var', '⠼'] + +# Partword/nocross + + - ['Denne', '⡯⠫'] + - ['denne', '⠯⠫'] + - ['Mændene', '⡍⠜⠝⠹⠫'] + - ['mændene', '⠍⠜⠝⠹⠫'] + - ['Derhos', '⡾⠓⠕⠎'] + - ['derhos', '⠾⠓⠕⠎'] + - ['Hunderace', '⡓⠥⠝⠹⠗⠁⠉⠑'] + - ['hunderace', '⠓⠥⠝⠹⠗⠁⠉⠑'] + - ['Dette', '⡮⠳'] + - ['dette', '⠮⠳'] + - ['Detalje', '⡹⠞⠁⠇⠚⠑'] + - ['detalje', '⠹⠞⠁⠇⠚⠑'] + +# Nocross multiple cells + + - ['Endda', '⡑⠟⠙⠁'] + - ['endda', '⠑⠟⠙⠁'] + - ['Morgendag', '⡍⠭⠛⠣⠙⠁⠛'] + - ['morgendag', '⠍⠭⠛⠣⠙⠁⠛'] + - ['Gendanne', '⡛⠣⠙⠁⠝⠫'] + - ['gendanne', '⠛⠣⠙⠁⠝⠫'] + - ['Generelt', '⡻⠫⠷⠇⠞'] + - ['generelt', '⠻⠫⠷⠇⠞'] + + - ['Fra!', '⡋⠗⠁⠖'] + - ['fra!', '⠋⠗⠁⠖'] + - ['!Fra', '⠰⠖⡖'] + - ['!fra', '⠖⠋⠗⠁'] + - ['''Af', '⠈⡴'] + - ['''af', '⠈⠁⠋'] + +# Capsnocont + + - ['UNDER et', '⡥⡝⡙⡑⡗ ⠬'] + +# No single cell contractions before or after dashes + + - ['at-bliver', '⠁⠞⠤⠃'] + - ['d-d-du', '⠰⠙⠤⠰⠙⠤⠙⠥'] + +# Combinations with slashes and other punctuation signs + + - ["at!", "⠁⠖"] + - ["bliver!", "⠃⠖"] + - ["og!", "⠉⠖"] + - ['Han/hun', '⡽⢌⠥'] + - ['han/hun', '⠽⢌⠥'] + - ['Over/under', '⡭⢌⠟'] + - ['over/under', '⠭⢌⠟'] + - ['Til/fra', '⡞⢌⠖'] + - ['til/fra', '⠞⢌⠖'] + +# Combinations which require letsign + + - ['1st', '⢁⠰⠎⠞'] + - ['2nd', '⢃⠝⠙'] + - ['1A', '⢁⠰⡁'] + - ['1a', '⢁⠰⠁'] + - ['2B', '⢃⠰⡃'] + - ['2b', '⢃⠰⠃'] + +# Emphasis (cannot be back-translated) +# Single letter emphasis currently fails due to contraction across emphasis. + +flags: {testmode: forward} +tests: + + - # Bold line + - En linje med fed. + - ⠨⡣ ⠇⠊⠝⠚⠑ ⠍ ⠋⠑⠙⠄⠨ + - typeform: + bold: '+++++++++++++++++' + - # Bold word + - Et ord med fed. + - ⡬ ⠨⠭⠙⠨ ⠍ ⠋⠑⠙⠄ + - typeform: + bold: ' +++ ' + - # Bold letter + - Et bogstav med fed. + - ⡬ ⠃⠕⠛⠨⠎⠨⠞⠁⠧ ⠍ ⠋⠑⠙⠄ + - {typeform: {bold: ' + '}, xfail: true} + + - # Italic line + - En linje med kursiv. + - ⠨⡣ ⠇⠊⠝⠚⠑ ⠍ ⠅⠥⠗⠎⠊⠧⠄⠨ + - typeform: + italic: '++++++++++++++++++++' + - # Italic word + - Et ord med kursiv. + - ⡬ ⠨⠭⠙⠨ ⠍ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' +++ ' + - # Italic letter + - Et bogstav med kursiv. + - ⡬ ⠃⠕⠛⠨⠎⠨⠞⠁⠧ ⠍ ⠅⠥⠗⠎⠊⠧⠄ + - {typeform: {italic: ' + '}, xfail: true} + + - # Underlined line + - En linje med understreget. + - ⠨⡣ ⠇⠊⠝⠚⠑ ⠍ ⠥⠝⠾⠵⠷⠛⠬⠄⠨ + - typeform: + underline: '++++++++++++++++++++++++++' + - # Underlined word + - Et ord med understreget. + - ⡬ ⠨⠭⠙⠨ ⠍ ⠥⠝⠾⠵⠷⠛⠬⠄ + - typeform: + underline: ' +++ ' + - # Underlined letter + - Et bogstav med understreget. + - ⡬ ⠃⠕⠛⠨⠎⠨⠞⠁⠧ ⠍ ⠥⠝⠾⠵⠷⠛⠬⠄ + - {typeform: {underline: ' + '}, xfail: true} + + - # Bold, italic and underlined line + - En linje med alt. + - ⠨⡣ ⠇⠊⠝⠚⠑ ⠍ ⠁⠇⠞⠄⠨ + - typeform: + bold: '+++++++++++++++++' + italic: '+++++++++++++++++' + underline: '+++++++++++++++++' + - # Bold, italic and underlined word + - Et ord med alt. + - ⡬ ⠨⠭⠙⠨ ⠍ ⠁⠇⠞⠄ + - typeform: + bold: ' +++ ' + italic: ' +++ ' + underline: ' +++ ' + - # Bold, italic and underlined letter + - Et bogstav med alt. + - ⡬ ⠃⠕⠛⠨⠎⠨⠞⠁⠧ ⠍ ⠁⠇⠞⠄ + - {typeform: {bold: ' + ', italic: ' + ', underline: ' + '}, xfail: true} diff --git a/LibLouis.NET.Test/braille-specs/da-dk_1993.yaml b/LibLouis.NET.Test/braille-specs/da-dk_1993.yaml new file mode 100644 index 0000000..a1d0cbf --- /dev/null +++ b/LibLouis.NET.Test/braille-specs/da-dk_1993.yaml @@ -0,0 +1,5970 @@ +# This file contains tests for the complete Danish 1993 braille +# standard: all grades (0, 1, 1.5 and 2), 6-dot and 8-dot, regular and +# "literary" (not back-translatable). Previously there was one test +# file for each variant, but now that there is a new standard and the +# table for the old standard are pretty much frozen, it has been +# decided to collect the tests in a single file to keep things +# tidy. The dictionary tests were left in separate files. + +# There is a lot of repetition in this file which could be eliminated +# with some refactoring, but because we're dealing with a deprecated +# standard we're not going to bother. + +display: unicode-without-blank.dis + +############# +### 6-DOT ### +############# + +# ----------------- +# Grade 1 (regular) +# ----------------- + +# Round trip tests +# Commented tests currently fail backwards but should be fixed. + +table: {language: da, grade: 1, dots: 6, direction: both, version: 1993, __assert-match: da-dk-g16_1993.ctb} +flags: {testmode: bothDirections} +tests: + +# Characters + + - [" ", " "] + - ["\"", "⠶"] + - ["\u0023", "⠘⠼"] + - ["$", "⠘⠲"] + - ["%", "⠚⠴"] + - ["&", "⠯"] + - ["'", "⠈"] + - ["(", "⠦"] + - [")", "⠴"] + - ["*", "⠔"] + - ["+", "⠘⠖"] + - [",", "⠂"] + - ["-", "⠤⠤"] + - [".", "⠄"] + - ["/", "⠌"] + - ["0", "⠼⠚"] + - ["1", "⠼⠁"] + - ["2", "⠼⠃"] + - ["3", "⠼⠉"] + - ["4", "⠼⠙"] + - ["5", "⠼⠑"] + - ["6", "⠼⠋"] + - ["7", "⠼⠛"] + - ["8", "⠼⠓"] + - ["9", "⠼⠊"] + - [":", "⠒"] + - [";", "⠆"] + - ["<", "⠘⠍"] + - ["=", "⠘⠶"] + - [">", "⠘⠎"] + - ["?", "⠢"] + - ["@", "⠘⠁"] + - ["A", "⠨⠁"] + - ["B", "⠨⠃"] + - ["C", "⠨⠉"] + - ["D", "⠨⠙"] + - ["E", "⠨⠑"] + - ["F", "⠨⠋"] + - ["G", "⠨⠛"] + - ["H", "⠨⠓"] + - ["I", "⠨⠊"] + - ["J", "⠨⠚"] + - ["K", "⠨⠅"] + - ["L", "⠨⠇"] + - ["M", "⠨⠍"] + - ["N", "⠨⠝"] + - ["O", "⠨⠕"] + - ["P", "⠨⠏"] + - ["Q", "⠨⠟"] + - ["R", "⠨⠗"] + - ["S", "⠨⠎"] + - ["T", "⠨⠞"] + - ["U", "⠨⠥"] + - ["V", "⠨⠧"] + - ["W", "⠨⠺"] + - ["X", "⠨⠭"] + - ["Y", "⠨⠽"] + - ["Z", "⠨⠵"] + - ["[", "⠐⠦"] + - ["\u005c\u005c", "⠘⠡"] + - ["]", "⠐⠴"] + - ["^", "⠘⠬"] + - ["_", "⠘⠤"] + - ["a", "⠁"] + - ["b", "⠃"] + - ["c", "⠉"] + - ["d", "⠙"] + - ["e", "⠑"] + - ["f", "⠋"] + - ["g", "⠛"] + - ["h", "⠓"] + - ["i", "⠊"] + - ["j", "⠚"] + - ["k", "⠅"] + - ["l", "⠇"] + - ["m", "⠍"] + - ["n", "⠝"] + - ["o", "⠕"] + - ["p", "⠏"] + - ["q", "⠟"] + - ["r", "⠗"] + - ["s", "⠎"] + - ["t", "⠞"] + - ["u", "⠥"] + - ["v", "⠧"] + - ["w", "⠺"] + - ["x", "⠭"] + - ["y", "⠽"] + - ["z", "⠵"] + - ["{", "⠘⠪"] + - ["|", "⠘⠸"] + - ["}", "⠘⠕"] + - ["€", "⠘⠑"] + - ["ƒ", "⠘⠋"] + - ["‰", "⠚⠴⠴"] + - ["Š", "⠨⠐⠎"] + - ["Ž", "⠨⠐⠵"] + - ["•", "⠘⠄"] + - ["™", "⠘⠞"] + - ["š", "⠐⠎"] + - ["ž", "⠐⠵"] + - ["¢", "⠘⠒"] + - ["£", "⠘⠇"] + - ["¥", "⠘⠽"] + - ["§", "⠬"] + - ["©", "⠘⠉"] + - ["®", "⠘⠗"] + - ["°", "⠈⠴"] + - ["²", "⠼⠬⠃"] + - ["³", "⠼⠬⠉"] + - ["µ", "⠐⠍"] + - ["¹", "⠼⠬⠁"] + - ["À", "⠨⠐⠁"] + - ["Å", "⠨⠡"] + - ["Æ", "⠨⠜"] + - ["Ç", "⠨⠐⠉"] + - ["É", "⠨⠐⠑"] + - ["Î", "⠨⠐⠊"] + - ["Ð", "⠨⠐⠙"] + - ["Ñ", "⠨⠐⠝"] + - ["Ô", "⠨⠐⠕"] + - ["Ø", "⠨⠪"] + - ["Û", "⠨⠐⠥"] + - ["Ü", "⠨⠳"] + - ["Ý", "⠨⠐⠽"] + - ["Þ", "⠨⠐⠞"] + - ["à", "⠐⠁"] + - ["å", "⠡"] + - ["æ", "⠜"] + - ["ç", "⠐⠉"] + - ["é", "⠐⠑"] + - ["î", "⠐⠊"] + - ["ð", "⠐⠙"] + - ["ñ", "⠐⠝"] + - ["ô", "⠐⠕"] + - ["÷", "⠲"] + - ["ø", "⠪"] + - ["û", "⠐⠥"] + - ["ü", "⠳"] + - ["ý", "⠐⠽"] + - ["þ", "⠐⠞"] + +# Misc. Unicode (most cannot be back-translated) +# Some tests may be repetitions of tests above, since +# some characters can occur both inside and outside the 8 bit range. +# for accented letters in the range u+0080 - u+00ff: the letters +# that are thought to occur most frequently in Danish texts are used +# for back-translation. +# For all accented letters above u+00ff, the first occurring instance is chosen for back-translation. +# There are no rules about this in the specs of Danish Braille, +# So the choice is arbitrary and may change over time. + + - ["\u0134\u0135", "⠨⠐⠚⠐⠚"] + - ["\u0138", "⠐⠟"] + - ["\u0192", "⠘⠋"] + +# Greek letters (with dot 5 as prefix) + + - ["\u0392\u03b2", "⠨⠐⠃⠐⠃"] + - ["\u0393\u03b3", "⠨⠐⠛⠐⠛"] + - ["\u0397\u03b7", "⠨⠐⠱⠐⠱"] + - ["\u0398\u03b8", "⠨⠐⠹⠐⠹"] + - ["\u039a\u03ba", "⠨⠐⠅⠐⠅"] + - ["\u039b\u03bb", "⠨⠐⠇⠐⠇"] + - ["\u039e\u03be", "⠨⠐⠭⠐⠭"] + - ["\u03a0\u03c0", "⠨⠐⠏⠐⠏"] + - ["\u03a1\u03c1", "⠨⠐⠗⠐⠗"] + - ["\u03a6\u03c6", "⠨⠐⠋⠐⠋"] + - ["\u03a7\u03c7", "⠨⠐⠓⠐⠓"] + - ["\u03a9\u03c9", "⠨⠐⠺⠐⠺"] + +# Arrows (only a few in 6 dots) + + - ["\u2190", "⠘⠺"] + - ["\u2191", "⠘⠷"] + - ["\u2193", "⠘⠟"] + +# Pangram + + - - "Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Walther spillede på xylofon." + - "⠨⠟⠥⠊⠵⠙⠑⠇⠞⠁⠛⠑⠗⠝⠑ ⠎⠏⠊⠎⠞⠑ ⠚⠕⠗⠙⠃⠜⠗ ⠍⠑⠙ ⠋⠇⠪⠙⠑⠂ ⠍⠑⠝⠎ ⠉⠊⠗⠅⠥⠎⠅⠇⠕⠧⠝⠑⠝ ⠨⠺⠁⠇⠞⠓⠑⠗ ⠎⠏⠊⠇⠇⠑⠙⠑ ⠏⠡ ⠭⠽⠇⠕⠋⠕⠝⠄" + +# Caps and mixed case + + - ["Foobar", "⠨⠋⠕⠕⠃⠁⠗"] + - ["FOOBAR", "⠸⠋⠕⠕⠃⠁⠗"] + - ["FOObar", "⠸⠋⠕⠕⠠⠃⠁⠗"] + - ["FOO-barfOObar", "⠸⠋⠕⠕⠤⠃⠁⠗⠋⠸⠕⠕⠠⠃⠁⠗"] + +# Times vs. bullit + + - ["\u2022Bullit", "⠘⠄⠨⠃⠥⠇⠇⠊⠞"] +# - ["2 \u00d7 2 = 4", "⠼⠃ ⠘⠄ ⠼⠃ ⠘⠶ ⠼⠙"] + +# Section sign + + - ["§ 3", "⠬⠼⠉"] + - ["§ 3:", "⠬⠼⠉⠒"] + +# Numbers and punctuation + + - ["1,2", "⠼⠁⠂⠃"] + - ["123.456", "⠼⠁⠃⠉⠄⠙⠑⠋"] + - ["3-4", "⠼⠉⠤⠼⠙"] + - ["56-", "⠼⠑⠋⠤"] + - ["1/2", "⠼⠁⠌⠃"] + - ["12:34", "⠼⠁⠃⠒⠉⠙"] + - ["2^10", "⠼⠃⠘⠬⠁⠚"] + - ["J) j) %) ') \u2030)", "⠨⠚⠠⠴ ⠚⠠⠴ ⠚⠴⠠⠴ ⠈⠠⠴ ⠚⠴⠴⠠⠴"] + + - ["\"quotes\"", "⠶⠟⠥⠕⠞⠑⠎⠶"] + - ["-dashes-", "⠤⠙⠁⠎⠓⠑⠎⠤"] + - [":-) :-(", "⠒⠤⠴ ⠒⠤⠦"] + - [";-) ;-(", "⠆⠤⠴ ⠆⠤⠦"] + - ["---", "⠤⠤⠤"] + - ["-", "⠤⠤"] +# - [" -", " ⠤⠤"] + - [" a-", " ⠁⠤"] + - [" - ", " ⠤⠤ "] + - [" -a-", " ⠤⠁⠤"] + - [" ", " "] + - ["(parentheses)", "⠦⠏⠁⠗⠑⠝⠞⠓⠑⠎⠑⠎⠴"] + +# Digits and letters + + - ["1a", "⠼⠁⠠⠁"] + - ["1.,-a", "⠼⠁⠄⠂⠤⠠⠁"] + +# Multi-pass tests + +# - ["~x |z", "⠘⠠⠭ ⠘⠸⠵"] +# - ["~X |Z", "⠘⠠⠨⠭ ⠘⠸⠨⠵"] + - ["5É", "⠼⠑⠨⠐⠑"] + +# Characters and constructs which cannot be properly back-translated + +flags: {testmode: forward} +tests: + + - ["`", "⠈"] + - ["~", "⠘⠠"] + - ["‚", "⠈"] + - ["„", "⠶"] + - ["…", "⠄⠄⠄"] + - ["‹", "⠈"] + - ["Œ", "⠨⠕⠑"] + - ["‘", "⠈"] + - ["’", "⠈"] + - ["“", "⠶"] + - ["”", "⠶"] + - ["–", "⠤⠤"] + - ["—", "⠤⠤"] + - ["˜", "⠘⠠"] + - ["›", "⠈"] + - ["œ", "⠕⠑"] + - ["Ÿ", "⠨⠐⠽"] + - ["¡", "⠲"] + - ["«", "⠶"] + - ["­", "⠤⠤"] + - ["¯", "⠢"] + - ["±", "⠘⠖⠤"] + - ["´", "⠈"] + - ["»", "⠶"] + - ["¼", "⠼⠁⠌⠙"] + - ["½", "⠼⠁⠌⠃"] + - ["¾", "⠼⠉⠌⠙"] + - ["Á", "⠨⠐⠁"] + - ["Â", "⠨⠐⠁"] + - ["Ã", "⠨⠐⠁"] + - ["Ä", "⠨⠜"] + - ["È", "⠨⠐⠑"] + - ["Ê", "⠨⠐⠑"] + - ["Ë", "⠨⠐⠑"] + - ["Ì", "⠨⠐⠊"] + - ["Í", "⠨⠐⠊"] + - ["Ï", "⠨⠐⠊"] + - ["Ò", "⠨⠐⠕"] + - ["Ó", "⠨⠐⠕"] + - ["Õ", "⠨⠐⠕"] + - ["Ö", "⠨⠪"] + - ["×", "⠘⠄"] + - ["Ù", "⠨⠐⠥"] + - ["Ú", "⠨⠐⠥"] + - ["ß", "⠎⠎"] + - ["á", "⠐⠁"] + - ["â", "⠐⠁"] + - ["ã", "⠐⠁"] + - ["ä", "⠜"] + - ["è", "⠐⠑"] + - ["ê", "⠐⠑"] + - ["ë", "⠐⠑"] + - ["ì", "⠐⠊"] + - ["í", "⠐⠊"] + - ["ï", "⠐⠊"] + - ["ò", "⠐⠕"] + - ["ó", "⠐⠕"] + - ["õ", "⠐⠕"] + - ["ö", "⠪"] + - ["ù", "⠐⠥"] + - ["ú", "⠐⠥"] + - ["ÿ", "⠐⠽"] + +# Latin Extended-A + + - ["\u0100\u0101", "⠨⠐⠁⠐⠁"] + - ["\u0102\u0103", "⠨⠐⠁⠐⠁"] + - ["\u0104\u0105", "⠨⠐⠁⠐⠁"] + - ["\u0106\u0107", "⠨⠐⠉⠐⠉"] + - ["\u0108\u0109", "⠨⠐⠉⠐⠉"] + - ["\u010a\u010b", "⠨⠐⠉⠐⠉"] + - ["\u010c\u010d", "⠨⠐⠉⠐⠉"] + - ["\u010e\u010f", "⠨⠐⠙⠐⠙"] + - ["\u0110\u0111", "⠨⠐⠙⠐⠙"] + - ["\u0112\u0113", "⠨⠐⠑⠐⠑"] + - ["\u0114\u0115", "⠨⠐⠑⠐⠑"] + - ["\u0116\u0117", "⠨⠐⠑⠐⠑"] + - ["\u0118\u0119", "⠨⠐⠑⠐⠑"] + - ["\u011a\u011b", "⠨⠐⠑⠐⠑"] + - ["\u011c\u011d", "⠨⠐⠛⠐⠛"] + - ["\u011e\u011f", "⠨⠐⠛⠐⠛"] + - ["\u0120\u0121", "⠨⠐⠛⠐⠛"] + - ["\u0122\u0123", "⠨⠐⠛⠐⠛"] + - ["\u0124\u0125", "⠨⠐⠓⠐⠓"] + - ["\u0126\u0127", "⠨⠐⠓⠐⠓"] + - ["\u0128\u0129", "⠨⠐⠊⠐⠊"] + - ["\u012a\u012b", "⠨⠐⠊⠐⠊"] + - ["\u012c\u012d", "⠨⠐⠊⠐⠊"] + - ["\u012e\u012f", "⠨⠐⠊⠐⠊"] + - ["\u0130\u0131", "⠨⠐⠊⠐⠊"] + - ["\u0132\u0133", "⠨⠊⠚⠊⠚"] + - ["\u0136\u0137", "⠨⠐⠅⠐⠅"] + - ["\u0139\u013a", "⠨⠐⠇⠐⠇"] + - ["\u013b\u013c", "⠨⠐⠇⠐⠇"] + - ["\u013d\u013e", "⠨⠐⠇⠐⠇"] + - ["\u013f\u0140", "⠨⠐⠇⠐⠇"] + - ["\u0141\u0142", "⠨⠐⠇⠐⠇"] + - ["\u0143\u0144", "⠨⠐⠝⠐⠝"] + - ["\u0145\u0146", "⠨⠐⠝⠐⠝"] + - ["\u0147\u0148", "⠨⠐⠝⠐⠝"] + - ["\u0149", "⠈⠝"] + - ["\u014a\u014b", "⠨⠐⠝⠐⠝"] + - ["\u014c\u014d", "⠨⠐⠕⠐⠕"] + - ["\u014e\u014f", "⠨⠐⠕⠐⠕"] + - ["\u0150\u0151", "⠨⠐⠕⠐⠕"] + - ["\u0152\u0153", "⠨⠕⠑⠕⠑"] + - ["\u0154\u0155", "⠨⠐⠗⠐⠗"] + - ["\u0156\u0157", "⠨⠐⠗⠐⠗"] + - ["\u0158\u0159", "⠨⠐⠗⠐⠗"] + - ["\u015a\u015b", "⠨⠐⠎⠐⠎"] + - ["\u015c\u015d", "⠨⠐⠎⠐⠎"] + - ["\u015e\u015f", "⠨⠐⠎⠐⠎"] + - ["\u0160\u0161", "⠨⠐⠎⠐⠎"] + - ["\u0162\u0163", "⠨⠐⠞⠐⠞"] + - ["\u0164\u0165", "⠨⠐⠞⠐⠞"] + - ["\u0166\u0167", "⠨⠐⠞⠐⠞"] + - ["\u0168\u0169", "⠨⠐⠥⠐⠥"] + - ["\u016a\u016b", "⠨⠐⠥⠐⠥"] + - ["\u016c\u016d", "⠨⠐⠥⠐⠥"] + - ["\u016e\u016f", "⠨⠐⠥⠐⠥"] + - ["\u0170\u0171", "⠨⠐⠥⠐⠥"] + - ["\u0172\u0173", "⠨⠐⠥⠐⠥"] + - ["\u0174\u0175", "⠨⠐⠺⠐⠺"] + - ["\u0176\u0177", "⠨⠐⠽⠐⠽"] + - ["\u0178\u00ff", "⠨⠐⠽⠐⠽"] + - ["\u0179\u017a", "⠨⠐⠵⠐⠵"] + - ["\u017b\u017c", "⠨⠐⠵⠐⠵"] + - ["\u017d\u017e", "⠨⠐⠵⠐⠵"] + - ["\u017f", "⠐⠎"] + +# Latin Extended-B + + - ["\u02dc", "⠘⠠"] + +# Greek letters (with dot 5 as prefix) + + - ["\u0386\u03ac", "⠨⠐⠁⠐⠁"] + - ["\u0388\u03ad", "⠨⠐⠑⠐⠑"] + - ["\u0389\u03ae", "⠨⠐⠱⠐⠱"] + - ["\u038a\u03af", "⠨⠐⠊⠐⠊"] + - ["\u038c\u03cc", "⠨⠐⠕⠐⠕"] + - ["\u038e\u03cd", "⠨⠐⠥⠐⠥"] + - ["\u038f\u03ce", "⠨⠐⠺⠐⠺"] + - ["\u0391\u03b1", "⠨⠐⠁⠐⠁"] + - ["\u0394\u03b4", "⠨⠐⠙⠐⠙"] + - ["\u0395\u03b5", "⠨⠐⠑⠐⠑"] + - ["\u0396\u03b6", "⠨⠐⠵⠐⠵"] + - ["\u0399\u03b9", "⠨⠐⠊⠐⠊"] + - ["\u039c\u03bc", "⠨⠐⠍⠐⠍"] + - ["\u039d\u03bd", "⠨⠐⠝⠐⠝"] + - ["\u039f\u03bf", "⠨⠐⠕⠐⠕"] + - ["\u03a3\u03c3", "⠨⠐⠎⠐⠎"] + - ["\u03a4\u03c4", "⠨⠐⠞⠐⠞"] + - ["\u03a5\u03c5", "⠨⠐⠥⠐⠥"] + - ["\u03a8\u03c8", "⠨⠐⠽⠐⠽"] + +# Punctuation and bullits + + - ["\u2000", " "] + - ["\u2001", " "] + - ["\u2002", " "] + - ["\u2003", " "] + - ["\u2004", " "] + - ["\u2005", " "] + - ["\u2006", " "] + - ["\u2007", " "] + - ["\u2008", " "] + - ["\u2009", " "] + - ["\u200a", " "] + - ["\u2010", "⠤"] + - ["\u2011", "⠤"] + - ["\u2012", "⠤"] + - ["\u2013", "⠤⠤"] + - ["\u2014", "⠤⠤"] + - ["\u2018", "⠈"] + - ["\u2019", "⠈"] + - ["\u201a", "⠈"] + - ["\u201b", "⠈"] + - ["\u201c", "⠶"] + - ["\u201d", "⠶"] + - ["\u201e", "⠶"] + - ["\u201f", "⠶"] + - ["\u2023", "⠘⠄"] + - ["\u2026", "⠄⠄⠄"] + - ["\u202f", " "] + - ["\u2030", "⠚⠴⠴"] + - ["\u2039", "⠈"] + - ["\u203a", "⠈"] + - ["\u203c", "⠖⠖"] + - ["\u203d", "⠢⠖"] + - ["\u2043", "⠘⠄"] + - ["\u2047", "⠢⠢"] + - ["\u2048", "⠢⠖"] + - ["\u2049", "⠖⠢"] + - ["\u204c", "⠘⠄"] + - ["\u204d", "⠘⠄"] + - ["\u20ac", "⠘⠑"] + - ["\u2122", "⠘⠞"] + +# Arrows (only a few in 6 dots) + + - ["\u2192", "⠘⠗"] # back-translates as "registered". + +# Geometrical shapes + + - ["\u25e6", "⠘⠄"] + +# Extra digits + + - ["1\u00bc + 1\u00bd = 2\u00be", "⠼⠁⠼⠁⠌⠙ ⠘⠖ ⠼⠁⠼⠁⠌⠃ ⠘⠶ ⠼⠃⠼⠉⠌⠙"] + +# Dashes + + - ["\u2014 \u0096 \u0097 \u00ad", "⠤⠤ ⠤⠤ ⠤⠤ ⠤⠤"] + +# Quotes + + - ["\u201e \u0084 \u201c \u0093 \u201d \u0094 \u00ab \u00bb", "⠶ ⠶ ⠶ ⠶ ⠶ ⠶ ⠶ ⠶"] + +# Apostrophes + + - ["` \u201a \u0082 \u2039 \u008b \u2018 \u0091 \u2019 \u0092 \u203a \u009b \u00b4", "⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈"] + + - ["\u0089) \u201a) \u0082) \u2039) \u009b) \u2018) \u0091) \u2019) \u0092) \u203a) \u009b)", "⠚⠴⠴⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴"] + +# Emphasis + + - # Bold line + - En linje med fed. + - ⠰⠨⠑⠝ ⠇⠊⠝⠚⠑ ⠍⠑⠙ ⠋⠑⠙⠄⠰ + - typeform: + bold: '+++++++++++++++++' + - # Bold word + - Et ord med fed. + - ⠨⠑⠞ ⠰⠕⠗⠙⠰ ⠍⠑⠙ ⠋⠑⠙⠄ + - typeform: + bold: ' +++ ' + - # Bold letter + - Et bogstav med fed. + - ⠨⠑⠞ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍⠑⠙ ⠋⠑⠙⠄ + - typeform: + bold: ' + ' + + - # Italic line + - En linje med kursiv. + - ⠰⠨⠑⠝ ⠇⠊⠝⠚⠑ ⠍⠑⠙ ⠅⠥⠗⠎⠊⠧⠄⠰ + - typeform: + italic: '++++++++++++++++++++' + - # Italic word + - Et ord med kursiv. + - ⠨⠑⠞ ⠰⠕⠗⠙⠰ ⠍⠑⠙ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' +++ ' + - # Italic letter + - Et bogstav med kursiv. + - ⠨⠑⠞ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍⠑⠙ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' + ' + + - # Underlined line + - En linje med understreget. + - ⠰⠨⠑⠝ ⠇⠊⠝⠚⠑ ⠍⠑⠙ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄⠰ + - typeform: + underline: '++++++++++++++++++++++++++' + - # Underlined word + - Et ord med understreget. + - ⠨⠑⠞ ⠰⠕⠗⠙⠰ ⠍⠑⠙ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄ + - typeform: + underline: ' +++ ' + - # Underlined letter + - Et bogstav med understreget. + - ⠨⠑⠞ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍⠑⠙ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄ + - typeform: + underline: ' + ' + + - # Bold, italic and underlined line + - En linje med alt. + - ⠰⠨⠑⠝ ⠇⠊⠝⠚⠑ ⠍⠑⠙ ⠁⠇⠞⠄⠰ + - typeform: + bold: '+++++++++++++++++' + italic: '+++++++++++++++++' + underline: '+++++++++++++++++' + - # Bold, italic and underlined word + - Et ord med alt. + - ⠨⠑⠞ ⠰⠕⠗⠙⠰ ⠍⠑⠙ ⠁⠇⠞⠄ + - typeform: + bold: ' +++ ' + italic: ' +++ ' + underline: ' +++ ' + - # Bold, italic and underlined letter + - Et bogstav med alt. + - ⠨⠑⠞ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍⠑⠙ ⠁⠇⠞⠄ + - typeform: + bold: ' + ' + italic: ' + ' + underline: ' + ' + +# Braille patterns which back-translate to something other than the original +# Typically, single character representations back-translating to strings with more than one character + +flags: {testmode: backward} +tests: + +# Characters + + - ["⠄⠄⠄", "..."] + - ["⠨⠕⠑", "Oe"] + - ["⠕⠑", "oe"] + - ["⠘⠖⠤", "±", {xfail: true}] + - ["⠼⠁⠌⠙", "1/4"] + - ["⠼⠁⠌⠃", "1/2"] + - ["⠼⠉⠌⠙", "3/4"] + +# Extra digits + + - ["⠼⠁⠼⠁⠌⠙ ⠘⠖ ⠼⠁⠼⠁⠌⠃ ⠘⠶ ⠼⠃⠼⠉⠌⠙", "1\u00bc + 1\u00bd = 2\u00be", {xfail: true}] + +# ------------------------------- +# Grade 1 literary (forward only) +# ------------------------------- + +table: {language: da, grade: 1, dots: 6, direction: forward, version: 1993, __assert-match: da-dk-g16-lit_1993.ctb} +flags: {testmode: forward} +tests: + +# Characters + + - [" ", " "] + - ["\"", "⠶"] + - ["\u0023", "⠘⠼"] + - ["$", "⠘⠲"] + - ["%", "⠚⠴"] + - ["&", "⠯"] + - ["'", "⠈"] + - ["(", "⠦"] + - [")", "⠴"] + - ["*", "⠔"] + - ["+", "⠘⠖"] + - [",", "⠂"] + - ["-", "⠤⠤"] + - [".", "⠄"] + - ["/", "⠌"] + - ["0", "⠼⠚"] + - ["1", "⠼⠁"] + - ["2", "⠼⠃"] + - ["3", "⠼⠉"] + - ["4", "⠼⠙"] + - ["5", "⠼⠑"] + - ["6", "⠼⠋"] + - ["7", "⠼⠛"] + - ["8", "⠼⠓"] + - ["9", "⠼⠊"] + - [":", "⠒"] + - [";", "⠆"] + - ["<", "⠘⠍"] + - ["=", "⠘⠶"] + - [">", "⠘⠎"] + - ["?", "⠢"] + - ["@", "⠘⠁"] + - ["A", "⠨⠁"] + - ["B", "⠨⠃"] + - ["C", "⠨⠉"] + - ["D", "⠨⠙"] + - ["E", "⠨⠑"] + - ["F", "⠨⠋"] + - ["G", "⠨⠛"] + - ["H", "⠨⠓"] + - ["I", "⠨⠊"] + - ["J", "⠨⠚"] + - ["K", "⠨⠅"] + - ["L", "⠨⠇"] + - ["M", "⠨⠍"] + - ["N", "⠨⠝"] + - ["O", "⠨⠕"] + - ["P", "⠨⠏"] + - ["Q", "⠨⠟"] + - ["R", "⠨⠗"] + - ["S", "⠨⠎"] + - ["T", "⠨⠞"] + - ["U", "⠨⠥"] + - ["V", "⠨⠧"] + - ["W", "⠨⠺"] + - ["X", "⠨⠭"] + - ["Y", "⠨⠽"] + - ["Z", "⠨⠵"] + - ["[", "⠐⠦"] + - ["\u005c\u005c", "⠘⠡"] + - ["]", "⠐⠴"] + - ["^", "⠘⠬"] + - ["_", "⠘⠤"] + - ["`", "⠈"] + - ["a", "⠁"] + - ["b", "⠃"] + - ["c", "⠉"] + - ["d", "⠙"] + - ["e", "⠑"] + - ["f", "⠋"] + - ["g", "⠛"] + - ["h", "⠓"] + - ["i", "⠊"] + - ["j", "⠚"] + - ["k", "⠅"] + - ["l", "⠇"] + - ["m", "⠍"] + - ["n", "⠝"] + - ["o", "⠕"] + - ["p", "⠏"] + - ["q", "⠟"] + - ["r", "⠗"] + - ["s", "⠎"] + - ["t", "⠞"] + - ["u", "⠥"] + - ["v", "⠧"] + - ["w", "⠺"] + - ["x", "⠭"] + - ["y", "⠽"] + - ["z", "⠵"] + - ["{", "⠘⠪"] + - ["|", "⠘⠸"] + - ["}", "⠘⠕"] + - ["~", "⠘⠠"] + - ["€", "⠘⠑"] + - ["‚", "⠈"] + - ["ƒ", "⠘⠋"] + - ["„", "⠶"] + - ["…", "⠄⠄⠄"] + - ["‰", "⠚⠴⠴"] + - ["Š", "⠨⠐⠎"] + - ["‹", "⠈"] + - ["Œ", "⠨⠕⠑"] + - ["Ž", "⠨⠐⠵"] + - ["‘", "⠈"] + - ["’", "⠈"] + - ["“", "⠶"] + - ["”", "⠶"] + - ["•", "⠘⠄"] + - ["–", "⠤⠤"] + - ["—", "⠤⠤"] + - ["˜", "⠘⠠"] + - ["™", "⠘⠞"] + - ["š", "⠐⠎"] + - ["›", "⠈"] + - ["œ", "⠕⠑"] + - ["ž", "⠐⠵"] + - ["Ÿ", "⠨⠐⠽"] + - ["¡", "⠲"] + - ["¢", "⠘⠒"] + - ["£", "⠘⠇"] + - ["¥", "⠘⠽"] + - ["§", "⠬"] + - ["©", "⠘⠉"] + - ["«", "⠶"] + - ["­", "⠤⠤"] + - ["®", "⠘⠗"] + - ["¯", "⠢"] + - ["°", "⠈⠴"] + - ["±", "⠘⠖⠤"] + - ["²", "⠼⠬⠃"] + - ["³", "⠼⠬⠉"] + - ["´", "⠈"] + - ["µ", "⠐⠍"] + - ["¹", "⠼⠬⠁"] + - ["»", "⠶"] + - ["¼", "⠼⠁⠌⠙"] + - ["½", "⠼⠁⠌⠃"] + - ["¾", "⠼⠉⠌⠙"] + - ["À", "⠨⠐⠁"] + - ["Á", "⠨⠐⠁"] + - ["Â", "⠨⠐⠁"] + - ["Ã", "⠨⠐⠁"] + - ["Ä", "⠨⠜"] + - ["Å", "⠨⠡"] + - ["Æ", "⠨⠜"] + - ["Ç", "⠨⠐⠉"] + - ["È", "⠨⠐⠑"] + - ["É", "⠨⠐⠑"] + - ["Ê", "⠨⠐⠑"] + - ["Ë", "⠨⠐⠑"] + - ["Ì", "⠨⠐⠊"] + - ["Í", "⠨⠐⠊"] + - ["Î", "⠨⠐⠊"] + - ["Ï", "⠨⠐⠊"] + - ["Ð", "⠨⠐⠙"] + - ["Ñ", "⠨⠐⠝"] + - ["Ò", "⠨⠐⠕"] + - ["Ó", "⠨⠐⠕"] + - ["Ô", "⠨⠐⠕"] + - ["Õ", "⠨⠐⠕"] + - ["Ö", "⠨⠪"] + - ["×", "⠘⠄"] + - ["Ø", "⠨⠪"] + - ["Ù", "⠨⠐⠥"] + - ["Ú", "⠨⠐⠥"] + - ["Û", "⠨⠐⠥"] + - ["Ü", "⠨⠳"] + - ["Ý", "⠨⠐⠽"] + - ["Þ", "⠨⠐⠞"] + - ["ß", "⠎⠎"] + - ["à", "⠐⠁"] + - ["á", "⠐⠁"] + - ["â", "⠐⠁"] + - ["ã", "⠐⠁"] + - ["ä", "⠜"] + - ["å", "⠡"] + - ["æ", "⠜"] + - ["ç", "⠐⠉"] + - ["è", "⠐⠑"] + - ["é", "⠐⠑"] + - ["ê", "⠐⠑"] + - ["ë", "⠐⠑"] + - ["ì", "⠐⠊"] + - ["í", "⠐⠊"] + - ["î", "⠐⠊"] + - ["ï", "⠐⠊"] + - ["ð", "⠐⠙"] + - ["ñ", "⠐⠝"] + - ["ò", "⠐⠕"] + - ["ó", "⠐⠕"] + - ["ô", "⠐⠕"] + - ["õ", "⠐⠕"] + - ["ö", "⠪"] + - ["÷", "⠲"] + - ["ø", "⠪"] + - ["ù", "⠐⠥"] + - ["ú", "⠐⠥"] + - ["û", "⠐⠥"] + - ["ü", "⠳"] + - ["ý", "⠐⠽"] + - ["þ", "⠐⠞"] + - ["ÿ", "⠐⠽"] + +# Latin Extended-A + + - ["\u0100\u0101", "⠐⠁⠐⠁"] + - ["\u0102\u0103", "⠐⠁⠐⠁"] + - ["\u0104\u0105", "⠐⠁⠐⠁"] + - ["\u0106\u0107", "⠐⠉⠐⠉"] + - ["\u0108\u0109", "⠐⠉⠐⠉"] + - ["\u010a\u010b", "⠐⠉⠐⠉"] + - ["\u010c\u010d", "⠐⠉⠐⠉"] + - ["\u010e\u010f", "⠐⠙⠐⠙"] + - ["\u0110\u0111", "⠐⠙⠐⠙"] + - ["\u0112\u0113", "⠐⠑⠐⠑"] + - ["\u0114\u0115", "⠐⠑⠐⠑"] + - ["\u0116\u0117", "⠐⠑⠐⠑"] + - ["\u0118\u0119", "⠐⠑⠐⠑"] + - ["\u011a\u011b", "⠐⠑⠐⠑"] + - ["\u011c\u011d", "⠐⠛⠐⠛"] + - ["\u011e\u011f", "⠐⠛⠐⠛"] + - ["\u0120\u0121", "⠐⠛⠐⠛"] + - ["\u0122\u0123", "⠐⠛⠐⠛"] + - ["\u0124\u0125", "⠐⠓⠐⠓"] + - ["\u0126\u0127", "⠐⠓⠐⠓"] + - ["\u0128\u0129", "⠐⠊⠐⠊"] + - ["\u012a\u012b", "⠐⠊⠐⠊"] + - ["\u012c\u012d", "⠐⠊⠐⠊"] + - ["\u012e\u012f", "⠐⠊⠐⠊"] + - ["\u0130\u0131", "⠐⠊⠐⠊"] + - ["\u0132\u0133", "⠊⠚⠊⠚"] + - ["\u0134\u0135", "⠐⠚⠐⠚"] + - ["\u0136\u0137", "⠐⠅⠐⠅"] + - ["\u0138", "⠐⠟"] + - ["\u0139\u013a", "⠐⠇⠐⠇"] + - ["\u013b\u013c", "⠐⠇⠐⠇"] + - ["\u013d\u013e", "⠐⠇⠐⠇"] + - ["\u013f\u0140", "⠐⠇⠐⠇"] + - ["\u0141\u0142", "⠐⠇⠐⠇"] + - ["\u0143\u0144", "⠐⠝⠐⠝"] + - ["\u0145\u0146", "⠐⠝⠐⠝"] + - ["\u0147\u0148", "⠐⠝⠐⠝"] + - ["\u0149", "⠈⠝"] + - ["\u014a\u014b", "⠐⠝⠐⠝"] + - ["\u014c\u014d", "⠐⠕⠐⠕"] + - ["\u014e\u014f", "⠐⠕⠐⠕"] + - ["\u0150\u0151", "⠐⠕⠐⠕"] + - ["\u0152\u0153", "⠕⠑⠕⠑"] + - ["\u0154\u0155", "⠐⠗⠐⠗"] + - ["\u0156\u0157", "⠐⠗⠐⠗"] + - ["\u0158\u0159", "⠐⠗⠐⠗"] + - ["\u015a\u015b", "⠐⠎⠐⠎"] + - ["\u015c\u015d", "⠐⠎⠐⠎"] + - ["\u015e\u015f", "⠐⠎⠐⠎"] + - ["\u0160\u0161", "⠐⠎⠐⠎"] + - ["\u0162\u0163", "⠐⠞⠐⠞"] + - ["\u0164\u0165", "⠐⠞⠐⠞"] + - ["\u0166\u0167", "⠐⠞⠐⠞"] + - ["\u0168\u0169", "⠐⠥⠐⠥"] + - ["\u016a\u016b", "⠐⠥⠐⠥"] + - ["\u016c\u016d", "⠐⠥⠐⠥"] + - ["\u016e\u016f", "⠐⠥⠐⠥"] + - ["\u0170\u0171", "⠐⠥⠐⠥"] + - ["\u0172\u0173", "⠐⠥⠐⠥"] + - ["\u0174\u0175", "⠐⠺⠐⠺"] + - ["\u0176\u0177", "⠐⠽⠐⠽"] + - ["\u0178\u00ff", "⠐⠽⠐⠽"] + - ["\u0179\u017a", "⠐⠵⠐⠵"] + - ["\u017b\u017c", "⠐⠵⠐⠵"] + - ["\u017d\u017e", "⠐⠵⠐⠵"] + - ["\u017f", "⠐⠎"] + +# Latin Extended-B + + - ["\u0192", "⠘⠋"] + - ["\u02dc", "⠘⠠"] + +# Greek letters (with dot 5 as prefix) + + - ["\u0386\u03ac", "⠐⠁⠐⠁"] + - ["\u0388\u03ad", "⠐⠑⠐⠑"] + - ["\u0389\u03ae", "⠐⠱⠐⠱"] + - ["\u038a\u03af", "⠐⠊⠐⠊"] + - ["\u038c\u03cc", "⠐⠕⠐⠕"] + - ["\u038e\u03cd", "⠐⠥⠐⠥"] + - ["\u038f\u03ce", "⠐⠺⠐⠺"] + - ["\u0391\u03b1", "⠐⠁⠐⠁"] + - ["\u0392\u03b2", "⠐⠃⠐⠃"] + - ["\u0393\u03b3", "⠐⠛⠐⠛"] + - ["\u0394\u03b4", "⠐⠙⠐⠙"] + - ["\u0395\u03b5", "⠐⠑⠐⠑"] + - ["\u0396\u03b6", "⠐⠵⠐⠵"] + - ["\u0397\u03b7", "⠐⠱⠐⠱"] + - ["\u0398\u03b8", "⠐⠹⠐⠹"] + - ["\u0399\u03b9", "⠐⠊⠐⠊"] + - ["\u039a\u03ba", "⠐⠅⠐⠅"] + - ["\u039b\u03bb", "⠐⠇⠐⠇"] + - ["\u039c\u03bc", "⠐⠍⠐⠍"] + - ["\u039d\u03bd", "⠐⠝⠐⠝"] + - ["\u039e\u03be", "⠐⠭⠐⠭"] + - ["\u039f\u03bf", "⠐⠕⠐⠕"] + - ["\u03a0\u03c0", "⠐⠏⠐⠏"] + - ["\u03a1\u03c1", "⠐⠗⠐⠗"] + - ["\u03a3\u03c3", "⠐⠎⠐⠎"] + - ["\u03a4\u03c4", "⠐⠞⠐⠞"] + - ["\u03a5\u03c5", "⠐⠥⠐⠥"] + - ["\u03a6\u03c6", "⠐⠋⠐⠋"] + - ["\u03a7\u03c7", "⠐⠓⠐⠓"] + - ["\u03a8\u03c8", "⠐⠽⠐⠽"] + - ["\u03a9\u03c9", "⠐⠺⠐⠺"] + +# Punctuation and bullits + + - ["\u2000", " "] + - ["\u2001", " "] + - ["\u2002", " "] + - ["\u2003", " "] + - ["\u2004", " "] + - ["\u2005", " "] + - ["\u2006", " "] + - ["\u2007", " "] + - ["\u2008", " "] + - ["\u2009", " "] + - ["\u200a", " "] + - ["\u2010", "⠤"] + - ["\u2011", "⠤"] + - ["\u2012", "⠤"] + - ["\u2013", "⠤⠤"] + - ["\u2014", "⠤⠤"] + - ["\u2018", "⠈"] + - ["\u2019", "⠈"] + - ["\u201a", "⠈"] + - ["\u201b", "⠈"] + - ["\u201c", "⠶"] + - ["\u201d", "⠶"] + - ["\u201e", "⠶"] + - ["\u201f", "⠶"] + - ["\u2023", "⠘⠄"] + - ["\u2026", "⠄⠄⠄"] + - ["\u202f", " "] + - ["\u2030", "⠚⠴⠴"] + - ["\u2039", "⠈"] + - ["\u203a", "⠈"] + - ["\u203c", "⠖⠖"] + - ["\u203d", "⠢⠖"] + - ["\u2043", "⠘⠄"] + - ["\u2047", "⠢⠢"] + - ["\u2048", "⠢⠖"] + - ["\u2049", "⠖⠢"] + - ["\u204c", "⠘⠄"] + - ["\u204d", "⠘⠄"] + - ["\u20ac", "⠘⠑"] + - ["\u2122", "⠘⠞"] + +# Arrows (only a few in 6 dots) + + - ["\u2190", "⠘⠺"] + - ["\u2191", "⠘⠷"] + - ["\u2192", "⠘⠗"] + - ["\u2193", "⠘⠟"] + +# Geometrical shapes + + - ["\u25e6", "⠘⠄"] + +# Pangram + + - - "Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Walther spillede på xylofon." + - "⠟⠥⠊⠵⠙⠑⠇⠞⠁⠛⠑⠗⠝⠑ ⠎⠏⠊⠎⠞⠑ ⠚⠕⠗⠙⠃⠜⠗ ⠍⠑⠙ ⠋⠇⠪⠙⠑⠂ ⠍⠑⠝⠎ ⠉⠊⠗⠅⠥⠎⠅⠇⠕⠧⠝⠑⠝ ⠺⠁⠇⠞⠓⠑⠗ ⠎⠏⠊⠇⠇⠑⠙⠑ ⠏⠡ ⠭⠽⠇⠕⠋⠕⠝⠄" + +# Emphasis + + - # Bold line + - En linje med fed. + - ⠰⠑⠝ ⠇⠊⠝⠚⠑ ⠍⠑⠙ ⠋⠑⠙⠄⠰ + - typeform: + bold: '+++++++++++++++++' + - # Bold word + - Et ord med fed. + - ⠑⠞ ⠰⠕⠗⠙⠰ ⠍⠑⠙ ⠋⠑⠙⠄ + - typeform: + bold: ' +++ ' + - # Bold letter + - Et bogstav med fed. + - ⠑⠞ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍⠑⠙ ⠋⠑⠙⠄ + - typeform: + bold: ' + ' + + - # Italic line + - En linje med kursiv. + - ⠰⠑⠝ ⠇⠊⠝⠚⠑ ⠍⠑⠙ ⠅⠥⠗⠎⠊⠧⠄⠰ + - typeform: + italic: '++++++++++++++++++++' + - # Italic word + - Et ord med kursiv. + - ⠑⠞ ⠰⠕⠗⠙⠰ ⠍⠑⠙ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' +++ ' + - # Italic letter + - Et bogstav med kursiv. + - ⠑⠞ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍⠑⠙ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' + ' + + - # Underlined line + - En linje med understreget. + - ⠰⠑⠝ ⠇⠊⠝⠚⠑ ⠍⠑⠙ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄⠰ + - typeform: + underline: '++++++++++++++++++++++++++' + - # Underlined word + - Et ord med understreget. + - ⠑⠞ ⠰⠕⠗⠙⠰ ⠍⠑⠙ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄ + - typeform: + underline: ' +++ ' + - # Underlined letter + - Et bogstav med understreget. + - ⠑⠞ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍⠑⠙ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄ + - typeform: + underline: ' + ' + + - # Bold, italic and underlined line + - En linje med alt. + - ⠰⠑⠝ ⠇⠊⠝⠚⠑ ⠍⠑⠙ ⠁⠇⠞⠄⠰ + - typeform: + bold: '+++++++++++++++++' + italic: '+++++++++++++++++' + underline: '+++++++++++++++++' + - # Bold, italic and underlined word + - Et ord med alt. + - ⠑⠞ ⠰⠕⠗⠙⠰ ⠍⠑⠙ ⠁⠇⠞⠄ + - typeform: + bold: ' +++ ' + italic: ' +++ ' + underline: ' +++ ' + - # Bold, italic and underlined letter + - Et bogstav med alt. + - ⠑⠞ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍⠑⠙ ⠁⠇⠞⠄ + - typeform: + bold: ' + ' + italic: ' + ' + underline: ' + ' + +# Caps and mixed case + + - ["Foobar", "⠋⠕⠕⠃⠁⠗"] + - ["FOOBAR", "⠸⠋⠕⠕⠃⠁⠗"] + - ["FOObar", "⠸⠋⠕⠕⠠⠃⠁⠗"] + - ["FOO-barfOObar", "⠸⠋⠕⠕⠤⠃⠁⠗⠋⠸⠕⠕⠠⠃⠁⠗"] + +# Extra digits + + - ["1\u00bc + 1\u00bd = 2\u00be", "⠼⠁⠼⠁⠌⠙ ⠖⠼⠁⠼⠁⠌⠃ ⠶⠼⠃⠼⠉⠌⠙"] + +# Dashes + + - ["\u2014 \u0096 \u0097 \u00ad", "⠤⠤ ⠤⠤ ⠤⠤ ⠤⠤"] + +# Quotes + + - ["\u201e \u0084 \u201c \u0093 \u201d \u0094 \u00ab \u00bb", "⠶ ⠶ ⠶ ⠶ ⠶ ⠶ ⠶ ⠶"] + +# Apostrophes + + - ["` \u201a \u0082 \u2039 \u008b \u2018 \u0091 \u2019 \u0092 \u203a \u009b \u00b4", "⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈"] + +# Times vs. bullit + + - ["\u2022Bullit", "⠘⠄⠃⠥⠇⠇⠊⠞"] + - ["2 \u00d7 2 = 4", "⠼⠃ ⠄⠼⠃ ⠶⠼⠙"] + +# Section sign + + - ["§ 3", "⠬⠼⠉"] + - ["§ 3:", "⠬⠼⠉⠒"] + +# Numbers and punctuation + + - ["1,2", "⠼⠁⠂⠃"] + - ["123.456", "⠼⠁⠃⠉⠄⠙⠑⠋"] + - ["3-4", "⠼⠉⠤⠼⠙"] + - ["56-", "⠼⠑⠋⠤"] + - ["1/2", "⠼⠁⠌⠃"] + - ["12:34", "⠼⠁⠃⠒⠉⠙"] + - ["2^10", "⠼⠃⠘⠬⠁⠚"] + - ["2\u00d72", "⠼⠃⠘⠄⠼⠃"] + + - ["\"quotes\"", "⠶⠟⠥⠕⠞⠑⠎⠶"] + - ["-dashes-", "⠤⠙⠁⠎⠓⠑⠎⠤"] + - [":-) :-(", "⠒⠤⠴ ⠒⠤⠦"] + - [";-) ;-(", "⠆⠤⠴ ⠆⠤⠦"] + - ["---", "⠤⠤⠤"] + - ["-", "⠤⠤"] + - [" -", " ⠤⠤"] + - [" a-", " ⠁⠤"] + - [" - ", " ⠤⠤ "] + - [" -a-", " ⠤⠁⠤"] + - [" ", " "] + - ["(parentheses)", "⠦⠏⠁⠗⠑⠝⠞⠓⠑⠎⠑⠎⠴"] + - ["J) j) %) ') \u2030) \u0089) \u201a) \u0082) \u2039) \u009b) \u2018) \u0091) \u2019) \u0092) \u203a) \u009b)", "⠨⠚⠠⠴ ⠚⠠⠴ ⠚⠴⠠⠴ ⠈⠠⠴ ⠚⠴⠴⠠⠴ ⠚⠴⠴⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴"] + +# Digits and letters + + - ["1a", "⠼⠁⠠⠁"] + - ["1.,-a", "⠼⠁⠄⠂⠤⠠⠁"] + +# Multi-pass tests + + - ["~x |z", "⠘⠠⠭ ⠘⠸⠵"] + - ["~X |Z", "⠘⠠⠨⠭ ⠘⠸⠨⠵"] + - ["5É", "⠼⠑⠨⠐⠑"] + +# ------------------- +# Grade 1.5 (regular) +# ------------------- + +# Round trip tests +# Commented tests currently fail backwards but should be fixed. + +table: {language: da, grade: 1.5, dots: 6, direction: both, version: 1993, __assert-match: da-dk-g26l_1993.ctb} +flags: {testmode: bothDirections} +tests: + +# Characters + + - [" ", " "] + - ["\"", "⠶"] + - ["\u0023", "⠘⠼"] + - ["$", "⠘⠲"] + - ["%", "⠚⠴"] + - ["&", "⠠⠯"] + - ["'", "⠈"] + - ["(", "⠠⠦"] + - [")", "⠠⠴"] + - ["*", "⠠⠔"] + - ["+", "⠘⠖"] + - [",", "⠂"] + - ["-", "⠤⠤"] + - [".", "⠄"] +# - ["/", "⠌"] + - ["0", "⠼⠚"] + - ["1", "⠼⠁"] + - ["2", "⠼⠃"] + - ["3", "⠼⠉"] + - ["4", "⠼⠙"] + - ["5", "⠼⠑"] + - ["6", "⠼⠋"] + - ["7", "⠼⠛"] + - ["8", "⠼⠓"] + - ["9", "⠼⠊"] + - [":", "⠒"] + - [";", "⠆"] + - ["<", "⠘⠍"] + - ["=", "⠘⠶"] + - [">", "⠘⠎"] + - ["?", "⠢"] + - ["@", "⠘⠁"] + - ["A", "⠨⠠⠁"] + - ["B", "⠨⠠⠃"] + - ["C", "⠨⠠⠉"] + - ["D", "⠨⠠⠙"] + - ["E", "⠨⠠⠑"] + - ["F", "⠨⠠⠋"] + - ["G", "⠨⠠⠛"] + - ["H", "⠨⠠⠓"] + - ["I", "⠨⠊"] + - ["J", "⠨⠠⠚"] + - ["K", "⠨⠠⠅"] + - ["L", "⠨⠠⠇"] + - ["M", "⠨⠠⠍"] + - ["N", "⠨⠠⠝"] + - ["O", "⠨⠠⠕"] + - ["P", "⠨⠠⠏"] + - ["Q", "⠨⠠⠟"] + - ["R", "⠨⠠⠗"] + - ["S", "⠨⠠⠎"] + - ["T", "⠨⠠⠞"] + - ["U", "⠨⠠⠥"] + - ["V", "⠨⠠⠧"] + - ["W", "⠨⠠⠺"] + - ["X", "⠨⠠⠭"] + - ["Y", "⠨⠠⠽"] + - ["Z", "⠨⠠⠵"] + - ["[", "⠐⠦"] + - ["\u005c\u005c", "⠘⠡"] + - ["]", "⠐⠴"] + - ["^", "⠘⠬"] + - ["_", "⠘⠤"] + - ["a", "⠠⠁"] + - ["b", "⠠⠃"] + - ["c", "⠠⠉"] + - ["d", "⠠⠙"] + - ["e", "⠠⠑"] + - ["f", "⠠⠋"] + - ["g", "⠠⠛"] + - ["h", "⠠⠓"] + - ["i", "⠊"] + - ["j", "⠠⠚"] + - ["k", "⠠⠅"] + - ["l", "⠠⠇"] + - ["m", "⠠⠍"] + - ["n", "⠠⠝"] + - ["o", "⠠⠕"] + - ["p", "⠠⠏"] + - ["q", "⠠⠟"] + - ["r", "⠠⠗"] + - ["s", "⠠⠎"] + - ["t", "⠠⠞"] + - ["u", "⠠⠥"] + - ["v", "⠠⠧"] + - ["w", "⠠⠺"] + - ["x", "⠠⠭"] + - ["y", "⠠⠽"] + - ["z", "⠠⠵"] + - ["{", "⠘⠪"] + - ["|", "⠘⠸"] + - ["}", "⠘⠕"] + - ["~", "⠘⠠"] + - ["€", "⠘⠑"] + - ["ƒ", "⠘⠋"] + - ["‰", "⠚⠴⠴"] + - ["Š", "⠨⠐⠎"] + - ["Ž", "⠨⠐⠵"] + - ["•", "⠘⠄"] + - ["™", "⠘⠞"] + - ["š", "⠐⠎"] + - ["ž", "⠐⠵"] + - ["¡", "⠠⠲"] + - ["¢", "⠘⠒"] + - ["£", "⠘⠇"] + - ["¥", "⠘⠽"] + - ["©", "⠘⠉"] + - ["®", "⠘⠗"] + - ["°", "⠈⠴"] + - ["µ", "⠐⠍"] + - ["À", "⠨⠐⠁"] + - ["Å", "⠨⠠⠡"] + - ["Æ", "⠨⠠⠜"] + - ["Ç", "⠨⠐⠉"] + - ["É", "⠨⠐⠑"] + - ["Î", "⠨⠐⠊"] + - ["Ð", "⠨⠐⠙"] + - ["Ñ", "⠨⠐⠝"] + - ["Ô", "⠨⠐⠕"] + - ["Ø", "⠨⠠⠪"] + - ["Û", "⠨⠐⠥"] + - ["Ü", "⠨⠠⠳"] + - ["Ý", "⠨⠐⠽"] + - ["Þ", "⠨⠐⠞"] + - ["à", "⠐⠁"] + - ["å", "⠠⠡"] + - ["æ", "⠠⠜"] + - ["ç", "⠐⠉"] + - ["é", "⠐⠑"] + - ["î", "⠐⠊"] + - ["ð", "⠐⠙"] + - ["ñ", "⠐⠝"] + - ["ô", "⠐⠕"] + - ["ø", "⠠⠪"] + - ["û", "⠐⠥"] + - ["ü", "⠠⠳"] + - ["ý", "⠐⠽"] + - ["þ", "⠐⠞"] + +# Misc. Unicode (most cannot be back-translated) +# Some tests may be repetitions of tests above, since +# some characters can occur both inside and outside the 8 bit range. +# for accented letters in the range u+0080 - u+00ff: the letters +# that are thought to occur most frequently in Danish texts are used +# for back-translation. +# For all accented letters above u+00ff, the first occurring instance is chosen for back-translation. +# There are no rules about this in the specs of Danish Braille, +# So the choice is arbitrary and may change over time. + + - ["\u0134\u0135", "⠨⠐⠚⠐⠚"] + - ["\u0138", "⠐⠟"] + - ["\u0192", "⠘⠋"] + +# Greek letters (with dot 5 as prefix) + + - ["\u0392\u03b2", "⠨⠐⠃⠐⠃"] + - ["\u0393\u03b3", "⠨⠐⠛⠐⠛"] + - ["\u0397\u03b7", "⠨⠐⠱⠐⠱"] + - ["\u0398\u03b8", "⠨⠐⠹⠐⠹"] + - ["\u039a\u03ba", "⠨⠐⠅⠐⠅"] + - ["\u039b\u03bb", "⠨⠐⠇⠐⠇"] + - ["\u039e\u03be", "⠨⠐⠭⠐⠭"] + - ["\u03a0\u03c0", "⠨⠐⠏⠐⠏"] + - ["\u03a1\u03c1", "⠨⠐⠗⠐⠗"] + - ["\u03a6\u03c6", "⠨⠐⠋⠐⠋"] + - ["\u03a7\u03c7", "⠨⠐⠓⠐⠓"] + - ["\u03a9\u03c9", "⠨⠐⠺⠐⠺"] + +# Arrows (only a few in 6 dots) + + - ["\u2190", "⠘⠺"] + - ["\u2191", "⠘⠷"] + - ["\u2193", "⠘⠟"] + +# Pangram + + - - "Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Walther spillede på xylofon." + - "⠨⠠⠟⠥⠊⠠⠵⠙⠑⠇⠞⠁⠛⠑⠗⠝⠑ ⠎⠏⠊⠎⠞⠑ ⠚⠕⠗⠙⠃⠜⠗ ⠍ ⠋⠇⠪⠙⠑⠂ ⠍⠑⠝⠎ ⠉⠊⠗⠅⠥⠎⠅⠇⠕⠧⠝⠑⠝ ⠨⠠⠺⠁⠇⠞⠓⠑⠗ ⠎⠏⠊⠇⠇⠑⠙⠑ ⠏ ⠠⠭⠽⠇⠕⠋⠕⠝⠄" + +# Caps and mixed case + + - ["Foobar", "⠨⠋⠕⠕⠃⠁⠗"] + - ["FOOBAR", "⠸⠋⠕⠕⠃⠁⠗"] + - ["FOObar", "⠸⠋⠕⠕⠠⠃⠁⠗"] + - ["FOO-barfOObar", "⠸⠋⠕⠕⠤⠃⠁⠗⠋⠸⠕⠕⠠⠃⠁⠗"] + +# Percent and permille + + - ["1%", "⠼⠁ ⠚⠴"] + +# Times vs. bullit + + - ["\u2022Bullit", "⠘⠄⠨⠃⠥⠇⠇⠊⠞"] +# - ["2 \u00d7 2 = 4", "⠼⠃ ⠘⠄ ⠼⠃ ⠘⠶ ⠼⠙"] + +# Section sign + + - ["§ 3", "⠬⠼⠉"] + - ["§ 3:", "⠬⠼⠉⠒"] + +# Numbers and punctuation + + - ["1,2", "⠼⠁⠂⠃"] + - ["123.456", "⠼⠁⠃⠉⠄⠙⠑⠋"] + - ["3-4", "⠼⠉⠤⠼⠙"] + - ["56-", "⠼⠑⠋⠤"] + - ["1/2", "⠼⠁⠌⠃"] + - ["12:34", "⠼⠁⠃⠒⠉⠙"] + - ["2^10", "⠼⠃⠘⠬⠁⠚"] +# - ["2\u00d72", "⠼⠃⠘⠄⠼⠃"] + + - ["\"quotes\"", "⠶⠠⠟⠥⠕⠞⠑⠎⠶"] + - ["-dashes-", "⠤⠙⠁⠎⠓⠑⠎⠤"] + - [":-) :-(", "⠒⠤⠠⠴ ⠒⠤⠠⠦"] + - [";-) ;-(", "⠆⠤⠠⠴ ⠆⠤⠠⠦"] + - ["---", "⠤⠤⠤"] + - ["-", "⠤⠤"] +# - [" -", " ⠤⠤"] + - [" a-", " ⠠⠁⠤"] + - [" - ", " ⠤⠤ "] + - [" -a-", " ⠤⠠⠁⠤"] + - [" ", " "] + - ["(parentheses)", "⠦⠏⠁⠗⠑⠝⠞⠓⠑⠎⠑⠎⠴"] + +# Exclamation + + - ["\u00a1Que lastima!", "⠠⠲⠨⠠⠟⠥⠑ ⠇⠁⠎⠞⠊⠍⠁⠖"] + +# Digits and letters + + - ["1a", "⠼⠁⠠⠁"] + - ["1.,-a", "⠼⠁⠄⠂⠤⠠⠁"] + +# URLs emails and file names + + - ["$at", "⠘⠲⠁⠞"] + - ["\u005c\u005cat\u005c\u005cbliver", "⠘⠡⠁⠞⠘⠡⠃⠇⠊⠧⠑⠗"] + - ["at@bliver.og", "⠁⠞⠘⠁⠃⠇⠊⠧⠑⠗⠄⠕⠛"] + - ["http://at.bliver.og", "⠓⠞⠞⠏⠒⠌⠌⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠕⠛"] + - ["www.at.bliver.og", "⠠⠺⠠⠺⠠⠺⠄⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠕⠛"] + - ["www.at.bliver.com", "⠠⠺⠠⠺⠠⠺⠄⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠉⠕⠍"] + - ["www.a.b.c", "⠠⠺⠠⠺⠠⠺⠄⠠⠁⠄⠠⠃⠄⠠⠉"] + - ["test.txt", "⠞⠑⠎⠞⠄⠞⠠⠭⠞"] + +# Word contractions + + - ["At", "⠨⠁"] + - ["at", "⠁"] + - ["Bliver", "⠨⠃"] + - ["bliver", "⠃"] + - ["Og", "⠨⠉"] + - ["og", "⠉"] + - ["Du", "⠨⠙"] + - ["du", "⠙"] + - ["Eller", "⠨⠑"] + - ["eller", "⠑"] + - ["For", "⠨⠋"] + - ["for", "⠋"] + - ["Gør", "⠨⠛"] + - ["gør", "⠛"] + - ["Har", "⠨⠓"] + - ["har", "⠓"] + - ["Jeg", "⠨⠚"] + - ["jeg", "⠚"] + - ["Kan", "⠨⠅"] + - ["kan", "⠅"] + - ["Lige", "⠨⠇"] + - ["lige", "⠇"] + - ["Med", "⠨⠍"] + - ["med", "⠍"] + - ["Når", "⠨⠝"] + - ["når", "⠝"] + - ["Op", "⠨⠕"] + - ["op", "⠕"] + - ["På", "⠨⠏"] + - ["på", "⠏"] + - ["Under", "⠨⠟"] + - ["under", "⠟"] + - ["Rigtig", "⠨⠗"] + - ["rigtig", "⠗"] + - ["Som", "⠨⠎"] + - ["som", "⠎"] + - ["Til", "⠨⠞"] + - ["til", "⠞"] + - ["Hun", "⠨⠥"] + - ["hun", "⠥"] + - ["Ved", "⠨⠧"] + - ["ved", "⠧"] + - ["Hvad", "⠨⠺"] + - ["hvad", "⠺"] + - ["Over", "⠨⠭"] + - ["over", "⠭"] + - ["Han", "⠨⠽"] + - ["han", "⠽"] + - ["Efter", "⠨⠵"] + - ["efter", "⠵"] + - ["Være", "⠨⠜"] + - ["være", "⠜"] + - ["Før", "⠨⠪"] + - ["før", "⠪"] + - ["Så", "⠨⠡"] + - ["så", "⠡"] + - ["Den", "⠨⠯"] + - ["den", "⠯"] + - ["Der", "⠨⠾"] + - ["der", "⠾"] + - ["Det", "⠨⠮"] + - ["det", "⠮"] + - ["De", "⠨⠹"] + - ["de", "⠹"] + - ["En", "⠨⠣"] + - ["en", "⠣"] + - ["Er", "⠨⠱"] + - ["er", "⠱"] + - ["Et", "⠨⠬"] + - ["et", "⠬"] + - ["Gennem", "⠨⠻"] + - ["gennem", "⠻"] + - ["Hvor", "⠨⠌"] + - ["hvor", "⠌"] + - ["Men", "⠨⠩"] + - ["men", "⠩"] + - ["Ned", "⠨⠫"] + - ["ned", "⠫"] + - ["Ret", "⠨⠷"] + - ["ret", "⠷"] + - ["Skal", "⠨⠿"] + - ["skal", "⠿"] + - ["Te", "⠨⠳"] + - ["te", "⠳"] + - ["Ve", "⠨⠼"] + - ["ve", "⠼"] + +# No single cell contractions before or after dashes + + - ["at-bliver", "⠁⠞⠤⠃"] + - ["d-d-du", "⠠⠙⠤⠠⠙⠤⠙⠥"] + +# Combinations with slashes and other punctuation signs + + - ["Han/hun", "⠨⠽⠌⠥"] + - ["han/hun", "⠽⠌⠥"] + - ["Over/under", "⠨⠭⠌⠟"] + - ["over/under", "⠭⠌⠟"] + - ["Til/fra", "⠨⠞⠌⠖"] + - ["til/fra", "⠞⠌⠖"] + +# Combinations which require letsign + + - ["1st", "⠼⠁⠠⠎⠞"] + - ["2nd", "⠼⠃⠠⠝⠙"] + - ["1A", "⠼⠁⠨⠠⠁", {xfail: {forward: true}}] # unclear spec + - ["1a", "⠼⠁⠠⠁"] + - ["2B", "⠼⠃⠨⠠⠃", {xfail: {forward: true}}] # unclear spec + - ["2b", "⠼⠃⠠⠃"] + +# Multi-pass tests + + - ["~x |z", "⠘⠠⠠⠭ ⠘⠸⠠⠵"] + - ["~X |Z", "⠘⠠⠨⠠⠭ ⠘⠸⠨⠠⠵"] + - ["5É", "⠼⠑⠨⠐⠑"] + +# Characters and constructs which cannot be properly back-translated + +flags: {testmode: forward} +tests: + - ["`", "⠈"] + - ["‚", "⠈"] + - ["„", "⠶"] + - ["…", "⠄⠄⠄"] + - ["‹", "⠈"] + - ["Œ", "⠨⠕⠑"] + - ["‘", "⠈"] + - ["’", "⠈"] + - ["“", "⠶"] + - ["”", "⠶"] + - ["–", "⠤⠤"] + - ["—", "⠤⠤"] + - ["˜", "⠘⠠"] + - ["›", "⠈"] + - ["œ", "⠕⠑"] + - ["Ÿ", "⠨⠐⠽"] + - ["§", "⠬"] + - ["«", "⠶"] + - ["±", "⠘⠖⠤"] + - ["²", "⠼⠬⠃"] + - ["³", "⠼⠬⠉"] + - ["´", "⠈"] + - ["¹", "⠼⠬⠁"] + - ["»", "⠶"] + - ["¼", "⠼⠁⠌⠙"] + - ["½", "⠼⠁⠌⠃"] + - ["¾", "⠼⠉⠌⠙"] + - ["Á", "⠨⠐⠁"] + - ["Â", "⠨⠐⠁"] + - ["Ã", "⠨⠐⠁"] + - ["Ä", "⠨⠠⠜"] + - ["È", "⠨⠐⠑"] + - ["Ê", "⠨⠐⠑"] + - ["Ë", "⠨⠐⠑"] + - ["Ì", "⠨⠐⠊"] + - ["Í", "⠨⠐⠊"] + - ["Ï", "⠨⠐⠊"] + - ["Ò", "⠨⠐⠕"] + - ["Ó", "⠨⠐⠕"] + - ["Õ", "⠨⠐⠕"] + - ["Ö", "⠨⠠⠪"] + - ["×", "⠘⠄"] + - ["Ù", "⠨⠐⠥"] + - ["Ú", "⠨⠐⠥"] + - ["ß", "⠎⠎"] + - ["á", "⠐⠁"] + - ["â", "⠐⠁"] + - ["ã", "⠐⠁"] + - ["ä", "⠠⠜"] + - ["è", "⠐⠑"] + - ["ê", "⠐⠑"] + - ["ë", "⠐⠑"] + - ["ì", "⠐⠊"] + - ["í", "⠐⠊"] + - ["ï", "⠐⠊"] + - ["ò", "⠐⠕"] + - ["ó", "⠐⠕"] + - ["õ", "⠐⠕"] + - ["ö", "⠠⠪"] + - ["÷", "⠘⠲"] + - ["ù", "⠐⠥"] + - ["ú", "⠐⠥"] + - ["ÿ", "⠐⠽"] + +# Latin Extended-A + + - ["\u0100\u0101", "⠨⠐⠁⠐⠁"] + - ["\u0102\u0103", "⠨⠐⠁⠐⠁"] + - ["\u0104\u0105", "⠨⠐⠁⠐⠁"] + - ["\u0106\u0107", "⠨⠐⠉⠐⠉"] + - ["\u0108\u0109", "⠨⠐⠉⠐⠉"] + - ["\u010a\u010b", "⠨⠐⠉⠐⠉"] + - ["\u010c\u010d", "⠨⠐⠉⠐⠉"] + - ["\u010e\u010f", "⠨⠐⠙⠐⠙"] + - ["\u0110\u0111", "⠨⠐⠙⠐⠙"] + - ["\u0112\u0113", "⠨⠐⠑⠐⠑"] + - ["\u0114\u0115", "⠨⠐⠑⠐⠑"] + - ["\u0116\u0117", "⠨⠐⠑⠐⠑"] + - ["\u0118\u0119", "⠨⠐⠑⠐⠑"] + - ["\u011a\u011b", "⠨⠐⠑⠐⠑"] + - ["\u011c\u011d", "⠨⠐⠛⠐⠛"] + - ["\u011e\u011f", "⠨⠐⠛⠐⠛"] + - ["\u0120\u0121", "⠨⠐⠛⠐⠛"] + - ["\u0122\u0123", "⠨⠐⠛⠐⠛"] + - ["\u0124\u0125", "⠨⠐⠓⠐⠓"] + - ["\u0126\u0127", "⠨⠐⠓⠐⠓"] + - ["\u0128\u0129", "⠨⠐⠊⠐⠊"] + - ["\u012a\u012b", "⠨⠐⠊⠐⠊"] + - ["\u012c\u012d", "⠨⠐⠊⠐⠊"] + - ["\u012e\u012f", "⠨⠐⠊⠐⠊"] + - ["\u0130\u0131", "⠨⠐⠊⠐⠊"] + - ["\u0132\u0133", "⠨⠊⠚⠊⠚"] + - ["\u0136\u0137", "⠨⠐⠅⠐⠅"] + - ["\u0139\u013a", "⠨⠐⠇⠐⠇"] + - ["\u013b\u013c", "⠨⠐⠇⠐⠇"] + - ["\u013d\u013e", "⠨⠐⠇⠐⠇"] + - ["\u013f\u0140", "⠨⠐⠇⠐⠇"] + - ["\u0141\u0142", "⠨⠐⠇⠐⠇"] + - ["\u0143\u0144", "⠨⠐⠝⠐⠝"] + - ["\u0145\u0146", "⠨⠐⠝⠐⠝"] + - ["\u0147\u0148", "⠨⠐⠝⠐⠝"] + - ["\u0149", "⠈⠝"] + - ["\u014a\u014b", "⠨⠐⠝⠐⠝"] + - ["\u014c\u014d", "⠨⠐⠕⠐⠕"] + - ["\u014e\u014f", "⠨⠐⠕⠐⠕"] + - ["\u0150\u0151", "⠨⠐⠕⠐⠕"] + - ["\u0152\u0153", "⠨⠕⠑⠕⠑"] + - ["\u0154\u0155", "⠨⠐⠗⠐⠗"] + - ["\u0156\u0157", "⠨⠐⠗⠐⠗"] + - ["\u0158\u0159", "⠨⠐⠗⠐⠗"] + - ["\u015a\u015b", "⠨⠐⠎⠐⠎"] + - ["\u015c\u015d", "⠨⠐⠎⠐⠎"] + - ["\u015e\u015f", "⠨⠐⠎⠐⠎"] + - ["\u0160\u0161", "⠨⠐⠎⠐⠎"] + - ["\u0162\u0163", "⠨⠐⠞⠐⠞"] + - ["\u0164\u0165", "⠨⠐⠞⠐⠞"] + - ["\u0166\u0167", "⠨⠐⠞⠐⠞"] + - ["\u0168\u0169", "⠨⠐⠥⠐⠥"] + - ["\u016a\u016b", "⠨⠐⠥⠐⠥"] + - ["\u016c\u016d", "⠨⠐⠥⠐⠥"] + - ["\u016e\u016f", "⠨⠐⠥⠐⠥"] + - ["\u0170\u0171", "⠨⠐⠥⠐⠥"] + - ["\u0172\u0173", "⠨⠐⠥⠐⠥"] + - ["\u0174\u0175", "⠨⠐⠺⠐⠺"] + - ["\u0176\u0177", "⠨⠐⠽⠐⠽"] + - ["\u0178\u00ff", "⠨⠐⠽⠐⠽"] + - ["\u0179\u017a", "⠨⠐⠵⠐⠵"] + - ["\u017b\u017c", "⠨⠐⠵⠐⠵"] + - ["\u017d\u017e", "⠨⠐⠵⠐⠵"] + - ["\u017f", "⠐⠎"] + +# Latin Extended-B + + - ["\u02dc", "⠘⠠"] + +# Greek letters (with dot 5 as prefix) + + - ["\u0386\u03ac", "⠨⠐⠁⠐⠁"] + - ["\u0388\u03ad", "⠨⠐⠑⠐⠑"] + - ["\u0389\u03ae", "⠨⠐⠱⠐⠱"] + - ["\u038a\u03af", "⠨⠐⠊⠐⠊"] + - ["\u038c\u03cc", "⠨⠐⠕⠐⠕"] + - ["\u038e\u03cd", "⠨⠐⠥⠐⠥"] + - ["\u038f\u03ce", "⠨⠐⠺⠐⠺"] + - ["\u0391\u03b1", "⠨⠐⠁⠐⠁"] + - ["\u0394\u03b4", "⠨⠐⠙⠐⠙"] + - ["\u0395\u03b5", "⠨⠐⠑⠐⠑"] + - ["\u0396\u03b6", "⠨⠐⠵⠐⠵"] + - ["\u0399\u03b9", "⠨⠐⠊⠐⠊"] + - ["\u039c\u03bc", "⠨⠐⠍⠐⠍"] + - ["\u039d\u03bd", "⠨⠐⠝⠐⠝"] + - ["\u039f\u03bf", "⠨⠐⠕⠐⠕"] + - ["\u03a3\u03c3", "⠨⠐⠎⠐⠎"] + - ["\u03a4\u03c4", "⠨⠐⠞⠐⠞"] + - ["\u03a5\u03c5", "⠨⠐⠥⠐⠥"] + - ["\u03a8\u03c8", "⠨⠐⠽⠐⠽"] + +# Punctuation and bullits + + - ["\u2000", " "] + - ["\u2001", " "] + - ["\u2002", " "] + - ["\u2003", " "] + - ["\u2004", " "] + - ["\u2005", " "] + - ["\u2006", " "] + - ["\u2007", " "] + - ["\u2008", " "] + - ["\u2009", " "] + - ["\u200a", " "] + - ["\u2010", "⠤"] + - ["\u2011", "⠤"] + - ["\u2012", "⠤"] + - ["\u2013", "⠤⠤"] + - ["\u2014", "⠤⠤"] + - ["\u2018", "⠈"] + - ["\u2019", "⠈"] + - ["\u201a", "⠈"] + - ["\u201b", "⠈"] + - ["\u201c", "⠶"] + - ["\u201d", "⠶"] + - ["\u201e", "⠶"] + - ["\u201f", "⠶"] + - ["\u2023", "⠘⠄"] + - ["\u2026", "⠄⠄⠄"] + - ["\u202f", " "] + - ["\u2030", "⠚⠴⠴"] + - ["\u2039", "⠈"] + - ["\u203a", "⠈"] + - ["\u203c", "⠖⠖"] + - ["\u203d", "⠢⠖"] + - ["\u2043", "⠘⠄"] + - ["\u2047", "⠢⠢"] + - ["\u2048", "⠢⠖"] + - ["\u2049", "⠖⠢"] + - ["\u204c", "⠘⠄"] + - ["\u204d", "⠘⠄"] + - ["\u20ac", "⠘⠑"] + - ["\u2122", "⠘⠞"] + +# Arrows (only a few in 6 dots) + + - ["\u2192", "⠘⠗"] # back-translates as "registered". + +# Geometrical shapes + + - ["\u25e6", "⠘⠄"] + +# Extra digits + + - ["1\u00bc + 1\u00bd = 2\u00be", "⠼⠁⠼⠁⠌⠙ ⠘⠖ ⠼⠁⠼⠁⠌⠃ ⠘⠶ ⠼⠃⠼⠉⠌⠙"] + +# Dashes + + - ["\u2014 \u0096 \u0097 \u00ad", "⠤⠤ ⠤⠤ ⠤⠤ ⠤⠤"] + +# Quotes + + - ["\u201e \u0084 \u201c \u0093 \u201d \u0094 \u00ab \u00bb", "⠶ ⠶ ⠶ ⠶ ⠶ ⠶ ⠶ ⠶"] + +# Apostrophes + + - ["` \u201a \u0082 \u2039 \u008b \u2018 \u0091 \u2019 \u0092 \u203a \u009b \u00b4", "⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈"] + - ["J) j) %) ') \u2030) \u0089) \u201a) \u0082) \u2039) \u009b) \u2018) \u0091) \u2019) \u0092) \u203a) \u009b)", "⠨⠠⠚⠠⠴ ⠠⠚⠠⠴ ⠚⠴⠠⠴ ⠈⠠⠴ ⠚⠴⠴⠠⠴ ⠚⠴⠴⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴"] + +# Emphasis + + - # Bold line + - En linje med fed. + - ⠰⠨⠣ ⠇⠊⠝⠚⠑ ⠍ ⠋⠑⠙⠄⠰ + - typeform: + bold: '+++++++++++++++++' + - # Bold word + - Et ord med fed. + - ⠨⠬ ⠰⠕⠗⠙⠰ ⠍ ⠋⠑⠙⠄ + - typeform: + bold: ' +++ ' + - # Bold letter + - Et bogstav med fed. + - ⠨⠬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠋⠑⠙⠄ + - typeform: + bold: ' + ' + + - # Italic line + - En linje med kursiv. + - ⠰⠨⠣ ⠇⠊⠝⠚⠑ ⠍ ⠅⠥⠗⠎⠊⠧⠄⠰ + - typeform: + italic: '++++++++++++++++++++' + - # Italic word + - Et ord med kursiv. + - ⠨⠬ ⠰⠕⠗⠙⠰ ⠍ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' +++ ' + - # Italic letter + - Et bogstav med kursiv. + - ⠨⠬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' + ' + + - # Underlined line + - En linje med understreget. + - ⠰⠨⠣ ⠇⠊⠝⠚⠑ ⠍ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄⠰ + - typeform: + underline: '++++++++++++++++++++++++++' + - # Underlined word + - Et ord med understreget. + - ⠨⠬ ⠰⠕⠗⠙⠰ ⠍ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄ + - typeform: + underline: ' +++ ' + - # Underlined letter + - Et bogstav med understreget. + - ⠨⠬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄ + - typeform: + underline: ' + ' + + - # Bold, italic and underlined line + - En linje med alt. + - ⠰⠨⠣ ⠇⠊⠝⠚⠑ ⠍ ⠁⠇⠞⠄⠰ + - typeform: + bold: '+++++++++++++++++' + italic: '+++++++++++++++++' + underline: '+++++++++++++++++' + - # Bold, italic and underlined word + - Et ord med alt. + - ⠨⠬ ⠰⠕⠗⠙⠰ ⠍ ⠁⠇⠞⠄ + - typeform: + bold: ' +++ ' + italic: ' +++ ' + underline: ' +++ ' + - # Bold, italic and underlined letter + - Et bogstav med alt. + - ⠨⠬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠁⠇⠞⠄ + - typeform: + bold: ' + ' + italic: ' + ' + underline: ' + ' + +flags: {testmode: backward} +tests: + +# Characters + + - ["⠄⠄⠄", "..."] + - ["⠨⠕⠑", "Oe"] + - ["⠕⠑", "oe"] + - ["⠘⠖⠤", "±", {xfail: true}] + - ["⠼⠁⠌⠙", "1/4"] + - ["⠼⠁⠌⠃", "1/2"] + - ["⠼⠉⠌⠙", "3/4"] + +# Extra digits + + - ["⠼⠁⠼⠁⠌⠙ ⠘⠖ ⠼⠁⠼⠁⠌⠃ ⠘⠶ ⠼⠃⠼⠉⠌⠙", "1\u00bc + 1\u00bd = 2\u00be", {xfail: true}] + +# --------------------------------- +# Grade 1.5 literary (forward only) +# --------------------------------- + +table: {language: da, grade: 1.5, dots: 6, direction: forward, version: 1993, __assert-match: da-dk-g26l-lit_1993.ctb} +flags: {testmode: forward} +tests: + +# Characters + + - [" ", " "] + - ["\"", "⠶"] + - ["\u0023", "⠘⠼"] + - ["$", "⠘⠲"] + - ["%", "⠚⠴"] + - ["&", "⠠⠯"] + - ["'", "⠈"] + - ["(", "⠠⠦"] + - [")", "⠠⠴"] + - ["*", "⠠⠔"] + - ["+", "⠘⠖"] + - [",", "⠂"] + - ["-", "⠤⠤"] + - [".", "⠄"] + - ["/", "⠌"] + - ["0", "⠼⠚"] + - ["1", "⠼⠁"] + - ["2", "⠼⠃"] + - ["3", "⠼⠉"] + - ["4", "⠼⠙"] + - ["5", "⠼⠑"] + - ["6", "⠼⠋"] + - ["7", "⠼⠛"] + - ["8", "⠼⠓"] + - ["9", "⠼⠊"] + - [":", "⠒"] + - [";", "⠆"] + - ["<", "⠘⠍"] + - ["=", "⠘⠶"] + - [">", "⠘⠎"] + - ["?", "⠢"] + - ["@", "⠘⠁"] + - ["A", "⠨⠠⠁"] + - ["B", "⠨⠠⠃"] + - ["C", "⠨⠠⠉"] + - ["D", "⠨⠠⠙"] + - ["E", "⠨⠠⠑"] + - ["F", "⠨⠠⠋"] + - ["G", "⠨⠠⠛"] + - ["H", "⠨⠠⠓"] + - ["I", "⠨⠊"] + - ["J", "⠨⠠⠚"] + - ["K", "⠨⠠⠅"] + - ["L", "⠨⠠⠇"] + - ["M", "⠨⠠⠍"] + - ["N", "⠨⠠⠝"] + - ["O", "⠨⠠⠕"] + - ["P", "⠨⠠⠏"] + - ["Q", "⠨⠠⠟"] + - ["R", "⠨⠠⠗"] + - ["S", "⠨⠠⠎"] + - ["T", "⠨⠠⠞"] + - ["U", "⠨⠠⠥"] + - ["V", "⠨⠠⠧"] + - ["W", "⠨⠠⠺"] + - ["X", "⠨⠠⠭"] + - ["Y", "⠨⠠⠽"] + - ["Z", "⠨⠠⠵"] + - ["[", "⠐⠦"] + - ["\u005c\u005c", "⠘⠡"] + - ["]", "⠐⠴"] + - ["^", "⠘⠬"] + - ["_", "⠘⠤"] + - ["`", "⠈"] + - ["a", "⠠⠁"] + - ["b", "⠠⠃"] + - ["c", "⠠⠉"] + - ["d", "⠠⠙"] + - ["e", "⠠⠑"] + - ["f", "⠠⠋"] + - ["g", "⠠⠛"] + - ["h", "⠠⠓"] + - ["i", "⠊"] + - ["j", "⠠⠚"] + - ["k", "⠠⠅"] + - ["l", "⠠⠇"] + - ["m", "⠠⠍"] + - ["n", "⠠⠝"] + - ["o", "⠠⠕"] + - ["p", "⠠⠏"] + - ["q", "⠠⠟"] + - ["r", "⠠⠗"] + - ["s", "⠠⠎"] + - ["t", "⠠⠞"] + - ["u", "⠠⠥"] + - ["v", "⠠⠧"] + - ["w", "⠠⠺"] + - ["x", "⠠⠭"] + - ["y", "⠠⠽"] + - ["z", "⠠⠵"] + - ["{", "⠘⠪"] + - ["|", "⠘⠸"] + - ["}", "⠘⠕"] + - ["~", "⠘⠠"] + - ["€", "⠘⠑"] + - ["‚", "⠈"] + - ["ƒ", "⠘⠋"] + - ["„", "⠶"] + - ["…", "⠄⠄⠄"] + - ["‰", "⠚⠴⠴"] + - ["Š", "⠨⠐⠎"] + - ["‹", "⠈"] + - ["Œ", "⠨⠕⠑"] + - ["Ž", "⠨⠐⠵"] + - ["‘", "⠈"] + - ["’", "⠈"] + - ["“", "⠶"] + - ["”", "⠶"] + - ["•", "⠘⠄"] + - ["–", "⠤⠤"] + - ["—", "⠤⠤"] + - ["˜", "⠘⠠"] + - ["™", "⠘⠞"] + - ["š", "⠐⠎"] + - ["›", "⠈"] + - ["œ", "⠕⠑"] + - ["ž", "⠐⠵"] + - ["Ÿ", "⠨⠐⠽"] + - ["¡", "⠠⠲"] + - ["¢", "⠘⠒"] + - ["£", "⠘⠇"] + - ["¥", "⠘⠽"] + - ["§", "⠬"] + - ["©", "⠘⠉"] + - ["«", "⠶"] + - ["®", "⠘⠗"] + - ["°", "⠈⠴"] + - ["±", "⠘⠖⠤"] + - ["²", "⠼⠬⠃"] + - ["³", "⠼⠬⠉"] + - ["´", "⠈"] + - ["µ", "⠐⠍"] + - ["¹", "⠼⠬⠁"] + - ["»", "⠶"] + - ["¼", "⠼⠁⠌⠙"] + - ["½", "⠼⠁⠌⠃"] + - ["¾", "⠼⠉⠌⠙"] + - ["À", "⠨⠐⠁"] + - ["Á", "⠨⠐⠁"] + - ["Â", "⠨⠐⠁"] + - ["Ã", "⠨⠐⠁"] + - ["Ä", "⠨⠠⠜"] + - ["Å", "⠨⠠⠡"] + - ["Æ", "⠨⠠⠜"] + - ["Ç", "⠨⠐⠉"] + - ["È", "⠨⠐⠑"] + - ["É", "⠨⠐⠑"] + - ["Ê", "⠨⠐⠑"] + - ["Ë", "⠨⠐⠑"] + - ["Ì", "⠨⠐⠊"] + - ["Í", "⠨⠐⠊"] + - ["Î", "⠨⠐⠊"] + - ["Ï", "⠨⠐⠊"] + - ["Ð", "⠨⠐⠙"] + - ["Ñ", "⠨⠐⠝"] + - ["Ò", "⠨⠐⠕"] + - ["Ó", "⠨⠐⠕"] + - ["Ô", "⠨⠐⠕"] + - ["Õ", "⠨⠐⠕"] + - ["Ö", "⠨⠠⠪"] + - ["×", "⠘⠄"] + - ["Ø", "⠨⠠⠪"] + - ["Ù", "⠨⠐⠥"] + - ["Ú", "⠨⠐⠥"] + - ["Û", "⠨⠐⠥"] + - ["Ü", "⠨⠠⠳"] + - ["Ý", "⠨⠐⠽"] + - ["Þ", "⠨⠐⠞"] + - ["ß", "⠎⠎"] + - ["à", "⠐⠁"] + - ["á", "⠐⠁"] + - ["â", "⠐⠁"] + - ["ã", "⠐⠁"] + - ["ä", "⠠⠜"] + - ["å", "⠠⠡"] + - ["æ", "⠠⠜"] + - ["ç", "⠐⠉"] + - ["è", "⠐⠑"] + - ["é", "⠐⠑"] + - ["ê", "⠐⠑"] + - ["ë", "⠐⠑"] + - ["ì", "⠐⠊"] + - ["í", "⠐⠊"] + - ["î", "⠐⠊"] + - ["ï", "⠐⠊"] + - ["ð", "⠐⠙"] + - ["ñ", "⠐⠝"] + - ["ò", "⠐⠕"] + - ["ó", "⠐⠕"] + - ["ô", "⠐⠕"] + - ["õ", "⠐⠕"] + - ["ö", "⠠⠪"] + - ["÷", "⠘⠲"] + - ["ø", "⠠⠪"] + - ["ù", "⠐⠥"] + - ["ú", "⠐⠥"] + - ["û", "⠐⠥"] + - ["ü", "⠠⠳"] + - ["ý", "⠐⠽"] + - ["þ", "⠐⠞"] + - ["ÿ", "⠐⠽"] + +# Latin Extended-A + + - ["\u0100\u0101", "⠐⠁⠐⠁"] + - ["\u0102\u0103", "⠐⠁⠐⠁"] + - ["\u0104\u0105", "⠐⠁⠐⠁"] + - ["\u0106\u0107", "⠐⠉⠐⠉"] + - ["\u0108\u0109", "⠐⠉⠐⠉"] + - ["\u010a\u010b", "⠐⠉⠐⠉"] + - ["\u010c\u010d", "⠐⠉⠐⠉"] + - ["\u010e\u010f", "⠐⠙⠐⠙"] + - ["\u0110\u0111", "⠐⠙⠐⠙"] + - ["\u0112\u0113", "⠐⠑⠐⠑"] + - ["\u0114\u0115", "⠐⠑⠐⠑"] + - ["\u0116\u0117", "⠐⠑⠐⠑"] + - ["\u0118\u0119", "⠐⠑⠐⠑"] + - ["\u011a\u011b", "⠐⠑⠐⠑"] + - ["\u011c\u011d", "⠐⠛⠐⠛"] + - ["\u011e\u011f", "⠐⠛⠐⠛"] + - ["\u0120\u0121", "⠐⠛⠐⠛"] + - ["\u0122\u0123", "⠐⠛⠐⠛"] + - ["\u0124\u0125", "⠐⠓⠐⠓"] + - ["\u0126\u0127", "⠐⠓⠐⠓"] + - ["\u0128\u0129", "⠐⠊⠐⠊"] + - ["\u012a\u012b", "⠐⠊⠐⠊"] + - ["\u012c\u012d", "⠐⠊⠐⠊"] + - ["\u012e\u012f", "⠐⠊⠐⠊"] + - ["\u0130\u0131", "⠐⠊⠐⠊"] + - ["\u0132\u0133", "⠊⠚⠊⠚"] + - ["\u0134\u0135", "⠐⠚⠐⠚"] + - ["\u0136\u0137", "⠐⠅⠐⠅"] + - ["\u0138", "⠐⠟"] + - ["\u0139\u013a", "⠐⠇⠐⠇"] + - ["\u013b\u013c", "⠐⠇⠐⠇"] + - ["\u013d\u013e", "⠐⠇⠐⠇"] + - ["\u013f\u0140", "⠐⠇⠐⠇"] + - ["\u0141\u0142", "⠐⠇⠐⠇"] + - ["\u0143\u0144", "⠐⠝⠐⠝"] + - ["\u0145\u0146", "⠐⠝⠐⠝"] + - ["\u0147\u0148", "⠐⠝⠐⠝"] + - ["\u0149", "⠈⠝"] + - ["\u014a\u014b", "⠐⠝⠐⠝"] + - ["\u014c\u014d", "⠐⠕⠐⠕"] + - ["\u014e\u014f", "⠐⠕⠐⠕"] + - ["\u0150\u0151", "⠐⠕⠐⠕"] + - ["\u0152\u0153", "⠕⠑⠕⠑"] + - ["\u0154\u0155", "⠐⠗⠐⠗"] + - ["\u0156\u0157", "⠐⠗⠐⠗"] + - ["\u0158\u0159", "⠐⠗⠐⠗"] + - ["\u015a\u015b", "⠐⠎⠐⠎"] + - ["\u015c\u015d", "⠐⠎⠐⠎"] + - ["\u015e\u015f", "⠐⠎⠐⠎"] + - ["\u0160\u0161", "⠐⠎⠐⠎"] + - ["\u0162\u0163", "⠐⠞⠐⠞"] + - ["\u0164\u0165", "⠐⠞⠐⠞"] + - ["\u0166\u0167", "⠐⠞⠐⠞"] + - ["\u0168\u0169", "⠐⠥⠐⠥"] + - ["\u016a\u016b", "⠐⠥⠐⠥"] + - ["\u016c\u016d", "⠐⠥⠐⠥"] + - ["\u016e\u016f", "⠐⠥⠐⠥"] + - ["\u0170\u0171", "⠐⠥⠐⠥"] + - ["\u0172\u0173", "⠐⠥⠐⠥"] + - ["\u0174\u0175", "⠐⠺⠐⠺"] + - ["\u0176\u0177", "⠐⠽⠐⠽"] + - ["\u0178\u00ff", "⠐⠽⠐⠽"] + - ["\u0179\u017a", "⠐⠵⠐⠵"] + - ["\u017b\u017c", "⠐⠵⠐⠵"] + - ["\u017d\u017e", "⠐⠵⠐⠵"] + - ["\u017f", "⠐⠎"] + +# Latin Extended-B + + - ["\u0192", "⠘⠋"] + - ["\u02dc", "⠘⠠"] + +# Greek letters (with dot 5 as prefix) + + - ["\u0386\u03ac", "⠐⠁⠐⠁"] + - ["\u0388\u03ad", "⠐⠑⠐⠑"] + - ["\u0389\u03ae", "⠐⠱⠐⠱"] + - ["\u038a\u03af", "⠐⠊⠐⠊"] + - ["\u038c\u03cc", "⠐⠕⠐⠕"] + - ["\u038e\u03cd", "⠐⠥⠐⠥"] + - ["\u038f\u03ce", "⠐⠺⠐⠺"] + - ["\u0391\u03b1", "⠐⠁⠐⠁"] + - ["\u0392\u03b2", "⠐⠃⠐⠃"] + - ["\u0393\u03b3", "⠐⠛⠐⠛"] + - ["\u0394\u03b4", "⠐⠙⠐⠙"] + - ["\u0395\u03b5", "⠐⠑⠐⠑"] + - ["\u0396\u03b6", "⠐⠵⠐⠵"] + - ["\u0397\u03b7", "⠐⠱⠐⠱"] + - ["\u0398\u03b8", "⠐⠹⠐⠹"] + - ["\u0399\u03b9", "⠐⠊⠐⠊"] + - ["\u039a\u03ba", "⠐⠅⠐⠅"] + - ["\u039b\u03bb", "⠐⠇⠐⠇"] + - ["\u039c\u03bc", "⠐⠍⠐⠍"] + - ["\u039d\u03bd", "⠐⠝⠐⠝"] + - ["\u039e\u03be", "⠐⠭⠐⠭"] + - ["\u039f\u03bf", "⠐⠕⠐⠕"] + - ["\u03a0\u03c0", "⠐⠏⠐⠏"] + - ["\u03a1\u03c1", "⠐⠗⠐⠗"] + - ["\u03a3\u03c3", "⠐⠎⠐⠎"] + - ["\u03a4\u03c4", "⠐⠞⠐⠞"] + - ["\u03a5\u03c5", "⠐⠥⠐⠥"] + - ["\u03a6\u03c6", "⠐⠋⠐⠋"] + - ["\u03a7\u03c7", "⠐⠓⠐⠓"] + - ["\u03a8\u03c8", "⠐⠽⠐⠽"] + - ["\u03a9\u03c9", "⠐⠺⠐⠺"] + +# Punctuation and bullits + + - ["\u2000", " "] + - ["\u2001", " "] + - ["\u2002", " "] + - ["\u2003", " "] + - ["\u2004", " "] + - ["\u2005", " "] + - ["\u2006", " "] + - ["\u2007", " "] + - ["\u2008", " "] + - ["\u2009", " "] + - ["\u200a", " "] + - ["\u2010", "⠤"] + - ["\u2011", "⠤"] + - ["\u2012", "⠤"] + - ["\u2013", "⠤⠤"] + - ["\u2014", "⠤⠤"] + - ["\u2018", "⠈"] + - ["\u2019", "⠈"] + - ["\u201a", "⠈"] + - ["\u201b", "⠈"] + - ["\u201c", "⠶"] + - ["\u201d", "⠶"] + - ["\u201e", "⠶"] + - ["\u201f", "⠶"] + - ["\u2023", "⠘⠄"] + - ["\u2026", "⠄⠄⠄"] + - ["\u202f", " "] + - ["\u2030", "⠚⠴⠴"] + - ["\u2039", "⠈"] + - ["\u203a", "⠈"] + - ["\u203c", "⠖⠖"] + - ["\u203d", "⠢⠖"] + - ["\u2043", "⠘⠄"] + - ["\u2047", "⠢⠢"] + - ["\u2048", "⠢⠖"] + - ["\u2049", "⠖⠢"] + - ["\u204c", "⠘⠄"] + - ["\u204d", "⠘⠄"] + - ["\u20ac", "⠘⠑"] + - ["\u2122", "⠘⠞"] + +# Arrows (only a few in 6 dots) + + - ["\u2190", "⠘⠺"] + - ["\u2191", "⠘⠷"] + - ["\u2192", "⠘⠗"] + - ["\u2193", "⠘⠟"] + +# Geometrical shapes + + - ["\u25e6", "⠘⠄"] + +# Pangram + + - - "Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Walther spillede på xylofon." + - "⠠⠟⠥⠊⠠⠵⠙⠑⠇⠞⠁⠛⠑⠗⠝⠑ ⠎⠏⠊⠎⠞⠑ ⠚⠕⠗⠙⠃⠜⠗ ⠍ ⠋⠇⠪⠙⠑⠂ ⠍⠑⠝⠎ ⠉⠊⠗⠅⠥⠎⠅⠇⠕⠧⠝⠑⠝ ⠠⠺⠁⠇⠞⠓⠑⠗ ⠎⠏⠊⠇⠇⠑⠙⠑ ⠏ ⠠⠭⠽⠇⠕⠋⠕⠝⠄" + +# Emphasis + + - # Bold line + - En linje med fed. + - ⠰⠣ ⠇⠊⠝⠚⠑ ⠍ ⠋⠑⠙⠄⠰ + - typeform: + bold: '+++++++++++++++++' + - # Bold word + - Et ord med fed. + - ⠬ ⠰⠕⠗⠙⠰ ⠍ ⠋⠑⠙⠄ + - typeform: + bold: ' +++ ' + - # Bold letter + - Et bogstav med fed. + - ⠬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠋⠑⠙⠄ + - typeform: + bold: ' + ' + + - # Italic line + - En linje med kursiv. + - ⠰⠣ ⠇⠊⠝⠚⠑ ⠍ ⠅⠥⠗⠎⠊⠧⠄⠰ + - typeform: + italic: '++++++++++++++++++++' + - # Italic word + - Et ord med kursiv. + - ⠬ ⠰⠕⠗⠙⠰ ⠍ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' +++ ' + - # Italic letter + - Et bogstav med kursiv. + - ⠬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' + ' + + - # Underlined line + - En linje med understreget. + - ⠰⠣ ⠇⠊⠝⠚⠑ ⠍ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄⠰ + - typeform: + underline: '++++++++++++++++++++++++++' + - # Underlined word + - Et ord med understreget. + - ⠬ ⠰⠕⠗⠙⠰ ⠍ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄ + - typeform: + underline: ' +++ ' + - # Underlined letter + - Et bogstav med understreget. + - ⠬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄ + - typeform: + underline: ' + ' + + - # Bold, italic and underlined line + - En linje med alt. + - ⠰⠣ ⠇⠊⠝⠚⠑ ⠍ ⠁⠇⠞⠄⠰ + - typeform: + bold: '+++++++++++++++++' + italic: '+++++++++++++++++' + underline: '+++++++++++++++++' + - # Bold, italic and underlined word + - Et ord med alt. + - ⠬ ⠰⠕⠗⠙⠰ ⠍ ⠁⠇⠞⠄ + - typeform: + bold: ' +++ ' + italic: ' +++ ' + underline: ' +++ ' + - # Bold, italic and underlined letter + - Et bogstav med alt. + - ⠬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠁⠇⠞⠄ + - typeform: + bold: ' + ' + italic: ' + ' + underline: ' + ' + +#caps and mixed case + - ["Foobar", "⠋⠕⠕⠃⠁⠗"] + - ["FOOBAR", "⠸⠋⠕⠕⠃⠁⠗"] + - ["FOObar", "⠸⠋⠕⠕⠠⠃⠁⠗"] + - ["FOO-barfOObar", "⠸⠋⠕⠕⠤⠃⠁⠗⠋⠸⠕⠕⠠⠃⠁⠗"] + +# Percent and permille + - ["1%", "⠼⠁ ⠚⠴"] + +# Extra digits + - ["1\u00bc + 1\u00bd = 2\u00be", "⠼⠁⠼⠁⠌⠙ ⠖⠼⠁⠼⠁⠌⠃ ⠶⠼⠃⠼⠉⠌⠙"] + +# Dashes + - ["\u2014 \u0096 \u0097 \u00ad", "⠤⠤ ⠤⠤ ⠤⠤ ⠤⠤"] + +# Quotes + - ["\u201e \u0084 \u201c \u0093 \u201d \u0094 \u00ab \u00bb", "⠶ ⠶ ⠶ ⠶ ⠶ ⠶ ⠶ ⠶"] + +# Apostrophes + - ["` \u201a \u0082 \u2039 \u008b \u2018 \u0091 \u2019 \u0092 \u203a \u009b \u00b4", "⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈"] + +#Times vs. bullit + - ["\u2022Bullit", "⠘⠄⠃⠥⠇⠇⠊⠞"] + - ["2 \u00d7 2 = 4", "⠼⠃ ⠄⠼⠃ ⠶⠼⠙"] + +# Section sign + - ["§ 3", "⠬⠼⠉"] + - ["§ 3:", "⠬⠼⠉⠒"] + +# numbers and punctuation + - ["1,2", "⠼⠁⠂⠃"] + - ["123.456", "⠼⠁⠃⠉⠄⠙⠑⠋"] + - ["3-4", "⠼⠉⠤⠼⠙"] + - ["56-", "⠼⠑⠋⠤"] + - ["1/2", "⠼⠁⠌⠃"] + - ["12:34", "⠼⠁⠃⠒⠉⠙"] + - ["2^10", "⠼⠃⠘⠬⠁⠚"] + - ["2\u00d72", "⠼⠃⠘⠄⠼⠃"] + + - ["\"quotes\"", "⠶⠠⠟⠥⠕⠞⠑⠎⠶"] + - ["-dashes-", "⠤⠙⠁⠎⠓⠑⠎⠤"] + - [":-) :-(", "⠒⠤⠠⠴ ⠒⠤⠠⠦"] + - [";-) ;-(", "⠆⠤⠠⠴ ⠆⠤⠠⠦"] + - ["---", "⠤⠤⠤"] + - ["-", "⠤⠤"] + - [" -", " ⠤⠤"] + - [" a-", " ⠠⠁⠤"] + - [" - ", " ⠤⠤ "] + - [" -a-", " ⠤⠠⠁⠤"] + - [" ", " "] + - ["(parentheses)", "⠦⠏⠁⠗⠑⠝⠞⠓⠑⠎⠑⠎⠴"] + - ["J) j) %) ') \u2030) \u0089) \u201a) \u0082) \u2039) \u009b) \u2018) \u0091) \u2019) \u0092) \u203a) \u009b)", "⠨⠠⠚⠠⠴ ⠠⠚⠠⠴ ⠚⠴⠠⠴ ⠈⠠⠴ ⠚⠴⠴⠠⠴ ⠚⠴⠴⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴"] + + # Exclamation + - ["\u00a1Que lastima!", "⠠⠲⠠⠟⠥⠑ ⠇⠁⠎⠞⠊⠍⠁⠖"] + +# digits and letters + - ["1a", "⠼⠁⠠⠁"] + - ["1.,-a", "⠼⠁⠄⠂⠤⠠⠁"] + +# URLs emails and file names + + - ["$at", "⠘⠲⠁⠞"] + - ["\u005c\u005cat\u005c\u005cbliver", "⠘⠡⠁⠞⠘⠡⠃⠇⠊⠧⠑⠗"] + - ["at@bliver.og", "⠁⠞⠘⠁⠃⠇⠊⠧⠑⠗⠄⠕⠛"] + - ["http://at.bliver.og", "⠓⠞⠞⠏⠒⠌⠌⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠕⠛"] + - ["www.at.bliver.og", "⠠⠺⠠⠺⠠⠺⠄⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠕⠛"] + - ["www.at.bliver.com", "⠠⠺⠠⠺⠠⠺⠄⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠉⠕⠍"] + - ["www.a.b.c", "⠠⠺⠠⠺⠠⠺⠄⠠⠁⠄⠠⠃⠄⠠⠉"] + - ["test.txt", "⠞⠑⠎⠞⠄⠞⠠⠭⠞"] + +# word contractions + + - ["At", "⠁"] + - ["at", "⠁"] + - ["Bliver", "⠃"] + - ["bliver", "⠃"] + - ["Og", "⠉"] + - ["og", "⠉"] + - ["Du", "⠙"] + - ["du", "⠙"] + - ["Eller", "⠑"] + - ["eller", "⠑"] + - ["For", "⠋"] + - ["for", "⠋"] + - ["Gør", "⠛"] + - ["gør", "⠛"] + - ["Har", "⠓"] + - ["har", "⠓"] + - ["Jeg", "⠚"] + - ["jeg", "⠚"] + - ["Kan", "⠅"] + - ["kan", "⠅"] + - ["Lige", "⠇"] + - ["lige", "⠇"] + - ["Med", "⠍"] + - ["med", "⠍"] + - ["Når", "⠝"] + - ["når", "⠝"] + - ["Op", "⠕"] + - ["op", "⠕"] + - ["På", "⠏"] + - ["på", "⠏"] + - ["Under", "⠟"] + - ["under", "⠟"] + - ["Rigtig", "⠗"] + - ["rigtig", "⠗"] + - ["Som", "⠎"] + - ["som", "⠎"] + - ["Til", "⠞"] + - ["til", "⠞"] + - ["Hun", "⠥"] + - ["hun", "⠥"] + - ["Ved", "⠧"] + - ["ved", "⠧"] + - ["Hvad", "⠺"] + - ["hvad", "⠺"] + - ["Over", "⠭"] + - ["over", "⠭"] + - ["Han", "⠽"] + - ["han", "⠽"] + - ["Efter", "⠵"] + - ["efter", "⠵"] + - ["Være", "⠜"] + - ["være", "⠜"] + - ["Før", "⠪"] + - ["før", "⠪"] + - ["Så", "⠡"] + - ["så", "⠡"] + - ["Den", "⠯"] + - ["den", "⠯"] + - ["Der", "⠾"] + - ["der", "⠾"] + - ["Det", "⠮"] + - ["det", "⠮"] + - ["De", "⠹"] + - ["de", "⠹"] + - ["En", "⠣"] + - ["en", "⠣"] + - ["Er", "⠱"] + - ["er", "⠱"] + - ["Et", "⠬"] + - ["et", "⠬"] + - ["Gennem", "⠻"] + - ["gennem", "⠻"] + - ["Hvor", "⠌"] + - ["hvor", "⠌"] + - ["Men", "⠩"] + - ["men", "⠩"] + - ["Ned", "⠫"] + - ["ned", "⠫"] + - ["Ret", "⠷"] + - ["ret", "⠷"] + - ["Skal", "⠿"] + - ["skal", "⠿"] + - ["Te", "⠳"] + - ["te", "⠳"] + - ["Ve", "⠼"] + - ["ve", "⠼"] + +# No single cell contractions before or after dashes + + - ["at-bliver", "⠁⠞⠤⠃"] + - ["d-d-du", "⠠⠙⠤⠠⠙⠤⠙⠥"] + +# combinations with slashes and other punctuation signs + + - ["at!", "⠁⠖"] + - ["bliver!", "⠃⠖"] + - ["og!", "⠉⠖"] + - ["Han/hun", "⠽⠌⠥"] + - ["han/hun", "⠽⠌⠥"] + - ["Over/under", "⠭⠌⠟"] + - ["over/under", "⠭⠌⠟"] + - ["Til/fra", "⠞⠌⠖"] + - ["til/fra", "⠞⠌⠖"] + +# Combinations which require letsign + + - ["1st", "⠼⠁⠠⠎⠞"] + - ["2nd", "⠼⠃⠠⠝⠙"] + - ["1A", "⠼⠁⠨⠠⠁", {xfail: true}] # unclear spec + - ["1a", "⠼⠁⠠⠁"] + - ["2B", "⠼⠃⠨⠠⠃", {xfail: true}] # unclear spec + - ["2b", "⠼⠃⠠⠃"] + +# Multi-pass tests + - ["~x |z", "⠘⠠⠠⠭ ⠘⠸⠠⠵"] + - ["~X |Z", "⠘⠠⠨⠠⠭ ⠘⠸⠨⠠⠵"] + - ["5É", "⠼⠑⠨⠐⠑"] + +# ----------------- +# Grade 2 (regular) +# ----------------- + +# Round trip tests +# Commented tests currently fail backwards but should be fixed. + +table: {language: da, grade: 2, dots: 6, direction: both, version: 1993, __assert-match: da-dk-g26_1993.ctb} +flags: {testmode: bothDirections} +tests: + +# Characters + + - [" ", " "] + - ["\"", "⠶"] + - ["\u0023", "⠘⠼"] + - ["$", "⠘⠲"] + - ["%", "⠚⠴"] + - ["&", "⠠⠯"] + - ["'", "⠈"] + - ["(", "⠠⠦"] + - [")", "⠠⠴"] + - ["*", "⠠⠔"] + - ["+", "⠘⠖"] + - [",", "⠂"] + - ["-", "⠤⠤"] + - [".", "⠄"] + - ["/", "⠠⠌", {xfail: {forward: true}}] + - ["0", "⠼⠚"] + - ["1", "⠼⠁"] + - ["2", "⠼⠃"] + - ["3", "⠼⠉"] + - ["4", "⠼⠙"] + - ["5", "⠼⠑"] + - ["6", "⠼⠋"] + - ["7", "⠼⠛"] + - ["8", "⠼⠓"] + - ["9", "⠼⠊"] + - [":", "⠒"] + - [";", "⠆"] + - ["<", "⠘⠍"] + - ["=", "⠘⠶"] + - [">", "⠘⠎"] + - ["?", "⠢"] + - ["@", "⠘⠁"] + - ["A", "⠨⠠⠁"] + - ["B", "⠨⠠⠃"] + - ["C", "⠨⠠⠉"] + - ["D", "⠨⠠⠙"] + - ["E", "⠨⠠⠑"] + - ["F", "⠨⠠⠋"] + - ["G", "⠨⠠⠛"] + - ["H", "⠨⠠⠓"] + - ["I", "⠨⠊"] + - ["J", "⠨⠠⠚"] + - ["K", "⠨⠠⠅"] + - ["L", "⠨⠠⠇"] + - ["M", "⠨⠠⠍"] + - ["N", "⠨⠠⠝"] + - ["O", "⠨⠠⠕"] + - ["P", "⠨⠠⠏"] + - ["Q", "⠨⠠⠟"] + - ["R", "⠨⠠⠗"] + - ["S", "⠨⠠⠎"] + - ["T", "⠨⠠⠞"] + - ["U", "⠨⠠⠥"] + - ["V", "⠨⠠⠧"] + - ["W", "⠨⠠⠺"] + - ["X", "⠨⠠⠭"] + - ["Y", "⠨⠠⠽"] + - ["Z", "⠨⠠⠵"] + - ["[", "⠐⠦"] + - ["\u005c\u005c", "⠘⠡"] + - ["]", "⠐⠴"] + - ["^", "⠘⠬"] + - ["_", "⠘⠤"] + - ["a", "⠠⠁"] + - ["b", "⠠⠃"] + - ["c", "⠠⠉"] + - ["d", "⠠⠙"] + - ["e", "⠠⠑"] + - ["f", "⠠⠋"] + - ["g", "⠠⠛"] + - ["h", "⠠⠓"] + - ["i", "⠊"] + - ["j", "⠠⠚"] + - ["k", "⠠⠅"] + - ["l", "⠠⠇"] + - ["m", "⠠⠍"] + - ["n", "⠠⠝"] + - ["o", "⠠⠕"] + - ["p", "⠠⠏"] + - ["q", "⠠⠟"] + - ["r", "⠠⠗"] + - ["s", "⠠⠎"] + - ["t", "⠠⠞"] + - ["u", "⠠⠥"] + - ["v", "⠠⠧"] + - ["w", "⠠⠺"] + - ["x", "⠠⠭"] + - ["y", "⠠⠽"] + - ["z", "⠠⠵"] + - ["{", "⠘⠪"] + - ["|", "⠘⠸"] + - ["}", "⠘⠕"] + - ["~", "⠘⠠"] + - ["€", "⠘⠑"] + - ["ƒ", "⠘⠋"] + - ["‰", "⠚⠴⠴"] + - ["Š", "⠨⠐⠎"] + - ["Ž", "⠨⠐⠵"] + - ["•", "⠘⠄"] + - ["™", "⠘⠞"] + - ["š", "⠐⠎"] + - ["ž", "⠐⠵"] + - ["¡", "⠠⠲"] + - ["¢", "⠘⠒"] + - ["£", "⠘⠇"] + - ["¥", "⠘⠽"] + - ["©", "⠘⠉"] + - ["®", "⠘⠗"] + - ["°", "⠈⠴"] + - ["µ", "⠐⠍"] + - ["À", "⠨⠐⠁"] + - ["Å", "⠨⠠⠡"] + - ["Æ", "⠨⠠⠜"] + - ["Ç", "⠨⠐⠉"] + - ["É", "⠨⠐⠑"] + - ["Î", "⠨⠐⠊"] + - ["Ð", "⠨⠐⠙"] + - ["Ñ", "⠨⠐⠝"] + - ["Ô", "⠨⠐⠕"] + - ["Ø", "⠨⠠⠪"] + - ["Û", "⠨⠐⠥"] + - ["Ü", "⠨⠠⠳"] + - ["Ý", "⠨⠐⠽"] + - ["Þ", "⠨⠐⠞"] + - ["à", "⠐⠁"] + - ["å", "⠠⠡"] + - ["æ", "⠠⠜"] + - ["ç", "⠐⠉"] + - ["é", "⠐⠑"] + - ["î", "⠐⠊"] + - ["ð", "⠐⠙"] + - ["ñ", "⠐⠝"] + - ["ô", "⠐⠕"] + - ["ø", "⠠⠪"] + - ["û", "⠐⠥"] + - ["ü", "⠠⠳"] + - ["ý", "⠐⠽"] + - ["þ", "⠐⠞"] + +# Misc. Unicode (most cannot be back-translated) +# Some tests may be repetitions of tests above, since +# some characters can occur both inside and outside the 8 bit range. +# for accented letters in the range u+0080 - u+00ff: the letters +# that are thought to occur most frequently in Danish texts are used +# for back-translation. +# For all accented letters above u+00ff, the first occurring instance is chosen for back-translation. +# There are no rules about this in the specs of Danish Braille, +# So the choice is arbitrary and may change over time. + + - ["\u0134\u0135", "⠨⠐⠚⠐⠚"] + - ["\u0138", "⠐⠟"] + - ["\u0192", "⠘⠋"] + +# Greek letters (with dot 5 as prefix) + + - ["\u0392\u03b2", "⠨⠐⠃⠐⠃"] + - ["\u0393\u03b3", "⠨⠐⠛⠐⠛"] + - ["\u0397\u03b7", "⠨⠐⠱⠐⠱"] + - ["\u0398\u03b8", "⠨⠐⠹⠐⠹"] + - ["\u039a\u03ba", "⠨⠐⠅⠐⠅"] + - ["\u039b\u03bb", "⠨⠐⠇⠐⠇"] + - ["\u039e\u03be", "⠨⠐⠭⠐⠭"] + - ["\u03a0\u03c0", "⠨⠐⠏⠐⠏"] + - ["\u03a1\u03c1", "⠨⠐⠗⠐⠗"] + - ["\u03a6\u03c6", "⠨⠐⠋⠐⠋"] + - ["\u03a7\u03c7", "⠨⠐⠓⠐⠓"] + - ["\u03a9\u03c9", "⠨⠐⠺⠐⠺"] + +# Arrows (only a few in 6 dots) + + - ["\u2190", "⠘⠺"] + - ["\u2191", "⠘⠷"] + - ["\u2193", "⠘⠟"] + +# Pangram + + - - "Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Walther spillede på xylofon." + - "⠨⠠⠟⠥⠊⠠⠵⠹⠇⠞⠁⠛⠱⠫ ⠎⠏⠊⠵⠑ ⠚⠭⠙⠃⠜⠗ ⠍ ⠋⠇⠪⠹⠂ ⠍⠣⠎ ⠉⠊⠗⠅⠥⠎⠅⠇⠕⠧⠝⠣ ⠨⠠⠺⠁⠇⠞⠓⠱ ⠎⠏⠊⠇⠇⠑⠹ ⠏ ⠠⠭⠽⠇⠕⠋⠕⠝⠄" + +# Caps and mixed case + + - ["Foobar", "⠨⠋⠕⠕⠃⠁⠗"] + - ["FOOBAR", "⠸⠋⠕⠕⠃⠁⠗"] + - ["FOObar", "⠸⠋⠕⠕⠠⠃⠁⠗"] + - ["FOO-barfOObar", "⠸⠋⠕⠕⠤⠃⠁⠗⠋⠸⠕⠕⠠⠃⠁⠗"] + +# Percent and permille + + - ["1%", "⠼⠁ ⠚⠴"] + +# Times vs. bullit + + - ["\u2022Bullit", "⠘⠄⠨⠃⠥⠇⠇⠊⠞"] +# - ["2 \u00d7 2 = 4", "⠼⠃ ⠘⠄ ⠼⠃ ⠘⠶ ⠼⠙"] + +# Section sign + + - ["§ 3", "⠬⠼⠉"] + - ["§ 3:", "⠬⠼⠉⠒"] + +# Numbers and punctuation + + - ["1,2", "⠼⠁⠂⠃"] + - ["123.456", "⠼⠁⠃⠉⠄⠙⠑⠋"] + - ["3-4", "⠼⠉⠤⠼⠙"] + - ["56-", "⠼⠑⠋⠤"] + - ["1/2", "⠼⠁⠌⠃"] + - ["12:34", "⠼⠁⠃⠒⠉⠙"] + - ["2^10", "⠼⠃⠘⠬⠁⠚"] +# - ["2\u00d72", "⠼⠃⠘⠄⠼⠃"] + + - ["\"quotes\"", "⠶⠠⠟⠥⠕⠳⠎⠶"] + - ["-dashes-", "⠤⠙⠁⠎⠓⠑⠎⠤"] + - [":-) :-(", "⠒⠤⠠⠴ ⠒⠤⠠⠦"] + - [";-) ;-(", "⠆⠤⠠⠴ ⠆⠤⠠⠦"] + - ["---", "⠤⠤⠤"] + - ["-", "⠤⠤"] +# - [" -", " ⠤⠤"] + - [" a-", " ⠠⠁⠤"] + - [" - ", " ⠤⠤ "] + - [" -a-", " ⠤⠠⠁⠤"] + - [" ", " "] + - ["(parentheses)", "⠦⠏⠁⠗⠣⠞⠓⠑⠎⠑⠎⠴"] + +# Exclamation + + - ["\u00a1Que lastima!", "⠠⠲⠨⠠⠟⠥⠑ ⠇⠁⠵⠊⠍⠁⠖"] + +# Digits and letters + + - ["1a", "⠼⠁⠠⠁"] + - ["1.,-a", "⠼⠁⠄⠂⠤⠠⠁"] + +# URLs emails and file names + + - ["$at", "⠘⠲⠁⠞"] + - ["\u005c\u005cat\u005c\u005cbliver", "⠘⠡⠁⠞⠘⠡⠃⠇⠊⠧⠑⠗"] + - ["at@bliver.og", "⠁⠞⠘⠁⠃⠇⠊⠧⠑⠗⠄⠕⠛"] + - ["http://at.bliver.og", "⠓⠞⠞⠏⠒⠌⠌⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠕⠛"] + - ["www.at.bliver.og", "⠠⠺⠠⠺⠠⠺⠄⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠕⠛"] + - ["www.at.bliver.com", "⠠⠺⠠⠺⠠⠺⠄⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠉⠕⠍"] + - ["www.a.b.c", "⠠⠺⠠⠺⠠⠺⠄⠠⠁⠄⠠⠃⠄⠠⠉"] + - ["test.txt", "⠳⠵⠄⠞⠠⠭⠞"] + +# Word contractions + + - ["At", "⠨⠁"] + - ["at", "⠁"] + - ["Aldrig", "⠨⠁⠔"] + - ["aldrig", "⠁⠔"] + - ["aig", "⠁⠊⠛"] + - ["Alle", "⠨⠁⠑"] + - ["alle", "⠁⠑"] + - ["ae", "⠠⠁⠑"] + - ["Allerede", "⠨⠁⠇⠗"] + - ["allerede", "⠁⠇⠗"] + - ["alr", "⠠⠁⠇⠗"] + - ["Alligevel", "⠨⠁⠇⠧"] + - ["alligevel", "⠁⠇⠧"] + - ["alv", "⠠⠁⠇⠧"] + - ["Altid", "⠨⠁⠞⠙"] + - ["altid", "⠁⠞⠙"] + - ["atd", "⠠⠁⠞⠙"] + - ["Altså", "⠨⠁⠡"] + - ["altså", "⠁⠡"] + - ["aå", "⠠⠁⠡"] + - ["Bliver", "⠨⠃"] + - ["bliver", "⠃"] + - ["Og", "⠨⠉"] + - ["og", "⠉"] + - ["Deres", "⠨⠲"] + - ["deres", "⠲"] + - ["Du", "⠨⠙"] + - ["du", "⠙"] + - ["Eller", "⠨⠑"] + - ["eller", "⠑"] + - ["For", "⠨⠋"] + - ["for", "⠋"] + - ["Gør", "⠨⠛"] + - ["gør", "⠛"] + - ["Har", "⠨⠓"] + - ["har", "⠓"] + - ["Jeg", "⠨⠚"] + - ["jeg", "⠚"] + - ["Kan", "⠨⠅"] + - ["kan", "⠅"] + - ["Lige", "⠨⠇"] + - ["lige", "⠇"] + - ["Med", "⠨⠍"] + - ["med", "⠍"] + - ["Når", "⠨⠝"] + - ["når", "⠝"] + - ["Op", "⠨⠕"] + - ["op", "⠕"] + - ["På", "⠨⠏"] + - ["på", "⠏"] + - ["Under", "⠨⠟"] + - ["under", "⠟"] + - ["Rigtig", "⠨⠗"] + - ["rigtig", "⠗"] + - ["Som", "⠨⠎"] + - ["som", "⠎"] + - ["Til", "⠨⠞"] + - ["til", "⠞"] + - ["Hun", "⠨⠥"] + - ["hun", "⠥"] + - ["Ved", "⠨⠧"] + - ["ved", "⠧"] + - ["Hvad", "⠨⠺"] + - ["hvad", "⠺"] + - ["Over", "⠨⠭"] + - ["over", "⠭"] + - ["Han", "⠨⠽"] + - ["han", "⠽"] + - ["Efter", "⠨⠵"] + - ["efter", "⠵"] + - ["Være", "⠨⠜"] + - ["være", "⠜"] + - ["Før", "⠨⠪"] + - ["før", "⠪"] + - ["Så", "⠨⠡"] + - ["så", "⠡"] + - ["Den", "⠨⠯"] + - ["den", "⠯"] + - ["Der", "⠨⠾"] + - ["der", "⠾"] + - ["Det", "⠨⠮"] + - ["det", "⠮"] + - ["De", "⠨⠹"] + - ["de", "⠹"] + - ["En", "⠨⠣"] + - ["en", "⠣"] + - ["Er", "⠨⠱"] + - ["er", "⠱"] + - ["Et", "⠨⠬"] + - ["et", "⠬"] + - ["Gennem", "⠨⠻"] + - ["gennem", "⠻"] + - ["Hvor", "⠨⠌"] + - ["hvor", "⠌"] + - ["Men", "⠨⠩"] + - ["men", "⠩"] + - ["Ned", "⠨⠫"] + - ["ned", "⠫"] + - ["Ret", "⠨⠷"] + - ["ret", "⠷"] + - ["Skal", "⠨⠿"] + - ["skal", "⠿"] + - ["Te", "⠨⠳"] + - ["te", "⠳"] + - ["Ve", "⠨⠼"] + - ["ve", "⠼"] + +# Partword/nocross + + - ["Denne", "⠨⠯⠫"] + - ["denne", "⠯⠫"] + - ["Mændene", "⠨⠍⠜⠝⠹⠫"] + - ["mændene", "⠍⠜⠝⠹⠫"] + - ["Derhos", "⠨⠾⠓⠕⠎"] + - ["derhos", "⠾⠓⠕⠎"] + - ["Hunderace", "⠨⠓⠥⠝⠹⠗⠁⠉⠑"] + - ["hunderace", "⠓⠥⠝⠹⠗⠁⠉⠑"] + - ["Dette", "⠨⠮⠳"] + - ["dette", "⠮⠳"] + - ["Detalje", "⠨⠹⠞⠁⠇⠚⠑"] + - ["detalje", "⠹⠞⠁⠇⠚⠑"] + +# Nocross multiple cells + + - ["Endda", "⠨⠑⠟⠙⠁"] + - ["endda", "⠑⠟⠙⠁"] + - ["Morgendag", "⠨⠍⠭⠛⠣⠙⠁⠛"] + - ["morgendag", "⠍⠭⠛⠣⠙⠁⠛"] + - ["Gendanne", "⠨⠛⠣⠙⠁⠝⠫"] + - ["gendanne", "⠛⠣⠙⠁⠝⠫"] + - ["Generelt", "⠨⠻⠫⠷⠇⠞"] + - ["generelt", "⠻⠫⠷⠇⠞"] + + - ["Fra!", "⠨⠋⠗⠁⠖"] + - ["fra!", "⠋⠗⠁⠖"] + - ["!Fra", "⠠⠖⠨⠖"] + - ["!fra", "⠖⠋⠗⠁"] + - ["'Af", "⠈⠨⠴"] + - ["'af", "⠈⠁⠋"] + +# No single cell contractions before or after dashes + + - ["at-bliver", "⠁⠞⠤⠃"] + - ["d-d-du", "⠠⠙⠤⠠⠙⠤⠙⠥"] + +# Combinations with slashes and other punctuation signs + + - ["at!", "⠁⠖"] + - ["bliver!", "⠃⠖"] + - ["og!", "⠉⠖"] + - ["Han/hun", "⠨⠽⠌⠥"] + - ["han/hun", "⠽⠌⠥"] + - ["Over/under", "⠨⠭⠌⠟"] + - ["over/under", "⠭⠌⠟"] + - ["Til/fra", "⠨⠞⠌⠖"] + - ["til/fra", "⠞⠌⠖"] + +# Combinations which require letsign + + - ["1st", "⠼⠁⠠⠎⠞"] + - ["2nd", "⠼⠃⠠⠝⠙"] + - ["1A", "⠼⠁⠨⠠⠁", {xfail: {forward: true}}] # unclear spec + - ["1a", "⠼⠁⠠⠁"] + - ["2B", "⠼⠃⠨⠠⠃", {xfail: {forward: true}}] # unclear spec + - ["2b", "⠼⠃⠠⠃"] + +# Multi-pass tests + + - ["~x |z", "⠘⠠⠠⠭ ⠘⠸⠠⠵"] + - ["~X |Z", "⠘⠠⠨⠠⠭ ⠘⠸⠨⠠⠵"] + - ["5É", "⠼⠑⠨⠐⠑"] + +# Characters and constructs which cannot be properly back-translated + +flags: {testmode: forward} +tests: + + - ["`", "⠈"] + - ["‚", "⠈"] + - ["„", "⠶"] + - ["…", "⠄⠄⠄"] + - ["‹", "⠈"] + - ["‘", "⠈"] + - ["’", "⠈"] + - ["“", "⠶"] + - ["”", "⠶"] + - ["Œ", "⠨⠕⠑"] + - ["–", "⠤⠤"] + - ["—", "⠤⠤"] + - ["˜", "⠘⠠"] + - ["›", "⠈"] + - ["œ", "⠕⠑"] + - ["Ÿ", "⠨⠐⠽"] + - ["§", "⠬"] + - ["«", "⠶"] + - ["±", "⠘⠖⠤"] + - ["²", "⠼⠬⠃"] + - ["³", "⠼⠬⠉"] + - ["´", "⠈"] + - ["¹", "⠼⠬⠁"] + - ["»", "⠶"] + - ["¼", "⠼⠁⠌⠙"] + - ["½", "⠼⠁⠌⠃"] + - ["¾", "⠼⠉⠌⠙"] + - ["Á", "⠨⠐⠁"] + - ["Â", "⠨⠐⠁"] + - ["Ã", "⠨⠐⠁"] + - ["Ä", "⠨⠠⠜"] + - ["È", "⠨⠐⠑"] + - ["Ê", "⠨⠐⠑"] + - ["Ë", "⠨⠐⠑"] + - ["Ì", "⠨⠐⠊"] + - ["Í", "⠨⠐⠊"] + - ["Ï", "⠨⠐⠊"] + - ["Ò", "⠨⠐⠕"] + - ["Ó", "⠨⠐⠕"] + - ["Õ", "⠨⠐⠕"] + - ["Ö", "⠨⠠⠪"] + - ["×", "⠘⠄"] + - ["Ù", "⠨⠐⠥"] + - ["Ú", "⠨⠐⠥"] + - ["ß", "⠎⠎"] + - ["á", "⠐⠁"] + - ["â", "⠐⠁"] + - ["ã", "⠐⠁"] + - ["ä", "⠠⠜"] + - ["è", "⠐⠑"] + - ["ê", "⠐⠑"] + - ["ë", "⠐⠑"] + - ["ì", "⠐⠊"] + - ["í", "⠐⠊"] + - ["ï", "⠐⠊"] + - ["ò", "⠐⠕"] + - ["ó", "⠐⠕"] + - ["õ", "⠐⠕"] + - ["ö", "⠠⠪"] + - ["÷", "⠘⠲"] + - ["ù", "⠐⠥"] + - ["ú", "⠐⠥"] + - ["ÿ", "⠐⠽"] + +# Latin Extended-A + + - ["\u0100\u0101", "⠨⠐⠁⠐⠁"] + - ["\u0102\u0103", "⠨⠐⠁⠐⠁"] + - ["\u0104\u0105", "⠨⠐⠁⠐⠁"] + - ["\u0106\u0107", "⠨⠐⠉⠐⠉"] + - ["\u0108\u0109", "⠨⠐⠉⠐⠉"] + - ["\u010a\u010b", "⠨⠐⠉⠐⠉"] + - ["\u010c\u010d", "⠨⠐⠉⠐⠉"] + - ["\u010e\u010f", "⠨⠐⠙⠐⠙"] + - ["\u0110\u0111", "⠨⠐⠙⠐⠙"] + - ["\u0112\u0113", "⠨⠐⠑⠐⠑"] + - ["\u0114\u0115", "⠨⠐⠑⠐⠑"] + - ["\u0116\u0117", "⠨⠐⠑⠐⠑"] + - ["\u0118\u0119", "⠨⠐⠑⠐⠑"] + - ["\u011a\u011b", "⠨⠐⠑⠐⠑"] + - ["\u011c\u011d", "⠨⠐⠛⠐⠛"] + - ["\u011e\u011f", "⠨⠐⠛⠐⠛"] + - ["\u0120\u0121", "⠨⠐⠛⠐⠛"] + - ["\u0122\u0123", "⠨⠐⠛⠐⠛"] + - ["\u0124\u0125", "⠨⠐⠓⠐⠓"] + - ["\u0126\u0127", "⠨⠐⠓⠐⠓"] + - ["\u0128\u0129", "⠨⠐⠊⠐⠊"] + - ["\u012a\u012b", "⠨⠐⠊⠐⠊"] + - ["\u012c\u012d", "⠨⠐⠊⠐⠊"] + - ["\u012e\u012f", "⠨⠐⠊⠐⠊"] + - ["\u0130\u0131", "⠨⠐⠊⠐⠊"] + - ["\u0132\u0133", "⠨⠊⠚⠊⠚"] + - ["\u0136\u0137", "⠨⠐⠅⠐⠅"] + - ["\u0139\u013a", "⠨⠐⠇⠐⠇"] + - ["\u013b\u013c", "⠨⠐⠇⠐⠇"] + - ["\u013d\u013e", "⠨⠐⠇⠐⠇"] + - ["\u013f\u0140", "⠨⠐⠇⠐⠇"] + - ["\u0141\u0142", "⠨⠐⠇⠐⠇"] + - ["\u0143\u0144", "⠨⠐⠝⠐⠝"] + - ["\u0145\u0146", "⠨⠐⠝⠐⠝"] + - ["\u0147\u0148", "⠨⠐⠝⠐⠝"] + - ["\u0149", "⠈⠝"] + - ["\u014a\u014b", "⠨⠐⠝⠐⠝"] + - ["\u014c\u014d", "⠨⠐⠕⠐⠕"] + - ["\u014e\u014f", "⠨⠐⠕⠐⠕"] + - ["\u0150\u0151", "⠨⠐⠕⠐⠕"] + - ["\u0152\u0153", "⠨⠕⠑⠕⠑"] + - ["\u0154\u0155", "⠨⠐⠗⠐⠗"] + - ["\u0156\u0157", "⠨⠐⠗⠐⠗"] + - ["\u0158\u0159", "⠨⠐⠗⠐⠗"] + - ["\u015a\u015b", "⠨⠐⠎⠐⠎"] + - ["\u015c\u015d", "⠨⠐⠎⠐⠎"] + - ["\u015e\u015f", "⠨⠐⠎⠐⠎"] + - ["\u0160\u0161", "⠨⠐⠎⠐⠎"] + - ["\u0162\u0163", "⠨⠐⠞⠐⠞"] + - ["\u0164\u0165", "⠨⠐⠞⠐⠞"] + - ["\u0166\u0167", "⠨⠐⠞⠐⠞"] + - ["\u0168\u0169", "⠨⠐⠥⠐⠥"] + - ["\u016a\u016b", "⠨⠐⠥⠐⠥"] + - ["\u016c\u016d", "⠨⠐⠥⠐⠥"] + - ["\u016e\u016f", "⠨⠐⠥⠐⠥"] + - ["\u0170\u0171", "⠨⠐⠥⠐⠥"] + - ["\u0172\u0173", "⠨⠐⠥⠐⠥"] + - ["\u0174\u0175", "⠨⠐⠺⠐⠺"] + - ["\u0176\u0177", "⠨⠐⠽⠐⠽"] + - ["\u0178\u00ff", "⠨⠐⠽⠐⠽"] + - ["\u0179\u017a", "⠨⠐⠵⠐⠵"] + - ["\u017b\u017c", "⠨⠐⠵⠐⠵"] + - ["\u017d\u017e", "⠨⠐⠵⠐⠵"] + - ["\u017f", "⠐⠎"] + +# Latin Extended-B + + - ["\u02dc", "⠘⠠"] + +# Greek letters (with dot 5 as prefix) + + - ["\u0386\u03ac", "⠨⠐⠁⠐⠁"] + - ["\u0388\u03ad", "⠨⠐⠑⠐⠑"] + - ["\u0389\u03ae", "⠨⠐⠱⠐⠱"] + - ["\u038a\u03af", "⠨⠐⠊⠐⠊"] + - ["\u038c\u03cc", "⠨⠐⠕⠐⠕"] + - ["\u038e\u03cd", "⠨⠐⠥⠐⠥"] + - ["\u038f\u03ce", "⠨⠐⠺⠐⠺"] + - ["\u0391\u03b1", "⠨⠐⠁⠐⠁"] + - ["\u0394\u03b4", "⠨⠐⠙⠐⠙"] + - ["\u0395\u03b5", "⠨⠐⠑⠐⠑"] + - ["\u0396\u03b6", "⠨⠐⠵⠐⠵"] + - ["\u0399\u03b9", "⠨⠐⠊⠐⠊"] + - ["\u039c\u03bc", "⠨⠐⠍⠐⠍"] + - ["\u039d\u03bd", "⠨⠐⠝⠐⠝"] + - ["\u039f\u03bf", "⠨⠐⠕⠐⠕"] + - ["\u03a3\u03c3", "⠨⠐⠎⠐⠎"] + - ["\u03a4\u03c4", "⠨⠐⠞⠐⠞"] + - ["\u03a5\u03c5", "⠨⠐⠥⠐⠥"] + - ["\u03a8\u03c8", "⠨⠐⠽⠐⠽"] + +# Punctuation and bullits + + - ["\u2000", " "] + - ["\u2001", " "] + - ["\u2002", " "] + - ["\u2003", " "] + - ["\u2004", " "] + - ["\u2005", " "] + - ["\u2006", " "] + - ["\u2007", " "] + - ["\u2008", " "] + - ["\u2009", " "] + - ["\u200a", " "] + - ["\u2010", "⠤"] + - ["\u2011", "⠤"] + - ["\u2012", "⠤"] + - ["\u2013", "⠤⠤"] + - ["\u2014", "⠤⠤"] + - ["\u2018", "⠈"] + - ["\u2019", "⠈"] + - ["\u201a", "⠈"] + - ["\u201b", "⠈"] + - ["\u201c", "⠶"] + - ["\u201d", "⠶"] + - ["\u201e", "⠶"] + - ["\u201f", "⠶"] + - ["\u2023", "⠘⠄"] + - ["\u2026", "⠄⠄⠄"] + - ["\u202f", " "] + - ["\u2030", "⠚⠴⠴"] + - ["\u2039", "⠈"] + - ["\u203a", "⠈"] + - ["\u203c", "⠖⠖"] + - ["\u203d", "⠢⠖"] + - ["\u2043", "⠘⠄"] + - ["\u2047", "⠢⠢"] + - ["\u2048", "⠢⠖"] + - ["\u2049", "⠖⠢"] + - ["\u204c", "⠘⠄"] + - ["\u204d", "⠘⠄"] + - ["\u20ac", "⠘⠑"] + - ["\u2122", "⠘⠞"] + +# Arrows (only a few in 6 dots) + + - ["\u2192", "⠘⠗"] # back-translates as "registered". + +# Geometrical shapes + + - ["\u25e6", "⠘⠄"] + +# Extra digits + + - ["1\u00bc + 1\u00bd = 2\u00be", "⠼⠁⠼⠁⠌⠙ ⠘⠖ ⠼⠁⠼⠁⠌⠃ ⠘⠶ ⠼⠃⠼⠉⠌⠙"] + +# Dashes + + - ["\u2014 \u0096 \u0097 \u00ad", "⠤⠤ ⠤⠤ ⠤⠤ ⠤⠤"] + +# Quotes + + - ["\u201e \u0084 \u201c \u0093 \u201d \u0094 \u00ab \u00bb", "⠶ ⠶ ⠶ ⠶ ⠶ ⠶ ⠶ ⠶"] + +# Apostrophes + + - ["` \u201a \u0082 \u2039 \u008b \u2018 \u0091 \u2019 \u0092 \u203a \u009b \u00b4", "⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈"] + +# Parentheses and misc + + - ["J) j) %) ') \u2030) \u0089) \u201a) \u0082) \u2039) \u009b) \u2018) \u0091) \u2019) \u0092) \u203a) \u009b)", "⠨⠠⠚⠠⠴ ⠠⠚⠠⠴ ⠚⠴⠠⠴ ⠈⠠⠴ ⠚⠴⠴⠠⠴ ⠚⠴⠴⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴"] + +# Emphasis + + - # Bold line + - En linje med fed. + - ⠰⠨⠣ ⠇⠊⠝⠚⠑ ⠍ ⠋⠑⠙⠄⠰ + - typeform: + bold: '+++++++++++++++++' + - # Bold word + - Et ord med fed. + - ⠨⠬ ⠰⠭⠙⠰ ⠍ ⠋⠑⠙⠄ + - typeform: + bold: ' +++ ' + - # Bold letter + - Et bogstav med fed. + - ⠨⠬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠋⠑⠙⠄ + - typeform: + bold: ' + ' + + - # Italic line + - En linje med kursiv. + - ⠰⠨⠣ ⠇⠊⠝⠚⠑ ⠍ ⠅⠥⠗⠎⠊⠧⠄⠰ + - typeform: + italic: '++++++++++++++++++++' + - # Italic word + - Et ord med kursiv. + - ⠨⠬ ⠰⠭⠙⠰ ⠍ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' +++ ' + - # Italic letter + - Et bogstav med kursiv. + - ⠨⠬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' + ' + + - # Underlined line + - En linje med understreget. + - ⠰⠨⠣ ⠇⠊⠝⠚⠑ ⠍ ⠥⠝⠾⠵⠷⠛⠬⠄⠰ + - typeform: + underline: '++++++++++++++++++++++++++' + - # Underlined word + - Et ord med understreget. + - ⠨⠬ ⠰⠭⠙⠰ ⠍ ⠥⠝⠾⠵⠷⠛⠬⠄ + - typeform: + underline: ' +++ ' + - # Underlined letter + - Et bogstav med understreget. + - ⠨⠬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠥⠝⠾⠵⠷⠛⠬⠄ + - typeform: + underline: ' + ' + + - # Bold, italic and underlined line + - En linje med alt. + - ⠰⠨⠣ ⠇⠊⠝⠚⠑ ⠍ ⠁⠇⠞⠄⠰ + - typeform: + bold: '+++++++++++++++++' + italic: '+++++++++++++++++' + underline: '+++++++++++++++++' + - # Bold, italic and underlined word + - Et ord med alt. + - ⠨⠬ ⠰⠭⠙⠰ ⠍ ⠁⠇⠞⠄ + - typeform: + bold: ' +++ ' + italic: ' +++ ' + underline: ' +++ ' + - # Bold, italic and underlined letter + - Et bogstav med alt. + - ⠨⠬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠁⠇⠞⠄ + - typeform: + bold: ' + ' + italic: ' + ' + underline: ' + ' + +# Braille patterns which back-translate to something other than the original + +flags: {testmode: backward} +tests: + +# Characters + + - ["⠄⠄⠄", "..."] + - ["⠨⠕⠑", "Oe"] + - ["⠕⠑", "oe"] + - ["⠘⠖⠤", "±", {xfail: true}] + - ["⠼⠁⠌⠙", "1/4"] + - ["⠼⠁⠌⠃", "1/2"] + - ["⠼⠉⠌⠙", "3/4"] + +# Extra digits + + - ["⠼⠁⠼⠁⠌⠙ ⠘⠖ ⠼⠁⠼⠁⠌⠃ ⠘⠶ ⠼⠃⠼⠉⠌⠙", "1\u00bc + 1\u00bd = 2\u00be", {xfail: true}] + +# ------------------------------- +# Grade 2 literary (forward only) +# ------------------------------- + +table: {language: da, grade: 2, dots: 6, direction: forward, version: 1993, __assert-match: da-dk-g26-lit_1993.ctb} +flags: {testmode: forward} +tests: + +# Characters + + - [" ", " "] + - ["\"", "⠶"] + - ["\u0023", "⠘⠼"] + - ["$", "⠘⠲"] + - ["%", "⠚⠴"] + - ["&", "⠠⠯"] + - ["'", "⠈"] + - ["(", "⠠⠦"] + - [")", "⠠⠴"] + - ["*", "⠠⠔"] + - ["+", "⠘⠖"] + - [",", "⠂"] + - ["-", "⠤⠤"] + - [".", "⠄"] + - ["/", "⠠⠌", {xfail: true}] + - ["0", "⠼⠚"] + - ["1", "⠼⠁"] + - ["2", "⠼⠃"] + - ["3", "⠼⠉"] + - ["4", "⠼⠙"] + - ["5", "⠼⠑"] + - ["6", "⠼⠋"] + - ["7", "⠼⠛"] + - ["8", "⠼⠓"] + - ["9", "⠼⠊"] + - [":", "⠒"] + - [";", "⠆"] + - ["<", "⠘⠍"] + - ["=", "⠘⠶"] + - [">", "⠘⠎"] + - ["?", "⠢"] + - ["@", "⠘⠁"] + - ["A", "⠨⠠⠁"] + - ["B", "⠨⠠⠃"] + - ["C", "⠨⠠⠉"] + - ["D", "⠨⠠⠙"] + - ["E", "⠨⠠⠑"] + - ["F", "⠨⠠⠋"] + - ["G", "⠨⠠⠛"] + - ["H", "⠨⠠⠓"] + - ["I", "⠨⠊"] + - ["J", "⠨⠠⠚"] + - ["K", "⠨⠠⠅"] + - ["L", "⠨⠠⠇"] + - ["M", "⠨⠠⠍"] + - ["N", "⠨⠠⠝"] + - ["O", "⠨⠠⠕"] + - ["P", "⠨⠠⠏"] + - ["Q", "⠨⠠⠟"] + - ["R", "⠨⠠⠗"] + - ["S", "⠨⠠⠎"] + - ["T", "⠨⠠⠞"] + - ["U", "⠨⠠⠥"] + - ["V", "⠨⠠⠧"] + - ["W", "⠨⠠⠺"] + - ["X", "⠨⠠⠭"] + - ["Y", "⠨⠠⠽"] + - ["Z", "⠨⠠⠵"] + - ["[", "⠐⠦"] + - ["\u005c\u005c", "⠘⠡"] + - ["]", "⠐⠴"] + - ["^", "⠘⠬"] + - ["_", "⠘⠤"] + - ["`", "⠈"] + - ["a", "⠠⠁"] + - ["b", "⠠⠃"] + - ["c", "⠠⠉"] + - ["d", "⠠⠙"] + - ["e", "⠠⠑"] + - ["f", "⠠⠋"] + - ["g", "⠠⠛"] + - ["h", "⠠⠓"] + - ["i", "⠊"] + - ["j", "⠠⠚"] + - ["k", "⠠⠅"] + - ["l", "⠠⠇"] + - ["m", "⠠⠍"] + - ["n", "⠠⠝"] + - ["o", "⠠⠕"] + - ["p", "⠠⠏"] + - ["q", "⠠⠟"] + - ["r", "⠠⠗"] + - ["s", "⠠⠎"] + - ["t", "⠠⠞"] + - ["u", "⠠⠥"] + - ["v", "⠠⠧"] + - ["w", "⠠⠺"] + - ["x", "⠠⠭"] + - ["y", "⠠⠽"] + - ["z", "⠠⠵"] + - ["{", "⠘⠪"] + - ["|", "⠘⠸"] + - ["}", "⠘⠕"] + - ["~", "⠘⠠"] + - ["€", "⠘⠑"] + - ["‚", "⠈"] + - ["ƒ", "⠘⠋"] + - ["„", "⠶"] + - ["…", "⠄⠄⠄"] + - ["‰", "⠚⠴⠴"] + - ["Š", "⠨⠐⠎"] + - ["‹", "⠈"] + - ["Œ", "⠨⠕⠑"] + - ["Ž", "⠨⠐⠵"] + - ["‘", "⠈"] + - ["’", "⠈"] + - ["“", "⠶"] + - ["”", "⠶"] + - ["•", "⠘⠄"] + - ["–", "⠤⠤"] + - ["—", "⠤⠤"] + - ["˜", "⠘⠠"] + - ["™", "⠘⠞"] + - ["š", "⠐⠎"] + - ["›", "⠈"] + - ["œ", "⠕⠑"] + - ["ž", "⠐⠵"] + - ["Ÿ", "⠨⠐⠽"] + - ["¡", "⠠⠲"] + - ["¢", "⠘⠒"] + - ["£", "⠘⠇"] + - ["¥", "⠘⠽"] + - ["§", "⠬"] + - ["©", "⠘⠉"] + - ["«", "⠶"] + - ["®", "⠘⠗"] + - ["°", "⠈⠴"] + - ["±", "⠘⠖⠤"] + - ["²", "⠼⠬⠃"] + - ["³", "⠼⠬⠉"] + - ["´", "⠈"] + - ["µ", "⠐⠍"] + - ["¹", "⠼⠬⠁"] + - ["»", "⠶"] + - ["¼", "⠼⠁⠌⠙"] + - ["½", "⠼⠁⠌⠃"] + - ["¾", "⠼⠉⠌⠙"] + - ["À", "⠨⠐⠁"] + - ["Á", "⠨⠐⠁"] + - ["Â", "⠨⠐⠁"] + - ["Ã", "⠨⠐⠁"] + - ["Ä", "⠨⠠⠜"] + - ["Å", "⠨⠠⠡"] + - ["Æ", "⠨⠠⠜"] + - ["Ç", "⠨⠐⠉"] + - ["È", "⠨⠐⠑"] + - ["É", "⠨⠐⠑"] + - ["Ê", "⠨⠐⠑"] + - ["Ë", "⠨⠐⠑"] + - ["Ì", "⠨⠐⠊"] + - ["Í", "⠨⠐⠊"] + - ["Î", "⠨⠐⠊"] + - ["Ï", "⠨⠐⠊"] + - ["Ð", "⠨⠐⠙"] + - ["Ñ", "⠨⠐⠝"] + - ["Ò", "⠨⠐⠕"] + - ["Ó", "⠨⠐⠕"] + - ["Ô", "⠨⠐⠕"] + - ["Õ", "⠨⠐⠕"] + - ["Ö", "⠨⠠⠪"] + - ["×", "⠘⠄"] + - ["Ø", "⠨⠠⠪"] + - ["Ù", "⠨⠐⠥"] + - ["Ú", "⠨⠐⠥"] + - ["Û", "⠨⠐⠥"] + - ["Ü", "⠨⠠⠳"] + - ["Ý", "⠨⠐⠽"] + - ["Þ", "⠨⠐⠞"] + - ["ß", "⠎⠎"] + - ["à", "⠐⠁"] + - ["á", "⠐⠁"] + - ["â", "⠐⠁"] + - ["ã", "⠐⠁"] + - ["ä", "⠠⠜"] + - ["å", "⠠⠡"] + - ["æ", "⠠⠜"] + - ["ç", "⠐⠉"] + - ["è", "⠐⠑"] + - ["é", "⠐⠑"] + - ["ê", "⠐⠑"] + - ["ë", "⠐⠑"] + - ["ì", "⠐⠊"] + - ["í", "⠐⠊"] + - ["î", "⠐⠊"] + - ["ï", "⠐⠊"] + - ["ð", "⠐⠙"] + - ["ñ", "⠐⠝"] + - ["ò", "⠐⠕"] + - ["ó", "⠐⠕"] + - ["ô", "⠐⠕"] + - ["õ", "⠐⠕"] + - ["ö", "⠠⠪"] + - ["÷", "⠘⠲"] + - ["ø", "⠠⠪"] + - ["ù", "⠐⠥"] + - ["ú", "⠐⠥"] + - ["û", "⠐⠥"] + - ["ü", "⠠⠳"] + - ["ý", "⠐⠽"] + - ["þ", "⠐⠞"] + - ["ÿ", "⠐⠽"] + +# Latin Extended-A + + - ["\u0100\u0101", "⠐⠁⠐⠁"] + - ["\u0102\u0103", "⠐⠁⠐⠁"] + - ["\u0104\u0105", "⠐⠁⠐⠁"] + - ["\u0106\u0107", "⠐⠉⠐⠉"] + - ["\u0108\u0109", "⠐⠉⠐⠉"] + - ["\u010a\u010b", "⠐⠉⠐⠉"] + - ["\u010c\u010d", "⠐⠉⠐⠉"] + - ["\u010e\u010f", "⠐⠙⠐⠙"] + - ["\u0110\u0111", "⠐⠙⠐⠙"] + - ["\u0112\u0113", "⠐⠑⠐⠑"] + - ["\u0114\u0115", "⠐⠑⠐⠑"] + - ["\u0116\u0117", "⠐⠑⠐⠑"] + - ["\u0118\u0119", "⠐⠑⠐⠑"] + - ["\u011a\u011b", "⠐⠑⠐⠑"] + - ["\u011c\u011d", "⠐⠛⠐⠛"] + - ["\u011e\u011f", "⠐⠛⠐⠛"] + - ["\u0120\u0121", "⠐⠛⠐⠛"] + - ["\u0122\u0123", "⠐⠛⠐⠛"] + - ["\u0124\u0125", "⠐⠓⠐⠓"] + - ["\u0126\u0127", "⠐⠓⠐⠓"] + - ["\u0128\u0129", "⠐⠊⠐⠊"] + - ["\u012a\u012b", "⠐⠊⠐⠊"] + - ["\u012c\u012d", "⠐⠊⠐⠊"] + - ["\u012e\u012f", "⠐⠊⠐⠊"] + - ["\u0130\u0131", "⠐⠊⠐⠊"] + - ["\u0132\u0133", "⠊⠚⠊⠚"] + - ["\u0134\u0135", "⠐⠚⠐⠚"] + - ["\u0136\u0137", "⠐⠅⠐⠅"] + - ["\u0138", "⠐⠟"] + - ["\u0139\u013a", "⠐⠇⠐⠇"] + - ["\u013b\u013c", "⠐⠇⠐⠇"] + - ["\u013d\u013e", "⠐⠇⠐⠇"] + - ["\u013f\u0140", "⠐⠇⠐⠇"] + - ["\u0141\u0142", "⠐⠇⠐⠇"] + - ["\u0143\u0144", "⠐⠝⠐⠝"] + - ["\u0145\u0146", "⠐⠝⠐⠝"] + - ["\u0147\u0148", "⠐⠝⠐⠝"] + - ["\u0149", "⠈⠝"] + - ["\u014a\u014b", "⠐⠝⠐⠝"] + - ["\u014c\u014d", "⠐⠕⠐⠕"] + - ["\u014e\u014f", "⠐⠕⠐⠕"] + - ["\u0150\u0151", "⠐⠕⠐⠕"] + - ["\u0152\u0153", "⠕⠑⠕⠑"] + - ["\u0154\u0155", "⠐⠗⠐⠗"] + - ["\u0156\u0157", "⠐⠗⠐⠗"] + - ["\u0158\u0159", "⠐⠗⠐⠗"] + - ["\u015a\u015b", "⠐⠎⠐⠎"] + - ["\u015c\u015d", "⠐⠎⠐⠎"] + - ["\u015e\u015f", "⠐⠎⠐⠎"] + - ["\u0160\u0161", "⠐⠎⠐⠎"] + - ["\u0162\u0163", "⠐⠞⠐⠞"] + - ["\u0164\u0165", "⠐⠞⠐⠞"] + - ["\u0166\u0167", "⠐⠞⠐⠞"] + - ["\u0168\u0169", "⠐⠥⠐⠥"] + - ["\u016a\u016b", "⠐⠥⠐⠥"] + - ["\u016c\u016d", "⠐⠥⠐⠥"] + - ["\u016e\u016f", "⠐⠥⠐⠥"] + - ["\u0170\u0171", "⠐⠥⠐⠥"] + - ["\u0172\u0173", "⠐⠥⠐⠥"] + - ["\u0174\u0175", "⠐⠺⠐⠺"] + - ["\u0176\u0177", "⠐⠽⠐⠽"] + - ["\u0178\u00ff", "⠐⠽⠐⠽"] + - ["\u0179\u017a", "⠐⠵⠐⠵"] + - ["\u017b\u017c", "⠐⠵⠐⠵"] + - ["\u017d\u017e", "⠐⠵⠐⠵"] + - ["\u017f", "⠐⠎"] + +# Latin Extended-B + + - ["\u0192", "⠘⠋"] + - ["\u02dc", "⠘⠠"] + +# Greek letters (with dot 5 as prefix) + + - ["\u0386\u03ac", "⠐⠁⠐⠁"] + - ["\u0388\u03ad", "⠐⠑⠐⠑"] + - ["\u0389\u03ae", "⠐⠱⠐⠱"] + - ["\u038a\u03af", "⠐⠊⠐⠊"] + - ["\u038c\u03cc", "⠐⠕⠐⠕"] + - ["\u038e\u03cd", "⠐⠥⠐⠥"] + - ["\u038f\u03ce", "⠐⠺⠐⠺"] + - ["\u0391\u03b1", "⠐⠁⠐⠁"] + - ["\u0392\u03b2", "⠐⠃⠐⠃"] + - ["\u0393\u03b3", "⠐⠛⠐⠛"] + - ["\u0394\u03b4", "⠐⠙⠐⠙"] + - ["\u0395\u03b5", "⠐⠑⠐⠑"] + - ["\u0396\u03b6", "⠐⠵⠐⠵"] + - ["\u0397\u03b7", "⠐⠱⠐⠱"] + - ["\u0398\u03b8", "⠐⠹⠐⠹"] + - ["\u0399\u03b9", "⠐⠊⠐⠊"] + - ["\u039a\u03ba", "⠐⠅⠐⠅"] + - ["\u039b\u03bb", "⠐⠇⠐⠇"] + - ["\u039c\u03bc", "⠐⠍⠐⠍"] + - ["\u039d\u03bd", "⠐⠝⠐⠝"] + - ["\u039e\u03be", "⠐⠭⠐⠭"] + - ["\u039f\u03bf", "⠐⠕⠐⠕"] + - ["\u03a0\u03c0", "⠐⠏⠐⠏"] + - ["\u03a1\u03c1", "⠐⠗⠐⠗"] + - ["\u03a3\u03c3", "⠐⠎⠐⠎"] + - ["\u03a4\u03c4", "⠐⠞⠐⠞"] + - ["\u03a5\u03c5", "⠐⠥⠐⠥"] + - ["\u03a6\u03c6", "⠐⠋⠐⠋"] + - ["\u03a7\u03c7", "⠐⠓⠐⠓"] + - ["\u03a8\u03c8", "⠐⠽⠐⠽"] + - ["\u03a9\u03c9", "⠐⠺⠐⠺"] + +# Punctuation and bullits + + - ["\u2000", " "] + - ["\u2001", " "] + - ["\u2002", " "] + - ["\u2003", " "] + - ["\u2004", " "] + - ["\u2005", " "] + - ["\u2006", " "] + - ["\u2007", " "] + - ["\u2008", " "] + - ["\u2009", " "] + - ["\u200a", " "] + - ["\u2010", "⠤"] + - ["\u2011", "⠤"] + - ["\u2012", "⠤"] + - ["\u2013", "⠤⠤"] + - ["\u2014", "⠤⠤"] + - ["\u2018", "⠈"] + - ["\u2019", "⠈"] + - ["\u201a", "⠈"] + - ["\u201b", "⠈"] + - ["\u201c", "⠶"] + - ["\u201d", "⠶"] + - ["\u201e", "⠶"] + - ["\u201f", "⠶"] + - ["\u2023", "⠘⠄"] + - ["\u2026", "⠄⠄⠄"] + - ["\u202f", " "] + - ["\u2030", "⠚⠴⠴"] + - ["\u2039", "⠈"] + - ["\u203a", "⠈"] + - ["\u203c", "⠖⠖"] + - ["\u203d", "⠢⠖"] + - ["\u2043", "⠘⠄"] + - ["\u2047", "⠢⠢"] + - ["\u2048", "⠢⠖"] + - ["\u2049", "⠖⠢"] + - ["\u204c", "⠘⠄"] + - ["\u204d", "⠘⠄"] + - ["\u20ac", "⠘⠑"] + - ["\u2122", "⠘⠞"] + +# Arrows (only a few in 6 dots) + + - ["\u2190", "⠘⠺"] + - ["\u2191", "⠘⠷"] + - ["\u2192", "⠘⠗"] + - ["\u2193", "⠘⠟"] + +# Geometrical shapes + + - ["\u25e6", "⠘⠄"] + +# Pangram + + - - "Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Walther spillede på xylofon." + - "⠠⠟⠥⠊⠠⠵⠹⠇⠞⠁⠛⠱⠫ ⠎⠏⠊⠵⠑ ⠚⠭⠙⠃⠜⠗ ⠍ ⠋⠇⠪⠹⠂ ⠍⠣⠎ ⠉⠊⠗⠅⠥⠎⠅⠇⠕⠧⠝⠣ ⠠⠺⠁⠇⠞⠓⠱ ⠎⠏⠊⠇⠇⠑⠹ ⠏ ⠠⠭⠽⠇⠕⠋⠕⠝⠄" + +# Emphasis + + - # Bold line + - En linje med fed. + - ⠰⠣ ⠇⠊⠝⠚⠑ ⠍ ⠋⠑⠙⠄⠰ + - typeform: + bold: '+++++++++++++++++' + - # Bold word + - Et ord med fed. + - ⠬ ⠰⠭⠙⠰ ⠍ ⠋⠑⠙⠄ + - typeform: + bold: ' +++ ' + - # Bold letter + - Et bogstav med fed. + - ⠬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠋⠑⠙⠄ + - typeform: + bold: ' + ' + + - # Italic line + - En linje med kursiv. + - ⠰⠣ ⠇⠊⠝⠚⠑ ⠍ ⠅⠥⠗⠎⠊⠧⠄⠰ + - typeform: + italic: '++++++++++++++++++++' + - # Italic word + - Et ord med kursiv. + - ⠬ ⠰⠭⠙⠰ ⠍ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' +++ ' + - # Italic letter + - Et bogstav med kursiv. + - ⠬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' + ' + + - # Underlined line + - En linje med understreget. + - ⠰⠣ ⠇⠊⠝⠚⠑ ⠍ ⠥⠝⠾⠵⠷⠛⠬⠄⠰ + - typeform: + underline: '++++++++++++++++++++++++++' + - # Underlined word + - Et ord med understreget. + - ⠬ ⠰⠭⠙⠰ ⠍ ⠥⠝⠾⠵⠷⠛⠬⠄ + - typeform: + underline: ' +++ ' + - # Underlined letter + - Et bogstav med understreget. + - ⠬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠥⠝⠾⠵⠷⠛⠬⠄ + - typeform: + underline: ' + ' + + - # Bold, italic and underlined line + - En linje med alt. + - ⠰⠣ ⠇⠊⠝⠚⠑ ⠍ ⠁⠇⠞⠄⠰ + - typeform: + bold: '+++++++++++++++++' + italic: '+++++++++++++++++' + underline: '+++++++++++++++++' + - # Bold, italic and underlined word + - Et ord med alt. + - ⠬ ⠰⠭⠙⠰ ⠍ ⠁⠇⠞⠄ + - typeform: + bold: ' +++ ' + italic: ' +++ ' + underline: ' +++ ' + - # Bold, italic and underlined letter + - Et bogstav med alt. + - ⠬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠁⠇⠞⠄ + - typeform: + bold: ' + ' + italic: ' + ' + underline: ' + ' + +# Caps and mixed case + + - ["Foobar", "⠋⠕⠕⠃⠁⠗"] + - ["FOOBAR", "⠸⠋⠕⠕⠃⠁⠗"] + - ["FOObar", "⠸⠋⠕⠕⠠⠃⠁⠗"] + - ["FOO-barfOObar", "⠸⠋⠕⠕⠤⠃⠁⠗⠋⠸⠕⠕⠠⠃⠁⠗"] + +# Percent and permille + + - ["1%", "⠼⠁ ⠚⠴"] + +# Extra digits + + - ["1\u00bc + 1\u00bd = 2\u00be", "⠼⠁⠼⠁⠌⠙ ⠖⠼⠁⠼⠁⠌⠃ ⠶⠼⠃⠼⠉⠌⠙"] + +# Dashes + + - ["\u2014 \u0096 \u0097 \u00ad", "⠤⠤ ⠤⠤ ⠤⠤ ⠤⠤"] + +# Quotes + + - ["\u201e \u0084 \u201c \u0093 \u201d \u0094 \u00ab \u00bb", "⠶ ⠶ ⠶ ⠶ ⠶ ⠶ ⠶ ⠶"] + +# Apostrophes + + - ["` \u201a \u0082 \u2039 \u008b \u2018 \u0091 \u2019 \u0092 \u203a \u009b \u00b4", "⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈ ⠈"] + +# Times vs. bullit + + - ["\u2022Bullit", "⠘⠄⠃⠥⠇⠇⠊⠞"] + - ["2 \u00d7 2 = 4", "⠼⠃ ⠄⠼⠃ ⠶⠼⠙"] + +# Section sign + - ["§ 3", "⠬⠼⠉"] + - ["§ 3:", "⠬⠼⠉⠒"] + +# Numbers and punctuation + + - ["1,2", "⠼⠁⠂⠃"] + - ["123.456", "⠼⠁⠃⠉⠄⠙⠑⠋"] + - ["3-4", "⠼⠉⠤⠼⠙"] + - ["56-", "⠼⠑⠋⠤"] + - ["1/2", "⠼⠁⠌⠃"] + - ["12:34", "⠼⠁⠃⠒⠉⠙"] + - ["2^10", "⠼⠃⠘⠬⠁⠚"] + - ["2\u00d72", "⠼⠃⠘⠄⠼⠃"] + + - ["\"quotes\"", "⠶⠠⠟⠥⠕⠳⠎⠶"] + - ["-dashes-", "⠤⠙⠁⠎⠓⠑⠎⠤"] + - [":-) :-(", "⠒⠤⠠⠴ ⠒⠤⠠⠦"] + - [";-) ;-(", "⠆⠤⠠⠴ ⠆⠤⠠⠦"] + - ["---", "⠤⠤⠤"] + - ["-", "⠤⠤"] + - [" -", " ⠤⠤"] + - [" a-", " ⠠⠁⠤"] + - [" - ", " ⠤⠤ "] + - [" -a-", " ⠤⠠⠁⠤"] + - [" ", " "] + - ["(parentheses)", "⠦⠏⠁⠗⠣⠞⠓⠑⠎⠑⠎⠴"] + - ["J) j) %) ') \u2030) \u0089) \u201a) \u0082) \u2039) \u009b) \u2018) \u0091) \u2019) \u0092) \u203a) \u009b)", "⠨⠠⠚⠠⠴ ⠠⠚⠠⠴ ⠚⠴⠠⠴ ⠈⠠⠴ ⠚⠴⠴⠠⠴ ⠚⠴⠴⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴ ⠈⠠⠴"] + +# Exclamation + + - ["\u00a1Que lastima!", "⠠⠲⠠⠟⠥⠑ ⠇⠁⠵⠊⠍⠁⠖"] + +# Digits and letters + + - ["1a", "⠼⠁⠠⠁"] + - ["1.,-a", "⠼⠁⠄⠂⠤⠠⠁"] + +# URLs emails and file names + + - ["$at", "⠘⠲⠁⠞"] + - ["\u005c\u005cat\u005c\u005cbliver", "⠘⠡⠁⠞⠘⠡⠃⠇⠊⠧⠑⠗"] + - ["at@bliver.og", "⠁⠞⠘⠁⠃⠇⠊⠧⠑⠗⠄⠕⠛"] + - ["http://at.bliver.og", "⠓⠞⠞⠏⠒⠌⠌⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠕⠛"] + - ["www.at.bliver.og", "⠠⠺⠠⠺⠠⠺⠄⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠕⠛"] + - ["www.at.bliver.com", "⠠⠺⠠⠺⠠⠺⠄⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠉⠕⠍"] + - ["www.a.b.c", "⠠⠺⠠⠺⠠⠺⠄⠠⠁⠄⠠⠃⠄⠠⠉"] + - ["test.txt", "⠳⠵⠄⠞⠠⠭⠞"] + +# Word contractions + + - ["At", "⠁"] + - ["at", "⠁"] + - ["Aldrig", "⠁⠔"] + - ["aldrig", "⠁⠔"] + - ["aig", "⠁⠊⠛"] + - ["Alle", "⠁⠑"] + - ["alle", "⠁⠑"] + - ["ae", "⠠⠁⠑"] + - ["Allerede", "⠁⠇⠗"] + - ["allerede", "⠁⠇⠗"] + - ["alr", "⠠⠁⠇⠗"] + - ["Alligevel", "⠁⠇⠧"] + - ["alligevel", "⠁⠇⠧"] + - ["alv", "⠠⠁⠇⠧"] + - ["Altid", "⠁⠞⠙"] + - ["altid", "⠁⠞⠙"] + - ["atd", "⠠⠁⠞⠙"] + - ["Altså", "⠁⠡"] + - ["altså", "⠁⠡"] + - ["aå", "⠠⠁⠡"] + - ["Bliver", "⠃"] + - ["bliver", "⠃"] + - ["Og", "⠉"] + - ["og", "⠉"] + - ["Deres", "⠲"] + - ["deres", "⠲"] + - ["Du", "⠙"] + - ["du", "⠙"] + - ["Eller", "⠑"] + - ["eller", "⠑"] + - ["For", "⠋"] + - ["for", "⠋"] + - ["Gør", "⠛"] + - ["gør", "⠛"] + - ["Har", "⠓"] + - ["har", "⠓"] + - ["Jeg", "⠚"] + - ["jeg", "⠚"] + - ["Kan", "⠅"] + - ["kan", "⠅"] + - ["Lige", "⠇"] + - ["lige", "⠇"] + - ["Med", "⠍"] + - ["med", "⠍"] + - ["Når", "⠝"] + - ["når", "⠝"] + - ["Op", "⠕"] + - ["op", "⠕"] + - ["På", "⠏"] + - ["på", "⠏"] + - ["Under", "⠟"] + - ["under", "⠟"] + - ["Rigtig", "⠗"] + - ["rigtig", "⠗"] + - ["Som", "⠎"] + - ["som", "⠎"] + - ["Til", "⠞"] + - ["til", "⠞"] + - ["Hun", "⠥"] + - ["hun", "⠥"] + - ["Ved", "⠧"] + - ["ved", "⠧"] + - ["Hvad", "⠺"] + - ["hvad", "⠺"] + - ["Over", "⠭"] + - ["over", "⠭"] + - ["Han", "⠽"] + - ["han", "⠽"] + - ["Efter", "⠵"] + - ["efter", "⠵"] + - ["Være", "⠜"] + - ["være", "⠜"] + - ["Før", "⠪"] + - ["før", "⠪"] + - ["Så", "⠡"] + - ["så", "⠡"] + - ["Den", "⠯"] + - ["den", "⠯"] + - ["Der", "⠾"] + - ["der", "⠾"] + - ["Det", "⠮"] + - ["det", "⠮"] + - ["De", "⠹"] + - ["de", "⠹"] + - ["En", "⠣"] + - ["en", "⠣"] + - ["Er", "⠱"] + - ["er", "⠱"] + - ["Et", "⠬"] + - ["et", "⠬"] + - ["Gennem", "⠻"] + - ["gennem", "⠻"] + - ["Hvor", "⠌"] + - ["hvor", "⠌"] + - ["Men", "⠩"] + - ["men", "⠩"] + - ["Ned", "⠫"] + - ["ned", "⠫"] + - ["Ret", "⠷"] + - ["ret", "⠷"] + - ["Skal", "⠿"] + - ["skal", "⠿"] + - ["Te", "⠳"] + - ["te", "⠳"] + - ["Ve", "⠼"] + - ["ve", "⠼"] + +# Partword/nocross + + - ["Denne", "⠯⠫"] + - ["denne", "⠯⠫"] + - ["Mændene", "⠍⠜⠝⠹⠫"] + - ["mændene", "⠍⠜⠝⠹⠫"] + - ["Derhos", "⠾⠓⠕⠎"] + - ["derhos", "⠾⠓⠕⠎"] + - ["Hunderace", "⠓⠥⠝⠹⠗⠁⠉⠑"] + - ["hunderace", "⠓⠥⠝⠹⠗⠁⠉⠑"] + - ["Dette", "⠮⠳"] + - ["dette", "⠮⠳"] + - ["Detalje", "⠹⠞⠁⠇⠚⠑"] + - ["detalje", "⠹⠞⠁⠇⠚⠑"] + +# Nocross multiple cells + + - ["Endda", "⠑⠟⠙⠁"] + - ["endda", "⠑⠟⠙⠁"] + - ["Morgendag", "⠍⠭⠛⠣⠙⠁⠛"] + - ["morgendag", "⠍⠭⠛⠣⠙⠁⠛"] + - ["Gendanne", "⠛⠣⠙⠁⠝⠫"] + - ["gendanne", "⠛⠣⠙⠁⠝⠫"] + - ["Generelt", "⠻⠫⠷⠇⠞"] + - ["generelt", "⠻⠫⠷⠇⠞"] + + - ["Fra!", "⠋⠗⠁⠖"] + - ["fra!", "⠋⠗⠁⠖"] + - ["!Fra", "⠖⠋⠗⠁"] + - ["!fra", "⠖⠋⠗⠁"] + - ["'Af", "⠈⠁⠋"] + - ["'af", "⠈⠁⠋"] + + +# No single cell contractions before or after dashes + + - ["at-bliver", "⠁⠞⠤⠃"] + - ["d-d-du", "⠠⠙⠤⠠⠙⠤⠙⠥"] + +# Combinations with slashes and other punctuation signs + + - ["Han/hun", "⠽⠌⠥"] + - ["han/hun", "⠽⠌⠥"] + - ["Over/under", "⠭⠌⠟"] + - ["over/under", "⠭⠌⠟"] + - ["Til/fra", "⠞⠌⠖"] + - ["til/fra", "⠞⠌⠖"] + +# Combinations which require letsign + + - ["1st", "⠼⠁⠠⠎⠞"] + - ["2nd", "⠼⠃⠠⠝⠙"] + - ["1A", "⠼⠁⠨⠠⠁", {xfail: true}] # unclear spec + - ["1a", "⠼⠁⠠⠁"] + - ["2B", "⠼⠃⠨⠠⠃", {xfail: true}] # unclear spec + - ["2b", "⠼⠃⠠⠃"] + +# Multi-pass tests + + - ["~x |z", "⠘⠠⠠⠭ ⠘⠸⠠⠵"] + - ["~X |Z", "⠘⠠⠨⠠⠭ ⠘⠸⠨⠠⠵"] + - ["5É", "⠼⠑⠨⠐⠑"] + +############# +### 8-DOT ### +############# + +# ------- +# Grade 0 +# ------- + +table: {language: da, grade: 0, dots: 8, version: 1993, __assert-match: da-dk-g08_1993.ctb} +flags: {testmode: bothDirections} +tests: + +# Characters from 0x21 to 0xff + + - [' ', ' '] + - ['"', '⠶'] + - ['$', '⣲'] + - ['%', '⣚'] + - ['&', '⢯'] + - ['''', '⠈'] + - ['(', '⢦'] + - [')', '⢴'] + - ['*', '⠔'] + - ['+', '⢖'] + - [',', '⠂'] + - ['-', '⢤'] + - ['.', '⠄'] + - ['/', '⢌'] + - ['0', '⢚'] + - ['1', '⢁'] + - ['2', '⢃'] + - ['3', '⢉'] + - ['4', '⢙'] + - ['5', '⢑'] + - ['6', '⢋'] + - ['7', '⢛'] + - ['8', '⢓'] + - ['9', '⢊'] + - [':', '⠒'] + - [';', '⠆'] + - ['<', '⢔'] + - ['=', '⢶'] + - ['>', '⡢'] + - ['?', '⠢'] + - ['@', '⣈'] + - ['A', '⡁'] + - ['B', '⡃'] + - ['C', '⡉'] + - ['D', '⡙'] + - ['E', '⡑'] + - ['F', '⡋'] + - ['G', '⡛'] + - ['H', '⡓'] + - ['I', '⡊'] + - ['J', '⡚'] + - ['K', '⡅'] + - ['L', '⡇'] + - ['M', '⡍'] + - ['N', '⡝'] + - ['O', '⡕'] + - ['P', '⡏'] + - ['Q', '⡟'] + - ['R', '⡗'] + - ['S', '⡎'] + - ['T', '⡞'] + - ['U', '⡥'] + - ['V', '⡧'] + - ['W', '⡺'] + - ['X', '⡭'] + - ['Y', '⡽'] + - ['Z', '⡵'] + - ['[', '⣦'] + - ['\\', '⡌'] + - [']', '⣴'] + - ['^', '⢏'] + - ['_', '⣤'] + - ['`', '⠐'] + - ['a', '⠁'] + - ['b', '⠃'] + - ['c', '⠉'] + - ['d', '⠙'] + - ['e', '⠑'] + - ['f', '⠋'] + - ['g', '⠛'] + - ['h', '⠓'] + - ['i', '⠊'] + - ['j', '⠚'] + - ['k', '⠅'] + - ['l', '⠇'] + - ['m', '⠍'] + - ['n', '⠝'] + - ['o', '⠕'] + - ['p', '⠏'] + - ['q', '⠟'] + - ['r', '⠗'] + - ['s', '⠎'] + - ['t', '⠞'] + - ['u', '⠥'] + - ['v', '⠧'] + - ['w', '⠺'] + - ['x', '⠭'] + - ['y', '⠽'] + - ['z', '⠵'] + - ['{', '⣧'] + - ['|', '⢸'] + - ['}', '⣼'] + - ['~', '⡨'] + - ['€', '⣑'] + - ['‚', '⡘'] + - ['ƒ', '⢐'] + - ['„', '⣆'] + - ['…', '⠠'] + - ['†', '⡖'] + - ['‡', '⣖'] + - ['ˆ', '⣰'] + - ['‰', '⣺'] + - ['Š', '⣎'] + - ['‹', '⠸'] + - ['Œ', '⣕'] + - ['Ž', '⡬'] + - ['‘', '⡈'] + - ['’', '⢈'] + - ['“', '⡆'] + - ['”', '⢰'] + - ['•', '⡄'] + - ['–', '⠤'] + - ['—', '⡤'] + - ['˜', '⠨'] + - ['™', '⣞'] + - ['š', '⢎'] + - ['›', '⡸'] + - ['œ', '⢕'] + - ['ž', '⠬'] + - ['Ÿ', '⣾'] + - [' ', '⢞'] + - ['¡', '⠲'] + - ['¢', '⣒'] + - ['£', '⢇'] + - ['¤', '⡦'] + - ['¥', '⡠'] + - ['¦', '⣌'] + - ['§', '⣐'] + - ['¨', '⠰'] + - ['©', '⣭'] + - ['ª', '⣮'] + - ['«', '⡐'] + - ['¬', '⡼'] + - ['­', '⣄'] + - ['®', '⣗'] + - ['¯', '⡶'] + - ['°', '⠴'] + - ['±', '⢟'] + - ['²', '⢆'] + - ['³', '⢒'] + - ['´', '⢨'] + - ['µ', '⠦'] + - ['¶', '⢿'] + - ['·', '⢄'] + - ['¸', '⣨'] + - ['¹', '⢂'] + - ['º', '⣿'] + - ['»', '⡰'] + - ['¼', '⢝'] + - ['½', '⢘'] + - ['¾', '⠼'] + - ['À', '⡷'] + - ['Á', '⣷'] + - ['Â', '⣡'] + - ['Ã', '⣩'] + - ['Ä', '⣜'] + - ['Å', '⡡'] + - ['Æ', '⡜'] + - ['Ç', '⡯'] + - ['È', '⡮'] + - ['É', '⡿'] + - ['Ê', '⡣'] + - ['Ë', '⡫'] + - ['Ì', '⣱'] + - ['Í', '⣣'] + - ['Î', '⡩'] + - ['Ï', '⡻'] + - ['Ð', '⣽'] + - ['Ñ', '⣻'] + - ['Ò', '⣫'] + - ['Ó', '⣬'] + - ['Ô', '⡹'] + - ['Õ', '⣹'] + - ['Ö', '⣪'] + - ['×', '⢭'] + - ['Ø', '⡪'] + - ['Ù', '⡾'] + - ['Ú', '⣳'] + - ['Û', '⡱'] + - ['Ü', '⡳'] + - ['Ý', '⣍'] + - ['Þ', '⣅'] + - ['ß', '⢮'] + - ['à', '⠷'] + - ['á', '⢷'] + - ['â', '⢡'] + - ['ã', '⢩'] + - ['ä', '⢜'] + - ['å', '⠡'] + - ['æ', '⠜'] + - ['ç', '⠯'] + - ['è', '⠮'] + - ['é', '⠿'] + - ['ê', '⠣'] + - ['ë', '⠫'] + - ['ì', '⢱'] + - ['í', '⢣'] + - ['î', '⠩'] + - ['ï', '⠻'] + - ['ð', '⢽'] + - ['ñ', '⢻'] + - ['ò', '⢫'] + - ['ó', '⢬'] + - ['ô', '⠹'] + - ['õ', '⢹'] + - ['ö', '⢪'] + - ['÷', '⢲'] + - ['ø', '⠪'] + - ['ù', '⠾'] + - ['ú', '⢳'] + - ['û', '⠱'] + - ['ü', '⠳'] + - ['ý', '⢍'] + - ['þ', '⢅'] + - ['ÿ', '⢾'] + +# Pangram + + - - 'Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Walther spillede på xylofon.' + - '⡟⠥⠊⠵⠙⠑⠇⠞⠁⠛⠑⠗⠝⠑ ⠎⠏⠊⠎⠞⠑ ⠚⠕⠗⠙⠃⠜⠗ ⠍⠑⠙ ⠋⠇⠪⠙⠑⠂ ⠍⠑⠝⠎ ⠉⠊⠗⠅⠥⠎⠅⠇⠕⠧⠝⠑⠝ ⡺⠁⠇⠞⠓⠑⠗ ⠎⠏⠊⠇⠇⠑⠙⠑ ⠏⠡ ⠭⠽⠇⠕⠋⠕⠝⠄' + +# ------- +# Grade 1 +# ------- + +table: {language: da, grade: 1, dots: 8, version: 1993, __assert-match: da-dk-g18_1993.ctb} +flags: {testmode: bothDirections} +tests: + +# Characters from 0x21 to 0xff + + - [' ', ' '] + - ['"', '⠶'] + - ['$', '⣲'] + - ['%', '⣚'] + - ['&', '⢯'] + - ['''', '⠈'] + - ['(', '⢦'] + - [')', '⢴'] + - ['*', '⠔'] + - ['+', '⢖'] + - [',', '⠂'] + - ['-', '⢤'] + - ['.', '⠄'] + - ['/', '⢌'] + - ['0', '⢚'] + - ['1', '⢁'] + - ['2', '⢃'] + - ['3', '⢉'] + - ['4', '⢙'] + - ['5', '⢑'] + - ['6', '⢋'] + - ['7', '⢛'] + - ['8', '⢓'] + - ['9', '⢊'] + - [':', '⠒'] + - [';', '⠆'] + - ['<', '⢔'] + - ['=', '⢶'] + - ['>', '⡢'] + - ['?', '⠢'] + - ['@', '⣈'] + - ['A', '⡁'] + - ['B', '⡃'] + - ['C', '⡉'] + - ['D', '⡙'] + - ['E', '⡑'] + - ['F', '⡋'] + - ['G', '⡛'] + - ['H', '⡓'] + - ['I', '⡊'] + - ['J', '⡚'] + - ['K', '⡅'] + - ['L', '⡇'] + - ['M', '⡍'] + - ['N', '⡝'] + - ['O', '⡕'] + - ['P', '⡏'] + - ['Q', '⡟'] + - ['R', '⡗'] + - ['S', '⡎'] + - ['T', '⡞'] + - ['U', '⡥'] + - ['V', '⡧'] + - ['W', '⡺'] + - ['X', '⡭'] + - ['Y', '⡽'] + - ['Z', '⡵'] + - ['[', '⣦'] + - ['\\', '⡌'] + - [']', '⣴'] + - ['^', '⢏'] + - ['_', '⣤'] + - ['`', '⠐'] + - ['a', '⠁'] + - ['b', '⠃'] + - ['c', '⠉'] + - ['d', '⠙'] + - ['e', '⠑'] + - ['f', '⠋'] + - ['g', '⠛'] + - ['h', '⠓'] + - ['i', '⠊'] + - ['j', '⠚'] + - ['k', '⠅'] + - ['l', '⠇'] + - ['m', '⠍'] + - ['n', '⠝'] + - ['o', '⠕'] + - ['p', '⠏'] + - ['q', '⠟'] + - ['r', '⠗'] + - ['s', '⠎'] + - ['t', '⠞'] + - ['u', '⠥'] + - ['v', '⠧'] + - ['w', '⠺'] + - ['x', '⠭'] + - ['y', '⠽'] + - ['z', '⠵'] + - ['{', '⣧'] + - ['|', '⢸'] + - ['}', '⣼'] + - ['~', '⡨'] + - ['€', '⣑'] + - ['‚', '⡘'] + - ['ƒ', '⢐'] + - ['„', '⣆'] + - ['…', '⠠⠄⠄⠄'] + - ['†', '⡖'] + - ['‡', '⣖'] + - ['ˆ', '⣰'] + - ['‰', '⣺'] + - ['Š', '⣎'] + - ['‹', '⠸'] + - ['Œ', '⣕'] + - ['Ž', '⡬'] + - ['‘', '⡈'] + - ['’', '⢈'] + - ['“', '⡆'] + - ['”', '⢰'] + - ['•', '⡄'] + - ['–', '⠠⠤'] + - ['—', '⠠⡤'] + - ['˜', '⠨'] + - ['™', '⣞'] + - ['š', '⢎'] + - ['›', '⡸'] + - ['œ', '⢕'] + - ['ž', '⠬'] + - ['Ÿ', '⣾'] + - [' ', '⢞'] + - ['¡', '⠲'] + - ['¢', '⣒'] + - ['£', '⢇'] + - ['¤', '⡦'] + - ['¥', '⡠'] + - ['¦', '⣌'] + - ['§', '⣐'] + - ['¨', '⠰'] + - ['©', '⣭'] + - ['ª', '⣮'] + - ['«', '⡐'] + - ['¬', '⡼'] + - ['­', '⣄'] + - ['®', '⣗'] + - ['¯', '⡶'] + - ['°', '⠴'] + - ['±', '⢟'] + - ['²', '⢆'] + - ['³', '⢒'] + - ['´', '⢨'] + - ['µ', '⠦'] + - ['¶', '⢿'] + - ['·', '⢄'] + - ['¸', '⣨'] + - ['¹', '⢂'] + - ['º', '⣿'] + - ['»', '⡰'] + - ['¼', '⢝'] + - ['½', '⢘'] + - ['¾', '⠼'] + - ['À', '⡷'] + - ['Á', '⣷'] + - ['Â', '⣡'] + - ['Ã', '⣩'] + - ['Ä', '⣜'] + - ['Å', '⡡'] + - ['Æ', '⡜'] + - ['Ç', '⡯'] + - ['È', '⡮'] + - ['É', '⡿'] + - ['Ê', '⡣'] + - ['Ë', '⡫'] + - ['Ì', '⣱'] + - ['Í', '⣣'] + - ['Î', '⡩'] + - ['Ï', '⡻'] + - ['Ð', '⣽'] + - ['Ñ', '⣻'] + - ['Ò', '⣫'] + - ['Ó', '⣬'] + - ['Ô', '⡹'] + - ['Õ', '⣹'] + - ['Ö', '⣪'] + - ['×', '⢭'] + - ['Ø', '⡪'] + - ['Ù', '⡾'] + - ['Ú', '⣳'] + - ['Û', '⡱'] + - ['Ü', '⡳'] + - ['Ý', '⣍'] + - ['Þ', '⣅'] + - ['ß', '⢮'] + - ['à', '⠷'] + - ['á', '⢷'] + - ['â', '⢡'] + - ['ã', '⢩'] + - ['ä', '⢜'] + - ['å', '⠡'] + - ['æ', '⠜'] + - ['ç', '⠯'] + - ['è', '⠮'] + - ['é', '⠿'] + - ['ê', '⠣'] + - ['ë', '⠫'] + - ['ì', '⢱'] + - ['í', '⢣'] + - ['î', '⠩'] + - ['ï', '⠻'] + - ['ð', '⢽'] + - ['ñ', '⢻'] + - ['ò', '⢫'] + - ['ó', '⢬'] + - ['ô', '⠹'] + - ['õ', '⢹'] + - ['ö', '⢪'] + - ['÷', '⢲'] + - ['ø', '⠪'] + - ['ù', '⠾'] + - ['ú', '⢳'] + - ['û', '⠱'] + - ['ü', '⠳'] + - ['ý', '⢍'] + - ['þ', '⢅'] + - ['ÿ', '⢾'] + +# Misc Unicode chars +# For each accented letter, the first occurring instance is chosen for back-translation. +# There are no rules about this in the specs of Danish Braille, +# So the choice is arbitrary and may change over time. + + - ['\x0100\x0101', '⠐⡁⠐⠁'] + - ['\x0106\x0107', '⠐⡉⠐⠉'] + - ['\x010e\x010f', '⠐⡙⠐⠙'] + - ['\x0112\x0113', '⠐⡑⠐⠑'] + - ['\x011c\x011d', '⠐⡛⠐⠛'] + - ['\x0124\x0125', '⠐⡓⠐⠓'] + - ['\x0128\x0129', '⠐⡊⠐⠊'] + - ['\x0134\x0135', '⠐⡚⠐⠚'] + - ['\x0136\x0137', '⠐⡅⠐⠅'] + - ['\x0138', '⠐⠟'] + - ['\x0139\x013a', '⠐⡇⠐⠇'] + - ['\x0143\x0144', '⠐⡝⠐⠝'] + - ['\x014c\x014d', '⠐⡕⠐⠕'] + - ['\x0154\x0155', '⠐⡗⠐⠗'] + - ['\x015a\x015b', '⠐⡎⠐⠎'] + - ['\x0162\x0163', '⠐⡞⠐⠞'] + - ['\x0168\x0169', '⠐⡥⠐⠥'] + - ['\x0174\x0175', '⠐⡺⠐⠺'] + - ['\x0176\x0177', '⠐⡽⠐⠽'] + - ['\x0179\x017a', '⠐⡵⠐⠵'] + +# Greek letters (with dots 458 as prefix) + + - ['\x0386\x03ac', '⢘⠐⡁⢘⠐⠁'] + - ['\x0388\x03ad', '⢘⠐⡑⢘⠐⠑'] + - ['\x0389\x03ae', '⢘⠐⡱⢘⠐⠱'] + - ['\x038a\x03af', '⢘⠐⡊⢘⠐⠊'] + - ['\x038c\x03cc', '⢘⠐⡕⢘⠐⠕'] + - ['\x038e\x03cd', '⢘⠐⡥⢘⠐⠥'] + - ['\x038f\x03ce', '⢘⠐⡺⢘⠐⠺'] + - ['\x0391\x03b1', '⢘⡁⢘⠁'] + - ['\x0392\x03b2', '⢘⡃⢘⠃'] + - ['\x0393\x03b3', '⢘⡛⢘⠛'] + - ['\x0394\x03b4', '⢘⡙⢘⠙'] + - ['\x0395\x03b5', '⢘⡑⢘⠑'] + - ['\x0396\x03b6', '⢘⡵⢘⠵'] + - ['\x0397\x03b7', '⢘⡱⢘⠱'] + - ['\x0398\x03b8', '⢘⡹⢘⠹'] + - ['\x0399\x03b9', '⢘⡊⢘⠊'] + - ['\x039a\x03ba', '⢘⡅⢘⠅'] + - ['\x039b\x03bb', '⢘⡇⢘⠇'] + - ['\x039c\x03bc', '⢘⡍⢘⠍'] + - ['\x039d\x03bd', '⢘⡝⢘⠝'] + - ['\x039e\x03be', '⢘⡭⢘⠭'] + - ['\x039f\x03bf', '⢘⡕⢘⠕'] + - ['\x03a0\x03c0', '⢘⡏⢘⠏'] + - ['\x03a1\x03c1', '⢘⡗⢘⠗'] + - ['\x03a3\x03c3', '⢘⡎⢘⠎'] + - ['\x03a4\x03c4', '⢘⡞⢘⠞'] + - ['\x03a5\x03c5', '⢘⡥⢘⠥'] + - ['\x03a6\x03c6', '⢘⡋⢘⠋'] + - ['\x03a7\x03c7', '⢘⡯⢘⠯'] + - ['\x03a8\x03c8', '⢘⡽⢘⠽'] + - ['\x03a9\x03c9', '⢘⡺⢘⠺'] + +# Punctuation and bullits + + - ['\x2016', '⠘⢸'] + - ['\x2017', '⠘⣤'] + +# Arrows + + - ['\x2190', '⠘⠳⠪'] + - ['\x2191', '⠘⠳⠬'] + - ['\x2192', '⠘⠳⠕'] + - ['\x2193', '⠘⠳⠩'] + - ['\x2194', '⠘⠳⠺⠗⠕'] + - ['\x2196', '⠘⠳⠱'] + - ['\x2197', '⠘⠳⠎'] + - ['\x2198', '⠘⠳⠣'] + - ['\x2199', '⠘⠳⠜'] + - ['\x21D4', '⠘⠳⠺⠶⠗⠕'] + +# Math signs (experimental) + + - ['\x2200', '⠘⠁'] + - ['\x2208', '⠘⠑'] + - ['\x2213', '⠸⠤'] + - ['\x221d', '⠸⠐⢶'] + - ['\x2229', '⠨⠦'] + - ['\x222a', '⠨⠖'] + - ['\x2243', '⠸⠔'] + - ['\x2245', '⠐⠘⠔'] + - ['\x2248', '⠘⠔'] + - ['\x224f', '⠘⠐⢶'] + - ['\x2251', '⠨⠐⢶'] + - ['\x2260', '⠐⢶⠈⠱'] + - ['\x2261', '⠸⠿'] + - ['\x2264', '⠸⢔'] + - ['\x2265', '⠸⡢'] + - ['\x226a', '⠨⢔'] + - ['\x226b', '⠨⡢'] + - ['\x22c5', '⠐⠲'] + +# Pangram + + - - 'Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Walther spillede på xylofon.' + - '⡟⠥⠊⠵⠙⠑⠇⠞⠁⠛⠑⠗⠝⠑ ⠎⠏⠊⠎⠞⠑ ⠚⠕⠗⠙⠃⠜⠗ ⠍⠑⠙ ⠋⠇⠪⠙⠑⠂ ⠍⠑⠝⠎ ⠉⠊⠗⠅⠥⠎⠅⠇⠕⠧⠝⠑⠝ ⡺⠁⠇⠞⠓⠑⠗ ⠎⠏⠊⠇⠇⠑⠙⠑ ⠏⠡ ⠭⠽⠇⠕⠋⠕⠝⠄' + +# Section sign + + - ["§ 3", "⣐⢉"] + - ["§ 3:", "⣐⢉⠒"] + +# Characters and constructs which cannot be properly back-translated + +flags: {testmode: forward} +tests: + + - ['\x0102\x0103', '⠐⡁⠐⠁'] + - ['\x0104\x0105', '⠐⡁⠐⠁'] + - ['\x0108\x0109', '⠐⡉⠐⠉'] + - ['\x010a\x010b', '⠐⡉⠐⠉'] + - ['\x010c\x010d', '⠐⡉⠐⠉'] + - ['\x0110\x0111', '⠐⡙⠐⠙'] + - ['\x0114\x0115', '⠐⡑⠐⠑'] + - ['\x0116\x0117', '⠐⡑⠐⠑'] + - ['\x0118\x0119', '⠐⡑⠐⠑'] + - ['\x011a\x011b', '⠐⡑⠐⠑'] + - ['\x011e\x011f', '⠐⡛⠐⠛'] + - ['\x0120\x0121', '⠐⡛⠐⠛'] + - ['\x0122\x0123', '⠐⡛⠐⠛'] + - ['\x0126\x0127', '⠐⡓⠐⠓'] + - ['\x012a\x012b', '⠐⡊⠐⠊'] + - ['\x012c\x012d', '⠐⡊⠐⠊'] + - ['\x012e\x012f', '⠐⡊⠐⠊'] + - ['\x0130\x0131', '⠐⡊⠐⠊'] + - ['\x0132\x0133', '⡊⠚⠊⠚'] + - ['\x013b\x013c', '⠐⡇⠐⠇'] + - ['\x013d\x013e', '⠐⡇⠐⠇'] + - ['\x013f\x0140', '⠐⡇⠐⠇'] + - ['\x0141\x0142', '⠐⡇⠐⠇'] + - ['\x0145\x0146', '⠐⡝⠐⠝'] + - ['\x0147\x0148', '⠐⡝⠐⠝'] + - ['\x0149', '⠈⠝'] + - ['\x014a\x014b', '⠐⡝⠐⠝'] + - ['\x014e\x014f', '⠐⡕⠐⠕'] + - ['\x0150\x0151', '⠐⡕⠐⠕'] + - ['\x0156\x0157', '⠐⡗⠐⠗'] + - ['\x0158\x0159', '⠐⡗⠐⠗'] + - ['\x015c\x015d', '⠐⡎⠐⠎'] + - ['\x015e\x015f', '⠐⡎⠐⠎'] + - ['\x0164\x0165', '⠐⡞⠐⠞'] + - ['\x0166\x0167', '⠐⡞⠐⠞'] + - ['\x016a\x016b', '⠐⡥⠐⠥'] + - ['\x016c\x016d', '⠐⡥⠐⠥'] + - ['\x016e\x016f', '⠐⡥⠐⠥'] + - ['\x0170\x0171', '⠐⡥⠐⠥'] + - ['\x0172\x0173', '⠐⡥⠐⠥'] + - ['\x017b\x017c', '⠐⡵⠐⠵'] + - ['\x017f', '⠐⠎'] + +# Punctuation and bullits + + - ['\x2000', ' '] + - ['\x2001', ' '] + - ['\x2002', ' '] + - ['\x2003', ' '] + - ['\x2004', ' '] + - ['\x2005', ' '] + - ['\x2006', ' '] + - ['\x2007', ' '] + - ['\x2008', ' '] + - ['\x2009', ' '] + - ['\x200a', ' '] + - ['\x2010', '⢤'] + - ['\x2011', '⢤'] + - ['\x2012', '⢤'] + - ['\x201b', '⠈'] + - ['\x201f', '⠶'] + - ['\x2023', '⡄'] + - ['\x202f', ' '] + - ['\x203c', '⠖⠖'] + - ['\x203d', '⠢⠖'] + - ['\x2043', '⡄'] + - ['\x2047', '⠢⠢'] + - ['\x2048', '⠢⠖'] + - ['\x2049', '⠖⠢'] + - ['\x204c', '⡄'] + - ['\x204d', '⡄'] + +# Geometrical shapes + + - ['\x25e6', '⡄'] + +# Emphasis + + - # Bold line + - En linje med fed. + - ⠰⡑⠝ ⠇⠊⠝⠚⠑ ⠍⠑⠙ ⠋⠑⠙⠄⠰ + - typeform: + bold: '+++++++++++++++++' + - # Bold word + - Et ord med fed. + - ⡑⠞ ⠰⠕⠗⠙⠰ ⠍⠑⠙ ⠋⠑⠙⠄ + - typeform: + bold: ' +++ ' + - # Bold letter + - Et bogstav med fed. + - ⡑⠞ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍⠑⠙ ⠋⠑⠙⠄ + - typeform: + bold: ' + ' + + - # Italic line + - En linje med kursiv. + - ⠰⡑⠝ ⠇⠊⠝⠚⠑ ⠍⠑⠙ ⠅⠥⠗⠎⠊⠧⠄⠰ + - typeform: + italic: '++++++++++++++++++++' + - # Italic word + - Et ord med kursiv. + - ⡑⠞ ⠰⠕⠗⠙⠰ ⠍⠑⠙ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' +++ ' + - # Italic letter + - Et bogstav med kursiv. + - ⡑⠞ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍⠑⠙ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' + ' + + - # Underlined line + - En linje med understreget. + - ⠰⡑⠝ ⠇⠊⠝⠚⠑ ⠍⠑⠙ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄⠰ + - typeform: + underline: '++++++++++++++++++++++++++' + - # Underlined word + - Et ord med understreget. + - ⡑⠞ ⠰⠕⠗⠙⠰ ⠍⠑⠙ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄ + - typeform: + underline: ' +++ ' + - # Underlined letter + - Et bogstav med understreget. + - ⡑⠞ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍⠑⠙ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄ + - typeform: + underline: ' + ' + + - # Bold, italic and underlined line + - En linje med alt. + - ⠰⡑⠝ ⠇⠊⠝⠚⠑ ⠍⠑⠙ ⠁⠇⠞⠄⠰ + - typeform: + bold: '+++++++++++++++++' + italic: '+++++++++++++++++' + underline: '+++++++++++++++++' + - # Bold, italic and underlined word + - Et ord med alt. + - ⡑⠞ ⠰⠕⠗⠙⠰ ⠍⠑⠙ ⠁⠇⠞⠄ + - typeform: + bold: ' +++ ' + italic: ' +++ ' + underline: ' +++ ' + - # Bold, italic and underlined letter + - Et bogstav med alt. + - ⡑⠞ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍⠑⠙ ⠁⠇⠞⠄ + - typeform: + bold: ' + ' + italic: ' + ' + underline: ' + ' + +# --------- +# Grade 1.5 +# --------- + +table: {language: da, grade: 1.5, dots: 8, version: 1993, __assert-match: da-dk-g28l_1993.ctb} +flags: {testmode: bothDirections} +tests: + +# Characters from 0x21 to 0xff + + - [' ', ' '] + - ['"', '⠶'] + - ['$', '⣲'] + - ['%', '⣚'] + - ['&', '⢯'] + - ['''', '⠈'] + - ['(', '⢦'] + - [')', '⢴'] + - ['*', '⠠⠔'] + - ['+', '⢖'] + - [',', '⠂'] + - ['-', '⢤'] + - ['.', '⠄'] + - ['/', '⢌'] + - ['0', '⢚'] + - ['1', '⢁'] + - ['2', '⢃'] + - ['3', '⢉'] + - ['4', '⢙'] + - ['5', '⢑'] + - ['6', '⢋'] + - ['7', '⢛'] + - ['8', '⢓'] + - ['9', '⢊'] + - [':', '⠒'] + - [';', '⠆'] + - ['<', '⢔'] + - ['=', '⢶'] + - ['>', '⡢'] + - ['?', '⠢'] + - ['@', '⣈'] + - ['A', '⠠⡁'] + - ['B', '⠠⡃'] + - ['C', '⠠⡉'] + - ['D', '⠠⡙'] + - ['E', '⠠⡑'] + - ['F', '⠠⡋'] + - ['G', '⠠⡛'] + - ['H', '⠠⡓'] + - ['I', '⡊'] + - ['J', '⠠⡚'] + - ['K', '⠠⡅'] + - ['L', '⠠⡇'] + - ['M', '⠠⡍'] + - ['N', '⠠⡝'] + - ['O', '⠠⡕'] + - ['P', '⠠⡏'] + - ['Q', '⠠⡟'] + - ['R', '⠠⡗'] + - ['S', '⠠⡎'] + - ['T', '⠠⡞'] + - ['U', '⠠⡥'] + - ['V', '⠠⡧'] + - ['W', '⠠⡺'] + - ['X', '⠠⡭'] + - ['Y', '⠠⡽'] + - ['Z', '⠠⡵'] + - ['[', '⣦'] + - ['\\', '⠠⡌'] + - [']', '⣴'] + - ['^', '⢏'] + - ['_', '⣤'] + - ['`', '⠐'] + - ['a', '⠠⠁'] + - ['b', '⠠⠃'] + - ['c', '⠠⠉'] + - ['d', '⠠⠙'] + - ['e', '⠠⠑'] + - ['f', '⠠⠋'] + - ['g', '⠠⠛'] + - ['h', '⠠⠓'] + - ['i', '⠊'] + - ['j', '⠠⠚'] + - ['k', '⠠⠅'] + - ['l', '⠠⠇'] + - ['m', '⠠⠍'] + - ['n', '⠠⠝'] + - ['o', '⠠⠕'] + - ['p', '⠠⠏'] + - ['q', '⠠⠟'] + - ['r', '⠠⠗'] + - ['s', '⠠⠎'] + - ['t', '⠠⠞'] + - ['u', '⠠⠥'] + - ['v', '⠠⠧'] + - ['w', '⠠⠺'] + - ['x', '⠠⠭'] + - ['y', '⠠⠽'] + - ['z', '⠠⠵'] + - ['{', '⣧'] + - ['|', '⢸'] + - ['}', '⣼'] + - ['~', '⡨'] + - ['€', '⣑'] + - ['‚', '⡘'] + - ['ƒ', '⢐'] + - ['„', '⣆'] + - ['…', '⠠⠄⠄⠄'] + - ['†', '⠠⡖'] + - ['‡', '⠠⣖'] + - ['ˆ', '⠠⣰'] + - ['‰', '⣺'] + - ['Š', '⠠⣎'] + - ['‹', '⠸'] + - ['Œ', '⠠⣕'] + - ['Ž', '⠠⡬'] + - ['‘', '⡈'] + - ['’', '⢈'] + - ['“', '⡆'] + - ['”', '⢰'] + - ['•', '⡄'] + - ['–', '⠠⠤'] + - ['—', '⠠⡤'] + - ['˜', '⠨'] + - ['™', '⣞'] + - ['š', '⠠⢎'] + - ['›', '⡸'] + - ['œ', '⠠⢕'] + - ['ž', '⠠⠬'] + - ['Ÿ', '⠠⣾'] + - [' ', '⢞'] + - ['¡', '⠠⠲', {xfail: true}] + - ['¢', '⣒'] + - ['£', '⢇'] + - ['¤', '⠠⡦'] + - ['¥', '⡠'] + - ['¦', '⣌'] + - ['§', '⣐'] + - ['¨', '⠠⠰'] + - ['©', '⣭'] + - ['ª', '⠠⣮'] + - ['«', '⡐'] + - ['¬', '⠠⡼'] + - ['­', '⠠⣄'] + - ['®', '⣗'] + - ['¯', '⡶'] + - ['°', '⠠⠴', {xfail: true}] + - ['±', '⢟'] + - ['²', '⢆'] + - ['³', '⢒'] + - ['´', '⢨'] + - ['µ', '⠠⠦'] + - ['¶', '⢿'] + - ['·', '⢄'] + - ['¸', '⣨'] + - ['¹', '⢂'] + - ['º', '⠠⣿'] + - ['»', '⡰'] + - ['¼', '⢝'] + - ['½', '⢘'] + - ['¾', '⠠⠼'] + - ['À', '⠠⡷'] + - ['Á', '⠠⣷'] + - ['Â', '⠠⣡'] + - ['Ã', '⠠⣩'] + - ['Ä', '⠠⣜'] + - ['Å', '⠠⡡'] + - ['Æ', '⠠⡜'] + - ['Ç', '⠠⡯'] + - ['È', '⠠⡮'] + - ['É', '⠠⡿'] + - ['Ê', '⠠⡣'] + - ['Ë', '⠠⡫'] + - ['Ì', '⠠⣱'] + - ['Í', '⠠⣣'] + - ['Î', '⠠⡩'] + - ['Ï', '⠠⡻'] + - ['Ð', '⠠⣽'] + - ['Ñ', '⠠⣻'] + - ['Ò', '⠠⣫'] + - ['Ó', '⠠⣬'] + - ['Ô', '⠠⡹'] + - ['Õ', '⠠⣹'] + - ['Ö', '⠠⣪'] + - ['×', '⢭'] + - ['Ø', '⠠⡪'] + - ['Ù', '⠠⡾'] + - ['Ú', '⠠⣳'] + - ['Û', '⠠⡱'] + - ['Ü', '⠠⡳'] + - ['Ý', '⠠⣍'] + - ['Þ', '⠠⣅'] + - ['ß', '⠠⢮'] + - ['à', '⠠⠷'] + - ['á', '⠠⢷'] + - ['â', '⠠⢡'] + - ['ã', '⠠⢩'] + - ['ä', '⠠⢜'] + - ['å', '⠠⠡'] + - ['æ', '⠠⠜'] + - ['ç', '⠠⠯'] + - ['è', '⠠⠮'] + - ['é', '⠠⠿'] + - ['ê', '⠠⠣'] + - ['ë', '⠠⠫'] + - ['ì', '⠠⢱'] + - ['í', '⠠⢣'] + - ['î', '⠠⠩'] + - ['ï', '⠠⠻'] + - ['ð', '⠠⢽'] + - ['ñ', '⠠⢻'] + - ['ò', '⠠⢫'] + - ['ó', '⠠⢬'] + - ['ô', '⠠⠹'] + - ['õ', '⠠⢹'] + - ['ö', '⠠⢪'] + - ['÷', '⢲'] + - ['ø', '⠠⠪'] + - ['ù', '⠠⠾'] + - ['ú', '⠠⢳'] + - ['û', '⠠⠱'] + - ['ü', '⠠⠳'] + - ['ý', '⠠⢍'] + - ['þ', '⠠⢅'] + - ['ÿ', '⠠⢾'] + +# Misc Unicode chars +# For each accented letter, the first occurring instance is chosen for back-translation. +# There are no rules about this in the specs of Danish Braille, +# So the choice is arbitrary and may change over time. + + - ['\x0100\x0101', '⠐⡁⠐⠁'] + - ['\x0106\x0107', '⠐⡉⠐⠉'] + - ['\x010e\x010f', '⠐⡙⠐⠙'] + - ['\x0112\x0113', '⠐⡑⠐⠑'] + - ['\x011c\x011d', '⠐⡛⠐⠛'] + - ['\x0124\x0125', '⠐⡓⠐⠓'] + - ['\x0128\x0129', '⠐⡊⠐⠊'] + - ['\x0134\x0135', '⠐⡚⠐⠚'] + - ['\x0136\x0137', '⠐⡅⠐⠅'] + - ['\x0138', '⠐⠟'] + - ['\x0139\x013a', '⠐⡇⠐⠇'] + - ['\x0143\x0144', '⠐⡝⠐⠝'] + - ['\x014c\x014d', '⠐⡕⠐⠕'] + - ['\x0154\x0155', '⠐⡗⠐⠗'] + - ['\x015a\x015b', '⠐⡎⠐⠎'] + - ['\x0162\x0163', '⠐⡞⠐⠞'] + - ['\x0168\x0169', '⠐⡥⠐⠥'] + - ['\x0174\x0175', '⠐⡺⠐⠺'] + - ['\x0176\x0177', '⠐⡽⠐⠽'] + - ['\x0179\x017a', '⠐⡵⠐⠵'] + +# Greek letters (with dots 458 as prefix) + + - ['\x0386\x03ac', '⢘⠐⡁⢘⠐⠁', {xfail: {forward: true}}] + - ['\x0388\x03ad', '⢘⠐⡑⢘⠐⠑', {xfail: {forward: true}}] + - ['\x0389\x03ae', '⢘⠐⡱⢘⠐⠱', {xfail: {forward: true}}] + - ['\x038a\x03af', '⢘⠐⡊⢘⠐⠊', {xfail: {forward: true}}] + - ['\x038c\x03cc', '⢘⠐⡕⢘⠐⠕', {xfail: {forward: true}}] + - ['\x038e\x03cd', '⢘⠐⡥⢘⠐⠥', {xfail: {forward: true}}] + - ['\x038f\x03ce', '⢘⠐⡺⢘⠐⠺', {xfail: {forward: true}}] + - ['\x0391\x03b1', '⢘⡁⢘⠁', {xfail: {forward: true}}] + - ['\x0392\x03b2', '⢘⡃⢘⠃', {xfail: {forward: true}}] + - ['\x0393\x03b3', '⢘⡛⢘⠛', {xfail: {forward: true}}] + - ['\x0394\x03b4', '⢘⡙⢘⠙', {xfail: {forward: true}}] + - ['\x0395\x03b5', '⢘⡑⢘⠑', {xfail: {forward: true}}] + - ['\x0396\x03b6', '⢘⡵⢘⠵', {xfail: {forward: true}}] + - ['\x0397\x03b7', '⢘⡱⢘⠱', {xfail: {forward: true}}] + - ['\x0398\x03b8', '⢘⡹⢘⠹', {xfail: {forward: true}}] + - ['\x0399\x03b9', '⢘⡊⢘⠊', {xfail: {forward: true}}] + - ['\x039a\x03ba', '⢘⡅⢘⠅', {xfail: {forward: true}}] + - ['\x039b\x03bb', '⢘⡇⢘⠇', {xfail: {forward: true}}] + - ['\x039c\x03bc', '⢘⡍⢘⠍', {xfail: {forward: true}}] + - ['\x039d\x03bd', '⢘⡝⢘⠝', {xfail: {forward: true}}] + - ['\x039e\x03be', '⢘⡭⢘⠭', {xfail: {forward: true}}] + - ['\x039f\x03bf', '⢘⡕⢘⠕', {xfail: {forward: true}}] + - ['\x03a0\x03c0', '⢘⡏⢘⠏', {xfail: {forward: true}}] + - ['\x03a1\x03c1', '⢘⡗⢘⠗', {xfail: {forward: true}}] + - ['\x03a3\x03c3', '⢘⡎⢘⠎', {xfail: {forward: true}}] + - ['\x03a4\x03c4', '⢘⡞⢘⠞', {xfail: {forward: true}}] + - ['\x03a5\x03c5', '⢘⡥⢘⠥', {xfail: {forward: true}}] + - ['\x03a6\x03c6', '⢘⡋⢘⠋', {xfail: {forward: true}}] + - ['\x03a7\x03c7', '⢘⡯⢘⠯', {xfail: {forward: true}}] + - ['\x03a8\x03c8', '⢘⡽⢘⠽', {xfail: {forward: true}}] + - ['\x03a9\x03c9', '⢘⡺⢘⠺', {xfail: {forward: true}}] + +# Punctuation and bullits + + - ['\x2016', '⠘⢸'] + - ['\x2017', '⠘⣤'] + +# Arrows + + - ['\x2190', '⠘⠳⠪'] + - ['\x2191', '⠘⠳⠬'] + - ['\x2192', '⠘⠳⠕'] + - ['\x2193', '⠘⠳⠩'] + - ['\x2194', '⠘⠳⠺⠗⠕'] + - ['\x2196', '⠘⠳⠱'] + - ['\x2197', '⠘⠳⠎'] + - ['\x2198', '⠘⠳⠣'] + - ['\x2199', '⠘⠳⠜'] + - ['\x21D4', '⠘⠳⠺⠶⠗⠕'] + +# Math signs (experimental) + + - ['\x2200', '⠘⠁'] + - ['\x2208', '⠘⠑'] + - ['\x2213', '⠸⠤'] + - ['\x221d', '⠸⠐⢶'] + - ['\x2229', '⠨⠦'] + - ['\x222a', '⠨⠖'] + - ['\x2243', '⠸⠔'] + - ['\x2245', '⠐⠘⠔'] + - ['\x2248', '⠘⠔'] + - ['\x224f', '⠘⠐⢶'] + - ['\x2251', '⠨⠐⢶'] + - ['\x2260', '⠐⢶⠈⠱'] + - ['\x2261', '⠸⠿'] + - ['\x2264', '⠸⢔'] + - ['\x2265', '⠸⡢'] + - ['\x226a', '⠨⢔'] + - ['\x226b', '⠨⡢'] + - ['\x22c5', '⠐⠲'] + +# Pangram + + - - 'Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Walther spillede på xylofon.' + - '⠠⡟⠥⠊⠠⠵⠙⠑⠇⠞⠁⠛⠑⠗⠝⠑ ⠎⠏⠊⠎⠞⠑ ⠚⠕⠗⠙⠃⠜⠗ ⠍ ⠋⠇⠪⠙⠑⠂ ⠍⠑⠝⠎ ⠉⠊⠗⠅⠥⠎⠅⠇⠕⠧⠝⠑⠝ ⠠⡺⠁⠇⠞⠓⠑⠗ ⠎⠏⠊⠇⠇⠑⠙⠑ ⠏ ⠠⠭⠽⠇⠕⠋⠕⠝⠄' + +# No letsign before numbers + + - ['v8', '⠠⠧⢓'] + - ['A4', '⠠⡁⢙'] + - ['Eleva2ren', '⡑⠇⠑⠧⠁⢃⠗⠑⠝'] + +# URLs emails and file names + + - ['$at', '⣲⠁⠞'] + - ['\\at\\bliver', '⠠⡌⠁⠞⠠⡌⠃⠇⠊⠧⠑⠗'] + - ['at@bliver.og', '⠁⠞⣈⠃⠇⠊⠧⠑⠗⠄⠕⠛'] + - ['http://at.bliver.og', '⠓⠞⠞⠏⠒⢌⢌⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠕⠛'] + - ['www.at.bliver.og', '⠠⠺⠠⠺⠠⠺⠄⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠕⠛'] + - ['www.at.bliver.com', '⠠⠺⠠⠺⠠⠺⠄⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠉⠕⠍'] + - ['www.a.b.c', '⠠⠺⠠⠺⠠⠺⠄⠠⠁⠄⠠⠃⠄⠠⠉'] + +# Section sign + + - ["§ 3", "⣐⢉"] + - ["§ 3:", "⣐⢉⠒"] + +# Single cell word contractions + + - ['At', '⡁'] + - ['at', '⠁'] + - ['Bliver', '⡃'] + - ['bliver', '⠃'] + - ['Og', '⡉'] + - ['og', '⠉'] + - ['Du', '⡙'] + - ['du', '⠙'] + - ['Eller', '⡑'] + - ['eller', '⠑'] + - ['For', '⡋'] + - ['for', '⠋'] + - ['Gør', '⡛'] + - ['gør', '⠛'] + - ['Har', '⡓'] + - ['har', '⠓'] + - ['Jeg', '⡚'] + - ['jeg', '⠚'] + - ['Kan', '⡅'] + - ['kan', '⠅'] + - ['Lige', '⡇'] + - ['lige', '⠇'] + - ['Med', '⡍'] + - ['med', '⠍'] + - ['Når', '⡝'] + - ['når', '⠝'] + - ['Op', '⡕'] + - ['op', '⠕'] + - ['På', '⡏'] + - ['på', '⠏'] + - ['Under', '⡟'] + - ['under', '⠟'] + - ['Rigtig', '⡗'] + - ['rigtig', '⠗'] + - ['Som', '⡎'] + - ['som', '⠎'] + - ['Til', '⡞'] + - ['til', '⠞'] + - ['Hun', '⡥'] + - ['hun', '⠥'] + - ['Ved', '⡧'] + - ['ved', '⠧'] + - ['Hvad', '⡺'] + - ['hvad', '⠺'] + - ['Over', '⡭'] + - ['over', '⠭'] + - ['Han', '⡽'] + - ['han', '⠽'] + - ['Efter', '⡵'] + - ['efter', '⠵'] + - ['Være', '⡜'] + - ['være', '⠜'] + - ['Før', '⡪'] + - ['før', '⠪'] + - ['Så', '⡡'] + - ['så', '⠡'] + - ['Den', '⡯'] + - ['den', '⠯'] + - ['Der', '⡾'] + - ['der', '⠾'] + - ['Det', '⡮'] + - ['det', '⠮'] + - ['De', '⡹'] + - ['de', '⠹'] + - ['En', '⡣'] + - ['en', '⠣'] + - ['Er', '⡱'] + - ['er', '⠱'] + - ['Et', '⡬'] + - ['et', '⠬'] + - ['Gennem', '⡻'] + - ['gennem', '⠻'] + - ['Hvor', '⡌'] + - ['hvor', '⠌'] + - ['Men', '⡩'] + - ['men', '⠩'] + - ['Ned', '⡫'] + - ['ned', '⠫'] + - ['Ret', '⡷'] + - ['ret', '⠷'] + - ['Skal', '⡿'] + - ['skal', '⠿'] + - ['Te', '⡳'] + - ['te', '⠳'] + - ['Ve', '⡼'] + - ['ve', '⠼'] + +# Capsnocont + + - ['UNDER et', '⡥⡝⡙⡑⡗ ⠬'] + +# No single cell contractions before or after dashes + + - ['at-bliver', '⠁⠞⢤⠃⠇⠊⠧⠑⠗', {xfail: {forward: true}}] + - ['d-d-du', '⠠⠙⢤⠠⠙⢤⠙⠥', {xfail: {forward: true}}] + +# Combinations with slashes and other punctuation signs + + - ["at!", "⠁⠖"] + - ["bliver!", "⠃⠖"] + - ["og!", "⠉⠖"] + - ['Han/hun', '⡽⢌⠥'] + - ['han/hun', '⠽⢌⠥'] + - ['Over/under', '⡭⢌⠟'] + - ['over/under', '⠭⢌⠟'] + - ['Til/fra', '⡞⢌⠋', {xfail: true}] + - ['til/fra', '⠞⢌⠋', {xfail: true}] + +# Combinations which require letsign + + - ['1st', '⢁⠠⠎⠞'] + - ['2nd', '⢃⠠⠝⠙', {xfail: {forward: true}}] + - ['1A', '⢁⠠⡁'] + - ['1a', '⢁⠠⠁'] + - ['2B', '⢃⠠⡃'] + - ['2b', '⢃⠠⠃'] + +# Characters and constructs which cannot be properly back-translated + +flags: {testmode: forward} +tests: + - ['\x0102\x0103', '⠐⡁⠐⠁'] + - ['\x0104\x0105', '⠐⡁⠐⠁'] + - ['\x0108\x0109', '⠐⡉⠐⠉'] + - ['\x010a\x010b', '⠐⡉⠐⠉'] + - ['\x010c\x010d', '⠐⡉⠐⠉'] + - ['\x0110\x0111', '⠐⡙⠐⠙'] + - ['\x0114\x0115', '⠐⡑⠐⠑'] + - ['\x0116\x0117', '⠐⡑⠐⠑'] + - ['\x0118\x0119', '⠐⡑⠐⠑'] + - ['\x011a\x011b', '⠐⡑⠐⠑'] + - ['\x011e\x011f', '⠐⡛⠐⠛'] + - ['\x0120\x0121', '⠐⡛⠐⠛'] + - ['\x0122\x0123', '⠐⡛⠐⠛'] + - ['\x0126\x0127', '⠐⡓⠐⠓'] + - ['\x012a\x012b', '⠐⡊⠐⠊'] + - ['\x012c\x012d', '⠐⡊⠐⠊'] + - ['\x012e\x012f', '⠐⡊⠐⠊'] + - ['\x0130\x0131', '⠐⡊⠐⠊'] + - ['\x0132\x0133', '⡊⠚⠊⠚'] + - ['\x013b\x013c', '⠐⡇⠐⠇'] + - ['\x013d\x013e', '⠐⡇⠐⠇'] + - ['\x013f\x0140', '⠐⡇⠐⠇'] + - ['\x0141\x0142', '⠐⡇⠐⠇'] + - ['\x0145\x0146', '⠐⡝⠐⠝'] + - ['\x0147\x0148', '⠐⡝⠐⠝'] + - ['\x0149', '⠈⠝'] + - ['\x014a\x014b', '⠐⡝⠐⠝'] + - ['\x014e\x014f', '⠐⡕⠐⠕'] + - ['\x0150\x0151', '⠐⡕⠐⠕'] + - ['\x0156\x0157', '⠐⡗⠐⠗'] + - ['\x0158\x0159', '⠐⡗⠐⠗'] + - ['\x015c\x015d', '⠐⡎⠐⠎'] + - ['\x015e\x015f', '⠐⡎⠐⠎'] + - ['\x0164\x0165', '⠐⡞⠐⠞'] + - ['\x0166\x0167', '⠐⡞⠐⠞'] + - ['\x016a\x016b', '⠐⡥⠐⠥'] + - ['\x016c\x016d', '⠐⡥⠐⠥'] + - ['\x016e\x016f', '⠐⡥⠐⠥'] + - ['\x0170\x0171', '⠐⡥⠐⠥'] + - ['\x0172\x0173', '⠐⡥⠐⠥'] + - ['\x017b\x017c', '⠐⡵⠐⠵'] + - ['\x017f', '⠐⠎'] + +# Punctuation and bullits + + - ['\x2000', ' '] + - ['\x2001', ' '] + - ['\x2002', ' '] + - ['\x2003', ' '] + - ['\x2004', ' '] + - ['\x2005', ' '] + - ['\x2006', ' '] + - ['\x2007', ' '] + - ['\x2008', ' '] + - ['\x2009', ' '] + - ['\x200a', ' '] + - ['\x2010', '⢤'] + - ['\x2011', '⢤'] + - ['\x2012', '⢤'] + - ['\x201b', '⠈'] + - ['\x201f', '⠶'] + - ['\x2023', '⡄'] + - ['\x202f', ' '] + - ['\x203c', '⠖⠖'] + - ['\x203d', '⠢⠖'] + - ['\x2043', '⡄'] + - ['\x2047', '⠢⠢'] + - ['\x2048', '⠢⠖'] + - ['\x2049', '⠖⠢'] + - ['\x204c', '⡄'] + - ['\x204d', '⡄'] + +# Geometrical shapes + + - ['\x25e6', '⡄'] + +# Emphasis + + - # Bold line + - En linje med fed. + - ⠰⡣ ⠇⠊⠝⠚⠑ ⠍ ⠋⠑⠙⠄⠰ + - typeform: + bold: '+++++++++++++++++' + - # Bold word + - Et ord med fed. + - ⡬ ⠰⠕⠗⠙⠰ ⠍ ⠋⠑⠙⠄ + - typeform: + bold: ' +++ ' + - # Bold letter + - Et bogstav med fed. + - ⡬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠋⠑⠙⠄ + - typeform: + bold: ' + ' + + - # Italic line + - En linje med kursiv. + - ⠰⡣ ⠇⠊⠝⠚⠑ ⠍ ⠅⠥⠗⠎⠊⠧⠄⠰ + - typeform: + italic: '++++++++++++++++++++' + - # Italic word + - Et ord med kursiv. + - ⡬ ⠰⠕⠗⠙⠰ ⠍ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' +++ ' + - # Italic letter + - Et bogstav med kursiv. + - ⡬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' + ' + + - # Underlined line + - En linje med understreget. + - ⠰⡣ ⠇⠊⠝⠚⠑ ⠍ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄⠰ + - typeform: + underline: '++++++++++++++++++++++++++' + - # Underlined word + - Et ord med understreget. + - ⡬ ⠰⠕⠗⠙⠰ ⠍ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄ + - typeform: + underline: ' +++ ' + - # Underlined letter + - Et bogstav med understreget. + - ⡬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠥⠝⠙⠑⠗⠎⠞⠗⠑⠛⠑⠞⠄ + - typeform: + underline: ' + ' + + - # Bold, italic and underlined line + - En linje med alt. + - ⠰⡣ ⠇⠊⠝⠚⠑ ⠍ ⠁⠇⠞⠄⠰ + - typeform: + bold: '+++++++++++++++++' + italic: '+++++++++++++++++' + underline: '+++++++++++++++++' + - # Bold, italic and underlined word + - Et ord med alt. + - ⡬ ⠰⠕⠗⠙⠰ ⠍ ⠁⠇⠞⠄ + - typeform: + bold: ' +++ ' + italic: ' +++ ' + underline: ' +++ ' + - # Bold, italic and underlined letter + - Et bogstav med alt. + - ⡬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠁⠇⠞⠄ + - typeform: + bold: ' + ' + italic: ' + ' + underline: ' + ' + +# ------- +# Grade 2 +# ------- + +table: {language: da, grade: 2, dots: 8, version: 1993, __assert-match: da-dk-g28_1993.ctb} +flags: {testmode: bothDirections} +tests: + +# Characters from 0x21 to 0xff + + - [' ', ' '] + - ['"', '⠶'] + - ['$', '⣲'] + - ['%', '⣚'] + - ['&', '⢯'] + - ['''', '⠈'] + - ['(', '⢦'] + - [')', '⢴'] + - ['*', '⠠⠔'] + - ['+', '⢖'] + - [',', '⠂'] + - ['-', '⢤'] + - ['.', '⠄'] + - ['/', '⢌'] + - ['0', '⢚'] + - ['1', '⢁'] + - ['2', '⢃'] + - ['3', '⢉'] + - ['4', '⢙'] + - ['5', '⢑'] + - ['6', '⢋'] + - ['7', '⢛'] + - ['8', '⢓'] + - ['9', '⢊'] + - [':', '⠒'] + - [';', '⠆'] + - ['<', '⢔'] + - ['=', '⢶'] + - ['>', '⡢'] + - ['?', '⠢'] + - ['@', '⣈'] + - ['A', '⠠⡁'] + - ['B', '⠠⡃'] + - ['C', '⠠⡉'] + - ['D', '⠠⡙'] + - ['E', '⠠⡑'] + - ['F', '⠠⡋'] + - ['G', '⠠⡛'] + - ['H', '⠠⡓'] + - ['I', '⡊'] + - ['J', '⠠⡚'] + - ['K', '⠠⡅'] + - ['L', '⠠⡇'] + - ['M', '⠠⡍'] + - ['N', '⠠⡝'] + - ['O', '⠠⡕'] + - ['P', '⠠⡏'] + - ['Q', '⠠⡟'] + - ['R', '⠠⡗'] + - ['S', '⠠⡎'] + - ['T', '⠠⡞'] + - ['U', '⠠⡥'] + - ['V', '⠠⡧'] + - ['W', '⠠⡺'] + - ['X', '⠠⡭'] + - ['Y', '⠠⡽'] + - ['Z', '⠠⡵'] + - ['[', '⣦'] + - ['\\', '⠠⡌'] + - [']', '⣴'] + - ['^', '⢏'] + - ['_', '⣤'] + - ['`', '⠐'] + - ['a', '⠠⠁'] + - ['b', '⠠⠃'] + - ['c', '⠠⠉'] + - ['d', '⠠⠙'] + - ['e', '⠠⠑'] + - ['f', '⠠⠋'] + - ['g', '⠠⠛'] + - ['h', '⠠⠓'] + - ['i', '⠊'] + - ['j', '⠠⠚'] + - ['k', '⠠⠅'] + - ['l', '⠠⠇'] + - ['m', '⠠⠍'] + - ['n', '⠠⠝'] + - ['o', '⠠⠕'] + - ['p', '⠠⠏'] + - ['q', '⠠⠟'] + - ['r', '⠠⠗'] + - ['s', '⠠⠎'] + - ['t', '⠠⠞'] + - ['u', '⠠⠥'] + - ['v', '⠠⠧'] + - ['w', '⠠⠺'] + - ['x', '⠠⠭'] + - ['y', '⠠⠽'] + - ['z', '⠠⠵'] + - ['{', '⣧'] + - ['|', '⢸'] + - ['}', '⣼'] + - ['~', '⡨'] + - ['€', '⣑'] + - ['‚', '⡘'] + - ['ƒ', '⢐'] + - ['„', '⣆'] + - ['…', '⠠⠄⠄⠄'] + - ['†', '⠠⡖'] + - ['‡', '⠠⣖'] + - ['ˆ', '⠠⣰'] + - ['‰', '⣺'] + - ['Š', '⠠⣎'] + - ['‹', '⠸'] + - ['Œ', '⠠⣕'] + - ['Ž', '⠠⡬'] + - ['‘', '⡈'] + - ['’', '⢈'] + - ['“', '⡆'] + - ['”', '⢰'] + - ['•', '⡄'] + - ['–', '⠠⠤'] + - ['—', '⠠⡤'] + - ['˜', '⠨'] + - ['™', '⣞'] + - ['š', '⠠⢎'] + - ['›', '⡸'] + - ['œ', '⠠⢕'] + - ['ž', '⠠⠬'] + - ['Ÿ', '⠠⣾'] + - [' ', '⢞'] + - ['¡', '⠠⠲'] + - ['¢', '⣒'] + - ['£', '⢇'] + - ['¤', '⠠⡦'] + - ['¥', '⡠'] + - ['¦', '⣌'] + - ['§', '⣐'] + - ['¨', '⠠⠰'] + - ['©', '⣭'] + - ['ª', '⠠⣮'] + - ['«', '⡐'] + - ['¬', '⠠⡼'] + - ['­', '⠠⣄'] + - ['®', '⣗'] + - ['¯', '⡶'] + - ['°', '⠈⠴'] + - ['±', '⢟'] + - ['²', '⢆'] + - ['³', '⢒'] + - ['´', '⢨'] + - ['µ', '⠠⠦'] + - ['¶', '⢿'] + - ['·', '⢄'] + - ['¸', '⣨'] + - ['¹', '⢂'] + - ['º', '⠠⣿'] + - ['»', '⡰'] + - ['¼', '⢝'] + - ['½', '⢘'] + - ['¾', '⠠⠼'] + - ['À', '⠠⡷'] + - ['Á', '⠠⣷'] + - ['Â', '⠠⣡'] + - ['Ã', '⠠⣩'] + - ['Ä', '⠠⣜'] + - ['Å', '⠠⡡'] + - ['Æ', '⠠⡜'] + - ['Ç', '⠠⡯'] + - ['È', '⠠⡮'] + - ['É', '⠠⡿'] + - ['Ê', '⠠⡣'] + - ['Ë', '⠠⡫'] + - ['Ì', '⠠⣱'] + - ['Í', '⠠⣣'] + - ['Î', '⠠⡩'] + - ['Ï', '⠠⡻'] + - ['Ð', '⠠⣽'] + - ['Ñ', '⠠⣻'] + - ['Ò', '⠠⣫'] + - ['Ó', '⠠⣬'] + - ['Ô', '⠠⡹'] + - ['Õ', '⠠⣹'] + - ['Ö', '⠠⣪'] + - ['×', '⢭'] + - ['Ø', '⠠⡪'] + - ['Ù', '⠠⡾'] + - ['Ú', '⠠⣳'] + - ['Û', '⠠⡱'] + - ['Ü', '⠠⡳'] + - ['Ý', '⠠⣍'] + - ['Þ', '⠠⣅'] + - ['ß', '⠠⢮'] + - ['à', '⠠⠷'] + - ['á', '⠠⢷'] + - ['â', '⠠⢡'] + - ['ã', '⠠⢩'] + - ['ä', '⠠⢜'] + - ['å', '⠠⠡'] + - ['æ', '⠠⠜'] + - ['ç', '⠠⠯'] + - ['è', '⠠⠮'] + - ['é', '⠠⠿'] + - ['ê', '⠠⠣'] + - ['ë', '⠠⠫'] + - ['ì', '⠠⢱'] + - ['í', '⠠⢣'] + - ['î', '⠠⠩'] + - ['ï', '⠠⠻'] + - ['ð', '⠠⢽'] + - ['ñ', '⠠⢻'] + - ['ò', '⠠⢫'] + - ['ó', '⠠⢬'] + - ['ô', '⠠⠹'] + - ['õ', '⠠⢹'] + - ['ö', '⠠⢪'] + - ['÷', '⢲'] + - ['ø', '⠠⠪'] + - ['ù', '⠠⠾'] + - ['ú', '⠠⢳'] + - ['û', '⠠⠱'] + - ['ü', '⠠⠳'] + - ['ý', '⠠⢍'] + - ['þ', '⠠⢅'] + - ['ÿ', '⠠⢾'] + +# Misc Unicode chars +# For each accented letter, the first occurring instance is chosen for back-translation. +# There are no rules about this in the specs of Danish Braille, +# So the choice is arbitrary and may change over time. + + - ['\x0100\x0101', '⠐⡁⠐⠁'] + - ['\x0106\x0107', '⠐⡉⠐⠉'] + - ['\x010e\x010f', '⠐⡙⠐⠙'] + - ['\x0112\x0113', '⠐⡑⠐⠑'] + - ['\x011c\x011d', '⠐⡛⠐⠛'] + - ['\x0124\x0125', '⠐⡓⠐⠓'] + - ['\x0128\x0129', '⠐⡊⠐⠊'] + - ['\x0134\x0135', '⠐⡚⠐⠚'] + - ['\x0136\x0137', '⠐⡅⠐⠅'] + - ['\x0138', '⠐⠟'] + - ['\x0139\x013a', '⠐⡇⠐⠇'] + - ['\x0143\x0144', '⠐⡝⠐⠝'] + - ['\x014c\x014d', '⠐⡕⠐⠕'] + - ['\x0154\x0155', '⠐⡗⠐⠗'] + - ['\x015a\x015b', '⠐⡎⠐⠎'] + - ['\x0162\x0163', '⠐⡞⠐⠞'] + - ['\x0168\x0169', '⠐⡥⠐⠥'] + - ['\x0174\x0175', '⠐⡺⠐⠺'] + - ['\x0176\x0177', '⠐⡽⠐⠽'] + - ['\x0179\x017a', '⠐⡵⠐⠵'] + +# Greek letters (with dots 458 as prefix) + + - ['\x0386\x03ac', '⢘⠐⡁⢘⠐⠁', {xfail: {forward: true}}] + - ['\x0388\x03ad', '⢘⠐⡑⢘⠐⠑', {xfail: {forward: true}}] + - ['\x0389\x03ae', '⢘⠐⡱⢘⠐⠱', {xfail: {forward: true}}] + - ['\x038a\x03af', '⢘⠐⡊⢘⠐⠊', {xfail: {forward: true}}] + - ['\x038c\x03cc', '⢘⠐⡕⢘⠐⠕', {xfail: {forward: true}}] + - ['\x038e\x03cd', '⢘⠐⡥⢘⠐⠥', {xfail: {forward: true}}] + - ['\x038f\x03ce', '⢘⠐⡺⢘⠐⠺', {xfail: {forward: true}}] + - ['\x0391\x03b1', '⢘⡁⢘⠁', {xfail: {forward: true}}] + - ['\x0392\x03b2', '⢘⡃⢘⠃', {xfail: {forward: true}}] + - ['\x0393\x03b3', '⢘⡛⢘⠛', {xfail: {forward: true}}] + - ['\x0394\x03b4', '⢘⡙⢘⠙', {xfail: {forward: true}}] + - ['\x0395\x03b5', '⢘⡑⢘⠑', {xfail: {forward: true}}] + - ['\x0396\x03b6', '⢘⡵⢘⠵', {xfail: {forward: true}}] + - ['\x0397\x03b7', '⢘⡱⢘⠱', {xfail: {forward: true}}] + - ['\x0398\x03b8', '⢘⡹⢘⠹', {xfail: {forward: true}}] + - ['\x0399\x03b9', '⢘⡊⢘⠊', {xfail: {forward: true}}] + - ['\x039a\x03ba', '⢘⡅⢘⠅', {xfail: {forward: true}}] + - ['\x039b\x03bb', '⢘⡇⢘⠇', {xfail: {forward: true}}] + - ['\x039c\x03bc', '⢘⡍⢘⠍', {xfail: {forward: true}}] + - ['\x039d\x03bd', '⢘⡝⢘⠝', {xfail: {forward: true}}] + - ['\x039e\x03be', '⢘⡭⢘⠭', {xfail: {forward: true}}] + - ['\x039f\x03bf', '⢘⡕⢘⠕', {xfail: {forward: true}}] + - ['\x03a0\x03c0', '⢘⡏⢘⠏', {xfail: {forward: true}}] + - ['\x03a1\x03c1', '⢘⡗⢘⠗', {xfail: {forward: true}}] + - ['\x03a3\x03c3', '⢘⡎⢘⠎', {xfail: {forward: true}}] + - ['\x03a4\x03c4', '⢘⡞⢘⠞', {xfail: {forward: true}}] + - ['\x03a5\x03c5', '⢘⡥⢘⠥', {xfail: {forward: true}}] + - ['\x03a6\x03c6', '⢘⡋⢘⠋', {xfail: {forward: true}}] + - ['\x03a7\x03c7', '⢘⡯⢘⠯', {xfail: {forward: true}}] + - ['\x03a8\x03c8', '⢘⡽⢘⠽', {xfail: {forward: true}}] + - ['\x03a9\x03c9', '⢘⡺⢘⠺', {xfail: {forward: true}}] + +# Punctuation and bullits + + - ['\x2016', '⠘⢸'] + - ['\x2017', '⠘⣤'] + +# Arrows + + - ['\x2190', '⠘⠳⠪'] + - ['\x2191', '⠘⠳⠬'] + - ['\x2192', '⠘⠳⠕'] + - ['\x2193', '⠘⠳⠩'] + - ['\x2194', '⠘⠳⠺⠗⠕'] + - ['\x2196', '⠘⠳⠱'] + - ['\x2197', '⠘⠳⠎'] + - ['\x2198', '⠘⠳⠣'] + - ['\x2199', '⠘⠳⠜'] + - ['\x21D4', '⠘⠳⠺⠶⠗⠕'] + +# Math signs (experimental) + + - ['\x2200', '⠘⠁'] + - ['\x2208', '⠘⠑'] + - ['\x2213', '⠸⠤'] + - ['\x221d', '⠸⠐⢶'] + - ['\x2229', '⠨⠦'] + - ['\x222a', '⠨⠖'] + - ['\x2243', '⠸⠔'] + - ['\x2245', '⠐⠘⠔'] + - ['\x2248', '⠘⠔'] + - ['\x224f', '⠘⠐⢶'] + - ['\x2251', '⠨⠐⢶'] + - ['\x2260', '⠐⢶⠈⠱'] + - ['\x2261', '⠸⠿'] + - ['\x2264', '⠸⢔'] + - ['\x2265', '⠸⡢'] + - ['\x226a', '⠨⢔'] + - ['\x226b', '⠨⡢'] + - ['\x22c5', '⠐⠲'] + +# Pangram + + - - 'Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Walther spillede på xylofon.' + - '⠠⡟⠥⠊⠠⠵⠹⠇⠞⠁⠛⠱⠫ ⠎⠏⠊⠵⠑ ⠚⠭⠙⠃⠜⠗ ⠍ ⠋⠇⠪⠹⠂ ⠍⠣⠎ ⠉⠊⠗⠅⠥⠎⠅⠇⠕⠧⠝⠣ ⠠⡺⠁⠇⠞⠓⠱ ⠎⠏⠊⠇⠇⠑⠹ ⠏ ⠠⠭⠽⠇⠕⠋⠕⠝⠄' + +# Inverted exclamation + + - ['¡Que lastima!', '⠠⠲⠠⡟⠥⠑ ⠇⠁⠵⠊⠍⠁⠖'] + +# No letsign before numbers + + - ['v8', '⠠⠧⢓'] + - ['A4', '⠠⡁⢙'] + - ['Eleva2ren', '⡑⠇⠑⠧⠁⢃⠗⠣'] + +# URLs emails and file names + + - ['$at', '⣲⠁⠞'] + - ['\\at\\bliver', '⠠⡌⠁⠞⠠⡌⠃⠇⠊⠧⠑⠗'] + - ['at@bliver.og', '⠁⠞⣈⠃⠇⠊⠧⠑⠗⠄⠕⠛'] + - ['http://at.bliver.og', '⠓⠞⠞⠏⠒⢌⢌⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠕⠛'] + - ['www.at.bliver.og', '⠠⠺⠠⠺⠠⠺⠄⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠕⠛'] + - ['www.at.bliver.com', '⠠⠺⠠⠺⠠⠺⠄⠁⠞⠄⠃⠇⠊⠧⠑⠗⠄⠉⠕⠍'] + - ['www.a.b.c', '⠠⠺⠠⠺⠠⠺⠄⠠⠁⠄⠠⠃⠄⠠⠉'] + - ['test.txt', '⠳⠵⠄⠞⠠⠭⠞'] + +# Section sign + + - ["§ 3", "⣐⢉"] + - ["§ 3:", "⣐⢉⠒"] + +# Word contractions + + - ['At', '⡁'] + - ['at', '⠁'] + - ['Aldrig', '⡁⠔'] + - ['aldrig', '⠁⠔'] + - ['aig', '⠁⠊⠛'] + - ['Alle', '⡁⠑'] + - ['alle', '⠁⠑'] + - ['ae', '⠠⠁⠑'] + - ['Allerede', '⡁⠇⠗'] + - ['allerede', '⠁⠇⠗'] + - ['alr', '⠠⠁⠇⠗'] + - ['Alligevel', '⡁⠇⠧'] + - ['alligevel', '⠁⠇⠧'] + - ['alv', '⠠⠁⠇⠧'] + - ['Altid', '⡁⠞⠙'] + - ['altid', '⠁⠞⠙'] + - ['atd', '⠠⠁⠞⠙'] + - ['Altså', '⡁⠡'] + - ['altså', '⠁⠡'] + - ['aå', '⠠⠁⠡'] + - ['Bliver', '⡃'] + - ['bliver', '⠃'] + - ['Og', '⡉'] + - ['og', '⠉'] + - ['Deres', '⡲'] + - ['deres', '⠲'] + - ['Du', '⡙'] + - ['du', '⠙'] + - ['Eller', '⡑'] + - ['eller', '⠑'] + - ['For', '⡋'] + - ['for', '⠋'] + - ['Gør', '⡛'] + - ['gør', '⠛'] + - ['Har', '⡓'] + - ['har', '⠓'] + - ['Jeg', '⡚'] + - ['jeg', '⠚'] + - ['Kan', '⡅'] + - ['kan', '⠅'] + - ['Lige', '⡇'] + - ['lige', '⠇'] + - ['Med', '⡍'] + - ['med', '⠍'] + - ['Når', '⡝'] + - ['når', '⠝'] + - ['Op', '⡕'] + - ['op', '⠕'] + - ['På', '⡏'] + - ['på', '⠏'] + - ['Under', '⡟'] + - ['under', '⠟'] + - ['Rigtig', '⡗'] + - ['rigtig', '⠗'] + - ['Som', '⡎'] + - ['som', '⠎'] + - ['Til', '⡞'] + - ['til', '⠞'] + - ['Hun', '⡥'] + - ['hun', '⠥'] + - ['Ved', '⡧'] + - ['ved', '⠧'] + - ['Hvad', '⡺'] + - ['hvad', '⠺'] + - ['Over', '⡭'] + - ['over', '⠭'] + - ['Han', '⡽'] + - ['han', '⠽'] + - ['Efter', '⡵'] + - ['efter', '⠵'] + - ['Være', '⡜'] + - ['være', '⠜'] + - ['Før', '⡪'] + - ['før', '⠪'] + - ['Så', '⡡'] + - ['så', '⠡'] + - ['Den', '⡯'] + - ['den', '⠯'] + - ['Der', '⡾'] + - ['der', '⠾'] + - ['Det', '⡮'] + - ['det', '⠮'] + - ['De', '⡹'] + - ['de', '⠹'] + - ['En', '⡣'] + - ['en', '⠣'] + - ['Er', '⡱'] + - ['er', '⠱'] + - ['Et', '⡬'] + - ['et', '⠬'] + - ['Gennem', '⡻'] + - ['gennem', '⠻'] + - ['Hvor', '⡌'] + - ['hvor', '⠌'] + - ['Men', '⡩'] + - ['men', '⠩'] + - ['Ned', '⡫'] + - ['ned', '⠫'] + - ['Ret', '⡷'] + - ['ret', '⠷'] + - ['Skal', '⡿'] + - ['skal', '⠿'] + - ['Te', '⡳'] + - ['te', '⠳'] + - ['Ve', '⡼'] + - ['ve', '⠼'] + +# Partword/nocross + + - ['Denne', '⡯⠫'] + - ['denne', '⠯⠫'] + - ['Mændene', '⡍⠜⠝⠹⠫'] + - ['mændene', '⠍⠜⠝⠹⠫'] + - ['Derhos', '⡾⠓⠕⠎'] + - ['derhos', '⠾⠓⠕⠎'] + - ['Hunderace', '⡓⠥⠝⠹⠗⠁⠉⠑'] + - ['hunderace', '⠓⠥⠝⠹⠗⠁⠉⠑'] + - ['Dette', '⡮⠳'] + - ['dette', '⠮⠳'] + - ['Detalje', '⡹⠞⠁⠇⠚⠑'] + - ['detalje', '⠹⠞⠁⠇⠚⠑'] + +# Nocross multiple cells + + - ['Endda', '⡑⠟⠙⠁'] + - ['endda', '⠑⠟⠙⠁'] + - ['Morgendag', '⡍⠭⠛⠣⠙⠁⠛'] + - ['morgendag', '⠍⠭⠛⠣⠙⠁⠛'] + - ['Gendanne', '⡛⠣⠙⠁⠝⠫'] + - ['gendanne', '⠛⠣⠙⠁⠝⠫'] + - ['Generelt', '⡻⠫⠷⠇⠞'] + - ['generelt', '⠻⠫⠷⠇⠞'] + + - ['Fra!', '⡋⠗⠁⠖'] + - ['fra!', '⠋⠗⠁⠖'] + - ['!Fra', '⠠⠖⡖'] + - ['!fra', '⠖⠋⠗⠁'] + - ['''Af', '⠈⡴'] + - ['''af', '⠈⠁⠋'] + +# Capsnocont + + - ['UNDER et', '⡥⡝⡙⡑⡗ ⠬'] + +# No single cell contractions before or after dashes + + - ['at-bliver', '⠁⠞⢤⠃'] + - ['d-d-du', '⠠⠙⢤⠠⠙⢤⠙⠥'] + +# Combinations with slashes and other punctuation signs + + - ["at!", "⠁⠖"] + - ["bliver!", "⠃⠖"] + - ["og!", "⠉⠖"] + - ['Han/hun', '⡽⢌⠥'] + - ['han/hun', '⠽⢌⠥'] + - ['Over/under', '⡭⢌⠟'] + - ['over/under', '⠭⢌⠟'] + - ['Til/fra', '⡞⢌⠖'] + - ['til/fra', '⠞⢌⠖'] + +# Combinations which require letsign + + - ['1st', '⢁⠠⠎⠞'] + - ['2nd', '⢃⠝⠙'] + - ['1A', '⢁⠠⡁'] + - ['1a', '⢁⠠⠁'] + - ['2B', '⢃⠠⡃'] + - ['2b', '⢃⠠⠃'] + +# Characters and constructs which cannot be properly back-translated + +flags: {testmode: forward} +tests: + - ['\x0102\x0103', '⠐⡁⠐⠁'] + - ['\x0104\x0105', '⠐⡁⠐⠁'] + - ['\x0108\x0109', '⠐⡉⠐⠉'] + - ['\x010a\x010b', '⠐⡉⠐⠉'] + - ['\x010c\x010d', '⠐⡉⠐⠉'] + - ['\x0110\x0111', '⠐⡙⠐⠙'] + - ['\x0114\x0115', '⠐⡑⠐⠑'] + - ['\x0116\x0117', '⠐⡑⠐⠑'] + - ['\x0118\x0119', '⠐⡑⠐⠑'] + - ['\x011a\x011b', '⠐⡑⠐⠑'] + - ['\x011e\x011f', '⠐⡛⠐⠛'] + - ['\x0120\x0121', '⠐⡛⠐⠛'] + - ['\x0122\x0123', '⠐⡛⠐⠛'] + - ['\x0126\x0127', '⠐⡓⠐⠓'] + - ['\x012a\x012b', '⠐⡊⠐⠊'] + - ['\x012c\x012d', '⠐⡊⠐⠊'] + - ['\x012e\x012f', '⠐⡊⠐⠊'] + - ['\x0130\x0131', '⠐⡊⠐⠊'] + - ['\x0132\x0133', '⡊⠚⠊⠚'] + - ['\x013b\x013c', '⠐⡇⠐⠇'] + - ['\x013d\x013e', '⠐⡇⠐⠇'] + - ['\x013f\x0140', '⠐⡇⠐⠇'] + - ['\x0141\x0142', '⠐⡇⠐⠇'] + - ['\x0145\x0146', '⠐⡝⠐⠝'] + - ['\x0147\x0148', '⠐⡝⠐⠝'] + - ['\x0149', '⠈⠝'] + - ['\x014a\x014b', '⠐⡝⠐⠝'] + - ['\x014e\x014f', '⠐⡕⠐⠕'] + - ['\x0150\x0151', '⠐⡕⠐⠕'] + - ['\x0156\x0157', '⠐⡗⠐⠗'] + - ['\x0158\x0159', '⠐⡗⠐⠗'] + - ['\x015c\x015d', '⠐⡎⠐⠎'] + - ['\x015e\x015f', '⠐⡎⠐⠎'] + - ['\x0164\x0165', '⠐⡞⠐⠞'] + - ['\x0166\x0167', '⠐⡞⠐⠞'] + - ['\x016a\x016b', '⠐⡥⠐⠥'] + - ['\x016c\x016d', '⠐⡥⠐⠥'] + - ['\x016e\x016f', '⠐⡥⠐⠥'] + - ['\x0170\x0171', '⠐⡥⠐⠥'] + - ['\x0172\x0173', '⠐⡥⠐⠥'] + - ['\x017b\x017c', '⠐⡵⠐⠵'] + - ['\x017f', '⠐⠎'] + +# Punctuation and bullits + + - ['\x2000', ' '] + - ['\x2001', ' '] + - ['\x2002', ' '] + - ['\x2003', ' '] + - ['\x2004', ' '] + - ['\x2005', ' '] + - ['\x2006', ' '] + - ['\x2007', ' '] + - ['\x2008', ' '] + - ['\x2009', ' '] + - ['\x200a', ' '] + - ['\x2010', '⢤'] + - ['\x2011', '⢤'] + - ['\x2012', '⢤'] + - ['\x201b', '⠈'] + - ['\x201f', '⠶'] + - ['\x2023', '⡄'] + - ['\x202f', ' '] + - ['\x203c', '⠖⠖'] + - ['\x203d', '⠢⠖'] + - ['\x2043', '⡄'] + - ['\x2047', '⠢⠢'] + - ['\x2048', '⠢⠖'] + - ['\x2049', '⠖⠢'] + - ['\x204c', '⡄'] + - ['\x204d', '⡄'] + +# Geometrical shapes + + - ['\x25e6', '⡄'] + +# Emphasis + + - # Bold line + - En linje med fed. + - ⠰⡣ ⠇⠊⠝⠚⠑ ⠍ ⠋⠑⠙⠄⠰ + - typeform: + bold: '+++++++++++++++++' + - # Bold word + - Et ord med fed. + - ⡬ ⠰⠭⠙⠰ ⠍ ⠋⠑⠙⠄ + - typeform: + bold: ' +++ ' + - # Bold letter + - Et bogstav med fed. + - ⡬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠋⠑⠙⠄ + - typeform: + bold: ' + ' + + - # Italic line + - En linje med kursiv. + - ⠰⡣ ⠇⠊⠝⠚⠑ ⠍ ⠅⠥⠗⠎⠊⠧⠄⠰ + - typeform: + italic: '++++++++++++++++++++' + - # Italic word + - Et ord med kursiv. + - ⡬ ⠰⠭⠙⠰ ⠍ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' +++ ' + - # Italic letter + - Et bogstav med kursiv. + - ⡬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠅⠥⠗⠎⠊⠧⠄ + - typeform: + italic: ' + ' + + - # Underlined line + - En linje med understreget. + - ⠰⡣ ⠇⠊⠝⠚⠑ ⠍ ⠥⠝⠾⠵⠷⠛⠬⠄⠰ + - typeform: + underline: '++++++++++++++++++++++++++' + - # Underlined word + - Et ord med understreget. + - ⡬ ⠰⠭⠙⠰ ⠍ ⠥⠝⠾⠵⠷⠛⠬⠄ + - typeform: + underline: ' +++ ' + - # Underlined letter + - Et bogstav med understreget. + - ⡬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠥⠝⠾⠵⠷⠛⠬⠄ + - typeform: + underline: ' + ' + + - # Bold, italic and underlined line + - En linje med alt. + - ⠰⡣ ⠇⠊⠝⠚⠑ ⠍ ⠁⠇⠞⠄⠰ + - typeform: + bold: '+++++++++++++++++' + italic: '+++++++++++++++++' + underline: '+++++++++++++++++' + - # Bold, italic and underlined word + - Et ord med alt. + - ⡬ ⠰⠭⠙⠰ ⠍ ⠁⠇⠞⠄ + - typeform: + bold: ' +++ ' + italic: ' +++ ' + underline: ' +++ ' + - # Bold, italic and underlined letter + - Et bogstav med alt. + - ⡬ ⠃⠕⠛⠰⠎⠰⠞⠁⠧ ⠍ ⠁⠇⠞⠄ + - typeform: + bold: ' + ' + italic: ' + ' + underline: ' + ' + +# Space characters + +display: | + include unicode-without-blank.dis + display a a + +table: {language: da, grade: 0, dots: 8, direction: both, version: 1993} +table: {language: da, grade: 1, dots: 8, direction: both, version: 1993} +table: {language: da, grade: 1.5, dots: 8, direction: both, version: 1993} +table: {language: da, grade: 2, dots: 8, direction: both, version: 1993} +table: {language: da, grade: 1, dots: 6, direction: forward, version: 1993} +table: {language: da, grade: 1.5, dots: 6, direction: forward, version: 1993} +table: {language: da, grade: 2, dots: 6, direction: forward, version: 1993} +table: {language: da, grade: 1, dots: 6, direction: both, version: 1993} +table: {language: da, grade: 1.5, dots: 6, direction: both, version: 1993} +table: {language: da, grade: 2, dots: 6, direction: both, version: 1993} +flags: {testmode: bothDirections} +tests: + - ['\x0009', '⣊'] + - ['\x000a', '⣠'] + - ['\x000b', '⢥'] + - ['\x000c', '⣇'] + - ['\x000d', '⡒'] + - ['\x0020', '\x0020'] + +table: {language: da, grade: 0, dots: 8, direction: both, version: 1993} +table: {language: da, grade: 1, dots: 8, direction: both, version: 1993} +table: {language: da, grade: 1.5, dots: 8, direction: both, version: 1993} +table: {language: da, grade: 2, dots: 8, direction: both, version: 1993} +flags: {testmode: bothDirections} +tests: + - ['\x00a0', '⢞'] + +table: {language: da, grade: 1, dots: 6, direction: forward, version: 1993} +table: {language: da, grade: 1.5, dots: 6, direction: forward, version: 1993} +table: {language: da, grade: 2, dots: 6, direction: forward, version: 1993} +table: {language: da, grade: 1, dots: 6, direction: both, version: 1993} +table: {language: da, grade: 1.5, dots: 6, direction: both, version: 1993} +table: {language: da, grade: 2, dots: 6, direction: both, version: 1993} +flags: {testmode: bothDirections} +tests: + - ['\x00a0', 'a'] diff --git a/LibLouis.NET.Test/nota-tables/README.md b/LibLouis.NET.Test/nota-tables/README.md new file mode 100644 index 0000000..da6339e --- /dev/null +++ b/LibLouis.NET.Test/nota-tables/README.md @@ -0,0 +1,31 @@ +# Nota's Danish tables, for the tests + +Kept in their own directory, and copied to `nota-tables/` in the output rather than `tables/`. + +`LibLouis.NET.Tables` copies the upstream table set into `tables/`. Five of the files here share a +name with an upstream table and differ from it, so a single shared directory would make which copy +a test gets depend on MSBuild item ordering. Separate directories mean every test states which set +it means, and the upstream braille specs can be checked against upstream tables without this set +shadowing them. + +## What these are + +A fork of an older upstream, not a patch on the current one. They add things upstream does not +have, such as the `foreign` emphasis class behind `TypeForm.ForeignLanguage`, and they are missing +things upstream has since fixed, such as the rules that remove the space between `§` and a following +number. Do not assume a file here matches the upstream file of the same name in either direction. + +Two of them, `da-dk-g16-markers.ctb` and `da-dk-braillo.dis`, exist nowhere upstream, which is why +the tests cannot simply use the upstream set. + +## These are not canonical + +The canonical Nota tables ship with the application. This is a copy, and a stale one: only seven of +the thirty files are reachable from any test. + +Reachable: `da-dk-g16-markers.ctb`, `da-dk-braillo.dis`, `da-dk-g08.ctb`, `da-dk-g26.ctb`, and the +three they include (`da-dk-6miscChars.cti`, `da-dk-octobraille.dis`, `da-dk-g2.dic`). + +The other twenty three are referenced by nothing. They are kept for now, but nothing keeps them in +step with the application's copy, so treat any of them as evidence of nothing. Prefer adding a test +that needs a file over adding a file that no test needs. diff --git a/LibLouis.NET.Test/tables/da-dk-6miscChars.cti b/LibLouis.NET.Test/nota-tables/da-dk-6miscChars.cti similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-6miscChars.cti rename to LibLouis.NET.Test/nota-tables/da-dk-6miscChars.cti diff --git a/LibLouis.NET.Test/tables/da-dk-6miscChars_1993.cti b/LibLouis.NET.Test/nota-tables/da-dk-6miscChars_1993.cti similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-6miscChars_1993.cti rename to LibLouis.NET.Test/nota-tables/da-dk-6miscChars_1993.cti diff --git a/LibLouis.NET.Test/tables/da-dk-8miscChars.cti b/LibLouis.NET.Test/nota-tables/da-dk-8miscChars.cti similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-8miscChars.cti rename to LibLouis.NET.Test/nota-tables/da-dk-8miscChars.cti diff --git a/LibLouis.NET.Test/tables/da-dk-8miscChars_1993.cti b/LibLouis.NET.Test/nota-tables/da-dk-8miscChars_1993.cti similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-8miscChars_1993.cti rename to LibLouis.NET.Test/nota-tables/da-dk-8miscChars_1993.cti diff --git a/LibLouis.NET.Test/tables/da-dk-braillo.dis b/LibLouis.NET.Test/nota-tables/da-dk-braillo.dis similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-braillo.dis rename to LibLouis.NET.Test/nota-tables/da-dk-braillo.dis diff --git a/LibLouis.NET.Test/tables/da-dk-g08.ctb b/LibLouis.NET.Test/nota-tables/da-dk-g08.ctb similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g08.ctb rename to LibLouis.NET.Test/nota-tables/da-dk-g08.ctb diff --git a/LibLouis.NET.Test/tables/da-dk-g08_1993.ctb b/LibLouis.NET.Test/nota-tables/da-dk-g08_1993.ctb similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g08_1993.ctb rename to LibLouis.NET.Test/nota-tables/da-dk-g08_1993.ctb diff --git a/LibLouis.NET.Test/tables/da-dk-g16-crossword.ctb b/LibLouis.NET.Test/nota-tables/da-dk-g16-crossword.ctb similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g16-crossword.ctb rename to LibLouis.NET.Test/nota-tables/da-dk-g16-crossword.ctb diff --git a/LibLouis.NET.Test/tables/da-dk-g16-lit_1993.ctb b/LibLouis.NET.Test/nota-tables/da-dk-g16-lit_1993.ctb similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g16-lit_1993.ctb rename to LibLouis.NET.Test/nota-tables/da-dk-g16-lit_1993.ctb diff --git a/LibLouis.NET.Test/tables/da-dk-g16-markers.ctb b/LibLouis.NET.Test/nota-tables/da-dk-g16-markers.ctb similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g16-markers.ctb rename to LibLouis.NET.Test/nota-tables/da-dk-g16-markers.ctb diff --git a/LibLouis.NET.Test/tables/da-dk-g16.ctb b/LibLouis.NET.Test/nota-tables/da-dk-g16.ctb similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g16.ctb rename to LibLouis.NET.Test/nota-tables/da-dk-g16.ctb diff --git a/LibLouis.NET.Test/tables/da-dk-g16_1993-markers.ctb b/LibLouis.NET.Test/nota-tables/da-dk-g16_1993-markers.ctb similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g16_1993-markers.ctb rename to LibLouis.NET.Test/nota-tables/da-dk-g16_1993-markers.ctb diff --git a/LibLouis.NET.Test/tables/da-dk-g16_1993.ctb b/LibLouis.NET.Test/nota-tables/da-dk-g16_1993.ctb similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g16_1993.ctb rename to LibLouis.NET.Test/nota-tables/da-dk-g16_1993.ctb diff --git a/LibLouis.NET.Test/tables/da-dk-g18.ctb b/LibLouis.NET.Test/nota-tables/da-dk-g18.ctb similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g18.ctb rename to LibLouis.NET.Test/nota-tables/da-dk-g18.ctb diff --git a/LibLouis.NET.Test/tables/da-dk-g18_1993.ctb b/LibLouis.NET.Test/nota-tables/da-dk-g18_1993.ctb similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g18_1993.ctb rename to LibLouis.NET.Test/nota-tables/da-dk-g18_1993.ctb diff --git a/LibLouis.NET.Test/tables/da-dk-g2.dic b/LibLouis.NET.Test/nota-tables/da-dk-g2.dic similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g2.dic rename to LibLouis.NET.Test/nota-tables/da-dk-g2.dic diff --git a/LibLouis.NET.Test/tables/da-dk-g26-lit_1993.ctb b/LibLouis.NET.Test/nota-tables/da-dk-g26-lit_1993.ctb similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g26-lit_1993.ctb rename to LibLouis.NET.Test/nota-tables/da-dk-g26-lit_1993.ctb diff --git a/LibLouis.NET.Test/tables/da-dk-g26.ctb b/LibLouis.NET.Test/nota-tables/da-dk-g26.ctb similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g26.ctb rename to LibLouis.NET.Test/nota-tables/da-dk-g26.ctb diff --git a/LibLouis.NET.Test/tables/da-dk-g26_1993.ctb b/LibLouis.NET.Test/nota-tables/da-dk-g26_1993.ctb similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g26_1993.ctb rename to LibLouis.NET.Test/nota-tables/da-dk-g26_1993.ctb diff --git a/LibLouis.NET.Test/tables/da-dk-g26l-lit.ctb b/LibLouis.NET.Test/nota-tables/da-dk-g26l-lit.ctb similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g26l-lit.ctb rename to LibLouis.NET.Test/nota-tables/da-dk-g26l-lit.ctb diff --git a/LibLouis.NET.Test/tables/da-dk-g26l-lit_1993.ctb b/LibLouis.NET.Test/nota-tables/da-dk-g26l-lit_1993.ctb similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g26l-lit_1993.ctb rename to LibLouis.NET.Test/nota-tables/da-dk-g26l-lit_1993.ctb diff --git a/LibLouis.NET.Test/tables/da-dk-g26l.ctb b/LibLouis.NET.Test/nota-tables/da-dk-g26l.ctb similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g26l.ctb rename to LibLouis.NET.Test/nota-tables/da-dk-g26l.ctb diff --git a/LibLouis.NET.Test/tables/da-dk-g26l_1993.ctb b/LibLouis.NET.Test/nota-tables/da-dk-g26l_1993.ctb similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g26l_1993.ctb rename to LibLouis.NET.Test/nota-tables/da-dk-g26l_1993.ctb diff --git a/LibLouis.NET.Test/tables/da-dk-g28.ctb b/LibLouis.NET.Test/nota-tables/da-dk-g28.ctb similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g28.ctb rename to LibLouis.NET.Test/nota-tables/da-dk-g28.ctb diff --git a/LibLouis.NET.Test/tables/da-dk-g28_1993.ctb b/LibLouis.NET.Test/nota-tables/da-dk-g28_1993.ctb similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g28_1993.ctb rename to LibLouis.NET.Test/nota-tables/da-dk-g28_1993.ctb diff --git a/LibLouis.NET.Test/tables/da-dk-g28l.ctb b/LibLouis.NET.Test/nota-tables/da-dk-g28l.ctb similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g28l.ctb rename to LibLouis.NET.Test/nota-tables/da-dk-g28l.ctb diff --git a/LibLouis.NET.Test/tables/da-dk-g28l_1993.ctb b/LibLouis.NET.Test/nota-tables/da-dk-g28l_1993.ctb similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g28l_1993.ctb rename to LibLouis.NET.Test/nota-tables/da-dk-g28l_1993.ctb diff --git a/LibLouis.NET.Test/tables/da-dk-g2_1993.dic b/LibLouis.NET.Test/nota-tables/da-dk-g2_1993.dic similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-g2_1993.dic rename to LibLouis.NET.Test/nota-tables/da-dk-g2_1993.dic diff --git a/LibLouis.NET.Test/tables/da-dk-octobraille.dis b/LibLouis.NET.Test/nota-tables/da-dk-octobraille.dis similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-octobraille.dis rename to LibLouis.NET.Test/nota-tables/da-dk-octobraille.dis diff --git a/LibLouis.NET.Test/tables/da-dk-octobraille_1993.dis b/LibLouis.NET.Test/nota-tables/da-dk-octobraille_1993.dis similarity index 100% rename from LibLouis.NET.Test/tables/da-dk-octobraille_1993.dis rename to LibLouis.NET.Test/nota-tables/da-dk-octobraille_1993.dis diff --git a/LibLouis.NET/LibLouis.cs b/LibLouis.NET/LibLouis.cs index 22bb72b..6d8e661 100644 --- a/LibLouis.NET/LibLouis.cs +++ b/LibLouis.NET/LibLouis.cs @@ -8,7 +8,7 @@ namespace LibLouis.NET; -public class LibLouis : IDisposable +public class LibLouis { /// /// LibLouis loglevels to ILogger logLevels table. @@ -27,7 +27,13 @@ public class LibLouis : IDisposable /// /// LibLouis is *NOT* thread safe, so we'll have to use a lock to avoid concurrrent access to native liblouis calls. /// - private readonly object _lock; + /// + /// Static, and shared with : the state it protects belongs to the native + /// library, not to this instance, so every native call in the assembly has to serialise on the + /// same object. Monitor is reentrant, so a logger that calls back in while liblouis is logging + /// does not deadlock. + /// + internal static readonly object NativeLock = new(); /// /// LibLouis can currently use either UCS-4 (1:1 mapping of UTF-32), or UCS-2 (WTF-16 without surrogate pairs), @@ -48,6 +54,17 @@ public class LibLouis : IDisposable private string _lastLogMessage = string.Empty; + /// + /// Roots the delegate behind the function pointer liblouis holds. + /// + /// + /// The interop stub only keeps the delegate alive for the duration of the registration call, + /// but liblouis keeps calling the pointer for the rest of the process's life. Without a + /// reference here the delegate is collected and the next native log message kills the process + /// with "A callback was made on a garbage collected delegate". + /// + private readonly NativeMethods.LoggingCallback _logCallback; + static LibLouis() { Instance = new LibLouis(); @@ -55,7 +72,8 @@ static LibLouis() private LibLouis() { - _lock = new object(); + // unlocked: the type initializer runs single threaded, and no other thread can hold a + // reference to the singleton until it has finished, so there is nothing to race with. CharacterSize = NativeMethods.lou_charSize(); LibLouisStringEncoder = CharacterSize switch { @@ -64,19 +82,27 @@ private LibLouis() _ => throw new NotImplementedException($"Liblouis is a character size of {CharacterSize}!?"), }; + _logCallback = LogCallback; + // Register managed log callback, so we can give reasonable exception messages. - NativeMethods.lou_registerLogCallback(LogCallback); + // unlocked: same reason - still inside the type initializer. + NativeMethods.lou_registerLogCallback(_logCallback); } - // https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/unmanaged - ~LibLouis() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: false); - } + // Deliberately no finalizer. lou_free tears down state that is global to the process, while a + // finalizer runs per managed instance: in a collectible AssemblyLoadContext it would free the + // tables of every other context still using liblouis, from the finalizer thread, outside the + // lock. Nothing here owns a handle that leaks if Shutdown is never called. private ILogger _logger = NullLogger.Instance; - private bool disposedValue; + + /// + /// Read by the guards without the lock, written by under it. + /// + /// + /// Static because what it tracks is the state of the native library, not of this instance. + /// + private static volatile bool _shutDown; /// /// ILogger instance LibLouis will log to. @@ -91,34 +117,64 @@ private void SetLogger(ILogger logger) { ArgumentNullException.ThrowIfNull(logger, nameof(logger)); - lock (_lock) + // Deliberately usable after disposal: neither call touches anything lou_free released, + // and being able to attach a logger while shutting down is worth more than the symmetry. + lock (NativeLock) { _logger = logger; NativeMethods.lou_setLogLevel(LogLevel.All); - NativeMethods.lou_registerLogCallback(LogCallback); + NativeMethods.lou_registerLogCallback(_logCallback); } } + /// + /// Called by liblouis, on a native stack. + /// + /// + /// Nothing may be thrown out of here. liblouis has no way to handle a managed exception, and + /// letting one unwind through its frames tears the process down. + /// private void LogCallback(LogLevel level, string message) { - Microsoft.Extensions.Logging.LogLevel l = LogLevels[level]; - _lastLogMessage = message; + try + { + _lastLogMessage = message; - if (_logger.IsEnabled(l)) + // liblouis is free to introduce log levels we have no mapping for. + if (!LogLevels.TryGetValue(level, out Microsoft.Extensions.Logging.LogLevel l)) + { + l = Microsoft.Extensions.Logging.LogLevel.Information; + } + + if (_logger.IsEnabled(l)) + { + // Passed as an argument, not as the template: liblouis messages contain table + // paths and rule text, and a stray brace would otherwise be parsed as a + // placeholder. + _logger.Log(l, "{LiblouisMessage}", message); + } + } + catch { - _logger.Log(l, message); + // A logger that throws must not become a native crash. } } /// /// Returns version number of the native liblouis library. /// + /// + /// Readable after disposal: lou_version returns a compile-time constant and touches nothing + /// lou_free released, and version information is worth having while diagnosing a shutdown. + /// public string Version { get { - string version = NativeMethods.lou_version(); - return version; + lock (NativeLock) + { + return NativeMethods.lou_version(); + } } } @@ -130,16 +186,18 @@ public string? DataPath { get { - lock (_lock) + lock (NativeLock) { + ThrowIfShutDown(); return NativeMethods.lou_getDataPath(); } } set { ArgumentException.ThrowIfNullOrWhiteSpace(value, nameof(value)); - lock (_lock) + lock (NativeLock) { + ThrowIfShutDown(); NativeMethods.lou_setDataPath(value); } } @@ -161,8 +219,9 @@ public string? DataPath { ArgumentException.ThrowIfNullOrWhiteSpace(query, nameof(query)); - lock (_lock) + lock (NativeLock) { + ThrowIfShutDown(); return NativeMethods.lou_findTable(query); } } @@ -173,9 +232,17 @@ public string? DataPath /// tables must be an IEnumerable of file names. public void IndexTables(IEnumerable tables) { - lock (_lock) + ArgumentNullException.ThrowIfNull(tables); + + // liblouis walks the array until it reads a null pointer, so it needs a terminator on top + // of the table names. Without it, it reads whatever managed memory follows the array and + // hands it to _lou_logMessage as a string. + string?[] nullTerminated = [.. tables, null]; + + lock (NativeLock) { - NativeMethods.lou_indexTables(tables.ToArray()); + ThrowIfShutDown(); + NativeMethods.lou_indexTables(nullTerminated); } } @@ -188,15 +255,18 @@ public string DotsToCharacters(IEnumerable tableList, string input) { ArgumentNullException.ThrowIfNull(input, nameof(input)); + int length = CountUCSCharacters(input); + byte[] inputBuffer = PrepareUCSInputBuffer(input); - byte[] outputBuffer = PrepareUCSOutputBuffer(input.Length); + byte[] outputBuffer = PrepareUCSOutputBuffer(length); string tables = string.Join(',', tableList); bool success; - lock (_lock) + lock (NativeLock) { - success = NativeMethods.lou_dotsToChar(tables, inputBuffer, outputBuffer, input.Length, TranslationMode.Regular) > 0; + ThrowIfShutDown(); + success = NativeMethods.lou_dotsToChar(tables, inputBuffer, outputBuffer, length, TranslationMode.Regular) > 0; } if (!success) @@ -204,7 +274,7 @@ public string DotsToCharacters(IEnumerable tableList, string input) throw new LibLouisException($"String translation failed: {_lastLogMessage}"); } - return ConvertUCSOutputBufferToString(outputBuffer, input.Length); + return ConvertUCSOutputBufferToString(outputBuffer, length); } /// @@ -216,16 +286,19 @@ public string CharactersToDots(IEnumerable tableList, string input) { ArgumentNullException.ThrowIfNull(input, nameof(input)); + int length = CountUCSCharacters(input); + byte[] inputBuffer = PrepareUCSInputBuffer(input); - byte[] outputBuffer = PrepareUCSOutputBuffer(input.Length); - + byte[] outputBuffer = PrepareUCSOutputBuffer(length); + string tables = string.Join(',', tableList); bool success; - lock (_lock) + lock (NativeLock) { - success = NativeMethods.lou_charToDots(tables, inputBuffer, outputBuffer, input.Length, TranslationMode.Regular) > 0; + ThrowIfShutDown(); + success = NativeMethods.lou_charToDots(tables, inputBuffer, outputBuffer, length, TranslationMode.Regular) > 0; } if (!success) @@ -233,8 +306,7 @@ public string CharactersToDots(IEnumerable tableList, string input) throw new LibLouisException($"String translation failed: {_lastLogMessage}"); } - return ConvertUCSOutputBufferToString(outputBuffer, input.Length); - + return ConvertUCSOutputBufferToString(outputBuffer, length); } /// @@ -285,18 +357,27 @@ public TranslatedString Translate( throw new ArgumentException($"{nameof(outputPosition)} parameter must point to an array of integers with at least input length elements.", nameof(outputPosition)); } - int inputLength = input.Length + 1; + // The number of widechars to translate, excluding the NUL terminator, which is what the + // header means by inlen and what upstream callers pass. The buffer stays terminated: the + // translate functions clamp at the first NUL, so an embedded NUL still ends the input. + int inputLength = CountUCSCharacters(input); int outputBufferLength = outputLength; byte[] inputBuffer = PrepareUCSInputBuffer(input); byte[] outputBuffer = PrepareUCSOutputBuffer(outputBufferLength); + TypeForm[]? typeFormBuffer = PrepareTypeFormBuffer(formtype, inputLength, outputBufferLength); + + // The cursor arrives as a .NET string index and liblouis wants a widechar index. + int[] inputOffsets = Utf16OffsetOfWidechar(input); + int widecharCursor = ToWidecharCursor(input, cursorPosition); string tables = string.Join(',', tableList); bool success; - lock (_lock) + lock (NativeLock) { - success = NativeMethods.lou_translate(tables, inputBuffer, ref inputLength, outputBuffer, ref outputLength, formtype, spacing, outputPosition, inputPosition, ref cursorPosition, mode) > 0; + ThrowIfShutDown(); + success = NativeMethods.lou_translate(tables, inputBuffer, ref inputLength, outputBuffer, ref outputLength, typeFormBuffer, spacing, outputPosition, inputPosition, ref widecharCursor, mode) > 0; } if (!success) @@ -304,12 +385,18 @@ public TranslatedString Translate( throw new LibLouisException($"String translation failed: {_lastLogMessage}"); } + string output = ConvertUCSOutputBufferToString(outputBuffer, outputLength); + + (int[] mappedOutputPosition, int[] mappedInputPosition, int mappedCursor) = + MapPositionsToUtf16(input, output, inputOffsets, outputPosition, inputPosition, widecharCursor); + return new TranslatedString { - Output = ConvertUCSOutputBufferToString(outputBuffer, outputLength), - CursorPosition = cursorPosition, - InputPosition = inputPosition, - OutputPosition = outputPosition, + Output = output, + CursorPosition = mappedCursor, + InputPosition = mappedInputPosition, + OutputPosition = mappedOutputPosition, + OutputDots78 = ExtractOutputDots78(typeFormBuffer, outputLength), }; } @@ -339,18 +426,23 @@ public string Translate(IEnumerable tableList, string input, int outputL throw new ArgumentException("Spacing must be the same length as input or null"); } - int inputLength = input.Length + 1; + // The number of widechars to translate, excluding the NUL terminator, which is what the + // header means by inlen and what upstream callers pass. The buffer stays terminated: the + // translate functions clamp at the first NUL, so an embedded NUL still ends the input. + int inputLength = CountUCSCharacters(input); int outputBufferLength = outputLength; byte[] inputBuffer = PrepareUCSInputBuffer(input); byte[] outputBuffer = PrepareUCSOutputBuffer(outputBufferLength); + TypeForm[]? typeFormBuffer = PrepareTypeFormBuffer(formtype, inputLength, outputBufferLength); string tables = string.Join(',', tableList); bool success; - lock (_lock) + lock (NativeLock) { - success = NativeMethods.lou_translateString(tables, inputBuffer, ref inputLength, outputBuffer, ref outputLength, formtype, spacing, mode) > 0; + ThrowIfShutDown(); + success = NativeMethods.lou_translateString(tables, inputBuffer, ref inputLength, outputBuffer, ref outputLength, typeFormBuffer, spacing, mode) > 0; } if (!success) @@ -410,18 +502,27 @@ public TranslatedString BackTranslate( throw new ArgumentException($"{nameof(outputPosition)} parameter must point to an array of integers with at least input length elements.", nameof(outputPosition)); } - int inputLength = input.Length + 1; + // The number of widechars to translate, excluding the NUL terminator, which is what the + // header means by inlen and what upstream callers pass. The buffer stays terminated: the + // translate functions clamp at the first NUL, so an embedded NUL still ends the input. + int inputLength = CountUCSCharacters(input); int outputBufferLength = outputLength; byte[] inputBuffer = PrepareUCSInputBuffer(input); byte[] outputBuffer = PrepareUCSOutputBuffer(outputBufferLength); + TypeForm[]? typeFormBuffer = PrepareTypeFormBuffer(formtype, inputLength, outputBufferLength); + + // The cursor arrives as a .NET string index and liblouis wants a widechar index. + int[] inputOffsets = Utf16OffsetOfWidechar(input); + int widecharCursor = ToWidecharCursor(input, cursorPosition); string tables = string.Join(',', tableList); bool success; - lock (_lock) + lock (NativeLock) { - success = NativeMethods.lou_backTranslate(tables, inputBuffer, ref inputLength, outputBuffer, ref outputLength, formtype, spacing, outputPosition, inputPosition, ref cursorPosition, mode) > 0; + ThrowIfShutDown(); + success = NativeMethods.lou_backTranslate(tables, inputBuffer, ref inputLength, outputBuffer, ref outputLength, typeFormBuffer, spacing, outputPosition, inputPosition, ref widecharCursor, mode) > 0; } if (!success) @@ -429,12 +530,17 @@ public TranslatedString BackTranslate( throw new LibLouisException($"String translation failed: {_lastLogMessage}"); } + string output = ConvertUCSOutputBufferToString(outputBuffer, outputLength); + + (int[] mappedOutputPosition, int[] mappedInputPosition, int mappedCursor) = + MapPositionsToUtf16(input, output, inputOffsets, outputPosition, inputPosition, widecharCursor); + return new TranslatedString { - Output = ConvertUCSOutputBufferToString(outputBuffer, outputLength), - CursorPosition = cursorPosition, - InputPosition = inputPosition, - OutputPosition = outputPosition, + Output = output, + CursorPosition = mappedCursor, + InputPosition = mappedInputPosition, + OutputPosition = mappedOutputPosition, }; } @@ -462,18 +568,23 @@ public string BackTranslate(IEnumerable tableList, string input, int out throw new ArgumentException("Spacing must be the same length as input or null"); } - int inputLength = input.Length + 1; + // The number of widechars to translate, excluding the NUL terminator, which is what the + // header means by inlen and what upstream callers pass. The buffer stays terminated: the + // translate functions clamp at the first NUL, so an embedded NUL still ends the input. + int inputLength = CountUCSCharacters(input); int outputBufferLength = outputLength; byte[] inputBuffer = PrepareUCSInputBuffer(input); byte[] outputBuffer = PrepareUCSOutputBuffer(outputBufferLength); + TypeForm[]? typeFormBuffer = PrepareTypeFormBuffer(formtype, inputLength, outputBufferLength); string tables = string.Join(',', tableList); bool success; - lock (_lock) + lock (NativeLock) { - success = NativeMethods.lou_backTranslateString(tables, inputBuffer, ref inputLength, outputBuffer, ref outputLength, formtype, spacing, mode) > 0; + ThrowIfShutDown(); + success = NativeMethods.lou_backTranslateString(tables, inputBuffer, ref inputLength, outputBuffer, ref outputLength, typeFormBuffer, spacing, mode) > 0; } if (!success) @@ -491,33 +602,260 @@ public string BackTranslate(IEnumerable tableList, string input, int out /// If it does not, the function does nothing. /// /// - /// + /// The word to hyphenate. Must be shorter than 100 characters. /// - /// + /// + /// One character per character of : '1' where the word may be broken, + /// '0' where it may not, '2' after an existing hyphen. On a UCS-4 build a non-BMP character + /// counts once, so the result can be shorter than . + /// /// public string Hyphenate(IEnumerable tableList, string input, TranslationMode mode) { ArgumentNullException.ThrowIfNull(tableList); - ArgumentNullException.ThrowIfNullOrEmpty(nameof(input)); + ArgumentException.ThrowIfNullOrEmpty(input); + + int length = CountUCSCharacters(input); + + // liblouis rejects anything from HYPHSTRING characters up, and would otherwise report it + // as an ordinary hyphenation failure. + if (length >= MaxHyphenationLength) + { + throw new ArgumentException( + $"{nameof(input)} must be shorter than {MaxHyphenationLength} characters.", nameof(input)); + } string tables = string.Join(',', tableList); - string hyphens = new('\0', input.Length + 1); + + // liblouis writes one flag per character plus a NUL terminator into a caller-allocated + // char buffer. inlen must not count the terminator: lou_hyphenate memcpy's exactly inlen + // widechars rather than stopping at a NUL the way the translate functions do, so an + // inlen in the wrong unit reads straight past the input buffer. + byte[] hyphens = new byte[length + 1]; byte[] inputBuffer = PrepareUCSInputBuffer(input); bool success; - - lock (_lock) + + lock (NativeLock) { - success = NativeMethods.lou_hyphenate(tables, inputBuffer, input.Length + 1, ref hyphens, mode) > 0; + ThrowIfShutDown(); + success = NativeMethods.lou_hyphenate(tables, inputBuffer, length, hyphens, mode) > 0; } - + if (!success) { throw new LibLouisException($"Hyphenation failed {_lastLogMessage}"); } - return hyphens; + // The flags are ASCII digits; the trailing terminator is not part of the result. + return Encoding.ASCII.GetString(hyphens, 0, length); + } + + /// + /// liblouis hyphenates into a fixed 100 character buffer (HYPHSTRING) and refuses any input + /// that would not fit. + /// + private const int MaxHyphenationLength = 100; + + /// + /// Copy the caller's typeform values into a buffer that is safe to hand to liblouis. + /// + /// + /// The typeform parameter is in/out: liblouis reads one entry per input character, but on a + /// successful translation it writes one entry per *output* cell. A translation that grows the + /// text - which the marker tables do routinely - would therefore write past the end of an + /// array sized to the input, corrupting the managed heap. We give liblouis a buffer big enough + /// for both directions and treat the caller's array as input only. + /// + private static TypeForm[]? PrepareTypeFormBuffer(TypeForm[]? formtype, int inputLength, int outputLength) + { + if (formtype is null) + { + return null; + } + + TypeForm[] buffer = new TypeForm[Math.Max(inputLength, outputLength) + 1]; + formtype.AsSpan(0, Math.Min(formtype.Length, buffer.Length)).CopyTo(buffer); + + return buffer; + } + + /// + /// Reads the per-cell dot 7/8 information liblouis wrote into the scratch typeform buffer. + /// + /// + /// The write-back half of : on a successful forward + /// translation liblouis stores the ASCII character '8' in the slot of every output cell that + /// contains dot 7 or dot 8, and '0' otherwise (lou_translateString.c:1330). Those are + /// characters smuggled through a formtype array, not TypeForm flag values, which is why this + /// converts to booleans instead of exposing the buffer. + /// + private static bool[]? ExtractOutputDots78(TypeForm[]? typeFormBuffer, int outputLength) + { + if (typeFormBuffer is null) + { + return null; + } + + bool[] dots = new bool[outputLength]; + + for (int k = 0; k < outputLength; k++) + { + dots[k] = typeFormBuffer[k] == (TypeForm)'8'; + } + + return dots; + } + + /// + /// Converts a cursor given as a .NET string index into the widechar index liblouis expects. + /// + /// + /// Negative means "no cursor" to liblouis and is passed through untouched. + /// + private int ToWidecharCursor(string input, int cursorPosition) + { + if (cursorPosition < 0 || input.Length == 0) + { + return cursorPosition; + } + + int[] widechars = WidecharOfUtf16Offset(input); + + return widechars[Math.Clamp(cursorPosition, 0, input.Length - 1)]; + } + + /// + /// Rewrites liblouis's widechar-indexed position arrays as UTF-16 indices into the managed + /// strings, so every value can be used directly as a string index. + /// + /// + /// liblouis counts in widechars: on a UCS-4 build one widechar is a whole Unicode character, + /// while a .NET string counts UTF-16 code units. The two agree for BMP text and diverge from + /// the first non-BMP character on, which silently misaligns any caller that treats these + /// values as string indices - and the arrays exist for nothing else. + /// + /// The results are sized to the strings they index rather than to the caller's scratch + /// buffers, so OutputPosition has one entry per char of the input and + /// InputPosition one per char of the output. No slicing is required to use them. + /// + /// Both halves of a surrogate pair report the same position, since they are one character. + /// + private (int[] OutputPosition, int[] InputPosition, int CursorPosition) MapPositionsToUtf16( + string input, + string output, + int[] inputOffsets, + int[] outputWidecharPositions, + int[] inputWidecharPositions, + int widecharCursor) + { + int[] outputOffsets = Utf16OffsetOfWidechar(output); + int[] inputWidechars = WidecharOfUtf16Offset(input); + int[] outputWidechars = WidecharOfUtf16Offset(output); + + int lastInputWidechar = Math.Max(inputOffsets.Length - 2, 0); + int lastOutputWidechar = Math.Max(outputOffsets.Length - 2, 0); + + int[] outputPosition = new int[input.Length]; + + for (int i = 0; i < input.Length; i++) + { + int widechar = inputWidechars[i]; + + int cell = widechar < outputWidecharPositions.Length ? outputWidecharPositions[widechar] : 0; + + outputPosition[i] = outputOffsets[Math.Clamp(cell, 0, lastOutputWidechar)]; + } + + int[] inputPosition = new int[output.Length]; + + for (int t = 0; t < output.Length; t++) + { + int widechar = outputWidechars[t]; + + int character = widechar < inputWidecharPositions.Length ? inputWidecharPositions[widechar] : 0; + + inputPosition[t] = inputOffsets[Math.Clamp(character, 0, lastInputWidechar)]; + } + + // A negative cursor means "no cursor" to liblouis; leave it alone. + int cursorPosition = widecharCursor < 0 || output.Length == 0 + ? widecharCursor + : outputOffsets[Math.Clamp(widecharCursor, 0, lastOutputWidechar)]; + + return (outputPosition, inputPosition, cursorPosition); + } + + /// + /// The UTF-16 offset at which each widechar of starts, with a + /// sentinel holding the string's length at the end. + /// + private int[] Utf16OffsetOfWidechar(string value) + { + int[] offsets = new int[CountUCSCharacters(value) + 1]; + + int widechar = 0; + + for (int i = 0; i < value.Length; widechar++) + { + offsets[widechar] = i; + i += IsSurrogatePairAt(value, i) ? 2 : 1; + } + + offsets[widechar] = value.Length; + + return offsets; + } + + /// + /// The widechar that each UTF-16 offset of belongs to. Both halves of + /// a surrogate pair map to the same widechar, because they are one character to liblouis. + /// + private int[] WidecharOfUtf16Offset(string value) + { + int[] widechars = new int[value.Length]; + + int widechar = 0; + + for (int i = 0; i < value.Length; widechar++) + { + int width = IsSurrogatePairAt(value, i) ? 2 : 1; + + for (int k = 0; k < width; k++) + { + widechars[i + k] = widechar; + } + + i += width; + } + + return widechars; + } + + /// + /// Whether a surrogate pair - one widechar, two chars - starts at . + /// Never true on a UCS-2 build, where a widechar is a UTF-16 code unit. + /// + private bool IsSurrogatePairAt(string value, int index) + { + return CharacterSize == 4 + && char.IsHighSurrogate(value[index]) + && index + 1 < value.Length + && char.IsLowSurrogate(value[index + 1]); + } + + /// + /// The number of liblouis widechars occupies. + /// + /// + /// Not the same as string.Length on a UCS-4 build: a non-BMP character is one widechar but + /// two chars. Lengths handed to liblouis have to be counted in widechars, or they describe a + /// longer buffer than the one that was allocated. + /// + private int CountUCSCharacters(string input) + { + return LibLouisStringEncoder.GetByteCount(input) / CharacterSize; } /// @@ -554,27 +892,59 @@ private string ConvertUCSOutputBufferToString(byte[] outputBuffer, int outputLen return LibLouisStringEncoder.GetString(outputBuffer, 0, Math.Min(outputLength * CharacterSize, outputBuffer.Length)); } - protected virtual void Dispose(bool disposing) + /// + /// Throws if liblouis has already been torn down. + /// + /// + /// Called from inside the lock, immediately before the native call. Checking on the way in + /// instead would leave a window for Shutdown to free the tables between check and call. + /// + /// Not ObjectDisposedException: this type is not disposable, and "cannot access a disposed + /// object" would send the reader looking for a Dispose call that does not exist. + /// + private static void ThrowIfShutDown() { - if (!disposedValue) + if (_shutDown) { - if (disposing) + throw new InvalidOperationException( + "liblouis has been shut down. LibLouis.Shutdown() frees state that is global to " + + "the process and cannot be undone."); + } + } + + /// + /// Frees everything liblouis has allocated. Final: there is no way back. + /// + /// + /// Deliberately a static method rather than IDisposable. lou_free walks and frees the + /// translation and display table chains, which are global to the process, so this is teardown + /// for the whole application rather than the release of a resource one caller owns. Exposing + /// it as IDisposable invited using (LibLouis.Instance), which reads as ordinary + /// cleanup and would leave every other consumer in the process unable to translate. + /// + /// Only worth calling when you need the tables released before the process exits - checking + /// for leaks, say. Normal applications should not call it at all: liblouis caches compiled + /// tables per table list rather than per call, so nothing accumulates, and process exit + /// reclaims it anyway. + /// + /// Takes the same lock as every other native call. Freeing those chains while another thread + /// is translating is a use-after-free, which shows up as anything from a nonsense + /// "no mapping for dot pattern" error to a crash. + /// + /// Calling it more than once does nothing. + /// + public static void Shutdown() + { + lock (NativeLock) + { + if (_shutDown) { - // Dispose managed state (managed objects) + return; } - // Free unmanaged resources (unmanaged objects) and override finalizer NativeMethods.lou_free(); - // Set large fields to null - disposedValue = true; + _shutDown = true; } } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } } diff --git a/LibLouis.NET/Logging.cs b/LibLouis.NET/Logging.cs index a4a077d..e3449e1 100644 --- a/LibLouis.NET/Logging.cs +++ b/LibLouis.NET/Logging.cs @@ -1,10 +1,29 @@ -namespace LibLouis.NET; +using System; +namespace LibLouis.NET; + +/// +/// These change the same global liblouis state that uses, so they take the +/// same lock. Setting the callback or the log level while another thread is inside a translation +/// is otherwise an unsynchronised write to state liblouis reads as it logs. +/// public static class Logging { + /// + /// Roots the delegate behind the function pointer liblouis holds. Callers routinely pass a + /// method group, which would otherwise be collected while liblouis still calls it. + /// + private static NativeMethods.LoggingCallback? _callback; + public static void SetCallback(NativeMethods.LoggingCallback value) { - NativeMethods.lou_registerLogCallback(value); + ArgumentNullException.ThrowIfNull(value); + + lock (LibLouis.NativeLock) + { + _callback = value; + NativeMethods.lou_registerLogCallback(_callback); + } } private static LogLevel _logLevel = LogLevel.Off; @@ -13,12 +32,18 @@ public static LogLevel LogLevel { get { - return _logLevel; + lock (LibLouis.NativeLock) + { + return _logLevel; + } } set { - _logLevel = value; - NativeMethods.lou_setLogLevel(value); + lock (LibLouis.NativeLock) + { + _logLevel = value; + NativeMethods.lou_setLogLevel(value); + } } } diff --git a/LibLouis.NET/NativeMethod.cs b/LibLouis.NET/NativeMethod.cs index 4eaf17f..95f55df 100644 --- a/LibLouis.NET/NativeMethod.cs +++ b/LibLouis.NET/NativeMethod.cs @@ -1,4 +1,5 @@ using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; namespace LibLouis.NET; @@ -12,7 +13,8 @@ public static partial class NativeMethods /// /// LibLouis version. [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - [LibraryImport("liblouis", EntryPoint = "lou_version", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(UTF8StringNoFreeMarshaller))] + [LibraryImport("liblouis", EntryPoint = "lou_version")] + [return: MarshalUsing(typeof(UTF8StringNoFreeMarshaller))] internal static partial string lou_version(); /// @@ -130,13 +132,22 @@ internal static partial int lou_backTranslateString( /// /// Contains a hyphenation table. /// length of the character string in inbuf. - /// inlen is the length of the character string in inbuf - /// array of characters and must be of size inlen + 1 (to account for the NULL terminator). + /// + /// The number of characters in inbuf. Unlike the translate functions, lou_hyphenate does not + /// stop at a NUL: it copies exactly inlen characters, so this must not count the terminator. + /// It must also be less than 100 (HYPHSTRING), or liblouis refuses the call. + /// + /// + /// Caller-allocated output buffer of at least inlen + 1 bytes. liblouis writes one ASCII + /// '0' / '1' / '2' per character plus a NUL terminator. It is a plain char buffer, so it must + /// be marshalled as a byte array - a string would pass a pointer to a pointer and liblouis + /// would write over the marshalling stub's own stack. + /// /// /// 0 if error, 1 if success. [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] [LibraryImport("liblouis", EntryPoint = "lou_hyphenate", StringMarshalling = StringMarshalling.Utf8)] - internal static partial int lou_hyphenate(string tableList, byte[] inbuf, int inlen, ref string hyphens, TranslationMode mode); + internal static partial int lou_hyphenate(string tableList, byte[] inbuf, int inlen, byte[] hyphens, TranslationMode mode); /// /// This function enables you to compile a table entry on the fly at run-time. @@ -174,25 +185,48 @@ internal static partial int lou_backTranslateString( [LibraryImport("liblouis", EntryPoint = "lou_registerLogCallback")] internal static partial void lou_registerLogCallback(LoggingCallback callback); + /// + /// A pointer into static storage inside liblouis, or if the path was + /// never set. Must not be freed. + /// [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - [LibraryImport("liblouis", EntryPoint = "lou_getDataPath", StringMarshalling = StringMarshalling.Utf8)] - internal static partial string lou_getDataPath(); + [LibraryImport("liblouis", EntryPoint = "lou_getDataPath")] + [return: MarshalUsing(typeof(UTF8StringNoFreeMarshaller))] + internal static partial string? lou_getDataPath(); + /// + /// A pointer into static storage inside liblouis, or if the path was + /// rejected. Must not be freed. + /// [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] [LibraryImport("liblouis", EntryPoint = "lou_setDataPath", StringMarshalling = StringMarshalling.Utf8)] - internal static partial string lou_setDataPath(string path); + [return: MarshalUsing(typeof(UTF8StringNoFreeMarshaller))] + internal static partial string? lou_setDataPath(string path); [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] [LibraryImport("liblouis", EntryPoint = "lou_checkTable", StringMarshalling = StringMarshalling.Utf8)] internal static partial int lou_checkTable(string tableList); + /// + /// Parses, analyzes and indexes the given tables. + /// + /// + /// Must be NULL terminated: liblouis walks the array until it reads a null pointer, so the + /// final element has to be . + /// [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] [LibraryImport("liblouis", EntryPoint = "lou_indexTables", StringMarshalling = StringMarshalling.Utf8)] - internal static partial void lou_indexTables(string[] tables); + internal static partial void lou_indexTables(string?[] tables); + /// + /// The best matching table name, or when there is no match. liblouis + /// documents this as the caller's to free, but the memory comes from liblouis's own C runtime + /// - see for why we leak it instead. + /// [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] [LibraryImport("liblouis", EntryPoint = "lou_findTable", StringMarshalling = StringMarshalling.Utf8)] - internal static partial string lou_findTable(string query); + [return: MarshalUsing(typeof(UTF8StringNoFreeMarshaller))] + internal static partial string? lou_findTable(string query); [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] [LibraryImport("liblouis", EntryPoint = "lou_compileString", StringMarshalling = StringMarshalling.Utf8)] diff --git a/LibLouis.NET/TranslatedString.cs b/LibLouis.NET/TranslatedString.cs index b043b02..4e82eab 100644 --- a/LibLouis.NET/TranslatedString.cs +++ b/LibLouis.NET/TranslatedString.cs @@ -3,10 +3,45 @@ public class TranslatedString { public required string Output { get; set; } - + + /// + /// For each char of the input, the index into it translated to. One entry + /// per char, so OutputPosition.Length equals the input's length and no slicing is + /// needed. Both halves of a surrogate pair report the same position. + /// + /// + /// A UTF-16 index, usable directly against the strings. liblouis reports these in widechars - + /// whole characters on a UCS-4 build - which agrees with UTF-16 only for BMP text; the + /// wrapper translates them. This is not the array passed in, which stays as liblouis wrote it. + /// public required int[] OutputPosition { get; set; } - + + /// + /// For each char of , the index into the input it came from. One entry per + /// char, so InputPosition.Length equals Output.Length. + /// + /// + /// A UTF-16 index, on the same terms as . Values always address + /// the start of a character, never the trailing half of a surrogate pair. + /// public required int[] InputPosition { get; set; } - + + /// + /// Where the cursor ended up, as an index into . Negative when the + /// translation was given no cursor. + /// public required int CursorPosition { get; set; } + + /// + /// Per output cell, whether liblouis reported the cell as containing dot 7 or dot 8. + /// when the translation ran without a formtype array, because liblouis + /// only computes this when one is supplied. + /// + /// + /// This is the write-back half of the native typeform parameter. liblouis writes it per + /// *output* cell, which is why it cannot go into the caller's input-sized formtype array - + /// that write is exactly the buffer overrun the wrapper exists to prevent. Forward + /// translation only: back-translation zero-fills the buffer and reports nothing. + /// + public bool[]? OutputDots78 { get; set; } } diff --git a/LibLouis.NET/UTF8StringNoFreeMarshaller.cs b/LibLouis.NET/UTF8StringNoFreeMarshaller.cs index 17fa844..0b5e470 100644 --- a/LibLouis.NET/UTF8StringNoFreeMarshaller.cs +++ b/LibLouis.NET/UTF8StringNoFreeMarshaller.cs @@ -1,49 +1,43 @@ -using System; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; -using System.Text; namespace LibLouis.NET; -[CustomMarshaller(typeof(string), MarshalMode.Default, typeof(UTF8StringNoFreeMarshaller))] -public unsafe static class UTF8StringNoFreeMarshaller +/// +/// Marshals a UTF-8 string that liblouis owns, without freeing it. +/// +/// +/// Several liblouis functions return a char * the caller must not release: lou_version, +/// lou_getDataPath and lou_setDataPath all hand back a pointer into static storage inside the +/// library. The default UTF-8 marshalling frees whatever the callee returned, which for those +/// pointers aborts the process ("pointer being freed was not allocated"). +/// +/// It is deliberately restricted to - return +/// values and out parameters. Not freeing is only correct for memory we did not allocate; +/// applying it to an input parameter would leak the buffer allocated for every call, so +/// parameters keep using the built-in . +/// +/// liblouis also has functions whose result the caller *is* expected to free (lou_findTable, +/// lou_findTables, lou_getTableInfo, lou_listTables). Those use this marshaller too: the Windows +/// binaries are built with mingw-w64 and allocate from msvcrt.dll while .NET frees through +/// ucrtbase.dll, so releasing that memory from managed code would corrupt the heap. Leaking a +/// bounded number of small strings is the safer trade. +/// +[CustomMarshaller(typeof(string), MarshalMode.ManagedToUnmanagedOut, typeof(UTF8StringNoFreeMarshaller))] +public static unsafe class UTF8StringNoFreeMarshaller { - public const byte NullTerminator = (byte)0; - - public static byte* ConvertToUnmanaged(string? managedString) - { - if (managedString is null) - { - return null; - } - - int unmanagedLength = Encoding.UTF8.GetByteCount(managedString) + 1; - byte* bufferPointer = (byte*)NativeMemory.Alloc((nuint)unmanagedLength); - Span byteSpan = new(bufferPointer, unmanagedLength); - - byteSpan = Encoding.UTF8.GetBytes(managedString); - byteSpan[^1] = NullTerminator; - - return bufferPointer; - } - - + /// + /// Copies the NUL terminated UTF-8 string at into a managed string. + /// public static string? ConvertToManaged(byte* unmanaged) { - if (unmanaged == null) - { - return null; - } - - Span stringSpan = new(unmanaged, int.MaxValue); - int length = stringSpan.IndexOf(NullTerminator); - - return Encoding.UTF8.GetString(unmanaged, length); + return Marshal.PtrToStringUTF8((nint)unmanaged); } - + /// + /// Deliberately does nothing: the string belongs to liblouis. + /// public static void Free(byte* unmanaged) { - // Do nothing, not caller's responsiblity to free it. } }