Apply the new ground_level method.
[crawl.git] / crawl-ref / source / format.cc
blob62c8a5ab3de7e058cdb381ede6712278e810e4f9
1 /*
2 * File: format.cc
3 * Created by: haranp on Sat Feb 17 13:35:54 2007 UTC
4 */
6 #include <limits.h>
8 #include "AppHdr.h"
10 #include "colour.h"
11 #include "format.h"
12 #include "libutil.h"
13 #include "showsymb.h"
14 #include "viewchar.h"
16 formatted_string::formatted_string(int init_colour)
17 : ops()
19 if (init_colour)
20 textcolor(init_colour);
23 formatted_string::formatted_string(const std::string &s, int init_colour)
24 : ops()
26 if (init_colour)
27 textcolor(init_colour);
28 cprintf(s);
31 int formatted_string::get_colour(const std::string &tag)
33 if (tag == "h")
34 return (YELLOW);
36 if (tag == "w")
37 return (WHITE);
39 const int colour = str_to_colour(tag);
40 return (colour != -1? colour : LIGHTGREY);
43 // Display a formatted string without printing literal \n.
44 // This is important if the text is not supposed
45 // to clobber existing text to the right of the lines being displayed
46 // (some of the tutorial messages need this).
47 void display_tagged_block(const std::string &s)
49 std::vector<formatted_string> lines;
50 formatted_string::parse_string_to_multiple(s, lines);
52 int x = wherex();
53 int y = wherey();
54 for (int i = 0, size = lines.size(); i < size; ++i)
56 cgotoxy(x, y);
57 lines[i].display();
58 y++;
62 formatted_string formatted_string::parse_string(
63 const std::string &s,
64 bool eot_ends_format,
65 bool (*process)(const std::string &tag),
66 int main_colour)
68 // main_colour will usually be LIGHTGREY (default).
69 std::vector<int> colour_stack;
70 colour_stack.push_back(main_colour);
72 formatted_string fs;
74 parse_string1(s, fs, colour_stack, process);
75 if (eot_ends_format)
77 if (colour_stack.back() != colour_stack.front())
78 fs.textcolor(colour_stack.front());
80 return fs;
83 // Parses a formatted string in much the same way as parse_string, but
84 // handles \n by creating a new formatted_string.
85 void formatted_string::parse_string_to_multiple(
86 const std::string &s,
87 std::vector<formatted_string> &out)
89 std::vector<int> colour_stack;
90 colour_stack.push_back(LIGHTGREY);
92 std::vector<std::string> lines = split_string("\n", s, false, true);
94 for (int i = 0, size = lines.size(); i < size; ++i)
96 out.push_back(formatted_string());
97 formatted_string& fs = out.back();
98 fs.textcolor(colour_stack.back());
99 parse_string1(lines[i], fs, colour_stack, NULL);
100 if (colour_stack.back() != colour_stack.front())
101 fs.textcolor(colour_stack.front());
105 // Helper for the other parse_ methods.
106 void formatted_string::parse_string1(
107 const std::string &s,
108 formatted_string &fs,
109 std::vector<int> &colour_stack,
110 bool (*process)(const std::string &tag))
112 // FIXME: This is a lame mess, just good enough for the task on hand
113 // (keyboard help).
114 std::string::size_type tag = std::string::npos;
115 std::string::size_type length = s.length();
117 std::string currs;
118 bool masked = false;
120 for (tag = 0; tag < length; ++tag)
122 bool revert_colour = false;
123 std::string::size_type endpos = std::string::npos;
125 // Break string up if it gets too big.
126 if (currs.size() >= 999)
128 // Break the string at the end of a line, if possible, so
129 // that none of the broken string ends up overwritten.
130 std::string::size_type bound = currs.rfind("\n", 999);
131 if (bound != endpos)
132 bound++;
133 else
134 bound = 999;
136 fs.cprintf(currs.substr(0, bound));
137 if (currs.size() > bound)
138 currs = currs.substr(bound);
139 else
140 currs.clear();
141 tag--;
142 continue;
145 if (s[tag] != '<' || tag >= length - 2)
147 if (!masked)
148 currs += s[tag];
149 continue;
152 // Is this a << escape?
153 if (s[tag + 1] == '<')
155 if (!masked)
156 currs += s[tag];
157 tag++;
158 continue;
161 endpos = s.find('>', tag + 1);
162 // No closing >?
163 if (endpos == std::string::npos)
165 if (!masked)
166 currs += s[tag];
167 continue;
170 std::string tagtext = s.substr(tag + 1, endpos - tag - 1);
171 if (tagtext.empty() || tagtext == "/")
173 if (!masked)
174 currs += s[tag];
175 continue;
178 if (tagtext[0] == '/')
180 revert_colour = true;
181 tagtext = tagtext.substr(1);
182 tag++;
185 if (tagtext[0] == '?')
187 if (tagtext.length() == 1)
188 masked = false;
189 else if (process && !process(tagtext.substr(1)))
190 masked = true;
192 tag += tagtext.length() + 1;
193 continue;
196 if (!currs.empty())
198 fs.cprintf(currs);
199 currs.clear();
202 if (revert_colour)
204 colour_stack.pop_back();
205 if (colour_stack.size() < 1)
206 die("Stack underflow in string \"%s\"", s.c_str());
208 else
210 colour_stack.push_back(get_colour(tagtext));
213 // fs.cprintf("%d%d", colour_stack.size(), colour_stack.back());
214 fs.textcolor(colour_stack.back());
216 tag += tagtext.length() + 1;
218 if (currs.length())
219 fs.cprintf(currs);
222 formatted_string::operator std::string() const
224 std::string s;
225 for (unsigned i = 0, size = ops.size(); i < size; ++i)
227 if (ops[i] == FSOP_TEXT)
228 s += ops[i].text;
230 return (s);
233 void replace_all_in_string(std::string& s, const std::string& search,
234 const std::string& replace)
236 std::string::size_type pos = 0;
237 while ((pos = s.find(search, pos)) != std::string::npos)
239 s.replace(pos, search.size(), replace);
240 pos += replace.size();
244 std::string formatted_string::html_dump() const
246 std::string s;
247 for (unsigned i = 0; i < ops.size(); ++i)
249 std::string tmp;
250 switch (ops[i].type)
252 case FSOP_TEXT:
253 tmp = ops[i].text;
254 // (very) crude HTMLification
255 replace_all_in_string(tmp, "&", "&amp;");
256 replace_all_in_string(tmp, " ", "&nbsp;");
257 replace_all_in_string(tmp, "<", "&lt;");
258 replace_all_in_string(tmp, ">", "&gt;");
259 replace_all_in_string(tmp, "\n", "<br>");
260 s += tmp;
261 break;
262 case FSOP_COLOUR:
263 s += "<font color=";
264 s += colour_to_str(ops[i].x);
265 s += ">";
266 break;
269 return s;
272 bool formatted_string::operator < (const formatted_string &other) const
274 return std::string(*this) < std::string(other);
277 const formatted_string &
278 formatted_string::operator += (const formatted_string &other)
280 ops.insert(ops.end(), other.ops.begin(), other.ops.end());
281 return (*this);
284 std::string::size_type formatted_string::length() const
286 // Just add up the individual string lengths.
287 std::string::size_type len = 0;
288 for (unsigned i = 0, size = ops.size(); i < size; ++i)
289 if (ops[i] == FSOP_TEXT)
290 len += ops[i].text.length();
291 return (len);
294 inline void cap(int &i, int max)
296 if (i < 0 && -i <= max)
297 i += max;
298 if (i >= max)
299 i = max - 1;
300 if (i < 0)
301 i = 0;
304 char &formatted_string::operator [] (size_t idx)
306 size_t rel_idx = idx;
307 int size = ops.size();
308 for (int i = 0; i < size; ++i)
310 if (ops[i] != FSOP_TEXT)
311 continue;
313 size_t len = ops[i].text.length();
314 if (rel_idx >= len)
315 rel_idx -= len;
316 else
317 return ops[i].text[rel_idx];
319 die("Invalid index");
323 std::string formatted_string::tostring(int s, int e) const
325 std::string st;
327 int size = ops.size();
328 cap(s, size);
329 cap(e, size);
331 for (int i = s; i <= e && i < size; ++i)
333 if (ops[i] == FSOP_TEXT)
334 st += ops[i].text;
336 return st;
339 std::string formatted_string::to_colour_string() const
341 std::string st;
342 const int size = ops.size();
343 for (int i = 0; i < size; ++i)
345 if (ops[i] == FSOP_TEXT)
347 // gotta double up those '<' chars ...
348 size_t start = st.size();
349 st += ops[i].text;
351 while (true)
353 const size_t left_angle = st.find('<', start);
354 if (left_angle == std::string::npos)
355 break;
357 st.insert(left_angle, "<");
358 start = left_angle + 2;
361 else if (ops[i] == FSOP_COLOUR)
363 st += "<";
364 st += colour_to_str(ops[i].x);
365 st += ">";
369 return st;
372 void formatted_string::display(int s, int e) const
374 int size = ops.size();
375 if (!size)
376 return;
378 cap(s, size);
379 cap(e, size);
381 for (int i = s; i <= e && i < size; ++i)
382 ops[i].display();
385 int formatted_string::find_last_colour() const
387 if (!ops.empty())
389 for (int i = ops.size() - 1; i >= 0; --i)
390 if (ops[i].type == FSOP_COLOUR)
391 return (ops[i].x);
393 return (LIGHTGREY);
396 formatted_string formatted_string::substr(size_t start, size_t substr_length) const
398 const unsigned int NONE = UINT_MAX; // from limits.h
399 unsigned int last_FSOP_COLOUR = NONE;
401 // Find the first string to copy
402 unsigned int i;
403 for (i = 0; i < ops.size(); ++i)
405 const fs_op& op = ops[i];
406 if (op.type == FSOP_COLOUR)
407 last_FSOP_COLOUR = i;
408 else if (op.type == FSOP_TEXT)
410 if (op.text.length() > start)
411 break;
412 else
413 start -= op.text.length();
417 if (i == ops.size())
418 return formatted_string();
420 formatted_string result;
421 // set up the state
422 if (last_FSOP_COLOUR != NONE)
423 result.ops.push_back(ops[last_FSOP_COLOUR]);
425 // Copy the text
426 for (; i<ops.size(); i++)
428 const fs_op& op = ops[i];
429 if (op.type == FSOP_TEXT)
431 result.ops.push_back(op);
432 std::string& new_string = result.ops[result.ops.size()-1].text;
433 if (start > 0 || op.text.length() > substr_length)
434 new_string = new_string.substr(start, substr_length);
436 substr_length -= new_string.length();
437 if (substr_length == 0)
438 break;
440 else
442 result.ops.push_back(op);
446 return result;
449 void formatted_string::add_glyph(glyph g)
451 const int last_col = find_last_colour();
452 this->textcolor(g.col);
453 this->cprintf("%s", stringize_glyph(g.ch).c_str());
454 this->textcolor(last_col);
457 void formatted_string::textcolor(int color)
459 if (!ops.empty() && ops[ ops.size() - 1 ].type == FSOP_COLOUR)
460 ops.erase(ops.end() - 1);
462 ops.push_back(color);
465 void formatted_string::clear()
467 ops.clear();
470 bool formatted_string::empty()
472 return (ops.empty());
475 void formatted_string::cprintf(const char *s, ...)
477 va_list args;
478 va_start(args, s);
479 cprintf(vmake_stringf(s, args));
480 va_end(args);
483 void formatted_string::cprintf(const std::string &s)
485 ops.push_back(s);
488 void formatted_string::fs_op::display() const
490 switch (type)
492 case FSOP_COLOUR:
493 ::textattr(x);
494 break;
495 case FSOP_TEXT:
496 ::cprintf("%s", text.c_str());
497 break;
501 void formatted_string::swap(formatted_string& other)
503 ops.swap(other.ops);
506 void formatted_string::all_caps()
508 for (unsigned int i = 0; i < ops.size(); i++)
509 if (ops[i].type == FSOP_TEXT)
510 uppercase(ops[i].text);
513 int count_linebreaks(const formatted_string& fs)
515 std::string::size_type where = 0;
516 const std::string s = fs;
517 int count = 0;
518 while (1)
520 where = s.find("\n", where);
521 if (where == std::string::npos)
522 break;
523 else
525 ++count;
526 ++where;
529 return count;
532 // Return the actual (string) offset of character #loc to be printed,
533 // i.e. ignoring tags. So for instance, if s == "<tag>ab</tag>", then
534 // _find_string_location(s, 2) == 6.
535 static int _find_string_location(const std::string& s, int loc)
537 int real_offset = 0;
538 bool in_tag = false;
539 int last_taglen = 0;
540 int offset = 0;
541 for (std::string::const_iterator ci = s.begin();
542 ci != s.end() && real_offset < loc;
543 ++ci, ++offset)
545 if (in_tag)
547 if (*ci == '<' && last_taglen == 1)
549 ++real_offset;
550 in_tag = false;
552 else if (*ci == '>')
554 in_tag = false;
556 else
558 ++last_taglen;
561 else if (*ci == '<')
563 in_tag = true;
564 last_taglen = 1;
566 else
568 ++real_offset;
571 return (offset);
574 // Return the substring of s from character start to character end,
575 // where tags count as length 0.
576 std::string tagged_string_substr(const std::string& s, int start, int end)
578 return (s.substr(_find_string_location(s, start),
579 _find_string_location(s, end)));
582 int tagged_string_printable_length(const std::string& s)
584 int len = 0;
585 bool in_tag = false;
586 int last_taglen = 0;
587 for (std::string::const_iterator ci = s.begin(); ci != s.end(); ++ci)
589 if (in_tag)
591 if (*ci == '<' && last_taglen == 1) // "<<" sequence
593 in_tag = false; // this is an escape for '<'
594 ++len; // len wasn't incremented before
596 else if (*ci == '>') // tag close, still nothing printed
598 in_tag = false;
600 else // tag continues
602 ++last_taglen;
605 else if (*ci == '<') // tag starts
607 in_tag = true;
608 last_taglen = 1;
610 else // normal, printable character
612 ++len;
615 return (len);
619 // Count the length of the tags in the string.
620 int tagged_string_tag_length(const std::string& s)
622 return s.size() - tagged_string_printable_length(s);