1/////////////////////////////////////////////////////////////////////////////
2// Name:        src/richtext/richtexthtml.cpp
3// Purpose:     HTML I/O for wxRichTextCtrl
4// Author:      Julian Smart
5// Modified by:
6// Created:     2005-09-30
7// RCS-ID:      $Id: richtexthtml.cpp 64162 2010-04-27 16:10:27Z JS $
8// Copyright:   (c) Julian Smart
9// Licence:     wxWindows licence
10/////////////////////////////////////////////////////////////////////////////
11
12// For compilers that support precompilation, includes "wx.h".
13#include "wx/wxprec.h"
14
15#ifdef __BORLANDC__
16    #pragma hdrstop
17#endif
18
19#if wxUSE_RICHTEXT
20
21#include "wx/richtext/richtexthtml.h"
22#include "wx/richtext/richtextstyles.h"
23
24#ifndef WX_PRECOMP
25#endif
26
27#include "wx/filename.h"
28#include "wx/wfstream.h"
29#include "wx/txtstrm.h"
30
31#if wxUSE_FILESYSTEM
32#include "wx/filesys.h"
33#include "wx/fs_mem.h"
34#endif
35
36IMPLEMENT_DYNAMIC_CLASS(wxRichTextHTMLHandler, wxRichTextFileHandler)
37
38int wxRichTextHTMLHandler::sm_fileCounter = 1;
39
40wxRichTextHTMLHandler::wxRichTextHTMLHandler(const wxString& name, const wxString& ext, int type)
41    : wxRichTextFileHandler(name, ext, type), m_buffer(NULL), m_font(false), m_inTable(false)
42{
43    m_fontSizeMapping.Add(8);
44    m_fontSizeMapping.Add(10);
45    m_fontSizeMapping.Add(13);
46    m_fontSizeMapping.Add(17);
47    m_fontSizeMapping.Add(22);
48    m_fontSizeMapping.Add(30);
49    m_fontSizeMapping.Add(100);
50}
51
52/// Can we handle this filename (if using files)? By default, checks the extension.
53bool wxRichTextHTMLHandler::CanHandle(const wxString& filename) const
54{
55    wxString path, file, ext;
56    wxSplitPath(filename, & path, & file, & ext);
57
58    return (ext.Lower() == wxT("html") || ext.Lower() == wxT("htm"));
59}
60
61
62#if wxUSE_STREAMS
63bool wxRichTextHTMLHandler::DoLoadFile(wxRichTextBuffer *WXUNUSED(buffer), wxInputStream& WXUNUSED(stream))
64{
65    return false;
66}
67
68/*
69 * We need to output only _changes_ in character formatting.
70 */
71
72bool wxRichTextHTMLHandler::DoSaveFile(wxRichTextBuffer *buffer, wxOutputStream& stream)
73{
74    m_buffer = buffer;
75
76    ClearTemporaryImageLocations();
77
78    buffer->Defragment();
79
80#if wxUSE_UNICODE
81    wxCSConv* customEncoding = NULL;
82    wxMBConv* conv = NULL;
83    if (!GetEncoding().IsEmpty())
84    {
85        customEncoding = new wxCSConv(GetEncoding());
86        if (!customEncoding->IsOk())
87        {
88            delete customEncoding;
89            customEncoding = NULL;
90        }
91    }
92    if (customEncoding)
93        conv = customEncoding;
94    else
95        conv = & wxConvUTF8;
96#endif
97
98    {
99#if wxUSE_UNICODE
100        wxTextOutputStream str(stream, wxEOL_NATIVE, *conv);
101#else
102        wxTextOutputStream str(stream, wxEOL_NATIVE);
103#endif
104
105        wxTextAttrEx currentParaStyle = buffer->GetAttributes();
106        wxTextAttrEx currentCharStyle = buffer->GetAttributes();
107
108        if ((GetFlags() & wxRICHTEXT_HANDLER_NO_HEADER_FOOTER) == 0)
109            str << wxT("<html><head></head><body>\n");
110
111        OutputFont(currentParaStyle, str);
112
113        m_font = false;
114        m_inTable = false;
115
116        m_indents.Clear();
117        m_listTypes.Clear();
118
119        wxRichTextObjectList::compatibility_iterator node = buffer->GetChildren().GetFirst();
120        while (node)
121        {
122            wxRichTextParagraph* para = wxDynamicCast(node->GetData(), wxRichTextParagraph);
123            wxASSERT (para != NULL);
124
125            if (para)
126            {
127                wxTextAttrEx paraStyle(para->GetCombinedAttributes());
128
129                BeginParagraphFormatting(currentParaStyle, paraStyle, str);
130
131                wxRichTextObjectList::compatibility_iterator node2 = para->GetChildren().GetFirst();
132                while (node2)
133                {
134                    wxRichTextObject* obj = node2->GetData();
135                    wxRichTextPlainText* textObj = wxDynamicCast(obj, wxRichTextPlainText);
136                    if (textObj && !textObj->IsEmpty())
137                    {
138                        wxTextAttrEx charStyle(para->GetCombinedAttributes(obj->GetAttributes()));
139                        BeginCharacterFormatting(currentCharStyle, charStyle, paraStyle, str);
140
141                        wxString text = textObj->GetText();
142
143                        if (charStyle.HasTextEffects() && (charStyle.GetTextEffects() & wxTEXT_ATTR_EFFECT_CAPITALS))
144                            text.MakeUpper();
145
146                        wxString toReplace = wxRichTextLineBreakChar;
147                        text.Replace(toReplace, wxT("<br>"));
148
149                        str << text;
150
151                        EndCharacterFormatting(currentCharStyle, charStyle, paraStyle, str);
152                    }
153
154                    wxRichTextImage* image = wxDynamicCast(obj, wxRichTextImage);
155                    if( image && (!image->IsEmpty() || image->GetImageBlock().GetData()))
156                        WriteImage( image, stream );
157
158                    node2 = node2->GetNext();
159                }
160
161                EndParagraphFormatting(currentParaStyle, paraStyle, str);
162
163                str << wxT("\n");
164            }
165            node = node->GetNext();
166        }
167
168        CloseLists(-1, str);
169
170        str << wxT("</font>");
171
172        if ((GetFlags() & wxRICHTEXT_HANDLER_NO_HEADER_FOOTER) == 0)
173            str << wxT("</body></html>");
174
175        str << wxT("\n");
176    }
177
178#if wxUSE_UNICODE
179    if (customEncoding)
180        delete customEncoding;
181#endif
182
183    m_buffer = NULL;
184
185    return true;
186}
187
188void wxRichTextHTMLHandler::BeginCharacterFormatting(const wxTextAttrEx& currentStyle, const wxTextAttrEx& thisStyle, const wxTextAttrEx& WXUNUSED(paraStyle), wxTextOutputStream& str)
189{
190    wxString style;
191
192    // Is there any change in the font properties of the item?
193    if (thisStyle.GetFont().GetFaceName() != currentStyle.GetFont().GetFaceName())
194    {
195        wxString faceName(thisStyle.GetFont().GetFaceName());
196        style += wxString::Format(wxT(" face=\"%s\""), faceName.c_str());
197    }
198    if (thisStyle.GetFont().GetPointSize() != currentStyle.GetFont().GetPointSize())
199        style += wxString::Format(wxT(" size=\"%ld\""), PtToSize(thisStyle.GetFont().GetPointSize()));
200    if (thisStyle.GetTextColour() != currentStyle.GetTextColour() )
201    {
202        wxString color(thisStyle.GetTextColour().GetAsString(wxC2S_HTML_SYNTAX));
203        style += wxString::Format(wxT(" color=\"%s\""), color.c_str());
204    }
205
206    if (style.size())
207    {
208        str << wxString::Format(wxT("<font %s >"), style.c_str());
209        m_font = true;
210    }
211
212    if (thisStyle.GetFont().GetWeight() == wxBOLD)
213        str << wxT("<b>");
214    if (thisStyle.GetFont().GetStyle() == wxITALIC)
215        str << wxT("<i>");
216    if (thisStyle.GetFont().GetUnderlined())
217        str << wxT("<u>");
218
219    if (thisStyle.HasURL())
220        str << wxT("<a href=\"") << thisStyle.GetURL() << wxT("\">");
221}
222
223void wxRichTextHTMLHandler::EndCharacterFormatting(const wxTextAttrEx& WXUNUSED(currentStyle), const wxTextAttrEx& thisStyle, const wxTextAttrEx& WXUNUSED(paraStyle), wxTextOutputStream& stream)
224{
225    if (thisStyle.HasURL())
226        stream << wxT("</a>");
227
228    if (thisStyle.GetFont().GetUnderlined())
229        stream << wxT("</u>");
230    if (thisStyle.GetFont().GetStyle() == wxITALIC)
231        stream << wxT("</i>");
232    if (thisStyle.GetFont().GetWeight() == wxBOLD)
233        stream << wxT("</b>");
234
235    if (m_font)
236    {
237        m_font = false;
238        stream << wxT("</font>");
239    }
240}
241
242/// Begin paragraph formatting
243void wxRichTextHTMLHandler::BeginParagraphFormatting(const wxTextAttrEx& WXUNUSED(currentStyle), const wxTextAttrEx& thisStyle, wxTextOutputStream& str)
244{
245    if (thisStyle.HasPageBreak())
246    {
247        str << wxT("<div style=\"page-break-after:always\"></div>\n");
248    }
249
250    if (thisStyle.HasLeftIndent() && thisStyle.GetLeftIndent() != 0)
251    {
252        if (thisStyle.HasBulletStyle())
253        {
254            int indent = thisStyle.GetLeftIndent();
255
256            // Close levels high than this
257            CloseLists(indent, str);
258
259            if (m_indents.GetCount() > 0 && indent == m_indents.Last())
260            {
261                // Same level, no need to start a new list
262            }
263            else if (m_indents.GetCount() == 0 || indent > m_indents.Last())
264            {
265                m_indents.Add(indent);
266
267                wxString tag;
268                int listType = TypeOfList(thisStyle, tag);
269                m_listTypes.Add(listType);
270
271                // wxHTML needs an extra <p> before a list when using <p> ... </p> in previous paragraphs.
272                // TODO: pass a flag that indicates we're using wxHTML.
273                str << wxT("<p>\n");
274
275                str << tag;
276            }
277
278            str << wxT("<li> ");
279        }
280        else
281        {
282            CloseLists(-1, str);
283
284            wxString align = GetAlignment(thisStyle);
285            str << wxString::Format(wxT("<p align=\"%s\""), align.c_str());
286
287            wxString styleStr;
288
289            if ((GetFlags() & wxRICHTEXT_HANDLER_USE_CSS) && thisStyle.HasParagraphSpacingBefore())
290            {
291                float spacingBeforeMM = thisStyle.GetParagraphSpacingBefore() / 10.0;
292
293                styleStr += wxString::Format(wxT("margin-top: %.2fmm; "), spacingBeforeMM);
294            }
295            if ((GetFlags() & wxRICHTEXT_HANDLER_USE_CSS) && thisStyle.HasParagraphSpacingAfter())
296            {
297                float spacingAfterMM = thisStyle.GetParagraphSpacingAfter() / 10.0;
298
299                styleStr += wxString::Format(wxT("margin-bottom: %.2fmm; "), spacingAfterMM);
300            }
301
302            float indentLeftMM = (thisStyle.GetLeftIndent() + thisStyle.GetLeftSubIndent())/10.0;
303            if ((GetFlags() & wxRICHTEXT_HANDLER_USE_CSS) && (indentLeftMM > 0.0))
304            {
305                styleStr += wxString::Format(wxT("margin-left: %.2fmm; "), indentLeftMM);
306            }
307            float indentRightMM = thisStyle.GetRightIndent()/10.0;
308            if ((GetFlags() & wxRICHTEXT_HANDLER_USE_CSS) && thisStyle.HasRightIndent() && (indentRightMM > 0.0))
309            {
310                styleStr += wxString::Format(wxT("margin-right: %.2fmm; "), indentRightMM);
311            }
312            // First line indentation
313            float firstLineIndentMM = - thisStyle.GetLeftSubIndent() / 10.0;
314            if ((GetFlags() & wxRICHTEXT_HANDLER_USE_CSS) && (firstLineIndentMM > 0.0))
315            {
316                styleStr += wxString::Format(wxT("text-indent: %.2fmm; "), firstLineIndentMM);
317            }
318
319            if (!styleStr.IsEmpty())
320                str << wxT(" style=\"") << styleStr << wxT("\"");
321
322            str << wxT(">");
323
324            // TODO: convert to pixels
325            int indentPixels = indentLeftMM*10/4;
326
327            if ((GetFlags() & wxRICHTEXT_HANDLER_USE_CSS) == 0)
328            {
329                // Use a table to do indenting if we don't have CSS
330                str << wxString::Format(wxT("<table border=0 cellpadding=0 cellspacing=0><tr><td width=\"%d\"></td><td>"), indentPixels);
331                m_inTable = true;
332            }
333
334            if (((GetFlags() & wxRICHTEXT_HANDLER_USE_CSS) == 0) && (thisStyle.GetLeftSubIndent() < 0))
335            {
336                str << SymbolicIndent( - thisStyle.GetLeftSubIndent());
337            }
338        }
339    }
340    else
341    {
342        CloseLists(-1, str);
343
344        wxString align = GetAlignment(thisStyle);
345        str << wxString::Format(wxT("<p align=\"%s\""), align.c_str());
346
347        wxString styleStr;
348
349        if ((GetFlags() & wxRICHTEXT_HANDLER_USE_CSS) && thisStyle.HasParagraphSpacingBefore())
350        {
351            float spacingBeforeMM = thisStyle.GetParagraphSpacingBefore() / 10.0;
352
353            styleStr += wxString::Format(wxT("margin-top: %.2fmm; "), spacingBeforeMM);
354        }
355        if ((GetFlags() & wxRICHTEXT_HANDLER_USE_CSS) && thisStyle.HasParagraphSpacingAfter())
356        {
357            float spacingAfterMM = thisStyle.GetParagraphSpacingAfter() / 10.0;
358
359            styleStr += wxString::Format(wxT("margin-bottom: %.2fmm; "), spacingAfterMM);
360        }
361
362        if (!styleStr.IsEmpty())
363            str << wxT(" style=\"") << styleStr << wxT("\"");
364
365        str << wxT(">");
366    }
367    OutputFont(thisStyle, str);
368}
369
370/// End paragraph formatting
371void wxRichTextHTMLHandler::EndParagraphFormatting(const wxTextAttrEx& WXUNUSED(currentStyle), const wxTextAttrEx& thisStyle, wxTextOutputStream& stream)
372{
373    if (thisStyle.HasFont())
374        stream << wxT("</font>");
375
376    if (m_inTable)
377    {
378        stream << wxT("</td></tr></table></p>\n");
379        m_inTable = false;
380    }
381    else if (!thisStyle.HasBulletStyle())
382        stream << wxT("</p>\n");
383}
384
385/// Closes lists to level (-1 means close all)
386void wxRichTextHTMLHandler::CloseLists(int level, wxTextOutputStream& str)
387{
388    // Close levels high than this
389    int i = m_indents.GetCount()-1;
390    while (i >= 0)
391    {
392        int l = m_indents[i];
393        if (l > level)
394        {
395            if (m_listTypes[i] == 0)
396                str << wxT("</ol>");
397            else
398                str << wxT("</ul>");
399            m_indents.RemoveAt(i);
400            m_listTypes.RemoveAt(i);
401        }
402        else
403            break;
404        i --;
405     }
406}
407
408/// Output font tag
409void wxRichTextHTMLHandler::OutputFont(const wxTextAttrEx& style, wxTextOutputStream& stream)
410{
411    if (style.HasFont())
412    {
413        stream << wxString::Format(wxT("<font face=\"%s\" size=\"%ld\""), style.GetFont().GetFaceName().c_str(), PtToSize(style.GetFont().GetPointSize()));
414        if (style.HasTextColour())
415            stream << wxString::Format(wxT(" color=\"%s\""), style.GetTextColour().GetAsString(wxC2S_HTML_SYNTAX).c_str());
416        stream << wxT(" >");
417    }
418}
419
420int wxRichTextHTMLHandler::TypeOfList( const wxTextAttrEx& thisStyle, wxString& tag )
421{
422    // We can use number attribute of li tag but not all the browsers support it.
423    // also wxHtmlWindow doesn't support type attribute.
424
425    bool m_is_ul = false;
426    if (thisStyle.GetBulletStyle() == (wxTEXT_ATTR_BULLET_STYLE_ARABIC|wxTEXT_ATTR_BULLET_STYLE_PERIOD))
427        tag = wxT("<ol type=\"1\">");
428    else if (thisStyle.GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_LETTERS_UPPER)
429        tag = wxT("<ol type=\"A\">");
430    else if (thisStyle.GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_LETTERS_LOWER)
431        tag = wxT("<ol type=\"a\">");
432    else if (thisStyle.GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_ROMAN_UPPER)
433        tag = wxT("<ol type=\"I\">");
434    else if (thisStyle.GetBulletStyle() == wxTEXT_ATTR_BULLET_STYLE_ROMAN_LOWER)
435        tag = wxT("<ol type=\"i\">");
436    else
437    {
438        tag = wxT("<ul>");
439        m_is_ul = true;
440    }
441
442    if (m_is_ul)
443        return 1;
444    else
445        return 0;
446}
447
448wxString wxRichTextHTMLHandler::GetAlignment( const wxTextAttrEx& thisStyle )
449{
450    switch( thisStyle.GetAlignment() )
451    {
452    case wxTEXT_ALIGNMENT_LEFT:
453        return  wxT("left");
454    case wxTEXT_ALIGNMENT_RIGHT:
455        return wxT("right");
456    case wxTEXT_ALIGNMENT_CENTER:
457        return wxT("center");
458    case wxTEXT_ALIGNMENT_JUSTIFIED:
459        return wxT("justify");
460    default:
461        return wxT("left");
462    }
463}
464
465void wxRichTextHTMLHandler::WriteImage(wxRichTextImage* image, wxOutputStream& stream)
466{
467    wxTextOutputStream str(stream);
468
469    str << wxT("<img src=\"");
470
471#if wxUSE_FILESYSTEM
472    if (GetFlags() & wxRICHTEXT_HANDLER_SAVE_IMAGES_TO_MEMORY)
473    {
474        if (!image->GetImage().Ok() && image->GetImageBlock().GetData())
475            image->LoadFromBlock();
476        if (image->GetImage().Ok() && !image->GetImageBlock().GetData())
477            image->MakeBlock();
478
479        if (image->GetImage().Ok())
480        {
481            wxString ext(image->GetImageBlock().GetExtension());
482            wxString tempFilename(wxString::Format(wxT("image%d.%s"), sm_fileCounter, (const wxChar*) ext));
483            wxMemoryFSHandler::AddFile(tempFilename, image->GetImage(), image->GetImageBlock().GetImageType());
484
485            m_imageLocations.Add(tempFilename);
486
487            str << wxT("memory:") << tempFilename;
488        }
489        else
490            str << wxT("memory:?");
491
492        sm_fileCounter ++;
493    }
494    else if (GetFlags() & wxRICHTEXT_HANDLER_SAVE_IMAGES_TO_FILES)
495    {
496        if (!image->GetImage().Ok() && image->GetImageBlock().GetData())
497            image->LoadFromBlock();
498        if (image->GetImage().Ok() && !image->GetImageBlock().GetData())
499            image->MakeBlock();
500
501        if (image->GetImage().Ok())
502        {
503            wxString tempDir(GetTempDir());
504            if (tempDir.IsEmpty())
505                tempDir = wxFileName::GetTempDir();
506
507            wxString ext(image->GetImageBlock().GetExtension());
508            wxString tempFilename(wxString::Format(wxT("%s/image%d.%s"), (const wxChar*) tempDir, sm_fileCounter, (const wxChar*) ext));
509            image->GetImageBlock().Write(tempFilename);
510
511            m_imageLocations.Add(tempFilename);
512
513            str << wxFileSystem::FileNameToURL(tempFilename);
514        }
515        else
516            str << wxT("file:?");
517
518        sm_fileCounter ++;
519    }
520    else // if (GetFlags() & wxRICHTEXT_HANDLER_SAVE_IMAGES_TO_BASE64) // this is implied
521#endif
522    {
523        str << wxT("data:");
524        str << GetMimeType(image->GetImageBlock().GetImageType());
525        str << wxT(";base64,");
526
527        if (image->GetImage().Ok() && !image->GetImageBlock().GetData())
528            image->MakeBlock();
529
530        wxChar* data = b64enc( image->GetImageBlock().GetData(), image->GetImageBlock().GetDataSize() );
531        str << data;
532
533        delete[] data;
534    }
535
536    str << wxT("\" />");
537}
538
539long wxRichTextHTMLHandler::PtToSize(long size)
540{
541    int i;
542    int len = m_fontSizeMapping.GetCount();
543    for (i = 0; i < len; i++)
544        if (size <= m_fontSizeMapping[i])
545            return i+1;
546    return 7;
547}
548
549wxString wxRichTextHTMLHandler::SymbolicIndent(long indent)
550{
551    wxString in;
552    for(;indent > 0; indent -= 20)
553        in.Append( wxT("&nbsp;") );
554    return in;
555}
556
557const wxChar* wxRichTextHTMLHandler::GetMimeType(int imageType)
558{
559    switch(imageType)
560    {
561    case wxBITMAP_TYPE_BMP:
562        return wxT("image/bmp");
563    case wxBITMAP_TYPE_TIF:
564        return wxT("image/tiff");
565    case wxBITMAP_TYPE_GIF:
566        return wxT("image/gif");
567    case wxBITMAP_TYPE_PNG:
568        return wxT("image/png");
569    case wxBITMAP_TYPE_JPEG:
570        return wxT("image/jpeg");
571    default:
572        return wxT("image/unknown");
573    }
574}
575
576// exim-style base64 encoder
577wxChar* wxRichTextHTMLHandler::b64enc( unsigned char* input, size_t in_len )
578{
579    // elements of enc64 array must be 8 bit values
580    // otherwise encoder will fail
581    // hmmm.. Does wxT macro define a char as 16 bit value
582    // when compiling with UNICODE option?
583    static const wxChar enc64[] = wxT("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
584    wxChar* output = new wxChar[4*((in_len+2)/3)+1];
585    wxChar* p = output;
586
587    while( in_len-- > 0 )
588    {
589        register wxChar a, b;
590
591        a = *input++;
592
593        *p++ = enc64[ (a >> 2) & 0x3f ];
594
595        if( in_len-- == 0 )
596        {
597            *p++ = enc64[ (a << 4 ) & 0x30 ];
598            *p++ = '=';
599            *p++ = '=';
600            break;
601        }
602
603        b = *input++;
604
605        *p++ = enc64[(( a << 4 ) | ((b >> 4) &0xf )) & 0x3f];
606
607        if( in_len-- == 0 )
608        {
609            *p++ = enc64[ (b << 2) & 0x3f ];
610            *p++ = '=';
611            break;
612        }
613
614        a = *input++;
615
616        *p++ = enc64[ ((( b << 2 ) & 0x3f ) | ((a >> 6)& 0x3)) & 0x3f ];
617
618        *p++ = enc64[ a & 0x3f ];
619    }
620    *p = 0;
621
622    return output;
623}
624#endif
625// wxUSE_STREAMS
626
627/// Delete the in-memory or temporary files generated by the last operation
628bool wxRichTextHTMLHandler::DeleteTemporaryImages()
629{
630    return DeleteTemporaryImages(GetFlags(), m_imageLocations);
631}
632
633/// Delete the in-memory or temporary files generated by the last operation
634bool wxRichTextHTMLHandler::DeleteTemporaryImages(int flags, const wxArrayString& imageLocations)
635{
636    size_t i;
637    for (i = 0; i < imageLocations.GetCount(); i++)
638    {
639        wxString location = imageLocations[i];
640
641        if (flags & wxRICHTEXT_HANDLER_SAVE_IMAGES_TO_MEMORY)
642        {
643#if wxUSE_FILESYSTEM
644            wxMemoryFSHandler::RemoveFile(location);
645#endif
646        }
647        else if (flags & wxRICHTEXT_HANDLER_SAVE_IMAGES_TO_FILES)
648        {
649            if (wxFileExists(location))
650                wxRemoveFile(location);
651        }
652    }
653
654    return true;
655}
656
657
658#endif
659// wxUSE_RICHTEXT
660
661