1 // -*- Mode: C++; tab-width:2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 // vi:tw=80:et:ts=2:sts=2
3 //
4 // -----------------------------------------------------------------------
5 //
6 // This file is part of RLVM, a RealLive virtual machine clone.
7 //
8 // -----------------------------------------------------------------------
9 //
10 // Copyright (C) 2007 Elliot Glaysher
11 //
12 // This program is free software; you can redistribute it and/or modify
13 // it under the terms of the GNU General Public License as published by
14 // the Free Software Foundation; either version 3 of the License, or
15 // (at your option) any later version.
16 //
17 // This program is distributed in the hope that it will be useful,
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 // GNU General Public License for more details.
21 //
22 // You should have received a copy of the GNU General Public License
23 // along with this program; if not, write to the Free Software
24 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
25 //
26 // -----------------------------------------------------------------------
27
28 #include "systems/base/text_page.h"
29
30 #include <algorithm>
31 #include <string>
32
33 #include "libreallive/gameexe.h"
34 #include "machine/rlmachine.h"
35 #include "systems/base/system.h"
36 #include "systems/base/text_system.h"
37 #include "systems/base/text_window.h"
38 #include "utf8cpp/utf8.h"
39 #include "utilities/exception.h"
40 #include "utilities/string_utilities.h"
41
42 using std::bind;
43 using std::ref;
44 using std::placeholders::_1;
45 using std::placeholders::_2;
46
47 // Represents the various commands.
48 enum CommandType {
49 TYPE_CHARACTERS,
50 TYPE_NAME,
51 TYPE_KOE_MARKER,
52 TYPE_HARD_BREAK,
53 TYPE_SET_INDENTATION,
54 TYPE_RESET_INDENTATION,
55 TYPE_FONT_COLOUR,
56 TYPE_DEFAULT_FONT_SIZE,
57 TYPE_FONT_SIZE,
58 TYPE_RUBY_BEGIN,
59 TYPE_RUBY_END,
60 TYPE_SET_INSERTION_X,
61 TYPE_SET_INSERTION_Y,
62 TYPE_OFFSET_INSERTION_X,
63 TYPE_OFFSET_INSERTION_Y,
64 TYPE_FACE_OPEN,
65 TYPE_FACE_CLOSE,
66 TYPE_NEXT_CHAR_IS_ITALIC,
67 };
68
69 // Storage for each command.
70 struct TextPage::Command {
71 explicit Command(CommandType type);
72 Command(CommandType type, int one);
73 Command(CommandType type, const std::string& one);
74 Command(CommandType type, const std::string& one, const std::string& two);
75 Command(CommandType type, const std::string& one, int two);
76 Command(const Command& rhs);
77 ~Command();
78
79 enum CommandType command;
80 union {
81 // TYPE_CHARACTERS
82 std::string characters;
83
84 // TYPE_NAME
85 struct {
86 std::string name;
87 std::string next_char;
88 } name;
89
90 // TYPE_KOE_MARKER
91 int koe_id;
92
93 // TYPE_FONT_COLOUR
94 int font_colour;
95
96 // TYPE_FONT_SIZE
97 int font_size;
98
99 // TYPE_RUBY_END
100 std::string ruby_text;
101
102 // TYPE_SET_INSERTION_X
103 int set_insertion_x;
104
105 // TYPE_SET_INSERTION_Y
106 int set_insertion_y;
107
108 // TYPE_OFFSET_INSERTION_X
109 int offset_insertion_x;
110
111 // TYPE_OFFSET_INSERTION_Y
112 int offset_insertion_y;
113
114 // TYPE_FACE_OPEN
115 struct {
116 std::string filename;
117 int index;
118 } face_open;
119
120 // TYPE_FACE_CLOSE
121 int face_close;
122
123 // For the empty types:
124 // TYPE_HARD_BREAK
125 // TYPE_SET_INDENTATION
126 // TYPE_RESET_INDENTATION
127 // TYPE_DEFAULT_FONT_SIZE
128 // TYPE_RUBY_BEGIN
129 int empty;
130 };
131 };
132
Command(CommandType type)133 TextPage::Command::Command(CommandType type)
134 : command(type) {
135 switch (type) {
136 case TYPE_HARD_BREAK:
137 case TYPE_SET_INDENTATION:
138 case TYPE_RESET_INDENTATION:
139 case TYPE_DEFAULT_FONT_SIZE:
140 case TYPE_RUBY_BEGIN:
141 case TYPE_NEXT_CHAR_IS_ITALIC:
142 empty = 0;
143 break;
144 case TYPE_CHARACTERS:
145 new (&characters) std::string;
146 break;
147 default:
148 throw rlvm::Exception("Incorrect arity");
149 }
150 }
151
Command(CommandType type,int one)152 TextPage::Command::Command(CommandType type, int one)
153 : command(type) {
154 switch (type) {
155 case TYPE_KOE_MARKER:
156 koe_id = one;
157 break;
158 case TYPE_FONT_COLOUR:
159 font_colour = one;
160 break;
161 case TYPE_FONT_SIZE:
162 font_size = one;
163 break;
164 case TYPE_SET_INSERTION_X:
165 set_insertion_x = one;
166 break;
167 case TYPE_SET_INSERTION_Y:
168 set_insertion_y = one;
169 break;
170 case TYPE_OFFSET_INSERTION_X:
171 offset_insertion_x = one;
172 break;
173 case TYPE_OFFSET_INSERTION_Y:
174 offset_insertion_y = one;
175 break;
176 case TYPE_FACE_CLOSE:
177 face_close = one;
178 break;
179 default:
180 throw rlvm::Exception("Incorrect arity");
181 }
182 }
183
Command(CommandType type,const std::string & one)184 TextPage::Command::Command(CommandType type, const std::string& one)
185 : command(type) {
186 switch (type) {
187 case TYPE_RUBY_END:
188 new (&ruby_text) std::string(one);
189 break;
190 default:
191 throw rlvm::Exception("Incorrect arity");
192 }
193 }
194
Command(CommandType type,const std::string & one,const std::string & two)195 TextPage::Command::Command(CommandType type,
196 const std::string& one,
197 const std::string& two)
198 : command(type) {
199 switch (type) {
200 case TYPE_NAME:
201 new (&name.name) std::string(one);
202 new (&name.next_char) std::string(two);
203 break;
204 default:
205 throw rlvm::Exception("Incorrect arity");
206 }
207 }
208
Command(CommandType type,const std::string & one,int two)209 TextPage::Command::Command(CommandType type,
210 const std::string& one,
211 int two)
212 : command(type) {
213 switch (type) {
214 case TYPE_FACE_OPEN:
215 new (&face_open.filename) std::string(one);
216 face_open.index = two;
217 break;
218 default:
219 throw rlvm::Exception("Incorrect arity");
220 }
221 }
222
Command(const Command & rhs)223 TextPage::Command::Command(const Command& rhs)
224 : command(rhs.command) {
225 switch (rhs.command) {
226 case TYPE_HARD_BREAK:
227 case TYPE_SET_INDENTATION:
228 case TYPE_RESET_INDENTATION:
229 case TYPE_DEFAULT_FONT_SIZE:
230 case TYPE_RUBY_BEGIN:
231 case TYPE_NEXT_CHAR_IS_ITALIC:
232 empty = 0;
233 break;
234 case TYPE_KOE_MARKER:
235 koe_id = rhs.koe_id;
236 break;
237 case TYPE_FONT_COLOUR:
238 font_colour = rhs.font_colour;
239 break;
240 case TYPE_FONT_SIZE:
241 font_size = rhs.font_size;
242 break;
243 case TYPE_SET_INSERTION_X:
244 set_insertion_x = rhs.set_insertion_x;
245 break;
246 case TYPE_SET_INSERTION_Y:
247 set_insertion_y = rhs.set_insertion_y;
248 break;
249 case TYPE_OFFSET_INSERTION_X:
250 offset_insertion_x = rhs.offset_insertion_x;
251 break;
252 case TYPE_OFFSET_INSERTION_Y:
253 offset_insertion_y = rhs.offset_insertion_y;
254 break;
255 case TYPE_FACE_CLOSE:
256 face_close = rhs.face_close;
257 break;
258 case TYPE_CHARACTERS:
259 new (&characters) std::string(rhs.characters);
260 break;
261 case TYPE_RUBY_END:
262 new (&ruby_text) std::string(rhs.ruby_text);
263 break;
264 case TYPE_NAME:
265 new (&name.name) std::string(rhs.name.name);
266 new (&name.next_char) std::string(rhs.name.next_char);
267 break;
268 case TYPE_FACE_OPEN:
269 command = TYPE_FACE_OPEN;
270 new (&face_open.filename) std::string(rhs.face_open.filename);
271 face_open.index = rhs.face_open.index;
272 break;
273 }
274 }
275
~Command()276 TextPage::Command::~Command() {
277 // Needed to get around a quirk of the language
278 using string_type = std::string;
279
280 switch (command) {
281 case TYPE_CHARACTERS:
282 characters.~string_type();
283 break;
284 case TYPE_RUBY_END:
285 ruby_text.~string_type();
286 break;
287 case TYPE_NAME:
288 name.name.~string_type();
289 name.next_char.~string_type();
290 break;
291 case TYPE_FACE_OPEN:
292 face_open.filename.~string_type();
293 break;
294 default:
295 break;
296 }
297 }
298
299 // -----------------------------------------------------------------------
300 // TextPage
301 // -----------------------------------------------------------------------
302
TextPage(System & system,int window_num)303 TextPage::TextPage(System& system, int window_num)
304 : system_(&system),
305 window_num_(window_num),
306 number_of_chars_on_page_(0),
307 in_ruby_gloss_(false) {
308 }
309
310 TextPage::TextPage(const TextPage& rhs) = default;
311
312 TextPage::TextPage(TextPage&& rhs) = default;
313
~TextPage()314 TextPage::~TextPage() {}
315
Replay(bool is_active_page)316 void TextPage::Replay(bool is_active_page) {
317 // Reset the font color.
318 if (!is_active_page) {
319 Gameexe& gexe = system_->gameexe();
320 GameexeInterpretObject colour(gexe("COLOR_TABLE", 254));
321 if (colour.Exists()) {
322 system_->text().GetTextWindow(window_num_)->SetFontColor(colour);
323 }
324 }
325
326 for_each(elements_to_replay_.begin(),
327 elements_to_replay_.end(),
328 [&](Command& c) { RunTextPageCommand(c, is_active_page); });
329 }
330
331 // ------------------------------------------------- [ Public operations ]
332
Character(const string & current,const string & rest)333 bool TextPage::Character(const string& current, const string& rest) {
334 bool rendered = CharacterImpl(current, rest);
335
336 if (rendered) {
337 if (elements_to_replay_.size() == 0 ||
338 elements_to_replay_.back().command != TYPE_CHARACTERS) {
339 elements_to_replay_.emplace_back(TYPE_CHARACTERS);
340 }
341
342 elements_to_replay_.back().characters.append(current);
343
344 number_of_chars_on_page_++;
345 }
346
347 return rendered;
348 }
349
Name(const string & name,const string & next_char)350 void TextPage::Name(const string& name, const string& next_char) {
351 AddAction(Command(TYPE_NAME, name, next_char));
352 number_of_chars_on_page_++;
353 }
354
KoeMarker(int id)355 void TextPage::KoeMarker(int id) {
356 AddAction(Command(TYPE_KOE_MARKER, id));
357 }
358
HardBrake()359 void TextPage::HardBrake() {
360 AddAction(Command(TYPE_HARD_BREAK));
361 }
362
SetIndentation()363 void TextPage::SetIndentation() {
364 AddAction(Command(TYPE_SET_INDENTATION));
365 }
366
ResetIndentation()367 void TextPage::ResetIndentation() {
368 AddAction(Command(TYPE_RESET_INDENTATION));
369 }
370
FontColour(int colour)371 void TextPage::FontColour(int colour) {
372 AddAction(Command(TYPE_FONT_COLOUR, colour));
373 }
374
DefaultFontSize()375 void TextPage::DefaultFontSize() {
376 AddAction(Command(TYPE_DEFAULT_FONT_SIZE));
377 }
378
FontSize(const int size)379 void TextPage::FontSize(const int size) {
380 AddAction(Command(TYPE_FONT_SIZE, size));
381 }
382
MarkRubyBegin()383 void TextPage::MarkRubyBegin() {
384 AddAction(Command(TYPE_RUBY_BEGIN));
385 }
386
DisplayRubyText(const std::string & utf8str)387 void TextPage::DisplayRubyText(const std::string& utf8str) {
388 AddAction(Command(TYPE_RUBY_END, utf8str));
389 }
390
SetInsertionPointX(int x)391 void TextPage::SetInsertionPointX(int x) {
392 AddAction(Command(TYPE_SET_INSERTION_X, x));
393 }
394
SetInsertionPointY(int y)395 void TextPage::SetInsertionPointY(int y) {
396 AddAction(Command(TYPE_SET_INSERTION_Y, y));
397 }
398
Offset_insertion_point_x(int offset)399 void TextPage::Offset_insertion_point_x(int offset) {
400 AddAction(Command(TYPE_OFFSET_INSERTION_X, offset));
401 }
402
Offset_insertion_point_y(int offset)403 void TextPage::Offset_insertion_point_y(int offset) {
404 AddAction(Command(TYPE_OFFSET_INSERTION_Y, offset));
405 }
406
FaceOpen(const std::string & filename,int index)407 void TextPage::FaceOpen(const std::string& filename, int index) {
408 AddAction(Command(TYPE_FACE_OPEN, filename, index));
409 }
410
FaceClose(int index)411 void TextPage::FaceClose(int index) {
412 AddAction(Command(TYPE_FACE_CLOSE, index));
413 }
414
NextCharIsItalic()415 void TextPage::NextCharIsItalic() {
416 AddAction(Command(TYPE_NEXT_CHAR_IS_ITALIC));
417 }
418
IsFull() const419 bool TextPage::IsFull() const {
420 return system_->text().GetTextWindow(window_num_)->IsFull();
421 }
422
AddAction(const Command & command)423 void TextPage::AddAction(const Command& command) {
424 RunTextPageCommand(command, true);
425 elements_to_replay_.push_back(command);
426 }
427
CharacterImpl(const string & c,const string & rest)428 bool TextPage::CharacterImpl(const string& c, const string& rest) {
429 return system_->text().GetTextWindow(window_num_)->DisplayCharacter(c, rest);
430 }
431
RunTextPageCommand(const Command & command,bool is_active_page)432 void TextPage::RunTextPageCommand(const Command& command,
433 bool is_active_page) {
434 std::shared_ptr<TextWindow> window =
435 system_->text().GetTextWindow(window_num_);
436
437 switch (command.command) {
438 case TYPE_CHARACTERS:
439 if (command.characters.size()) {
440 PrintTextToFunction(
441 bind(&TextPage::CharacterImpl, ref(*this), _1, _2),
442 command.characters,
443 "");
444 }
445 break;
446 case TYPE_NAME:
447 window->SetName(command.name.name, command.name.next_char);
448 break;
449 case TYPE_KOE_MARKER:
450 if (!is_active_page)
451 window->KoeMarker(command.koe_id);
452 break;
453 case TYPE_HARD_BREAK:
454 window->HardBrake();
455 break;
456 case TYPE_SET_INDENTATION:
457 window->SetIndentation();
458 break;
459 case TYPE_RESET_INDENTATION:
460 window->ResetIndentation();
461 break;
462 case TYPE_FONT_COLOUR:
463 if (is_active_page) {
464 window->SetFontColor(
465 system_->gameexe()("COLOR_TABLE", command.font_colour));
466 }
467 break;
468 case TYPE_DEFAULT_FONT_SIZE:
469 window->set_font_size_to_default();
470 break;
471 case TYPE_FONT_SIZE:
472 window->set_font_size_in_pixels(command.font_size);
473 break;
474 case TYPE_RUBY_BEGIN:
475 window->MarkRubyBegin();
476 in_ruby_gloss_ = true;
477 break;
478 case TYPE_RUBY_END:
479 window->DisplayRubyText(command.ruby_text);
480 in_ruby_gloss_ = false;
481 break;
482 case TYPE_SET_INSERTION_X:
483 window->set_insertion_point_x(command.set_insertion_x);
484 break;
485 case TYPE_SET_INSERTION_Y:
486 window->set_insertion_point_y(command.set_insertion_y);
487 break;
488 case TYPE_OFFSET_INSERTION_X:
489 window->offset_insertion_point_x(command.offset_insertion_x);
490 break;
491 case TYPE_OFFSET_INSERTION_Y:
492 window->offset_insertion_point_y(command.offset_insertion_y);
493 break;
494 case TYPE_FACE_OPEN:
495 window->FaceOpen(command.face_open.filename, command.face_open.index);
496 break;
497 case TYPE_FACE_CLOSE:
498 window->FaceClose(command.face_close);
499 break;
500 case TYPE_NEXT_CHAR_IS_ITALIC:
501 window->NextCharIsItalic();
502 }
503 }
504