mirror of
https://github.com/spicyjpeg/573in1.git
synced 2025-03-01 07:20:42 +01:00
Add UTF-8 support and extended font
This commit is contained in:
parent
3f06ea5c50
commit
6ca0c02944
@ -121,6 +121,7 @@ addPS1Executable(
|
||||
src/common/util/log.cpp
|
||||
src/common/util/misc.cpp
|
||||
src/common/util/string.cpp
|
||||
src/common/util/string.s
|
||||
src/common/util/tween.cpp
|
||||
src/common/args.cpp
|
||||
src/common/gpu.cpp
|
||||
@ -219,6 +220,8 @@ addLauncher(803fd000 803ffff0)
|
||||
|
||||
## Boot stub and resource archive
|
||||
|
||||
file(GLOB_RECURSE assetList RELATIVE "${PROJECT_SOURCE_DIR}" assets/* )
|
||||
|
||||
configure_file(assets/about.txt about.txt NEWLINE_STYLE LF)
|
||||
|
||||
function(addBootStub name resourceName)
|
||||
@ -233,8 +236,7 @@ function(addBootStub name resourceName)
|
||||
OUTPUT ${resourceName}.zip
|
||||
DEPENDS
|
||||
${resourceName}.json
|
||||
assets/app.palette.json
|
||||
assets/app.strings.json
|
||||
${assetList}
|
||||
main.psexe
|
||||
launcher801fd000.psexe
|
||||
launcher803fd000.psexe
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"AboutScreen": {
|
||||
"title": "{RIGHT_ARROW} About 573in1",
|
||||
"prompt": "{RIGHT_ARROW} Use {LEFT_BUTTON}{RIGHT_BUTTON} to scroll. Press {START_BUTTON} to go back."
|
||||
"title": "▸ About 573in1",
|
||||
"prompt": "▸ Use ◁▷ to scroll. Press ▭ to go back."
|
||||
},
|
||||
|
||||
"App": {
|
||||
@ -73,7 +73,7 @@
|
||||
"erase": "Erasing existing header...\nDo not turn off the 573.",
|
||||
"write": "Writing new header...\nDo not turn off the 573.",
|
||||
"flashError": "An error occurred while erasing and rewriting the first sector of the internal flash memory.\n\nError code: %s\nPress the Test button to view debug logs.",
|
||||
"unsupported": "The flash memory chips on this device are unresponsive to commands or are currently unsupported. If you are trying to erase a PCMCIA card with a write protect switch, make sure the switch is off.\n\nSee the documentation for more information on supported flash chips."
|
||||
"unsupported": "The internal flash memory chips are unresponsive to commands or are currently unsupported. This likely means the 573 motherboard is damaged.\n\nSee the documentation for more information on supported flash chips."
|
||||
},
|
||||
"qrCodeWorker": {
|
||||
"compress": "Compressing cartridge dump...",
|
||||
@ -122,9 +122,9 @@
|
||||
},
|
||||
|
||||
"AudioTestScreen": {
|
||||
"title": "{RIGHT_ARROW} Audio output test",
|
||||
"title": "▸ Audio output test",
|
||||
"prompt": "Note that the speaker amplifier and analog audio passthrough are disabled by default.",
|
||||
"itemPrompt": "{RIGHT_ARROW} Press {START_BUTTON} to select, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back",
|
||||
"itemPrompt": "▸ Press ▭ to select, hold ◁▷ + ▭ to go back",
|
||||
|
||||
"playLeft": "Play sound on left channel",
|
||||
"playRight": "Play sound on right channel",
|
||||
@ -140,13 +140,13 @@
|
||||
"rom": "A valid boot executable has been found on the internal flash memory or a PCMCIA card and will be launched shortly. You may disable automatic booting globally by turning off DIP switch 1 or for flash devices only by using DIP switch 4.",
|
||||
"ide": "A valid boot executable has been found on an IDE drive and will be launched shortly. You may disable automatic booting globally by turning off DIP switch 1 or per-drive by creating a file named noboot.txt in the root of the filesystem.\n\nFile: %s",
|
||||
|
||||
"cancel": "{START_BUTTON} Cancel (%ds)"
|
||||
"cancel": "▭ Cancel (%ds)"
|
||||
},
|
||||
|
||||
"ButtonMappingScreen": {
|
||||
"title": "{RIGHT_ARROW} Select button mapping",
|
||||
"prompt": "Use {START_BUTTON} or the Test button to select a mapping preset suitable for your cabinet or JAMMA setup. Other buttons will be enabled once a mapping is selected.",
|
||||
"itemPrompt": "{RIGHT_ARROW} Press and hold {START_BUTTON} or Test to confirm",
|
||||
"title": "▸ Select button mapping",
|
||||
"prompt": "Use ▭ or the Test button to select a mapping preset suitable for your cabinet or JAMMA setup. Other buttons will be enabled once a mapping is selected.",
|
||||
"itemPrompt": "▸ Press and hold ▭ or Test to confirm",
|
||||
|
||||
"joystick": "JAMMA supergun or joystick/buttons",
|
||||
"ddrCab": "Dance Dance Revolution (2-player) cabinet",
|
||||
@ -166,8 +166,8 @@
|
||||
},
|
||||
|
||||
"CartActionsScreen": {
|
||||
"title": "{RIGHT_ARROW} Cartridge options",
|
||||
"itemPrompt": "{RIGHT_ARROW} Press {START_BUTTON} to select, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back",
|
||||
"title": "▸ Cartridge options",
|
||||
"itemPrompt": "▸ Press ▭ to select, hold ◁▷ + ▭ to go back",
|
||||
|
||||
"qrDump": {
|
||||
"name": "Dump cartridge as QR code",
|
||||
@ -218,7 +218,7 @@
|
||||
},
|
||||
|
||||
"CartInfoScreen": {
|
||||
"title": "{RIGHT_ARROW} Cartridge information",
|
||||
"title": "▸ Cartridge information",
|
||||
|
||||
"digitalIO": {
|
||||
"header": "Digital I/O board:\n",
|
||||
@ -233,8 +233,8 @@
|
||||
"zs01Info": " Chip type:\t\tKonami ZS01 (PIC16CE625)\n Unlock status:\t%s\n DS2401 ID:\t\t%s\n ZS01 ID:\t\t%s\n Configuration:\t%s\n"
|
||||
},
|
||||
"unlockStatus": {
|
||||
"locked": "{CLOSED_LOCK} locked, game key required",
|
||||
"unlocked": "{OPEN_LOCK} unlocked"
|
||||
"locked": "🔒 locked, game key required",
|
||||
"unlocked": "🔓 unlocked"
|
||||
},
|
||||
"id": {
|
||||
"error": "read failure",
|
||||
@ -265,9 +265,9 @@
|
||||
}
|
||||
},
|
||||
"prompt": {
|
||||
"locked": "{RIGHT_ARROW} Press {START_BUTTON} to unlock, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back.",
|
||||
"unlocked": "{RIGHT_ARROW} Press {START_BUTTON} to continue, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back.",
|
||||
"error": "{RIGHT_ARROW} Hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back."
|
||||
"locked": "▸ Press ▭ to unlock, hold ◁▷ + ▭ to go back.",
|
||||
"unlocked": "▸ Press ▭ to continue, hold ◁▷ + ▭ to go back.",
|
||||
"error": "▸ Hold ◁▷ + ▭ to go back."
|
||||
},
|
||||
|
||||
"unlockWarning": {
|
||||
@ -278,8 +278,8 @@
|
||||
},
|
||||
|
||||
"ChecksumScreen": {
|
||||
"title": "{RIGHT_ARROW} Storage device checksums",
|
||||
"prompt": "{RIGHT_ARROW} Press {START_BUTTON} to go back.",
|
||||
"title": "▸ Storage device checksums",
|
||||
"prompt": "▸ Press ▭ to go back.",
|
||||
|
||||
"bios": "BIOS ROM (512 KB):\t\t\t\t%08X\n",
|
||||
"rtc": "RTC RAM (8184 bytes):\t\t\t%08X\n",
|
||||
@ -289,8 +289,8 @@
|
||||
},
|
||||
|
||||
"ColorIntensityScreen": {
|
||||
"title": "{RIGHT_ARROW} Monitor color intensity test",
|
||||
"prompt": "{RIGHT_ARROW} Press {START_BUTTON} to go back.",
|
||||
"title": "▸ Monitor color intensity test",
|
||||
"prompt": "▸ Press ▭ to go back.",
|
||||
|
||||
"white": "White",
|
||||
"red": "Red",
|
||||
@ -305,18 +305,18 @@
|
||||
},
|
||||
|
||||
"FileBrowserScreen": {
|
||||
"title": "{RIGHT_ARROW} Select file",
|
||||
"itemPrompt": "{RIGHT_ARROW} Press {START_BUTTON} to select, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back",
|
||||
"title": "▸ Select file",
|
||||
"itemPrompt": "▸ Press ▭ to select, hold ◁▷ + ▭ to go back",
|
||||
|
||||
"parentDir": "[Parent directory]",
|
||||
"subdirError": "An error occurred while enumerating files in the selected subdirectory. The filesystem may be corrupted or otherwise inaccessible.\n\nPath: %s\nPress the Test button to view debug logs."
|
||||
},
|
||||
|
||||
"FilePickerScreen": {
|
||||
"title": "{RIGHT_ARROW} Select IDE drive",
|
||||
"itemPrompt": "{RIGHT_ARROW} Press {START_BUTTON} to select, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back",
|
||||
"title": "▸ Select IDE drive",
|
||||
"itemPrompt": "▸ Press ▭ to select, hold ◁▷ + ▭ to go back",
|
||||
|
||||
"host": "{HOST_ICON} Host filesystem (PCDRV)",
|
||||
"host": "🖧 Host filesystem (PCDRV)",
|
||||
"noFS": "no disc or unsupported FS",
|
||||
|
||||
"noDeviceError": "No drives have been found and successfully initialized on the IDE bus. Make sure the drives are appropriately configured as primary or secondary and are receiving power.\n\nPress the Test button to view debug logs.",
|
||||
@ -326,18 +326,18 @@
|
||||
},
|
||||
|
||||
"GeometryScreen": {
|
||||
"title": "{RIGHT_ARROW} Monitor geometry test",
|
||||
"prompt": "{RIGHT_ARROW} Press {START_BUTTON} to go back."
|
||||
"title": "▸ Monitor geometry test",
|
||||
"prompt": "▸ Press ▭ to go back."
|
||||
},
|
||||
|
||||
"HexdumpScreen": {
|
||||
"title": "{RIGHT_ARROW} Cartridge dump",
|
||||
"prompt": "{RIGHT_ARROW} Use {LEFT_BUTTON}{RIGHT_BUTTON} to scroll. Press {START_BUTTON} to go back."
|
||||
"title": "▸ Cartridge dump",
|
||||
"prompt": "▸ Use ◁▷ to scroll. Press ▭ to go back."
|
||||
},
|
||||
|
||||
"IDEInfoScreen": {
|
||||
"title": "{RIGHT_ARROW} IDE device information",
|
||||
"prompt": "{RIGHT_ARROW} Use {LEFT_BUTTON}{RIGHT_BUTTON} to scroll. Press {START_BUTTON} to go back.",
|
||||
"title": "▸ IDE device information",
|
||||
"prompt": "▸ Use ◁▷ to scroll. Press ▭ to go back.",
|
||||
|
||||
"device": {
|
||||
"header": {
|
||||
@ -371,8 +371,8 @@
|
||||
},
|
||||
|
||||
"JAMMATestScreen": {
|
||||
"title": "{RIGHT_ARROW} JAMMA input test",
|
||||
"prompt": "{RIGHT_ARROW} Press and hold {START_BUTTON} to go back.",
|
||||
"title": "▸ JAMMA input test",
|
||||
"prompt": "▸ Press and hold ▭ to go back.",
|
||||
|
||||
"noInputs": "No button is currently held down. If a button does not appear here when pressed, make sure the JAMMA harness is wired up correctly and the button is not damaged.\n",
|
||||
"inputs": "The following buttons are currently held down:\n",
|
||||
@ -389,7 +389,7 @@
|
||||
"button4": " Player 1 button 4\t\tJAMMA pin 25\n",
|
||||
"button5": " Player 1 button 5\t\tJAMMA pin 26\n",
|
||||
"button6": " Player 1 button 6\n",
|
||||
"start": " Player 1 start button ({START_BUTTON})\tJAMMA pin 17\n"
|
||||
"start": " Player 1 start button (▭)\tJAMMA pin 17\n"
|
||||
},
|
||||
"p2": {
|
||||
"left": " Player 2 joystick left\t\tJAMMA pin X\n",
|
||||
@ -402,7 +402,7 @@
|
||||
"button4": " Player 2 button 4\t\tJAMMA pin c\n",
|
||||
"button5": " Player 2 button 5\t\tJAMMA pin d\n",
|
||||
"button6": " Player 2 button 6\n",
|
||||
"start": " Player 2 start button ({START_BUTTON})\tJAMMA pin U\n"
|
||||
"start": " Player 2 start button (▭)\tJAMMA pin U\n"
|
||||
},
|
||||
|
||||
"coin1": " Coin switch 1\t\t\tJAMMA pin 16\n",
|
||||
@ -413,14 +413,14 @@
|
||||
|
||||
"KeyEntryScreen": {
|
||||
"title": "Enter unlocking key",
|
||||
"body": "Enter the 8-byte key this cartridge was last locked with.\n\nUse {LEFT_BUTTON}{RIGHT_BUTTON} to move the cursor, hold {START_BUTTON} and use {LEFT_BUTTON}{RIGHT_BUTTON} to edit the highlighted digit.",
|
||||
"body": "Enter the 8-byte key this cartridge was last locked with.\n\nUse ◁▷ to move the cursor, hold ▭ and use ◁▷ to edit the highlighted digit.",
|
||||
"cancel": "Cancel",
|
||||
"ok": "Confirm"
|
||||
},
|
||||
|
||||
"MainMenuScreen": {
|
||||
"title": "{RIGHT_ARROW} 573in1",
|
||||
"itemPrompt": "{RIGHT_ARROW} Use {LEFT_BUTTON}{RIGHT_BUTTON} to move, select by pressing {START_BUTTON}",
|
||||
"title": "▸ 573in1",
|
||||
"itemPrompt": "▸ Use ◁▷ to move, select by pressing ▭",
|
||||
|
||||
"cartInfo": {
|
||||
"name": "Manage security cartridge",
|
||||
@ -476,20 +476,20 @@
|
||||
},
|
||||
|
||||
"QRCodeScreen": {
|
||||
"title": "{RIGHT_ARROW} Cartridge dump",
|
||||
"prompt": "Scan this code and paste the resulting string into the decodeDump.py script provided alongside 573in1 to obtain a dump of the cartridge. Press {START_BUTTON} to go back."
|
||||
"title": "▸ Cartridge dump",
|
||||
"prompt": "Scan this code and paste the resulting string into the decodeDump.py script provided alongside 573in1 to obtain a dump of the cartridge. Press ▭ to go back."
|
||||
},
|
||||
|
||||
"ReflashGameScreen": {
|
||||
"title": "{RIGHT_ARROW} Select game to convert cartridge to",
|
||||
"title": "▸ Select game to convert cartridge to",
|
||||
"prompt": "Make sure you select the correct region. Note that cartridges can only be converted for use with games that accept the same cartridge type.",
|
||||
"itemPrompt": "{RIGHT_ARROW} Press {START_BUTTON} to select, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back"
|
||||
"itemPrompt": "▸ Press ▭ to select, hold ◁▷ + ▭ to go back"
|
||||
},
|
||||
|
||||
"ResolutionScreen": {
|
||||
"title": "{RIGHT_ARROW} Select screen resolution",
|
||||
"title": "▸ Select screen resolution",
|
||||
"prompt": "Select a resolution appropriate for your monitor or upscaler setup. Note that interlaced modes may be subject to flickering.",
|
||||
"itemPrompt": "{RIGHT_ARROW} Press {START_BUTTON} to select, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back",
|
||||
"itemPrompt": "▸ Press ▭ to select, hold ◁▷ + ▭ to go back",
|
||||
|
||||
"320x240p": "320x240 (4:3), progressive",
|
||||
"320x240i": "320x240 (4:3), interlaced (line doubled)",
|
||||
@ -504,14 +504,14 @@
|
||||
|
||||
"RTCTimeScreen": {
|
||||
"title": "Set RTC date and time",
|
||||
"body": "Enter the current date and time. Note that System 573 games only accept years in 1970-2069 range.\n\nUse {LEFT_BUTTON}{RIGHT_BUTTON} to move the cursor, hold {START_BUTTON} and use {LEFT_BUTTON}{RIGHT_BUTTON} to edit the highlighted field.",
|
||||
"body": "Enter the current date and time. Note that System 573 games only accept years in 1970-2069 range.\n\nUse ◁▷ to move the cursor, hold ▭ and use ◁▷ to edit the highlighted field.",
|
||||
"cancel": "Cancel",
|
||||
"ok": "Confirm"
|
||||
},
|
||||
|
||||
"StorageActionsScreen": {
|
||||
"title": "{RIGHT_ARROW} Storage device options",
|
||||
"itemPrompt": "{RIGHT_ARROW} Press {START_BUTTON} to select, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back",
|
||||
"title": "▸ Storage device options",
|
||||
"itemPrompt": "▸ Press ▭ to select, hold ◁▷ + ▭ to go back",
|
||||
"cardError": "The selected PCMCIA slot is empty.\n\nTurn off the system and insert a supported PCMCIA linear flash card in order to continue. DO NOT HOTPLUG CARDS; hotplugging may damage both the 573 and the card.",
|
||||
|
||||
"runExecutable": {
|
||||
@ -616,8 +616,8 @@
|
||||
},
|
||||
|
||||
"StorageInfoScreen": {
|
||||
"title": "{RIGHT_ARROW} Storage device information",
|
||||
"prompt": "{RIGHT_ARROW} Press {START_BUTTON} for more options, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back.",
|
||||
"title": "▸ Storage device information",
|
||||
"prompt": "▸ Press ▭ for more options, hold ◁▷ + ▭ to go back.",
|
||||
|
||||
"bios": {
|
||||
"header": "BIOS ROM:\n",
|
||||
@ -666,14 +666,14 @@
|
||||
|
||||
"SystemIDEntryScreen": {
|
||||
"title": "Edit system identifier",
|
||||
"body": "Enter the new digital I/O board's identifier. To obtain the ID of another board, run 573in1 on its respective system.\n\nUse {LEFT_BUTTON}{RIGHT_BUTTON} to move the cursor, hold {START_BUTTON} and use {LEFT_BUTTON}{RIGHT_BUTTON} to edit the highlighted digit.",
|
||||
"body": "Enter the new digital I/O board's identifier. To obtain the ID of another board, run 573in1 on its respective system.\n\nUse ◁▷ to move the cursor, hold ▭ and use ◁▷ to edit the highlighted digit.",
|
||||
"cancel": "Cancel",
|
||||
"ok": "Confirm"
|
||||
},
|
||||
|
||||
"TestMenuScreen": {
|
||||
"title": "{RIGHT_ARROW} Hardware test suite",
|
||||
"itemPrompt": "{RIGHT_ARROW} Press {START_BUTTON} to select, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back",
|
||||
"title": "▸ Hardware test suite",
|
||||
"itemPrompt": "▸ Press ▭ to select, hold ◁▷ + ▭ to go back",
|
||||
|
||||
"jammaTest": {
|
||||
"name": "Test JAMMA inputs",
|
||||
@ -694,9 +694,9 @@
|
||||
},
|
||||
|
||||
"UnlockKeyScreen": {
|
||||
"title": "{RIGHT_ARROW} Select unlocking key",
|
||||
"title": "▸ Select unlocking key",
|
||||
"prompt": "If the cartridge has been converted before, select the game it was last converted to. If it is currently blank, select 00-00-00-00-00-00-00-00.",
|
||||
"itemPrompt": "{RIGHT_ARROW} Press {START_BUTTON} to select, hold {LEFT_BUTTON}{RIGHT_BUTTON} + {START_BUTTON} to go back",
|
||||
"itemPrompt": "▸ Press ▭ to select, hold ◁▷ + ▭ to go back",
|
||||
|
||||
"autoUnlock": "Use key from identified game (recommended)",
|
||||
"useCustomKey": "Enter key manually...",
|
||||
@ -709,7 +709,7 @@
|
||||
"body": "573in1 is an experimental tool provided with no warranty whatsoever. It is not guaranteed to work and improper usage may PERMANENTLY BRICK your System 573 security cartridges.\n\nUse this tool at your own risk. Do not proceed if you do not know what you are doing.",
|
||||
|
||||
"cooldown": "Wait... (%ds)",
|
||||
"ok": "{START_BUTTON} Continue"
|
||||
"ok": "▭ Continue"
|
||||
},
|
||||
|
||||
"WorkerStatusScreen": {
|
341
assets/textures/font.json
Normal file
341
assets/textures/font.json
Normal file
@ -0,0 +1,341 @@
|
||||
{
|
||||
"$schema": "../../schema/metrics.json",
|
||||
|
||||
"spaceWidth": 4,
|
||||
"tabWidth": 32,
|
||||
"lineHeight": 10,
|
||||
"baselineOffset": -1,
|
||||
|
||||
"characterSizes": {
|
||||
" ": { "x": 0, "y": 0, "width": 4, "height": 10 },
|
||||
"!": { "x": 6, "y": 0, "width": 2, "height": 10 },
|
||||
"\"": { "x": 12, "y": 0, "width": 4, "height": 10 },
|
||||
"#": { "x": 18, "y": 0, "width": 6, "height": 10 },
|
||||
"$": { "x": 24, "y": 0, "width": 6, "height": 10 },
|
||||
"%": { "x": 30, "y": 0, "width": 6, "height": 10 },
|
||||
"&": { "x": 36, "y": 0, "width": 6, "height": 10 },
|
||||
"'": { "x": 42, "y": 0, "width": 2, "height": 10 },
|
||||
"(": { "x": 48, "y": 0, "width": 3, "height": 10 },
|
||||
")": { "x": 54, "y": 0, "width": 3, "height": 10 },
|
||||
"*": { "x": 60, "y": 0, "width": 4, "height": 10 },
|
||||
"+": { "x": 66, "y": 0, "width": 6, "height": 10 },
|
||||
",": { "x": 72, "y": 0, "width": 3, "height": 10 },
|
||||
"-": { "x": 78, "y": 0, "width": 6, "height": 10 },
|
||||
".": { "x": 84, "y": 0, "width": 2, "height": 10 },
|
||||
"/": { "x": 90, "y": 0, "width": 6, "height": 10 },
|
||||
"0": { "x": 0, "y": 10, "width": 6, "height": 10 },
|
||||
"1": { "x": 6, "y": 10, "width": 6, "height": 10 },
|
||||
"2": { "x": 12, "y": 10, "width": 6, "height": 10 },
|
||||
"3": { "x": 18, "y": 10, "width": 6, "height": 10 },
|
||||
"4": { "x": 24, "y": 10, "width": 6, "height": 10 },
|
||||
"5": { "x": 30, "y": 10, "width": 6, "height": 10 },
|
||||
"6": { "x": 36, "y": 10, "width": 6, "height": 10 },
|
||||
"7": { "x": 42, "y": 10, "width": 6, "height": 10 },
|
||||
"8": { "x": 48, "y": 10, "width": 6, "height": 10 },
|
||||
"9": { "x": 54, "y": 10, "width": 6, "height": 10 },
|
||||
":": { "x": 60, "y": 10, "width": 2, "height": 10 },
|
||||
";": { "x": 66, "y": 10, "width": 3, "height": 10 },
|
||||
"<": { "x": 72, "y": 10, "width": 6, "height": 10 },
|
||||
"=": { "x": 78, "y": 10, "width": 6, "height": 10 },
|
||||
">": { "x": 84, "y": 10, "width": 6, "height": 10 },
|
||||
"?": { "x": 90, "y": 10, "width": 6, "height": 10 },
|
||||
"@": { "x": 0, "y": 20, "width": 6, "height": 10 },
|
||||
"A": { "x": 6, "y": 20, "width": 6, "height": 10 },
|
||||
"B": { "x": 12, "y": 20, "width": 6, "height": 10 },
|
||||
"C": { "x": 18, "y": 20, "width": 6, "height": 10 },
|
||||
"D": { "x": 24, "y": 20, "width": 6, "height": 10 },
|
||||
"E": { "x": 30, "y": 20, "width": 6, "height": 10 },
|
||||
"F": { "x": 36, "y": 20, "width": 6, "height": 10 },
|
||||
"G": { "x": 42, "y": 20, "width": 6, "height": 10 },
|
||||
"H": { "x": 48, "y": 20, "width": 6, "height": 10 },
|
||||
"I": { "x": 54, "y": 20, "width": 4, "height": 10 },
|
||||
"J": { "x": 60, "y": 20, "width": 5, "height": 10 },
|
||||
"K": { "x": 66, "y": 20, "width": 6, "height": 10 },
|
||||
"L": { "x": 72, "y": 20, "width": 6, "height": 10 },
|
||||
"M": { "x": 78, "y": 20, "width": 6, "height": 10 },
|
||||
"N": { "x": 84, "y": 20, "width": 6, "height": 10 },
|
||||
"O": { "x": 90, "y": 20, "width": 6, "height": 10 },
|
||||
"P": { "x": 0, "y": 30, "width": 6, "height": 10 },
|
||||
"Q": { "x": 6, "y": 30, "width": 6, "height": 10 },
|
||||
"R": { "x": 12, "y": 30, "width": 6, "height": 10 },
|
||||
"S": { "x": 18, "y": 30, "width": 6, "height": 10 },
|
||||
"T": { "x": 24, "y": 30, "width": 6, "height": 10 },
|
||||
"U": { "x": 30, "y": 30, "width": 6, "height": 10 },
|
||||
"V": { "x": 36, "y": 30, "width": 6, "height": 10 },
|
||||
"W": { "x": 42, "y": 30, "width": 6, "height": 10 },
|
||||
"X": { "x": 48, "y": 30, "width": 6, "height": 10 },
|
||||
"Y": { "x": 54, "y": 30, "width": 6, "height": 10 },
|
||||
"Z": { "x": 60, "y": 30, "width": 6, "height": 10 },
|
||||
"[": { "x": 66, "y": 30, "width": 3, "height": 10 },
|
||||
"\\": { "x": 72, "y": 30, "width": 6, "height": 10 },
|
||||
"]": { "x": 78, "y": 30, "width": 3, "height": 10 },
|
||||
"^": { "x": 84, "y": 30, "width": 4, "height": 10 },
|
||||
"_": { "x": 90, "y": 30, "width": 6, "height": 10 },
|
||||
"`": { "x": 0, "y": 40, "width": 3, "height": 10 },
|
||||
"a": { "x": 6, "y": 40, "width": 6, "height": 10 },
|
||||
"b": { "x": 12, "y": 40, "width": 6, "height": 10 },
|
||||
"c": { "x": 18, "y": 40, "width": 6, "height": 10 },
|
||||
"d": { "x": 24, "y": 40, "width": 6, "height": 10 },
|
||||
"e": { "x": 30, "y": 40, "width": 6, "height": 10 },
|
||||
"f": { "x": 36, "y": 40, "width": 5, "height": 10 },
|
||||
"g": { "x": 42, "y": 40, "width": 6, "height": 10 },
|
||||
"h": { "x": 48, "y": 40, "width": 5, "height": 10 },
|
||||
"i": { "x": 54, "y": 40, "width": 2, "height": 10 },
|
||||
"j": { "x": 60, "y": 40, "width": 4, "height": 10 },
|
||||
"k": { "x": 66, "y": 40, "width": 5, "height": 10 },
|
||||
"l": { "x": 72, "y": 40, "width": 2, "height": 10 },
|
||||
"m": { "x": 78, "y": 40, "width": 6, "height": 10 },
|
||||
"n": { "x": 84, "y": 40, "width": 5, "height": 10 },
|
||||
"o": { "x": 90, "y": 40, "width": 6, "height": 10 },
|
||||
"p": { "x": 0, "y": 50, "width": 6, "height": 10 },
|
||||
"q": { "x": 6, "y": 50, "width": 6, "height": 10 },
|
||||
"r": { "x": 12, "y": 50, "width": 6, "height": 10 },
|
||||
"s": { "x": 18, "y": 50, "width": 6, "height": 10 },
|
||||
"t": { "x": 24, "y": 50, "width": 5, "height": 10 },
|
||||
"u": { "x": 30, "y": 50, "width": 5, "height": 10 },
|
||||
"v": { "x": 36, "y": 50, "width": 6, "height": 10 },
|
||||
"w": { "x": 42, "y": 50, "width": 6, "height": 10 },
|
||||
"x": { "x": 48, "y": 50, "width": 6, "height": 10 },
|
||||
"y": { "x": 54, "y": 50, "width": 6, "height": 10 },
|
||||
"z": { "x": 60, "y": 50, "width": 5, "height": 10 },
|
||||
"{": { "x": 66, "y": 50, "width": 4, "height": 10 },
|
||||
"|": { "x": 72, "y": 50, "width": 2, "height": 10 },
|
||||
"}": { "x": 78, "y": 50, "width": 4, "height": 10 },
|
||||
"~": { "x": 84, "y": 50, "width": 6, "height": 10 },
|
||||
|
||||
"\u00a0": { "x": 0, "y": 60, "width": 4, "height": 10 },
|
||||
"¡": { "x": 6, "y": 60, "width": 2, "height": 10 },
|
||||
"¢": { "x": 12, "y": 60, "width": 5, "height": 10 },
|
||||
"£": { "x": 18, "y": 60, "width": 6, "height": 10 },
|
||||
"¤": { "x": 24, "y": 60, "width": 6, "height": 10 },
|
||||
"¥": { "x": 30, "y": 60, "width": 6, "height": 10 },
|
||||
"¦": { "x": 36, "y": 60, "width": 2, "height": 10 },
|
||||
"§": { "x": 42, "y": 60, "width": 5, "height": 10 },
|
||||
"¨": { "x": 48, "y": 60, "width": 4, "height": 10 },
|
||||
"©": { "x": 54, "y": 60, "width": 6, "height": 10 },
|
||||
"ª": { "x": 60, "y": 60, "width": 4, "height": 10 },
|
||||
"«": { "x": 66, "y": 60, "width": 6, "height": 10 },
|
||||
"¬": { "x": 72, "y": 60, "width": 6, "height": 10 },
|
||||
"\u00ad": { "x": 78, "y": 60, "width": 4, "height": 10 },
|
||||
"®": { "x": 84, "y": 60, "width": 6, "height": 10 },
|
||||
"¯": { "x": 90, "y": 60, "width": 6, "height": 10 },
|
||||
"°": { "x": 0, "y": 70, "width": 4, "height": 10 },
|
||||
"±": { "x": 6, "y": 70, "width": 6, "height": 10 },
|
||||
"²": { "x": 12, "y": 70, "width": 4, "height": 10 },
|
||||
"³": { "x": 18, "y": 70, "width": 4, "height": 10 },
|
||||
"´": { "x": 24, "y": 70, "width": 3, "height": 10 },
|
||||
"µ": { "x": 30, "y": 70, "width": 5, "height": 10 },
|
||||
"¶": { "x": 36, "y": 70, "width": 6, "height": 10 },
|
||||
"·": { "x": 42, "y": 70, "width": 4, "height": 10 },
|
||||
"¸": { "x": 48, "y": 70, "width": 5, "height": 10 },
|
||||
"¹": { "x": 54, "y": 70, "width": 4, "height": 10 },
|
||||
"º": { "x": 60, "y": 70, "width": 4, "height": 10 },
|
||||
"»": { "x": 66, "y": 70, "width": 6, "height": 10 },
|
||||
"¼": { "x": 72, "y": 70, "width": 6, "height": 10 },
|
||||
"½": { "x": 78, "y": 70, "width": 6, "height": 10 },
|
||||
"¾": { "x": 84, "y": 70, "width": 6, "height": 10 },
|
||||
"¿": { "x": 90, "y": 70, "width": 6, "height": 10 },
|
||||
"À": { "x": 0, "y": 80, "width": 6, "height": 10 },
|
||||
"Á": { "x": 6, "y": 80, "width": 6, "height": 10 },
|
||||
"Â": { "x": 12, "y": 80, "width": 6, "height": 10 },
|
||||
"Ã": { "x": 18, "y": 80, "width": 6, "height": 10 },
|
||||
"Ä": { "x": 24, "y": 80, "width": 6, "height": 10 },
|
||||
"Å": { "x": 30, "y": 80, "width": 6, "height": 10 },
|
||||
"Æ": { "x": 36, "y": 80, "width": 6, "height": 10 },
|
||||
"Ç": { "x": 42, "y": 80, "width": 6, "height": 10 },
|
||||
"È": { "x": 48, "y": 80, "width": 6, "height": 10 },
|
||||
"É": { "x": 54, "y": 80, "width": 6, "height": 10 },
|
||||
"Ê": { "x": 60, "y": 80, "width": 6, "height": 10 },
|
||||
"Ë": { "x": 66, "y": 80, "width": 6, "height": 10 },
|
||||
"Ì": { "x": 72, "y": 80, "width": 4, "height": 10 },
|
||||
"Í": { "x": 78, "y": 80, "width": 4, "height": 10 },
|
||||
"Î": { "x": 84, "y": 80, "width": 4, "height": 10 },
|
||||
"Ï": { "x": 90, "y": 80, "width": 4, "height": 10 },
|
||||
"Ð": { "x": 0, "y": 90, "width": 6, "height": 10 },
|
||||
"Ñ": { "x": 6, "y": 90, "width": 6, "height": 10 },
|
||||
"Ò": { "x": 12, "y": 90, "width": 6, "height": 10 },
|
||||
"Ó": { "x": 18, "y": 90, "width": 6, "height": 10 },
|
||||
"Ô": { "x": 24, "y": 90, "width": 6, "height": 10 },
|
||||
"Õ": { "x": 30, "y": 90, "width": 6, "height": 10 },
|
||||
"Ö": { "x": 36, "y": 90, "width": 6, "height": 10 },
|
||||
"×": { "x": 42, "y": 90, "width": 6, "height": 10 },
|
||||
"Ø": { "x": 48, "y": 90, "width": 6, "height": 10 },
|
||||
"Ù": { "x": 54, "y": 90, "width": 6, "height": 10 },
|
||||
"Ú": { "x": 60, "y": 90, "width": 6, "height": 10 },
|
||||
"Û": { "x": 66, "y": 90, "width": 6, "height": 10 },
|
||||
"Ü": { "x": 72, "y": 90, "width": 6, "height": 10 },
|
||||
"Ý": { "x": 78, "y": 90, "width": 6, "height": 10 },
|
||||
"Þ": { "x": 84, "y": 90, "width": 6, "height": 10 },
|
||||
"ß": { "x": 90, "y": 90, "width": 6, "height": 10 },
|
||||
"à": { "x": 0, "y": 100, "width": 6, "height": 10 },
|
||||
"á": { "x": 6, "y": 100, "width": 6, "height": 10 },
|
||||
"â": { "x": 12, "y": 100, "width": 6, "height": 10 },
|
||||
"ã": { "x": 18, "y": 100, "width": 6, "height": 10 },
|
||||
"ä": { "x": 24, "y": 100, "width": 6, "height": 10 },
|
||||
"å": { "x": 30, "y": 100, "width": 6, "height": 10 },
|
||||
"æ": { "x": 36, "y": 100, "width": 6, "height": 10 },
|
||||
"ç": { "x": 42, "y": 100, "width": 6, "height": 10 },
|
||||
"è": { "x": 48, "y": 100, "width": 6, "height": 10 },
|
||||
"é": { "x": 54, "y": 100, "width": 6, "height": 10 },
|
||||
"ê": { "x": 60, "y": 100, "width": 6, "height": 10 },
|
||||
"ë": { "x": 66, "y": 100, "width": 6, "height": 10 },
|
||||
"ì": { "x": 72, "y": 100, "width": 3, "height": 10 },
|
||||
"í": { "x": 78, "y": 100, "width": 3, "height": 10 },
|
||||
"î": { "x": 84, "y": 100, "width": 4, "height": 10 },
|
||||
"ï": { "x": 90, "y": 100, "width": 4, "height": 10 },
|
||||
"ð": { "x": 0, "y": 110, "width": 5, "height": 10 },
|
||||
"ñ": { "x": 6, "y": 110, "width": 5, "height": 10 },
|
||||
"ò": { "x": 12, "y": 110, "width": 6, "height": 10 },
|
||||
"ó": { "x": 18, "y": 110, "width": 6, "height": 10 },
|
||||
"ô": { "x": 24, "y": 110, "width": 6, "height": 10 },
|
||||
"õ": { "x": 30, "y": 110, "width": 6, "height": 10 },
|
||||
"ö": { "x": 36, "y": 110, "width": 6, "height": 10 },
|
||||
"÷": { "x": 42, "y": 110, "width": 6, "height": 10 },
|
||||
"ø": { "x": 48, "y": 110, "width": 6, "height": 10 },
|
||||
"ù": { "x": 54, "y": 110, "width": 5, "height": 10 },
|
||||
"ú": { "x": 60, "y": 110, "width": 5, "height": 10 },
|
||||
"û": { "x": 66, "y": 110, "width": 5, "height": 10 },
|
||||
"ü": { "x": 72, "y": 110, "width": 5, "height": 10 },
|
||||
"ý": { "x": 78, "y": 110, "width": 6, "height": 10 },
|
||||
"þ": { "x": 84, "y": 110, "width": 5, "height": 10 },
|
||||
"ÿ": { "x": 90, "y": 110, "width": 6, "height": 10 },
|
||||
|
||||
"▴": { "x": 0, "y": 120, "width": 6, "height": 10 },
|
||||
"▾": { "x": 6, "y": 120, "width": 6, "height": 10 },
|
||||
"◂": { "x": 12, "y": 120, "width": 4, "height": 10 },
|
||||
"▸": { "x": 18, "y": 120, "width": 4, "height": 10 },
|
||||
"↑": { "x": 24, "y": 120, "width": 6, "height": 10 },
|
||||
"↓": { "x": 30, "y": 120, "width": 6, "height": 10 },
|
||||
"←": { "x": 36, "y": 120, "width": 6, "height": 10 },
|
||||
"→": { "x": 42, "y": 120, "width": 6, "height": 10 },
|
||||
"<22>": { "x": 48, "y": 120, "width": 6, "height": 10 },
|
||||
|
||||
"゠": { "x": 96, "y": 0, "width": 7, "height": 10 },
|
||||
"ァ": { "x": 104, "y": 0, "width": 6, "height": 10 },
|
||||
"ア": { "x": 112, "y": 0, "width": 7, "height": 10 },
|
||||
"ィ": { "x": 120, "y": 0, "width": 5, "height": 10 },
|
||||
"イ": { "x": 128, "y": 0, "width": 6, "height": 10 },
|
||||
"ゥ": { "x": 136, "y": 0, "width": 6, "height": 10 },
|
||||
"ウ": { "x": 144, "y": 0, "width": 6, "height": 10 },
|
||||
"ェ": { "x": 152, "y": 0, "width": 6, "height": 10 },
|
||||
"エ": { "x": 160, "y": 0, "width": 6, "height": 10 },
|
||||
"ォ": { "x": 168, "y": 0, "width": 6, "height": 10 },
|
||||
"オ": { "x": 176, "y": 0, "width": 7, "height": 10 },
|
||||
"カ": { "x": 184, "y": 0, "width": 7, "height": 10 },
|
||||
"ガ": { "x": 192, "y": 0, "width": 8, "height": 10 },
|
||||
"キ": { "x": 200, "y": 0, "width": 6, "height": 10 },
|
||||
"ギ": { "x": 208, "y": 0, "width": 8, "height": 10 },
|
||||
"ク": { "x": 216, "y": 0, "width": 5, "height": 10 },
|
||||
"グ": { "x": 96, "y": 10, "width": 8, "height": 10 },
|
||||
"ケ": { "x": 104, "y": 10, "width": 7, "height": 10 },
|
||||
"ゲ": { "x": 112, "y": 10, "width": 8, "height": 10 },
|
||||
"コ": { "x": 120, "y": 10, "width": 5, "height": 10 },
|
||||
"ゴ": { "x": 128, "y": 10, "width": 7, "height": 10 },
|
||||
"サ": { "x": 136, "y": 10, "width": 6, "height": 10 },
|
||||
"ザ": { "x": 144, "y": 10, "width": 8, "height": 10 },
|
||||
"シ": { "x": 152, "y": 10, "width": 7, "height": 10 },
|
||||
"ジ": { "x": 160, "y": 10, "width": 8, "height": 10 },
|
||||
"ス": { "x": 168, "y": 10, "width": 6, "height": 10 },
|
||||
"ズ": { "x": 176, "y": 10, "width": 8, "height": 10 },
|
||||
"セ": { "x": 184, "y": 10, "width": 7, "height": 10 },
|
||||
"ゼ": { "x": 192, "y": 10, "width": 8, "height": 10 },
|
||||
"ソ": { "x": 200, "y": 10, "width": 5, "height": 10 },
|
||||
"ゾ": { "x": 208, "y": 10, "width": 8, "height": 10 },
|
||||
"タ": { "x": 216, "y": 10, "width": 5, "height": 10 },
|
||||
"ダ": { "x": 96, "y": 20, "width": 8, "height": 10 },
|
||||
"チ": { "x": 104, "y": 20, "width": 6, "height": 10 },
|
||||
"ヂ": { "x": 112, "y": 20, "width": 8, "height": 10 },
|
||||
"ッ": { "x": 120, "y": 20, "width": 7, "height": 10 },
|
||||
"ツ": { "x": 128, "y": 20, "width": 7, "height": 10 },
|
||||
"ヅ": { "x": 136, "y": 20, "width": 8, "height": 10 },
|
||||
"テ": { "x": 144, "y": 20, "width": 6, "height": 10 },
|
||||
"デ": { "x": 152, "y": 20, "width": 8, "height": 10 },
|
||||
"ト": { "x": 160, "y": 20, "width": 4, "height": 10 },
|
||||
"ド": { "x": 168, "y": 20, "width": 7, "height": 10 },
|
||||
"ナ": { "x": 176, "y": 20, "width": 6, "height": 10 },
|
||||
"ニ": { "x": 184, "y": 20, "width": 6, "height": 10 },
|
||||
"ヌ": { "x": 192, "y": 20, "width": 5, "height": 10 },
|
||||
"ネ": { "x": 200, "y": 20, "width": 6, "height": 10 },
|
||||
"ノ": { "x": 208, "y": 20, "width": 5, "height": 10 },
|
||||
"ハ": { "x": 216, "y": 20, "width": 6, "height": 10 },
|
||||
"バ": { "x": 96, "y": 30, "width": 8, "height": 10 },
|
||||
"パ": { "x": 104, "y": 30, "width": 8, "height": 10 },
|
||||
"ヒ": { "x": 112, "y": 30, "width": 5, "height": 10 },
|
||||
"ビ": { "x": 120, "y": 30, "width": 7, "height": 10 },
|
||||
"ピ": { "x": 128, "y": 30, "width": 8, "height": 10 },
|
||||
"フ": { "x": 136, "y": 30, "width": 6, "height": 10 },
|
||||
"ブ": { "x": 144, "y": 30, "width": 8, "height": 10 },
|
||||
"プ": { "x": 152, "y": 30, "width": 8, "height": 10 },
|
||||
"ヘ": { "x": 160, "y": 30, "width": 7, "height": 10 },
|
||||
"ベ": { "x": 168, "y": 30, "width": 8, "height": 10 },
|
||||
"ペ": { "x": 176, "y": 30, "width": 8, "height": 10 },
|
||||
"ホ": { "x": 184, "y": 30, "width": 8, "height": 10 },
|
||||
"ボ": { "x": 192, "y": 30, "width": 8, "height": 10 },
|
||||
"ポ": { "x": 200, "y": 30, "width": 8, "height": 10 },
|
||||
"マ": { "x": 208, "y": 30, "width": 7, "height": 10 },
|
||||
"ミ": { "x": 216, "y": 30, "width": 4, "height": 10 },
|
||||
"ム": { "x": 96, "y": 40, "width": 7, "height": 10 },
|
||||
"メ": { "x": 104, "y": 40, "width": 5, "height": 10 },
|
||||
"モ": { "x": 112, "y": 40, "width": 6, "height": 10 },
|
||||
"ャ": { "x": 120, "y": 40, "width": 7, "height": 10 },
|
||||
"ヤ": { "x": 128, "y": 40, "width": 7, "height": 10 },
|
||||
"ュ": { "x": 136, "y": 40, "width": 6, "height": 10 },
|
||||
"ユ": { "x": 144, "y": 40, "width": 7, "height": 10 },
|
||||
"ョ": { "x": 152, "y": 40, "width": 5, "height": 10 },
|
||||
"ヨ": { "x": 160, "y": 40, "width": 7, "height": 10 },
|
||||
"ラ": { "x": 168, "y": 40, "width": 6, "height": 10 },
|
||||
"リ": { "x": 176, "y": 40, "width": 5, "height": 10 },
|
||||
"ル": { "x": 184, "y": 40, "width": 8, "height": 10 },
|
||||
"レ": { "x": 192, "y": 40, "width": 6, "height": 10 },
|
||||
"ロ": { "x": 200, "y": 40, "width": 5, "height": 10 },
|
||||
"ヮ": { "x": 208, "y": 40, "width": 6, "height": 10 },
|
||||
"ワ": { "x": 216, "y": 40, "width": 6, "height": 10 },
|
||||
"ヰ": { "x": 96, "y": 50, "width": 7, "height": 10 },
|
||||
"ヱ": { "x": 104, "y": 50, "width": 6, "height": 10 },
|
||||
"ヲ": { "x": 112, "y": 50, "width": 6, "height": 10 },
|
||||
"ン": { "x": 120, "y": 50, "width": 7, "height": 10 },
|
||||
"ヴ": { "x": 128, "y": 50, "width": 8, "height": 10 },
|
||||
"ヵ": { "x": 136, "y": 50, "width": 6, "height": 10 },
|
||||
"ヶ": { "x": 144, "y": 50, "width": 6, "height": 10 },
|
||||
"ヷ": { "x": 152, "y": 50, "width": 8, "height": 10 },
|
||||
"ヸ": { "x": 160, "y": 50, "width": 8, "height": 10 },
|
||||
"ヹ": { "x": 168, "y": 50, "width": 8, "height": 10 },
|
||||
"ヺ": { "x": 176, "y": 50, "width": 8, "height": 10 },
|
||||
"・": { "x": 184, "y": 50, "width": 4, "height": 10 },
|
||||
"ー": { "x": 192, "y": 50, "width": 7, "height": 10 },
|
||||
"ヽ": { "x": 200, "y": 50, "width": 5, "height": 10 },
|
||||
"ヾ": { "x": 208, "y": 50, "width": 7, "height": 10 },
|
||||
"ヿ": { "x": 216, "y": 50, "width": 6, "height": 10 },
|
||||
|
||||
"\u3000": { "x": 96, "y": 120, "width": 4, "height": 10 },
|
||||
"、": { "x": 104, "y": 120, "width": 3, "height": 10 },
|
||||
"。": { "x": 112, "y": 120, "width": 4, "height": 10 },
|
||||
"〃": { "x": 120, "y": 120, "width": 5, "height": 10 },
|
||||
"〈": { "x": 128, "y": 120, "width": 4, "height": 10 },
|
||||
"〉": { "x": 136, "y": 120, "width": 4, "height": 10 },
|
||||
"《": { "x": 144, "y": 120, "width": 6, "height": 10 },
|
||||
"》": { "x": 152, "y": 120, "width": 6, "height": 10 },
|
||||
"「": { "x": 160, "y": 120, "width": 3, "height": 10 },
|
||||
"」": { "x": 168, "y": 120, "width": 3, "height": 10 },
|
||||
"【": { "x": 176, "y": 120, "width": 4, "height": 10 },
|
||||
"】": { "x": 184, "y": 120, "width": 4, "height": 10 },
|
||||
"〔": { "x": 192, "y": 120, "width": 4, "height": 10 },
|
||||
"〕": { "x": 200, "y": 120, "width": 4, "height": 10 },
|
||||
|
||||
"○": { "x": 224, "y": 0, "width": 10, "height": 10, "icon": true },
|
||||
"✕": { "x": 234, "y": 0, "width": 10, "height": 10, "icon": true },
|
||||
"△": { "x": 244, "y": 0, "width": 10, "height": 10, "icon": true },
|
||||
"□": { "x": 224, "y": 10, "width": 10, "height": 10, "icon": true },
|
||||
"◁": { "x": 234, "y": 10, "width": 7, "height": 10, "icon": true },
|
||||
"▷": { "x": 244, "y": 10, "width": 7, "height": 10, "icon": true },
|
||||
"▭": { "x": 224, "y": 20, "width": 9, "height": 10, "icon": true },
|
||||
"🔒": { "x": 234, "y": 20, "width": 8, "height": 10, "icon": true },
|
||||
"🔓": { "x": 244, "y": 20, "width": 10, "height": 10, "icon": true },
|
||||
"🖸": { "x": 224, "y": 30, "width": 10, "height": 10, "icon": true },
|
||||
"🖴": { "x": 234, "y": 30, "width": 10, "height": 10, "icon": true },
|
||||
"🖧": { "x": 244, "y": 30, "width": 10, "height": 10, "icon": true },
|
||||
"🗀": { "x": 224, "y": 40, "width": 10, "height": 10, "icon": true },
|
||||
"🖿": { "x": 234, "y": 40, "width": 10, "height": 10, "icon": true },
|
||||
"🗎": { "x": 244, "y": 40, "width": 10, "height": 10, "icon": true }
|
||||
}
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
{
|
||||
"$schema": "../../schema/metrics.json",
|
||||
|
||||
"spaceWidth": 4,
|
||||
"tabWidth": 32,
|
||||
"lineHeight": 10,
|
||||
|
||||
"characterSizes": {
|
||||
" ": { "x": 0, "y": 0, "width": 4, "height": 9 },
|
||||
"!": { "x": 6, "y": 0, "width": 2, "height": 9 },
|
||||
"\"": { "x": 12, "y": 0, "width": 4, "height": 9 },
|
||||
"#": { "x": 18, "y": 0, "width": 6, "height": 9 },
|
||||
"$": { "x": 24, "y": 0, "width": 6, "height": 9 },
|
||||
"%": { "x": 30, "y": 0, "width": 6, "height": 9 },
|
||||
"&": { "x": 36, "y": 0, "width": 6, "height": 9 },
|
||||
"'": { "x": 42, "y": 0, "width": 2, "height": 9 },
|
||||
"(": { "x": 48, "y": 0, "width": 3, "height": 9 },
|
||||
")": { "x": 54, "y": 0, "width": 3, "height": 9 },
|
||||
"*": { "x": 60, "y": 0, "width": 4, "height": 9 },
|
||||
"+": { "x": 66, "y": 0, "width": 6, "height": 9 },
|
||||
",": { "x": 72, "y": 0, "width": 3, "height": 9 },
|
||||
"-": { "x": 78, "y": 0, "width": 6, "height": 9 },
|
||||
".": { "x": 84, "y": 0, "width": 2, "height": 9 },
|
||||
"/": { "x": 90, "y": 0, "width": 6, "height": 9 },
|
||||
"0": { "x": 0, "y": 9, "width": 6, "height": 9 },
|
||||
"1": { "x": 6, "y": 9, "width": 6, "height": 9 },
|
||||
"2": { "x": 12, "y": 9, "width": 6, "height": 9 },
|
||||
"3": { "x": 18, "y": 9, "width": 6, "height": 9 },
|
||||
"4": { "x": 24, "y": 9, "width": 6, "height": 9 },
|
||||
"5": { "x": 30, "y": 9, "width": 6, "height": 9 },
|
||||
"6": { "x": 36, "y": 9, "width": 6, "height": 9 },
|
||||
"7": { "x": 42, "y": 9, "width": 6, "height": 9 },
|
||||
"8": { "x": 48, "y": 9, "width": 6, "height": 9 },
|
||||
"9": { "x": 54, "y": 9, "width": 6, "height": 9 },
|
||||
":": { "x": 60, "y": 9, "width": 2, "height": 9 },
|
||||
";": { "x": 66, "y": 9, "width": 3, "height": 9 },
|
||||
"<": { "x": 72, "y": 9, "width": 6, "height": 9 },
|
||||
"=": { "x": 78, "y": 9, "width": 6, "height": 9 },
|
||||
">": { "x": 84, "y": 9, "width": 6, "height": 9 },
|
||||
"?": { "x": 90, "y": 9, "width": 6, "height": 9 },
|
||||
"@": { "x": 0, "y": 18, "width": 6, "height": 9 },
|
||||
"A": { "x": 6, "y": 18, "width": 6, "height": 9 },
|
||||
"B": { "x": 12, "y": 18, "width": 6, "height": 9 },
|
||||
"C": { "x": 18, "y": 18, "width": 6, "height": 9 },
|
||||
"D": { "x": 24, "y": 18, "width": 6, "height": 9 },
|
||||
"E": { "x": 30, "y": 18, "width": 6, "height": 9 },
|
||||
"F": { "x": 36, "y": 18, "width": 6, "height": 9 },
|
||||
"G": { "x": 42, "y": 18, "width": 6, "height": 9 },
|
||||
"H": { "x": 48, "y": 18, "width": 6, "height": 9 },
|
||||
"I": { "x": 54, "y": 18, "width": 4, "height": 9 },
|
||||
"J": { "x": 60, "y": 18, "width": 5, "height": 9 },
|
||||
"K": { "x": 66, "y": 18, "width": 6, "height": 9 },
|
||||
"L": { "x": 72, "y": 18, "width": 6, "height": 9 },
|
||||
"M": { "x": 78, "y": 18, "width": 6, "height": 9 },
|
||||
"N": { "x": 84, "y": 18, "width": 6, "height": 9 },
|
||||
"O": { "x": 90, "y": 18, "width": 6, "height": 9 },
|
||||
"P": { "x": 0, "y": 27, "width": 6, "height": 9 },
|
||||
"Q": { "x": 6, "y": 27, "width": 6, "height": 9 },
|
||||
"R": { "x": 12, "y": 27, "width": 6, "height": 9 },
|
||||
"S": { "x": 18, "y": 27, "width": 6, "height": 9 },
|
||||
"T": { "x": 24, "y": 27, "width": 6, "height": 9 },
|
||||
"U": { "x": 30, "y": 27, "width": 6, "height": 9 },
|
||||
"V": { "x": 36, "y": 27, "width": 6, "height": 9 },
|
||||
"W": { "x": 42, "y": 27, "width": 6, "height": 9 },
|
||||
"X": { "x": 48, "y": 27, "width": 6, "height": 9 },
|
||||
"Y": { "x": 54, "y": 27, "width": 6, "height": 9 },
|
||||
"Z": { "x": 60, "y": 27, "width": 6, "height": 9 },
|
||||
"[": { "x": 66, "y": 27, "width": 3, "height": 9 },
|
||||
"\\": { "x": 72, "y": 27, "width": 6, "height": 9 },
|
||||
"]": { "x": 78, "y": 27, "width": 3, "height": 9 },
|
||||
"^": { "x": 84, "y": 27, "width": 4, "height": 9 },
|
||||
"_": { "x": 90, "y": 27, "width": 6, "height": 9 },
|
||||
"`": { "x": 0, "y": 36, "width": 3, "height": 9 },
|
||||
"a": { "x": 6, "y": 36, "width": 6, "height": 9 },
|
||||
"b": { "x": 12, "y": 36, "width": 6, "height": 9 },
|
||||
"c": { "x": 18, "y": 36, "width": 6, "height": 9 },
|
||||
"d": { "x": 24, "y": 36, "width": 6, "height": 9 },
|
||||
"e": { "x": 30, "y": 36, "width": 6, "height": 9 },
|
||||
"f": { "x": 36, "y": 36, "width": 5, "height": 9 },
|
||||
"g": { "x": 42, "y": 36, "width": 6, "height": 9 },
|
||||
"h": { "x": 48, "y": 36, "width": 5, "height": 9 },
|
||||
"i": { "x": 54, "y": 36, "width": 2, "height": 9 },
|
||||
"j": { "x": 60, "y": 36, "width": 4, "height": 9 },
|
||||
"k": { "x": 66, "y": 36, "width": 5, "height": 9 },
|
||||
"l": { "x": 72, "y": 36, "width": 2, "height": 9 },
|
||||
"m": { "x": 78, "y": 36, "width": 6, "height": 9 },
|
||||
"n": { "x": 84, "y": 36, "width": 5, "height": 9 },
|
||||
"o": { "x": 90, "y": 36, "width": 6, "height": 9 },
|
||||
"p": { "x": 0, "y": 45, "width": 6, "height": 9 },
|
||||
"q": { "x": 6, "y": 45, "width": 6, "height": 9 },
|
||||
"r": { "x": 12, "y": 45, "width": 6, "height": 9 },
|
||||
"s": { "x": 18, "y": 45, "width": 6, "height": 9 },
|
||||
"t": { "x": 24, "y": 45, "width": 5, "height": 9 },
|
||||
"u": { "x": 30, "y": 45, "width": 5, "height": 9 },
|
||||
"v": { "x": 36, "y": 45, "width": 6, "height": 9 },
|
||||
"w": { "x": 42, "y": 45, "width": 6, "height": 9 },
|
||||
"x": { "x": 48, "y": 45, "width": 6, "height": 9 },
|
||||
"y": { "x": 54, "y": 45, "width": 6, "height": 9 },
|
||||
"z": { "x": 60, "y": 45, "width": 5, "height": 9 },
|
||||
"{": { "x": 66, "y": 45, "width": 4, "height": 9 },
|
||||
"|": { "x": 72, "y": 45, "width": 2, "height": 9 },
|
||||
"}": { "x": 78, "y": 45, "width": 4, "height": 9 },
|
||||
"~": { "x": 84, "y": 45, "width": 6, "height": 9 },
|
||||
|
||||
"\u007f": { "x": 90, "y": 45, "width": 6, "height": 9 },
|
||||
|
||||
"\u0080": { "x": 0, "y": 54, "width": 6, "height": 9 },
|
||||
"\u0081": { "x": 6, "y": 54, "width": 6, "height": 9 },
|
||||
"\u0082": { "x": 12, "y": 54, "width": 4, "height": 9 },
|
||||
"\u0083": { "x": 18, "y": 54, "width": 4, "height": 9 },
|
||||
"\u0084": { "x": 24, "y": 54, "width": 6, "height": 9 },
|
||||
"\u0085": { "x": 30, "y": 54, "width": 6, "height": 9 },
|
||||
"\u0086": { "x": 36, "y": 54, "width": 6, "height": 9 },
|
||||
"\u0087": { "x": 42, "y": 54, "width": 6, "height": 9 },
|
||||
|
||||
"\u0090": { "x": 0, "y": 63, "width": 7, "height": 9, "icon": true },
|
||||
"\u0091": { "x": 12, "y": 63, "width": 7, "height": 9, "icon": true },
|
||||
"\u0092": { "x": 24, "y": 63, "width": 9, "height": 9, "icon": true },
|
||||
"\u0093": { "x": 36, "y": 63, "width": 8, "height": 10, "icon": true },
|
||||
"\u0094": { "x": 48, "y": 63, "width": 11, "height": 10, "icon": true },
|
||||
"\u0095": { "x": 60, "y": 63, "width": 12, "height": 10, "icon": true },
|
||||
"\u0096": { "x": 72, "y": 63, "width": 14, "height": 9, "icon": true },
|
||||
|
||||
"\u00a0": { "x": 0, "y": 73, "width": 10, "height": 10, "icon": true },
|
||||
"\u00a1": { "x": 12, "y": 73, "width": 10, "height": 10, "icon": true },
|
||||
"\u00a2": { "x": 24, "y": 73, "width": 10, "height": 10, "icon": true },
|
||||
"\u00a3": { "x": 36, "y": 73, "width": 10, "height": 9, "icon": true },
|
||||
"\u00a4": { "x": 48, "y": 73, "width": 10, "height": 9, "icon": true },
|
||||
"\u00a5": { "x": 60, "y": 73, "width": 10, "height": 10, "icon": true }
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 6.3 KiB |
@ -19,6 +19,11 @@
|
||||
"source": "${PROJECT_BINARY_DIR}/launcher803fd000.psexe"
|
||||
},
|
||||
|
||||
{
|
||||
"type": "strings",
|
||||
"name": "assets/lang/en.lang",
|
||||
"source": "${PROJECT_SOURCE_DIR}/assets/lang/en.json"
|
||||
},
|
||||
{
|
||||
"type": "tim",
|
||||
"name": "assets/textures/background.tim",
|
||||
@ -32,21 +37,21 @@
|
||||
"name": "assets/textures/font.tim",
|
||||
"source": "${PROJECT_SOURCE_DIR}/assets/textures/font.png",
|
||||
"quantize": 16,
|
||||
"imagePos": { "x": 984, "y": 0 },
|
||||
"clutPos": { "x": 1008, "y": 1 }
|
||||
"imagePos": { "x": 960, "y": 126 },
|
||||
"clutPos": { "x": 1008, "y": 1 }
|
||||
},
|
||||
{
|
||||
"type": "tim",
|
||||
"name": "assets/textures/splash.tim",
|
||||
"source": "${PROJECT_SOURCE_DIR}/assets/textures/splash.png",
|
||||
"quantize": 16,
|
||||
"imagePos": { "x": 960, "y": 96 },
|
||||
"clutPos": { "x": 1008, "y": 2 }
|
||||
"imagePos": { "x": 984, "y": 0 },
|
||||
"clutPos": { "x": 1008, "y": 2 }
|
||||
},
|
||||
{
|
||||
"type": "metrics",
|
||||
"name": "assets/textures/font.metrics",
|
||||
"source": "${PROJECT_SOURCE_DIR}/assets/textures/font.metrics.json"
|
||||
"source": "${PROJECT_SOURCE_DIR}/assets/textures/font.json"
|
||||
},
|
||||
{
|
||||
"type": "binary",
|
||||
@ -96,21 +101,17 @@
|
||||
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/screenshot.vag",
|
||||
"compression": "none"
|
||||
},
|
||||
{
|
||||
"type": "palette",
|
||||
"name": "assets/app.palette",
|
||||
"source": "${PROJECT_SOURCE_DIR}/assets/app.palette.json"
|
||||
},
|
||||
{
|
||||
"type": "strings",
|
||||
"name": "assets/app.strings",
|
||||
"source": "${PROJECT_SOURCE_DIR}/assets/app.strings.json"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "assets/about.txt",
|
||||
"source": "${PROJECT_BINARY_DIR}/about.txt"
|
||||
},
|
||||
{
|
||||
"type": "palette",
|
||||
"name": "assets/palette.dat",
|
||||
"source": "${PROJECT_SOURCE_DIR}/assets/palette.json",
|
||||
"compression": "none"
|
||||
},
|
||||
|
||||
{
|
||||
"type": "binary",
|
||||
|
@ -19,6 +19,11 @@
|
||||
"source": "${PROJECT_BINARY_DIR}/launcher803fd000.psexe"
|
||||
},
|
||||
|
||||
{
|
||||
"type": "strings",
|
||||
"name": "assets/lang/en.lang",
|
||||
"source": "${PROJECT_SOURCE_DIR}/assets/lang/en.json"
|
||||
},
|
||||
{
|
||||
"type": "tim",
|
||||
"name": "assets/textures/background.tim",
|
||||
@ -32,21 +37,21 @@
|
||||
"name": "assets/textures/font.tim",
|
||||
"source": "${PROJECT_SOURCE_DIR}/assets/textures/font.png",
|
||||
"quantize": 16,
|
||||
"imagePos": { "x": 984, "y": 0 },
|
||||
"clutPos": { "x": 1008, "y": 1 }
|
||||
"imagePos": { "x": 960, "y": 126 },
|
||||
"clutPos": { "x": 1008, "y": 1 }
|
||||
},
|
||||
{
|
||||
"type": "tim",
|
||||
"name": "assets/textures/splash.tim",
|
||||
"source": "${PROJECT_SOURCE_DIR}/assets/textures/splash.png",
|
||||
"quantize": 16,
|
||||
"imagePos": { "x": 960, "y": 96 },
|
||||
"clutPos": { "x": 1008, "y": 2 }
|
||||
"imagePos": { "x": 984, "y": 0 },
|
||||
"clutPos": { "x": 1008, "y": 2 }
|
||||
},
|
||||
{
|
||||
"type": "metrics",
|
||||
"name": "assets/textures/font.metrics",
|
||||
"source": "${PROJECT_SOURCE_DIR}/assets/textures/font.metrics.json"
|
||||
"source": "${PROJECT_SOURCE_DIR}/assets/textures/font.json"
|
||||
},
|
||||
{
|
||||
"type": "binary",
|
||||
@ -90,25 +95,16 @@
|
||||
"source": "${PROJECT_SOURCE_DIR}/assets/sounds/screenshot.vag",
|
||||
"compression": "none"
|
||||
},
|
||||
{
|
||||
"type": "palette",
|
||||
"name": "assets/app.palette",
|
||||
"source": "${PROJECT_SOURCE_DIR}/assets/app.palette.json"
|
||||
},
|
||||
{
|
||||
"type": "strings",
|
||||
"name": "assets/app.strings",
|
||||
"source": "${PROJECT_SOURCE_DIR}/assets/app.strings.json"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "assets/about.txt",
|
||||
"source": "${PROJECT_BINARY_DIR}/about.txt"
|
||||
},
|
||||
{
|
||||
"type": "db",
|
||||
"name": "data/games.db",
|
||||
"source": "${PROJECT_SOURCE_DIR}/data/games.json"
|
||||
"type": "palette",
|
||||
"name": "assets/palette.dat",
|
||||
"source": "${PROJECT_SOURCE_DIR}/assets/palette.json",
|
||||
"compression": "none"
|
||||
},
|
||||
|
||||
{
|
||||
@ -116,6 +112,12 @@
|
||||
"name": "data/fpga.bit",
|
||||
"source": "${PROJECT_SOURCE_DIR}/data/fpga.bit"
|
||||
},
|
||||
{
|
||||
"type": "db",
|
||||
"name": "data/games.db",
|
||||
"source": "${PROJECT_SOURCE_DIR}/data/games.json"
|
||||
},
|
||||
|
||||
{
|
||||
"type": "binary",
|
||||
"name": "data/x76f041.db",
|
||||
|
@ -5,7 +5,13 @@
|
||||
"title": "Root",
|
||||
"type": "object",
|
||||
|
||||
"required": [ "spaceWidth", "tabWidth", "lineHeight", "characterSizes" ],
|
||||
"required": [
|
||||
"spaceWidth",
|
||||
"tabWidth",
|
||||
"lineHeight",
|
||||
"baselineOffset",
|
||||
"characterSizes"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"spaceWidth": {
|
||||
@ -20,13 +26,18 @@
|
||||
},
|
||||
"lineHeight": {
|
||||
"title": "Line height",
|
||||
"description": "Height of each line in pixels, including any padding. Note that characters whose height is lower than this value will be aligned to the top of the line, rather than the bottom.",
|
||||
"description": "Height of each line in pixels, including any padding. Note that characters whose height is lower than this value will be aligned to the top of the line by default.",
|
||||
"type": "integer"
|
||||
},
|
||||
"baselineOffset": {
|
||||
"title": "Baseline offset",
|
||||
"description": "Offset to add to the Y coordinate of each line. Can be negative.",
|
||||
"type": "integer"
|
||||
},
|
||||
|
||||
"characterSizes": {
|
||||
"title": "Character list",
|
||||
"description": "List of all glyphs in the texture. Each entry's key must be a single-character string containing a printable ASCII or extended (\\u0080-\\u00ff) character.",
|
||||
"description": "List of all glyphs in the texture. Each entry's key must be a single-character string containing a printable Unicode character.",
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": false,
|
||||
|
@ -62,6 +62,8 @@
|
||||
|
||||
"oneOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
|
||||
"properties": {
|
||||
"type": { "const": "empty" },
|
||||
|
||||
@ -75,11 +77,14 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"required": [ "source" ],
|
||||
"required": [ "source" ],
|
||||
"additionalProperties": false,
|
||||
|
||||
"properties": {
|
||||
"type": { "pattern": "^text|binary$" },
|
||||
"name": { "type": "string" },
|
||||
"type": { "pattern": "^text|binary$" },
|
||||
"name": { "type": "string" },
|
||||
"compression": {},
|
||||
"compressLevel": {},
|
||||
|
||||
"source": {
|
||||
"title": "Path to source file",
|
||||
@ -95,8 +100,10 @@
|
||||
"additionalProperties": false,
|
||||
|
||||
"properties": {
|
||||
"type": { "const": "tim" },
|
||||
"name": { "type": "string" },
|
||||
"type": { "const": "tim" },
|
||||
"name": { "type": "string" },
|
||||
"compression": {},
|
||||
"compressLevel": {},
|
||||
|
||||
"source": {
|
||||
"title": "Path to source file",
|
||||
@ -174,8 +181,10 @@
|
||||
"additionalProperties": false,
|
||||
|
||||
"properties": {
|
||||
"type": { "pattern": "^metrics|palette|strings|db$" },
|
||||
"name": { "type": "string" },
|
||||
"type": { "pattern": "^metrics|palette|strings|db$" },
|
||||
"name": { "type": "string" },
|
||||
"compression": {},
|
||||
"compressLevel": {},
|
||||
|
||||
"source": {
|
||||
"title": "Path to source file",
|
||||
@ -191,8 +200,10 @@
|
||||
"additionalProperties": false,
|
||||
|
||||
"properties": {
|
||||
"type": { "const": "metrics" },
|
||||
"name": { "type": "string" },
|
||||
"type": { "const": "metrics" },
|
||||
"name": { "type": "string" },
|
||||
"compression": {},
|
||||
"compressLevel": {},
|
||||
|
||||
"metrics": {
|
||||
"title": "Font metrics",
|
||||
@ -206,8 +217,10 @@
|
||||
"additionalProperties": false,
|
||||
|
||||
"properties": {
|
||||
"type": { "const": "palette" },
|
||||
"name": { "type": "string" },
|
||||
"type": { "const": "palette" },
|
||||
"name": { "type": "string" },
|
||||
"compression": {},
|
||||
"compressLevel": {},
|
||||
|
||||
"palette": {
|
||||
"title": "Color entries",
|
||||
@ -221,8 +234,10 @@
|
||||
"additionalProperties": false,
|
||||
|
||||
"properties": {
|
||||
"type": { "const": "strings" },
|
||||
"name": { "type": "string" },
|
||||
"type": { "const": "strings" },
|
||||
"name": { "type": "string" },
|
||||
"compression": {},
|
||||
"compressLevel": {},
|
||||
|
||||
"strings": {
|
||||
"title": "String table",
|
||||
@ -236,8 +251,10 @@
|
||||
"additionalProperties": false,
|
||||
|
||||
"properties": {
|
||||
"type": { "const": "db" },
|
||||
"name": { "type": "string" },
|
||||
"type": { "const": "db" },
|
||||
"name": { "type": "string" },
|
||||
"compression": {},
|
||||
"compressLevel": {},
|
||||
|
||||
"strings": {
|
||||
"title": "Game database",
|
||||
|
@ -30,28 +30,28 @@
|
||||
#define VERSION_STRING VERSION "-debug"
|
||||
#endif
|
||||
|
||||
enum Character : char {
|
||||
CH_UP_ARROW = '\x80',
|
||||
CH_DOWN_ARROW = '\x81',
|
||||
CH_LEFT_ARROW = '\x82',
|
||||
CH_RIGHT_ARROW = '\x83',
|
||||
CH_UP_ARROW_ALT = '\x84',
|
||||
CH_DOWN_ARROW_ALT = '\x85',
|
||||
CH_LEFT_ARROW_ALT = '\x86',
|
||||
CH_RIGHT_ARROW_ALT = '\x87',
|
||||
#define CH_UP_ARROW "\u25b4"
|
||||
#define CH_DOWN_ARROW "\u25be"
|
||||
#define CH_LEFT_ARROW "\u25c2"
|
||||
#define CH_RIGHT_ARROW "\u25b8"
|
||||
#define CH_UP_ARROW_ALT "\u2191"
|
||||
#define CH_DOWN_ARROW_ALT "\u2193"
|
||||
#define CH_LEFT_ARROW_ALT "\u2190"
|
||||
#define CH_RIGHT_ARROW_ALT "\u2192"
|
||||
#define CH_INVALID_CHAR "\ufffd"
|
||||
|
||||
CH_LEFT_BUTTON = '\x90',
|
||||
CH_RIGHT_BUTTON = '\x91',
|
||||
CH_START_BUTTON = '\x92',
|
||||
CH_CLOSED_LOCK = '\x93',
|
||||
CH_OPEN_LOCK = '\x94',
|
||||
CH_CHIP_ICON = '\x95',
|
||||
CH_CART_ICON = '\x96',
|
||||
|
||||
CH_CDROM_ICON = '\xa0',
|
||||
CH_HDD_ICON = '\xa1',
|
||||
CH_HOST_ICON = '\xa2',
|
||||
CH_DIR_ICON = '\xa3',
|
||||
CH_PARENT_DIR_ICON = '\xa4',
|
||||
CH_FILE_ICON = '\xa5'
|
||||
};
|
||||
#define CH_LEFT_BUTTON "\u25c1"
|
||||
#define CH_RIGHT_BUTTON "\u25b7"
|
||||
#define CH_START_BUTTON "\u25ad"
|
||||
#define CH_CLOSED_LOCK "\U0001f512"
|
||||
#define CH_OPEN_LOCK "\U0001f513"
|
||||
#define CH_CIRCLE_BUTTON "\u25cb"
|
||||
#define CH_X_BUTTON "\u2715"
|
||||
#define CH_TRIANGLE_BUTTON "\u25b3"
|
||||
#define CH_SQUARE_BUTTON "\u25a1"
|
||||
#define CH_CDROM_ICON "\U0001f5b8"
|
||||
#define CH_HDD_ICON "\U0001f5b4"
|
||||
#define CH_HOST_ICON "\U0001f5a7"
|
||||
#define CH_DIR_ICON "\U0001f5c0"
|
||||
#define CH_PARENT_DIR_ICON "\U0001f5bf"
|
||||
#define CH_FILE_ICON "\U0001f5ce"
|
||||
|
@ -244,20 +244,17 @@ const char *StringTable::get(util::Hash id) const {
|
||||
if (!ptr)
|
||||
return _ERROR_STRING;
|
||||
|
||||
auto blob = reinterpret_cast<const char *>(ptr);
|
||||
auto table = reinterpret_cast<const StringTableEntry *>(ptr);
|
||||
auto blob = as<const char>();
|
||||
auto table = as<const StringTableEntry>();
|
||||
auto index = id % STRING_TABLE_BUCKET_COUNT;
|
||||
|
||||
auto entry = &table[id % TABLE_BUCKET_COUNT];
|
||||
|
||||
if (entry->hash == id)
|
||||
return &blob[entry->offset];
|
||||
|
||||
while (entry->chained) {
|
||||
entry = &table[entry->chained];
|
||||
do {
|
||||
auto entry = &table[index];
|
||||
index = entry->chained;
|
||||
|
||||
if (entry->hash == id)
|
||||
return &blob[entry->offset];
|
||||
}
|
||||
} while (index);
|
||||
|
||||
return _ERROR_STRING;
|
||||
}
|
||||
|
@ -159,7 +159,7 @@ public:
|
||||
|
||||
/* String table parser */
|
||||
|
||||
static constexpr int TABLE_BUCKET_COUNT = 256;
|
||||
static constexpr size_t STRING_TABLE_BUCKET_COUNT = 256;
|
||||
|
||||
struct StringTableEntry {
|
||||
public:
|
||||
|
@ -15,40 +15,72 @@
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "common/util/string.hpp"
|
||||
#include "common/gpu.hpp"
|
||||
#include "common/gpufont.hpp"
|
||||
#include "ps1/gpucmd.h"
|
||||
|
||||
namespace gpu {
|
||||
|
||||
/* Font metrics class */
|
||||
|
||||
CharacterSize FontMetrics::get(util::UTF8CodePoint id) const {
|
||||
if (!ptr)
|
||||
return 0;
|
||||
|
||||
auto table = reinterpret_cast<const FontMetricsEntry *>(getHeader() + 1);
|
||||
auto index = id % METRICS_BUCKET_COUNT;
|
||||
|
||||
do {
|
||||
auto entry = &table[index];
|
||||
index = entry->getChained();
|
||||
|
||||
if (entry->getCodePoint() == id)
|
||||
return entry->size;
|
||||
} while (index);
|
||||
|
||||
return (id == FONT_INVALID_CHAR) ? 0 : get(FONT_INVALID_CHAR);
|
||||
}
|
||||
|
||||
/* Font class */
|
||||
|
||||
void Font::draw(
|
||||
Context &ctx, const char *str, const Rect &rect, const Rect &clipRect,
|
||||
Color color, bool wordWrap
|
||||
) const {
|
||||
// This is required for non-ASCII characters to work properly.
|
||||
auto _str = reinterpret_cast<const uint8_t *>(str);
|
||||
|
||||
if (!str)
|
||||
if (!str || !metrics.ptr)
|
||||
return;
|
||||
|
||||
ctx.setTexturePage(image.texpage);
|
||||
|
||||
int x = rect.x1, y = rect.y1;
|
||||
auto header = metrics.getHeader();
|
||||
|
||||
for (uint8_t ch = *_str; ch; ch = *(++_str)) {
|
||||
int x = rect.x1;
|
||||
int clipX1 = clipRect.x1;
|
||||
int clipX2 = clipRect.x2;
|
||||
|
||||
int y = rect.y1 + header->baselineOffset;
|
||||
int clipY1 = clipRect.y1 + header->baselineOffset;
|
||||
int clipY2 = clipRect.y2 + header->baselineOffset;
|
||||
int rectY2 = rect.y2 + header->baselineOffset - header->lineHeight;
|
||||
|
||||
for (;;) {
|
||||
auto ch = util::parseUTF8Character(str);
|
||||
bool wrap = wordWrap;
|
||||
str += ch.length;
|
||||
|
||||
switch (ch.codePoint) {
|
||||
case 0:
|
||||
return;
|
||||
|
||||
switch (ch) {
|
||||
case '\t':
|
||||
x += metrics.tabWidth;
|
||||
x -= x % metrics.tabWidth;
|
||||
x += header->tabWidth;
|
||||
x -= x % header->tabWidth;
|
||||
break;
|
||||
|
||||
case '\n':
|
||||
x = rect.x1;
|
||||
y += metrics.lineHeight;
|
||||
y += header->lineHeight;
|
||||
break;
|
||||
|
||||
case '\r':
|
||||
@ -56,26 +88,25 @@ void Font::draw(
|
||||
break;
|
||||
|
||||
case ' ':
|
||||
x += metrics.spaceWidth;
|
||||
x += header->spaceWidth;
|
||||
break;
|
||||
|
||||
default:
|
||||
uint32_t size = metrics.getCharacterSize(ch);
|
||||
auto size = metrics.get(ch.codePoint);
|
||||
|
||||
int u = size & 0xff; size >>= 8;
|
||||
int v = size & 0xff; size >>= 8;
|
||||
int w = size & 0x7f; size >>= 7;
|
||||
int h = size & 0x7f; size >>= 7;
|
||||
|
||||
if (y > clipRect.y2)
|
||||
if (y > clipY2)
|
||||
return;
|
||||
if (
|
||||
(x >= (clipRect.x1 - w)) && (x <= clipRect.x2) &&
|
||||
(y >= (clipRect.y1 - h))
|
||||
(x >= (clipX1 - w)) && (x <= clipX2) && (y >= (clipY1 - h))
|
||||
) {
|
||||
auto cmd = ctx.newPacket(4);
|
||||
|
||||
cmd[0] = color | gp0_rectangle(true, size, true);
|
||||
cmd[0] = color | gp0_rectangle(true, size & 1, true);
|
||||
cmd[1] = gp0_xy(x, y);
|
||||
cmd[2] = gp0_uv(u + image.u, v + image.v, image.palette);
|
||||
cmd[3] = gp0_xy(w, h);
|
||||
@ -88,16 +119,15 @@ void Font::draw(
|
||||
// Handle word wrapping by calculating the length of the next word and
|
||||
// checking if it can still fit in the current line.
|
||||
int boundaryX = rect.x2;
|
||||
|
||||
if (wrap)
|
||||
boundaryX -= getStringWidth(
|
||||
reinterpret_cast<const char *>(&_str[1]), true
|
||||
);
|
||||
boundaryX -= getStringWidth(str, true);
|
||||
|
||||
if (x > boundaryX) {
|
||||
x = rect.x1;
|
||||
y += metrics.lineHeight;
|
||||
y += header->lineHeight;
|
||||
}
|
||||
if (y > (rect.y2 - metrics.lineHeight))
|
||||
if (y > rectY2)
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -122,7 +152,9 @@ void Font::draw(
|
||||
draw(ctx, str, _rect, color, wordWrap);
|
||||
}
|
||||
|
||||
int Font::getCharacterWidth(char ch) const {
|
||||
int Font::getCharacterWidth(util::UTF8CodePoint ch) const {
|
||||
auto header = metrics.getHeader();
|
||||
|
||||
switch (ch) {
|
||||
case 0:
|
||||
case '\n':
|
||||
@ -130,36 +162,43 @@ int Font::getCharacterWidth(char ch) const {
|
||||
return 0;
|
||||
|
||||
case '\t':
|
||||
return metrics.tabWidth;
|
||||
return header->tabWidth;
|
||||
|
||||
case ' ':
|
||||
return metrics.spaceWidth;
|
||||
return header->spaceWidth;
|
||||
|
||||
default:
|
||||
return (metrics.getCharacterSize(ch) >> 16) & 0x7f;
|
||||
auto size = metrics.get(ch);
|
||||
|
||||
return (size >> 16) & 0x7f;
|
||||
}
|
||||
}
|
||||
|
||||
void Font::getStringBounds(
|
||||
const char *str, Rect &rect, bool wordWrap, bool breakOnSpace
|
||||
) const {
|
||||
auto _str = reinterpret_cast<const uint8_t *>(str);
|
||||
|
||||
if (!str)
|
||||
if (!str || !metrics.ptr)
|
||||
return;
|
||||
|
||||
auto header = metrics.getHeader();
|
||||
|
||||
int x = rect.x1, maxX = rect.x1, y = rect.y1;
|
||||
|
||||
for (uint8_t ch = *_str; ch; ch = *(++_str)) {
|
||||
for (;;) {
|
||||
auto ch = util::parseUTF8Character(str);
|
||||
bool wrap = wordWrap;
|
||||
str += ch.length;
|
||||
|
||||
switch (ch.codePoint) {
|
||||
case 0:
|
||||
goto _break;
|
||||
|
||||
switch (ch) {
|
||||
case '\t':
|
||||
if (breakOnSpace)
|
||||
goto _break;
|
||||
|
||||
x += metrics.tabWidth;
|
||||
x -= x % metrics.tabWidth;
|
||||
x += header->tabWidth;
|
||||
x -= x % header->tabWidth;
|
||||
break;
|
||||
|
||||
case '\n':
|
||||
@ -169,7 +208,7 @@ void Font::getStringBounds(
|
||||
maxX = x;
|
||||
|
||||
x = rect.x1;
|
||||
y += metrics.lineHeight;
|
||||
y += header->lineHeight;
|
||||
break;
|
||||
|
||||
case '\r':
|
||||
@ -185,51 +224,59 @@ void Font::getStringBounds(
|
||||
if (breakOnSpace)
|
||||
goto _break;
|
||||
|
||||
x += metrics.spaceWidth;
|
||||
x += header->spaceWidth;
|
||||
break;
|
||||
|
||||
default:
|
||||
x += (metrics.getCharacterSize(ch) >> 16) & 0x7f;
|
||||
auto size = metrics.get(ch.codePoint);
|
||||
|
||||
x += (size >> 16) & 0x7f;
|
||||
wrap = false;
|
||||
}
|
||||
|
||||
int boundaryX = rect.x2;
|
||||
|
||||
if (wrap)
|
||||
boundaryX -= getStringWidth(
|
||||
reinterpret_cast<const char *>(&_str[1]), true
|
||||
);
|
||||
boundaryX -= getStringWidth(str, true);
|
||||
|
||||
if (x > boundaryX) {
|
||||
if (x > maxX)
|
||||
maxX = x;
|
||||
|
||||
x = rect.x1;
|
||||
y += metrics.lineHeight;
|
||||
y += header->lineHeight;
|
||||
}
|
||||
if (y > (rect.y2 - metrics.lineHeight))
|
||||
if (y > (rect.y2 - header->lineHeight))
|
||||
goto _break;
|
||||
}
|
||||
|
||||
_break:
|
||||
rect.x2 = maxX;
|
||||
rect.y2 = y + metrics.lineHeight;
|
||||
rect.y2 = y + header->lineHeight;
|
||||
}
|
||||
|
||||
int Font::getStringWidth(const char *str, bool breakOnSpace) const {
|
||||
auto _str = reinterpret_cast<const uint8_t *>(str);
|
||||
if (!str)
|
||||
if (!str || !metrics.ptr)
|
||||
return 0;
|
||||
|
||||
auto header = metrics.getHeader();
|
||||
|
||||
int width = 0, maxWidth = 0;
|
||||
|
||||
for (uint8_t ch = *_str; ch; ch = *(++_str)) {
|
||||
switch (ch) {
|
||||
for (;;) {
|
||||
auto ch = util::parseUTF8Character(str);
|
||||
str += ch.length;
|
||||
|
||||
switch (ch.codePoint) {
|
||||
case 0:
|
||||
goto _break;
|
||||
|
||||
case '\t':
|
||||
if (breakOnSpace)
|
||||
goto _break;
|
||||
|
||||
width += metrics.tabWidth;
|
||||
width -= width % metrics.tabWidth;
|
||||
width += header->tabWidth;
|
||||
width -= width % header->tabWidth;
|
||||
break;
|
||||
|
||||
case '\n':
|
||||
@ -246,11 +293,11 @@ int Font::getStringWidth(const char *str, bool breakOnSpace) const {
|
||||
if (breakOnSpace)
|
||||
goto _break;
|
||||
|
||||
width += metrics.spaceWidth;
|
||||
width += header->spaceWidth;
|
||||
break;
|
||||
|
||||
default:
|
||||
width += (metrics.getCharacterSize(ch) >> 16) & 0x7f;
|
||||
width += (metrics.get(ch.codePoint) >> 16) & 0x7f;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,34 +16,75 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "common/gpufont.hpp"
|
||||
#include "common/util/string.hpp"
|
||||
#include "common/util/templates.hpp"
|
||||
#include "common/gpu.hpp"
|
||||
|
||||
namespace gpu {
|
||||
|
||||
/* Font class */
|
||||
/* Font metrics class */
|
||||
|
||||
static constexpr char FONT_INVALID_CHAR = 0x7f;
|
||||
static constexpr size_t METRICS_BUCKET_COUNT = 256;
|
||||
static constexpr size_t METRICS_CODE_POINT_BITS = 21;
|
||||
|
||||
class FontMetrics {
|
||||
static constexpr util::UTF8CodePoint FONT_INVALID_CHAR = 0xfffd;
|
||||
|
||||
using CharacterSize = uint32_t;
|
||||
|
||||
struct FontMetricsHeader {
|
||||
public:
|
||||
uint8_t spaceWidth, tabWidth, lineHeight, _reserved;
|
||||
uint32_t characterSizes[256];
|
||||
uint8_t spaceWidth, tabWidth, lineHeight;
|
||||
int8_t baselineOffset;
|
||||
};
|
||||
|
||||
inline uint32_t getCharacterSize(uint8_t ch) const {
|
||||
uint32_t sizes = characterSizes[ch];
|
||||
if (!sizes)
|
||||
return characterSizes[int(FONT_INVALID_CHAR)];
|
||||
struct FontMetricsEntry {
|
||||
public:
|
||||
uint32_t codePoint;
|
||||
CharacterSize size;
|
||||
|
||||
return sizes;
|
||||
inline util::UTF8CodePoint getCodePoint(void) const {
|
||||
return codePoint & ((1 << METRICS_CODE_POINT_BITS) - 1);
|
||||
}
|
||||
inline uint32_t getChained(void) const {
|
||||
return codePoint >> METRICS_CODE_POINT_BITS;
|
||||
}
|
||||
};
|
||||
|
||||
class FontMetrics : public util::Data {
|
||||
public:
|
||||
inline const FontMetricsHeader *getHeader(void) const {
|
||||
return as<const FontMetricsHeader>();
|
||||
}
|
||||
inline CharacterSize operator[](util::UTF8CodePoint id) const {
|
||||
return get(id);
|
||||
}
|
||||
|
||||
CharacterSize get(util::UTF8CodePoint id) const;
|
||||
};
|
||||
|
||||
/* Font class */
|
||||
|
||||
class Font {
|
||||
public:
|
||||
Image image;
|
||||
FontMetrics metrics;
|
||||
|
||||
inline int getSpaceWidth(void) const {
|
||||
if (!metrics.ptr)
|
||||
return 0;
|
||||
|
||||
return metrics.getHeader()->spaceWidth;
|
||||
}
|
||||
inline int getLineHeight(void) const {
|
||||
if (!metrics.ptr)
|
||||
return 0;
|
||||
|
||||
return metrics.getHeader()->lineHeight;
|
||||
}
|
||||
|
||||
void draw(
|
||||
Context &ctx, const char *str, const Rect &rect, const Rect &clipRect,
|
||||
Color color = 0x808080, bool wordWrap = false
|
||||
@ -56,7 +97,7 @@ public:
|
||||
Context &ctx, const char *str, const RectWH &rect,
|
||||
Color color = 0x808080, bool wordWrap = false
|
||||
) const;
|
||||
int getCharacterWidth(char ch) const;
|
||||
int getCharacterWidth(util::UTF8CodePoint ch) const;
|
||||
void getStringBounds(
|
||||
const char *str, Rect &rect, bool wordWrap = false,
|
||||
bool breakOnSpace = false
|
||||
|
@ -69,6 +69,7 @@ public:
|
||||
bool enable = disableInterrupts();
|
||||
|
||||
//assert(enable);
|
||||
(void) enable;
|
||||
}
|
||||
inline ~ThreadCriticalSection(void) {
|
||||
enableInterrupts();
|
||||
|
@ -110,6 +110,24 @@ size_t encodeBase41(char *output, const uint8_t *input, size_t length) {
|
||||
return outLength;
|
||||
}
|
||||
|
||||
/* UTF-8 parser */
|
||||
|
||||
size_t getUTF8StringLength(const char *str) {
|
||||
for (size_t length = 0;; length++) {
|
||||
auto value = parseUTF8Character(str);
|
||||
|
||||
if (!value.length) { // Invalid character
|
||||
str++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!value.codePoint) // Null character
|
||||
return length;
|
||||
|
||||
str += value.length;
|
||||
}
|
||||
}
|
||||
|
||||
/* LZ4 decompressor */
|
||||
|
||||
void decompressLZ4(
|
||||
|
@ -33,6 +33,28 @@ size_t serialNumberToString(char *output, const uint8_t *input);
|
||||
size_t traceIDToString(char *output, const uint8_t *input);
|
||||
size_t encodeBase41(char *output, const uint8_t *input, size_t length);
|
||||
|
||||
/* UTF-8 parser */
|
||||
|
||||
using UTF8CodePoint = uint32_t;
|
||||
|
||||
struct UTF8Character {
|
||||
public:
|
||||
UTF8CodePoint codePoint;
|
||||
size_t length;
|
||||
};
|
||||
|
||||
extern "C" uint64_t _parseUTF8Character(const char *ch);
|
||||
size_t getUTF8StringLength(const char *str);
|
||||
|
||||
static inline UTF8Character parseUTF8Character(const char *ch) {
|
||||
auto values = _parseUTF8Character(ch);
|
||||
|
||||
return {
|
||||
.codePoint = UTF8CodePoint(values),
|
||||
.length = size_t(values >> 32)
|
||||
};
|
||||
}
|
||||
|
||||
/* LZ4 decompressor */
|
||||
|
||||
static inline size_t getLZ4InPlaceMargin(size_t inputLength) {
|
||||
|
92
src/common/util/string.s
Normal file
92
src/common/util/string.s
Normal file
@ -0,0 +1,92 @@
|
||||
# 573in1 - Copyright (C) 2022-2024 spicyjpeg
|
||||
#
|
||||
# 573in1 is free software: you can redistribute it and/or modify it under the
|
||||
# terms of the GNU General Public License as published by the Free Software
|
||||
# Foundation, either version 3 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# 573in1 is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# 573in1. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
.set noreorder
|
||||
|
||||
.set GTE_LZCS, $30 # Leading zero count input
|
||||
.set GTE_LZCR, $31 # Leading zero count output
|
||||
|
||||
## UTF-8 parser
|
||||
|
||||
.set codePoint, $v0
|
||||
.set length, $v1
|
||||
.set ch, $a0
|
||||
.set startMask, $a1
|
||||
.set contMask, $a2
|
||||
.set contByte, $a3
|
||||
.set temp, $t0
|
||||
.set i, $t1
|
||||
|
||||
.section .text._parseUTF8Character, "ax", @progbits
|
||||
.global _parseUTF8Character
|
||||
.type _parseUTF8Character, @function
|
||||
|
||||
_parseUTF8Character:
|
||||
# 1-byte character: 0xxxxxxx
|
||||
# 2-byte character: 110xxxxx 10xxxxxx
|
||||
# 3-byte character: 1110xxxx 10xxxxxx 10xxxxxx
|
||||
# 4-byte character: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
|
||||
# uint8_t codePoint = *(ch++);
|
||||
lb codePoint, 0(ch)
|
||||
addiu ch, 1
|
||||
bltz codePoint, .LmsbSet
|
||||
andi codePoint, 0xff
|
||||
|
||||
.LmsbNotSet: # if (signExtend(codePoint) >= 0)
|
||||
# return { codePoint, 1 };
|
||||
jr $ra
|
||||
li length, 1
|
||||
|
||||
.LmsbSet: # if (signExtend(codePoint) < 0)
|
||||
# size_t length = countLeadingOnes(codePoint << 24);
|
||||
sll temp, codePoint, 24
|
||||
mtc2 temp, GTE_LZCS
|
||||
li contMask, (1 << 7)
|
||||
nop
|
||||
mfc2 length, GTE_LZCR
|
||||
li startMask, (1 << 7) - 1
|
||||
|
||||
# codePoint &= (1 << (7 - length)) - 1;
|
||||
srlv startMask, startMask, length
|
||||
and codePoint, startMask
|
||||
addiu i, length, -2
|
||||
|
||||
.LcontinuationLoop: # for (size_t i = length - 1; i--;)
|
||||
# uint8_t contByte = *(ch++);
|
||||
lbu contByte, 0(ch)
|
||||
addiu ch, 1
|
||||
|
||||
# if ((contByte & 0xc0) != 0x80) goto returnInvalid;
|
||||
andi temp, contByte, (3 << 6)
|
||||
bne temp, contMask, .LreturnInvalid
|
||||
andi contByte, (1 << 6) - 1
|
||||
|
||||
# codePoint <<= 6;
|
||||
# codePoint |= contByte & 0x3f;
|
||||
sll codePoint, 6
|
||||
or codePoint, contByte
|
||||
|
||||
bnez i, .LcontinuationLoop
|
||||
addiu i, -1
|
||||
|
||||
.LreturnValid:
|
||||
# return { codePoint, length };
|
||||
jr $ra
|
||||
nop
|
||||
|
||||
.LreturnInvalid:
|
||||
# return { codePoint, 0 };
|
||||
jr $ra
|
||||
li length, 0
|
@ -243,12 +243,12 @@ static const char *const _UI_SOUND_PATHS[ui::NUM_UI_SOUNDS]{
|
||||
void App::_loadResources(void) {
|
||||
auto &res = _fileIO.resource;
|
||||
|
||||
res.loadStruct(_ctx.colors, "assets/palette.dat");
|
||||
res.loadTIM(_background.tile, "assets/textures/background.tim");
|
||||
res.loadTIM(_ctx.font.image, "assets/textures/font.tim");
|
||||
res.loadStruct(_ctx.font.metrics, "assets/textures/font.metrics");
|
||||
res.loadData(_ctx.font.metrics, "assets/textures/font.metrics");
|
||||
res.loadTIM(_splashOverlay.image, "assets/textures/splash.tim");
|
||||
res.loadStruct(_ctx.colors, "assets/app.palette");
|
||||
res.loadData(_stringTable, "assets/app.strings");
|
||||
res.loadData(_stringTable, "assets/lang/en.lang");
|
||||
|
||||
file::currentSPUOffset = spu::DUMMY_BLOCK_END;
|
||||
|
||||
|
@ -152,14 +152,14 @@ const char *FilePickerScreen::_getItemName(ui::Context &ctx, int index) const {
|
||||
auto &dev = ide::devices[drive];
|
||||
auto fs = APP->_fileIO.ide[drive];
|
||||
|
||||
auto icon = (dev.flags & ide::DEVICE_ATAPI)
|
||||
? CH_CDROM_ICON
|
||||
: CH_HDD_ICON;
|
||||
auto label = fs
|
||||
auto format = (dev.flags & ide::DEVICE_ATAPI)
|
||||
? (CH_CDROM_ICON " %s: %s")
|
||||
: (CH_HDD_ICON " %s: %s");
|
||||
auto label = fs
|
||||
? fs->volumeLabel
|
||||
: STR("FilePickerScreen.noFS");
|
||||
|
||||
snprintf(name, sizeof(name), "%c %s: %s", icon, dev.model, label);
|
||||
snprintf(name, sizeof(name), format, dev.model, label);
|
||||
return name;
|
||||
}
|
||||
|
||||
@ -310,26 +310,24 @@ const char *FileBrowserScreen::_getItemName(ui::Context &ctx, int index) const {
|
||||
if (!_isRoot)
|
||||
index--;
|
||||
|
||||
const char *path;
|
||||
const char *format, *path;
|
||||
|
||||
if (index < 0) {
|
||||
name[0] = CH_PARENT_DIR_ICON;
|
||||
path = STR("FileBrowserScreen.parentDir");
|
||||
format = CH_PARENT_DIR_ICON " %s";
|
||||
path = STR("FileBrowserScreen.parentDir");
|
||||
} else if (index < _numDirectories) {
|
||||
auto entries = _directories.as<file::FileInfo>();
|
||||
|
||||
name[0] = CH_DIR_ICON;
|
||||
path = entries[index].name;
|
||||
format = CH_DIR_ICON " %s";
|
||||
path = entries[index].name;
|
||||
} else {
|
||||
auto entries = _files.as<file::FileInfo>();
|
||||
|
||||
name[0] = CH_FILE_ICON;
|
||||
path = entries[index - _numDirectories].name;
|
||||
format = CH_FILE_ICON " %s";
|
||||
path = entries[index - _numDirectories].name;
|
||||
}
|
||||
|
||||
name[1] = ' ';
|
||||
__builtin_strncpy(&name[2], path, sizeof(name) - 2);
|
||||
|
||||
snprintf(name, sizeof(name), format, path);
|
||||
return name;
|
||||
}
|
||||
|
||||
|
@ -275,6 +275,7 @@ void TestPatternScreen::_drawTextOverlay(
|
||||
) const {
|
||||
int screenWidth = ctx.gpuCtx.width - ui::SCREEN_MARGIN_X * 2;
|
||||
int screenHeight = ctx.gpuCtx.height - ui::SCREEN_MARGIN_Y * 2;
|
||||
int lineHeight = ctx.font.getLineHeight();
|
||||
|
||||
_newLayer(ctx, 0, 0, ctx.gpuCtx.width, ctx.gpuCtx.height);
|
||||
|
||||
@ -283,7 +284,7 @@ void TestPatternScreen::_drawTextOverlay(
|
||||
backdropRect.x = ui::SCREEN_MARGIN_X - ui::SHADOW_OFFSET;
|
||||
backdropRect.y = ui::SCREEN_MARGIN_Y - ui::SHADOW_OFFSET;
|
||||
backdropRect.w = ui::SHADOW_OFFSET * 2 + screenWidth;
|
||||
backdropRect.h = ui::SHADOW_OFFSET * 2 + ctx.font.metrics.lineHeight;
|
||||
backdropRect.h = ui::SHADOW_OFFSET * 2 + lineHeight;
|
||||
ctx.gpuCtx.drawRect(backdropRect, ctx.colors[ui::COLOR_SHADOW], true);
|
||||
|
||||
backdropRect.y += screenHeight - ui::SCREEN_PROMPT_HEIGHT_MIN;
|
||||
@ -294,7 +295,7 @@ void TestPatternScreen::_drawTextOverlay(
|
||||
textRect.x1 = ui::SCREEN_MARGIN_X;
|
||||
textRect.y1 = ui::SCREEN_MARGIN_Y;
|
||||
textRect.x2 = textRect.x1 + screenWidth;
|
||||
textRect.y2 = textRect.y1 + ctx.font.metrics.lineHeight;
|
||||
textRect.y2 = textRect.y1 + lineHeight;
|
||||
ctx.font.draw(ctx.gpuCtx, title, textRect, ctx.colors[ui::COLOR_TITLE]);
|
||||
|
||||
textRect.y1 += screenHeight - ui::SCREEN_PROMPT_HEIGHT_MIN;
|
||||
@ -345,18 +346,19 @@ static const IntensityBar _INTENSITY_BARS[]{
|
||||
void ColorIntensityScreen::draw(ui::Context &ctx, bool active) const {
|
||||
TestPatternScreen::draw(ctx, active);
|
||||
|
||||
int barWidth = _INTENSITY_BAR_NAME_WIDTH + _INTENSITY_BAR_WIDTH;
|
||||
int barHeight = _INTENSITY_BAR_HEIGHT * util::countOf(_INTENSITY_BARS);
|
||||
int offsetX = (ctx.gpuCtx.width - barWidth) / 2;
|
||||
int offsetY = (ctx.gpuCtx.height - barHeight) / 2;
|
||||
int barWidth = _INTENSITY_BAR_NAME_WIDTH + _INTENSITY_BAR_WIDTH;
|
||||
int barHeight = _INTENSITY_BAR_HEIGHT * util::countOf(_INTENSITY_BARS);
|
||||
int offsetX = (ctx.gpuCtx.width - barWidth) / 2;
|
||||
int offsetY = (ctx.gpuCtx.height - barHeight) / 2;
|
||||
int lineHeight = ctx.font.getLineHeight();
|
||||
|
||||
gpu::RectWH textRect, barRect;
|
||||
|
||||
textRect.x = offsetX;
|
||||
textRect.y =
|
||||
offsetY + (_INTENSITY_BAR_HEIGHT - ctx.font.metrics.lineHeight) / 2;
|
||||
offsetY + (_INTENSITY_BAR_HEIGHT - lineHeight) / 2;
|
||||
textRect.w = _INTENSITY_BAR_NAME_WIDTH;
|
||||
textRect.h = ctx.font.metrics.lineHeight;
|
||||
textRect.h = lineHeight;
|
||||
|
||||
barRect.x = offsetX + _INTENSITY_BAR_NAME_WIDTH;
|
||||
barRect.y = offsetY;
|
||||
@ -381,7 +383,7 @@ void ColorIntensityScreen::draw(ui::Context &ctx, bool active) const {
|
||||
char value[2]{ 0, 0 };
|
||||
|
||||
textRect.x = barRect.x + 1;
|
||||
textRect.y = offsetY - ctx.font.metrics.lineHeight;
|
||||
textRect.y = offsetY - lineHeight;
|
||||
textRect.w = _INTENSITY_BAR_WIDTH / 32;
|
||||
|
||||
for (int i = 0; i < 32; i++, textRect.x += textRect.w) {
|
||||
|
@ -272,10 +272,12 @@ void TiledBackground::draw(Context &ctx, bool active) const {
|
||||
}
|
||||
|
||||
void TextOverlay::draw(Context &ctx, bool active) const {
|
||||
int lineHeight = ctx.font.getLineHeight();
|
||||
|
||||
gpu::RectWH rect;
|
||||
|
||||
rect.y = ctx.gpuCtx.height - (8 + ctx.font.metrics.lineHeight);
|
||||
rect.h = ctx.font.metrics.lineHeight;
|
||||
rect.y = ctx.gpuCtx.height - (8 + lineHeight);
|
||||
rect.h = lineHeight;
|
||||
|
||||
if (leftText) {
|
||||
rect.x = 8;
|
||||
@ -342,22 +344,22 @@ void LogOverlay::draw(Context &ctx, bool active) const {
|
||||
|
||||
// Text
|
||||
int screenHeight = ctx.gpuCtx.height - SCREEN_MIN_MARGIN_Y * 2;
|
||||
int linesShown = screenHeight / ctx.font.metrics.lineHeight;
|
||||
int lineHeight = ctx.font.getLineHeight();
|
||||
|
||||
gpu::Rect rect;
|
||||
|
||||
rect.x1 = SCREEN_MIN_MARGIN_X;
|
||||
rect.y1 = SCREEN_MIN_MARGIN_Y;
|
||||
rect.x2 = ctx.gpuCtx.width - SCREEN_MIN_MARGIN_X;
|
||||
rect.y2 = SCREEN_MIN_MARGIN_Y + ctx.font.metrics.lineHeight;
|
||||
rect.y2 = SCREEN_MIN_MARGIN_Y + lineHeight;
|
||||
|
||||
for (int i = linesShown - 1; i >= 0; i--) {
|
||||
for (int i = (screenHeight / lineHeight) - 1; i >= 0; i--) {
|
||||
ctx.font.draw(
|
||||
ctx.gpuCtx, _buffer.getLine(i), rect, ctx.colors[COLOR_TEXT1]
|
||||
);
|
||||
|
||||
rect.y1 = rect.y2;
|
||||
rect.y2 += ctx.font.metrics.lineHeight;
|
||||
rect.y2 += lineHeight;
|
||||
}
|
||||
}
|
||||
|
||||
@ -471,7 +473,7 @@ void ModalScreen::draw(Context &ctx, bool active) const {
|
||||
rect.x1 = TITLE_BAR_PADDING;
|
||||
rect.y1 = TITLE_BAR_PADDING;
|
||||
rect.x2 = _width - TITLE_BAR_PADDING;
|
||||
rect.y2 = TITLE_BAR_PADDING + ctx.font.metrics.lineHeight;
|
||||
rect.y2 = TITLE_BAR_PADDING + ctx.font.getLineHeight();
|
||||
//rect.y2 = TITLE_BAR_HEIGHT - TITLE_BAR_PADDING;
|
||||
ctx.font.draw(ctx.gpuCtx, _title, rect, ctx.colors[COLOR_TITLE]);
|
||||
|
||||
|
@ -38,6 +38,7 @@ void TextScreen::show(Context &ctx, bool goBack) {
|
||||
void TextScreen::draw(Context &ctx, bool active) const {
|
||||
int screenWidth = ctx.gpuCtx.width - SCREEN_MARGIN_X * 2;
|
||||
int screenHeight = ctx.gpuCtx.height - SCREEN_MARGIN_Y * 2;
|
||||
int lineHeight = ctx.font.getLineHeight();
|
||||
|
||||
// Top/bottom text
|
||||
_newLayer(
|
||||
@ -49,14 +50,14 @@ void TextScreen::draw(Context &ctx, bool active) const {
|
||||
rect.x1 = 0;
|
||||
rect.y1 = 0;
|
||||
rect.x2 = screenWidth;
|
||||
rect.y2 = ctx.font.metrics.lineHeight;
|
||||
rect.y2 = lineHeight;
|
||||
ctx.font.draw(ctx.gpuCtx, _title, rect, ctx.colors[COLOR_TITLE]);
|
||||
|
||||
rect.y1 = screenHeight - SCREEN_PROMPT_HEIGHT_MIN;
|
||||
rect.y2 = screenHeight;
|
||||
ctx.font.draw(ctx.gpuCtx, _prompt, rect, ctx.colors[COLOR_TEXT1], true);
|
||||
|
||||
int bodyOffset = ctx.font.metrics.lineHeight + SCREEN_BLOCK_MARGIN;
|
||||
int bodyOffset = lineHeight + SCREEN_BLOCK_MARGIN;
|
||||
int bodyHeight = screenHeight -
|
||||
(bodyOffset + SCREEN_PROMPT_HEIGHT_MIN + SCREEN_BLOCK_MARGIN);
|
||||
|
||||
@ -82,7 +83,7 @@ void TextScreen::update(Context &ctx) {
|
||||
return;
|
||||
|
||||
int screenHeight = ctx.gpuCtx.height - SCREEN_MARGIN_Y * 2;
|
||||
int bodyOffset = ctx.font.metrics.lineHeight + SCREEN_BLOCK_MARGIN;
|
||||
int bodyOffset = ctx.font.getLineHeight() + SCREEN_BLOCK_MARGIN;
|
||||
int bodyHeight = screenHeight -
|
||||
(bodyOffset + SCREEN_PROMPT_HEIGHT_MIN + SCREEN_BLOCK_MARGIN);
|
||||
|
||||
@ -128,6 +129,8 @@ _prompt(nullptr) {}
|
||||
void ImageScreen::draw(Context &ctx, bool active) const {
|
||||
_newLayer(ctx, 0, 0, ctx.gpuCtx.width, ctx.gpuCtx.height);
|
||||
|
||||
int lineHeight = ctx.font.getLineHeight();
|
||||
|
||||
if (_image) {
|
||||
int x = ctx.gpuCtx.width / 2;
|
||||
int y = ctx.gpuCtx.height / 2;
|
||||
@ -135,7 +138,7 @@ void ImageScreen::draw(Context &ctx, bool active) const {
|
||||
int height = _image->height * _imageScale / 2;
|
||||
|
||||
if (_prompt)
|
||||
y -= (SCREEN_PROMPT_HEIGHT - ctx.font.metrics.lineHeight) / 2;
|
||||
y -= (SCREEN_PROMPT_HEIGHT - lineHeight) / 2;
|
||||
|
||||
// Backdrop
|
||||
if (_imagePadding) {
|
||||
@ -162,7 +165,7 @@ void ImageScreen::draw(Context &ctx, bool active) const {
|
||||
rect.x1 = SCREEN_MARGIN_X;
|
||||
rect.y1 = SCREEN_MARGIN_Y;
|
||||
rect.x2 = ctx.gpuCtx.width - SCREEN_MARGIN_X;
|
||||
rect.y2 = SCREEN_MARGIN_Y + ctx.font.metrics.lineHeight;
|
||||
rect.y2 = SCREEN_MARGIN_Y + lineHeight;
|
||||
ctx.font.draw(ctx.gpuCtx, _title, rect, ctx.colors[COLOR_TITLE]);
|
||||
|
||||
rect.y1 = ctx.gpuCtx.height - (SCREEN_MARGIN_Y + SCREEN_PROMPT_HEIGHT);
|
||||
@ -177,6 +180,7 @@ void ListScreen::_drawItems(Context &ctx) const {
|
||||
int itemY = _scrollAnim.getValue(ctx.time);
|
||||
int itemWidth = _getItemWidth(ctx);
|
||||
int listHeight = _getListHeight(ctx);
|
||||
int lineHeight = ctx.font.getLineHeight();
|
||||
|
||||
gpu::Rect rect;
|
||||
|
||||
@ -185,10 +189,10 @@ void ListScreen::_drawItems(Context &ctx) const {
|
||||
//rect.y2 = listHeight;
|
||||
|
||||
for (int i = 0; (i < _listLength) && (itemY < listHeight); i++) {
|
||||
int itemHeight = ctx.font.metrics.lineHeight + LIST_ITEM_PADDING * 2;
|
||||
int itemHeight = lineHeight + LIST_ITEM_PADDING * 2;
|
||||
|
||||
if (i == _activeItem)
|
||||
itemHeight += ctx.font.metrics.lineHeight;
|
||||
itemHeight += lineHeight;
|
||||
|
||||
if ((itemY + itemHeight) >= 0) {
|
||||
if (i == _activeItem) {
|
||||
@ -201,15 +205,15 @@ void ListScreen::_drawItems(Context &ctx) const {
|
||||
itemHeight, ctx.colors[COLOR_HIGHLIGHT1]
|
||||
);
|
||||
|
||||
rect.y1 = itemY + LIST_ITEM_PADDING + ctx.font.metrics.lineHeight;
|
||||
rect.y2 = rect.y1 + ctx.font.metrics.lineHeight;
|
||||
rect.y1 = itemY + LIST_ITEM_PADDING + lineHeight;
|
||||
rect.y2 = rect.y1 + lineHeight;
|
||||
ctx.font.draw(
|
||||
ctx.gpuCtx, _itemPrompt, rect, ctx.colors[COLOR_SUBTITLE]
|
||||
);
|
||||
}
|
||||
|
||||
rect.y1 = itemY + LIST_ITEM_PADDING;
|
||||
rect.y2 = rect.y1 + ctx.font.metrics.lineHeight;
|
||||
rect.y2 = rect.y1 + lineHeight;
|
||||
ctx.font.draw(
|
||||
ctx.gpuCtx, _getItemName(ctx, i), rect, ctx.colors[COLOR_TITLE]
|
||||
);
|
||||
@ -231,6 +235,7 @@ void ListScreen::draw(Context &ctx, bool active) const {
|
||||
int screenWidth = ctx.gpuCtx.width - SCREEN_MARGIN_X * 2;
|
||||
int screenHeight = ctx.gpuCtx.height - SCREEN_MARGIN_Y * 2;
|
||||
int listHeight = _getListHeight(ctx);
|
||||
int lineHeight = ctx.font.getLineHeight();
|
||||
|
||||
_newLayer(
|
||||
ctx, SCREEN_MARGIN_X, SCREEN_MARGIN_Y, screenWidth, screenHeight
|
||||
@ -242,7 +247,7 @@ void ListScreen::draw(Context &ctx, bool active) const {
|
||||
rect.x1 = 0;
|
||||
rect.y1 = 0;
|
||||
rect.x2 = screenWidth;
|
||||
rect.y2 = ctx.font.metrics.lineHeight;
|
||||
rect.y2 = lineHeight;
|
||||
ctx.font.draw(ctx.gpuCtx, _title, rect, ctx.colors[COLOR_TITLE]);
|
||||
|
||||
rect.y1 = screenHeight - SCREEN_PROMPT_HEIGHT;
|
||||
@ -251,8 +256,8 @@ void ListScreen::draw(Context &ctx, bool active) const {
|
||||
|
||||
_newLayer(
|
||||
ctx, SCREEN_MARGIN_X,
|
||||
SCREEN_MARGIN_Y + ctx.font.metrics.lineHeight + SCREEN_BLOCK_MARGIN,
|
||||
screenWidth, listHeight
|
||||
SCREEN_MARGIN_Y + lineHeight + SCREEN_BLOCK_MARGIN, screenWidth,
|
||||
listHeight
|
||||
);
|
||||
_setBlendMode(ctx, GP0_BLEND_SEMITRANS, true);
|
||||
|
||||
@ -270,23 +275,22 @@ void ListScreen::draw(Context &ctx, bool active) const {
|
||||
|
||||
// Up/down arrow icons
|
||||
gpu::RectWH iconRect;
|
||||
char arrow[2]{ 0, 0 };
|
||||
|
||||
iconRect.x = screenWidth -
|
||||
(ctx.font.metrics.lineHeight + LIST_BOX_PADDING);
|
||||
iconRect.w = ctx.font.metrics.lineHeight;
|
||||
iconRect.h = ctx.font.metrics.lineHeight;
|
||||
iconRect.x = screenWidth - (lineHeight + LIST_BOX_PADDING);
|
||||
iconRect.w = lineHeight;
|
||||
iconRect.h = lineHeight;
|
||||
|
||||
if (_activeItem) {
|
||||
arrow[0] = CH_UP_ARROW;
|
||||
iconRect.y = LIST_BOX_PADDING;
|
||||
ctx.font.draw(ctx.gpuCtx, arrow, iconRect, ctx.colors[COLOR_TEXT1]);
|
||||
ctx.font.draw(
|
||||
ctx.gpuCtx, CH_UP_ARROW, iconRect, ctx.colors[COLOR_TEXT1]
|
||||
);
|
||||
}
|
||||
if (_activeItem < (_listLength - 1)) {
|
||||
arrow[0] = CH_DOWN_ARROW;
|
||||
iconRect.y = listHeight -
|
||||
(ctx.font.metrics.lineHeight + LIST_BOX_PADDING);
|
||||
ctx.font.draw(ctx.gpuCtx, arrow, iconRect, ctx.colors[COLOR_TEXT1]);
|
||||
iconRect.y = listHeight - (lineHeight + LIST_BOX_PADDING);
|
||||
ctx.font.draw(
|
||||
ctx.gpuCtx, CH_DOWN_ARROW, iconRect, ctx.colors[COLOR_TEXT1]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -323,8 +327,9 @@ void ListScreen::update(Context &ctx) {
|
||||
}
|
||||
|
||||
// Scroll the list if the selected item is not fully visible.
|
||||
int itemHeight = ctx.font.metrics.lineHeight + LIST_ITEM_PADDING * 2;
|
||||
int activeItemHeight = itemHeight + ctx.font.metrics.lineHeight;
|
||||
int lineHeight = ctx.font.getLineHeight();
|
||||
int itemHeight = lineHeight + LIST_ITEM_PADDING * 2;
|
||||
int activeItemHeight = lineHeight + itemHeight;
|
||||
|
||||
int topOffset = _activeItem * itemHeight;
|
||||
int bottomOffset = topOffset + activeItemHeight - _getListHeight(ctx);
|
||||
|
@ -70,7 +70,7 @@ private:
|
||||
inline int _getListHeight(Context &ctx) const {
|
||||
int screenHeight = ctx.gpuCtx.height - SCREEN_MARGIN_Y * 2;
|
||||
return screenHeight - (
|
||||
ctx.font.metrics.lineHeight + SCREEN_PROMPT_HEIGHT +
|
||||
ctx.font.getLineHeight() + SCREEN_PROMPT_HEIGHT +
|
||||
SCREEN_BLOCK_MARGIN * 2
|
||||
);
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ void MessageBoxScreen::draw(Context &ctx, bool active) const {
|
||||
|
||||
rect.y = buttonY + BUTTON_PADDING;
|
||||
rect.w = _getButtonWidth();
|
||||
rect.h = rect.y + ctx.font.metrics.lineHeight;
|
||||
rect.h = rect.y + ctx.font.getLineHeight();
|
||||
//rect.h = BUTTON_HEIGHT - BUTTON_PADDING * 2;
|
||||
|
||||
for (int i = 0; i < _numButtons; i++) {
|
||||
@ -184,7 +184,7 @@ void HexEntryScreen::draw(Context &ctx, bool active) const {
|
||||
rect.x1 = stringOffset;
|
||||
rect.y1 = boxY + BUTTON_PADDING;
|
||||
rect.x2 = _width - MODAL_PADDING;
|
||||
rect.y2 = boxY + BUTTON_PADDING + ctx.font.metrics.lineHeight;
|
||||
rect.y2 = boxY + BUTTON_PADDING + ctx.font.getLineHeight();
|
||||
ctx.font.draw(ctx.gpuCtx, string, rect, ctx.colors[COLOR_TITLE]);
|
||||
|
||||
// Highlighted field
|
||||
@ -297,7 +297,7 @@ void DateEntryScreen::show(Context &ctx, bool goBack) {
|
||||
_charWidth = ctx.font.getCharacterWidth('0');
|
||||
|
||||
int dateSepWidth = ctx.font.getCharacterWidth('-');
|
||||
int spaceWidth = ctx.font.metrics.spaceWidth;
|
||||
int spaceWidth = ctx.font.getSpaceWidth();
|
||||
int timeSepWidth = ctx.font.getCharacterWidth(':');
|
||||
|
||||
_fieldOffsets[0] = 0;
|
||||
@ -354,7 +354,7 @@ void DateEntryScreen::draw(Context &ctx, bool active) const {
|
||||
rect.x1 = stringOffset;
|
||||
rect.y1 = boxY + BUTTON_PADDING;
|
||||
rect.x2 = _width - MODAL_PADDING;
|
||||
rect.y2 = boxY + BUTTON_PADDING + ctx.font.metrics.lineHeight;
|
||||
rect.y2 = boxY + BUTTON_PADDING + ctx.font.getLineHeight();
|
||||
ctx.font.draw(ctx.gpuCtx, string, rect, ctx.colors[COLOR_TITLE]);
|
||||
|
||||
// Highlighted field
|
||||
|
@ -174,7 +174,7 @@ def createParser() -> ArgumentParser:
|
||||
)
|
||||
group.add_argument(
|
||||
"configFile",
|
||||
type = FileType("rt"),
|
||||
type = FileType("rt", encoding = "utf-8"),
|
||||
help = "Path to JSON configuration file",
|
||||
)
|
||||
group.add_argument(
|
||||
|
@ -72,7 +72,7 @@ def createParser() -> ArgumentParser:
|
||||
)
|
||||
group.add_argument(
|
||||
"configFile",
|
||||
type = FileType("rt"),
|
||||
type = FileType("rt", encoding = "utf-8"),
|
||||
help = "Path to JSON configuration file",
|
||||
)
|
||||
group.add_argument(
|
||||
@ -101,7 +101,9 @@ def main():
|
||||
data: ByteString = bytes(int(asset.get("size", 0)))
|
||||
|
||||
case "text":
|
||||
with open(sourceDir / asset["source"], "rt") as file:
|
||||
with open(
|
||||
sourceDir / asset["source"], "rt", encoding = "utf-8"
|
||||
) as file:
|
||||
data: ByteString = file.read().encode("ascii")
|
||||
|
||||
case "binary":
|
||||
@ -128,7 +130,10 @@ def main():
|
||||
if "metrics" in asset:
|
||||
metrics: dict = asset["metrics"]
|
||||
else:
|
||||
with open(sourceDir / asset["source"], "rt") as file:
|
||||
with open(
|
||||
sourceDir / asset["source"], "rt",
|
||||
encoding = "utf-8"
|
||||
) as file:
|
||||
metrics: dict = json.load(file)
|
||||
|
||||
data: ByteString = generateFontMetrics(metrics)
|
||||
@ -137,7 +142,10 @@ def main():
|
||||
if "palette" in asset:
|
||||
palette: dict = asset["palette"]
|
||||
else:
|
||||
with open(sourceDir / asset["source"], "rt") as file:
|
||||
with open(
|
||||
sourceDir / asset["source"], "rt",
|
||||
encoding = "utf-8"
|
||||
) as file:
|
||||
palette: dict = json.load(file)
|
||||
|
||||
data: ByteString = generateColorPalette(palette)
|
||||
@ -146,7 +154,10 @@ def main():
|
||||
if "strings" in asset:
|
||||
strings: dict = asset["strings"]
|
||||
else:
|
||||
with open(sourceDir / asset["source"], "rt") as file:
|
||||
with open(
|
||||
sourceDir / asset["source"], "rt",
|
||||
encoding = "utf-8"
|
||||
) as file:
|
||||
strings: dict = json.load(file)
|
||||
|
||||
data: ByteString = generateStringTable(strings)
|
||||
@ -155,7 +166,10 @@ def main():
|
||||
if "db" in asset:
|
||||
db: dict = asset["db"]
|
||||
else:
|
||||
with open(sourceDir / asset["source"], "rt") as file:
|
||||
with open(
|
||||
sourceDir / asset["source"], "rt",
|
||||
encoding = "utf-8"
|
||||
) as file:
|
||||
db: dict = json.load(file)
|
||||
|
||||
# TODO: implement
|
||||
|
@ -14,16 +14,14 @@
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# 573in1. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from itertools import chain
|
||||
from struct import Struct
|
||||
from typing import Any, Generator, Mapping, Sequence
|
||||
from itertools import chain
|
||||
from struct import Struct
|
||||
from typing import Any, Generator, Mapping, Sequence
|
||||
|
||||
import numpy
|
||||
from numpy import ndarray
|
||||
from PIL import Image
|
||||
from .util import colorFromString, hashData
|
||||
from .util import colorFromString, generateHashTable, hashData
|
||||
|
||||
## .TIM image converter
|
||||
|
||||
@ -102,51 +100,49 @@ def generateIndexedTIM(
|
||||
if (cx < 0) or (cx > 1023) or (cy < 0) or (cy > 1023):
|
||||
raise ValueError("palette X/Y coordinates must be in 0-1023 range")
|
||||
|
||||
image, clut = convertIndexedImage(imageObj)
|
||||
image, clut = convertIndexedImage(imageObj)
|
||||
data: bytearray = bytearray()
|
||||
|
||||
mode: int = 0x8 if (clut.size <= 16) else 0x9
|
||||
data: bytearray = bytearray(
|
||||
_TIM_HEADER_STRUCT.pack(_TIM_HEADER_VERSION, mode)
|
||||
data += _TIM_HEADER_STRUCT.pack(
|
||||
_TIM_HEADER_VERSION,
|
||||
0x8 if (clut.size <= 16) else 0x9
|
||||
)
|
||||
|
||||
data.extend(_TIM_SECTION_STRUCT.pack(
|
||||
data += _TIM_SECTION_STRUCT.pack(
|
||||
_TIM_SECTION_STRUCT.size + clut.size * 2,
|
||||
cx, cy, clut.shape[1], clut.shape[0]
|
||||
))
|
||||
cx,
|
||||
cy,
|
||||
clut.shape[1],
|
||||
clut.shape[0]
|
||||
)
|
||||
data.extend(clut)
|
||||
|
||||
data.extend(_TIM_SECTION_STRUCT.pack(
|
||||
data += _TIM_SECTION_STRUCT.pack(
|
||||
_TIM_SECTION_STRUCT.size + image.size,
|
||||
ix, iy, image.shape[1] // 2, image.shape[0]
|
||||
))
|
||||
ix,
|
||||
iy,
|
||||
image.shape[1] // 2,
|
||||
image.shape[0]
|
||||
)
|
||||
data.extend(image)
|
||||
|
||||
return data
|
||||
|
||||
## Font metrics generator
|
||||
|
||||
_METRICS_HEADER_STRUCT: Struct = Struct("< 3B x")
|
||||
_METRICS_ENTRY_STRUCT: Struct = Struct("< 2B H")
|
||||
_METRICS_HEADER_STRUCT: Struct = Struct("< 3B b")
|
||||
_METRICS_ENTRY_STRUCT: Struct = Struct("< 2I")
|
||||
_METRICS_BUCKET_COUNT: int = 256
|
||||
|
||||
def generateFontMetrics(metrics: Mapping[str, Any]) -> bytearray:
|
||||
data: bytearray = bytearray(
|
||||
_METRICS_HEADER_STRUCT.size + _METRICS_ENTRY_STRUCT.size * 256
|
||||
)
|
||||
spaceWidth: int = int(metrics["spaceWidth"])
|
||||
tabWidth: int = int(metrics["tabWidth"])
|
||||
lineHeight: int = int(metrics["lineHeight"])
|
||||
baselineOffset: int = int(metrics["baselineOffset"])
|
||||
|
||||
spaceWidth: int = int(metrics["spaceWidth"])
|
||||
tabWidth: int = int(metrics["tabWidth"])
|
||||
lineHeight: int = int(metrics["lineHeight"])
|
||||
|
||||
data[0:_METRICS_HEADER_STRUCT.size] = \
|
||||
_METRICS_HEADER_STRUCT.pack(spaceWidth, tabWidth, lineHeight)
|
||||
entries: dict[int, int] = {}
|
||||
|
||||
for ch, entry in metrics["characterSizes"].items():
|
||||
index: int = ord(ch)
|
||||
#index: int = ch.encode("ascii")[0]
|
||||
|
||||
if (index < 0) or (index > 255):
|
||||
raise ValueError(f"extended character {index} is not supported")
|
||||
|
||||
x: int = int(entry["x"])
|
||||
y: int = int(entry["y"])
|
||||
w: int = int(entry["width"])
|
||||
@ -160,12 +156,38 @@ def generateFontMetrics(metrics: Mapping[str, Any]) -> bytearray:
|
||||
if h > lineHeight:
|
||||
raise ValueError("character height exceeds line height")
|
||||
|
||||
offset: int = \
|
||||
_METRICS_HEADER_STRUCT.size + _METRICS_ENTRY_STRUCT.size * index
|
||||
data[offset:offset + _METRICS_ENTRY_STRUCT.size] = \
|
||||
_METRICS_ENTRY_STRUCT.pack(x, y, w | (h << 7) | (i << 14))
|
||||
entries[ord(ch)] = (0
|
||||
| (x << 0)
|
||||
| (y << 8)
|
||||
| (w << 16)
|
||||
| (h << 23)
|
||||
| (i << 30)
|
||||
)
|
||||
|
||||
return data
|
||||
buckets, chained = generateHashTable(entries, _METRICS_BUCKET_COUNT)
|
||||
table: bytearray = bytearray()
|
||||
|
||||
if (len(buckets) + len(chained)) > 2048:
|
||||
raise RuntimeError("font hash table must have <=2048 entries")
|
||||
|
||||
table += _METRICS_HEADER_STRUCT.pack(
|
||||
spaceWidth,
|
||||
tabWidth,
|
||||
lineHeight,
|
||||
baselineOffset
|
||||
)
|
||||
|
||||
for entry in chain(buckets, chained):
|
||||
if entry is None:
|
||||
table += _METRICS_ENTRY_STRUCT.pack(0, 0)
|
||||
continue
|
||||
|
||||
table += _METRICS_ENTRY_STRUCT.pack(
|
||||
entry.fullHash | (entry.chainIndex << 21),
|
||||
entry.data
|
||||
)
|
||||
|
||||
return table
|
||||
|
||||
## Color palette generator
|
||||
|
||||
@ -207,125 +229,71 @@ def generateColorPalette(
|
||||
else:
|
||||
r, g, b = color
|
||||
|
||||
data.extend(_PALETTE_ENTRY_STRUCT.pack(r, g, b))
|
||||
data += _PALETTE_ENTRY_STRUCT.pack(r, g, b)
|
||||
|
||||
return data
|
||||
|
||||
## String table generator
|
||||
|
||||
_TABLE_ENTRY_STRUCT: Struct = Struct("< I 2H")
|
||||
_TABLE_BUCKET_COUNT: int = 256
|
||||
_TABLE_STRING_ALIGN: int = 4
|
||||
|
||||
_TABLE_ESCAPE_REGEX: re.Pattern = re.compile(rb"\$?\{(.+?)\}")
|
||||
_TABLE_ESCAPE_REPL: Mapping[bytes, bytes] = {
|
||||
b"UP_ARROW": b"\x80",
|
||||
b"DOWN_ARROW": b"\x81",
|
||||
b"LEFT_ARROW": b"\x82",
|
||||
b"RIGHT_ARROW": b"\x83",
|
||||
b"UP_ARROW_ALT": b"\x84",
|
||||
b"DOWN_ARROW_ALT": b"\x85",
|
||||
b"LEFT_ARROW_ALT": b"\x86",
|
||||
b"RIGHT_ARROW_ALT": b"\x87",
|
||||
|
||||
b"LEFT_BUTTON": b"\x90",
|
||||
b"RIGHT_BUTTON": b"\x91",
|
||||
b"START_BUTTON": b"\x92",
|
||||
b"CLOSED_LOCK": b"\x93",
|
||||
b"OPEN_LOCK": b"\x94",
|
||||
b"CHIP_ICON": b"\x95",
|
||||
b"CART_ICON": b"\x96",
|
||||
|
||||
b"CDROM_ICON": b"\xa0",
|
||||
b"HDD_ICON": b"\xa1",
|
||||
b"HOST_ICON": b"\xa2",
|
||||
b"DIR_ICON": b"\xa3",
|
||||
b"PARENT_DIR_ICON": b"\xa4",
|
||||
b"FILE_ICON": b"\xa5"
|
||||
}
|
||||
|
||||
def _convertString(string: str) -> bytes:
|
||||
return _TABLE_ESCAPE_REGEX.sub(
|
||||
lambda match: _TABLE_ESCAPE_REPL[match.group(1).strip().upper()],
|
||||
string.encode("ascii")
|
||||
)
|
||||
_STRING_TABLE_ENTRY_STRUCT: Struct = Struct("< I 2H")
|
||||
_STRING_TABLE_BUCKET_COUNT: int = 256
|
||||
_STRING_TABLE_ALIGNMENT: int = 4
|
||||
|
||||
def _walkStringTree(
|
||||
strings: Mapping[str, Any], prefix: str = ""
|
||||
) -> Generator[tuple[int, bytes | None], None, None]:
|
||||
for key, value in strings.items():
|
||||
fullKey: str = prefix + key
|
||||
keyHash: int = hashData(fullKey.encode("ascii"))
|
||||
|
||||
if value is None:
|
||||
yield hashData(fullKey.encode("ascii")), None
|
||||
yield keyHash, None
|
||||
elif isinstance(value, str):
|
||||
yield hashData(fullKey.encode("ascii")), _convertString(value)
|
||||
yield keyHash, value.encode("utf-8")
|
||||
else:
|
||||
yield from _walkStringTree(value, f"{fullKey}.")
|
||||
|
||||
def generateStringTable(strings: Mapping[str, Any]) -> bytearray:
|
||||
offsets: dict[bytes, int] = {}
|
||||
chains: defaultdict[int, list[tuple[int, int | None]]] = defaultdict(list)
|
||||
offsets: dict[bytes, int] = {}
|
||||
entries: dict[int, int] = {}
|
||||
blob: bytearray = bytearray()
|
||||
|
||||
blob: bytearray = bytearray()
|
||||
|
||||
for fullHash, string in _walkStringTree(strings):
|
||||
for keyHash, string in _walkStringTree(strings):
|
||||
if string is None:
|
||||
entry: tuple[int, int | None] = fullHash, 0
|
||||
else:
|
||||
offset: int | None = offsets.get(string, None)
|
||||
|
||||
if offset is None:
|
||||
offset = len(blob)
|
||||
offsets[string] = offset
|
||||
|
||||
blob.extend(string)
|
||||
blob.append(0)
|
||||
|
||||
while len(blob) % _TABLE_STRING_ALIGN:
|
||||
blob.append(0)
|
||||
|
||||
entry: tuple[int, int | None] = fullHash, offset
|
||||
|
||||
chains[fullHash % _TABLE_BUCKET_COUNT].append(entry)
|
||||
|
||||
# Build the bucket array and all chains of entries.
|
||||
buckets: list[tuple[int, int | None, int]] = []
|
||||
chained: list[tuple[int, int | None, int]] = []
|
||||
|
||||
for shortHash in range(_TABLE_BUCKET_COUNT):
|
||||
entries: list[tuple[int, int | None]] = chains[shortHash]
|
||||
|
||||
if not entries:
|
||||
buckets.append(( 0, None, 0 ))
|
||||
entries[keyHash] = 0
|
||||
continue
|
||||
|
||||
for index, entry in enumerate(entries):
|
||||
if index < (len(entries) - 1):
|
||||
chainIndex: int = _TABLE_BUCKET_COUNT + len(chained)
|
||||
else:
|
||||
chainIndex: int = 0
|
||||
# Identical strings associated to multiple keys are deduplicated.
|
||||
offset: int | None = offsets.get(string, None)
|
||||
|
||||
fullHash, offset = entry
|
||||
if offset is None:
|
||||
offset = len(blob)
|
||||
offsets[string] = offset
|
||||
|
||||
if index:
|
||||
chained.append(( fullHash, offset, chainIndex + 1 ))
|
||||
else:
|
||||
buckets.append(( fullHash, offset, chainIndex ))
|
||||
blob += string
|
||||
blob.append(0)
|
||||
|
||||
while len(blob) % _STRING_TABLE_ALIGNMENT:
|
||||
blob.append(0)
|
||||
|
||||
entries[keyHash] = offset
|
||||
|
||||
buckets, chained = generateHashTable(entries, _STRING_TABLE_BUCKET_COUNT)
|
||||
table: bytearray = bytearray()
|
||||
|
||||
# Relocate the offsets and serialize the table.
|
||||
totalLength: int = len(buckets) + len(chained)
|
||||
blobOffset: int = _TABLE_ENTRY_STRUCT.size * totalLength
|
||||
data: bytearray = bytearray()
|
||||
blobOffset: int = \
|
||||
(len(buckets) + len(chained)) * _STRING_TABLE_ENTRY_STRUCT.size
|
||||
|
||||
for fullHash, offset, chainIndex in chain(buckets, chained):
|
||||
absOffset: int = 0 if (offset is None) else (blobOffset + offset)
|
||||
for entry in chain(buckets, chained):
|
||||
if entry is None:
|
||||
table += _STRING_TABLE_ENTRY_STRUCT.pack(0, 0, 0)
|
||||
continue
|
||||
|
||||
if absOffset > 0xffff:
|
||||
raise RuntimeError("string table exceeds 64 KB size limit")
|
||||
table += _STRING_TABLE_ENTRY_STRUCT.pack(
|
||||
entry.fullHash,
|
||||
0 if (entry.data is None) else (blobOffset + entry.data),
|
||||
entry.chainIndex
|
||||
)
|
||||
|
||||
data.extend(_TABLE_ENTRY_STRUCT.pack(fullHash, absOffset, chainIndex))
|
||||
|
||||
data.extend(blob)
|
||||
|
||||
return data
|
||||
return table + blob
|
||||
|
@ -15,10 +15,13 @@
|
||||
# 573in1. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import logging, re
|
||||
from hashlib import md5
|
||||
from io import SEEK_END, SEEK_SET
|
||||
from typing import \
|
||||
Any, BinaryIO, ByteString, Iterable, Iterator, Sequence, TextIO
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from hashlib import md5
|
||||
from io import SEEK_END, SEEK_SET
|
||||
from typing import \
|
||||
Any, BinaryIO, ByteString, Generator, Iterable, Iterator, Mapping, \
|
||||
Sequence, TextIO
|
||||
|
||||
## Value manipulation
|
||||
|
||||
@ -142,6 +145,44 @@ def setupLogger(level: int | None):
|
||||
)[min(level or 0, 2)]
|
||||
)
|
||||
|
||||
## Hash table generator
|
||||
|
||||
@dataclass
|
||||
class HashTableEntry:
|
||||
fullHash: int
|
||||
chainIndex: int
|
||||
data: Any
|
||||
|
||||
def generateHashTable(
|
||||
entries: Mapping[int, Any], numBuckets: int
|
||||
) -> tuple[list[HashTableEntry | None], list[HashTableEntry]]:
|
||||
chains: defaultdict[int, list[HashTableEntry]] = defaultdict(list)
|
||||
|
||||
for fullHash, data in entries.items():
|
||||
entry: HashTableEntry = HashTableEntry(fullHash, 0, data)
|
||||
|
||||
chains[fullHash % numBuckets].append(entry)
|
||||
|
||||
buckets: list[HashTableEntry | None] = []
|
||||
chained: list[HashTableEntry] = []
|
||||
|
||||
for shortHash in range(numBuckets):
|
||||
entries: list[HashTableEntry] = chains[shortHash]
|
||||
|
||||
if not len(entries): # Empty bucket
|
||||
buckets.append(None)
|
||||
continue
|
||||
|
||||
for index, entry in enumerate(entries):
|
||||
entry.chainIndex = numBuckets + len(chained) + index
|
||||
|
||||
entries[-1].chainIndex = 0 # Terminate chain
|
||||
|
||||
buckets.append(entries[0])
|
||||
chained += entries[1:]
|
||||
|
||||
return buckets, chained
|
||||
|
||||
## Odd/even interleaved file reader
|
||||
|
||||
class InterleavedFile(BinaryIO):
|
||||
|
@ -82,7 +82,7 @@ def createParser() -> ArgumentParser:
|
||||
)
|
||||
group.add_argument(
|
||||
"-l", "--log",
|
||||
type = FileType("at"),
|
||||
type = FileType("at", encoding = "utf-8"),
|
||||
default = sys.stdout,
|
||||
help = "Log cartridge info to specified file (stdout by default)",
|
||||
metavar = "file"
|
||||
|
Loading…
x
Reference in New Issue
Block a user