Utf8.fromUtf8(): allow passing the length (#61)
The string could be non-zero-terminated, for example, or even if it is,
the length may be already known, in which case an unnecessary strlen()
call can be avoided.
diff --git a/lib/src/utf8.dart b/lib/src/utf8.dart
index 38476c7..689646f 100644
--- a/lib/src/utf8.dart
+++ b/lib/src/utf8.dart
@@ -31,15 +31,20 @@
/// Creates a [String] containing the characters UTF-8 encoded in [string].
///
- /// The [string] must be a zero-terminated byte sequence of valid UTF-8
- /// encodings of Unicode scalar values. A [FormatException] is thrown if the
- /// input is malformed. See [Utf8Decoder] for details on decoding.
+ /// Either the [string] must be zero-terminated or its [length] — the
+ /// number of bytes — must be specified as a non-negative value. The
+ /// byte sequence must be valid UTF-8 encodings of Unicode scalar values. A
+ /// [FormatException] is thrown if the input is malformed. See [Utf8Decoder]
+ /// for details on decoding.
///
/// Returns a Dart string containing the decoded code points.
- static String fromUtf8(Pointer<Utf8> string) {
- final int length = strlen(string);
- return utf8.decode(Uint8List.view(
- string.cast<Uint8>().asTypedList(length).buffer, 0, length));
+ static String fromUtf8(Pointer<Utf8> string, {int? length}) {
+ if (length != null) {
+ RangeError.checkNotNegative(length, 'length');
+ } else {
+ length = strlen(string);
+ }
+ return utf8.decode(string.cast<Uint8>().asTypedList(length));
}
/// Convert a [String] to a UTF-8 encoded zero-terminated C string.
diff --git a/test/utf8_test.dart b/test/utf8_test.dart
index 61f3b07..d8d91ec 100644
--- a/test/utf8_test.dart
+++ b/test/utf8_test.dart
@@ -55,4 +55,38 @@
final Pointer<Utf8> utf8 = _bytesFromList([0x80, 0x00]).cast();
expect(() => Utf8.fromUtf8(utf8), throwsA(isFormatException));
});
+
+ test('fromUtf8 ASCII with length', () {
+ final Pointer<Utf8> utf8 = _bytesFromList(
+ [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 10, 0]).cast();
+ final String end = Utf8.fromUtf8(utf8, length: 5);
+ expect(end, 'Hello');
+ });
+
+ test('fromUtf8 emoji with length', () {
+ final Pointer<Utf8> utf8 = _bytesFromList(
+ [240, 159, 152, 142, 240, 159, 145, 191, 240, 159, 146, 172, 0]).cast();
+ final String end = Utf8.fromUtf8(utf8, length: 4);
+ expect(end, '😎');
+ });
+
+ test('fromUtf8 with zero length', () {
+ final Pointer<Utf8> utf8 = _bytesFromList(
+ [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 10, 0]).cast();
+ final String end = Utf8.fromUtf8(utf8, length: 0);
+ expect(end, '');
+ });
+
+ test('fromUtf8 with negative length', () {
+ final Pointer<Utf8> utf8 = _bytesFromList(
+ [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 10, 0]).cast();
+ expect(() => Utf8.fromUtf8(utf8, length: -1), throwsRangeError);
+ });
+
+ test('fromUtf8 with length and containing a zero byte', () {
+ final Pointer<Utf8> utf8 = _bytesFromList(
+ [72, 101, 108, 108, 111, 0, 87, 111, 114, 108, 100, 33, 10]).cast();
+ final String end = Utf8.fromUtf8(utf8, length: 13);
+ expect(end, 'Hello\x00World!\n');
+ });
}