Yes, it gets quite "hairy" with "cooked mode" and such.
The interface to ASCII terminals is based on teletypes, where the "Enter" key is "Carriage Return" (^M = 13). On output, to "go to the start of the next line," you need to send "Carriage Return" (^M) and "Line Feed" (^J).
And that's how "new lines" are stored in Windows (from MS-DOS, from CP/M), because CP/M was kind of crude and "dumb."
Unix files use '^J' for "new line." So it has to translate ("cooked mode") on I/O.
@JeffGrigg @b0rk Yeah it's actually a pretty good way to get a good range of characters and consistency on input from a limited device like a tty or an early terminal.
What I had forgotten about (until someone, maybe @cks ? reminded me) is that the driver does similar transformations on output as well.