== Tiles and Text Declared in ``. A tile is not a separate object. It is the 8x8 region of a surface at block coordinates `(bx, by)`, where `bx` runs 0..39 and `by` runs 0..24 -- 40x25 blocks across a 320x200 surface. The tile API moves 32-byte blocks between surfaces or fills them. The consequence is that everything else works on tiles too. You can paint tile content with `jlDrawPixel` and `jlFillRect`, generate it procedurally, or edit it at run time, because a "tileset" is just a surface you agree to treat as one. Fonts, terrain sets, and spritesheets are all the same thing. === Constants and Types [cols="3,1,4", options="header"] |=== |Name |Value |Meaning |`JL_TILE_PIXELS_PER_SIDE` |8 |Tile edge in pixels. |`JL_TILE_BYTES_PER_ROW` |4 |Bytes per tile row (8 pixels at 4bpp). |`JL_TILE_BYTES` |32 |Bytes in one tile. |`JL_TILE_BLOCKS_PER_ROW` |40 |Tile columns on a surface. |`JL_TILE_BLOCKS_PER_COL` |25 |Tile rows on a surface. |`JL_TILE_NO_GLYPH` |0xFFFF |`jlDrawText` map entry meaning "skip this character". |`JL_TILE_MAP_SKIP` |0xFF |`jlTileMapPaste` entry meaning "leave this cell alone". |=== [source,c] ---- typedef struct jlTileT { uint8_t pixels[JL_TILE_BYTES]; } jlTileT; ---- A stack-allocatable 32-byte tile snapshot, so save-under style work does not need a whole scratch surface. [IMPORTANT] ==== A `jlTileT` is *target-native*: plane-major on the Amiga and X68000, row by row with each row's four plane bytes together on the ST, and chunky on the others. Treat it as opaque. In particular, do not feed a tile from `jlTileSnap` or `jlTileBankLoad` to `jlTilePasteMono`, which wants a chunky silhouette -- on the Amiga, ST, and X68000 that is a format mismatch and draws the wrong shape. Use `jlTilePasteGlyph` for 1-bit glyph data instead. ==== === jlTileCopy [source,c] ---- void jlTileCopy(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy); ---- Copies one 8x8 block between surfaces, opaquely. [cols="1,1,4", options="header"] |=== |Parameter |Type |Description |`dst` |`jlSurfaceT *` |Destination surface. |`dstBx`, `dstBy` |`uint8_t` |Destination block coordinates. |`src` |`const jlSurfaceT *` |Source surface. May be the same as `dst`. |`srcBx`, `srcBy` |`uint8_t` |Source block coordinates. |=== *Returns:* nothing. Out-of-range block coordinates on either side are silent no-ops. === jlTileCopyMasked [source,c] ---- void jlTileCopyMasked(jlSurfaceT *dst, uint8_t dstBx, uint8_t dstBy, const jlSurfaceT *src, uint8_t srcBx, uint8_t srcBy, uint8_t transparentIndex); ---- As `jlTileCopy`, but source pixels equal to `transparentIndex` are skipped and the destination keeps its original value. This is the call for fonts and for any tileset with see-through pixels. [cols="1,1,4", options="header"] |=== |Parameter |Type |Description |`dst` |`jlSurfaceT *` |Destination surface. |`dstBx`, `dstBy` |`uint8_t` |Destination block coordinates. |`src` |`const jlSurfaceT *` |Source surface. |`srcBx`, `srcBy` |`uint8_t` |Source block coordinates. |`transparentIndex` |`uint8_t` |Source colour index treated as transparent. |=== *Returns:* nothing. === jlTileFill [source,c] ---- void jlTileFill(jlSurfaceT *s, uint8_t bx, uint8_t by, uint8_t colorIndex); ---- Fills one block with a solid colour. Equivalent to `jlFillRect(s, bx*8, by*8, 8, 8, colorIndex)` but skips the clipping maths, since block coordinates are already known to be in range. [cols="1,1,4", options="header"] |=== |Parameter |Type |Description |`s` |`jlSurfaceT *` |Target surface. |`bx`, `by` |`uint8_t` |Block coordinates. |`colorIndex` |`uint8_t` |Colour index 0..15. |=== *Returns:* nothing. === jlTileBankLoad [source,c] ---- uint16_t jlTileBankLoad(const char *path, jlTileT *outTiles, uint16_t maxTiles, bool *outValid, uint16_t *outPalette); ---- Loads tiles from a baked `.tbk` file. The file's per-target byte is checked against the build platform and mismatches are refused. [cols="1,1,4", options="header"] |=== |Parameter |Type |Description |`path` |`const char *` |File path. |`outTiles` |`jlTileT *` |Receives the tiles. Caller-owned. |`maxTiles` |`uint16_t` |Capacity of `outTiles`. |`outValid` |`bool *` |Optional, may be `NULL`. One flag per slot up to `maxTiles`: `true` for each loaded tile, `false` for the rest (all of them when the file is refused), so loaded tiles can be told from leftovers in a fixed-size array. |`outPalette` |`uint16_t *` |Optional, may be `NULL`. Receives the embedded 16-entry `$0RGB` palette if the file has one. |=== *Returns:* the number of tiles written. Returns 0 if the file is missing, the magic or target byte is wrong, or `maxTiles` is 0. === jlTileBankLoadToSurface [source,c] ---- uint16_t jlTileBankLoadToSurface(const char *path, jlSurfaceT *dst, uint16_t *outPalette); ---- Streams a baked `.tbk` straight onto a surface's block grid: tile `i` lands at block `(i % 40, i / 40)`, the row-major layout the bake tools author against. Use this instead of `jlTileBankLoad` for anything large. A 1000-tile font sheet is 32000 bytes, which no IIgs program can hold in an array or in bank-0 BSS; this needs only a small internal scratch buffer and one pass over the file. [cols="1,1,4", options="header"] |=== |Parameter |Type |Description |`path` |`const char *` |File path. |`dst` |`jlSurfaceT *` |Destination surface. |`outPalette` |`uint16_t *` |Optional, may be `NULL`. As `jlTileBankLoad`. |=== *Returns:* the number of tiles pasted. Blocks beyond the file's tile count keep their previous content, and tiles past the surface's 1000 blocks are ignored. === jlTileSnap [source,c] ---- void jlTileSnap(const jlSurfaceT *src, uint8_t bx, uint8_t by, jlTileT *out); ---- Captures one block into a caller-supplied `jlTileT`. [cols="1,1,4", options="header"] |=== |Parameter |Type |Description |`src` |`const jlSurfaceT *` |Source surface. |`bx`, `by` |`uint8_t` |Block coordinates. |`out` |`jlTileT *` |Receives the 32-byte snapshot. |=== *Returns:* nothing. === jlTilePaste [source,c] ---- void jlTilePaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, const jlTileT *in); ---- Pastes a `jlTileT` back onto a surface, always opaquely. Use `jlTileCopyMasked` if you need transparency. [cols="1,1,4", options="header"] |=== |Parameter |Type |Description |`dst` |`jlSurfaceT *` |Destination surface. |`bx`, `by` |`uint8_t` |Block coordinates. |`in` |`const jlTileT *` |Tile to paste. |=== *Returns:* nothing. === jlTilePasteMono [source,c] ---- void jlTilePasteMono(jlSurfaceT *dst, uint8_t bx, uint8_t by, const jlTileT *in, uint8_t fgColor, uint8_t bgColor); ---- Pastes a tile as a silhouette: any non-zero source pixel draws `fgColor`, zero pixels draw `bgColor`. Useful when the bitmap is a fixed glyph but the colour comes from a separate map, as in C64-style colour RAM. Opaque. [cols="1,1,4", options="header"] |=== |Parameter |Type |Description |`dst` |`jlSurfaceT *` |Destination surface. |`bx`, `by` |`uint8_t` |Block coordinates. |`in` |`const jlTileT *` |Source tile, read as a silhouette. |`fgColor` |`uint8_t` |Colour for non-zero pixels. Masked to 4 bits. |`bgColor` |`uint8_t` |Colour for zero pixels. Masked to 4 bits. |=== *Returns:* nothing. [WARNING] ==== This call wants a *chunky* silhouette, but a `jlTileT` from `jlTileSnap` or `jlTileBankLoad` is target-native. Feeding one to this function on the Amiga, ST, or X68000 is a format mismatch. For glyphs, use `jlTilePasteGlyph`, which takes unambiguous 1-bit input. ==== === jlTilePasteGlyph [source,c] ---- void jlTilePasteGlyph(jlSurfaceT *dst, uint8_t bx, uint8_t by, const uint8_t *bits, uint8_t fgColor, uint8_t bgColor); ---- Pastes an 8x8 one-bit-per-pixel glyph: `bits[row]` is that row, most significant bit leftmost. A set bit draws `fgColor`, a clear bit `bgColor`. Opaque. This is the shape a character generator already has -- a font ROM, a C64 charset, anything 1bpp -- so nothing needs expanding into a `jlTileT` first. *Prefer this over `jlTilePasteMono` for glyphs.* [cols="1,1,4", options="header"] |=== |Parameter |Type |Description |`dst` |`jlSurfaceT *` |Destination surface. |`bx`, `by` |`uint8_t` |Block coordinates. |`bits` |`const uint8_t *` |8 bytes, one per glyph row, MSB leftmost. |`fgColor` |`uint8_t` |Set-bit colour. Masked to 4 bits. |`bgColor` |`uint8_t` |Clear-bit colour. Masked to 4 bits. |=== *Returns:* nothing. === jlDrawText [source,c] ---- void jlDrawText(jlSurfaceT *dst, uint8_t bx, uint8_t by, const jlSurfaceT *fontSurface, const uint16_t *asciiMap, const char *str); ---- Draws a NUL-terminated ASCII string using glyphs from a font surface. Glyph colour 0 is transparent, so the background shows through. The cursor wraps at the right edge and truncates at the bottom. A start off the 40x25 grid uses up characters without drawing them as the cursor moves on: a start column past 39 drops the first character and carries on at the start of the next row, and a start row past 24 draws nothing. [cols="1,1,4", options="header"] |=== |Parameter |Type |Description |`dst` |`jlSurfaceT *` |Destination surface. |`bx`, `by` |`uint8_t` |Starting block coordinates. |`fontSurface` |`const jlSurfaceT *` |Surface holding the glyphs. |`asciiMap` |`const uint16_t *` |256 entries mapping ASCII code to glyph position: low byte is source block X, high byte is source block Y. An entry of `JL_TILE_NO_GLYPH`, or one off the font surface's 40x25 grid, skips that character, advancing the cursor without drawing. |`str` |`const char *` |NUL-terminated string. |=== *Returns:* nothing. === jlTileMapPaste [source,c] ---- void jlTileMapPaste(jlSurfaceT *dst, uint8_t bx, uint8_t by, uint8_t wTiles, uint8_t hTiles, const jlTileT *tiles, const uint8_t *map); ---- Pastes a whole rectangle of tiles in one call, so validation, dispatch, and the dirty-rectangle union are paid once for the rectangle instead of once per tile. For anything larger than a few tiles this is substantially faster than looping over `jlTilePaste`. [cols="1,1,4", options="header"] |=== |Parameter |Type |Description |`dst` |`jlSurfaceT *` |Destination surface. |`bx`, `by` |`uint8_t` |Top-left block coordinates. |`wTiles`, `hTiles` |`uint8_t` |Rectangle size in tiles. Zero is a no-op. |`tiles` |`const jlTileT *` |The tileset. |`map` |`const uint8_t *` |`wTiles * hTiles` tileset indices, row-major, so `map[row * wTiles + col]`. `JL_TILE_MAP_SKIP` leaves that cell untouched. |=== *Returns:* nothing. A rectangle whose edge crosses the surface boundary is rejected as a no-op. [WARNING] ==== Any map index other than `JL_TILE_MAP_SKIP` must be a valid tileset index. The batch does not know the tileset length and does *not* range-check it -- an out-of-range index reads past the end of your array. ====