From 2dec67617f3cf1e2b724d16176c5dc20e8d97471 Mon Sep 17 00:00:00 2001 From: Julian Smart Date: Tue, 7 Mar 2006 14:48:17 +0000 Subject: [PATCH] Added conversion to HTML, by Akin Demirtug git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@37853 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- include/wx/richtext/richtexthtml.h | 60 +++- src/richtext/richtexthtml.cpp | 544 +++++++++++++++++++++++++---- 2 files changed, 532 insertions(+), 72 deletions(-) diff --git a/include/wx/richtext/richtexthtml.h b/include/wx/richtext/richtexthtml.h index 7f93c00a91..8bca5ff644 100644 --- a/include/wx/richtext/richtexthtml.h +++ b/include/wx/richtext/richtexthtml.h @@ -39,17 +39,65 @@ public: /// Can we handle this filename (if using files)? By default, checks the extension. virtual bool CanHandle(const wxString& filename) const; - /// Output character formatting - virtual void OutputCharacterFormatting(const wxTextAttrEx& currentStyle, const wxTextAttrEx& thisStyle, wxOutputStream& stream, bool start); - - /// Output paragraph formatting - virtual void OutputParagraphFormatting(const wxTextAttrEx& currentStyle, const wxTextAttrEx& thisStyle, wxOutputStream& stream, bool start); - protected: + #if wxUSE_STREAMS virtual bool DoLoadFile(wxRichTextBuffer *buffer, wxInputStream& stream); virtual bool DoSaveFile(wxRichTextBuffer *buffer, wxOutputStream& stream); #endif + + /// Output character formatting + virtual void BeginCharacterFormatting(const wxTextAttrEx& currentStyle, const wxTextAttrEx& thisStyle, wxOutputStream& stream ); + virtual void EndCharacterFormatting(const wxTextAttrEx& WXUNUSED(currentStyle), const wxTextAttrEx& thisStyle, wxOutputStream& stream ); + + /// Output paragraph formatting + virtual void OutputParagraphFormatting(const wxTextAttrEx& WXUNUSED(currentStyle), const wxTextAttrEx& thisStyle, wxOutputStream& stream/*, bool start*/); + + /// Converts an image to its base64 equivalent + void Image_to_Base64(wxRichTextImage* image, wxOutputStream& stream); + + /// Builds required indentation + void Indent( const wxTextAttrEx& thisStyle, wxTextOutputStream& str ); + + /// Left indent + void LIndent( const wxTextAttrEx& thisStyle, wxTextOutputStream& str ); + + /// Converts from pt to size property compatible height + long Pt_To_Size(long size); + + /// Typical base64 encoder + wxChar* b64enc( unsigned char* input, size_t in_len ); + + /// Gets the mime type of the given wxBITMAP_TYPE + wxChar* GetMimeType(int imageType); + + /// Gets the html equivalent of the specified value + wxString GetAlignment( const wxTextAttrEx& thisStyle ); + + /// Generates   array for indentations + wxString SymbolicIndent(long indent); + + /// Finds the html equivalent of the specified bullet + void TypeOfList( const wxTextAttrEx& thisStyle, wxString& tag ); + + /// Closes existings or Opens new tables for navigation to an item's horizontal position. + void NavigateToListPosition( const wxTextAttrEx& thisStyle, wxTextOutputStream& str ); + + /// Indentation values of the table tags + wxArrayInt m_indents; + + /// Horizontal position of the current table + long m_indent; + + /// Is there any opened font tag + bool m_font; + + /// Is there any opened ul/ol tag + bool m_list; + + /// type of list, ul or ol? + bool m_is_ul; + }; #endif diff --git a/src/richtext/richtexthtml.cpp b/src/richtext/richtexthtml.cpp index c1e9c9c584..49552e64d3 100644 --- a/src/richtext/richtexthtml.cpp +++ b/src/richtext/richtexthtml.cpp @@ -53,24 +53,55 @@ bool wxRichTextHTMLHandler::DoLoadFile(wxRichTextBuffer *WXUNUSED(buffer), wxInp bool wxRichTextHTMLHandler::DoSaveFile(wxRichTextBuffer *buffer, wxOutputStream& stream) { buffer->Defragment(); - + wxTextOutputStream str(stream); - + wxTextAttrEx currentParaStyle = buffer->GetAttributes(); wxTextAttrEx currentCharStyle = buffer->GetAttributes(); - + str << wxT("\n"); - + + /* + wxRichText may be support paper formats like a1/a2/a3/a4 + when this widget grown enough, i should turn back and support its new features + but not yet + + str << wxT(""), + left_indent.c_str(), //Document-Wide Left Indent + right_indent.c_str()); //Document-Wide Right Indent + + str << wxT("
"); + + wxString left_indent = SymbolicIndent(currentParaStyle.GetLeftIndent()); + wxString right_indent = SymbolicIndent(currentParaStyle.GetRightIndent()); + + str << wxString::Format(wxT("%s%s
"); + */ + + str << wxT("
"); + + str << wxString::Format(wxT(""), + currentParaStyle.GetFont().GetFaceName(), Pt_To_Size( currentParaStyle.GetFont().GetPointSize() ), + currentParaStyle.GetTextColour().Red(), currentParaStyle.GetTextColour().Green(), + currentParaStyle.GetTextColour().Blue()); + + //wxString align = GetAlignment( currentParaStyle.GetAlignment() ); + //str << wxString::Format(wxT("

