Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
TradePanelTextPlayback.cpp
Go to the documentation of this file.
2
3#include "FontRuntime.hpp"
7
8#include <string_view>
9#include <utility>
10
11namespace monopoly::tradeui
12{
13 namespace
14 {
15 constexpr std::uint32_t PanelWidth = 200;
16 constexpr std::uint32_t PanelHeight = 225;
17 constexpr int WrapWidth = 176;
18 constexpr std::uint32_t TradingMessageId = 2013;
19 constexpr std::uint32_t Trading2MessageId = 2014;
20 constexpr std::uint32_t PanelTextColour = 0x00DCDCDCU;
21
22 [[nodiscard]] std::expected<std::string, std::string> languageText(
23 const engine::SequencePlayback& playback, std::uint32_t messageId)
24 {
25 const auto resources = playback.resources();
26 if (!resources) return std::unexpected("Trade panel text has no resources");
27 const auto language = resources->language();
28 if (!language || !language->catalog)
29 return std::unexpected("Trade panel text has no language catalog");
30 const auto text = language->catalog->message(messageId);
31 if (!text) return std::unexpected(text.error().detail);
32 const auto encoded = fonts::transcodeUtf8(std::u16string_view(**text));
33 if (!encoded) return std::unexpected(encoded.error().detail);
34 return *encoded;
35 }
36
37 [[nodiscard]] data::LegacyBitmapRGBA8 blankPanel(int scale)
38 {
39 data::LegacyBitmapRGBA8 image{PanelWidth * scale, PanelHeight * scale, {}};
40 image.pixels.assign(std::size_t(image.width) * image.height * 4U, 0U);
41 return image;
42 }
43
44 [[nodiscard]] std::expected<void, std::string> printCenteredLines(
45 data::LegacyBitmapRGBA8& image,
46 fonts::Runtime& font,
47 const std::vector<std::string>& lines,
48 int& y)
49 {
50 int height{};
51 if (!lines.empty())
52 {
53 const auto first = font.measure(lines.front());
54 if (!first) return std::unexpected(first.error().detail);
55 height = first->height;
56 }
57 for (const auto& line : lines)
58 {
59 if (!line.empty())
60 {
61 const auto metrics = font.measure(line);
62 if (!metrics) return std::unexpected(metrics.error().detail);
63 const int x = (static_cast<int>(PanelWidth) - metrics->width) / 2;
64 const auto blitted = font.blitText(image, line, x, y, PanelTextColour);
65 if (!blitted) return std::unexpected(blitted.error().detail);
66 }
67 y += height;
68 }
69 return {};
70 }
71
72 // Measure/wrap at the authored logical width; rasterize accepted lines
73 // with private font faces so high resolution never mutates the shared font.
74 // A fit failure leaves the complete native panel available, rather than
75 // publishing a modern bitmap with silently clipped instructions.
76 [[nodiscard]] std::expected<bool, std::string> printModernParagraph(
77 data::LegacyBitmapRGBA8& image, fonts::Runtime& font,
78 std::string_view text, int size, int weight, int& y)
79 {
80 const auto sized = font.setSize(size);
81 if (!sized) return std::unexpected(sized.error().detail);
82 font.setWeight(weight); font.setUnderline(false);
83 font.setItalic(false); font.setStrikeOut(false);
84 const auto lines = font.wrap(text, WrapWidth);
85 if (!lines) return std::unexpected(lines.error().detail);
86 int height{};
87 if (!lines->empty())
88 {
89 const auto metrics = font.measure(lines->front());
90 if (!metrics) return std::unexpected(metrics.error().detail);
91 height = metrics->height;
92 }
93 for (const auto& line : *lines)
94 {
95 if (!line.empty())
96 {
97 const auto raster = font.renderPresentation(
98 line, 0x00D3EBF5U, size * 3, weight, false, false, false, true);
99 if (!raster) return std::unexpected(raster.error().detail);
100 if (raster->width > image.width || y < 0 ||
101 std::uint64_t(y) * 3 + raster->height > image.height)
102 return false;
103 const int x = (static_cast<int>(image.width) - static_cast<int>(raster->width)) / 2;
104 const auto blitted = data::blitStraightRGBA8(
106 if (!blitted) return std::unexpected(blitted.error());
107 }
108 y += height;
109 }
110 return true;
111 }
112
113 struct RestoreDefaultFont final
114 {
115 fonts::Runtime* runtime{};
116 std::optional<fonts::Settings> caller;
118 {
119 if (runtime != nullptr)
120 {
121 if (!caller) (void)runtime->restoreSettings(0);
122 else
123 {
124 (void)runtime->setSize(caller->size); runtime->setWeight(caller->weight);
125 runtime->setItalic(caller->italic); runtime->setUnderline(caller->underline);
126 runtime->setStrikeOut(caller->strikeOut);
127 }
128 }
129 }
130 };
131 }
132
133 std::expected<void, std::string> PanelTextPlayback::sync(
134 const State& state,
138 {
140 state.playerA < rules::MaxPlayers;
141 if (!desiredVisible)
142 {
143 if (!visible_) return {};
144 if (!playback.commands().enqueue(sequence::StopSequenceCommand{
145 *surface_, TradePanelTextPriority, false}))
146 return std::unexpected("validated Trade panel-text stop rejected");
147 visible_ = false;
148 return {};
149 }
150 if (fontRuntime == nullptr || !fontRuntime->ready())
151 return std::unexpected("Trade panel text font runtime is unavailable");
152 if (!surface_)
153 {
154 const auto created = playback.runtimeBitmaps().create(
155 PanelWidth, PanelHeight, true);
156 if (!created) return std::unexpected(created.error());
157 surface_ = *created;
158 }
159
161 if (!trading) return std::unexpected(trading.error());
163 if (!trading2) return std::unexpected(trading2.error());
164 const auto resources = playback.resources();
165 modernAA = modernAA && resources &&
166 resources->context().board == data::BoardEdition::Usa &&
167 resources->context().language == data::LanguageId::EnglishUs;
168 const std::string key = *trading + '\n' + *trading2;
169 const auto callerSettings = fontRuntime->settings();
170
171 const bool contentChanged =
172 !contentKey_ || *contentKey_ != key || modernAA_ != modernAA ||
173 !fontSettings_ || *fontSettings_ != callerSettings;
174 if (contentChanged)
175 {
176 RestoreDefaultFont restore{fontRuntime, modernAA ? std::optional{callerSettings} : std::nullopt};
177 bool modernFits = modernAA;
178 auto image = blankPanel(modernAA ? 3 : 1);
179 if (modernAA)
180 {
181 int y = 14;
182 const auto title = printModernParagraph(image, *fontRuntime, *trading, 12, 600, y);
183 if (!title) return std::unexpected(title.error());
184 y += 3;
185 const auto body = *title ? printModernParagraph(image, *fontRuntime, *trading2, 10, 400, y)
186 : std::expected<bool, std::string>{false};
187 if (!body) return std::unexpected(body.error());
188 modernFits = *title && *body;
189 }
190 if (!modernFits)
191 {
192 image = blankPanel(1);
193 // Match the original renderer's inherited italic/strike settings
194 // even after a rejected modern layout has measured its fonts.
195 fontRuntime->setItalic(callerSettings.italic);
196 fontRuntime->setStrikeOut(callerSettings.strikeOut);
197 auto sized = fontRuntime->setSize(12);
198 if (!sized) return std::unexpected(sized.error().detail);
199 fontRuntime->setWeight(700);
200 fontRuntime->setUnderline(true);
201 const auto firstLines = fontRuntime->wrap(*trading, WrapWidth);
202 if (!firstLines) return std::unexpected(firstLines.error().detail);
203 int y = 14;
204 if (auto printed = printCenteredLines(
206 return printed;
207 y += 3;
208
209 sized = fontRuntime->setSize(9);
210 if (!sized) return std::unexpected(sized.error().detail);
211 fontRuntime->setWeight(700);
212 fontRuntime->setUnderline(false);
213 const auto secondLines = fontRuntime->wrap(*trading2, WrapWidth);
214 if (!secondLines) return std::unexpected(secondLines.error().detail);
215 if (auto printed = printCenteredLines(
217 return printed;
218 }
219
220 const auto updated = playback.runtimeBitmaps().update(
221 *surface_, std::move(image), modernFits ? std::optional<std::array<float,4>>{{0,0,float(PanelWidth),float(PanelHeight)}} : std::nullopt, modernFits);
222 if (!updated) return std::unexpected(updated.error());
223 contentKey_ = key;
224 modernAA_ = modernAA;
225 fontSettings_ = callerSettings;
226 }
227
228 if (visible_)
229 {
230 if (contentChanged)
231 {
232 const auto forced = playback.forceRedraw(
233 *surface_, TradePanelTextPriority);
234 if (!forced) return forced;
235 }
236 return {};
237 }
238 if (playback.commands().pendingCount() >=
240 return std::unexpected(
241 "sequence command queue cannot fit Trade panel-text start");
244 if (!program) return std::unexpected(program.error().detail);
245 if (!playback.commands().enqueue(sequence::StartSequenceCommand{
246 *program, TradePanelTextPriority, {},
248 return std::unexpected("validated Trade panel-text start rejected");
249 visible_ = true;
250 return {};
251 }
252
253 void PanelTextPlayback::reset() noexcept
254 {
255 surface_.reset();
256 contentKey_.reset();
257 visible_ = false;
258 modernAA_ = false; fontSettings_.reset();
259 }
260}
std::string_view text
LegacyBitmapRGBA8 image
fonts::Runtime & font
fonts::Metrics metrics
fonts::Runtime & runtime
std::uint64_t key
std::uint32_t height
unsigned state
unsigned index
data::DataTag first
std::optional< fonts::Settings > caller
static std::expected< std::shared_ptr< const SequenceProgram >, RuntimeError > rawBitmap(data::DataId id, data::LegacyDataType sourceType, DescriptionLimits limits={})
std::expected< void, std::string > sync(const State &state, display::Screen2D desiredView, fonts::Runtime *fontRuntime, engine::SequencePlayback &playback, bool modernAA=false)
std::expected< void, std::string > blitStraightRGBA8(LegacyBitmapRGBA8 &destination, const LegacyBitmapRGBA8 &source, std::int32_t destinationX, std::int32_t destinationY, BitmapBlitMode mode)
std::expected< std::string, Error > transcodeUtf8(std::u16string_view text)
constexpr PlayerNumber MaxPlayers
Definition RuleTypes.hpp:14
SequenceTransform moveXYTransform(std::int32_t x, std::int32_t y) noexcept
std::shared_ptr< const data::ResourceSnapshot > resources() noexcept
constexpr std::uint16_t TradePanelTextPriority