From 9b07cf710b1bbb17a4b43e6b88ab0e535dea1f50 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Sun, 22 Jun 2025 17:52:31 -0500 Subject: [PATCH] Add font data --- data/map project.tiled-session | 22 +++-- data/minogram.tsx | 4 + data/minogram_6x10.png | Bin 0 -> 1128 bytes src/dusk/ui/font.h | 2 +- src/dusk/ui/uitextbox.h | 8 +- tools/CMakeLists.txt | 3 +- tools/fontcompile/CMakeLists.txt | 20 +++++ tools/fontcompile/fontcompile.py | 142 +++++++++++++++++++++++++++++++ 8 files changed, 188 insertions(+), 13 deletions(-) create mode 100644 data/minogram.tsx create mode 100644 data/minogram_6x10.png create mode 100644 tools/fontcompile/CMakeLists.txt create mode 100644 tools/fontcompile/fontcompile.py diff --git a/data/map project.tiled-session b/data/map project.tiled-session index 18bcdef..5672977 100644 --- a/data/map project.tiled-session +++ b/data/map project.tiled-session @@ -1,8 +1,8 @@ { - "activeFile": "map.tmj", + "activeFile": "minogram.tsx", "expandedProjectPaths": [ - ".", - "templates" + "templates", + "." ], "fileStates": { ":/automap-tiles.tsx": { @@ -20,6 +20,10 @@ "y": 6836.833333333333 } }, + "minogram.tsx": { + "scaleInDock": 1, + "scaleInEditor": 16 + }, "overworld.tsx": { "scaleInDock": 1, "scaleInEditor": 4 @@ -30,19 +34,23 @@ "last.objectTemplatePath": "/home/yourwishes/htdocs/dusk/data/templates", "openFiles": [ "map.tmj", - "overworld.tsx" + "overworld.tsx", + "minogram.tsx" ], "project": "map project.tiled-project", "property.type": "int", "recentFiles": [ - "overworld.tsx", "map.tmj", + "overworld.tsx", + "minogram.tsx", "entities.tsx" ], "tileset.lastUsedFilter": "Tiled tileset files (*.tsx *.xml)", "tileset.lastUsedFormat": "tsx", + "tileset.margin": 1, + "tileset.spacing": 1, "tileset.tileSize": { - "height": 16, - "width": 16 + "height": 9, + "width": 5 } } diff --git a/data/minogram.tsx b/data/minogram.tsx new file mode 100644 index 0000000..373dc3f --- /dev/null +++ b/data/minogram.tsx @@ -0,0 +1,4 @@ + + + + diff --git a/data/minogram_6x10.png b/data/minogram_6x10.png new file mode 100644 index 0000000000000000000000000000000000000000..a198a751f7e63efea5cdeb5c9caad6f9c8b53c2e GIT binary patch literal 1128 zcmV-u1eg1XP)$Q00009a7bBm000XU z000XU0RWnu7ytkS8c9S!RCt{2T-%c5APCi|{Qoa!U)B^MTn>0iGLuu4>TN|N2nx78 zb>H{$A&%|;C)HjR+Gu-}URs%Iv|IeS3?3f}O0V&;#+|-z1|u$jH?c;qy@=Ls@wa$* zbP~`pmqq#sP9z~J>{Iwh6n!z6O{YDW)4^G2d@TORcF-eF^nFhB(jKP~XC@OT1=?vJ zqI3zCyw;uup}A@$PZV!GmbHTgLdI43gox`|Cp3q}-;=TZyBUGUhg%*jXZ_;197dd~ ze-fYKlJElHnHdF2-}=yqe@6R79_w(Jol$7?J(<7^i1FnQ{c<@MR83Sc>cQVRZZs(juEU(y!5>a+Ff; zw>X+D^ez-~xb*a&pjCh))mNk{i=3WF{mz?L<8AeOv79K2mgH-~MU!MnFdkd+mx{{M z+AWLK`q2RR5@e4!Rii1FB+P1bhf5S0;maOaS<3adWYjaivPFGiG`rk1Uqs<=@vIc;yfI{?=3`2FLlihDd-08HTppD2Cn z;{h*1Py4LEI2w>=+c_$|b3hqI=%uk<*?M+h?3LOj`i6skm8su5DZ72k2X9(lU< zV7|v$kvwga@aBWSf|)rqf=o!ES%Q^XN$n9ALyC`Hc)?}WwgS;0=pGlf7|*3>e8k5U zrQJ&DGT)`e(Dq*K{oz~{fo5sx6lMZO@mDQQGj-3Ty~rLp7!Vhm#ivWDUmTY}t@Tq} z7I#-iUJI@`Zhn?IN_8}^wie+(f<6jq|GuEsdQzNp3VCr|WW$Z}&&Z?kLE6yqdnwO# zRV$RR0$VHSMy=Jdx?_%2ii;=$&2c?NSe0QbaMwOhJXYR0pe~6h1yQb*mLZj1Z80D1 zs?ohq+Yhbp(wx$#xJXoSlCa*&M)r{?|BO5uAEXT(UrYHihZJ}?@*#lF;*#)wuey^b z3nY`!GLT5{y3_)^ABa@)tP+eDK&7xiJCu4C8HS~}yYCRt6O_kmOXNlmhn{MxHBKsV zqq2C#)u})fH@n2GK}*G?t3U68W;p2IR??u|GD#RMG~#-6Ut6}w$X|h29j`R2;Tt_| u=dPM@`7Vr%', '/', '*', ':', '#', '%', + '!', '?', '.', ',', "'", '"', '@', '&', '$', ' ' +] +CHAR_START = 0x20 # ASCII space character +CHAR_END = 0x7E # ASCII tilde character (exclusive) +CHARS_TOTAL = CHAR_END - CHAR_START + +# Check if the script is run with the correct arguments +parser = argparse.ArgumentParser(description="Generate chunk header files") +parser.add_argument('--output', required=True, help='File to write header') +parser.add_argument('--input', required=True, help='Input XML from tiled') +args = parser.parse_args() + +# Ensure outdir exists +outputFile = args.output +outputDir = os.path.dirname(outputFile) +os.makedirs(outputDir, exist_ok=True) + +# Read the XML file +inputFile = args.input +if not os.path.exists(inputFile): + print(f"Error: Input file '{inputFile}' does not exist.") + sys.exit(1) + +# Find root element +tree = ET.parse(inputFile) +root = tree.getroot() +# Check if the root element is 'tileset' +if root.tag != 'tileset': + print(f"Error: Expected root element 'tileset', found '{root.tag}'") + sys.exit(1) + +# Shoul have tilewidth and tileheight attributes +if 'tilewidth' not in root.attrib or 'tileheight' not in root.attrib: + print("Error: 'tileset' element must have 'tilewidth' and 'tileheight' attributes") + sys.exit(1) + +if 'tilecount' not in root.attrib: + print("Error: 'tileset' element must have 'tilecount' attribute") + sys.exit(1) + +# Find image element +image = root.find('image') +if image is None: + print("Error: 'tileset' element must contain an 'image' element") + sys.exit(1) + +# Ensure image has 'source' attribute +if 'source' not in image.attrib: + print("Error: 'image' element must have a 'source' attribute") + sys.exit(1) + +# Ensure image source exists +inputDir = os.path.dirname(inputFile) + +imageSource = os.path.join(inputDir, image.attrib['source']) +if not os.path.exists(imageSource): + print(f"Error: Image source '{imageSource}' does not exist.") + sys.exit(1) + +# Ensure image has 'width' and 'height' attributes +if 'width' not in image.attrib or 'height' not in image.attrib: + print("Error: 'image' element must have 'width' and 'height' attributes") + sys.exit(1) + +# Ensure image is readable +try: + img = Image.open(imageSource) +except Exception as e: + print(f"Error: Unable to open image '{imageSource}': {e}") + sys.exit(1) + +# Ensure image dimensions match the attributes +if img.width != int(image.attrib['width']) or img.height != int(image.attrib['height']): + print(f"Error: Image dimensions ({img.width}x{img.height}) do not match attributes ({image.attrib['width']}x{image.attrib['height']})") + sys.exit(1) + +# Prepare header content +now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") +tileCount = int(root.attrib['tilecount']) + +outputTileIndexes = [] +for i in range(CHARS_TOTAL): + # For some reason the input tilemap is not in ASCII, so we are going to map + # the tiles to the ASCII characters we expect, we start at 0x20 (space) + + # Find the index of the character in the CHARACTER_MAP + inputIndex = -1 + for j, char in enumerate(CHARACTER_MAP): + if ord(char) == (CHAR_START + i): + inputIndex = j + break + + if inputIndex == -1: + print(f"Warning: Character '{chr(CHAR_START + i)}' not found in CHARACTER_MAP") + outputTileIndexes.append(0) # Use 0 for missing characters (space) + continue + + outputTileIndexes.append(inputIndex) + +with open(outputFile, 'w') as f: + f.write(f"// Generated at {now}\n") + f.write("#pragma once\n") + f.write("#include \"dusk.h\"\n\n") + f.write(f"#define FONT_TILE_WIDTH {root.attrib['tilewidth']}\n") + f.write(f"#define FONT_TILE_HEIGHT {root.attrib['tileheight']}\n") + f.write(f"#define FONT_TILE_COUNT {len(outputTileIndexes)}\n") + f.write(f"#define FONT_CHAR_START {CHAR_START}\n") + f.write(f"#define FONT_CHAR_END {CHAR_END}\n\n") + + f.write("static const uint8_t TILE_PIXEL_DATA[FONT_TILE_COUNT][FONT_TILE_WIDTH * FONT_TILE_HEIGHT] = {\n") + for i in range(len(outputTileIndexes)): + tileIndex = outputTileIndexes[i] + f.write(f" // Character {i} ('{chr(CHAR_START + i)}'). Read from {tileIndex} tile.\n") + f.write(f" {{") + + # Read the tile from the image + tileX = (tileIndex % (img.width // int(root.attrib['tilewidth']))) * int(root.attrib['tilewidth']) + tileY = (tileIndex // (img.width // int(root.attrib['tilewidth']))) * int(root.attrib['tileheight']) + tile = img.crop((tileX, tileY, tileX + int(root.attrib['tilewidth']), tileY + int(root.attrib['tileheight']))) + + # Pixel is either 0 (transparent) or 1 (opaque) + for y in range(int(root.attrib['tileheight'])): + for x in range(int(root.attrib['tilewidth'])): + pixel = tile.getpixel((x, y)) + f.write(f"0x{1 if pixel[3] > 0 else 0:02X}, ") + + f.write("},\n") + f.write("};\n\n") \ No newline at end of file