/* Minetest Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "chat.h" #include #include #include #include "config.h" #include "debug.h" #include "util/strfnd.h" #include "util/string.h" #include "util/numeric.h" ChatBuffer::ChatBuffer(u32 scrollback): m_scrollback(scrollback) { if (m_scrollback == 0) m_scrollback = 1; m_empty_formatted_line.first = true; m_cache_clickable_chat_weblinks = false; // Curses mode cannot access g_settings here if (g_settings != nullptr) { m_cache_clickable_chat_weblinks = g_settings->getBool("clickable_chat_weblinks"); if (m_cache_clickable_chat_weblinks) { std::string colorval = g_settings->get("chat_weblink_color"); parseColorString(colorval, m_cache_chat_weblink_color, false, 255); m_cache_chat_weblink_color.setAlpha(255); } } } void ChatBuffer::addLine(const std::wstring &name, const std::wstring &text) { m_lines_modified = true; ChatLine line(name, text); m_unformatted.push_back(line); if (m_rows > 0) { // m_formatted is valid and must be kept valid bool scrolled_at_bottom = (m_scroll == getBottomScrollPos()); u32 num_added = formatChatLine(line, m_cols, m_formatted); if (scrolled_at_bottom) m_scroll += num_added; } // Limit number of lines by m_scrollback if (m_unformatted.size() > m_scrollback) { deleteOldest(m_unformatted.size() - m_scrollback); } } void ChatBuffer::clear() { m_unformatted.clear(); m_formatted.clear(); m_scroll = 0; m_lines_modified = true; } u32 ChatBuffer::getLineCount() const { return m_unformatted.size(); } const ChatLine& ChatBuffer::getLine(u32 index) const { assert(index < getLineCount()); // pre-condition return m_unformatted[index]; } void ChatBuffer::step(f32 dtime) { for (ChatLine &line : m_unformatted) { line.age += dtime; } } void ChatBuffer::deleteOldest(u32 count) { bool at_bottom = (m_scroll == getBottomScrollPos()); u32 del_unformatted = 0; u32 del_formatted = 0; while (count > 0 && del_unformatted < m_unformatted.size()) { ++del_unformatted; // keep m_formatted in sync if (del_formatted < m_formatted.size()) { sanity_check(m_formatted[del_formatted].first); ++del_formatted; while (del_formatted < m_formatted.size() && !m_formatted[del_formatted].first) ++del_formatted; } --count; } m_unformatted.erase(m_unformatted.begin(), m_unformatted.begin() + del_unformatted); m_formatted.erase(m_formatted.begin(), m_formatted.begin() + del_formatted); if (del_unformatted > 0) m_lines_modified = true; if (at_bottom) m_scroll = getBottomScrollPos(); else scrollAbsolute(m_scroll - del_formatted); } void ChatBuffer::deleteByAge(f32 maxAge) { u32 count = 0; while (count < m_unformatted.size() && m_unformatted[count].age > maxAge) ++count; deleteOldest(count); } u32 ChatBuffer::getRows() const { return m_rows; } void ChatBuffer::reformat(u32 cols, u32 rows) { if (cols == 0 || rows == 0) { // Clear formatted buffer m_cols = 0; m_rows = 0; m_scroll = 0; m_formatted.clear(); } else if (cols != m_cols || rows != m_rows) { // TODO: Avoid reformatting ALL lines (even invisible ones) // each time the console size changes. // Find out the scroll position in *unformatted* lines u32 restore_scroll_unformatted = 0; u32 restore_scroll_formatted = 0; bool at_bottom = (m_scroll == getBottomScrollPos()); if (!at_bottom) { for (s32 i = 0; i < m_scroll; ++i) { if (m_formatted[i].first) ++restore_scroll_unformatted; } } // If number of columns change, reformat everything if (cols != m_cols) { m_formatted.clear(); for (u32 i = 0; i < m_unformatted.size(); ++i) { if (i == restore_scroll_unformatted) restore_scroll_formatted = m_formatted.size(); formatChatLine(m_unformatted[i], cols, m_formatted); } } // Update the console size m_cols = cols; m_rows = rows; // Restore the scroll position if (at_bottom) { scrollBottom(); } else { scrollAbsolute(restore_scroll_formatted); } } } const ChatFormattedLine& ChatBuffer::getFormattedLine(u32 row) const { s32 index = m_scroll + (s32) row; if (index >= 0 && index < (s32) m_formatted.size()) return m_formatted[index]; return m_empty_formatted_line; } void ChatBuffer::scroll(s32 rows) { scrollAbsolute(m_scroll + rows); } void ChatBuffer::scrollAbsolute(s32 scroll) { s32 top = getTopScrollPos(); s32 bottom = getBottomScrollPos(); m_scroll = scroll; if (m_scroll < top) m_scroll = top; if (m_scroll > bottom) m_scroll = bottom; } void ChatBuffer::scrollBottom() { m_scroll = getBottomScrollPos(); } u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, std::vector& destination) const { u32 num_added = 0; std::vector next_frags; ChatFormattedLine next_line; ChatFormattedFragment temp_frag; u32 out_column = 0; u32 in_pos = 0; u32 hanging_indentation = 0; // Format the sender name and produce fragments if (!line.name.empty()) { temp_frag.text = L"<"; temp_frag.column = 0; //temp_frag.bold = 0; next_frags.push_back(temp_frag); temp_frag.text = line.name; temp_frag.column = 0; //temp_frag.bold = 1; next_frags.push_back(temp_frag); temp_frag.text = L"> "; temp_frag.column = 0; //temp_frag.bold = 0; next_frags.push_back(temp_frag); } std::wstring name_sanitized = line.name.c_str(); // Choose an indentation level if (line.name.empty()) { // Server messages hanging_indentation = 0; } else if (name_sanitized.size() + 3 <= cols/2) { // Names shorter than about half the console width hanging_indentation = line.name.size() + 3; } else { // Very long names hanging_indentation = 2; } //EnrichedString line_text(line.text); next_line.first = true; // Set/use forced newline after the last frag in each line bool mark_newline = false; // Produce fragments and layout them into lines while (!next_frags.empty() || in_pos < line.text.size()) { mark_newline = false; // now using this to USE line-end frag // Layout fragments into lines while (!next_frags.empty()) { ChatFormattedFragment& frag = next_frags[0]; // Force newline after this frag, if marked if (frag.column == INT_MAX) mark_newline = true; if (frag.text.size() <= cols - out_column) { // Fragment fits into current line frag.column = out_column; next_line.fragments.push_back(frag); out_column += frag.text.size(); next_frags.erase(next_frags.begin()); } else { // Fragment does not fit into current line // So split it up temp_frag.text = frag.text.substr(0, cols - out_column); temp_frag.column = out_column; temp_frag.weblink = frag.weblink; next_line.fragments.push_back(temp_frag); frag.text = frag.text.substr(cols - out_column); frag.column = 0; out_column = cols; } if (out_column == cols || mark_newline) { // End the current line destination.push_back(next_line); num_added++; next_line.fragments.clear(); next_line.first = false; out_column = hanging_indentation; mark_newline = false; } } // Produce fragment(s) for next formatted line if (!(in_pos < line.text.size())) continue; const std::wstring &linestring = line.text.getString(); u32 remaining_in_output = cols - out_column; size_t http_pos = std::wstring::npos; mark_newline = false; // now using this to SET line-end frag // Construct all frags for next output line while (!mark_newline) { // Determine a fragment length <= the minimum of // remaining_in_{in,out}put. Try to end the fragment // on a word boundary. u32 frag_length = 0, space_pos = 0; u32 remaining_in_input = line.text.size() - in_pos; if (m_cache_clickable_chat_weblinks) { // Note: unsigned(-1) on fail http_pos = linestring.find(L"https://", in_pos); if (http_pos == std::wstring::npos) http_pos = linestring.find(L"http://", in_pos); if (http_pos != std::wstring::npos) http_pos -= in_pos; } while (frag_length < remaining_in_input && frag_length < remaining_in_output) { if (iswspace(linestring[in_pos + frag_length])) space_pos = frag_length; ++frag_length; } if (http_pos >= remaining_in_output) { // Http not in range, grab until space or EOL, halt as normal. // Note this works because (http_pos = npos) is unsigned(-1) mark_newline = true; } else if (http_pos == 0) { // At http, grab ALL until FIRST whitespace or end marker. loop. // If at end of string, next loop will be empty string to mark end of weblink. frag_length = 6; // Frag is at least "http://" // Chars to mark end of weblink // TODO? replace this with a safer (slower) regex whitelist? static const std::wstring delim_chars = L"\'\";,"; wchar_t tempchar = linestring[in_pos+frag_length]; while (frag_length < remaining_in_input && !iswspace(tempchar) && delim_chars.find(tempchar) == std::wstring::npos) { ++frag_length; tempchar = linestring[in_pos+frag_length]; } space_pos = frag_length - 1; // This frag may need to be force-split. That's ok, urls aren't "words" if (frag_length >= remaining_in_output) { mark_newline = true; } } else { // Http in range, grab until http, loop space_pos = http_pos - 1; frag_length = http_pos; } // Include trailing space in current frag if (space_pos != 0 && frag_length < remaining_in_input) frag_length = space_pos + 1; temp_frag.text = line.text.substr(in_pos, frag_length); // A hack so this frag remembers mark_newline for the layout phase temp_frag.column = mark_newline ? INT_MAX : 0; if (http_pos == 0) { // Discard color stuff from the source frag temp_frag.text = EnrichedString(temp_frag.text.getString()); temp_frag.text.setDefaultColor(m_cache_chat_weblink_color); // Set weblink in the frag meta temp_frag.weblink = wide_to_utf8(temp_frag.text.getString()); } else { temp_frag.weblink.clear(); } next_frags.push_back(temp_frag); in_pos += frag_length; remaining_in_output -= std::min(frag_length, remaining_in_output); } } // End the last line if (num_added == 0 || !next_line.fragments.empty()) { destination.push_back(next_line); num_added++; } return num_added; } s32 ChatBuffer::getTopScrollPos() const { s32 formatted_count = (s32) m_formatted.size(); s32 rows = (s32) m_rows; if (rows == 0) return 0; if (formatted_count <= rows) return formatted_count - rows; return 0; } s32 ChatBuffer::getBottomScrollPos() const { s32 formatted_count = (s32) m_formatted.s/* mini-gmp, a minimalistic implementation of a GNU GMP subset. Copyright 2011, 2012, 2013 Free Software Foundation, Inc. This file is part of the GNU MP Library. The GNU MP Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. The GNU MP Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU MP Library. If not, see http://www.gnu.org/licenses/. */ /* About mini-gmp: This is a minimal implementation of a subset of the GMP interface. It is intended for inclusion into applications which have modest bignums needs, as a fallback when the real GMP library is not installed. This file defines the public interface. */ #ifndef __MINI_GMP_H__ #define __MINI_GMP_H__ /* For size_t */ #include <stddef.h> #if defined (__cplusplus) extern "C" { #endif void mp_set_memory_functions (void *(*) (size_t), void *(*) (void *, size_t, size_t), void (*) (void *, size_t)); void mp_get_memory_functions (void *(**) (size_t), void *(**) (void *, size_t, size_t), void (**) (void *, size_t)); typedef unsigned long mp_limb_t; typedef long mp_size_t; typedef unsigned long mp_bitcnt_t; typedef mp_limb_t *mp_ptr; typedef const mp_limb_t *mp_srcptr; typedef struct { int _mp_alloc; /* Number of *limbs* allocated and pointed to by the _mp_d field. */ int _mp_size; /* abs(_mp_size) is the number of limbs the last field points to. If _mp_size is negative this is a negative number. */ mp_limb_t *_mp_d; /* Pointer to the limbs. */ } __mpz_struct; typedef __mpz_struct mpz_t[1]; typedef __mpz_struct *mpz_ptr; typedef const __mpz_struct *mpz_srcptr; void mpn_copyi (mp_ptr, mp_srcptr, mp_size_t); void mpn_copyd (mp_ptr, mp_srcptr, mp_size_t); int mpn_cmp (mp_srcptr, mp_srcptr, mp_size_t); mp_limb_t mpn_add_1 (mp_ptr, mp_srcptr, mp_size_t, mp_limb_t); mp_limb_t mpn_add_n (mp_ptr, mp_srcptr, mp_srcptr, mp_size_t); mp_limb_t mpn_add (mp_ptr, mp_srcptr, mp_size_t, mp_srcptr, mp_size_t); mp_limb_t mpn_sub_1 (mp_ptr, mp_srcptr, mp_size_t, mp_limb_t); mp_limb_t mpn_sub_n (mp_ptr, mp_srcptr, mp_srcptr, mp_size_t); mp_limb_t mpn_sub (mp_ptr, mp_srcptr, mp_size_t, mp_srcptr, mp_size_t); mp_limb_t mpn_mul_1 (mp_ptr, mp_srcptr, mp_size_t, mp_limb_t); mp_limb_t mpn_addmul_1 (mp_ptr, mp_srcptr, mp_size_t, mp_limb_t); mp_limb_t mpn_submul_1 (mp_ptr, mp_srcptr, mp_size_t, mp_limb_t); mp_limb_t mpn_mul (mp_ptr, mp_srcptr, mp_size_t, mp_srcptr, mp_size_t); void mpn_mul_n (mp_ptr, mp_srcptr, mp_srcptr, mp_size_t); void mpn_sqr (mp_ptr, mp_srcptr, mp_size_t); mp_limb_t mpn_lshift (mp_ptr, mp_srcptr, mp_size_t, unsigned int); mp_limb_t mpn_rshift (mp_ptr, mp_srcptr, mp_size_t, unsigned int