NEONengine Devlog 3 - Language Packs
Today’s devlog is going to be short and sweet. Last time we talked about text rendering and that all of the NEONnoir strings are in a language pack that provides two things: the strings and the length and position of every word in every string.
The language pack has three sections:
- The header
- The strings chunk
- The words chunk
I borrowed the notion of chunks from the ILBM/IFF file format because it not only made everything pretty neat, it made it easy to look at the file in a hex editor and quickly figure out what was what where. When I split the strings and words out of the main NEONnoir data file, I kept the convention.
While a proper chunk-based file would allow chunks to be placed in any order, I don’t. This is purely due to wanting to keep the Blitz Basic code simple, and, for now, I don’t see the need to add the extra flexibility until I need it.
The header for the language pack is 8 bytes.
|Magic||4||The string ‘NEON’ with no terminator. Identifies this as a NEON file. It should probably be something else more descriptive, but here we are.|
|Version||2||The version of the language pack format. For now, version 1 is the only version we have.|
|Language Id||2||A numeric ID that identifies what language this pack is for. At the moment, English is 1, Italian is 2, and German is 3.|
The Strings Chunk
The Strings Chunk starts with a small header:
|Magic||4||The string ‘STRG’ with no terminator.|
|String Count||4||The number of Neon Strings in the chunk.|
The astute reader might ask, “What is a Neon String?” And I would have to reply, “It’s a simple structure that contains a four-byte character count followed by the characters of the string.”
There’s a happy accident I exploit with this structure. Since the length is 4 bytes and it’s unlikely ever to be long enough to warrant such a large size, the first byte of every Neon String is zero, effectively null-terminating the one before it. Allocating (and clearing) one byte more than we need will take care of the last string, and we can use these strings in any C string function. Is it a hack? Yes. Does it work? Also yes.
The Words Chunk
This chunk is suspiciously similar to the previous one.
|Magic||4||The string ‘WORD’ with no terminator.|
|Word List Count||4||The number of Neon Word Lists in the chunk.|
A Neon Word is simply two 16-bit values denoting a word’s start and end indices. A Neon Word List is a whole collection of these for every word in a string.
For example, the string string “The name’s Blake Steel!” would have the following word list:
|Word||Start Index||End Index|
Note that punctuation is part of a word.
Much like a Neon String, each word list is a 4-byte word count followed by several Neon Words.
Putting It All Together
The IDs for strings and word lists are kept in sync. The fourth word list references the fourth string.
When reading each chunk, we walk the chunk creating a table of contents, letting us know where the 325th string and word list are very quickly.
The API for all this is quite simple, really:
/** * @brief Loads a language pack. * * @param szFilePath The file path to the language pack. * @return LanguageCode The language in the pack, or LC_ERROR if there's a * problem * * @see langDestroy() * @see LanaguageCode */ LanguageCode langLoad(const char *szFilePath); /** * @brief Unloads the language pack from memory. * * @see langLoad() */ void langDestroy(); /** * @brief Get a string by id. * * @param uwStringId Id if the string to retrieve. * @return const NeonString* The requested string. */ const NeonString *langGetStringById(UWORD uwStringId); /** * @brief Get all the words in a string specified by id. * * @param uwStringId Id if the string to retrieve. * @return const NeonWordList* The requested words. */ const NeonWordList *langGetStringWordsById(UWORD uwStringId);
Next time, we will look at combining these language pack functions with the existing font rendering to display text that can word wrap and be justified.
See you next game!