Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e8b7c468a4 | |||
| 33998aab5d | |||
| 6cfc85ca83 |
@@ -386,8 +386,17 @@ Vautour (Burpy Fresh), licensed under CC-BY 4.0 (5x11 base characters,
|
||||
the registry entry: `file` for a mod-shipped TTF, `size` (the font's
|
||||
design em; Plain Pixel rasterizes cleanly only at multiples of 15),
|
||||
`spacing` added to every advance, `yOffset` for vertical alignment
|
||||
against the 8px cell grid, and `bold`, which double-prints at a 1px
|
||||
offset for fonts whose strokes read too light.
|
||||
against the 8px cell grid, `bold`, which double-prints at a 1px
|
||||
offset for fonts whose strokes read too light, and `tiles`, the
|
||||
characters that keep their ROM tile instead of coming from the TTF.
|
||||
|
||||
`tiles` matters for a CJK translation. Sizing the font so a kana fills
|
||||
the 8px cell leaves Latin narrower than the tile font it replaces, which
|
||||
pulls the numeric columns out of line: the party menu's `:L12` stops
|
||||
sitting over `34/ 34`. Naming `"0123456789/:"` keeps those on the
|
||||
vanilla tiles, so numbers render exactly as they do in English while
|
||||
kana still come from the font. It takes a string of characters, or a
|
||||
list when a multi-character charmap sequence is meant.
|
||||
|
||||
See the wiki's Translations guide.
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 42 KiB |
@@ -121,6 +121,32 @@ else
|
||||
say "version '$VERSION' is not X.Y.Z, shipping default engine (no stamp)"
|
||||
fi
|
||||
|
||||
# --------------------------------------------------------------- app icon
|
||||
# One source of truth for every platform's launcher icon; iOS resizes the
|
||||
# same file in scripts/build_ios.sh (apply_ios_icon) and the Android res/
|
||||
# drawables are generated from it too.
|
||||
ICON_SRC="$ROOT/assets/logo/gen1recomp_cover.png"
|
||||
|
||||
# pipx installs peresed (Windows exe icon patcher) here, off the default PATH.
|
||||
PATH="$PATH:$HOME/.local/bin"
|
||||
|
||||
make_icns() { # $1 = output .icns path
|
||||
[ -f "$ICON_SRC" ] || fail "missing icon source: $ICON_SRC"
|
||||
local iconset="$WORK/GameIcon.iconset" size scaled
|
||||
rm -rf "$iconset"; mkdir -p "$iconset"
|
||||
for size in 16 32 128 256 512; do
|
||||
sips -z "$size" "$size" "$ICON_SRC" --out "$iconset/icon_${size}x${size}.png" >/dev/null
|
||||
scaled=$((size * 2))
|
||||
sips -z "$scaled" "$scaled" "$ICON_SRC" --out "$iconset/icon_${size}x${size}@2x.png" >/dev/null
|
||||
done
|
||||
iconutil -c icns "$iconset" -o "$1"
|
||||
}
|
||||
|
||||
make_ico() { # $1 = output .ico path
|
||||
[ -f "$ICON_SRC" ] || fail "missing icon source: $ICON_SRC"
|
||||
magick "$ICON_SRC" -define icon:auto-resize=256,128,64,48,32,16 "$1"
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------- macOS
|
||||
build_mac() {
|
||||
say "building macOS app"
|
||||
@@ -147,9 +173,21 @@ build_mac() {
|
||||
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $VERSION" "$plist" 2>/dev/null \
|
||||
|| /usr/libexec/PlistBuddy -c "Add :CFBundleVersion string $VERSION" "$plist"
|
||||
|
||||
if [ -f "$ROOT/assets/icon.icns" ]; then
|
||||
cp "$ROOT/assets/icon.icns" "$out_app/Contents/Resources/GameIcon.icns"
|
||||
# Brand the app icon. LÖVE.app resolves its icon through CFBundleIconName ->
|
||||
# Assets.car first, so overwriting the loose .icns files alone changes
|
||||
# nothing; the compiled asset catalog has to go and the plist has to fall
|
||||
# back to CFBundleIconFile.
|
||||
local icns="$ROOT/assets/icon.icns"
|
||||
if [ ! -f "$icns" ]; then
|
||||
icns="$WORK/GameIcon.icns"
|
||||
make_icns "$icns"
|
||||
fi
|
||||
cp "$icns" "$out_app/Contents/Resources/GameIcon.icns"
|
||||
cp "$icns" "$out_app/Contents/Resources/OS X AppIcon.icns"
|
||||
rm -f "$out_app/Contents/Resources/Assets.car"
|
||||
/usr/libexec/PlistBuddy -c "Delete :CFBundleIconName" "$plist" 2>/dev/null || true
|
||||
/usr/libexec/PlistBuddy -c "Set :CFBundleIconFile OS X AppIcon" "$plist" 2>/dev/null \
|
||||
|| /usr/libexec/PlistBuddy -c "Add :CFBundleIconFile string 'OS X AppIcon'" "$plist"
|
||||
|
||||
local id="$IDENTITY"
|
||||
if [ -z "$id" ]; then
|
||||
@@ -223,7 +261,47 @@ build_win() {
|
||||
cp "$love_dir"/*.dll "$out_dir"/
|
||||
cp "$love_dir"/license.txt "$out_dir"/ 2>/dev/null || true
|
||||
|
||||
cat "$love_dir/love.exe" "$LOVE_FILE" > "$out_dir/$APP_NAME.exe"
|
||||
# The exe's icon lives in love.exe's PE resources, so it must be patched
|
||||
# BEFORE the .love is appended: peresed rewrites the whole file and would
|
||||
# drop the fused bytes. peresed (pipx install pe_tools) has no .ico input,
|
||||
# only raw --set-resource, so split the .ico into RT_ICON blobs plus a
|
||||
# GRPICONDIR that reuses love.exe's existing resource ids (1..N, lang 1033).
|
||||
local ico="$WORK/$APP_NAME.ico"
|
||||
make_ico "$ico"
|
||||
if command -v peresed >/dev/null 2>&1; then
|
||||
local ico_parts="$WORK/ico-parts"
|
||||
rm -rf "$ico_parts"; mkdir -p "$ico_parts"
|
||||
python3 - "$ico" "$ico_parts" <<'PY'
|
||||
import struct, sys
|
||||
data = open(sys.argv[1], "rb").read()
|
||||
outdir = sys.argv[2]
|
||||
count = struct.unpack_from("<H", data, 4)[0]
|
||||
group = struct.pack("<HHH", 0, 1, count)
|
||||
for i in range(count):
|
||||
w, h, colors, res, planes, bpp, size, off = struct.unpack_from("<BBBBHHII", data, 6 + 16 * i)
|
||||
open("%s/icon_%d.bin" % (outdir, i + 1), "wb").write(data[off:off + size])
|
||||
group += struct.pack("<BBBBHHIH", w, h, colors, res, planes, bpp, size, i + 1)
|
||||
open(outdir + "/group.bin", "wb").write(group)
|
||||
print(count)
|
||||
PY
|
||||
local n_icons args=()
|
||||
n_icons=$(ls "$ico_parts" | grep -c '^icon_')
|
||||
for i in $(seq 1 "$n_icons"); do
|
||||
args+=(-R RT_ICON "#$i" 1033 "$ico_parts/icon_$i.bin")
|
||||
done
|
||||
args+=(-R RT_GROUP_ICON "#1" 1033 "$ico_parts/group.bin")
|
||||
local exe_branded="$WORK/love-branded.exe"
|
||||
cp "$love_dir/love.exe" "$exe_branded"
|
||||
if peresed "${args[@]}" "$exe_branded" >/dev/null; then
|
||||
cat "$exe_branded" "$LOVE_FILE" > "$out_dir/$APP_NAME.exe"
|
||||
else
|
||||
warn "peresed failed to patch the exe icon, shipping stock LÖVE icon"
|
||||
cat "$love_dir/love.exe" "$LOVE_FILE" > "$out_dir/$APP_NAME.exe"
|
||||
fi
|
||||
else
|
||||
warn "peresed not found (pipx install pe_tools), shipping stock LÖVE exe icon"
|
||||
cat "$love_dir/love.exe" "$LOVE_FILE" > "$out_dir/$APP_NAME.exe"
|
||||
fi
|
||||
|
||||
local zip_out="$DIST/win/$APP_NAME-win64.zip"
|
||||
rm -f "$zip_out"
|
||||
@@ -278,6 +356,15 @@ build_linux() {
|
||||
unsquashfs -q -no-xattrs -o "$sfs_offset" -d "$appdir" "$love_appimage" >/dev/null
|
||||
|
||||
cp "$LOVE_FILE" "$appdir/game.love"
|
||||
|
||||
# The .desktop's Icon=love resolves against the AppDir root by basename,
|
||||
# so drop the stock love.svg and provide our PNG under the same name;
|
||||
# .DirIcon is what appimaged/thumbnailers show for the file itself.
|
||||
[ -f "$ICON_SRC" ] || fail "missing icon source: $ICON_SRC"
|
||||
rm -f "$appdir/love.svg" "$appdir/.DirIcon"
|
||||
sips -z 512 512 "$ICON_SRC" --out "$appdir/love.png" >/dev/null
|
||||
cp "$appdir/love.png" "$appdir/.DirIcon"
|
||||
|
||||
sed -i '' 's|^#FUSE_PATH="$APPDIR/my_game.love"$|FUSE_PATH="$APPDIR/game.love"|' "$appdir/AppRun"
|
||||
grep -q '^FUSE_PATH="\$APPDIR/game.love"$' "$appdir/AppRun" \
|
||||
|| fail "failed to enable FUSE_PATH in AppRun (upstream AppRun changed?)"
|
||||
|
||||
@@ -41,6 +41,13 @@ function Game:load()
|
||||
-- render pipelines dispatch off the merged dataset; point them at the
|
||||
-- one the mods just merged into before anything can draw a frame
|
||||
require("src.render.Pipelines").install(Data)
|
||||
-- Same reason, same moment: TypeChart caches the merged type records in an
|
||||
-- upvalue, and until now only BattleState loaded it, on entering a battle.
|
||||
-- Every non-battle reader of a type -- the summary screen's TYPE1/TYPE2
|
||||
-- rows, the move-select TYPE/ box -- ran against an unloaded module and got
|
||||
-- the raw id back instead of the display name, so a translation could not
|
||||
-- reach them. Loading here means a type reads the same whoever asks first.
|
||||
require("src.battle.TypeChart").load(Data)
|
||||
|
||||
self.input = Input
|
||||
Input:init()
|
||||
|
||||
@@ -1079,7 +1079,11 @@ R.font = {
|
||||
f.rec{ seq = f.str, code = f.int(0) },
|
||||
f.rec{ file = f.opt(f.path), size = f.opt(f.int(1)),
|
||||
spacing = f.opt(f.num), yOffset = f.opt(f.num),
|
||||
bold = f.opt(f.bool) },
|
||||
bold = f.opt(f.bool),
|
||||
-- characters that keep their ROM tile instead of coming from the
|
||||
-- TTF: a string of them, or a list when a multi-character charmap
|
||||
-- sequence is meant (src/render/Font.lua)
|
||||
tiles = f.opt(f.union{ f.str, f.list(f.str) }) },
|
||||
},
|
||||
extra = function(id, value)
|
||||
if fontIsCharmap(id) then
|
||||
@@ -1092,7 +1096,7 @@ R.font = {
|
||||
elseif fontIsTtf(id) then
|
||||
-- every field optional: {} is "the bundled font at its native size"
|
||||
if value.image ~= nil or value.base ~= nil then
|
||||
return 'the "ttf" entry takes file/size/spacing/yOffset/bold, not a page'
|
||||
return 'the "ttf" entry takes file/size/spacing/yOffset/bold/tiles, not a page'
|
||||
end
|
||||
elseif value.image == nil or value.base == nil then
|
||||
return "a font page needs an image and a base"
|
||||
|
||||
@@ -55,6 +55,34 @@ local function pagesOf(def)
|
||||
return pages
|
||||
end
|
||||
|
||||
-- ttf.tiles as a lookup keyed by charmap sequence. Accepts a plain string
|
||||
-- ("0123456789"), which is split into UTF-8 characters, or a list of
|
||||
-- sequences ({ "0", "1", "<PK>" }) when a multi-character macro is meant.
|
||||
local function tileSet(spec)
|
||||
local set = {}
|
||||
if type(spec) == "table" then
|
||||
for _, seq in ipairs(spec) do set[tostring(seq)] = true end
|
||||
elseif type(spec) == "string" then
|
||||
-- split on UTF-8 lead bytes rather than utf8Decode, which this file
|
||||
-- declares further down and would be nil here
|
||||
local i, n = 1, #spec
|
||||
while i <= n do
|
||||
local last = i
|
||||
if spec:byte(i) >= 0xC0 then
|
||||
local k = i + 1
|
||||
while k <= n do
|
||||
local b = spec:byte(k)
|
||||
if b < 0x80 or b > 0xBF then break end
|
||||
last, k = k, k + 1
|
||||
end
|
||||
end
|
||||
set[spec:sub(i, last)] = true
|
||||
i = last + 1
|
||||
end
|
||||
end
|
||||
return set
|
||||
end
|
||||
|
||||
function Font.load(data)
|
||||
loadedFrom = data
|
||||
local def = data.font
|
||||
@@ -128,6 +156,15 @@ function Font.load(data)
|
||||
-- caps line up and descenders hang below as the GB font's own do
|
||||
yOffset = def.ttf.yOffset or (obj.getBaseline
|
||||
and (GLYPH - 1 - obj:getBaseline()) or (GLYPH - obj:getHeight())),
|
||||
-- Single characters that keep their ROM tile instead of coming from
|
||||
-- the TTF. A CJK translation sizes the font so a kana fills the 8px
|
||||
-- cell, which leaves Latin narrower than the tile font it replaces:
|
||||
-- the numbers in a right-aligned column (the party menu's ":L12" over
|
||||
-- "34/ 34") then no longer land where the 8px-per-character layout put
|
||||
-- them. Naming "0123456789" here keeps digits on the vanilla tiles --
|
||||
-- identical to the English build -- while kana still come from the
|
||||
-- font. Sequence keys, so "é" or a "<PK>" macro can be listed too.
|
||||
tiles = tileSet(def.ttf.tiles),
|
||||
widths = {}, chars = {},
|
||||
}
|
||||
else
|
||||
@@ -227,9 +264,10 @@ function Font.split(text)
|
||||
for _, entry in ipairs(candidates) do
|
||||
local len = #entry.seq
|
||||
if text:sub(i, i + len - 1) == entry.seq then
|
||||
if ttf then
|
||||
if ttf and not ttf.tiles[entry.seq] then
|
||||
-- single characters belong to the TTF; only multi-character
|
||||
-- sequences (ligatures, <PK> macros) keep their tile mapping
|
||||
-- sequences (ligatures, <PK> macros) keep their tile mapping,
|
||||
-- plus anything the mod named in ttf.tiles (see Font.load)
|
||||
local cp, last = utf8Decode(entry.seq, 1)
|
||||
if cp and last == len then break end
|
||||
end
|
||||
@@ -367,8 +405,18 @@ for key, code in pairs(Font.DEFAULT_BORDER) do Font.BORDER[key] = code end
|
||||
|
||||
-- Draw a Game Boy style bordered box in tile coordinates.
|
||||
function Font.drawBox(tx, ty, tw, th)
|
||||
-- The white interior is a fill, so it needs the color; everything after it
|
||||
-- is a glyph and needs the caller's. Restoring is not cosmetic: the tile
|
||||
-- pages are black glyphs on transparent, so they come out black whatever
|
||||
-- the color is, and leaking white here was invisible for as long as every
|
||||
-- glyph was a tile. TTF text is not immune -- it draws in the current
|
||||
-- color -- so a leaked white left every label printed after a box white on
|
||||
-- white. On the summary screen that erased ATTACK/DEFENSE/SPEED/SPECIAL
|
||||
-- and TYPE1/TYPE2 while the numbers beside them, still tiles, stayed put.
|
||||
local r, g, b, a = love.graphics.getColor()
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.rectangle("fill", tx * 8, ty * 8, tw * 8, th * 8)
|
||||
love.graphics.setColor(r, g, b, a)
|
||||
local B = Font.BORDER
|
||||
Font.drawCode(B.tl, tx * 8, ty * 8)
|
||||
Font.drawCode(B.tr, (tx + tw - 1) * 8, ty * 8)
|
||||
|
||||
@@ -53,6 +53,42 @@ T.eq(#Font.encode("'d"), 1, "and still consumes the whole sequence")
|
||||
T.eq(Font.encode("\227\129\130")[1], BASE + 0x3042,
|
||||
"kana with no charmap entry at all still gets a glyph")
|
||||
|
||||
-- ------------------------------------------------- ttf.tiles opts back out
|
||||
|
||||
-- A CJK translation sizes the font so a kana fills the 8px cell, which leaves
|
||||
-- Latin narrower than the tile font it replaces and pulls the numeric columns
|
||||
-- (the party menu's ":L12" over "34/ 34") out of line. ttf.tiles names the
|
||||
-- characters that keep their ROM tile anyway, so numbers stay identical to the
|
||||
-- English build while kana still come from the font.
|
||||
Font.load({ font = { charmap = CHARMAP, ttf = { tiles = "A" } } })
|
||||
T.eq(Font.encode("A")[1], 0x80, "a character in ttf.tiles keeps its tile glyph")
|
||||
T.eq(Font.advanceOf(0x80), 8, "and its 8px monospace advance with it")
|
||||
T.eq(Font.encode("\195\169")[1], BASE + 0xE9,
|
||||
"while everything else still routes to the TTF")
|
||||
|
||||
-- a list form, for naming a multi-character sequence explicitly
|
||||
Font.load({ font = { charmap = CHARMAP, ttf = { tiles = { "\195\169" } } } })
|
||||
T.eq(Font.encode("\195\169")[1], 0xBA,
|
||||
"the list form accepts a multi-byte character")
|
||||
T.eq(Font.encode("A")[1], BASE + 65, "and leaves the others on the TTF")
|
||||
|
||||
Font.load({ font = { charmap = CHARMAP, ttf = {} } })
|
||||
T.eq(Font.encode("A")[1], BASE + 65, "no tiles list means the TTF takes it back")
|
||||
|
||||
-- ------------------------------------------------- drawBox restores color
|
||||
|
||||
-- drawBox fills its interior white and used to leave the color that way.
|
||||
-- Tile pages are black glyphs on transparent, so they draw black whatever the
|
||||
-- color is and the leak stayed invisible for as long as every glyph was a
|
||||
-- tile. TTF text draws in the current color, so every label printed after a
|
||||
-- box came out white on white -- the summary screen lost ATTACK/DEFENSE/
|
||||
-- SPEED/SPECIAL and TYPE1/TYPE2 while the numbers beside them survived.
|
||||
love.graphics.setColor(0, 0, 0, 1)
|
||||
Font.drawBox(0, 0, 4, 4)
|
||||
local r, g, b, a = love.graphics.getColor()
|
||||
T.eq(("%s,%s,%s,%s"):format(r, g, b, a), "0,0,0,1",
|
||||
"drawBox leaves the caller's color alone")
|
||||
|
||||
-- metrics come straight from the font object (the stub: half the point
|
||||
-- size per codepoint, doubled from U+1000 up, mimicking Plain Pixel's
|
||||
-- single/double width split)
|
||||
@@ -161,6 +197,13 @@ do
|
||||
T.check(select(1, Schemas.check(spec, "font", "ttf",
|
||||
{ file = "mods/x/f.ttf", size = 11, spacing = 1, yOffset = -3 })) == true,
|
||||
"so is a fully tuned entry")
|
||||
T.check(select(1, Schemas.check(spec, "font", "ttf",
|
||||
{ size = 10, tiles = "0123456789/:" })) == true,
|
||||
"tiles takes a string of characters")
|
||||
T.check(select(1, Schemas.check(spec, "font", "ttf",
|
||||
{ tiles = { "0", "<PK>" } })) == true, "or a list of charmap sequences")
|
||||
T.check(Schemas.check(spec, "font", "ttf", { tiles = 10 }) == nil,
|
||||
"but not a number")
|
||||
T.check(Schemas.check(spec, "font", "ttf", { image = "x.png", base = 0x100 })
|
||||
== nil, "a page payload under the ttf id is refused")
|
||||
T.check(Schemas.check(spec, "font", "page", {}) == nil,
|
||||
|
||||