Update I just reread the question and have realised the questioner already knows how the C64 character set data is organised, but I'm going to leave this answer here anyway for other people who maybe don't.
I can speak for two machines: The PET and the Commodore 64. Both machines organise the characters on an 8x8 grid of pixels in terms of how they look. In the PET, the programmer has no access to how the character data is organised. It's in a ROM somewhere and the hardware picks the data by reference to an index which it gets from the screen memory. If you
POKE 32768,1 you will put an
A in the first location of screen memory. Note that the screen code is not ASCII or PETSCII but a modified version thereof.
You put a number in a location on the screen and the character generator hardware looks up the relevant character internal and puts the right bit pattern on the screen. The character generator has two possible character sets - one with upper case and graphics and one with lower case and upper case - and these are selected by one of the control lines on the PET's VIA chip, which is, in turn controlled by the PCR register on the VIA that is mapped into the PET's memory at location 59468 ($E84C). That's why
POKE 59468,12 and
POKE 59468,14 select the different character sets.
The Commodore 64 is different in that the character set ROMs can be mapped into the 6510's address space so you can see them. Each character on screen takes 8 x 8 pixels. In the character ROM, each character takes 8 consecutive bytes, one byte for each row of pixels. The picture below is a hex dump of the first twenty eight characters in the C64 character ROM. The row highlighted in blue, is the character that appears on screen when you poke a screen location with 1.
If we take the bytes:
18 3C 66 7E 66 66 66 00
Each byte is a row, so stack them one on top of the other
If a bit is 1, then the pixel in that position is on. So expand the hex numbers above using an
X for 1 and a
. for 0
The aspect ratio is wrong, but you can see it is an A. For comparison, the equivalent PET character looks like this
The vertical strokes are thinner. The reason why the vertical strokes on the C64 are two pixels wide is that they characters are designed to be displayed on an ordinary television and it makes them more readable. The Commodore Programming Guide says this:
For best results, always make any vertical lines in your characters at least 2 dots (bits) wide. This helps prevent CHROMA noise (color distortion) on your characters when they are displayed on a TV screen.
There are 256 possible numbers you can poke into a screen location, so there are 8 x 256 bytes = 2K bytes needed to represent a whole character set. The C64 character ROM contains two character sets, one for upper case and graphics, the other for lower and upper case.
The VIC chip can take its character "ROM" from pretty much any 2Kbyte aligned slot in the C64 address space, so you can copy the character ROM to RAM, modify it as you see fit and then point the Vic at it to get your own custom characters.