26 #include "table/strings.h"
29 #if defined(WITH_ZLIB)
33 #if defined(WITH_LIBLZMA)
91 void TextfileWindow::ConstructWindow()
96 this->GetWidget<NWidgetCore>(
WID_TF_CAPTION)->SetDataTip(STR_TEXTFILE_README_CAPTION + this->
file_type, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS);
113 for (
auto &line : this->
lines) {
120 for (
auto &line : this->
lines) {
130 uint TextfileWindow::GetContentHeight()
132 if (this->
lines.empty())
return 0;
133 return this->
lines.back().bottom;
143 size.width = std::max(200u, size.width);
167 static const std::regex
_markdown_link_regex{
"\\[(.+?)\\]\\((.+?)\\)", std::regex_constants::ECMAScript | std::regex_constants::optimize};
208 for (
char c : line) {
211 if (c ==
'#')
continue;
212 if (c ==
' ')
continue;
218 if (c ==
' ' || c ==
'-')
continue;
229 }
else if (c ==
' ' || c ==
'-') {
246 std::string::const_iterator last_match_end = line.
text.cbegin();
247 std::string fixed_line;
251 while (matcher != std::sregex_iterator()) {
252 std::smatch match = *matcher;
255 link.
line = line_index;
257 this->
links.push_back(link);
263 link_colour = SCC_GREEN;
266 link_colour = SCC_LTBLUE;
269 link_colour = SCC_LTBROWN;
273 link_colour = SCC_CONTROL_END;
277 if (link_colour != SCC_CONTROL_END) {
279 fixed_line += std::string(last_match_end, match[0].first);
280 this->
links.back().begin = fixed_line.length();
281 fixed_line += std::string(ccbuf,
Utf8Encode(ccbuf, SCC_PUSH_COLOUR));
282 fixed_line += std::string(ccbuf,
Utf8Encode(ccbuf, link_colour));
283 fixed_line += match[1].str();
284 this->
links.back().end = fixed_line.length();
285 fixed_line += std::string(ccbuf,
Utf8Encode(ccbuf, SCC_POP_COLOUR));
286 last_match_end = match[0].second;
292 if (last_match_end == line.
text.cbegin())
return;
295 fixed_line += std::string(last_match_end, line.
text.cend());
298 line.
text = fixed_line;
308 if (this->
links.empty())
return;
315 auto it = std::find_if(std::begin(this->
lines), std::end(this->
lines), [clicked_row](
const Line &l) {
return l.
top <= clicked_row && l.
bottom > clicked_row; });
316 if (it == this->
lines.cend())
return;
317 line_index = it - this->
lines.cbegin();
318 subline = clicked_row - it->top;
319 Debug(misc, 4,
"TextfileWindow check hyperlink: clicked_row={}, line_index={}, line.top={}, subline={}", clicked_row, line_index, it->top, subline);
326 std::vector<Hyperlink> found_links;
327 for (
const auto &link : this->
links) {
328 if (link.line == line_index) found_links.push_back(link);
330 if (found_links.empty())
return;
335 assert(subline < layout.size());
337 if (char_index < 0)
return;
338 Debug(misc, 4,
"TextfileWindow check hyperlink click: line={}, subline={}, char_index={}", line_index, subline, (
int)char_index);
341 for (
const auto &link : found_links) {
342 Debug(misc, 4,
"Checking link from char {} to {}", link.begin, link.end);
343 if (
static_cast<size_t>(char_index) >= link.begin &&
static_cast<size_t>(char_index) < link.end) {
344 Debug(misc, 4,
"Activating link with destination: {}", link.destination);
358 this->
history.erase(this->
history.begin() + this->history_pos + 1, this->history.end());
381 if (delta == 0)
return;
382 if (delta < 0 &&
static_cast<int>(this->
history_pos) < -delta)
return;
406 auto it = std::find_if(this->
link_anchors.cbegin(), this->link_anchors.cend(), [&](
const Hyperlink &other) { return link.destination == other.destination; });
409 this->ScrollToLine(it->line);
438 if (!newfile.starts_with(
"./"))
return;
441 std::string newpath = this->
filepath;
442 size_t pos = newpath.find_last_of(PATHSEPCHAR);
443 if (pos == std::string::npos) {
446 newpath.erase(pos + 1);
450 size_t anchor_pos = newfile.find_first_of(
'#');
452 if (anchor_pos != std::string::npos) {
453 anchor = newfile.substr(anchor_pos);
454 newfile.erase(anchor_pos);
461 newfile = newfile.substr(2);
462 if (PATHSEPCHAR !=
'/') {
463 for (
char &c : newfile) {
464 if (c ==
'/') c = PATHSEPCHAR;
469 newpath = newpath + newfile;
477 this->
filename = newpath.substr(newpath.find_last_of(PATHSEP) + 1);
484 if (anchor.empty() || line != 0) {
485 this->ScrollToLine(line);
487 auto anchor_dest = std::find_if(this->
link_anchors.cbegin(), this->link_anchors.cend(), [&](
const Hyperlink &other) { return anchor == other.destination; });
489 this->ScrollToLine(anchor_dest->line);
492 this->ScrollToLine(0);
511 for (
size_t line_index = 0; line_index < this->
lines.size(); ++line_index) {
518 if (!line.
text.empty() && line.
text[0] ==
'#') {
519 this->
jumplist.push_back(line_index);
520 this->
lines[line_index].colour = TC_GOLD;
526 void TextfileWindow::OnClick([[maybe_unused]]
Point pt,
WidgetID widget, [[maybe_unused]]
int click_count)
536 for (
size_t line : this->
jumplist) {
538 list.push_back(MakeDropDownListStringItem(STR_TEXTFILE_JUMPLIST_ITEM, (
int)line));
558 void TextfileWindow::DrawWidget(
const Rect &r,
WidgetID widget)
const
577 for (
auto &line : this->
lines) {
578 if (line.bottom < pos)
continue;
579 if (line.top > pos + cap)
break;
581 int y_offset = (line.top - pos) * line_height;
598 void TextfileWindow::OnInvalidateData([[maybe_unused]]
int data, [[maybe_unused]]
bool gui_scope)
600 if (!gui_scope)
return;
605 void TextfileWindow::OnDropdownSelect(
WidgetID widget,
int index)
609 this->ScrollToLine(index);
612 void TextfileWindow::ScrollToLine(
size_t line)
617 newpos = this->lines[line].top;
619 newpos =
static_cast<int>(line);
649 #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
655 #if defined(WITH_ZLIB)
665 static std::vector<char>
Gunzip(std::span<char> input)
667 static const int BLOCKSIZE = 8192;
668 std::vector<char> output;
671 memset(&z, 0,
sizeof(z));
672 z.next_in =
reinterpret_cast<Bytef *
>(input.data());
673 z.avail_in =
static_cast<uInt
>(input.size());
676 int res = inflateInit2(&z, 15 + 32);
678 while (res == Z_OK || (res == Z_BUF_ERROR && z.avail_out == 0)) {
681 z.avail_out += BLOCKSIZE;
682 output.resize(output.size() + BLOCKSIZE);
683 z.next_out =
reinterpret_cast<Bytef *
>(&*output.end() - z.avail_out);
684 res = inflate(&z, Z_FINISH);
688 if (res != Z_STREAM_END)
return {};
690 output.resize(output.size() - z.avail_out);
695 #if defined(WITH_LIBLZMA)
705 static std::vector<char>
Xunzip(std::span<char> input)
707 static const int BLOCKSIZE = 8192;
708 std::vector<char> output;
710 lzma_stream z = LZMA_STREAM_INIT;
711 z.next_in =
reinterpret_cast<uint8_t *
>(input.data());
712 z.avail_in = input.size();
714 int res = lzma_auto_decoder(&z, UINT64_MAX, LZMA_CONCATENATED);
716 while (res == LZMA_OK || (res == LZMA_BUF_ERROR && z.avail_out == 0)) {
719 z.avail_out += BLOCKSIZE;
720 output.resize(output.size() + BLOCKSIZE);
721 z.next_out =
reinterpret_cast<uint8_t *
>(&*output.end() - z.avail_out);
722 res = lzma_code(&z, LZMA_FINISH);
726 if (res != LZMA_STREAM_END)
return {};
728 output.resize(output.size() - z.avail_out);
740 this->jumplist.clear();
744 if (textfile.empty())
return;
748 auto handle =
FioFOpenFile(textfile,
"rb", dir, &filesize);
749 if (!handle.has_value())
return;
751 if (filesize == 0)
return;
753 std::vector<char> buf;
754 buf.resize(filesize);
755 size_t read = fread(buf.data(), 1, buf.size(), *handle);
757 if (read != buf.size())
return;
759 #if defined(WITH_ZLIB)
761 if (textfile.ends_with(
".gz")) buf =
Gunzip(buf);
764 #if defined(WITH_LIBLZMA)
766 if (textfile.ends_with(
".xz")) buf =
Xunzip(buf);
769 if (buf.empty())
return;
771 std::string_view sv_buf(buf.data(), buf.size());
774 if (sv_buf.starts_with(
"\ufeff")) sv_buf.remove_prefix(3);
799 std::string_view p(text);
801 auto next = p.find_first_of(
'\n');
802 while (next != std::string_view::npos) {
803 this->lines.emplace_back(row, p.substr(0, next));
804 p.remove_prefix(next + 1);
807 next = p.find_first_of(
'\n');
809 this->lines.emplace_back(row, p);
813 for (
auto &line : this->lines) {
835 static const char *
const prefixes[] = {
840 static_assert(
lengthof(prefixes) == TFT_CONTENT_END);
843 if (type >= TFT_CONTENT_END)
return std::nullopt;
845 std::string_view prefix = prefixes[type];
847 if (filename.empty())
return std::nullopt;
849 auto slash = filename.find_last_of(PATHSEPCHAR);
850 if (slash == std::string::npos)
return std::nullopt;
852 std::string_view base_path(filename.data(), slash + 1);
854 static const std::initializer_list<std::string_view> extensions{
857 #if defined(WITH_ZLIB)
861 #if defined(WITH_LIBLZMA)
867 for (
auto &extension : extensions) {
874 file_path = fmt::format(
"{}{}.{}", base_path, prefix, extension);
Class for backupping variables and making sure they are restored later.
The layouter performs all the layout work.
ptrdiff_t GetCharAtPosition(int x, size_t line_index) const
Get the character that is at a pixel position in the first line of the layouted text.
Control codes that are embedded in the translation strings.
StringControlCode
List of string control codes used for string formatting, displaying, and by strgen to generate the la...
Functions related to debugging.
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
void ShowDropDownList(Window *w, DropDownList &&list, int selected, WidgetID button, uint width, bool instant_close, bool persist)
Show a drop down list.
Functions related to the drop down widget.
Types related to the drop down widget.
std::vector< std::unique_ptr< const DropDownListItem > > DropDownList
A drop down list is a collection of drop down list items.
bool FioCheckFileExists(const std::string &filename, Subdirectory subdir)
Check whether the given file exists.
std::optional< FileHandle > FioFOpenFile(const std::string &filename, const char *mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
Functions for Standard In/Out file operations.
Subdirectory
The different kinds of subdirectories OpenTTD uses.
@ NO_DIRECTORY
A path without any base directory.
fluid_settings_t * settings
FluidSynth settings handle.
int GetCharacterHeight(FontSize size)
Get height of a character for a given font size.
Functions to read fonts from files and cache them.
int GetStringHeight(std::string_view str, int maxw, FontSize fontsize)
Calculates height of string (in pixels).
Dimension GetStringBoundingBox(std::string_view str, FontSize start_fontsize)
Return the string dimension in pixels.
int DrawString(int left, int right, int top, std::string_view str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
Draw string, possibly truncated to make it fit in its allocated space.
int DrawStringMultiLine(int left, int right, int top, int bottom, std::string_view str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
Draw string, possibly over multiple lines.
bool FillDrawPixelInfo(DrawPixelInfo *n, int left, int top, int width, int height)
Set up a clipping area for only drawing into a certain area.
Functions related to the gfx engine.
Functions related to laying out the texts.
Types related to the graphics and/or input devices.
@ SA_TOP
Top align the text.
@ SA_LEFT
Left align the text.
FontSize
Available font sizes.
@ FS_MONO
Index of the monospaced font in the font tables.
void SetDirty() const
Mark entire window as dirty (in need of re-paint)
A number of safeguards to prevent using unsafe methods.
Definition of base types and functions in a cross-platform compatible way.
#define lengthof(array)
Return the length of an fixed size array.
static void StrMakeValid(T &dst, const char *str, const char *last, StringValidationSettings settings)
Copies the valid (UTF-8) characters from str up to last to the dst.
bool StrEndsWithIgnoreCase(std::string_view str, const std::string_view suffix)
Check whether the given string ends with the given suffix, ignoring case.
size_t Utf8Encode(T buf, char32_t c)
Encode a unicode character and place it in the buffer.
Functions related to low-level strings.
@ SVS_REPLACE_TAB_CR_NL_WITH_SPACE
Replace tabs ('\t'), carriage returns ('\r') and newlines (' ') with spaces.
@ SVS_ALLOW_NEWLINE
Allow newlines; replaces '\r ' with ' ' during processing.
@ SVS_REPLACE_WITH_QUESTION_MARK
Replace the unknown/bad bits with question marks.
void CheckForMissingGlyphs(bool base_font, MissingGlyphSearcher *searcher)
Check whether the currently loaded language pack uses characters that the currently loaded font does ...
const char * GetCurrentLanguageIsoCode()
Get the ISO language code of the currently loaded language.
void SetDParamStr(size_t n, const char *str)
This function is used to "bind" a C string to a OpenTTD dparam slot.
Class to backup a specific variable and restore it upon destruction of this object to prevent stack v...
Dimensions (a width and height) of a rectangle in 2D.
Data about how and where to blit pixels.
Settings for the four different fonts.
Coordinates of a point in 2D.
constexpr uint Horizontal() const
Get total horizontal padding of RectPadding.
constexpr uint Vertical() const
Get total vertical padding of RectPadding.
Specification of a rectangle with absolute coordinates of all edges.
Rect Shrink(int s) const
Copy and shrink Rect by s pixels.
Rect Translate(int x, int y) const
Copy and translate Rect by x,y pixels.
size_t line
Which line the link is on.
std::string destination
Destination for the link.
int bottom
Bottom scroll position in visual lines.
int top
Top scroll position in visual lines.
std::string text
Contents of the line.
Scrollbar * hscroll
Horizontal scrollbar.
uint ReflowContent()
Get the total height of the content displayed in this window, if wrapping is disabled.
void Reset() override
Reset the search, i.e.
uint search_iterator
Iterator for the font check search.
void AppendHistory(const std::string &filepath)
Append the new location to the history, so the user can go back.
void CheckHyperlinkClick(Point pt)
Check if the user clicked on a hyperlink, and handle it if so.
bool Monospace() override
Whether to search for a monospace font or not.
void NavigateToFile(std::string newfile, size_t line)
Navigate to the requested file.
std::optional< std::string_view > NextString() override
Get the next string to search through.
std::vector< Line > lines
#text, split into lines in a table with lines.
void LoadText(std::string_view buf)
Load a text into the textfile viewer.
void UpdateHistoryScrollpos()
Update the scroll position to the current, so we can restore there if we go back.
virtual void AfterLoadText()
Post-processing after the text is loaded.
std::string filepath
Full path to the filename.
TextfileType file_type
Type of textfile to view.
void AfterLoadMarkdown()
Post-processing of markdown files.
void SetupScrollbars(bool force_reflow)
Set scrollbars to the right lengths.
FontSize DefaultSize() override
Get the default (font) size of the string.
size_t history_pos
Position in browsing history (for forward movement).
std::vector< Hyperlink > link_anchors
Anchor names of headings that can be linked to.
void SetFontNames(FontCacheSettings *settings, const char *font_name, const void *os_data) override
Set the right font names.
void NavigateHistory(int delta)
Navigate through the history, either forward or backward.
virtual void OnHyperlinkClick(const Hyperlink &link)
Handle the clicking on a hyperlink.
std::vector< HistoryEntry > history
Browsing history in this window.
std::vector< Hyperlink > links
Clickable links in lines.
uint max_length
Maximum length of unwrapped text line.
std::string filename
Filename of the textfile.
Scrollbar * vscroll
Vertical scrollbar.
virtual void LoadTextfile(const std::string &textfile, Subdirectory dir)
Loads the textfile text from file and setup lines.
void OnResize() override
Called after the window got resized.
std::vector< size_t > jumplist
Table of contents list, line numbers.
void FindHyperlinksInMarkdown(Line &line, size_t line_index)
Find any hyperlinks in a given line.
High level window description.
Data structure for an opened window.
void ReInit(int rx=0, int ry=0, bool reposition=false)
Re-initialize a window, and optionally change its size.
void FinishInitNested(WindowNumber window_number=0)
Perform the second part of the initialization of a nested widget tree.
void InvalidateData(int data=0, bool gui_scope=true)
Mark this window's data as invalid (in need of re-computing)
ResizeInfo resize
Resize information.
void DisableWidget(WidgetID widget_index)
Sets a widget to disabled.
void CreateNestedTree()
Perform the first part of the initialization of a nested widget tree.
bool IsWidgetLowered(WidgetID widget_index) const
Gets the lowered state of a widget.
void EnableWidget(WidgetID widget_index)
Sets a widget to Enabled.
int GetRowFromWidget(int clickpos, WidgetID widget, int padding, int line_height=-1) const
Compute the row of a widget that a user clicked in.
const Scrollbar * GetScrollbar(WidgetID widnum) const
Return the Scrollbar to a widget index.
void SetWidgetDisabledState(WidgetID widget_index, bool disab_stat)
Sets the enabled/disabled status of a widget.
int height
Height of the window (number of pixels down in y direction)
void ToggleWidgetLoweredState(WidgetID widget_index)
Invert the lowered/raised status of a widget.
static constexpr NWidgetPart _nested_textfile_widgets[]
Widgets for the textfile window.
std::optional< std::string > GetTextfile(TextfileType type, Subdirectory dir, const std::string &filename)
Search a textfile file next to the given content.
static std::vector< char > Gunzip(std::span< char > input)
Do an in-memory gunzip operation.
static WindowDesc _textfile_desc(WDP_CENTER, "textfile", 630, 460, WC_TEXTFILE, WC_NONE, 0, _nested_textfile_widgets)
Window definition for the textfile window.
static HyperlinkType ClassifyHyperlink(const std::string &destination, bool trusted)
Classify the type of hyperlink the destination describes.
static const std::regex _markdown_link_regex
Regular expression that searches for Markdown links.
static std::string MakeAnchorSlug(const std::string &line)
Create a valid slug for the anchor.
static std::vector< char > Xunzip(std::span< char > input)
Do an in-memory xunzip operation.
HyperlinkType
Types of link we support in markdown files.
@ File
Link to a local file.
@ Internal
Internal link, or "anchor" in HTML language.
@ Web
Link to an external website.
GUI functions related to textfiles.
TextfileType
Additional text files accompanying Tar archives.
@ WDP_CENTER
Center the window.
@ WC_NONE
No window, redirects to WC_MAIN_WINDOW.
@ WC_TEXTFILE
textfile; Window numbers: