| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461 |
- #pragma once
- // License: zlib
- // Copyright (c) 2019 Juliette Foucaut & Doug Binks
- //
- // This software is provided 'as-is', without any express or implied
- // warranty. In no event will the authors be held liable for any damages
- // arising from the use of this software.
- //
- // Permission is granted to anyone to use this software for any purpose,
- // including commercial applications, and to alter it and redistribute it
- // freely, subject to the following restrictions:
- //
- // 1. The origin of this software must not be misrepresented; you must not
- // claim that you wrote the original software. If you use this software
- // in a product, an acknowledgment in the product documentation would be
- // appreciated but is not required.
- // 2. Altered source versions must be plainly marked as such, and must not be
- // misrepresented as being the original software.
- // 3. This notice may not be removed or altered from any source distribution.
- /*
- imgui_markdown https://github.com/juliettef/imgui_markdown
- Markdown for Dear ImGui
- A permissively licensed markdown single-header library for https://github.com/ocornut/imgui
- imgui_markdown currently supports the following markdown functionality:
- - Wrapped text
- - Headers H1, H2, H3
- - Indented text, multi levels
- - Unordered lists and sub-lists
- - Links
-
- Syntax
- Wrapping:
- Text wraps automatically. To add a new line, use 'Return'.
- Headers:
- # H1
- ## H2
- ### H3
- Indents:
- On a new line, at the start of the line, add two spaces per indent.
- ··Indent level 1
- ····Indent level 2
- Unordered lists:
- On a new line, at the start of the line, add two spaces, an asterisks and a space.
- For nested lists, add two additional spaces in front of the asterisk per list level increment.
- ··*·Unordered List level 1
- ····*·Unordered List level 2
- Links:
- [link description](https://...)
- ===============================================================================
- // Example use on Windows with links opening in a browser
- #include "ImGui.h" // https://github.com/ocornut/imgui
- #include "imgui_markdown.h" // https://github.com/juliettef/imgui_markdown
- #include "IconsFontAwesome5.h" // https://github.com/juliettef/IconFontCppHeaders
- // Following includes for Windows LinkCallback
- #define WIN32_LEAN_AND_MEAN
- #include <Windows.h>
- #include "Shellapi.h"
- #include <string>
- // You can make your own Markdown function with your prefered string container and markdown config.
- static ImGui::MarkdownConfig mdConfig{ LinkCallback, ICON_FA_LINK, { NULL, true, NULL, true, NULL, false } };
- void LinkCallback( const char* link_, uint32_t linkLength_ )
- {
- std::string url( link_, linkLength_ );
- ShellExecuteA( NULL, "open", url.c_str(), NULL, NULL, SW_SHOWNORMAL );
- }
- void LoadFonts( float fontSize_ = 12.0f )
- {
- ImGuiIO& io = ImGui::GetIO();
- io.Fonts->Clear();
- // Base font
- io.Fonts->AddFontFromFileTTF( "myfont.ttf", fontSize_ );
- // Bold headings H2 and H3
- mdConfig.headingFormats[ 1 ].font = io.Fonts->AddFontFromFileTTF( "myfont-bold.ttf", fontSize_ );
- mdConfig.headingFormats[ 2 ].font = mdConfig.headingFormats[ 1 ].font;
- // bold heading H1
- float fontSizeH1 = fontSize_ * 1.1f;
- mdConfig.headingFormats[ 0 ].font = io.Fonts->AddFontFromFileTTF( "myfont-bold.ttf", fontSizeH1 );
- }
- void Markdown( const std::string& markdown_ )
- {
- // fonts for, respectively, headings H1, H2, H3 and beyond
- ImGui::Markdown( markdown_.c_str(), markdown_.length(), mdConfig );
- }
- void MarkdownExample()
- {
- const std::string markdownText = u8R"(
- # H1 Header: Text and Links
- You can add [links like this one to enkisoftware](https://www.enkisoftware.com/) and lines will wrap well.
- ## H2 Header: indented text.
- This text has an indent (two leading spaces).
- This one has two.
- ### H3 Header: Lists
- * Unordered lists
- * Lists can be indented with two extra spaces.
- * Lists can have [links like this one to Avoyd](https://www.avoyd.com/)
- )";
- Markdown( markdownText );
- }
- ===============================================================================
- */
- #include <stdint.h>
- namespace ImGui
- {
- // Internals
- struct TextRegion;
- struct Line;
- inline void UnderLine( ImColor col_ );
- inline void RenderLine( const char* markdown_, Line& line_, TextRegion& textRegion_, const MarkdownConfig& mdConfig_ );
- struct TextRegion
- {
- TextRegion() : indentX( 0.0f )
- {
- pFont = ImGui::GetFont();
- }
- ~TextRegion()
- {
- ResetIndent();
- }
- // ImGui::TextWrapped will wrap at the starting position
- // so to work around this we render using our own wrapping for the first line
- void RenderTextWrapped( const char* text, const char* text_end, bool bIndentToHere = false )
- {
- const float scale = 1.0f;
- float widthLeft = GetContentRegionAvail().x;
- const char* endPrevLine = pFont->CalcWordWrapPosition( scale, text, text_end, widthLeft );
- ImGui::TextUnformatted( text, endPrevLine );
- if( bIndentToHere )
- {
- float indentNeeded = GetContentRegionAvail().x - widthLeft;
- if( indentNeeded )
- {
- ImGui::Indent( indentNeeded );
- indentX += indentNeeded;
- }
- }
- widthLeft = GetContentRegionAvail().x;
- while( endPrevLine < text_end )
- {
- text = endPrevLine;
- if( *text == ' ' ) { ++text; } // skip a space at start of line
- endPrevLine = pFont->CalcWordWrapPosition( scale, text, text_end, widthLeft );
- if (text == endPrevLine)
- {
- endPrevLine++;
- }
- ImGui::TextUnformatted( text, endPrevLine );
- }
- }
- void RenderListTextWrapped( const char* text, const char* text_end )
- {
- ImGui::Bullet();
- ImGui::SameLine();
- RenderTextWrapped( text, text_end, true );
- }
- void ResetIndent()
- {
- if( indentX > 0.0f )
- {
- ImGui::Unindent( indentX );
- }
- indentX = 0.0f;
- }
- private:
- float indentX;
- ImFont* pFont;
- };
- // Text that starts after a new line (or at beginning) and ends with a newline (or at end)
- struct Line {
- bool isHeading = false;
- bool isUnorderedListStart = false;
- bool isLeadingSpace = true; // spaces at start of line
- int leadSpaceCount = 0;
- int headingCount = 0;
- int lineStart = 0;
- int lineEnd = 0;
- int lastRenderPosition = 0; // lines may get rendered in multiple pieces
- };
- struct TextBlock { // subset of line
- int start = 0;
- int stop = 0;
- int size() const
- {
- return stop - start;
- }
- };
- struct Link {
- enum LinkState {
- NO_LINK,
- HAS_SQUARE_BRACKET_OPEN,
- HAS_SQUARE_BRACKETS,
- HAS_SQUARE_BRACKETS_ROUND_BRACKET_OPEN,
- };
- LinkState state = NO_LINK;
- TextBlock text;
- TextBlock url;
- };
- inline void UnderLine( ImColor col_ )
- {
- ImVec2 min = ImGui::GetItemRectMin();
- ImVec2 max = ImGui::GetItemRectMax();
- min.y = max.y;
- ImGui::GetWindowDrawList()->AddLine( min, max, col_, 1.0f );
- }
- inline void RenderLine( const char* markdown_, Line& line_, TextRegion& textRegion_, const MarkdownConfig& mdConfig_ )
- {
- // indent
- int indentStart = 0;
- if( line_.isUnorderedListStart ) // ImGui unordered list render always adds one indent
- {
- indentStart = 1;
- }
- for( int j = indentStart; j < line_.leadSpaceCount / 2; ++j ) // add indents
- {
- ImGui::Indent();
- }
- // render
- int textStart = line_.lastRenderPosition + 1;
- int textSize = line_.lineEnd - textStart;
- if( line_.isUnorderedListStart ) // render unordered list
- {
- const char* text = markdown_ + textStart + 1;
- textRegion_.RenderListTextWrapped( text, text + textSize - 1 );
- }
- else if( line_.isHeading ) // render heading
- {
- MarkdownConfig::HeadingFormat fmt;
- if( line_.headingCount > mdConfig_.NUMHEADINGS )
- {
- fmt = mdConfig_.headingFormats[ mdConfig_.NUMHEADINGS - 1 ];
- }
- else
- {
- fmt = mdConfig_.headingFormats[ line_.headingCount - 1 ];
- }
- bool popFontRequired = false;
- if( fmt.font && fmt.font != ImGui::GetFont() )
- {
- ImGui::PushFont( fmt.font, 0.0f );
- popFontRequired = true;
- }
- const char* text = markdown_ + textStart + 1;
- ImGui::NewLine();
- textRegion_.RenderTextWrapped( text, text + textSize - 1 );
- if( fmt.separator )
- {
- ImGui::Separator();
- }
- ImGui::NewLine();
- if( popFontRequired )
- {
- ImGui::PopFont();
- }
- }
- else // render a normal paragraph chunk
- {
- const char* text = markdown_ + textStart;
- textRegion_.RenderTextWrapped( text, text + textSize );
- }
-
- // unindent
- for( int j = indentStart; j < line_.leadSpaceCount / 2; ++j )
- {
- ImGui::Unindent();
- }
- }
-
- // render markdown
- void Markdown( const char* markdown_, int32_t markdownLength_, const MarkdownConfig& mdConfig_ )
- {
- ImGuiStyle& style = ImGui::GetStyle();
- Line line;
- Link link;
- TextRegion textRegion;
- char c = 0;
- for( int i=0; i < markdownLength_; ++i )
- {
- c = markdown_[i]; // get the character at index
- if( c == 0 ) { break; } // shouldn't happen but don't go beyond 0.
- // If we're at the beginning of the line, count any spaces
- if( line.isLeadingSpace )
- {
- if( c == ' ' )
- {
- ++line.leadSpaceCount;
- continue;
- }
- else
- {
- line.isLeadingSpace = false;
- line.lastRenderPosition = i - 1;
- if(( c == '*' ) && ( line.leadSpaceCount >= 2 ))
- {
- if(( markdownLength_ > i + 1 ) && ( markdown_[ i + 1 ] == ' ' )) // space after '*'
- {
- line.isUnorderedListStart = true;
- ++i;
- ++line.lastRenderPosition;
- }
- continue;
- }
- else if( c == '#' )
- {
- line.headingCount++;
- bool bContinueChecking = true;
- int32_t j = i;
- while( ++j < markdownLength_ && bContinueChecking )
- {
- c = markdown_[j];
- switch( c )
- {
- case '#':
- line.headingCount++;
- break;
- case ' ':
- line.lastRenderPosition = j - 1;
- i = j;
- line.isHeading = true;
- bContinueChecking = false;
- break;
- default:
- line.isHeading = false;
- bContinueChecking = false;
- break;
- }
- }
- if( line.isHeading ) { continue; }
- }
- }
- }
- // Test to see if we have a link
- switch( link.state )
- {
- case Link::NO_LINK:
- if( c == '[' )
- {
- link.state = Link::HAS_SQUARE_BRACKET_OPEN;
- link.text.start = i + 1;
- }
- break;
- case Link::HAS_SQUARE_BRACKET_OPEN:
- if( c == ']' )
- {
- link.state = Link::HAS_SQUARE_BRACKETS;
- link.text.stop = i;
- }
- break;
- case Link::HAS_SQUARE_BRACKETS:
- if( c == '(' )
- {
- link.state = Link::HAS_SQUARE_BRACKETS_ROUND_BRACKET_OPEN;
- link.url.start = i + 1;
- }
- break;
- case Link::HAS_SQUARE_BRACKETS_ROUND_BRACKET_OPEN:
- if( c == ')' ) // it's a link, render it.
- {
- // render previous line content
- line.lineEnd = link.text.start - 1;
- RenderLine( markdown_, line, textRegion, mdConfig_ );
- line.leadSpaceCount = 0;
- line.isUnorderedListStart = false; // the following text shouldn't have bullets
- // render link
- link.url.stop = i;
- ImGui::SameLine( 0.0f, 0.0f );
- ImGui::PushStyleColor( ImGuiCol_Text, style.Colors[ ImGuiCol_ButtonHovered ]);
- ImGui::PushTextWrapPos(-1.0f);
- const char* text = markdown_ + link.text.start ;
- ImGui::TextUnformatted( text, text + link.text.size() );
- ImGui::PopTextWrapPos();
- ImGui::PopStyleColor();
- if (ImGui::IsItemHovered())
- {
- if( ImGui::IsMouseClicked(0) )
- {
- if( mdConfig_.linkCallback )
- {
- mdConfig_.linkCallback( markdown_ + link.url.start, link.url.size() );
- }
- }
- ImGui::UnderLine( style.Colors[ ImGuiCol_ButtonHovered ] );
- ImGui::SetTooltip( "%s Open in browser\n%.*s", mdConfig_.linkIcon, link.url.size(), markdown_ + link.url.start );
- }
- else
- {
- ImGui::UnderLine( style.Colors[ ImGuiCol_Button ] );
- }
- ImGui::SameLine( 0.0f, 0.0f );
-
- // reset the link by reinitializing it
- link = Link();
- line.lastRenderPosition = i;
- }
- break;
- }
- // handle end of line (render)
- if( c == '\n' )
- {
- // render the line
- line.lineEnd = i;
- RenderLine( markdown_, line, textRegion, mdConfig_ );
- // reset the line
- line = Line();
- line.lineStart = i + 1;
- line.lastRenderPosition = i;
- textRegion.ResetIndent();
-
- // reset the link
- link = Link();
- }
- }
- // render any remaining text if last char wasn't 0
- if( markdownLength_ && line.lineStart < (int)markdownLength_ && markdown_[ line.lineStart ] != 0 )
- {
- line.lineEnd = (int)markdownLength_ - 1;
- RenderLine( markdown_, line, textRegion, mdConfig_ );
- }
- }
- }
|