summaryrefslogblamecommitdiffstatshomepage
path: root/src/frontend/mame/ui/text.cpp
blob: fbd9e8de5d24b59e606d208765105ece5a1bfad5 (plain) (tree)























                                                                            
                                               









                                                   
                                              











































                                                                                                                                                        
                                                                                                                                                                                               

 
                                             







                                                   
                                                                                                                                                                                                                                                    
                                                                                                                                          
 
                                                  






















                                                                     


                                                                 











































                                                                                                      

                                                 
                                       






































                                                                                                                                
                                      

                                                   
                                                      
 
                                       



                                                   



























                                                                   




                                                   











                                                                                                       



























                                                                                                                     

                                        
                             





















                                                                           

                                            












                                                                                 
                                                                  

                                                
                                                                                                                       





                                                    
                                                              
 

                                                                   

































                                                                                                                           






















































                                                                                                         




































                                                                                        
                                                     





















































                                                                                                                                                                      
                                         







                                                
                                                                      



                                                   























                                                                  


















                                                         
// license:BSD-3-Clause
// copyright-holders:Nicola Salmoria, Aaron Giles, Nathan Woods
/*********************************************************************

	text.cpp

	Text functionality for MAME's crude user interface

*********************************************************************/

#include "text.h"
#include "rendfont.h"
#include "render.h"

