diff --git a/LibLouis.NET.Test/InputLengthTests.cs b/LibLouis.NET.Test/InputLengthTests.cs index 120d509..731ddfb 100644 --- a/LibLouis.NET.Test/InputLengthTests.cs +++ b/LibLouis.NET.Test/InputLengthTests.cs @@ -54,7 +54,6 @@ public void Translate_DoesNotWriteOutputPositionsPastInputLength() Input, outputLength, null, - null, outputPosition, new int[outputLength], 0, @@ -74,7 +73,7 @@ public void Translate_DoesNotTranslateTheNulTerminator() const string input = "abc"; string translated = LibLouis.Instance.Translate( - TablePaths(), input, input.Length * 4, null, null, TranslationMode.Regular); + TablePaths(), input, input.Length * 4, null, TranslationMode.Regular); Assert.DoesNotContain('\0', translated); Assert.Equal(input, translated); diff --git a/LibLouis.NET.Test/LiblouisHeapTests.cs b/LibLouis.NET.Test/LiblouisHeapTests.cs index 6bbfe70..aaa7509 100644 --- a/LibLouis.NET.Test/LiblouisHeapTests.cs +++ b/LibLouis.NET.Test/LiblouisHeapTests.cs @@ -147,7 +147,7 @@ public void FindTable_ResultsCanBeFreedBetweenTranslations() Assert.NotNull(table); Assert.EndsWith("da-dk-g26.ctb", table, StringComparison.Ordinal); - Assert.NotEmpty(LibLouis.Instance.Translate(TablePaths(), "Første linje", 64, null, null, TranslationMode.Regular)); + Assert.NotEmpty(LibLouis.Instance.Translate(TablePaths(), "Første linje", 64, null, TranslationMode.Regular)); } } } diff --git a/LibLouis.NET.Test/LogCallbackTests.cs b/LibLouis.NET.Test/LogCallbackTests.cs index 507c7bc..724c302 100644 --- a/LibLouis.NET.Test/LogCallbackTests.cs +++ b/LibLouis.NET.Test/LogCallbackTests.cs @@ -33,7 +33,7 @@ public void Logger_StillReceivesMessagesAfterGarbageCollection() // 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)); + ["no-such-table-at-all.ctb"], "x", 8, null, TranslationMode.Regular)); Assert.NotEmpty(logger.Messages); } diff --git a/LibLouis.NET.Test/NativeLockTests.cs b/LibLouis.NET.Test/NativeLockTests.cs index b1765b1..27f82cf 100644 --- a/LibLouis.NET.Test/NativeLockTests.cs +++ b/LibLouis.NET.Test/NativeLockTests.cs @@ -77,7 +77,7 @@ .. Enumerable.Range(0, 4).Select(_ => Task.Run(() => while (!cts.IsCancellationRequested) { string result = LibLouis.Instance.Translate( - TablePaths(), input, 64, null, null, TranslationMode.Regular); + TablePaths(), input, 64, null, TranslationMode.Regular); if (result != expected) { diff --git a/LibLouis.NET.Test/NativeMethodsTests.cs b/LibLouis.NET.Test/NativeMethodsTests.cs index b95fa58..8cb572e 100644 --- a/LibLouis.NET.Test/NativeMethodsTests.cs +++ b/LibLouis.NET.Test/NativeMethodsTests.cs @@ -27,7 +27,6 @@ public void SingleMode() input, outputLength, modes, - null, TranslationMode.Regular); Assert.Equal("`,@this is a test.`,", resultString); @@ -52,7 +51,6 @@ public void NestedMode_EmphasisInForeignLanguage() input, outputLength, modes, - null, TranslationMode.Regular); Assert.Equal("`,@this \\is\\ a test.`,", resultString); @@ -93,7 +91,6 @@ public void NestedMode_ForeignLanguageInEmphasis() input, outputLength, modes, - null, TranslationMode.Regular); Assert.Equal("\\@han sagde `,yes`,.\\", resultString); @@ -112,7 +109,7 @@ public void TestPositionResults() int[] inputPosition = new int[outputLength]; int[] outputPosition = new int[input.Length]; - TranslatedString translated = LibLouis.Instance.Translate(tables, input, outputLength, null, null, outputPosition, inputPosition, cursorPosition, TranslationMode.Regular); + TranslatedString translated = LibLouis.Instance.Translate(tables, input, outputLength, null, outputPosition, inputPosition, cursorPosition, TranslationMode.Regular); Assert.Equal(expected, translated.Output); diff --git a/LibLouis.NET.Test/OutputDotsTests.cs b/LibLouis.NET.Test/OutputDotsTests.cs index 290db35..00e748f 100644 --- a/LibLouis.NET.Test/OutputDotsTests.cs +++ b/LibLouis.NET.Test/OutputDotsTests.cs @@ -29,7 +29,6 @@ private static TranslatedString TranslateWithTypeForm(string input) input, input.Length * 4, typeform, - null, new int[input.Length], new int[input.Length * 4], 0, @@ -65,7 +64,6 @@ public void IsNullWhenNoTypeFormWasPassed() "Abc", 16, null, - null, new int[3], new int[16], 0, @@ -89,7 +87,6 @@ public void CallersArrayStaysUntouched() "Abc", 16, typeform, - null, new int[3], new int[16], 0, diff --git a/LibLouis.NET.Test/PositionMappingTests.cs b/LibLouis.NET.Test/PositionMappingTests.cs index 1cc6f70..2140ea0 100644 --- a/LibLouis.NET.Test/PositionMappingTests.cs +++ b/LibLouis.NET.Test/PositionMappingTests.cs @@ -32,7 +32,6 @@ private static TranslatedString Translate(string input) input, outputLength, null, - null, new int[input.Length], new int[outputLength], 0, @@ -131,7 +130,7 @@ public void BmpTextIsUnchanged() int[] scratchInput = new int[outputLength]; TranslatedString result = LibLouis.Instance.Translate( - TablePaths(), input, outputLength, null, null, scratchOutput, scratchInput, 0, TranslationMode.Regular); + TablePaths(), input, outputLength, null, scratchOutput, scratchInput, 0, TranslationMode.Regular); Assert.Equal(scratchOutput, result.OutputPosition); Assert.Equal(scratchInput[..result.Output.Length], result.InputPosition); @@ -169,7 +168,6 @@ public void CursorPositionIsAnIndexIntoTheOutput() input, outputLength, null, - null, new int[input.Length], new int[outputLength], input.IndexOf('b', StringComparison.Ordinal), @@ -196,7 +194,6 @@ public void CursorAtEndOfInputStaysAtEndOfOutput(string input) input, outputLength, null, - null, new int[input.Length], new int[outputLength], input.Length, @@ -219,7 +216,6 @@ public void BackTranslateCursorAtEndOfInputStaysAtEndOfOutput() braille, outputLength, null, - null, new int[braille.Length], new int[outputLength], braille.Length, @@ -243,7 +239,6 @@ public void CursorOnLastCharacterStaysOnIt() input, outputLength, null, - null, new int[input.Length], new int[outputLength], input.Length - 1, diff --git a/LibLouis.NET.Test/ShutdownTests.cs b/LibLouis.NET.Test/ShutdownTests.cs index e41c0fc..c6d9f0b 100644 --- a/LibLouis.NET.Test/ShutdownTests.cs +++ b/LibLouis.NET.Test/ShutdownTests.cs @@ -59,14 +59,14 @@ public void UsingTheInstanceAfterShutdownThrows() WhileMarkedShutDown(() => { Assert.Throws( - () => LibLouis.Instance.Translate(TablePaths(), "abc", 16, null, null, TranslationMode.Regular)); + () => LibLouis.Instance.Translate(TablePaths(), "abc", 16, null, TranslationMode.Regular)); Assert.Throws( () => LibLouis.Instance.Translate( - TablePaths(), "abc", 16, null, null, new int[16], new int[16], 0, TranslationMode.Regular)); + TablePaths(), "abc", 16, null, new int[16], new int[16], 0, TranslationMode.Regular)); Assert.Throws( - () => LibLouis.Instance.BackTranslate(TablePaths(), "abc", 16, null, null, TranslationMode.Regular)); + () => LibLouis.Instance.BackTranslate(TablePaths(), "abc", 16, null, TranslationMode.Regular)); Assert.Throws( () => LibLouis.Instance.CharactersToDots(TablePaths(), "abc")); diff --git a/LibLouis.NET.Test/SpacingTests.cs b/LibLouis.NET.Test/SpacingTests.cs index 5c61ad0..f6a007e 100644 --- a/LibLouis.NET.Test/SpacingTests.cs +++ b/LibLouis.NET.Test/SpacingTests.cs @@ -2,163 +2,120 @@ using System.IO; using System.Linq; +using Microsoft.Extensions.Logging; + using Xunit; +// These tests exercise the spacing overloads, which are obsolete since liblouis 3.39.0 deprecated +// the parameter but stay supported until they are removed. +#pragma warning disable CS0618 + namespace LibLouis.NET.Test; /// -/// The spacing parameter is in/out: liblouis answers in the same buffer it reads the request from. -/// It used to be declared as a string, so the answer landed in the marshaller's temporary and was -/// freed unread, and the buffer was sized to the input while liblouis writes per output cell. +/// liblouis 3.39.0 deprecated the spacing parameter and ignores it, logging a warning whenever one +/// is passed. The obsolete overloads still accept it, so existing callers keep compiling, but the +/// wrapper drops it rather than handing liblouis a buffer it will never fill. /// public class SpacingTests { + private const string Input = "Anden linje, med kursiveret tekst."; + + private const string Braille = "@anç linje, m kursi#rò ükz."; + private static readonly string[] Tables = new[] { "da-dk-braillo.dis", "da-dk-g26.ctb" } .Select(t => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tables", t)) .ToArray(); - private static TranslatedString Translate(string input, string? spacing, int outputLength) - { - return LibLouis.Instance.Translate( - Tables, input, outputLength, null, spacing, - new int[input.Length], new int[outputLength], -1, TranslationMode.Regular); - } - - private static TranslatedString BackTranslate(string input, string? spacing, int outputLength) - { - return LibLouis.Instance.BackTranslate( - Tables, input, outputLength, null, spacing, - new int[input.Length], new int[outputLength], -1, TranslationMode.Regular); - } - [Fact] - public void Spacing_IsNotReported_WhenNotRequested() + public void Translate_WithSpacing_MatchesTheOverloadWithout() { - TranslatedString translated = Translate("Anden linje, med kursiveret tekst.", null, 200); - - Assert.Null(translated.OutputSpacing); + TranslatedString with = LibLouis.Instance.Translate( + Tables, Input, 200, null, new string('3', Input.Length), + new int[Input.Length], new int[200], -1, TranslationMode.Regular); + TranslatedString without = LibLouis.Instance.Translate( + Tables, Input, 200, null, + new int[Input.Length], new int[200], -1, TranslationMode.Regular); + + Assert.Equal(without.Output, with.Output); + Assert.Equal(without.InputPosition, with.InputPosition); + Assert.Equal(without.OutputPosition, with.OutputPosition); } [Fact] - public void Spacing_ReportsTheRequestedDigits_AgainstTheOutputCellsTheyProduced() + public void BackTranslate_WithSpacing_MatchesTheOverloadWithout() { - const string input = "Anden linje, med kursiveret tekst."; - - // Mark the three characters of "lin" in "linje". - char[] request = new string('0', input.Length).ToCharArray(); - request[6] = request[7] = request[8] = '3'; - - TranslatedString translated = Translate(input, new string(request), 200); - - Assert.Equal("@anç linje, m kursi#rò ükz.", translated.Output); - Assert.Equal("**0003330000000000000000000", translated.OutputSpacing); - - // One entry per output cell: '*' where liblouis reported nothing for a cell, otherwise the - // digit belonging to the input character that produced it. - Assert.Equal(translated.Output.Length, translated.OutputSpacing!.Length); - - for (int cell = 0; cell < translated.OutputSpacing.Length; cell++) - { - if (translated.OutputSpacing[cell] == '3') - { - Assert.Contains(translated.InputPosition[cell], new[] { 6, 7, 8 }); - } - } - } - - [Fact] - public void Spacing_IsClippedToTheInputLength_WhenTheOutputGrows() - { - // Forward translation copies its answer back over only the first inlen bytes of the buffer, - // so a translation that grows past the input reports nothing for the cells beyond it. The - // marker table expands "yes" into a foreign-language run. - const string input = "Han sagde yes."; - - char[] request = new string('7', input.Length).ToCharArray(); - - string[] markerTables = - new[] { "da-dk-braillo.dis", "da-dk-g16-markers.ctb" } - .Select(t => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tables", t)) - .ToArray(); - - TypeForm[] formtype = new TypeForm[input.Length]; - Array.Fill(formtype, TypeForm.Italic); - - TranslatedString translated = LibLouis.Instance.Translate( - markerTables, input, 200, formtype, new string(request), - new int[input.Length], new int[200], -1, TranslationMode.Regular); - - Assert.True(translated.Output.Length > input.Length, "expected the marker table to grow the text"); - Assert.Equal(input.Length, translated.OutputSpacing!.Length); + TranslatedString with = LibLouis.Instance.BackTranslate( + Tables, Braille, 200, null, new string('0', Braille.Length), + new int[Braille.Length], new int[200], -1, TranslationMode.Regular); + TranslatedString without = LibLouis.Instance.BackTranslate( + Tables, Braille, 200, null, + new int[Braille.Length], new int[200], -1, TranslationMode.Regular); + + Assert.Equal(without.Output, with.Output); + Assert.Equal(without.InputPosition, with.InputPosition); + Assert.Equal(without.OutputPosition, with.OutputPosition); } - [Fact] - public void Spacing_LeadingX_DisablesTheComputation() - { - const string input = "Anden linje, med kursiveret tekst."; - - // liblouis treats a leading 'X' as "do not compute", so there is no answer to report. - string request = 'X' + new string('0', input.Length - 1); - - TranslatedString translated = Translate(input, request, 200); - - Assert.Null(translated.OutputSpacing); - } - - [Fact] - public void Spacing_BackTranslation_CoversTheWholeOutput() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void StringOverloads_WithSpacing_MatchTheOverloadsWithout(bool forward) { - // Back-translation opens with memset(spacing, '*', outlen) and then writes per output cell, - // so the buffer has to be sized to the output capacity, not to the input. Here the capacity - // is four times the input's length: sizing to the input overran the buffer by 80 bytes. - const string braille = "@anç linje, m kursi#rò ükz."; - - int outputLength = braille.Length * 4; + string input = forward ? Input : Braille; + string request = new('0', input.Length); - TranslatedString translated = BackTranslate(braille, new string('0', braille.Length), outputLength); + string with = forward + ? LibLouis.Instance.Translate(Tables, input, 200, null, request, TranslationMode.Regular) + : LibLouis.Instance.BackTranslate(Tables, input, 200, null, request, TranslationMode.Regular); + string without = forward + ? LibLouis.Instance.Translate(Tables, input, 200, null, TranslationMode.Regular) + : LibLouis.Instance.BackTranslate(Tables, input, 200, null, TranslationMode.Regular); - Assert.True(translated.Output.Length > braille.Length, "expected back-translation to grow the text"); - Assert.Equal(translated.Output.Length, translated.OutputSpacing!.Length); - Assert.All(translated.OutputSpacing, c => Assert.True(c == '*' || char.IsAsciiDigit(c), $"unexpected spacing value '{c}'")); + Assert.Equal(without, with); } [Fact] - public void Spacing_CollapsesSurrogatePairs_ToOneEntryPerCharacter() + public void Spacing_IsNotValidated_SinceItIsIgnored() { - // liblouis indexes the spacing buffer in widechars, so the caller's per-char request has to - // collapse a surrogate pair into a single entry on the way in. Counting in UTF-16 units - // would describe a longer buffer than the one allocated. - const string input = "a\U0001F600b"; - - Assert.Equal(4, input.Length); + // The length check went with the buffer: there is nothing left for a mismatch to break. + string output = LibLouis.Instance.Translate(Tables, Input, 200, null, "00", TranslationMode.Regular); - TranslatedString translated = Translate(input, new string('5', input.Length), 40); - - Assert.Equal(3, translated.OutputSpacing!.Length); + Assert.NotEmpty(output); } + /// + /// liblouis logs "the spacing parameter is deprecated and ignored" for every call that passes + /// one. The wrapper never does, so a caller still on the obsolete overloads does not get a + /// warning per translation in their logs. + /// [Fact] - public void Spacing_MustMatchTheInputLength() + public void Spacing_IsNotPassedOn_SoLiblouisDoesNotWarn() { - Assert.Throws(() => Translate("Anden linje.", "00", 200)); - } + CollectingLogger logger = new(); + ILogger previousLogger = LibLouis.Instance.Logger; + LogLevel previousLevel = Logging.LogLevel; + LibLouis.Instance.Logger = logger; + Logging.LogLevel = LogLevel.All; - [Theory] - [InlineData(true)] - [InlineData(false)] - public void Spacing_OnTheOverloadsWithoutPositions_IsComputedAndDiscarded(bool forward) - { - // These overloads return a bare string and have nowhere to report the answer, but liblouis - // still writes into the buffer, so it has to be sized the same way. - const string input = "Anden linje, med kursiveret tekst."; - - string request = new string('0', input.Length); + try + { + LibLouis.Instance.Translate(Tables, Input, 200, null, new string('0', Input.Length), TranslationMode.Regular); + LibLouis.Instance.BackTranslate(Tables, Braille, 200, null, new string('0', Braille.Length), TranslationMode.Regular); - string output = forward - ? LibLouis.Instance.Translate(Tables, input, input.Length * 4, null, request, TranslationMode.Regular) - : LibLouis.Instance.BackTranslate(Tables, input, input.Length * 4, null, request, TranslationMode.Regular); + // Prove the logger is actually hearing from liblouis, so the assertion below is not + // vacuously true. + Assert.Throws( + () => LibLouis.Instance.Translate(["no-such-table-at-all.ctb"], "x", 8, null, TranslationMode.Regular)); + } + finally + { + Logging.LogLevel = previousLevel; + LibLouis.Instance.Logger = previousLogger; + } - Assert.NotEmpty(output); + Assert.NotEmpty(logger.Messages); + Assert.DoesNotContain(logger.Messages, m => m.Contains("spacing", StringComparison.OrdinalIgnoreCase)); } } diff --git a/LibLouis.NET.Test/TypeFormBufferTests.cs b/LibLouis.NET.Test/TypeFormBufferTests.cs index c4fd76d..38bf26b 100644 --- a/LibLouis.NET.Test/TypeFormBufferTests.cs +++ b/LibLouis.NET.Test/TypeFormBufferTests.cs @@ -92,7 +92,7 @@ public void Translate_DoesNotWriteIntoCallersTypeformArray() TypeForm[] untouched = (TypeForm[])typeform.Clone(); string output = LibLouis.Instance.Translate( - TablePaths(), Input, Input.Length * 2, typeform, null, TranslationMode.Regular); + TablePaths(), Input, Input.Length * 2, typeform, TranslationMode.Regular); Assert.Equal(ExpectedOutput, output); Assert.Equal(untouched, typeform); @@ -116,7 +116,6 @@ public void TranslateWithPositions_DoesNotWriteIntoCallersTypeformArray() Input, outputLength, typeform, - null, new int[Input.Length], new int[outputLength], 0, diff --git a/LibLouis.NET/LibLouis.cs b/LibLouis.NET/LibLouis.cs index ac33c6b..ff75df1 100644 --- a/LibLouis.NET/LibLouis.cs +++ b/LibLouis.NET/LibLouis.cs @@ -318,14 +318,6 @@ public string CharactersToDots(IEnumerable tableList, string input) /// String to translate. /// Maximum output length. /// The typeform parameter is used to indicate italic type, boldface type, computer braille, etc. It is an array of formtype with the same length as the input buffer pointed to by input. Each element indicates the typeform of the corresponding character in the input buffer. - /// - /// The spacing information to compute, one char per char of , or - /// to skip the computation. An ASCII digit marks a character whose - /// spacing should be carried into the output; liblouis ignores anything else, except that a - /// leading 'X' disables the computation. The answer arrives in - /// rather than in this string, which liblouis has - /// no way to write to. - /// /// /// /// @@ -338,7 +330,6 @@ public TranslatedString Translate( string input, int outputLength, TypeForm[]? formtype, - string? spacing, int[] outputPosition, int[] inputPosition, int cursorPosition, @@ -349,11 +340,6 @@ public TranslatedString Translate( throw new ArgumentException($"{nameof(outputLength)} must be over 0", nameof(outputLength)); } - if (spacing is not null && input.Length != spacing.Length) - { - throw new ArgumentException($"{nameof(spacing)} must be the same length as input or null"); - } - if (inputPosition.Length < outputLength) { throw new ArgumentException($"{nameof(inputPosition)} must be an array of integers of at least outputLength elements.", nameof(inputPosition)); @@ -373,11 +359,6 @@ public TranslatedString Translate( byte[] inputBuffer = PrepareUCSInputBuffer(input); byte[] outputBuffer = PrepareUCSOutputBuffer(outputBufferLength); TypeForm[]? typeFormBuffer = PrepareTypeFormBuffer(formtype, inputLength, outputBufferLength); - byte[]? spacingBuffer = PrepareSpacingBuffer(spacing, input, inputLength, outputBufferLength); - - // liblouis overwrites inlen with the number of characters it consumed, so hold on to the - // length that went in: it bounds how much of the spacing buffer liblouis fills. - int spacingInputLength = inputLength; // The cursor arrives as a .NET string index and liblouis wants a widechar index. A cursor // at the end of the input is not passed on, but placed at the end of the output below. @@ -390,7 +371,8 @@ public TranslatedString Translate( lock (NativeLock) { ThrowIfShutDown(); - success = NativeMethods.lou_translate(tables, inputBuffer, ref inputLength, outputBuffer, ref outputLength, typeFormBuffer, spacingBuffer, outputPosition, inputPosition, ref widecharCursor, mode) > 0; + // spacing is always NULL: liblouis 3.39.0 ignores it and logs a warning when it is not. + success = NativeMethods.lou_translate(tables, inputBuffer, ref inputLength, outputBuffer, ref outputLength, typeFormBuffer, null, outputPosition, inputPosition, ref widecharCursor, mode) > 0; } if (!success) @@ -410,11 +392,6 @@ public TranslatedString Translate( InputPosition = mappedInputPosition, OutputPosition = mappedOutputPosition, OutputDots78 = ExtractOutputDots78(typeFormBuffer, outputLength), - - // Forward translation copies its answer back over only the first inlen bytes of the - // buffer (lou_translateString.c:1384), so cells past the input's length hold no answer - // however long the output grew. - OutputSpacing = ExtractOutputSpacing(spacingBuffer, Math.Min(outputLength, spacingInputLength)), }; } @@ -428,28 +405,51 @@ public TranslatedString Translate( /// Maximum output length. /// The typeform parameter is used to indicate italic type, boldface type, computer braille, etc. It is an array of formtype with the same length as the input buffer pointed to by input. Each element indicates the typeform of the corresponding character in the input buffer. /// - /// The spacing information to compute, one char per char of , or - /// to skip the computation. This overload has nowhere to report the - /// answer, since liblouis returns it in a buffer and an immutable string cannot receive it, so - /// it is computed and discarded. Use the overload returning and - /// read if you need it. + /// Ignored. liblouis 3.39.0 deprecated the parameter and no longer computes spacing + /// information, so it is not passed on. /// + /// + /// + /// + /// The mode parameter specifies how the translation should be done. They are all powers of 2, so that a combined mode can be specified by adding up different values. + /// Translated string + /// + /// + [Obsolete("liblouis 3.39.0 deprecated the spacing parameter and ignores it. Use the overload without spacing.")] + public TranslatedString Translate( + IEnumerable tableList, + string input, + int outputLength, + TypeForm[]? formtype, + string? spacing, + int[] outputPosition, + int[] inputPosition, + int cursorPosition, + TranslationMode mode) + { + return Translate(tableList, input, outputLength, formtype, outputPosition, inputPosition, cursorPosition, mode); + } + + /// + /// This function takes a string of Unicode characters in inbuf and translates it into a string of characters in outbuf. + /// Each character produces a particular dot pattern in one braille cell when sent to an embosser or braille display or to a screen type font. + /// Which character represents which dot pattern is indicated by the character-definition and display opcodes in the translation table. + /// + /// The tableList parameter points to a list of translation tables. See How tables are found, for a description on how the tables are located in the file system. If only one table is given, no comma should be used after it. It is these tables which control just how the translation is made, whether in Grade 2, Grade 1, or something else. + /// String to translate. + /// Maximum output length. + /// The typeform parameter is used to indicate italic type, boldface type, computer braille, etc. It is an array of formtype with the same length as the input buffer pointed to by input. Each element indicates the typeform of the corresponding character in the input buffer. /// The mode parameter specifies how the translation should be done. They are all powers of 2, so that a combined mode can be specified by adding up different values. /// /// /// - public string Translate(IEnumerable tableList, string input, int outputLength, TypeForm[]? formtype, string? spacing, TranslationMode mode) + public string Translate(IEnumerable tableList, string input, int outputLength, TypeForm[]? formtype, TranslationMode mode) { if (outputLength < 1) { throw new ArgumentException("Output length must be over 0", nameof(outputLength)); } - if (spacing is not null && input.Length != spacing.Length) - { - throw new ArgumentException("Spacing must be the same length as input or null"); - } - // 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. @@ -459,7 +459,6 @@ public string Translate(IEnumerable tableList, string input, int outputL byte[] inputBuffer = PrepareUCSInputBuffer(input); byte[] outputBuffer = PrepareUCSOutputBuffer(outputBufferLength); TypeForm[]? typeFormBuffer = PrepareTypeFormBuffer(formtype, inputLength, outputBufferLength); - byte[]? spacingBuffer = PrepareSpacingBuffer(spacing, input, inputLength, outputBufferLength); string tables = string.Join(',', tableList); bool success; @@ -467,7 +466,8 @@ public string Translate(IEnumerable tableList, string input, int outputL lock (NativeLock) { ThrowIfShutDown(); - success = NativeMethods.lou_translateString(tables, inputBuffer, ref inputLength, outputBuffer, ref outputLength, typeFormBuffer, spacingBuffer, mode) > 0; + // spacing is always NULL: liblouis 3.39.0 ignores it and logs a warning when it is not. + success = NativeMethods.lou_translateString(tables, inputBuffer, ref inputLength, outputBuffer, ref outputLength, typeFormBuffer, null, mode) > 0; } if (!success) @@ -489,13 +489,28 @@ public string Translate(IEnumerable tableList, string input, int outputL /// Maximum output length. /// The typeform parameter is used to indicate italic type, boldface type, computer braille, etc. It is an array of formtype with the same length as the input buffer pointed to by input. Each element indicates the typeform of the corresponding character in the input buffer. /// - /// The spacing information to compute, one char per char of , or - /// to skip the computation. An ASCII digit marks a character whose - /// spacing should be carried into the output; liblouis ignores anything else, except that a - /// leading 'X' disables the computation. The answer arrives in - /// rather than in this string, which liblouis has - /// no way to write to. + /// Ignored. liblouis 3.39.0 deprecated the parameter and no longer computes spacing + /// information, so it is not passed on. /// + /// The mode parameter specifies how the translation should be done. They are all powers of 2, so that a combined mode can be specified by adding up different values. + /// + /// + /// + [Obsolete("liblouis 3.39.0 deprecated the spacing parameter and ignores it. Use the overload without spacing.")] + public string Translate(IEnumerable tableList, string input, int outputLength, TypeForm[]? formtype, string? spacing, TranslationMode mode) + { + return Translate(tableList, input, outputLength, formtype, mode); + } + + /// + /// This function takes a string of Unicode characters in inbuf and translates it into a string of characters in outbuf. + /// Each character produces a particular dot pattern in one braille cell when sent to an embosser or braille display or to a screen type font. + /// Which character represents which dot pattern is indicated by the character-definition and display opcodes in the translation table. + /// + /// The tableList parameter points to a list of translation tables. See How tables are found, for a description on how the tables are located in the file system. If only one table is given, no comma should be used after it. It is these tables which control just how the translation is made, whether in Grade 2, Grade 1, or something else. + /// String to translate. + /// Maximum output length. + /// The typeform parameter is used to indicate italic type, boldface type, computer braille, etc. It is an array of formtype with the same length as the input buffer pointed to by input. Each element indicates the typeform of the corresponding character in the input buffer. /// /// /// @@ -508,7 +523,6 @@ public TranslatedString BackTranslate( string input, int outputLength, TypeForm[]? formtype, - string? spacing, int[] outputPosition, int[] inputPosition, int cursorPosition, @@ -519,11 +533,6 @@ public TranslatedString BackTranslate( throw new ArgumentException($"{nameof(outputLength)} must be over 0", nameof(outputLength)); } - if (spacing is not null && input.Length != spacing.Length) - { - throw new ArgumentException($"{nameof(spacing)} must be the same length as input or null"); - } - if (inputPosition.Length < outputLength) { throw new ArgumentException($"{nameof(inputPosition)} must be an array of integers of at least outputLength elements.", nameof(inputPosition)); @@ -543,7 +552,6 @@ public TranslatedString BackTranslate( byte[] inputBuffer = PrepareUCSInputBuffer(input); byte[] outputBuffer = PrepareUCSOutputBuffer(outputBufferLength); TypeForm[]? typeFormBuffer = PrepareTypeFormBuffer(formtype, inputLength, outputBufferLength); - byte[]? spacingBuffer = PrepareSpacingBuffer(spacing, input, inputLength, outputBufferLength); // The cursor arrives as a .NET string index and liblouis wants a widechar index. A cursor // at the end of the input is not passed on, but placed at the end of the output below. @@ -556,7 +564,8 @@ public TranslatedString BackTranslate( lock (NativeLock) { ThrowIfShutDown(); - success = NativeMethods.lou_backTranslate(tables, inputBuffer, ref inputLength, outputBuffer, ref outputLength, typeFormBuffer, spacingBuffer, outputPosition, inputPosition, ref widecharCursor, mode) > 0; + // spacing is always NULL: liblouis 3.39.0 ignores it and logs a warning when it is not. + success = NativeMethods.lou_backTranslate(tables, inputBuffer, ref inputLength, outputBuffer, ref outputLength, typeFormBuffer, null, outputPosition, inputPosition, ref widecharCursor, mode) > 0; } if (!success) @@ -575,44 +584,62 @@ public TranslatedString BackTranslate( CursorPosition = IsCursorAtEnd(input, cursorPosition) ? output.Length : mappedCursor, InputPosition = mappedInputPosition, OutputPosition = mappedOutputPosition, - - // Back-translation writes straight into the caller's buffer, one entry per output cell - // across the whole output, so unlike the forward direction it is not clipped to the - // input's length. - OutputSpacing = ExtractOutputSpacing(spacingBuffer, outputLength), }; } /// - /// This is exactly the opposite of Translate. input is a string of Unicode characters representing braille. Return value will contain a string of Unicode characters. + /// This function takes a string of Unicode characters in inbuf and translates it into a string of characters in outbuf. + /// Each character produces a particular dot pattern in one braille cell when sent to an embosser or braille display or to a screen type font. + /// Which character represents which dot pattern is indicated by the character-definition and display opcodes in the translation table. /// /// The tableList parameter points to a list of translation tables. See How tables are found, for a description on how the tables are located in the file system. If only one table is given, no comma should be used after it. It is these tables which control just how the translation is made, whether in Grade 2, Grade 1, or something else. /// String to translate. /// Maximum output length. /// The typeform parameter is used to indicate italic type, boldface type, computer braille, etc. It is an array of formtype with the same length as the input buffer pointed to by input. Each element indicates the typeform of the corresponding character in the input buffer. /// - /// The spacing information to compute, one char per char of , or - /// to skip the computation. This overload has nowhere to report the - /// answer, since liblouis returns it in a buffer and an immutable string cannot receive it, so - /// it is computed and discarded. Use the overload returning and - /// read if you need it. + /// Ignored. liblouis 3.39.0 deprecated the parameter and no longer computes spacing + /// information, so it is not passed on. /// + /// + /// + /// + /// The mode parameter specifies how the translation should be done. They are all powers of 2, so that a combined mode can be specified by adding up different values. + /// Translated string + /// + /// + [Obsolete("liblouis 3.39.0 deprecated the spacing parameter and ignores it. Use the overload without spacing.")] + public TranslatedString BackTranslate( + IEnumerable tableList, + string input, + int outputLength, + TypeForm[]? formtype, + string? spacing, + int[] outputPosition, + int[] inputPosition, + int cursorPosition, + TranslationMode mode) + { + return BackTranslate(tableList, input, outputLength, formtype, outputPosition, inputPosition, cursorPosition, mode); + } + + /// + /// This is exactly the opposite of Translate. input is a string of Unicode characters representing braille. Return value will contain a string of Unicode characters. + /// + /// The tableList parameter points to a list of translation tables. See How tables are found, for a description on how the tables are located in the file system. If only one table is given, no comma should be used after it. It is these tables which control just how the translation is made, whether in Grade 2, Grade 1, or something else. + /// String to translate. + /// Maximum output length. + /// The typeform parameter is used to indicate italic type, boldface type, computer braille, etc. It is an array of formtype with the same length as the input buffer pointed to by input. Each element indicates the typeform of the corresponding character in the input buffer. /// The mode parameter specifies how the translation should be done. They are all powers of 2, so that a combined mode can be specified by adding up different values. /// /// /// - public string BackTranslate(IEnumerable tableList, string input, int outputLength, TypeForm[]? formtype, string? spacing, TranslationMode mode) + public string BackTranslate(IEnumerable tableList, string input, int outputLength, TypeForm[]? formtype, TranslationMode mode) { if (outputLength < 1) { throw new ArgumentException("Output length must be over 0", nameof(outputLength)); } - if (spacing is not null && input.Length != spacing.Length) - { - throw new ArgumentException("Spacing must be the same length as input or null"); - } - // 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. @@ -622,7 +649,6 @@ public string BackTranslate(IEnumerable tableList, string input, int out byte[] inputBuffer = PrepareUCSInputBuffer(input); byte[] outputBuffer = PrepareUCSOutputBuffer(outputBufferLength); TypeForm[]? typeFormBuffer = PrepareTypeFormBuffer(formtype, inputLength, outputBufferLength); - byte[]? spacingBuffer = PrepareSpacingBuffer(spacing, input, inputLength, outputBufferLength); string tables = string.Join(',', tableList); bool success; @@ -630,7 +656,8 @@ public string BackTranslate(IEnumerable tableList, string input, int out lock (NativeLock) { ThrowIfShutDown(); - success = NativeMethods.lou_backTranslateString(tables, inputBuffer, ref inputLength, outputBuffer, ref outputLength, typeFormBuffer, spacingBuffer, mode) > 0; + // spacing is always NULL: liblouis 3.39.0 ignores it and logs a warning when it is not. + success = NativeMethods.lou_backTranslateString(tables, inputBuffer, ref inputLength, outputBuffer, ref outputLength, typeFormBuffer, null, mode) > 0; } if (!success) @@ -641,6 +668,27 @@ public string BackTranslate(IEnumerable tableList, string input, int out return ConvertUCSOutputBufferToString(outputBuffer, outputLength); } + /// + /// This is exactly the opposite of Translate. input is a string of Unicode characters representing braille. Return value will contain a string of Unicode characters. + /// + /// The tableList parameter points to a list of translation tables. See How tables are found, for a description on how the tables are located in the file system. If only one table is given, no comma should be used after it. It is these tables which control just how the translation is made, whether in Grade 2, Grade 1, or something else. + /// String to translate. + /// Maximum output length. + /// The typeform parameter is used to indicate italic type, boldface type, computer braille, etc. It is an array of formtype with the same length as the input buffer pointed to by input. Each element indicates the typeform of the corresponding character in the input buffer. + /// + /// Ignored. liblouis 3.39.0 deprecated the parameter and no longer computes spacing + /// information, so it is not passed on. + /// + /// The mode parameter specifies how the translation should be done. They are all powers of 2, so that a combined mode can be specified by adding up different values. + /// + /// + /// + [Obsolete("liblouis 3.39.0 deprecated the spacing parameter and ignores it. Use the overload without spacing.")] + public string BackTranslate(IEnumerable tableList, string input, int outputLength, TypeForm[]? formtype, string? spacing, TranslationMode mode) + { + return BackTranslate(tableList, input, outputLength, formtype, mode); + } + /// /// This function looks at the characters in inbuf and if it finds a sequence of letters attempts to hyphenate it as a word. /// Note that Hyphenate operates on single words only, and spaces or punctuation marks between letters are not allowed. @@ -760,77 +808,6 @@ public string Hyphenate(IEnumerable tableList, string input, Translation /// private static bool IsCursorAtEnd(string input, int cursorPosition) => cursorPosition >= input.Length; - /// - /// Copy the caller's spacing request into a buffer that is safe to hand to liblouis. - /// - /// - /// The spacing parameter is in/out - liblouis writes its answer back over the same buffer it - /// read the request from - but it used to be declared as a string. That meant the answer landed - /// in the marshaller's temporary, which is freed after the call and could never be copied back - /// into an immutable string, so no caller could ever receive spacing information. The trailing - /// NUL liblouis appends at spacing[inlen] (lou_translateString.c:1385) also fell one byte - /// past that temporary. - /// - /// Sizing is max(inputLength, outputLength) + 1, not the "at least inlen elements" the - /// header promises, because the two directions disagree: forward translation writes - /// inlen + 1 bytes, while back-translation opens with - /// memset(spacing, '*', outlen) (lou_backTranslateString.c:229) and then writes per - /// output cell. An inlen-sized buffer is a heap overrun in the back-translation direction - /// whenever the output is longer than the input. - /// - /// - /// It is a char buffer rather than widechar, and liblouis indexes it in widechars, so the - /// caller's one-entry-per-char string is collapsed to one entry per character on the way in. - /// Only ASCII digits mean anything to liblouis; everything else is ignored, except that a first - /// byte of 'X' disables the computation outright (lou_translateString.c:1203). - /// - /// - private byte[]? PrepareSpacingBuffer(string? spacing, string input, int inputLength, int outputLength) - { - if (spacing is null) - { - return null; - } - - byte[] buffer = new byte[Math.Max(inputLength, outputLength) + 1]; - int[] offsets = Utf16OffsetOfWidechar(input); - - for (int k = 0; k < inputLength; k++) - { - char c = spacing[offsets[k]]; - buffer[k] = c < 0x80 ? (byte)c : (byte)'*'; - } - - return buffer; - } - - /// - /// Reads the spacing information liblouis wrote back into the scratch spacing buffer. - /// - /// - /// The write-back half of . Values are indexed by *output* - /// cell in both directions: '*' where liblouis reported nothing for a cell, an ASCII digit - /// carried over from the input character that produced it, or '1' where back-translation - /// inserted a space. differs by direction and is the caller's to - /// decide - see the call sites. - /// - private static string? ExtractOutputSpacing(byte[]? spacingBuffer, int count) - { - if (spacingBuffer is null) - { - return null; - } - - // A request whose first byte is 'X' tells forward translation to skip the computation, so - // the buffer still holds the request and there is no answer to report. - if (spacingBuffer[0] == (byte)'X') - { - return null; - } - - return Encoding.ASCII.GetString(spacingBuffer, 0, Math.Clamp(count, 0, spacingBuffer.Length - 1)); - } - /// /// Converts a cursor given as a .NET string index into the widechar index liblouis expects. /// diff --git a/LibLouis.NET/NativeMethod.cs b/LibLouis.NET/NativeMethod.cs index bd7e5b7..87c70f1 100644 --- a/LibLouis.NET/NativeMethod.cs +++ b/LibLouis.NET/NativeMethod.cs @@ -27,9 +27,8 @@ public static partial class NativeMethods /// Length of buffer for output (make sure to allow for additional characters). /// Formtype is not used. /// - /// In/out buffer of single bytes, not widechars, or NULL. Must be at least - /// max(inlen, outlen) + 1 bytes long; see for why - /// the header's "at least inlen" is not enough. + /// Always NULL. liblouis 3.39.0 deprecated the parameter and ignores it, logging a warning + /// whenever it is not NULL. /// /// Array of original-to-braille positions. /// Array of braille-to-original positions. @@ -61,8 +60,8 @@ internal static partial int lou_translate( /// Length of buffer for output (make sure to allow for additional characters). /// Formtype is not used. /// - /// In/out buffer of single bytes, not widechars, or NULL. Must be at least - /// max(inlen, outlen) + 1 bytes long; back-translation memsets outlen of them before it starts. + /// Always NULL. liblouis 3.39.0 deprecated the parameter and ignores it, logging a warning + /// whenever it is not NULL. /// /// Array of original-to-braille positions. /// Array of braille-to-original positions. @@ -94,9 +93,8 @@ internal static partial int lou_backTranslate( /// Length of buffer for output (make sure to allow for additional characters). /// Formtype is not used. /// - /// In/out buffer of single bytes, not widechars, or NULL. Must be at least - /// max(inlen, outlen) + 1 bytes long; see for why - /// the header's "at least inlen" is not enough. + /// Always NULL. liblouis 3.39.0 deprecated the parameter and ignores it, logging a warning + /// whenever it is not NULL. /// /// Specifies how the translation should be done. They are all powers of 2, so that a combined mode can be specified by adding up different values. /// 0 if error, 1 if success. @@ -122,8 +120,8 @@ internal static partial int lou_translateString( /// Length of buffer for output (make sure to allow for additional characters). /// Formtype is not used. /// - /// In/out buffer of single bytes, not widechars, or NULL. Must be at least - /// max(inlen, outlen) + 1 bytes long; back-translation memsets outlen of them before it starts. + /// Always NULL. liblouis 3.39.0 deprecated the parameter and ignores it, logging a warning + /// whenever it is not NULL. /// /// Mode is not used. /// 0 if error, 1 if success. diff --git a/LibLouis.NET/TranslatedString.cs b/LibLouis.NET/TranslatedString.cs index 131ea4a..6d0dd6d 100644 --- a/LibLouis.NET/TranslatedString.cs +++ b/LibLouis.NET/TranslatedString.cs @@ -48,24 +48,4 @@ public class TranslatedString /// translation only: back-translation zero-fills the buffer and reports nothing. /// public bool[]? OutputDots78 { get; set; } - - /// - /// Per output cell, the spacing information liblouis reported: '*' where it reported nothing, - /// an ASCII digit carried over from the input character that produced the cell, or '1' where - /// back-translation inserted a space. when the translation ran without a - /// spacing argument, because liblouis only computes this when one is supplied. - /// - /// - /// This is the write-back half of the native spacing parameter, which is in/out: liblouis - /// answers in the same buffer it reads the request from. It cannot be reported through the - /// caller's spacing string - a .NET string is immutable, which is why the parameter - /// silently did nothing before - and it is indexed per output cell rather than per input - /// character, so it would not fit there anyway. - /// - /// Shorter than when a forward translation grew the text: that direction - /// only copies its answer back over as many bytes as the input was long, so the cells past that - /// point have no answer. Back-translation reports the full output. - /// - /// - public string? OutputSpacing { get; set; } }