Well first of all, in UTF-8 emoji and many other chars are more than one byte.
However "SomeString[ i ]" returns one byte (not one char, not one codepoint => one BYTE).
Except of course for widestring/unicodestring which are 16bit strings, and return one WORD (2byte).
However if you use Utf8Copy, this will give you codepoints (not chars).
Similar with Utf16, each 16 bit word will (usually) be a codepoint (yet again not a char)
All Utf-n are "Unicode transfer encoding" => A way to e.g. put unicode into memory...
Unicode (in any encoding, even utf32) is split into codepoints. Some codepoints are "combining", and they will modify other codepoints, and form a different char together with other codepoints.
So when you get a codepoint, you need to check if it is followed by one or more combining codepoints. And that is how you get your "char".
Note that on top of that, a font can decide that the exact same char is displayed different depending on what other chars are next to it (e.g. Script fonts (Arabic) or ligatures)