namespace ui {

/***************************************************************************
INLINE FUNCTIONS
***************************************************************************/

//-------------------------------------------------
//  is_space_character
//-------------------------------------------------

inline bool is_space_character(unicode_char ch)
{
	return ch == ' ';
}


//-------------------------------------------------
//  is_breakable_char - is a given unicode
//  character a possible line break?
//-------------------------------------------------

inline bool is_breakable_char(unicode_char ch)
{
	// regular spaces and hyphens are breakable
	if (is_space_character(ch) || ch == '-')
		return true;

	// In the following character sets, any character is breakable:
	//  Hiragana (3040-309F)
	//  Katakana (30A0-30FF)
	//  Bopomofo (3100-312F)
	//  Hangul Compatibility Jamo (3130-318F)
	//  Kanbun (3190-319F)
	//  Bopomofo Extended (31A0-31BF)
	//  CJK Strokes (31C0-31EF)
	//  Katakana Phonetic Extensions (31F0-31FF)
	//  Enclosed CJK Letters and Months (3200-32FF)
	//  CJK Compatibility (3300-33FF)
	//  CJK Unified Ideographs Extension A (3400-4DBF)
	//  Yijing Hexagram Symbols (4DC0-4DFF)
	//  CJK Unified Ideographs (4E00-9FFF)
	if (ch >= 0x3040 && ch <= 0x9fff)
		return true;

	// Hangul Syllables (AC00-D7AF) are breakable
	if (ch >= 0xac00 && ch <= 0xd7af)
		return true;

	// CJK Compatibility Ideographs (F900-FAFF) are breakable
	if (ch >= 0xf900 && ch <= 0xfaff)
		return true;

	return false;
}



/***************************************************************************
CORE IMPLEMENTATION
***************************************************************************/

//-------------------------------------------------
//  ctor
//-------------------------------------------------

text_layout::text_layout(render_font &font, float xscale, float yscale, float width, text_layout::text_justify justify, text_layout::word_wrapping wrap)
	: m_font(font), m_xscale(xscale), m_yscale(yscale), m_width(width), m_justify(justify), m_wrap(wrap), m_current_line(nullptr), m_last_break(0), m_text_position(0), m_truncating(false)

{
	invalidate_calculated_actual_width();
}


//-------------------------------------------------
//  ctor (move)
//-------------------------------------------------

text_layout::text_layout(text_layout &&that)
	: m_font(that.m_font), m_xscale(that.m_xscale), m_yscale(that.m_yscale), m_width(that.m_width), m_calculated_actual_width(that.m_calculated_actual_width), m_justify(that.m_justify), m_wrap(that.m_wrap), m_lines(std::move(that.m_lines)),
	  m_current_line(that.m_current_line), m_last_break(that.m_last_break), m_text_position(that.m_text_position), m_truncating(false)
{
	that.invalidate_calculated_actual_width();
}


//-------------------------------------------------
//  dtor
//-------------------------------------------------

text_layout::~text_layout()
{
}


//-------------------------------------------------
//  add_text
//-------------------------------------------------

void text_layout::add_text(const char *text, const char_style &style)
{
	int position = 0;
	int text_length = strlen(text);

	while(position < text_length)
	{
		// adding a character - we might change the width
		invalidate_calculated_actual_width();

		// do we need to create a new line?
		if (m_current_line == nullptr)
		{
			// get the current character
			unicode_char schar;
			int scharcount;
			scharcount = uchar_from_utf8(&schar, &text[position], text_length - position);
			if (scharcount == -1)
				break;

			// if the line starts with a tab character, center it regardless
			text_justify line_justify = justify();
			if (schar == '\t')
			{
				position += scharcount;
				line_justify = text_layout::CENTER;
			}

			// start a new line
			start_new_line(line_justify, style.size);
		}

		// get the current character
		int scharcount;
		unicode_char ch;
		scharcount = uchar_from_utf8(&ch, &text[position], text_length - position);
		if (scharcount < 0)
			break;
		position += scharcount;

		// set up source information
		source_info source = { 0, };
		source.start = m_text_position;
		source.span = scharcount;
		m_text_position += scharcount;

		// is this an endline?
		if (ch == '\n')
		{
			// first, start a line if we have not already
			if (m_current_line == nullptr)
				start_new_line(LEFT, style.size);

			// and then close up the current line
			m_current_line = nullptr;
		}
		else if (!m_truncating)
		{
			// if we hit a space, remember the location and width *without* the space
			if (is_space_character(ch))
				m_last_break = m_current_line->character_count();

			// append the character
			m_current_line->add_character(ch, style, source);

			// do we have to wrap?
			if (wrap() != NEVER && m_current_line->width() > m_width)
			{
				switch (wrap())
				{
					case TRUNCATE:
						truncate_wrap();
						break;

					case WORD:
						word_wrap();
						break;

					default:
						fatalerror("invalid word wrapping value");
						break;
				}
			}
			else
			{
				// we didn't wrap - if we hit any non-space breakable character, remember the location and width
				// *with* the breakable character
				if (ch != ' ' && is_breakable_char(ch))
					m_last_break = m_current_line->character_count();
			}
		}
	}
}


//-------------------------------------------------
//  invalidate_calculated_actual_width
//-------------------------------------------------

void text_layout::invalidate_calculated_actual_width()
{
	m_calculated_actual_width = -1;
}


//-------------------------------------------------
//  actual_left
//-------------------------------------------------

float text_layout::actual_left() const
{
	float result;
	if (empty())
	{
		// degenerate scenario
		result = 0;
	}
	else
	{
		result = 1.0f;
		for (const auto &line : m_lines)
		{
			result = std::min(result, line->xoffset());

			// take an opportunity to break out easily
			if (result <= 0)
				break;
		}
	}
	return result;
}


//-------------------------------------------------
//  actual_width
//-------------------------------------------------

float text_layout::actual_width() const
{
	// do we need to calculate the width?
	if (m_calculated_actual_width < 0)
	{
		// calculate the actual width
		m_calculated_actual_width = 0;
		for (const auto &line : m_lines)
			m_calculated_actual_width = std::max(m_calculated_actual_width, line->width());

	}

	// return it
	return m_calculated_actual_width;
}


//-------------------------------------------------
//  actual_height
//-------------------------------------------------

float text_layout::actual_height() const
{
	line *last_line = (m_lines.size() > 0)
		? m_lines[m_lines.size() - 1].get()
		: nullptr;
	return last_line
		? last_line->yoffset() + last_line->height()
		: 0;
}


//-------------------------------------------------
//  start_new_line
//-------------------------------------------------

void text_layout::start_new_line(text_layout::text_justify justify, float height)
{
	// create a new line
	std::unique_ptr<line> new_line(global_alloc_clear<line>(*this, justify, actual_height(), height * yscale()));

	// update the current line
	m_current_line = new_line.get();
	m_last_break = 0;
	m_truncating = false;

	// append it
	m_lines.push_back(std::move(new_line));
}


//-------------------------------------------------
//  get_char_width
//-------------------------------------------------

float text_layout::get_char_width(unicode_char ch, float size)
{
	return font().char_width(size * yscale(), xscale() / yscale(), ch);
}


//-------------------------------------------------
//  truncate_wrap
//-------------------------------------------------

void text_layout::truncate_wrap()
{
	const unicode_char elipsis = 0x2026;

	// for now, lets assume that we're only truncating the last character
	size_t truncate_position = m_current_line->character_count() - 1;
	const auto& truncate_char = m_current_line->character(truncate_position);

	// copy style information
	char_style style = truncate_char.style;

	// copy source information
	source_info source = { 0, };
	source.start = truncate_char.source.start + truncate_char.source.span;
	source.span = 0;

	// figure out how wide an elipsis is
	float elipsis_width = get_char_width(elipsis, style.size);

	// where should we really truncate from?
	while (truncate_position > 0 && m_current_line->character(truncate_position).xoffset + elipsis_width > width())
		truncate_position--;

	// truncate!!!
	m_current_line->truncate(truncate_position);

	// and append the elipsis
	m_current_line->add_character(elipsis, style, source);

	// take note that we are truncating; supress new characters
	m_truncating = true;
}


//-------------------------------------------------
//  word_wrap
//-------------------------------------------------

void text_layout::word_wrap()
{
	// keep track of the last line and break
	line *last_line = m_current_line;
	size_t last_break = m_last_break;

	// start a new line with the same justification
	start_new_line(last_line->justify(), last_line->character(last_line->character_count() - 1).style.size);

	// find the begining of the word to wrap
	size_t position = last_break;
	while (position + 1 < last_line->character_count() && is_space_character(last_line->character(position).character))
		position++;

	// transcribe the characters
	for (size_t i = position; i < last_line->character_count(); i++)
	{
		auto &ch = last_line->character(i);
		m_current_line->add_character(ch.character, ch.style, ch.source);
	}

	// and finally, truncate the last line
	last_line->truncate(last_break);
}


//-------------------------------------------------
//  hit_test
//-------------------------------------------------

bool text_layout::hit_test(float x, float y, size_t &start, size_t &span) const
{
	for (const auto &line : m_lines)
	{
		if (y >= line->yoffset() && y < line->yoffset() + line->height())
		{
			float line_xoffset = line->xoffset();
			if (x >= line_xoffset && x < line_xoffset + line->width())
			{
				for (size_t i = 0; i < line->character_count(); i++)
				{
					const auto &ch = line->character(i);
					if (x >= ch.xoffset && x < ch.xoffset + ch.xwidth)
					{
						start = ch.source.start;
						span = ch.source.span;
						return true;
					}
				}
			}
		}
	}
	start = 0;
	span = 0;
	return false;
}


//-------------------------------------------------
//  restyle
//-------------------------------------------------

void text_layout::restyle(size_t start, size_t span, rgb_t *fgcolor, rgb_t *bgcolor)
{
	for (const auto &line : m_lines)
	{
		for (size_t i = 0; i < line->character_count(); i++)
		{
			auto &ch = line->character(i);
			if (ch.source.start >= start && ch.source.start + ch.source.span <= start + span)
			{
				if (fgcolor != nullptr)
					ch.style.fgcolor = *fgcolor;
				if (bgcolor != nullptr)
					ch.style.bgcolor = *bgcolor;
			}
		}
	}
}


//-------------------------------------------------
//  get_wrap_info
//-------------------------------------------------

int text_layout::get_wrap_info(std::vector<int> &xstart, std::vector<int> &xend) const
{
	// this is a hacky method (tailored to the need to implement
	// mame_ui_manager::wrap_text) but so be it
	int line_count = 0;
	for (const auto &line : m_lines)
	{
		int start_pos = 0;
		int end_pos = 0;

		auto line_character_count = line->character_count();
		if (line_character_count > 0)
		{
			start_pos = line->character(0).source.start;
			end_pos = line->character(line_character_count - 1).source.start
				+ line->character(line_character_count - 1).source.span;
		}

		line_count++;
		xstart.push_back(start_pos);
		xend.push_back(end_pos);
	}
	return line_count;
}


//-------------------------------------------------
//  emit
//-------------------------------------------------

void text_layout::emit(render_container *container, float x, float y)
{
	for (const auto &line : m_lines)
	{
		float line_xoffset = line->xoffset();

		// emit every single character
		for (auto i = 0; i < line->character_count(); i++)
		{
			auto &ch = line->character(i);
			
			// position this specific character correctly (TODO - this doesn't
			// handle differently sized text (yet)
			float char_x = x + line_xoffset + ch.xoffset;
			float char_y = y + line->yoffset();
			float char_width = ch.xwidth;
			float char_height = line->height();

			// render the background of the character (if present)
			if (ch.style.bgcolor.a() != 0)
				container->add_rect(char_x, char_y, char_x + char_width, char_y + char_height, ch.style.bgcolor, PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA));

			// render the foreground
			container->add_char(
				char_x,
				char_y,
				char_height,
				xscale() / yscale(),
				ch.style.fgcolor,
				font(),
				ch.character);
		}
	}
}


