Skip to content

Commit 10508f4

Browse files
committed
Handle unicode endianness in C code
1 parent 7bc2b46 commit 10508f4

3 files changed

Lines changed: 27 additions & 16 deletions

File tree

R/read_npy.R

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -164,21 +164,29 @@ parse_npy_data <- function(bytes, shape, datatype, signed, typesize, endian) {
164164
num_elements <- prod(shape)
165165

166166
bytes <- readBin(bytes, "raw", n = num_elements * typesize)
167-
# Unicode is UTF-32: each codepoint is 4 bytes, so swap per codepoint.
168-
swap_unit <- if (datatype == "unicode") 4L else typesize
169167
# FIXME: optimize this
170-
if (!is.na(endian) && endian != .Platform$endian) {
171-
ind <- rep_len(rev(seq_len(swap_unit)), length(bytes)) +
172-
(seq_along(bytes) - 1L) %/% swap_unit * swap_unit
168+
if (datatype != "unicode" && !is.na(endian) && endian != .Platform$endian) {
169+
ind <- rep_len(rev(seq_len(typesize)), length(bytes)) +
170+
(seq_along(bytes) - 1L) %/% typesize * typesize
173171
bytes <- bytes[ind]
174172
}
175173

176-
data <- .Call(
177-
paste0("type_convert_", datatype),
178-
bytes,
179-
typesize,
180-
PACKAGE = "grumpy"
181-
)
174+
if (datatype == "unicode") {
175+
data <- .Call(
176+
"type_convert_unicode",
177+
bytes,
178+
typesize,
179+
endian,
180+
PACKAGE = "grumpy"
181+
)
182+
} else {
183+
data <- .Call(
184+
paste0("type_convert_", datatype),
185+
bytes,
186+
typesize,
187+
PACKAGE = "grumpy"
188+
)
189+
}
182190

183191
dim(data) <- shape
184192

src/type_conversion.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,11 @@ SEXP type_convert_string(SEXP input, SEXP _n_bytes) {
179179
return(data);
180180
}
181181

182-
SEXP type_convert_unicode(SEXP input, SEXP _n_bytes) {
182+
SEXP type_convert_unicode(SEXP input, SEXP _n_bytes, SEXP _endian) {
183183

184184
// n_bytes is the total bytes per string element (num_codepoints * 4).
185-
// The raw bytes have already been endian-swapped per codepoint (4 bytes each)
186-
// before being passed here, so the encoding is UTF-32LE.
185+
// Bytes are passed as-is from the file; we select UTF-32LE or UTF-32BE
186+
// based on the file's endianness so no R-side byte-swapping is needed.
187187
const size_t n_bytes = (size_t)INTEGER(_n_bytes)[0];
188188
const R_xlen_t length = xlength(input);
189189
const char *raw_buffer = (const char *)RAW(input);
@@ -192,10 +192,13 @@ SEXP type_convert_unicode(SEXP input, SEXP _n_bytes) {
192192
R_xlen_t i;
193193
SEXP data;
194194

195+
const char *endian = CHAR(STRING_ELT(_endian, 0));
196+
const char *utf32_enc = (strcmp(endian, "big") == 0) ? "UTF-32BE" : "UTF-32LE";
197+
195198
// Worst case: 4 UTF-8 bytes per UTF-32 codepoint.
196199
char *utf8_buf = (char *)R_alloc(n_bytes + 1, 1);
197200

198-
void *cd = Riconv_open("UTF-8", "UTF-32LE");
201+
void *cd = Riconv_open("UTF-8", utf32_enc);
199202
if (cd == (void *)-1)
200203
error("Riconv_open failed: cannot convert UTF-32LE to UTF-8");
201204

src/type_conversion.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ SEXP type_convert_uint(SEXP input, SEXP _n_bytes);
88
SEXP type_convert_float(SEXP input, SEXP _n_bytes);
99
SEXP type_convert_bool(SEXP input, SEXP _n_bytes);
1010
SEXP type_convert_string(SEXP input, SEXP _n_bytes);
11-
SEXP type_convert_unicode(SEXP input, SEXP _n_bytes);
11+
SEXP type_convert_unicode(SEXP input, SEXP _n_bytes, SEXP _endian);

0 commit comments

Comments
 (0)