Skip to content
API Documentation
BastionGuard™
hfs_unicode.hpp
Go to the documentation of this file.
1// hfs_unicode.hpp -- UTF-16BE -> UTF-8 + partial NFD -> NFC normalisation.
2//
3// HFS+ stores filenames as UTF-16 BIG-ENDIAN code units in NFD
4// (canonical decomposed) form. For example, "città" is stored as
5// c, i, t, t, à NO (that's NFC)
6// c, i, t, t, a, U+0300 YES (NFD: 'a' + COMBINING GRAVE)
7//
8// We need:
9// 1. Convert each UCS-2 code unit (surrogates included) to a UTF-8
10// string the host filesystem can use.
11// 2. Recompose common NFD sequences back to NFC. We CANNOT pull in
12// ICU or libutf8proc as a dependency, so we implement a finite
13// table of the most common compositions:
14// * Latin-1 letter + combining accent -> precomposed code point
15// * (a few hundred entries cover practically all European names)
16// Anything outside that table stays in NFD, which is still valid
17// Unicode, just visually equivalent to NFC.
18//
19// IMPORTANT: this is INTENTIONALLY a partial implementation. Hangul,
20// CJK ideographs, mathematical alphanumerics, and IPA all decompose in
21// ways our small table does not cover. Their NFD form will go through
22// unchanged. The host filesystem will display them correctly (modern
23// terminals and file browsers handle NFD), so this is a cosmetic
24// limitation, not a correctness issue.
25
26#pragma once
27
28#include <cstdint>
29#include <string>
30#include <vector>
31
32namespace hfs {
33
34// Decode N UTF-16-BE code units (2*N bytes) into a UTF-8 string.
35// Surrogate pairs are handled. Lone surrogates are replaced with U+FFFD.
36//
37// `src` points to the first byte; `code_units` is the number of UTF-16
38// code units (NOT bytes). For an HFSUniStr255 record, `code_units` is
39// the `length` field.
40std::string utf16be_to_utf8(const unsigned char* src, size_t code_units);
41
42// Apply a best-effort NFD -> NFC composition pass over a UTF-8 string.
43// Recognises a finite table of (base, combining) -> precomposed pairs
44// covering most European Latin scripts (Italian, French, Spanish,
45// German, Portuguese, Polish/Czech basics, Nordic). Everything not in
46// the table is left as-is.
47std::string nfd_to_nfc(const std::string& utf8);
48
49// Convenience: utf16be -> utf8 -> nfd_to_nfc, then strip characters
50// that are illegal in host filesystem paths (NUL, '/', and on Windows
51// the additional set "<>:\"|?*" plus reserved names) and trim trailing
52// dots / spaces (Windows-incompatible).
53//
54// On non-Windows hosts only NUL and '/' are stripped. Replacement
55// character is '_'.
56std::string hfs_filename_to_host(const unsigned char* src,
57 size_t code_units);
58
59} // namespace hfs
std::string hfs_filename_to_host(const unsigned char *src, size_t code_units)
std::string utf16be_to_utf8(const unsigned char *src, size_t code_units)
std::string nfd_to_nfc(const std::string &utf8)