diff --git a/Graphics/dan_select/yellow_box/exam_combo.png b/Graphics/dan_select/yellow_box/exam_combo.png index 82ecd26..d7264a9 100644 --- a/Graphics/dan_select/yellow_box/exam_combo.png +++ b/Graphics/dan_select/yellow_box/exam_combo.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2455c80a86f4003cfc32f0f08227bf2d96378bb72be5d483db86932010c471a3 -size 5888 +oid sha256:1bff70c16e8317b31c917e471c0b8e142e5fd2c61369bf631778f46559a10429 +size 10824 diff --git a/Graphics/dan_select/yellow_box/exam_gauge.png b/Graphics/dan_select/yellow_box/exam_gauge.png index 45b3e80..576ab60 100644 --- a/Graphics/dan_select/yellow_box/exam_gauge.png +++ b/Graphics/dan_select/yellow_box/exam_gauge.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b7c40a55e76b262c1497d290ee3b457d1c3f0eae47d91f50563d011ae4b6997 -size 3959 +oid sha256:8b39d7613a1d267671c38bbfc5c1295650f3f836f2d2ce326a591ccf963d5c78 +size 6892 diff --git a/Graphics/dan_select/yellow_box/exam_hit.png b/Graphics/dan_select/yellow_box/exam_hit.png index b741803..365644f 100644 --- a/Graphics/dan_select/yellow_box/exam_hit.png +++ b/Graphics/dan_select/yellow_box/exam_hit.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3b186760910e39d5e7f2f87f1aa26037dab39116697fbe517499ade1f1e64553 -size 4346 +oid sha256:3298eec052791aa93a1b55d4a68f0f60346885465b4382226254cdb6237f4562 +size 7408 diff --git a/Graphics/dan_select/yellow_box/exam_judgebad.png b/Graphics/dan_select/yellow_box/exam_judgebad.png index 34dfa8f..112e36f 100644 --- a/Graphics/dan_select/yellow_box/exam_judgebad.png +++ b/Graphics/dan_select/yellow_box/exam_judgebad.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4425a0a162803b976266e153118ca93f8a82b128ec77c6a1316f5d2544b0b7fb -size 4141 +oid sha256:f6a859d2565cfe7cc12d1e8ae5030a2175839505c7d4cf325f4114f517c1b068 +size 7584 diff --git a/Graphics/dan_select/yellow_box/exam_judgegood.png b/Graphics/dan_select/yellow_box/exam_judgegood.png index 73d837b..02bcd3a 100644 --- a/Graphics/dan_select/yellow_box/exam_judgegood.png +++ b/Graphics/dan_select/yellow_box/exam_judgegood.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:748b89b1d19ee060581238d824a00bea5e8dd93dc0a3118a8f34dcd1216af1c6 -size 3294 +oid sha256:02732c307ce94e16badb7097e4e1c00284ca0168daad756a895a7f6759e2fe66 +size 5915 diff --git a/Graphics/dan_select/yellow_box/exam_judgeperfect.png b/Graphics/dan_select/yellow_box/exam_judgeperfect.png index c814245..aa1d4cb 100644 --- a/Graphics/dan_select/yellow_box/exam_judgeperfect.png +++ b/Graphics/dan_select/yellow_box/exam_judgeperfect.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a30d3014394dde6c4764f800ddf178681cc038d35e18d33dc0f2738638edcb2 -size 3381 +oid sha256:97a88b44d48582c936f8554d962a73c3ba2f107ac93933962c605a9d29068e09 +size 6114 diff --git a/Graphics/dan_select/yellow_box/exam_roll.png b/Graphics/dan_select/yellow_box/exam_roll.png index 57b7c49..ebe57bc 100644 --- a/Graphics/dan_select/yellow_box/exam_roll.png +++ b/Graphics/dan_select/yellow_box/exam_roll.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:169352110d05d5edf708f9397e5e2a09610ac8bfb2c99491b2be120d140d141d -size 3684 +oid sha256:e987122b780b2121c5d6fc50c38234c53fcd4dcf0fae578cf02bdcecb122130f +size 6536 diff --git a/Graphics/dan_select/yellow_box/exam_score.png b/Graphics/dan_select/yellow_box/exam_score.png index c3f7bd8..5895e9a 100644 --- a/Graphics/dan_select/yellow_box/exam_score.png +++ b/Graphics/dan_select/yellow_box/exam_score.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f910fbdfa1d7a342fcfa7a6ceba1cac5a510540beb0c9ea8bb466e2d4d376d86 -size 2655 +oid sha256:9fc89499a1be002343efbcf6f57cca44be72e79fe13e027e09003e7501cff527 +size 4813 diff --git a/Graphics/dan_select/yellow_box/texture.json b/Graphics/dan_select/yellow_box/texture.json index df12103..a2604f8 100644 --- a/Graphics/dan_select/yellow_box/texture.json +++ b/Graphics/dan_select/yellow_box/texture.json @@ -83,12 +83,12 @@ "y": 186 }, "exam_header": { - "x": 420, - "y": 568 + "x": 421, + "y": 563 }, "exam_frame": { "x": 418, - "y": 601 + "y": 596 }, "exam_box_top_left": { "x": 420, diff --git a/Graphics/skin_config.json b/Graphics/skin_config.json index fe6847f..f1ca5c4 100644 --- a/Graphics/skin_config.json +++ b/Graphics/skin_config.json @@ -628,7 +628,8 @@ "x": 632, "y": 262, "height": 60, - "font_size": 36 + "font_size": 36, + "outline": 2.6 }, "dan_subtitle": { "x": 632, @@ -837,7 +838,7 @@ "dan_back_label": { "x": 0, "y": 89, - "width": 60, + "width": 52, "height": 88, "font_size": 42, "text": { @@ -952,11 +953,11 @@ }, "dan_exam_gauge_box": { "x": 436, - "y": 615 + "y": 610 }, "dan_exam_score_box": { "x": 769, - "y": 615, + "y": 610, "height": 132 }, "dan_exam_seg": { @@ -965,16 +966,16 @@ "width": 327 }, "dan_select_exam_border_text": { - "x": 319, - "y": 54, - "font_size": 28, - "outline": 2.0 + "x": 309, + "y": 55, + "font_size": 25, + "outline": 2.6 }, "dan_select_gauge_border_text": { - "x": 300, - "y": 55, - "font_size": 36, - "outline": 2.6666666666666665 + "x": 291, + "y": 67, + "font_size": 32, + "outline": 3.1 }, "dan_exam_value": { "x": 185, @@ -982,7 +983,7 @@ }, "dan_exam_label_pill": { "x": 28, - "y": 2 + "y": -1 }, "dan_exam_value_left": { "x": 528 diff --git a/Scripts/dan_select/dan_select.lua b/Scripts/dan_select/dan_select.lua index 9ec1898..4ece6f8 100644 --- a/Scripts/dan_select/dan_select.lua +++ b/Scripts/dan_select/dan_select.lua @@ -84,6 +84,123 @@ local function draw_level(level, x, y, f) tex.draw_texture(id, { frame = math.min(level, 11) - 1, x = x, y = y, fade = f }) end +-- The course rows are set as the cabinet's: the song title alone (no subtitle line), and the +-- exam values with narrow digits beside full-width 以上/未満 and a little room between them. +-- The skin has one font, so each text is cut into runs of ASCII and of everything else, each +-- run made on its own and scaled as it is drawn, the runs butted together. The parent is handed +-- the box through a view whose song_subtitle_text gives nothing and whose song_title_text gives +-- such a title, and the captions it makes for the exam values are made the same way. Only CJK +-- languages are rescaled; the skin's font for the others is narrow already. +local function cjk_language() + local l = tex.language() or "" + return l:find("^ja") or l:find("^zh") or l:find("^ko") or l == "tw" or l == "cn" +end +local TITLE_CJK = { latin = 1, cjk = 1.02, sy = 1, dy = 0, max_w = 1100 } +local VALUE_CJK = { latin = 0.85, cjk = 1.0, sy = 1.0, dy = 0, gap = 8 } +local TITLE_PLAIN = { latin = 1, cjk = 1, sy = 1, dy = 0, max_w = 1100 } +local VALUE_PLAIN = { latin = 1, cjk = 1, sy = 1, dy = 0 } + +local function runs_of(str) + local runs, cur, ascii = {}, {}, nil + for ch in str:gmatch("[%z\1-\127\194-\244][\128-\191]*") do + local a = ch:byte() < 128 + if ascii ~= nil and a ~= ascii then + runs[#runs + 1] = { text = table.concat(cur), ascii = ascii } + cur = {} + end + cur[#cur + 1] = ch + ascii = a + end + if #cur > 0 then runs[#runs + 1] = { text = table.concat(cur), ascii = ascii } end + return runs +end + +-- create: text.create_raw_text as it was before any wrapping. The parts' sizes are read when +-- they are used, since a text's texture (and size) may still be building when it is made. +local function mixed_text(create, str, size, color, outline_color, outline, spacing, style) + local pad = math.floor((outline or 0) * tex.get_screen_scale()) + 2 + local parts = {} + for _, r in ipairs(runs_of(str)) do + parts[#parts + 1] = { t = create(r.text, size, color, outline_color, false, outline, spacing), + sx = r.ascii and style.latin or style.cjk } + end + local function layout() + local function span(k) + local x = 0 + for n, q in ipairs(parts) do + q.x = x + local next_sx = parts[n + 1] and parts[n + 1].sx * k or 0 + x = x + (q.t.width - pad) * q.sx * k - pad * next_sx + if parts[n + 1] then x = x + (style.gap or 0) * k end + end + local last = parts[#parts] + return last and (last.x + last.t.width * last.sx * k) or 0 + end + local k, w = 1, span(1) + if style.max_w and w > style.max_w then + k = style.max_w / w + w = span(k) + end + local h = 0 + for _, q in ipairs(parts) do h = math.max(h, q.t.height) end + return k, w, h * style.sy + end + return setmetatable({ + text = str, + draw = function(_, p) + local k = layout() + for _, q in ipairs(parts) do + local sx = q.sx * k + q.t:draw({ x = p.x + q.x, y = (p.y or 0) + style.dy, fade = p.fade, + x2 = q.t.width * (sx - 1), y2 = q.t.height * (style.sy - 1) }) + end + end, + }, { + __index = function(_, key) + if key == "width" then local _, w = layout(); return w end + if key == "height" then local _, _, h = layout(); return h end + end, + }) +end + +local title_cache = {} -- by string, size and outline: texts are built once, not per frame +local raw_create = text.create_raw_text -- the engine's, before draw_dan_open wraps it + +local function course_view(box) + local titles = {} + return setmetatable({}, { + __index = function(_, k) + if k == "song_subtitle_text" then return function() return nil end end + if k == "song_title_text" then + return function(_, i) + if titles[i] == nil then + local t = box:song_title_text(i) + local ok, str = pcall(function() return t and t.text end) + local td = tex.get_skin_config("dan_title") + if t and ok and type(str) == "string" and str ~= "" and td then + local ol = td.outline >= 0 and td.outline or 5 + local key = str .. "\0" .. td.font_size .. "\0" .. ol + title_cache[key] = title_cache[key] + or mixed_text(raw_create, str, td.font_size, + { 255, 255, 255, 255 }, { 0, 0, 0, 255 }, ol, 2, + cjk_language() and TITLE_CJK or TITLE_PLAIN) + titles[i] = title_cache[key] + else + titles[i] = t or false -- an engine without OutlinedText.text + end + end + return titles[i] or nil + end + end + local v = box[k] + if type(v) == "function" then + return function(_, ...) return v(box, ...) end + end + return v + end, + }) +end + -- The course's clear stamp (yellow_box/clear_mark) sits over the dan plate and Don, as the -- cabinet's stamp_detail does (it is above don_1p). The parent draws it with the box, under -- both, so it is held back there and drawn with the title, which the engine draws after @@ -109,7 +226,16 @@ if parent_open then end return draw(id, p) end - local ok, err = pcall(parent_open, self, box) + local create = text.create_raw_text + text.create_raw_text = function(str, size, color, outline_color, vertical, outline, spacing, ...) + if vertical or type(str) ~= "string" then -- only the exam value captions + return raw_create(str, size, color, outline_color, vertical, outline, spacing, ...) + end + return mixed_text(raw_create, str, size, color, outline_color, outline, spacing, + cjk_language() and VALUE_CJK or VALUE_PLAIN) + end + local ok, err = pcall(parent_open, self, course_view(box)) + text.create_raw_text = create tex.draw_texture = draw if not ok then error(err, 0) end local f = box.open_fade.attribute @@ -139,17 +265,21 @@ end -- across, squeezed to the chip, for other languages. local CJK = { ja = true, zh = true, ko = true, tw = true, cn = true } local back_label +local BACK_ACROSS_SIZE = 0.7 -- size of a label set across, against dan_back_label.font_size local function get_back_label() if back_label == nil then local cfg = tex.get_skin_config("dan_back_label") if not cfg then back_label = false; return false end local lang = tex.language() local vertical = CJK[lang] or CJK[lang:sub(1, 2)] or false + -- a word set across has only the chip's width, so it takes a smaller size than the + -- vertical labels rather than being squeezed hard + local size = vertical and cfg.font_size or math.floor(cfg.font_size * BACK_ACROSS_SIZE + 0.5) back_label = { cfg = cfg, vertical = vertical, - main = text.create_raw_text(tex.skin_text("dan_back_label"), cfg.font_size, - { 0, 0, 0, 255 }, { 0, 0, 0, 255 }, vertical, 0, 2), + main = raw_create(tex.skin_text("dan_back_label"), size, + { 0, 0, 0, 255 }, { 0, 0, 0, 255 }, vertical, 0, 2), } end return back_label