Added UIMarkdownView and Markdown class (integrated md4c into eepp).

Fixes in HTML rendering.
Fix deadlock in Http pool shutdown.
This commit is contained in:
Martín Lucas Golini
2026-03-05 01:31:25 -03:00
parent 1a20708d78
commit d6fa55a696
27 changed files with 10113 additions and 26 deletions

View File

@@ -363,6 +363,12 @@
"command": "${project_root}/bin/eepp-ui-richtext-debug",
"name": "eepp-ui-richtext-debug",
"working_dir": "${project_root}/bin"
},
{
"args": "",
"command": "${project_root}/bin/eepp-ui-markdownview-debug",
"name": "eepp-ui-markdownview-debug",
"working_dir": "${project_root}/bin"
}
],
"var": {

View File

@@ -774,6 +774,7 @@ class EE_API Http : NonCopyable {
Mutex mThreadsMutex;
bool mIsSSL;
bool mHostSolved;
std::atomic<bool> mShuttingDown{ false };
URI mProxy;
Mutex mCurRequestsMutex;
std::unordered_map<Uint64, AsyncRequest*> mCurRequests;

View File

@@ -0,0 +1,17 @@
#ifndef EE_UI_DOC_MARKDOWNHELPER_HPP
#define EE_UI_DOC_MARKDOWNHELPER_HPP
#include <eepp/config.hpp>
#include <string>
#include <string_view>
namespace EE { namespace UI { namespace Doc {
class EE_API Markdown {
public:
static std::string toXHTML( std::string_view markdown );
};
}}} // namespace EE::UI::Doc
#endif

View File

@@ -828,6 +828,10 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
TextDirection getTextDirection() const;
virtual void loadFromXmlNode( const pugi::xml_node& node );
void disableEditorFeatures( bool useDefaultStyle = true );
protected:
struct LastXOffset {
TextPosition position{ 0, 0 };
@@ -1109,8 +1113,6 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
void resetPreviewColor();
void disableEditorFeatures();
void updateGlyphWidth();
Drawable* findIcon( const std::string& name );
@@ -1190,6 +1192,10 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
}
bool setInternalFontSize( const Float& size );
virtual void onAutoSize();
virtual void onClassChange();
};
}} // namespace EE::UI

View File

@@ -110,6 +110,7 @@ enum UINodeType {
UI_TYPE_NODELINK,
UI_TYPE_TEXTSPAN,
UI_TYPE_RICHTEXT,
UI_TYPE_MARKDOWNVIEW,
UI_TYPE_MODULES = 10000,
UI_TYPE_TERMINAL = 10001,
UI_TYPE_USER = 200000,

View File

@@ -0,0 +1,25 @@
#ifndef EE_UI_UIMARKDOWNVIEW_HPP
#define EE_UI_UIMARKDOWNVIEW_HPP
#include <eepp/ui/uilinearlayout.hpp>
namespace EE { namespace UI {
class EE_API UIMarkdownView : public UILinearLayout {
public:
static UIMarkdownView* New();
UIMarkdownView();
virtual Uint32 getType() const;
virtual bool isType( const Uint32& type ) const;
void loadFromString( std::string_view markdown );
virtual void loadFromXmlNode( const pugi::xml_node& node );
};
}} // namespace EE::UI
#endif

View File

@@ -763,6 +763,7 @@ function add_static_links()
"oniguruma-static",
"libwebp-static",
"libpng-static",
"md4c-static",
}
if not _OPTIONS["without-mojoal"] then
@@ -1321,6 +1322,13 @@ solution "eepp"
build_base_configuration( "mojoal" )
end
project "md4c-static"
kind "StaticLib"
language "C"
set_targetdir("libs/" .. os.get_real() .. "/thirdparty/")
files { "src/thirdparty/md4c/**.c" }
build_base_configuration( "md4c" )
project "libyaml-static"
kind "StaticLib"
language "C"
@@ -1580,6 +1588,12 @@ solution "eepp"
files { "src/examples/ui_richtext/*.cpp" }
build_link_configuration( "eepp-ui-richtext", true )
project "eepp-ui-markdownview"
set_kind()
language "C++"
files { "src/examples/ui_markdownview/*.cpp" }
build_link_configuration( "eepp-ui-markdownview", true )
project "eepp-richtext"
set_kind()
language "C++"

View File

@@ -603,6 +603,7 @@ function add_static_links()
"oniguruma-static",
"libwebp-static",
"libpng-static",
"md4c-static",
}
if not _OPTIONS["without-mojoal"] then
@@ -1225,6 +1226,13 @@ workspace "eepp"
filter { "options:windows-mingw-build", "options:arch=arm64" }
incdirs { remote_sdl2_arm64_cross_tools_path .."/include/" }
project "md4c-static"
kind "StaticLib"
language "C"
files { "src/thirdparty/md4c/**.c" }
build_base_configuration( "md4c" )
target_dir_thirdparty()
project "libyaml-static"
kind "StaticLib"
language "C"
@@ -1464,6 +1472,12 @@ workspace "eepp"
files { "src/examples/ui_richtext/*.cpp" }
build_link_configuration( "eepp-ui-richtext", true )
project "eepp-ui-markdownview"
set_kind()
language "C++"
files { "src/examples/ui_markdownview/*.cpp" }
build_link_configuration( "eepp-ui-markdownview", true )
project "eepp-richtext"
set_kind()
language "C++"

View File