"), align ); + + m_font = false; + m_indent = 0; + m_list = false; + wxRichTextObjectList::compatibility_iterator node = buffer->GetChildren().GetFirst(); while (node) { wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph); wxASSERT (para != NULL); - + if (para) { - OutputParagraphFormatting(currentParaStyle, para->GetAttributes(), stream, true); - + OutputParagraphFormatting(currentParaStyle, para->GetAttributes(), stream); + wxRichTextObjectList::compatibility_iterator node2 = para->GetChildren().GetFirst(); while (node2) { @@ -78,99 +109,480 @@ bool wxRichTextHTMLHandler::DoSaveFile(wxRichTextBuffer *buffer, wxOutputStream& wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText); if (textObj && !textObj->IsEmpty()) { - OutputCharacterFormatting(currentCharStyle, obj->GetAttributes(), stream, true); - + BeginCharacterFormatting(currentCharStyle, obj->GetAttributes(), stream); + str << textObj->GetText(); - - OutputCharacterFormatting(currentCharStyle, obj->GetAttributes(), stream, false); + + EndCharacterFormatting(currentCharStyle, obj->GetAttributes(), stream); } - + + wxRichTextImage* image = wxDynamicCast(obj, wxRichTextImage); + if( image && !image->IsEmpty()) + Image_to_Base64( image, stream ); + node2 = node2->GetNext(); } - - OutputParagraphFormatting(currentParaStyle, para->GetAttributes(), stream, false); - - str << wxT("

\n"); + //OutputParagraphFormatting(currentParaStyle, para->GetAttributes(), stream, false); } - node = node->GetNext(); } - - str << wxT("\n"); - + + str << wxT("

