fix(epub): NFC-normalize EPUB text so NFD diacritics render correctly (#2277)
Co-authored-by: Uri Tauber <uritaube@gmail.com> Co-authored-by: Julia <julia@uxj.io>
This commit is contained in:
co-authored by
Uri Tauber
Julia
parent
7d639cf880
commit
16b0853654
@@ -1,5 +1,73 @@
|
||||
#include "Utf8.h"
|
||||
|
||||
#include "Utf8ComposeTable.h"
|
||||
|
||||
namespace {
|
||||
// Look up the canonical composition of (base + combining mark), or 0 if none.
|
||||
uint32_t utf8ComposePair(const uint32_t base, const uint32_t mark) {
|
||||
if (base > 0xFFFF || mark > 0xFFFF) return 0;
|
||||
int lo = 0;
|
||||
int hi = kUtf8ComposeTableSize - 1;
|
||||
while (lo <= hi) {
|
||||
const int mid = (lo + hi) / 2;
|
||||
const Utf8ComposeEntry& e = kUtf8ComposeTable[mid];
|
||||
if (e.base < base || (e.base == base && e.mark < mark)) {
|
||||
lo = mid + 1;
|
||||
} else if (e.base > base || (e.base == base && e.mark > mark)) {
|
||||
hi = mid - 1;
|
||||
} else {
|
||||
return e.composed;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::string utf8ComposeNfc(const std::string& in) {
|
||||
// Fast path: NFC composition can only change text that contains a combining
|
||||
// diacritical mark U+0300-036F (UTF-8 lead byte 0xCC or 0xCD). Plain ASCII and
|
||||
// already-precomposed (NFC) text -- the vast majority of words -- have none, so
|
||||
// return them untouched without walking codepoints or allocating. A 0xCD that is
|
||||
// actually a non-combining codepoint just falls through to the full pass below.
|
||||
bool maybeHasMarks = false;
|
||||
for (const unsigned char c : in) {
|
||||
if (c == 0xCC || c == 0xCD) {
|
||||
maybeHasMarks = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!maybeHasMarks) return in;
|
||||
|
||||
std::string out;
|
||||
out.reserve(in.size());
|
||||
const unsigned char* p = reinterpret_cast<const unsigned char*>(in.c_str());
|
||||
uint32_t base = 0;
|
||||
bool haveBase = false;
|
||||
while (*p) {
|
||||
const uint32_t cp = utf8NextCodepoint(&p);
|
||||
if (cp == 0) break;
|
||||
if (utf8IsCombiningMark(cp)) {
|
||||
const uint32_t composed = haveBase ? utf8ComposePair(base, cp) : 0;
|
||||
if (composed) {
|
||||
base = composed; // keep accumulating further marks onto the composed char
|
||||
continue;
|
||||
}
|
||||
// No composition: flush the pending base, then emit the mark unchanged.
|
||||
if (haveBase) {
|
||||
utf8AppendCodepoint(base, out);
|
||||
haveBase = false;
|
||||
}
|
||||
utf8AppendCodepoint(cp, out);
|
||||
} else {
|
||||
if (haveBase) utf8AppendCodepoint(base, out);
|
||||
base = cp;
|
||||
haveBase = true;
|
||||
}
|
||||
}
|
||||
if (haveBase) utf8AppendCodepoint(base, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
int utf8CodepointLen(const unsigned char c) {
|
||||
if (c < 0x80) return 1; // 0xxxxxxx
|
||||
if ((c >> 5) == 0x6) return 2; // 110xxxxx
|
||||
|
||||
Reference in New Issue
Block a user