@@ -111,7 +111,7 @@ LOCAL_C_INCLUDES := $(EEPP_C_INCLUDES)
LOCAL_SRC_FILES := $(foreach F, $(CODE_SRCS), $(addprefix $(dir $(F)),$(notdir $(wildcard $(LOCAL_PATH)/$(F)))))
LOCAL_STATIC_LIBRARIES := freetype libpng libwebp pcre2 oniguruma harfbuzz sheenbidi
LOCAL_STATIC_LIBRARIES := freetype libpng libwebp md4c pcre2 oniguruma harfbuzz sheenbidi
LOCAL_SHARED_LIBRARIES := SDL2
@@ -367,6 +367,23 @@ LOCAL_SRC_FILES := $(foreach F, $(LIBYAML_SRCS), $(addprefix $(dir $(F)),$(not
include $(BUILD_STATIC_LIBRARY)
#*************** LIBYAML ***************
#*************** MD4C ***************
include $(CLEAR_VARS)
LOCAL_PATH := $(EEPP_THIRD_PARTY_PATH)
LOCAL_MODULE := md4c
MD4C_SRCS := md4c/src/*.c
LOCAL_C_INCLUDES := $(LOCAL_PATH)/md4c
LOCAL_CFLAGS := -Os
LOCAL_SRC_FILES := $(foreach F, $(MD4C_SRCS), $(addprefix $(dir $(F)),$(notdir $(wildcard $(LOCAL_PATH)/$(F)))))
include $(BUILD_STATIC_LIBRARY)
#*************** MD4C ***************
#**************** SDL 2 ***************
include $(CLEAR_VARS)

View File

@@ -669,22 +669,7 @@ Http::Http( const std::string& host, unsigned short port, bool useSSL, URI proxy
}
Http::~Http() {
{
Lock l( mThreadsMutex );
// First we wait to finish any request pending
for ( auto&& itt : mThreads ) {
itt->cancel();
itt->wait();
}
}
{
Lock l( mThreadsMutex );
for ( auto&& itt : mThreads ) {
eeDelete( itt );
}
}
mShuttingDown = true;
{
Lock l( mCurRequestsMutex );
@@ -692,6 +677,16 @@ Http::~Http() {
req->cancel();
}
{
Lock l( mThreadsMutex );
// First we wait to finish any request pending
for ( auto& thread : mThreads ) {
thread->cancel();
thread->wait();
eeDelete( thread );
}
}
// Then we destroy the last open connection
HttpConnection* connection = mConnection;
@@ -1258,7 +1253,7 @@ void Http::AsyncRequest::run() {
mRunning = false;
if ( mFromLocalPool ) {
if ( mFromLocalPool && !mHttp->mShuttingDown ) {
mHttp->removeAsyncRequest( this );
auto me = this;
eeSAFE_DELETE( me );

View File

@@ -218,7 +218,7 @@ void StyleSheetSpecification::registerDefaultProperties() {
.setType( PropertyType::Color )
.addAlias( "fontoutlinecolor" );
registerProperty( "text-selection", "" ).setType( PropertyType::Bool );
registerProperty( "text-align", "" );
registerProperty( "text-align", "" ).addAlias( "align" );
registerProperty( "icon", "" );
registerProperty( "min-icon-size", "" ).setType( PropertyType::Vector2 );
registerProperty( "src", "" ).setType( PropertyType::String );

View File

@@ -0,0 +1,18 @@
#include <eepp/ui/doc/markdownhelper.hpp>
#include <md4c/md4c-html.h>
namespace EE { namespace UI { namespace Doc {
static void process_output( const MD_CHAR* text, MD_SIZE size, void* userdata ) {
std::string* out = static_cast<std::string*>( userdata );
out->append( text, size );
}
std::string Markdown::toXHTML( std::string_view markdown ) {
std::string out;
md_html( markdown.data(), markdown.size(), process_output, &out, MD_DIALECT_GITHUB,
MD_HTML_FLAG_XHTML );
return out;
}
}}} // namespace EE::UI::Doc

View File

@@ -26,6 +26,9 @@
#include <eepp/window/input.hpp>
#include <eepp/window/window.hpp>
#define PUGIXML_HEADER_ONLY
#include <pugixml/pugixml.hpp>
using namespace EE::UI::Tools;
namespace EE { namespace UI {
@@ -142,12 +145,16 @@ UICodeEditor::UICodeEditor( const std::string& elementTag, const bool& autoRegis
mPreviewColor( Color::Transparent ),
mKeyBindings( getInput() ),
mFindLongestLineWidthUpdateFrequency( Seconds( 1 ) ) {
mWidthPolicy = SizePolicy::Fixed;
mHeightPolicy = SizePolicy::Fixed;
mFlags |= UI_TAB_STOP | UI_OWNS_CHILDREN_POSITION | UI_SCROLLABLE;
setTextSelection( true );
setColorScheme( SyntaxColorScheme::getDefault() );
refreshTag();
mDocView.setOnVisibleLineCountChange(
[this] { sendCommonEvent( Event::OnVisibleLinesCountChange ); } );
mDocView.setOnVisibleLineCountChange( [this] {
onAutoSize();
sendCommonEvent( Event::OnVisibleLinesCountChange );
} );
mDocView.setOnFoldUnfoldCb(
[this]( auto, auto ) { sendCommonEvent( Event::OnFoldUnfoldRange ); } );
mVScrollBar = UIScrollBar::NewVertical();
@@ -725,7 +732,7 @@ Uint32 UICodeEditor::onMessage( const NodeMessage* msg ) {
return UIWidget::onMessage( msg );
}
void UICodeEditor::disableEditorFeatures() {
void UICodeEditor::disableEditorFeatures( bool useDefaultStyle ) {
mShowLineNumber = false;
mShowWhitespaces = false;
mShowFoldingRegion = false;
@@ -736,7 +743,7 @@ void UICodeEditor::disableEditorFeatures() {
mMinimapEnabled = false;
mFindReplaceEnabled = false;
mLineBreakingColumn = 0;
mUseDefaultStyle = true;
mUseDefaultStyle = useDefaultStyle;
}
Float UICodeEditor::getViewportWidth( bool forceVScroll, bool includeMinimap ) const {
@@ -3386,6 +3393,10 @@ Float UICodeEditor::getGlyphWidth() const {
}
void UICodeEditor::updateGlyphWidth() {
if ( !mFont ) {
mGlyphWidth = 0;
return;
}
mGlyphWidth = mFont->getGlyph( '@', getCharacterSize(), false, false ).advance;
mMouseWheelScroll = 3 * getLineHeight();
invalidateLongestLineWidth();
@@ -5510,4 +5521,38 @@ TextDirection UICodeEditor::getTextDirection() const {
return mTextDirection;
}
void UICodeEditor::loadFromXmlNode( const pugi::xml_node& node ) {
beginAttributesTransaction();
UIWidget::loadFromXmlNode( node );
if ( !node.text().empty() ) {
std::string_view str{ node.text().as_string() };
if ( '\n' == str.back() )
str = str.substr( 0, str.size() - 1 );
mDoc->textInput( str );
}
endAttributesTransaction();
}
void UICodeEditor::onAutoSize() {
if ( mHeightPolicy == SizePolicy::WrapContent ) {
auto visibleLineCount = getDocumentView().getVisibleLinesCount();
Float lineHeight = getLineHeight();
Float height =
lineHeight * visibleLineCount + getPixelsPadding().Top + getPixelsPadding().Bottom;
setPixelsSize( getPixelsSize().getWidth(), height );
}
}
void UICodeEditor::onClassChange() {
for ( const auto& name : mClasses ) {
if ( String::startsWith( name, "language-" ) ) {
auto langname = std::string_view{ name }.substr( 9 );
setSyntaxDefinition( SyntaxDefinitionManager::instance()->findFromString( langname ) );
}
}
}
}} // namespace EE::UI

View File

@@ -282,6 +282,16 @@ bool UIImage::applyProperty( const StyleSheetProperty& attribute ) {
return false;
switch ( attribute.getPropertyDefinition()->getPropertyId() ) {
case PropertyId::TextAlign: {
std::string align = String::toLower( attribute.value() );
if ( align == "center" )
setHorizontalAlign( UI_HALIGN_CENTER );
else if ( align == "left" )
setHorizontalAlign( UI_HALIGN_LEFT );
else if ( align == "right" )
setHorizontalAlign( UI_HALIGN_RIGHT );
break;
}
case PropertyId::Src: {
std::string path( attribute.getValue() );
bool ownIt;

View File

@@ -0,0 +1,41 @@
#include <eepp/ui/doc/markdownhelper.hpp>
#include <eepp/ui/uimarkdownview.hpp>
#include <eepp/ui/uiscenenode.hpp>
#define PUGIXML_HEADER_ONLY
#include <pugixml/pugixml.hpp>
using namespace EE::UI::Doc;
namespace EE { namespace UI {
UIMarkdownView* UIMarkdownView::New() {
return eeNew( UIMarkdownView, () );
}
UIMarkdownView::UIMarkdownView() : UILinearLayout( "markdownview", UIOrientation::Vertical ) {
mWidthPolicy = SizePolicy::MatchParent;
mHeightPolicy = SizePolicy::WrapContent;
}
Uint32 UIMarkdownView::getType() const {
return UI_TYPE_MARKDOWNVIEW;
}
bool UIMarkdownView::isType( const Uint32& type ) const {
return UIMarkdownView::getType() == type || UILinearLayout::isType( type );
}
void UIMarkdownView::loadFromString( std::string_view markdown ) {
closeAllChildren();
std::string xhtml = Markdown::toXHTML( markdown );
getUISceneNode()->loadLayoutFromString( xhtml, this );
}
void UIMarkdownView::loadFromXmlNode( const pugi::xml_node& node ) {
UILinearLayout::loadFromXmlNode( node );
if ( !node.text().empty() )
loadFromString( node.text().as_string() );
}
}} // namespace EE::UI

View File

@@ -1,6 +1,7 @@
#include <eepp/graphics/fontmanager.hpp>
#include <eepp/graphics/text.hpp>
#include <eepp/ui/css/propertydefinition.hpp>
#include <eepp/ui/uicodeeditor.hpp>
#include <eepp/ui/uirichtext.hpp>
#include <eepp/ui/uiscenenode.hpp>
#include <eepp/ui/uitextspan.hpp>
@@ -332,6 +333,25 @@ void UIRichText::loadFromXmlNode( const pugi::xml_node& node ) {
UITextSpan* span = UITextSpan::New();
span->setParent( this );
span->loadFromXmlNode( child );
} else if ( mTag == "pre" && String::iequals( child.name(), "code" ) ) {
// Use a UICodeEditor for <pre><code>
UICodeEditor* editor = UICodeEditor::New();
if ( editor ) {
editor->setParent( this );
editor->loadFromXmlNode( child );
editor->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::WrapContent );
editor->setLineWrapMode( LineWrapMode::Word );
editor->setLineWrapType( LineWrapType::Viewport );
editor->disableEditorFeatures( false );
editor->setCursorVisible( false );
editor->setAllowSelectingTextFromGutter( false );
editor->setDisableCursorBlinkingAfterAMinuteOfInactivity( false );
editor->setCursorBlinkTime( Time::Zero );
editor->setShowLineNumber( false );
editor->setShowFoldingRegion( false );
editor->setLocked( true );
}
} else {
// Let parent logic load standard child widget
UIWidget* widget = UIWidgetCreator::createFromName( child.name() );

View File

@@ -16,6 +16,7 @@
#include <eepp/ui/uiwidget.hpp>
#include <eepp/window/engine.hpp>
#include <eepp/window/window.hpp>
#define PUGIXML_HEADER_ONLY
#include <pugixml/pugixml.hpp>

View File

@@ -11,6 +11,7 @@
#include <eepp/ui/uilistbox.hpp>
#include <eepp/ui/uilistview.hpp>
#include <eepp/ui/uiloader.hpp>
#include <eepp/ui/uimarkdownview.hpp>
#include <eepp/ui/uimenubar.hpp>
#include <eepp/ui/uinodelink.hpp>
#include <eepp/ui/uiprogressbar.hpp>
@@ -110,6 +111,7 @@ void UIWidgetCreator::createBaseWidgetList() {
registeredWidget["imageviewer"] = Tools::UIImageViewer::New;
registeredWidget["richtext"] = UIRichText::New;
registeredWidget["textspan"] = UITextSpan::New;
registeredWidget["markdownview"] = UIMarkdownView::New;
registeredWidget["hbox"] = UILinearLayout::NewHorizontal;
registeredWidget["vbox"] = UILinearLayout::NewVertical;
@@ -150,7 +152,7 @@ void UIWidgetCreator::createBaseWidgetList() {
registeredWidget["ul"] = UILinearLayout::NewVerticalWidthMatchParent;
registeredWidget["ol"] = UILinearLayout::NewVerticalWidthMatchParent;
registeredWidget["li"] = UIRichText::NewListItem;
registeredWidget["pre"] = UITextSpan::New;
registeredWidget["pre"] = [] { return UIRichText::NewWithTag( "pre" ); };
registeredWidget["img"] = [] { return UIImage::NewWithTag( "img" ); };
sBaseListCreated = true;

View File

@@ -0,0 +1,69 @@
#include <eepp/ee.hpp>
#include <eepp/ui/uimarkdownview.hpp>
EE_MAIN_FUNC int main( int, char** ) {
UIApplication app( { 800, 600, "eepp - UIMarkdownView Example" } );
Log::instance()->setLiveWrite( true );
Log::instance()->setLogToStdOut( true );
app.getUI()->loadLayoutFromString( R"xml(
<vbox layout_width="match_parent" layout_height="match_parent">
<ScrollView lw="mp" lh="mp">
<MarkdownView id="markdown_view" layout_width="match_parent" layout_height="wrap_content" padding="16dp">
# Markdown Header 1
## Markdown Header 2
### Markdown Header 3
This is a **bold** text and this is an *italic* text.
* Item 1
* Item 2
* Item 3
1. Ordered 1
2. Ordered 2
&gt; This is a blockquote.
`inline code`
```cpp
void main() {
printf("Hello World");
}
```
</MarkdownView>
</ScrollView>
</vbox>
)xml" );
auto markdownView = app.getUI()->find<UIMarkdownView>( "markdown_view" );
app.getWindow()->getInput()->pushCallback( [markdownView]( InputEvent* event ) {
switch ( event->Type ) {
case InputEvent::FileDropped: {
std::string file( event->file.file );
std::string data;
FileSystem::fileGet( file, data );
markdownView->loadFromString( data );
break;
}
case InputEvent::TextDropped: {
markdownView->loadFromString( event->textdrop.text );
break;
}
default:
break;
}
} );
app.getUI()->on( Event::KeyUp, [&app]( const Event* event ) {
if ( event->asKeyEvent()->getKeyCode() == KEY_F11 ) {
UIWidgetInspector::create( app.getUI() );
}
} );
return app.run();
}

View File

@@ -2,6 +2,7 @@
EE_MAIN_FUNC int main( int, char** ) {
UIApplication app( { 800, 600, "eepp - UIRichText Example" } );
app.getUI()->loadLayoutFromString( R"xml(
<vbox layout_width="match_parent" layout_height="match_parent">
<ScrollView lw="mp" lh="mp">

22
src/thirdparty/md4c/LICENSE.md vendored Normal file
View File

@@ -0,0 +1,22 @@
# The MIT License (MIT)
Copyright © 2016-2024 Martin Mitáš
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the “Software”),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.

2185
src/thirdparty/md4c/entity.c vendored Normal file

File diff suppressed because it is too large Load Diff

43
src/thirdparty/md4c/entity.h vendored Normal file
View File

@@ -0,0 +1,43 @@
/*
* MD4C: Markdown parser for C
* (http://github.com/mity/md4c)
*
* Copyright (c) 2016-2024 Martin Mitáš
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef MD4C_ENTITY_H
#define MD4C_ENTITY_H
#include <stdlib.h>
/* Most entities are formed by single Unicode codepoint, few by two codepoints.
* Single-codepoint entities have codepoints[1] set to zero. */
typedef struct ENTITY_tag ENTITY;
struct ENTITY_tag {
const char* name;
unsigned codepoints[2];
};
const ENTITY* entity_lookup(const char* name, size_t name_size);
#endif /* MD4C_ENTITY_H */

567
src/thirdparty/md4c/md4c-html.c vendored Normal file
View File

@@ -0,0 +1,567 @@
/*
* MD4C: Markdown parser for C
* (http://github.com/mity/md4c)
*
* Copyright (c) 2016-2024 Martin Mitáš
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <stdio.h>
#include <string.h>
#include "md4c-html.h"
#include "entity.h"
#if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 199409L
/* C89/90 or old compilers in general may not understand "inline". */
#if defined __GNUC__
#define inline __inline__
#elif defined _MSC_VER
#define inline __inline
#else
#define inline
#endif
#endif
#ifdef _WIN32
#define snprintf _snprintf
#endif
typedef struct MD_HTML_tag MD_HTML;
struct MD_HTML_tag {
void (*process_output)(const MD_CHAR*, MD_SIZE, void*);
void* userdata;
unsigned flags;
int image_nesting_level;
char escape_map[256];
};
#define NEED_HTML_ESC_FLAG 0x1
#define NEED_URL_ESC_FLAG 0x2
/*****************************************
*** HTML rendering helper functions ***
*****************************************/
#define ISDIGIT(ch) ('0' <= (ch) && (ch) <= '9')
#define ISLOWER(ch) ('a' <= (ch) && (ch) <= 'z')
#define ISUPPER(ch) ('A' <= (ch) && (ch) <= 'Z')
#define ISALNUM(ch) (ISLOWER(ch) || ISUPPER(ch) || ISDIGIT(ch))
static inline void
render_verbatim(MD_HTML* r, const MD_CHAR* text, MD_SIZE size)
{
r->process_output(text, size, r->userdata);
}
/* Keep this as a macro. Most compiler should then be smart enough to replace
* the strlen() call with a compile-time constant if the string is a C literal. */
#define RENDER_VERBATIM(r, verbatim) \
render_verbatim((r), (verbatim), (MD_SIZE) (strlen(verbatim)))
static void
render_html_escaped(MD_HTML* r, const MD_CHAR* data, MD_SIZE size)
{
MD_OFFSET beg = 0;
MD_OFFSET off = 0;
/* Some characters need to be escaped in normal HTML text. */
#define NEED_HTML_ESC(ch) (r->escape_map[(unsigned char)(ch)] & NEED_HTML_ESC_FLAG)
while(1) {
/* Optimization: Use some loop unrolling. */
while(off + 3 < size && !NEED_HTML_ESC(data[off+0]) && !NEED_HTML_ESC(data[off+1])
&& !NEED_HTML_ESC(data[off+2]) && !NEED_HTML_ESC(data[off+3]))
off += 4;
while(off < size && !NEED_HTML_ESC(data[off]))
off++;
if(off > beg)
render_verbatim(r, data + beg, off - beg);
if(off < size) {
switch(data[off]) {
case '&': RENDER_VERBATIM(r, "&amp;"); break;
case '<': RENDER_VERBATIM(r, "&lt;"); break;
case '>': RENDER_VERBATIM(r, "&gt;"); break;
case '"': RENDER_VERBATIM(r, "&quot;"); break;
}
off++;
} else {
break;
}
beg = off;
}
}
static void
render_url_escaped(MD_HTML* r, const MD_CHAR* data, MD_SIZE size)
{
static const MD_CHAR hex_chars[] = "0123456789ABCDEF";
MD_OFFSET beg = 0;
MD_OFFSET off = 0;
/* Some characters need to be escaped in URL attributes. */
#define NEED_URL_ESC(ch) (r->escape_map[(unsigned char)(ch)] & NEED_URL_ESC_FLAG)
while(1) {
while(off < size && !NEED_URL_ESC(data[off]))
off++;
if(off > beg)
render_verbatim(r, data + beg, off - beg);
if(off < size) {
char hex[3];
switch(data[off]) {
case '&': RENDER_VERBATIM(r, "&amp;"); break;
default:
hex[0] = '%';
hex[1] = hex_chars[((unsigned)data[off] >> 4) & 0xf];
hex[2] = hex_chars[((unsigned)data[off] >> 0) & 0xf];
render_verbatim(r, hex, 3);
break;
}
off++;
} else {
break;
}
beg = off;
}
}
static unsigned
hex_val(char ch)
{
if('0' <= ch && ch <= '9')
return ch - '0';
if('A' <= ch && ch <= 'Z')
return ch - 'A' + 10;
else
return ch - 'a' + 10;
}
static void
render_utf8_codepoint(MD_HTML* r, unsigned codepoint,
void (*fn_append)(MD_HTML*, const MD_CHAR*, MD_SIZE))
{
static const MD_CHAR utf8_replacement_char[] = { (char)0xef, (char)0xbf, (char)0xbd };
unsigned char utf8[4];
size_t n;
if(codepoint <= 0x7f) {
n = 1;
utf8[0] = codepoint;
} else if(codepoint <= 0x7ff) {
n = 2;
utf8[0] = 0xc0 | ((codepoint >> 6) & 0x1f);
utf8[1] = 0x80 + ((codepoint >> 0) & 0x3f);
} else if(codepoint <= 0xffff) {
n = 3;
utf8[0] = 0xe0 | ((codepoint >> 12) & 0xf);
utf8[1] = 0x80 + ((codepoint >> 6) & 0x3f);
utf8[2] = 0x80 + ((codepoint >> 0) & 0x3f);
} else {
n = 4;
utf8[0] = 0xf0 | ((codepoint >> 18) & 0x7);
utf8[1] = 0x80 + ((codepoint >> 12) & 0x3f);
utf8[2] = 0x80 + ((codepoint >> 6) & 0x3f);
utf8[3] = 0x80 + ((codepoint >> 0) & 0x3f);
}
if(0 < codepoint && codepoint <= 0x10ffff)
fn_append(r, (char*)utf8, (MD_SIZE)n);
else
fn_append(r, utf8_replacement_char, 3);
}
/* Translate entity to its UTF-8 equivalent, or output the verbatim one
* if such entity is unknown (or if the translation is disabled). */
static void
render_entity(MD_HTML* r, const MD_CHAR* text, MD_SIZE size,
void (*fn_append)(MD_HTML*, const MD_CHAR*, MD_SIZE))
{
if(r->flags & MD_HTML_FLAG_VERBATIM_ENTITIES) {
render_verbatim(r, text, size);
return;
}
/* We assume UTF-8 output is what is desired. */
if(size > 3 && text[1] == '#') {
unsigned codepoint = 0;
if(text[2] == 'x' || text[2] == 'X') {
/* Hexadecimal entity (e.g. "&#x1234abcd;")). */
MD_SIZE i;
for(i = 3; i < size-1; i++)
codepoint = 16 * codepoint + hex_val(text[i]);
} else {
/* Decimal entity (e.g. "&1234;") */
MD_SIZE i;
for(i = 2; i < size-1; i++)
codepoint = 10 * codepoint + (text[i] - '0');
}
render_utf8_codepoint(r, codepoint, fn_append);
return;
} else {
/* Named entity (e.g. "&nbsp;"). */
const ENTITY* ent;
ent = entity_lookup(text, size);
if(ent != NULL) {
render_utf8_codepoint(r, ent->codepoints[0], fn_append);
if(ent->codepoints[1])
render_utf8_codepoint(r, ent->codepoints[1], fn_append);
return;
}
}
fn_append(r, text, size);
}
static void
render_attribute(MD_HTML* r, const MD_ATTRIBUTE* attr,
void (*fn_append)(MD_HTML*, const MD_CHAR*, MD_SIZE))
{
int i;
for(i = 0; attr->substr_offsets[i] < attr->size; i++) {
MD_TEXTTYPE type = attr->substr_types[i];
MD_OFFSET off = attr->substr_offsets[i];
MD_SIZE size = attr->substr_offsets[i+1] - off;
const MD_CHAR* text = attr->text + off;
switch(type) {
case MD_TEXT_NULLCHAR: render_utf8_codepoint(r, 0x0000, render_verbatim); break;
case MD_TEXT_ENTITY: render_entity(r, text, size, fn_append); break;
default: fn_append(r, text, size); break;
}
}
}
static void
render_open_ol_block(MD_HTML* r, const MD_BLOCK_OL_DETAIL* det)
{
char buf[64];
if(det->start == 1) {
RENDER_VERBATIM(r, "<ol>\n");
return;
}
snprintf(buf, sizeof(buf), "<ol start=\"%u\">\n", det->start);
RENDER_VERBATIM(r, buf);
}
static void
render_open_li_block(MD_HTML* r, const MD_BLOCK_LI_DETAIL* det)
{
if(det->is_task) {
RENDER_VERBATIM(r, "<li class=\"task-list-item\">"
"<input type=\"checkbox\" class=\"task-list-item-checkbox\" disabled");
if(det->task_mark == 'x' || det->task_mark == 'X')
RENDER_VERBATIM(r, " checked");
RENDER_VERBATIM(r, ">");
} else {
RENDER_VERBATIM(r, "<li>");
}
}
static void
render_open_code_block(MD_HTML* r, const MD_BLOCK_CODE_DETAIL* det)
{
RENDER_VERBATIM(r, "<pre><code");
/* If known, output the HTML 5 attribute class="language-LANGNAME". */
if(det->lang.text != NULL) {
RENDER_VERBATIM(r, " class=\"language-");
render_attribute(r, &det->lang, render_html_escaped);
RENDER_VERBATIM(r, "\"");
}
RENDER_VERBATIM(r, ">");
}
static void
render_open_td_block(MD_HTML* r, const MD_CHAR* cell_type, const MD_BLOCK_TD_DETAIL* det)
{
RENDER_VERBATIM(r, "<");
RENDER_VERBATIM(r, cell_type);
switch(det->align) {
case MD_ALIGN_LEFT: RENDER_VERBATIM(r, " align=\"left\">"); break;
case MD_ALIGN_CENTER: RENDER_VERBATIM(r, " align=\"center\">"); break;
case MD_ALIGN_RIGHT: RENDER_VERBATIM(r, " align=\"right\">"); break;
default: RENDER_VERBATIM(r, ">"); break;
}
}
static void
render_open_a_span(MD_HTML* r, const MD_SPAN_A_DETAIL* det)
{
RENDER_VERBATIM(r, "<a href=\"");
render_attribute(r, &det->href, render_url_escaped);
if(det->title.text != NULL) {
RENDER_VERBATIM(r, "\" title=\"");
render_attribute(r, &det->title, render_html_escaped);
}
RENDER_VERBATIM(r, "\">");
}
static void
render_open_img_span(MD_HTML* r, const MD_SPAN_IMG_DETAIL* det)
{
RENDER_VERBATIM(r, "<img src=\"");
render_attribute(r, &det->src, render_url_escaped);
RENDER_VERBATIM(r, "\" alt=\"");
}
static void
render_close_img_span(MD_HTML* r, const MD_SPAN_IMG_DETAIL* det)
{
if(det->title.text != NULL) {
RENDER_VERBATIM(r, "\" title=\"");
render_attribute(r, &det->title, render_html_escaped);
}
RENDER_VERBATIM(r, (r->flags & MD_HTML_FLAG_XHTML) ? "\" />" : "\">");
}
static void
render_open_wikilink_span(MD_HTML* r, const MD_SPAN_WIKILINK_DETAIL* det)
{
RENDER_VERBATIM(r, "<x-wikilink data-target=\"");
render_attribute(r, &det->target, render_html_escaped);
RENDER_VERBATIM(r, "\">");
}
/**************************************
*** HTML renderer implementation ***
**************************************/
static int
enter_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
{
static const MD_CHAR* head[6] = { "<h1>", "<h2>", "<h3>", "<h4>", "<h5>", "<h6>" };
MD_HTML* r = (MD_HTML*) userdata;
switch(type) {
case MD_BLOCK_DOC: /* noop */ break;
case MD_BLOCK_QUOTE: RENDER_VERBATIM(r, "<blockquote>\n"); break;
case MD_BLOCK_UL: RENDER_VERBATIM(r, "<ul>\n"); break;
case MD_BLOCK_OL: render_open_ol_block(r, (const MD_BLOCK_OL_DETAIL*)detail); break;
case MD_BLOCK_LI: render_open_li_block(r, (const MD_BLOCK_LI_DETAIL*)detail); break;
case MD_BLOCK_HR: RENDER_VERBATIM(r, (r->flags & MD_HTML_FLAG_XHTML) ? "<hr />\n" : "<hr>\n"); break;
case MD_BLOCK_H: RENDER_VERBATIM(r, head[((MD_BLOCK_H_DETAIL*)detail)->level - 1]); break;
case MD_BLOCK_CODE: render_open_code_block(r, (const MD_BLOCK_CODE_DETAIL*) detail); break;
case MD_BLOCK_HTML: /* noop */ break;
case MD_BLOCK_P: RENDER_VERBATIM(r, "<p>"); break;
case MD_BLOCK_TABLE: RENDER_VERBATIM(r, "<table>\n"); break;
case MD_BLOCK_THEAD: RENDER_VERBATIM(r, "<thead>\n"); break;
case MD_BLOCK_TBODY: RENDER_VERBATIM(r, "<tbody>\n"); break;
case MD_BLOCK_TR: RENDER_VERBATIM(r, "<tr>\n"); break;
case MD_BLOCK_TH: render_open_td_block(r, "th", (MD_BLOCK_TD_DETAIL*)detail); break;
case MD_BLOCK_TD: render_open_td_block(r, "td", (MD_BLOCK_TD_DETAIL*)detail); break;
}
return 0;
}
static int
leave_block_callback(MD_BLOCKTYPE type, void* detail, void* userdata)
{
static const MD_CHAR* head[6] = { "</h1>\n", "</h2>\n", "</h3>\n", "</h4>\n", "</h5>\n", "</h6>\n" };
MD_HTML* r = (MD_HTML*) userdata;
switch(type) {
case MD_BLOCK_DOC: /*noop*/ break;
case MD_BLOCK_QUOTE: RENDER_VERBATIM(r, "</blockquote>\n"); break;
case MD_BLOCK_UL: RENDER_VERBATIM(r, "</ul>\n"); break;
case MD_BLOCK_OL: RENDER_VERBATIM(r, "</ol>\n"); break;
case MD_BLOCK_LI: RENDER_VERBATIM(r, "</li>\n"); break;
case MD_BLOCK_HR: /*noop*/ break;
case MD_BLOCK_H: RENDER_VERBATIM(r, head[((MD_BLOCK_H_DETAIL*)detail)->level - 1]); break;
case MD_BLOCK_CODE: RENDER_VERBATIM(r, "</code></pre>\n"); break;
case MD_BLOCK_HTML: /* noop */ break;
case MD_BLOCK_P: RENDER_VERBATIM(r, "</p>\n"); break;
case MD_BLOCK_TABLE: RENDER_VERBATIM(r, "</table>\n"); break;
case MD_BLOCK_THEAD: RENDER_VERBATIM(r, "</thead>\n"); break;
case MD_BLOCK_TBODY: RENDER_VERBATIM(r, "</tbody>\n"); break;
case MD_BLOCK_TR: RENDER_VERBATIM(r, "</tr>\n"); break;
case MD_BLOCK_TH: RENDER_VERBATIM(r, "</th>\n"); break;
case MD_BLOCK_TD: RENDER_VERBATIM(r, "</td>\n"); break;
}
return 0;
}
static int
enter_span_callback(MD_SPANTYPE type, void* detail, void* userdata)
{
MD_HTML* r = (MD_HTML*) userdata;
int inside_img = (r->image_nesting_level > 0);
/* We are inside a Markdown image label. Markdown allows to use any emphasis
* and other rich contents in that context similarly as in any link label.
*
* However, unlike in the case of links (where that contents becomescontents
* of the <a>...</a> tag), in the case of images the contents is supposed to
* fall into the attribute alt: <img alt="...">.
*
* In that context we naturally cannot output nested HTML tags. So lets
* suppress them and only output the plain text (i.e. what falls into text()
* callback).
*
* CommonMark specification declares this a recommended practice for HTML
* output.
*/
if(type == MD_SPAN_IMG)
r->image_nesting_level++;
if(inside_img)
return 0;
switch(type) {
case MD_SPAN_EM: RENDER_VERBATIM(r, "<em>"); break;
case MD_SPAN_STRONG: RENDER_VERBATIM(r, "<strong>"); break;
case MD_SPAN_U: RENDER_VERBATIM(r, "<u>"); break;
case MD_SPAN_A: render_open_a_span(r, (MD_SPAN_A_DETAIL*) detail); break;
case MD_SPAN_IMG: render_open_img_span(r, (MD_SPAN_IMG_DETAIL*) detail); break;
case MD_SPAN_CODE: RENDER_VERBATIM(r, "<code>"); break;
case MD_SPAN_DEL: RENDER_VERBATIM(r, "<del>"); break;
case MD_SPAN_LATEXMATH: RENDER_VERBATIM(r, "<x-equation>"); break;
case MD_SPAN_LATEXMATH_DISPLAY: RENDER_VERBATIM(r, "<x-equation type=\"display\">"); break;
case MD_SPAN_WIKILINK: render_open_wikilink_span(r, (MD_SPAN_WIKILINK_DETAIL*) detail); break;
}
return 0;
}
static int
leave_span_callback(MD_SPANTYPE type, void* detail, void* userdata)
{
MD_HTML* r = (MD_HTML*) userdata;
if(type == MD_SPAN_IMG)
r->image_nesting_level--;
if(r->image_nesting_level > 0)
return 0;
switch(type) {
case MD_SPAN_EM: RENDER_VERBATIM(r, "</em>"); break;
case MD_SPAN_STRONG: RENDER_VERBATIM(r, "</strong>"); break;
case MD_SPAN_U: RENDER_VERBATIM(r, "</u>"); break;
case MD_SPAN_A: RENDER_VERBATIM(r, "</a>"); break;
case MD_SPAN_IMG: render_close_img_span(r, (MD_SPAN_IMG_DETAIL*) detail); break;
case MD_SPAN_CODE: RENDER_VERBATIM(r, "</code>"); break;
case MD_SPAN_DEL: RENDER_VERBATIM(r, "</del>"); break;
case MD_SPAN_LATEXMATH: /*fall through*/
case MD_SPAN_LATEXMATH_DISPLAY: RENDER_VERBATIM(r, "</x-equation>"); break;
case MD_SPAN_WIKILINK: RENDER_VERBATIM(r, "</x-wikilink>"); break;
}
return 0;
}
static int
text_callback(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, void* userdata)
{
MD_HTML* r = (MD_HTML*) userdata;
switch(type) {
case MD_TEXT_NULLCHAR: render_utf8_codepoint(r, 0x0000, render_verbatim); break;
case MD_TEXT_BR: RENDER_VERBATIM(r, (r->image_nesting_level == 0
? ((r->flags & MD_HTML_FLAG_XHTML) ? "<br />\n" : "<br>\n")
: " "));
break;
case MD_TEXT_SOFTBR: RENDER_VERBATIM(r, (r->image_nesting_level == 0 ? "\n" : " ")); break;
case MD_TEXT_HTML: render_verbatim(r, text, size); break;
case MD_TEXT_ENTITY: render_entity(r, text, size, render_html_escaped); break;
default: render_html_escaped(r, text, size); break;
}
return 0;
}
static void
debug_log_callback(const char* msg, void* userdata)
{
MD_HTML* r = (MD_HTML*) userdata;
if(r->flags & MD_HTML_FLAG_DEBUG)
fprintf(stderr, "MD4C: %s\n", msg);
}
int
md_html(const MD_CHAR* input, MD_SIZE input_size,
void (*process_output)(const MD_CHAR*, MD_SIZE, void*),
void* userdata, unsigned parser_flags, unsigned renderer_flags)
{
MD_HTML render = { process_output, userdata, renderer_flags, 0, { 0 } };
int i;
MD_PARSER parser = {
0,
parser_flags,
enter_block_callback,
leave_block_callback,
enter_span_callback,
leave_span_callback,
text_callback,
debug_log_callback,
NULL
};
/* Build map of characters which need escaping. */
for(i = 0; i < 256; i++) {
unsigned char ch = (unsigned char) i;
if(strchr("\"&<>", ch) != NULL)
render.escape_map[i] |= NEED_HTML_ESC_FLAG;
if(!ISALNUM(ch) && strchr("~-_.+!*(),%#@?=;:/,+$", ch) == NULL)
render.escape_map[i] |= NEED_URL_ESC_FLAG;
}
/* Consider skipping UTF-8 byte order mark (BOM). */
if(renderer_flags & MD_HTML_FLAG_SKIP_UTF8_BOM && sizeof(MD_CHAR) == 1) {
static const MD_CHAR bom[3] = { (char)0xef, (char)0xbb, (char)0xbf };
if(input_size >= sizeof(bom) && memcmp(input, bom, sizeof(bom)) == 0) {
input += sizeof(bom);
input_size -= sizeof(bom);
}
}
return md_parse(input, input_size, &parser, (void*) &render);
}

68
src/thirdparty/md4c/md4c-html.h vendored Normal file
View File

@@ -0,0 +1,68 @@
/*
* MD4C: Markdown parser for C
* (http://github.com/mity/md4c)
*
* Copyright (c) 2016-2024 Martin Mitáš
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef MD4C_HTML_H
#define MD4C_HTML_H
#include "md4c.h"
#ifdef __cplusplus
extern "C" {
#endif
/* If set, debug output from md_parse() is sent to stderr. */
#define MD_HTML_FLAG_DEBUG 0x0001
#define MD_HTML_FLAG_VERBATIM_ENTITIES 0x0002
#define MD_HTML_FLAG_SKIP_UTF8_BOM 0x0004
#define MD_HTML_FLAG_XHTML 0x0008
/* Render Markdown into HTML.
*
* Note only contents of <body> tag is generated. Caller must generate
* HTML header/footer manually before/after calling md_html().
*
* Params input and input_size specify the Markdown input.
* Callback process_output() gets called with chunks of HTML output.
* (Typical implementation may just output the bytes to a file or append to
* some buffer).
* Param userdata is just propagated back to process_output() callback.
* Param parser_flags are flags from md4c.h propagated to md_parse().
* Param render_flags is bitmask of MD_HTML_FLAG_xxxx.
*
* Returns -1 on error (if md_parse() fails.)
* Returns 0 on success.
*/
int md_html(const MD_CHAR* input, MD_SIZE input_size,
void (*process_output)(const MD_CHAR*, MD_SIZE, void*),
void* userdata, unsigned parser_flags, unsigned renderer_flags);
#ifdef __cplusplus
} /* extern "C" { */
#endif
#endif /* MD4C_HTML_H */

6492
src/thirdparty/md4c/md4c.c vendored Normal file

File diff suppressed because it is too large Load Diff

407
src/thirdparty/md4c/md4c.h vendored Normal file
View File

@@ -0,0 +1,407 @@
/*
* MD4C: Markdown parser for C
* (http://github.com/mity/md4c)
*
* Copyright (c) 2016-2024 Martin Mitáš
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef MD4C_H
#define MD4C_H
#ifdef __cplusplus
extern "C" {
#endif
#if defined MD4C_USE_UTF16
/* Magic to support UTF-16. Note that in order to use it, you have to define
* the macro MD4C_USE_UTF16 both when building MD4C as well as when
* including this header in your code. */
#ifdef _WIN32
#include <windows.h>
typedef WCHAR MD_CHAR;
#else
#error MD4C_USE_UTF16 is only supported on Windows.
#endif
#else
typedef char MD_CHAR;
#endif
typedef unsigned MD_SIZE;
typedef unsigned MD_OFFSET;
/* Block represents a part of document hierarchy structure like a paragraph
* or list item.
*/
typedef enum MD_BLOCKTYPE {
/* <body>...</body> */
MD_BLOCK_DOC = 0,
/* <blockquote>...</blockquote> */
MD_BLOCK_QUOTE,
/* <ul>...</ul>
* Detail: Structure MD_BLOCK_UL_DETAIL. */
MD_BLOCK_UL,
/* <ol>...</ol>
* Detail: Structure MD_BLOCK_OL_DETAIL. */
MD_BLOCK_OL,
/* <li>...</li>
* Detail: Structure MD_BLOCK_LI_DETAIL. */
MD_BLOCK_LI,
/* <hr> */
MD_BLOCK_HR,
/* <h1>...</h1> (for levels up to 6)
* Detail: Structure MD_BLOCK_H_DETAIL. */
MD_BLOCK_H,
/* <pre><code>...</code></pre>
* Note the text lines within code blocks are terminated with '\n'
* instead of explicit MD_TEXT_BR. */
MD_BLOCK_CODE,
/* Raw HTML block. This itself does not correspond to any particular HTML
* tag. The contents of it _is_ raw HTML source intended to be put
* in verbatim form to the HTML output. */
MD_BLOCK_HTML,
/* <p>...</p> */
MD_BLOCK_P,
/* <table>...</table> and its contents.
* Detail: Structure MD_BLOCK_TABLE_DETAIL (for MD_BLOCK_TABLE),
* structure MD_BLOCK_TD_DETAIL (for MD_BLOCK_TH and MD_BLOCK_TD)
* Note all of these are used only if extension MD_FLAG_TABLES is enabled. */
MD_BLOCK_TABLE,
MD_BLOCK_THEAD,
MD_BLOCK_TBODY,
MD_BLOCK_TR,
MD_BLOCK_TH,
MD_BLOCK_TD
} MD_BLOCKTYPE;
/* Span represents an in-line piece of a document which should be rendered with
* the same font, color and other attributes. A sequence of spans forms a block
* like paragraph or list item. */
typedef enum MD_SPANTYPE {
/* <em>...</em> */
MD_SPAN_EM,
/* <strong>...</strong> */
MD_SPAN_STRONG,
/* <a href="xxx">...</a>
* Detail: Structure MD_SPAN_A_DETAIL. */
MD_SPAN_A,
/* <img src="xxx">...</a>
* Detail: Structure MD_SPAN_IMG_DETAIL.
* Note: Image text can contain nested spans and even nested images.
* If rendered into ALT attribute of HTML <IMG> tag, it's responsibility
* of the parser to deal with it.
*/
MD_SPAN_IMG,
/* <code>...</code> */
MD_SPAN_CODE,
/* <del>...</del>
* Note: Recognized only when MD_FLAG_STRIKETHROUGH is enabled.
*/
MD_SPAN_DEL,
/* For recognizing inline ($) and display ($$) equations
* Note: Recognized only when MD_FLAG_LATEXMATHSPANS is enabled.
*/
MD_SPAN_LATEXMATH,
MD_SPAN_LATEXMATH_DISPLAY,
/* Wiki links
* Note: Recognized only when MD_FLAG_WIKILINKS is enabled.
*/
MD_SPAN_WIKILINK,
/* <u>...</u>
* Note: Recognized only when MD_FLAG_UNDERLINE is enabled. */
MD_SPAN_U
} MD_SPANTYPE;
/* Text is the actual textual contents of span. */
typedef enum MD_TEXTTYPE {
/* Normal text. */
MD_TEXT_NORMAL = 0,
/* NULL character. CommonMark requires replacing NULL character with
* the replacement char U+FFFD, so this allows caller to do that easily. */
MD_TEXT_NULLCHAR,
/* Line breaks.
* Note these are not sent from blocks with verbatim output (MD_BLOCK_CODE
* or MD_BLOCK_HTML). In such cases, '\n' is part of the text itself. */
MD_TEXT_BR, /* <br> (hard break) */
MD_TEXT_SOFTBR, /* '\n' in source text where it is not semantically meaningful (soft break) */
/* Entity.
* (a) Named entity, e.g. &nbsp;
* (Note MD4C does not have a list of known entities.
* Anything matching the regexp /&[A-Za-z][A-Za-z0-9]{1,47};/ is
* treated as a named entity.)
* (b) Numerical entity, e.g. &#1234;
* (c) Hexadecimal entity, e.g. &#x12AB;
*
* As MD4C is mostly encoding agnostic, application gets the verbatim
* entity text into the MD_PARSER::text_callback(). */
MD_TEXT_ENTITY,
/* Text in a code block (inside MD_BLOCK_CODE) or inlined code (`code`).
* If it is inside MD_BLOCK_CODE, it includes spaces for indentation and
* '\n' for new lines. MD_TEXT_BR and MD_TEXT_SOFTBR are not sent for this
* kind of text. */
MD_TEXT_CODE,
/* Text is a raw HTML. If it is contents of a raw HTML block (i.e. not
* an inline raw HTML), then MD_TEXT_BR and MD_TEXT_SOFTBR are not used.
* The text contains verbatim '\n' for the new lines. */
MD_TEXT_HTML,
/* Text is inside an equation. This is processed the same way as inlined code
* spans (`code`). */
MD_TEXT_LATEXMATH
} MD_TEXTTYPE;
/* Alignment enumeration. */
typedef enum MD_ALIGN {
MD_ALIGN_DEFAULT = 0, /* When unspecified. */
MD_ALIGN_LEFT,
MD_ALIGN_CENTER,
MD_ALIGN_RIGHT
} MD_ALIGN;
/* String attribute.
*
* This wraps strings which are outside of a normal text flow and which are
* propagated within various detailed structures, but which still may contain
* string portions of different types like e.g. entities.
*
* So, for example, lets consider this image:
*
* ![image alt text](http://example.org/image.png 'foo &quot; bar')
*
* The image alt text is propagated as a normal text via the MD_PARSER::text()
* callback. However, the image title ('foo &quot; bar') is propagated as
* MD_ATTRIBUTE in MD_SPAN_IMG_DETAIL::title.
*
* Then the attribute MD_SPAN_IMG_DETAIL::title shall provide the following:
* -- [0]: "foo " (substr_types[0] == MD_TEXT_NORMAL; substr_offsets[0] == 0)
* -- [1]: "&quot;" (substr_types[1] == MD_TEXT_ENTITY; substr_offsets[1] == 4)
* -- [2]: " bar" (substr_types[2] == MD_TEXT_NORMAL; substr_offsets[2] == 10)
* -- [3]: (n/a) (n/a ; substr_offsets[3] == 14)
*
* Note that these invariants are always guaranteed:
* -- substr_offsets[0] == 0
* -- substr_offsets[LAST+1] == size
* -- Currently, only MD_TEXT_NORMAL, MD_TEXT_ENTITY, MD_TEXT_NULLCHAR
* substrings can appear. This could change only of the specification
* changes.
*/
typedef struct MD_ATTRIBUTE {
const MD_CHAR* text;
MD_SIZE size;
const MD_TEXTTYPE* substr_types;
const MD_OFFSET* substr_offsets;
} MD_ATTRIBUTE;
/* Detailed info for MD_BLOCK_UL. */
typedef struct MD_BLOCK_UL_DETAIL {
int is_tight; /* Non-zero if tight list, zero if loose. */
MD_CHAR mark; /* Item bullet character in MarkDown source of the list, e.g. '-', '+', '*'. */
} MD_BLOCK_UL_DETAIL;
/* Detailed info for MD_BLOCK_OL. */
typedef struct MD_BLOCK_OL_DETAIL {
unsigned start; /* Start index of the ordered list. */
int is_tight; /* Non-zero if tight list, zero if loose. */
MD_CHAR mark_delimiter; /* Character delimiting the item marks in MarkDown source, e.g. '.' or ')' */
} MD_BLOCK_OL_DETAIL;
/* Detailed info for MD_BLOCK_LI. */
typedef struct MD_BLOCK_LI_DETAIL {
int is_task; /* Can be non-zero only with MD_FLAG_TASKLISTS */
MD_CHAR task_mark; /* If is_task, then one of 'x', 'X' or ' '. Undefined otherwise. */
MD_OFFSET task_mark_offset; /* If is_task, then offset in the input of the char between '[' and ']'. */
} MD_BLOCK_LI_DETAIL;
/* Detailed info for MD_BLOCK_H. */
typedef struct MD_BLOCK_H_DETAIL {
unsigned level; /* Header level (1 - 6) */
} MD_BLOCK_H_DETAIL;
/* Detailed info for MD_BLOCK_CODE. */
typedef struct MD_BLOCK_CODE_DETAIL {
MD_ATTRIBUTE info;
MD_ATTRIBUTE lang;
MD_CHAR fence_char; /* The character used for fenced code block; or zero for indented code block. */
} MD_BLOCK_CODE_DETAIL;
/* Detailed info for MD_BLOCK_TABLE. */
typedef struct MD_BLOCK_TABLE_DETAIL {
unsigned col_count; /* Count of columns in the table. */
unsigned head_row_count; /* Count of rows in the table header (currently always 1) */
unsigned body_row_count; /* Count of rows in the table body */
} MD_BLOCK_TABLE_DETAIL;
/* Detailed info for MD_BLOCK_TH and MD_BLOCK_TD. */
typedef struct MD_BLOCK_TD_DETAIL {
MD_ALIGN align;
} MD_BLOCK_TD_DETAIL;
/* Detailed info for MD_SPAN_A. */
typedef struct MD_SPAN_A_DETAIL {
MD_ATTRIBUTE href;
MD_ATTRIBUTE title;
int is_autolink; /* nonzero if this is an autolink */
} MD_SPAN_A_DETAIL;
/* Detailed info for MD_SPAN_IMG. */
typedef struct MD_SPAN_IMG_DETAIL {
MD_ATTRIBUTE src;
MD_ATTRIBUTE title;
} MD_SPAN_IMG_DETAIL;
/* Detailed info for MD_SPAN_WIKILINK. */
typedef struct MD_SPAN_WIKILINK {
MD_ATTRIBUTE target;
} MD_SPAN_WIKILINK_DETAIL;
/* Flags specifying extensions/deviations from CommonMark specification.
*
* By default (when MD_PARSER::flags == 0), we follow CommonMark specification.
* The following flags may allow some extensions or deviations from it.
*/
#define MD_FLAG_COLLAPSEWHITESPACE 0x0001 /* In MD_TEXT_NORMAL, collapse non-trivial whitespace into single ' ' */
#define MD_FLAG_PERMISSIVEATXHEADERS 0x0002 /* Do not require space in ATX headers ( ###header ) */
#define MD_FLAG_PERMISSIVEURLAUTOLINKS 0x0004 /* Recognize URLs as autolinks even without '<', '>' */
#define MD_FLAG_PERMISSIVEEMAILAUTOLINKS 0x0008 /* Recognize e-mails as autolinks even without '<', '>' and 'mailto:' */
#define MD_FLAG_NOINDENTEDCODEBLOCKS 0x0010 /* Disable indented code blocks. (Only fenced code works.) */
#define MD_FLAG_NOHTMLBLOCKS 0x0020 /* Disable raw HTML blocks. */
#define MD_FLAG_NOHTMLSPANS 0x0040 /* Disable raw HTML (inline). */
#define MD_FLAG_TABLES 0x0100 /* Enable tables extension. */
#define MD_FLAG_STRIKETHROUGH 0x0200 /* Enable strikethrough extension. */
#define MD_FLAG_PERMISSIVEWWWAUTOLINKS 0x0400 /* Enable WWW autolinks (even without any scheme prefix, if they begin with 'www.') */
#define MD_FLAG_TASKLISTS 0x0800 /* Enable task list extension. */
#define MD_FLAG_LATEXMATHSPANS 0x1000 /* Enable $ and $$ containing LaTeX equations. */
#define MD_FLAG_WIKILINKS 0x2000 /* Enable wiki links extension. */
#define MD_FLAG_UNDERLINE 0x4000 /* Enable underline extension (and disables '_' for normal emphasis). */
#define MD_FLAG_HARD_SOFT_BREAKS 0x8000 /* Force all soft breaks to act as hard breaks. */
#define MD_FLAG_PERMISSIVEAUTOLINKS (MD_FLAG_PERMISSIVEEMAILAUTOLINKS | MD_FLAG_PERMISSIVEURLAUTOLINKS | MD_FLAG_PERMISSIVEWWWAUTOLINKS)
#define MD_FLAG_NOHTML (MD_FLAG_NOHTMLBLOCKS | MD_FLAG_NOHTMLSPANS)
/* Convenient sets of flags corresponding to well-known Markdown dialects.
*
* Note we may only support subset of features of the referred dialect.
* The constant just enables those extensions which bring us as close as
* possible given what features we implement.
*
* ABI compatibility note: Meaning of these can change in time as new
* extensions, bringing the dialect closer to the original, are implemented.
*/
#define MD_DIALECT_COMMONMARK 0
#define MD_DIALECT_GITHUB (MD_FLAG_PERMISSIVEAUTOLINKS | MD_FLAG_TABLES | MD_FLAG_STRIKETHROUGH | MD_FLAG_TASKLISTS)
/* Parser structure.
*/
typedef struct MD_PARSER {
/* Reserved. Set to zero.
*/
unsigned abi_version;
/* Dialect options. Bitmask of MD_FLAG_xxxx values.
*/
unsigned flags;
/* Caller-provided rendering callbacks.
*
* For some block/span types, more detailed information is provided in a
* type-specific structure pointed by the argument 'detail'.
*
* The last argument of all callbacks, 'userdata', is just propagated from
* md_parse() and is available for any use by the application.
*
* Note any strings provided to the callbacks as their arguments or as
* members of any detail structure are generally not zero-terminated.
* Application has to take the respective size information into account.
*
* Any rendering callback may abort further parsing of the document by
* returning non-zero.
*/
int (*enter_block)(MD_BLOCKTYPE /*type*/, void* /*detail*/, void* /*userdata*/);
int (*leave_block)(MD_BLOCKTYPE /*type*/, void* /*detail*/, void* /*userdata*/);
int (*enter_span)(MD_SPANTYPE /*type*/, void* /*detail*/, void* /*userdata*/);
int (*leave_span)(MD_SPANTYPE /*type*/, void* /*detail*/, void* /*userdata*/);
int (*text)(MD_TEXTTYPE /*type*/, const MD_CHAR* /*text*/, MD_SIZE /*size*/, void* /*userdata*/);
/* Debug callback. Optional (may be NULL).
*
* If provided and something goes wrong, this function gets called.
* This is intended for debugging and problem diagnosis for developers;
* it is not intended to provide any errors suitable for displaying to an
* end user.
*/
void (*debug_log)(const char* /*msg*/, void* /*userdata*/);
/* Reserved. Set to NULL.
*/
void (*syntax)(void);
} MD_PARSER;
/* For backward compatibility. Do not use in new code.
*/
typedef MD_PARSER MD_RENDERER;
/* Parse the Markdown document stored in the string 'text' of size 'size'.
* The parser provides callbacks to be called during the parsing so the
* caller can render the document on the screen or convert the Markdown
* to another format.
*
* Zero is returned on success. If a runtime error occurs (e.g. a memory
* fails), -1 is returned. If the processing is aborted due any callback
* returning non-zero, the return value of the callback is returned.
*/
int md_parse(const MD_CHAR* text, MD_SIZE size, const MD_PARSER* parser, void* userdata);
#ifdef __cplusplus
} /* extern "C" { */
#endif
#endif /* MD4C_H */