\n"); + return true; } -/// Output character formatting -void wxRichTextHTMLHandler::OutputCharacterFormatting(const wxTextAttrEx& WXUNUSED(currentStyle), const wxTextAttrEx& thisStyle, wxOutputStream& stream, bool start) +void wxRichTextHTMLHandler::BeginCharacterFormatting(const wxTextAttrEx& currentStyle, const wxTextAttrEx& thisStyle, wxOutputStream& stream) { wxTextOutputStream str(stream); - - bool isBold = false; - bool isItalic = false; - bool isUnderline = false; - wxString faceName; - - if (thisStyle.GetFont().Ok()) + + //Is the item bulleted one? + if( thisStyle.GetBulletStyle() != wxTEXT_ATTR_BULLET_STYLE_NONE ) { - if (thisStyle.GetFont().GetWeight() == wxBOLD) - isBold = true; - if (thisStyle.GetFont().GetStyle() == wxITALIC) - isItalic = true; - if (thisStyle.GetFont().GetUnderlined()) - isUnderline = true; - - faceName = thisStyle.GetFont().GetFaceName(); + //Is there any opened list? + if( m_list ) + { + //Yes there is + + //Is the item among the previous ones + //Is the item one of the previous list tag's child items + if( (thisStyle.GetLeftIndent() == (m_indent + 100)) || (thisStyle.GetLeftIndent() < 100) ) + str << wxT("
  • ");//Yes it is + else + { + //No it isn't + + //So we should close the list tag + str << (m_is_ul ? wxT("") : wxT("")); + + //And renavigate to new list's horizontal position + NavigateToListPosition(thisStyle, str); + //Ok it's done + + //Get the appropriate tag, an ol for numerical values, an ul for dot, square etc. + wxString tag; + TypeOfList(thisStyle, tag); + str << wxString::Format(wxT("%s
  • "), tag); + } + } + else + { + //No there isn't a list + + //navigate to new list's horizontal position(indent) + NavigateToListPosition(thisStyle, str); + + //Get the appropriate tag, an ol for numerical values, an ul for dot, square etc. + wxString tag; + TypeOfList(thisStyle, tag); + str << wxString::Format(wxT("%s
  • "), tag); + + //Now we have a list, mark it. + m_list = true; + } } + else if( m_list ) + { + //The item is not bulleted and there is a list what should be closed now. + //So close the list - if (start) + str << (m_is_ul ? wxT("") : wxT("")); + //And mark as there is no an opened list + m_list = false; + } + + // does the item have an indentation ? + if( thisStyle.GetLeftIndent() ) { - if (isBold) - str << wxT(""); - if (isItalic) - str << wxT(""); - if (isUnderline) - str << wxT(""); + if( thisStyle.GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_NONE ) + { + if( m_indent ) + { + if( (thisStyle.GetLeftIndent() + thisStyle.GetLeftSubIndent()) == m_indent ) + { + if( thisStyle.GetLeftSubIndent() < 0 ) + { + wxString symbolic_indent = SymbolicIndent(~thisStyle.GetLeftSubIndent()); + str << wxString::Format(wxT("%s"), symbolic_indent); + } + } + else + { + if( thisStyle.GetLeftIndent() + thisStyle.GetLeftSubIndent() > m_indent ) + { + Indent(thisStyle, str); + m_indent = thisStyle.GetLeftIndent() + thisStyle.GetLeftSubIndent(); + m_indents.Add( m_indent ); + } + else + { + int i = m_indents.size() - 1; + for(; i > -1; i--) + { + if( m_indent < (thisStyle.GetLeftIndent() + thisStyle.GetLeftSubIndent()) ) + { + Indent(thisStyle, str); + m_indent = thisStyle.GetLeftIndent() + thisStyle.GetLeftSubIndent(); + m_indents.Add( m_indent ); + + break; + } + else if( m_indent == (thisStyle.GetLeftIndent() + thisStyle.GetLeftSubIndent()) ) + { + if( thisStyle.GetLeftSubIndent() < 0 ) + { + wxString symbolic_indent = SymbolicIndent(~thisStyle.GetLeftSubIndent()); + str << wxString::Format(wxT("%s"), symbolic_indent); + } + break; + } + else + { + str << wxT("
  • "); + + m_indents.RemoveAt(i); + + if( i < 1 ){m_indent=0; break;} + m_indent = m_indents[i-1]; + } + } + } + } + } + else + { + Indent(thisStyle, str); + m_indent = thisStyle.GetLeftIndent() + thisStyle.GetLeftSubIndent(); + m_indents.Add( m_indent ); + } + } } - else + else if( m_indent ) { - if (isUnderline) - str << wxT(""); - if (isItalic) - str << wxT(""); - if (isBold) - str << wxT(""); + //The item is not indented and there is a table(s) what should be closed now. + + //So close them + for(unsigned int i = 0; i < m_indents.size(); i++ ) + str << wxT(""); + + m_indent = 0; + m_indents.Clear(); } + + + wxString style; + + //Is there any change on the font properties of the item + if( thisStyle.GetFont().GetFaceName() != currentStyle.GetFont().GetFaceName() ) + style += wxString::Format(wxT(" face=\"%s\""), thisStyle.GetFont().GetFaceName()); + if( thisStyle.GetFont().GetPointSize() != currentStyle.GetFont().GetPointSize() ) + style += wxString::Format(wxT(" size=\"%i\""), Pt_To_Size(thisStyle.GetFont().GetPointSize()) ); + if( thisStyle.GetTextColour() != currentStyle.GetTextColour() ) + style += wxString::Format(wxT(" color=\"#%02X%02X%02X\""), thisStyle.GetTextColour().Red(), + thisStyle.GetTextColour().Green(), thisStyle.GetTextColour().Blue()); + + if( style.size() ){str << wxString::Format(wxT(""), style); m_font = true;} + + if( thisStyle.GetFont().GetWeight() == wxBOLD ) + str << wxT(""); + if( thisStyle.GetFont().GetStyle() == wxITALIC ) + str << wxT(""); + if( thisStyle.GetFont().GetUnderlined() ) + str << wxT(""); } -/// Output paragraph formatting -void wxRichTextHTMLHandler::OutputParagraphFormatting(const wxTextAttrEx& WXUNUSED(currentStyle), const wxTextAttrEx& thisStyle, wxOutputStream& stream, bool start) +void wxRichTextHTMLHandler::EndCharacterFormatting(const wxTextAttrEx& WXUNUSED(currentStyle), const wxTextAttrEx& thisStyle, wxOutputStream& stream) { - // TODO: lists, indentation (using tables), fonts, right-align, ... - wxTextOutputStream str(stream); - bool isCentered = false; + + if( thisStyle.GetFont().GetUnderlined() ) + str << wxT(""); + if( thisStyle.GetFont().GetStyle() == wxITALIC ) + str << wxT(""); + if( thisStyle.GetFont().GetWeight() == wxBOLD ) + str << wxT(""); + + if( m_font ) + { + m_font = false; + str << wxT(""); + } +} - if (thisStyle.GetAlignment() == wxTEXT_ALIGNMENT_CENTRE) +/// Output paragraph formatting +void wxRichTextHTMLHandler::OutputParagraphFormatting(const wxTextAttrEx& WXUNUSED(currentStyle), const wxTextAttrEx& thisStyle, wxOutputStream& stream) +{ + //If there is no opened list currently, insert a

    after every paragraph + if(!m_list) { - isCentered = true; + wxTextOutputStream str(stream); + wxString align = GetAlignment( thisStyle ); + str << wxString::Format(wxT("

    "), align); } +} - if (start) +void wxRichTextHTMLHandler::NavigateToListPosition(const wxTextAttrEx& thisStyle, wxTextOutputStream& str) +{ + //indenting an item using an ul/ol tag is equal to inserting 5 x   on its left side. + //so we should start from 100 point left + + //Is the second td's left wall of the current indentaion table at the 100+ point-left-side + //of the item, horizontally? + if( m_indent + 100 < thisStyle.GetLeftIndent() ) { - if (isCentered) - str << wxT("

    "); + //yes it is + LIndent(thisStyle, str); + m_indent = thisStyle.GetLeftIndent() - 100; + m_indents.Add( m_indent ); + return; } + //No it isn't + + int i = m_indents.size() - 1; + for(; i > -1; i--) + { + //Is the second td's left wall of the current indentaion table at the 100+ point-left-side + //of the item ? + if( m_indent + 100 < thisStyle.GetLeftIndent() ) + { + //Yes it is + LIndent(thisStyle, str); + m_indent = thisStyle.GetLeftIndent() - 100; + m_indents.Add( m_indent ); + break; + } + else if( m_indent + 100 == thisStyle.GetLeftIndent() ) + break;//exact match + else + { + //No it is not, the second td's left wall of the current indentaion table is at the + //right side of the current item horizontally, so close it. + str << wxT(""); + + m_indents.RemoveAt(i); + + if( i < 1 ){m_indent=0; break;} + m_indent = m_indents[i-1]; + } + } +} +void wxRichTextHTMLHandler::Indent( const wxTextAttrEx& thisStyle, wxTextOutputStream& str ) +{ + //As a five year experienced web developer i assure you there is no way to indent an item + //in html way, but we can use tables. + + + + //Item -> "Hello world" + //Its Left Indentation -> 100 + //Its Left Sub-Indentation ->40 + //A typical indentation-table for the item will be construct as the following + + //3 x nbsp = 60 + //2 x nbsp = 40 + //LSI = Left Sub Indent + //LI = Left Indent - LSI + // + //------------------------------------------- + //|  nbsp;|nbsp;nbsp;Hello World | + //| | | | | + //| V | V | + //| --LI-- | --LSI-- | + //------------------------------------------- + + str << wxT(""); + + wxString symbolic_indent = SymbolicIndent( (thisStyle.GetLeftIndent() + thisStyle.GetLeftSubIndent()) - m_indent ); + str << wxString::Format( wxT(""), symbolic_indent ); + str << wxT("
    %s"); + + if( thisStyle.GetLeftSubIndent() < 0 ) + { + symbolic_indent = SymbolicIndent(~thisStyle.GetLeftSubIndent()); + str << wxString::Format(wxT("%s"), symbolic_indent); + } +} + +void wxRichTextHTMLHandler::LIndent( const wxTextAttrEx& thisStyle, wxTextOutputStream& str ) +{ + //Code: + //r.BeginNumberedBullet(1, 200, 60); + //r.Newline(); + //r.WriteText(wxT("first item")); + //r.EndNumberedBullet(); + //r.BeginNumberedBullet(2, 200, 60); + //r.Newline(); + //r.WriteText(wxT("second item.")); + //r.EndNumberedBullet(); + // + //A typical indentation-table for the item will be construct as the following + + //1 x nbsp = 20 point + //ULI -> 100pt (UL/OL tag indents its sub element by 100 point) + //<--------- 100 pt ---------->| + //------------------------------------------------------ + //|  nbsp; nbsp;|
      | + //| |<-ULI->
    • first item | + //| |<-ULI->
    • second item | + //| |
    | + //------------------------------------------------------ + // |<-100->| + + + str << wxT(""); + + wxString symbolic_indent = SymbolicIndent( (thisStyle.GetLeftIndent() - m_indent) - 100); + str << wxString::Format( wxT(""), symbolic_indent ); + str << wxT("
    %s"); +} + +void wxRichTextHTMLHandler::TypeOfList( const wxTextAttrEx& thisStyle, wxString& tag ) +{ + //We can use number attribute of li tag but not all the browsers support it. + //also wxHtmlWindow doesn't support type attribute. + + m_is_ul = false; + if( thisStyle.GetBulletStyle() == (wxTEXT_ATTR_BULLET_STYLE_ARABIC|wxTEXT_ATTR_BULLET_STYLE_PERIOD)) + tag = wxT("
      "); + else if( thisStyle.GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_LETTERS_UPPER ) + tag = wxT("
        "); + else if( thisStyle.GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_LETTERS_LOWER ) + tag = wxT("
          "); + else if( thisStyle.GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_ROMAN_UPPER ) + tag = wxT("
            "); + else if( thisStyle.GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_ROMAN_LOWER ) + tag = wxT("
              "); else { - if (isCentered) - str << wxT(""); + tag = wxT("
                "); + m_is_ul = true; } } +wxString wxRichTextHTMLHandler::GetAlignment( const wxTextAttrEx& thisStyle ) +{ + switch( thisStyle.GetAlignment() ) + { + case wxTEXT_ALIGNMENT_LEFT: + return wxT("left"); + case wxTEXT_ALIGNMENT_RIGHT: + return wxT("right"); + case wxTEXT_ALIGNMENT_CENTER: + return wxT("center"); + case wxTEXT_ALIGNMENT_JUSTIFIED: + return wxT("justify"); + default: + return wxT("left"); + } +} + +void wxRichTextHTMLHandler::Image_to_Base64(wxRichTextImage* image, wxOutputStream& stream) +{ + wxTextOutputStream str(stream); + + str << wxT("GetImageBlock().GetImageType()); + str << wxT(";base64,"); + + wxChar* data = b64enc( image->GetImageBlock().GetData(), image->GetImageBlock().GetDataSize() ); + str << data; + + delete[] data; + + str << wxT("\" />"); +} + +long wxRichTextHTMLHandler::Pt_To_Size(long size) +{ + //return most approximate size + if(size < 9 ) return 1; + else if( size < 11 ) return 2; + else if( size < 14 ) return 3; + else if( size < 18 ) return 4; + else if( size < 23 ) return 5; + else if( size < 30 ) return 6; + else return 7; +} + +wxString wxRichTextHTMLHandler::SymbolicIndent(long indent) +{ + wxString in; + for(;indent > 0; indent -= 20) + in.Append( wxT(" ") ); + return in; +} + +wxChar* wxRichTextHTMLHandler::GetMimeType(int imageType) +{ + switch(imageType) + { + case wxBITMAP_TYPE_BMP: + return wxT("image/bmp"); + case wxBITMAP_TYPE_TIF: + return wxT("image/tiff"); + case wxBITMAP_TYPE_GIF: + return wxT("image/gif"); + case wxBITMAP_TYPE_PNG: + return wxT("image/png"); + case wxBITMAP_TYPE_JPEG: + return wxT("image/jpeg"); + default: + return wxT("image/unknown"); + } +} + +//exim-style base64 encoder +wxChar* wxRichTextHTMLHandler::b64enc( unsigned char* input, size_t in_len ) +{ + //elements of enc64 array must be 8 bit values + //otherwise encoder will fail + //hmmm.. Does wxT macro define a char as 16 bit value + //when compiling with UNICODE option? + const static wxChar* enc64 = wxT("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); + wxChar* output = new wxChar[4*((in_len+2)/3)+1]; + wxChar* p = output; + + while( in_len-- > 0 ) + { + register wxChar a, b; + + a = *input++; + + *p++ = enc64[ (a >> 2) & 0x3f ]; + + if( in_len-- <= 0 ) + { + *p++ = enc64[ (a << 4 ) & 0x30 ]; + *p++ = '='; + *p++ = '='; + break; + } + + b = *input++; + + *p++ = enc64[(( a << 4 ) | ((b >> 4) &0xf )) & 0x3f]; + + if( in_len-- <= 0 ) + { + *p++ = enc64[ (b << 2) & 0x3f ]; + *p++ = '='; + break; + } + + a = *input++; + + *p++ = enc64[ ((( b << 2 ) & 0x3f ) | ((a >> 6)& 0x3)) & 0x3f ]; + + *p++ = enc64[ a & 0x3f ]; + } + *p = 0; + + return output; +} #endif - // wxUSE_STREAMS +// wxUSE_STREAMS #endif - // wxUSE_RICHTEXT - +// wxUSE_RICHTEXT + -- 2.45.2