//-------------------------------------------------
//  line::ctor
//-------------------------------------------------

text_layout::line::line(text_layout &layout, text_justify justify, float yoffset, float height)
	: m_layout(layout), m_justify(justify), m_yoffset(yoffset), m_width(0.0), m_height(height)
{
}


//-------------------------------------------------
//  line::add_character
//-------------------------------------------------

void text_layout::line::add_character(unicode_char ch, const char_style &style, const source_info &source)
{
	// get the width of this character
	float chwidth = m_layout.get_char_width(ch, style.size);

	// create the positioned character
	positioned_char positioned_char = { 0, };
	positioned_char.character = ch;
	positioned_char.xoffset = m_width;
	positioned_char.xwidth = chwidth;
	positioned_char.style = style;
	positioned_char.source = source;

	// append the character
	m_characters.push_back(positioned_char);
	m_width += chwidth;

	// we might be bigger
	m_height = std::max(m_height, style.size * m_layout.yscale());
}


//-------------------------------------------------
//  line::xoffset
//-------------------------------------------------

float text_layout::line::xoffset() const
{
	float result;
	switch (justify())
	{
		case LEFT:
		default:
			result = 0;
			break;
		case CENTER:
			result = (m_layout.width() - width()) / 2;
			break;
		case RIGHT:
			result = m_layout.width() - width();
			break;
	}
	return result;
}


//-------------------------------------------------
//  line::truncate
//-------------------------------------------------

void text_layout::line::truncate(size_t position)
{
	assert(position <= m_characters.size());

	// are we actually truncating?
	if (position < m_characters.size())
	{
		// set the width as appropriate
		m_width = m_characters[position].xoffset;

		// and resize the array
		m_characters.resize(position);
	}
}

} // namespace ui