Right. So, in addition to each page, you'll have some kind of list of variables displayed and how the values are formatted.
In any case, the core point is that if you have an array
TYPE name[N3][N2][N1];
then
name[i3]
refers to the i3'th sub-array, TYPE [N2][N1]. Also,
(void *)(name[i3]) == (void *)&(name[i3][0][0])
Similarly,
name[i3][i2]
refers to the [i3][i2]'th sub-array, TYPE[N1], and
(void *)(name[i3][i2]) == (void *)&(name[i3][i2][0])
So, if you have say
const unsigned char display_page[PAGES][4][20];
then
display_page[0]
refers to a two-dimensional array of const unsigned char with dimensions [4][20]. Because memcpy() takes void *, referring to the sub-array is converted to a void pointer to its first element. Thus,
memcpy(destination, display_page[pagenum], sizeof display_page[0]);
copies the 80 bytes of page pagenum to destination.
The sizeof display_page[0] works, because display_page[0] refers to a [4][20] array of const unsigned char, and the sizeof operator only examines its type; no memory is ever accessed.
Indeed, if you have say struct somestruct *pointer; then (sizeof *pointer == sizeof (struct somestruct)), because the sizeof operator does not evaluate its argument, it only examines it for the type of the argument. There is no problem even if pointer == NULL, because the operator does not cause pointer to be dereferenced. More importantly,
int x = 4;
printf("sizeof ++x = %zu\n", sizeof ++x);
printf("x = %d\n", x);
will print x = 4 (and NOT x = 5), simply because sizeof is the special size-examining operator, and not a function-like thing.