== 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 |`TILE_PIXELS_PER_SIDE` |8 |Tile edge in pixels. |`TILE_BYTES_PER_ROW` |4 |Bytes per tile row (8 pixels at 4bpp). |`TILE_BYTES` |32 |Bytes in one tile. |`TILE_BLOCKS_PER_ROW` |40 |Tile columns on a surface. |`TILE_BLOCKS_PER_COL` |25 |Tile rows on a surface. |`TILE_NO_GLYPH` |0xFFFF |`jlDrawText` map entry meaning "skip this character". |`TILE_MAP_SKIP` |0xFF |`jlTileMapPaste` entry meaning "leave this cell alone". |=== [source,c] ---- typedef struct jlTileT { uint8_t pixels[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 planar ports, chunky on the others. Treat it as opaque. In particular, do not feed a tile captured by `jlTileSnap` to `jlTilePasteMono`, which wants a chunky silhouette -- 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. === 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` is target-native. Feeding one to this function on a planar port 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. [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 `TILE_NO_GLYPH` 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]`. `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 `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. ====