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