/***********************************************************************************

    Copyright (C) 2007-2026 Ahmet Öztürk (aoz_2@yahoo.com)

    This file is part of Lifeograph.

    Lifeograph is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Lifeograph is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Lifeograph.  If not, see <http://www.gnu.org/licenses/>.

***********************************************************************************/


#include "lifeograph.hpp"
#include "app_window.hpp"
#include "printing.hpp"
#include "diaryelements/diary.hpp"
#include "widgets/chart_surface.hpp"


using namespace LoG;

// PRINTABLES ======================================================================================
void
PrintablePageStart::render()
{
    auto cr{ PrintOpr::s_print_context->get_cairo_context() };

    const double&& w{ PrintOpr::s_print_context->get_width() };
    const double&& h{ PrintOpr::s_print_context->get_height() };
    cr->rectangle( w / -2, h / -2, w * 2, h * 2 );

    cr->set_source_rgb( m_bg_color.get_red(), m_bg_color.get_green(), m_bg_color.get_blue() );
    cr->fill();
}

double
PrintableVSpace::get_height() const
{
    return( PrintOpr::s_print_context->get_dpi_y() / 8 );
}

void
PrintableTextLine::render()
{
    Cairo::RefPtr< Cairo::Context > cr( PrintOpr::s_print_context->get_cairo_context() );

    // Set the main color
    cr->set_source_rgb( m_color.get_red(), m_color.get_green(), m_color.get_blue() );
    cr->move_to( m_x, m_y + get_height() );
    m_layout->show_in_cairo_context( cr );
}

void
PrintableImage::render()
{
    Cairo::RefPtr< Cairo::Context > cr = PrintOpr::s_print_context->get_cairo_context();
    Gdk::Cairo::set_source_pixbuf( cr, m_pixbuf, m_x, m_y );
    cr->paint();
}

void
PrintableChart::render()
{
    Cairo::RefPtr< Cairo::Context > cr = PrintOpr::s_print_context->get_cairo_context();
    m_surface->render( cr, m_x, m_y );
}

void
PrintableTable::render()
{
    Cairo::RefPtr< Cairo::Context > cr = PrintOpr::s_print_context->get_cairo_context();
    m_surface->render( cr, m_x, m_y );
}

void
PrintableBullet::render()
{
    Cairo::RefPtr< Cairo::Context > cr = PrintOpr::s_print_context->get_cairo_context();

    // TODO? Gdk::Cairo::set_source_rgba( cr, m_p2theme->color_subsubheading );
    cr->begin_new_path();
    cr->set_line_width( 1 );
    cr->arc( m_x, m_y, m_radius, 0.0, 2 * HELPERS::PI );
    if( m_F_fill )
        cr->fill();
    else
        cr->stroke();
}

void
PrintableHRule::render()
{
    Cairo::RefPtr< Cairo::Context > cr = PrintOpr::s_print_context->get_cairo_context();

    // TODO? Gdk::Cairo::set_source_rgba( cr, m_p2theme->color_subsubheading );
    cr->begin_new_path();
    cr->set_line_width( 1 );
    cr->move_to( m_x, m_y );
    cr->rel_line_to( m_width, 0.0 );
    cr->stroke();
}

// TODO: 3.3 void
// PrintableTag::render()
// {
//     Cairo::RefPtr< Cairo::Context > cr = PrintOpr::s_print_context->get_cairo_context();
//
//     const double half_height { ( m_coords[ 3 ] - m_coords[ 1 ] ) / 2.0 };
//
//     if( !m_color.empty() )
//         Gdk::Cairo::set_source_rgba( cr, Color( m_color ) );
//
//     cr->set_line_width( 1.2 );
//
//     for( unsigned i = 0; i < m_coords.size(); i += 4 )
//     {
//         const double width { double( m_coords[ i + 2 ] - m_coords[ i ] ) };
//         cr->move_to( m_coords[ i + 2 ], m_coords[ i + 3 ] );
//         cr->rel_line_to( -width, 0.0 ); // top horizontal
//
//         if( i == 0 ) // beginning
//             cr->rel_line_to( 0.0, -2.0 * half_height );
//         else
//             cr->rel_move_to( 0.0, -2.0 * half_height );
//
//         cr->rel_line_to( width, 0.0 ); // bottom horizontal
//
//         if( i == m_coords.size() - 4 ) // end
//         {
//             cr->rel_line_to( half_height / 3.0, half_height );
//             cr->line_to( m_coords[ i + 2 ], m_coords[ i + 3 ] );
//         }
//     }
//
//     if( format->ref_id == DEID_UNSET )
//     {
//         cr->set_dash( m_dash_pattern, 0.0 );
//         cr->stroke();
//         cr->unset_dash();
//     }
//     else
//         cr->stroke();
// }

// PRINT OPERATION =================================================================================
Glib::RefPtr< Gtk::PrintContext > PrintOpr::s_print_context;

Glib::RefPtr< PrintOpr >
PrintOpr::create()
{
    return Glib::make_refptr_for_instance< PrintOpr >( new PrintOpr() );
}

PrintOpr::PrintOpr()
{
    set_track_print_status();
    set_embed_page_setup( true );
}

PrintOpr::~PrintOpr()
{
}

void
PrintOpr::clear_content()
{
    for( ListPrintables::iterator iter = m_content.begin(); iter != m_content.end(); ++iter )
        delete *iter;

    m_content.clear();
}

void
PrintOpr::init_variables()
{
    // ENTRY BOUNDARIES
    if( m_parser.m_opt_one_entry )
    {
        m_p2entry_bgn = AppWindow::p->UI_entry->get_entry();
        m_p2entry_end = m_p2entry_bgn->get_next_straight();
    }
    else
    {
        m_p2entry_bgn = Diary::d->get_entry_1st();
        m_p2entry_end = nullptr; //Diary::d->get_entry_last();
    }
}

void
PrintOpr::set_hide_comments( bool flag_hide )
{
    m_parser.m_opt_hide_comments = flag_hide;
}

Gtk::Widget*
PrintOpr::on_create_custom_widget()
{
    set_custom_tab_label( "Lifeograph Specific" );

    auto update_widgetry = [ this ]()
    {
        m_ChB_1_entry_1_page->set_visible( not( m_RB_current->get_active() ) );

        if( m_ChB_theme_colors->get_active() )
            m_ChB_1_entry_1_page->set_active();
        m_ChB_1_entry_1_page->set_sensitive( not( m_ChB_theme_colors->get_active() ) );

        m_FB->set_visible( not( m_ChB_theme_font->get_active() ) );
    };

    if( m_builder )
        m_builder.reset();

    m_builder               = Lifeograph::create_gui( Lifeograph::SHAREDIR + "/ui/print.ui" );

    m_Bx_print              = m_builder->get_widget< Gtk::Box >( "Bx_print" );

    m_RB_current            = m_builder->get_widget< Gtk::CheckButton >( "RB_print_current" );
    m_RB_filtered           = m_builder->get_widget< Gtk::CheckButton >( "RB_print_filtered" );
    m_ChB_1_entry_1_page    = m_builder->get_widget< Gtk::CheckButton >( "CB_one_entry_per_page" );

    m_FB                    = m_builder->get_widget< Gtk::FontButton >( "FB_print" );

    m_ChB_theme_font        = m_builder->get_widget< Gtk::CheckButton >( "CB_use_theme_font" );
    m_ChB_theme_colors      = m_builder->get_widget< Gtk::CheckButton >( "CB_use_theme_colors" );

    m_RB_margin_off         = m_builder->get_widget< Gtk::ToggleButton >( "RB_page_margin_off" );
    m_RB_margin_half        = m_builder->get_widget< Gtk::ToggleButton >( "RB_page_margin_half" );

    m_RB_current->signal_toggled().connect( update_widgetry );
    m_ChB_theme_font->signal_toggled().connect( update_widgetry );
    m_ChB_theme_colors->signal_toggled().connect( update_widgetry );

    update_widgetry();

    return m_Bx_print;
}

void
PrintOpr::on_custom_widget_apply( Gtk::Widget* )
{
    m_parser.m_opt_one_entry = m_RB_current->get_active();
    m_parser.m_opt_filtered_only = m_RB_filtered->get_active();

    m_parser.m_opt_entries_at_page_start = m_ChB_1_entry_1_page->get_active();

    m_parser.m_opt_use_theme_font = m_ChB_theme_font->get_active();

    if( !m_parser.m_opt_use_theme_font )
        m_parser.m_font_cur = m_parser.m_font_main = m_FB->get_font_desc();

    m_parser.m_opt_use_theme_colors = m_ChB_theme_colors->get_active();

    m_parser.m_opt_margin = m_RB_margin_off->get_active() ? 0.0 :
                                ( m_RB_margin_half->get_active() ? 0.25 : 0.5 );
}

// void
// PrintOpr::show_page_setup()
// {
//     // Show the page setup dialog, asking it to start with the existing settings:
//     Glib::RefPtr< Gtk::PageSetup > new_page_setup =
//             Gtk::run_page_setup_dialog( *AppWindow::p, m_refPageSetup, m_refSettings );
//
//     //Save the chosen page setup dialog for use when printing, previewing, or
//     //showing the page setup dialog again:
//     m_refPageSetup = new_page_setup;
// }

void
PrintOpr::parse()
{
    m_parser.init();

    // diary title:
    if( !m_parser.m_opt_one_entry )
    {
        m_parser.add_diary_cover();
        m_content.splice( m_content.end(), m_parser.m_content );
    }

    for( auto entry = m_p2entry_bgn; entry != m_p2entry_end; entry = entry->get_next_straight() )
    {
        if( m_parser.m_opt_one_entry || !m_parser.m_opt_filtered_only || !entry->is_filtered_out() )
        {
            m_parser.add_entry( entry );
            m_content.splice( m_content.end(), m_parser.m_content );
        }
    }

    set_n_pages( m_parser.m_n_pages );
}

void
PrintOpr::on_begin_print( const Glib::RefPtr< Gtk::PrintContext >& print_context )
{
    s_print_context = print_context;

    clear_content();
    init_variables();

    parse();
}

void
PrintOpr::on_draw_page( const Glib::RefPtr< Gtk::PrintContext >& print_context, int page_no )
{
    //s_print_context = print_context; // does it change after on_begin_print?

    int i_page{ -1 };

    for( Printable* printable : m_content )
    {
        if( printable->get_type() == Printable::Type::PT_PAGE_START )
            i_page++;

        if( i_page == page_no )
            printable->render();
        else if( i_page > page_no )
            break;
    }
}

void
PrintOpr::on_status_changed()
{
    if( is_finished() )
    {
        print_info( "Print job completed" );
    }
    else
    {
        print_info( get_status_string() );
    }
}

void
PrintOpr::on_done( Gtk::PrintOperation::Result result )
{
    //Printing is "done" when the print data is spooled.

    if( result == Gtk::PrintOperation::Result::ERROR )
    {
        DialogMessage::init( AppWindow::p, _( "Printing failed" ), _( "_Close" ) )->show();
    }
    else
    if( result == Gtk::PrintOperation::Result::APPLY )
    {
        // Update PrintSettings with the ones used in this PrintOperation:
        AppWindow::p->m_r2print_settings = get_print_settings();
    }

   /* if( ! is_finished())
    {
    // We will connect to the status-changed signal to track status
    // and update a status bar. In addition, you can, for example,
    // keep a list of active print operations, or provide a progress dialog.
        signal_status_changed().connect( sigc::bind( sigc::mem_fun( *this,
                    &PrintOpr::handle_print_status_changed ),
                operation ) );
    }*/
}

// PANGO ENTRY PARSER ==============================================================================
void
EntryParserPango::init()
{
    // margins are 1/2" on each side when full
    m_margin_x = PrintOpr::s_print_context->get_dpi_x() * m_opt_margin;
    m_margin_y = PrintOpr::s_print_context->get_dpi_y() * m_opt_margin;
    m_page_w = PrintOpr::s_print_context->get_width() - ( 2 * m_margin_x );
    m_page_h = PrintOpr::s_print_context->get_height() - ( 2 * m_margin_y );

    m_total_h_cur = m_page_h;   // to force add_page() at the beginning
    m_entry_break_h = PrintOpr::s_print_context->get_dpi_y() * 2.0; // 2" break
    m_unit_indent_w = PrintOpr::s_print_context->get_dpi_x() * 0.5; // 1/2" indent
    m_n_pages = 0;

    // when multiple entries will be printed set theme to default for the cover
    if( m_opt_use_theme_colors == false || m_opt_one_entry == false )
        m_p2theme = ThemeSystem::get();
}

void
EntryParserPango::add_diary_cover()
{
    add_page();

    m_alignment = Pango::Alignment::CENTER;
    m_total_h_cur = m_page_h / 1.9;

    if( m_opt_use_theme_font )
        m_font_cur = ThemeSystem::get()->font;

    R2Pixbuf&& pixbuf{ Lifeograph::icons->diary_32->scale_simple( m_page_h / 20,
                                                                  m_page_h / 20,
                                                                  Gdk::InterpType::BILINEAR ) };
    add_printable( new PrintableImage( pixbuf,
                                       m_margin_x + m_page_w / 2 - m_page_h / 40,
                                       m_total_h_cur ) );

    m_marked_up_text = STR::compose( "<span size='xx-large'>",
                                     Glib::Markup::escape_text( Diary::d->get_name() ),
                                     "</span>" );
    add_text();

    m_marked_up_text = Date::format_string( Date::get_today() );
    add_text();

    m_total_h_cur = m_page_h;   // to force add_page() at the beginning
}

void
EntryParserPango::update_line_heights()
{
    auto&& layout{ PrintOpr::s_print_context->create_pango_layout() };
    layout->set_font_description( m_font_cur );
    layout->set_markup( "LLL" );
    m_line_h_N = layout->get_iter().get_line()->get_pixel_logical_extents().get_height();
    layout->set_markup( "<b><span size='xx-large'>LLL</span></b>" );
    m_line_h_TTL = layout->get_iter().get_line()->get_pixel_logical_extents().get_height();
    layout->set_markup( "<b><span size='x-large'>LLL</span></b>" );
    m_line_h_HS1 = layout->get_iter().get_line()->get_pixel_logical_extents().get_height();
    layout->set_markup( "<b><span size='larger'>LLL</span></b>" );
    m_line_h_HS2 = layout->get_iter().get_line()->get_pixel_logical_extents().get_height();
}

void
EntryParserPango::add_entry( const Entry* entry )
{
    m_format_map_offset = 0;
    m_content.clear();
    m_source_text = entry->get_text();
    m_format_map.clear();
    m_format_map.assign( m_source_text.length() + 1, 0 ); // +1 is for the file end
    m_p2entry = entry;
    if( m_opt_use_theme_font )
        m_font_cur = m_font_main = entry->get_theme()->font;
    if( m_opt_use_theme_colors )
        m_p2theme = m_p2entry->get_theme();

    // ENTRY PARAGRAPHS
    for( Paragraph* para = entry->get_paragraph_1st(); para; para = para->m_p2next )
    {
        process_paragraph( para );

        add_text();    // add paragraph

        m_format_map_offset += ( para->get_size() + 1 ); // +1 is to account for the \n
    } // end of para loop

    add_entry_break();
}

inline void
EntryParserPango::add_page()
{
    m_content.push_back( new PrintablePageStart( m_p2theme->color_base ) );
    m_n_pages++;
    m_total_h_cur = m_margin_y;
}

void
EntryParserPango::add_printable( Printable* printable, bool F_adds_to_h )
{
    if( m_total_h_cur + printable->get_height() > m_page_h )
    {
        const auto prev_offset{ printable->m_y - m_total_h_cur };
        add_page();
        printable->m_y = m_total_h_cur + prev_offset;
    }

    // do not add empty lines to the top of pages
    if( printable->m_y == m_margin_y && printable->is_empty() )
        delete printable;
    else
    {
        m_content.push_back( printable );

        if( F_adds_to_h )
        {
            m_total_h_cur += printable->get_height();

            if( m_img_reserv.h > 0 )
            {
                m_img_reserv.h -= printable->get_height();
                if( m_img_reserv.h <= 0 ) // clear reservation
                    m_img_reserv.w = m_img_reserv.h = m_img_reserv.x = 0;
            }
        }
    }
}

inline void
EntryParserPango::add_entry_break()
{
    if( m_opt_entries_at_page_start || m_total_h_cur + m_entry_break_h > m_page_h )
        m_total_h_cur = m_page_h;   // so any new printable will cause an add_page()
    else
        m_total_h_cur += m_entry_break_h;
}

void
EntryParserPango::add_text()
{
    const double  px_indent { m_indent_level_cur * m_unit_indent_w };
    auto          layout    { PrintOpr::s_print_context->create_pango_layout() };

    layout->set_width( ( m_page_w - px_indent - m_img_reserv.w ) * Pango::SCALE );
    layout->set_font_description( m_font_cur );
    layout->set_markup( m_marked_up_text );
    layout->set_alignment( m_alignment );


    const int         line_count { layout->get_line_count() };
    Pango::Rectangle  ink_rect, logical_rect;

    for( int line = 0; line < line_count; ++line )
    {
        auto layout_line = layout->get_line( line );
        layout_line->get_extents( ink_rect, logical_rect );

        const auto  layout_w  { logical_rect.get_width() / Pango::SCALE };
        double      x         { m_margin_x + px_indent + m_img_reserv.x };

        switch( m_alignment )
        {
            case Pango::Alignment::LEFT:
                break;
            case Pango::Alignment::CENTER:
                x += ( ( m_page_w - px_indent - layout_w ) / 2 ); break;
            case Pango::Alignment::RIGHT:
                x += ( m_page_w - px_indent - layout_w - m_img_reserv.w ); break;
        }

        add_printable( new PrintableTextLine( layout_line, m_marked_up_text.empty(),
                                              x, m_total_h_cur,
                                              m_p2theme->color_text ) );
    }

    m_marked_up_text.clear();
}

inline void
EntryParserPango::add_image( const R2Pixbuf& pixbuf )
{
    if( !pixbuf ) return; // some callers may feed pixbuf without checking

    double            px_indent { m_indent_level_cur * m_unit_indent_w };
    double            x         { m_margin_x + px_indent };

    if( not( m_marked_up_text.empty() ) )
        add_text();

    switch( m_alignment )
    {
        case Pango::Alignment::LEFT:
            m_img_reserv.w = m_img_reserv.x = pixbuf->get_width() + IMAGE_MARGIN;
            m_img_reserv.h = pixbuf->get_height();
            break;
        case Pango::Alignment::CENTER:
            m_img_reserv.w = m_img_reserv.h = m_img_reserv.x = 0;
            x += ( ( m_page_w - px_indent - pixbuf->get_width() ) / 2 );
            break;
        case Pango::Alignment::RIGHT:
            m_img_reserv.w = pixbuf->get_width() + IMAGE_MARGIN;
            m_img_reserv.h = pixbuf->get_height();
            m_img_reserv.x = 0;
            x += ( m_page_w - px_indent - pixbuf->get_width() );
            break;
    }

    add_printable( new PrintableImage( pixbuf, x, m_total_h_cur ), m_img_reserv.h <= 0 );
}

inline bool
EntryParserPango::add_para_icon( const R2Pixbuf& pixbuf_base )
{
    R2Pixbuf&& pixbuf{ pixbuf_base->scale_simple( m_line_h_cur * 0.8,
                                                  m_line_h_cur * 0.8,
                                                  Gdk::InterpType::BILINEAR ) };

    auto pi { new PrintableImage(
            pixbuf,
            m_margin_x + m_indent_level_cur * m_unit_indent_w - m_line_h_cur * 1.2,
            m_total_h_cur + m_line_h_cur * 0.3 ) };
    pi->m_height = m_line_h_cur; // do not consider the y offset
    add_printable( pi, false ); // do not increment total_height

    return true;
}

inline void
EntryParserPango::add_para_number( const Paragraph* p )
{
    auto&&      layout { PrintOpr::s_print_context->create_pango_layout() };
    layout->set_font_description( m_font_cur );
    layout->set_text( p->get_list_order_str() );
    auto&&       line  { layout->get_iter().get_line() };
    const int    w     { line->get_pixel_logical_extents().get_width() };

    add_printable( new PrintableTextLine(
            line,
            false,
            m_margin_x + m_indent_level_cur * m_unit_indent_w - w - m_line_h_cur * 0.5,
            m_total_h_cur,
            m_p2theme->color_text ),
        false );
}

inline void
EntryParserPango::add_chart( const Paragraph* para )
{
    if( not( m_marked_up_text.empty() ) )
        add_text();

    auto chart{ Diary::d->get_element2< ChartElem >( D::DEID( para->get_uri() ) ) };

    if( !chart ) return;

    auto          cr      { PrintOpr::s_print_context->get_cairo_context() };
    auto          layout  { PrintOpr::s_print_context->create_pango_layout() };
    ChartSurface* surface { new ChartSurface( cr,
                                              chart,
                                              m_page_w * ( para->get_image_size() + 1 ) * 0.25,
                                              -1,
                                              layout,
                                              m_font_main, // use the main font even in quotations
                                              true ) };

    add_printable( new PrintableVSpace );
    add_printable( new PrintableChart( surface,
                                       m_margin_x + ( m_page_w - surface->get_width() ) / 2,
                                       m_total_h_cur ) );
}

inline void
EntryParserPango::add_table( const Paragraph* para )
{
    if( not( m_marked_up_text.empty() ) )
        add_text();

    auto table{ Diary::d->get_element2< TableElem >( D::DEID( para->get_uri() ) ) };

    if( !table ) return;

    auto          cr      { PrintOpr::s_print_context->get_cairo_context() };
    auto          layout  { PrintOpr::s_print_context->create_pango_layout() };
    TableSurface* surface { nullptr };

    add_printable( new PrintableVSpace );

    do
    {
        surface = new TableSurface( cr,
                                    table,
                                    m_page_w,
                                    m_page_h - m_total_h_cur,
                                    layout,
                                    m_font_main, // use the main font even in quotations
                                    bool( para->m_style & VT::PS_IMAGE_EXPND ),
                                    true,
                                    surface ? surface->get_i_line_top_next() : 0 );

        add_printable( new PrintableTable( surface,
                                           m_margin_x + ( m_page_w - surface->get_width() ) / 2,
                                           m_total_h_cur ) );

        if( surface->has_more() ) // for tables pages have to be added in advance
            add_page();
    }
    while( surface->has_more() );
}

inline bool
EntryParserPango::add_para_bullet( bool F_fill )
{
    add_printable( new PrintableBullet(
            m_margin_x + m_indent_level_cur * m_unit_indent_w - m_line_h_cur * 0.8,
            m_total_h_cur + m_line_h_cur * 0.75,
            m_line_h_cur / 5,
            F_fill ),
        false ); // do not increment total_height

    return true;
}

inline bool
EntryParserPango::add_para_hrule( bool F_empty )
{
    add_printable( new PrintableHRule( m_margin_x,
                                       m_total_h_cur + m_line_h_cur * ( F_empty ? 0.6 : 1.15 ),
                                       m_page_w ),
                   false ); // do not increment total_height

    return true;
}

// TODO: 3.3 inline bool
// EntryParserPango::add_tag()
// {
//     add_printable( new PrintableTag(
//             m_margin_x,
//             m_total_h_cur + m_line_h * 1.05,
//             m_page_w - m_margin_x * 2 ),
//         false ); // do not increment total_height
//
//     return true;
// }

void
EntryParserPango::append_markups()
{
    // close last
    if( ( m_format_prev & CS_LINK ) == CS_LINK )
        m_marked_up_text += "</span>";

    if( ( m_format_prev & ( CS_FADED ) ) == CS_FADED )
        m_marked_up_text += "</span>";

    if( ( m_format_prev & CS_SUPERSCRIPT ) == CS_SUPERSCRIPT ) // don't change the order with sub
        m_marked_up_text += "</sup>";
    else if( ( m_format_prev & CS_SUBSCRIPT ) == CS_SUBSCRIPT )
        m_marked_up_text += "</sub>";

    if( ( m_format_prev & ( CS_STRIKETHROUGH|CS_COMMENT ) ) == CS_STRIKETHROUGH )
        m_marked_up_text += "</s>";

    if( ( m_format_prev & CS_TODO_FILTER ) == CS_TODO_DONE )
        m_marked_up_text += "</span>";

    if( ( m_format_prev & CS_INLINE_TAG ) == CS_INLINE_TAG )
        m_marked_up_text += "</span>";

    if( ( m_format_prev & CS_HIGHLIGHT ) == CS_HIGHLIGHT )
        m_marked_up_text += "</span>";

    if( ( m_format_prev & CS_UNDERLINE ) == CS_UNDERLINE )
        m_marked_up_text += "</u>";

    if( ( m_format_prev & CS_ITALIC ) == CS_ITALIC )
        m_marked_up_text += "</i>";

    if( ( m_format_prev & CS_BOLD ) == CS_BOLD )
        m_marked_up_text += "</b>";

    if( m_format_prev & CS_SMALLER )
        m_marked_up_text += "</span>";

    if( m_format_prev & CS_COMMENT )
        m_marked_up_text += "</span>";

    if( m_format_prev & CS_HEADING ) // works for any heading type
        m_marked_up_text += "</span></b>";

    // open new (in reverse order)
    if( ( m_format_cur & CS_HEADING_FILTER ) == CS_HEADING )
        m_marked_up_text += STR::compose(
                "<b><span size='xx-large' color='",
                convert_gdkcolor_to_html( m_p2theme->color_title ), "'>" );

    if( ( m_format_cur & CS_HEADING_FILTER ) == CS_SUBHEADING )
        m_marked_up_text += STR::compose(
                "<b><span size='x-large' color='",
                convert_gdkcolor_to_html( m_p2theme->color_heading_L ), "'>" );

    if( ( m_format_cur & CS_HEADING_FILTER ) == CS_SUBSUBHEADING )
        m_marked_up_text += STR::compose(
                "<b><span size='larger' color='",
                convert_gdkcolor_to_html( m_p2theme->color_heading_M ), "'>" );

    if( ( m_format_cur & CS_COMMENT ) == CS_COMMENT )
        m_marked_up_text += STR::compose(
                "<span color='",
                convert_gdkcolor_to_html( m_p2theme->color_mid ), "'>" );

    if( ( m_format_cur & CS_SMALLER ) == CS_SMALLER )
        m_marked_up_text += STR::compose( "<span size='smaller'>" );

    if( ( m_format_cur & CS_BOLD ) == CS_BOLD )
        m_marked_up_text += "<b>";

    if( ( m_format_cur & CS_ITALIC ) == CS_ITALIC )
        m_marked_up_text += "<i>";

    if( ( m_format_cur & CS_UNDERLINE ) == CS_UNDERLINE )
        m_marked_up_text += "<u>";

    if( ( m_format_cur & CS_HIGHLIGHT ) == CS_HIGHLIGHT )
        m_marked_up_text += STR::compose(
                "<span bgcolor='", convert_gdkcolor_to_html( m_p2theme->color_highlight ), "'>" );

    if( ( m_format_cur & CS_INLINE_TAG ) == CS_INLINE_TAG )
        m_marked_up_text += STR::compose(
                "<span bgcolor='", convert_gdkcolor_to_html( m_p2theme->color_inline_tag ),
                "' font='Monospace'>" );

    if( ( m_format_cur & CS_TODO_FILTER ) == CS_TODO_DONE )
        m_marked_up_text += STR::compose(
                "<span color='", convert_gdkcolor_to_html( m_p2theme->color_done ),
                "' bgcolor='", convert_gdkcolor_to_html( m_p2theme->color_done_bg ), "'>" );

    // disable strikethrough in comments
    if( ( m_format_cur & ( CS_STRIKETHROUGH|CS_COMMENT ) ) == CS_STRIKETHROUGH )
        m_marked_up_text += "<s>";

    if( ( m_format_cur & CS_SUPERSCRIPT ) == CS_SUPERSCRIPT ) // don't change the order
        m_marked_up_text += "<sup>";
    else if( ( m_format_cur & CS_SUBSCRIPT ) == CS_SUBSCRIPT )
        m_marked_up_text += "<sub>";

    if( ( m_format_cur & CS_FADED ) == CS_FADED )
        m_marked_up_text += STR::compose(
                "<span color='", convert_gdkcolor_to_html( m_p2theme->color_pale ), "'>" );

    if( ( m_format_cur & CS_LINK ) == CS_LINK )
        m_marked_up_text += STR::compose(
                "<span color='", convert_gdkcolor_to_html( m_p2theme->color_link ),
                "' underline='single'>" );
}

// PARSING METHODS
void
EntryParserPango::process_paragraph( Paragraph* para )
{
    // SET PARAMS
    UstringSize         i         { 0 };
    const UstringSize   para_size { ( unsigned ) para->get_size() };

    m_format_prev = 0;
    m_alignment = para->get_pango_alignment();
    m_indent_level_cur = para->get_indent_level();

    // IMAGE RESERVATION
    if( m_img_reserv.h > 0 )
    {
        m_total_h_cur += m_img_reserv.h;
        m_img_reserv.w = m_img_reserv.h = m_img_reserv.x = 0; // clear the reservation
    }

    // FONT UPDATE
    if( para->is_quote() )
        m_font_cur = ( m_opt_use_theme_font ? m_p2entry->get_theme()->font_literary
                                            : Pango::FontDescription( "Serif 11") );
    else if( para->is_code() )
        m_font_cur = ( m_opt_use_theme_font ? m_p2entry->get_theme()->font_monospace
                                            : Pango::FontDescription( "Monospace 11") );
    else
        m_font_cur = ( m_opt_use_theme_font ? m_p2entry->get_theme()->font
                                            : m_font_main );
    update_line_heights();

    // HEADING TYPE
    switch( para->get_heading_level() )
    {
        case VT::PHS::TITLE::I:
            m_line_h_cur = m_line_h_TTL;
            apply_format( ( para->m_host->get_todo_status() & ES::CANCELED ) ?
                          CS_HEADING|CS_STRIKETHROUGH : CS_HEADING,
                          0, para->get_end_offset_in_host() );
            if( !m_opt_one_entry || m_p2entry->get_todo_status_effective() != ES::NOT_TODO )
            {
                add_image( m_p2entry->get_icon32()->scale_simple( m_line_h_TTL,
                                                                  m_line_h_TTL,
                                                                  Gdk::InterpType::BILINEAR ) );
            }
            break;
        case VT::PHS::LARGE::I:
            m_line_h_cur = m_line_h_HS1;
            m_total_h_cur += ( m_line_h_cur * 0.5 );
            apply_format( CS_SUBHEADING,
                          m_format_map_offset, m_format_map_offset + para->get_size() );
            break;
        case VT::PHS::MEDIUM::I:
            m_line_h_cur = m_line_h_HS2;
            m_total_h_cur += ( m_line_h_cur * 0.5 );
            apply_format( CS_SUBSUBHEADING,
                          m_format_map_offset, m_format_map_offset + para->get_size() );
            break;
        default:
            m_line_h_cur = m_line_h_N;
            break;
    }

    // LIST TYPE
    switch( para->get_list_type() )
    {
        case VT::PLS::BULLET::I:
            add_para_bullet( para->get_indent_level() % 2 );
            break;
        case VT::PLS::TODO::I:
            add_para_icon( Lifeograph::icons->todo_open_32 );
            break;
        case VT::PLS::PROGRS::I:
            add_para_icon( Lifeograph::icons->todo_progressed_32 );
            break;
        case VT::PLS::DONE::I:
            add_para_icon( Lifeograph::icons->todo_done_32 );
            break;
        case VT::PLS::CANCLD::I:
            add_para_icon( Lifeograph::icons->todo_canceled_32 );
            break;
        case VT::PLS::NUMBER::I:
        case VT::PLS::CLTTR::I:
        case VT::PLS::SLTTR::I:
            add_para_number( para );
            break;
    }

    // IMAGE TYPE
    const auto image_type { para->m_style & VT::PS_FLT_IMAGE };
    if( image_type )
    {
        // headers already have a greater margin:
        if( para->get_heading_level() == VT::PHS::NORMAL::I )
            m_total_h_cur += IMAGE_MARGIN;

        switch( image_type )
        {
            case VT::PS_IMAGE_FILE:
            {
                auto buf { para->get_image( m_page_w, m_font_main ) };
                if( buf )
                    add_image( buf );
                break;
            }
            case VT::PS_IMAGE_CHART:
                add_chart( para );
                break;
            case VT::PS_IMAGE_TABLE:
                add_table( para );
                break;
        }
    }

    if( para->has_hrule() )
        add_para_hrule( para->is_empty() );

    // HIDDEN FORMATS
    for( auto format : para->m_formats )
    {
        int formatting_tag{ 0 };
        switch( format->type )
        {
            case VT::HFT_TIME_MS:         formatting_tag = CS_SMALLER; break;
            case VT::FMT::DATE::I:
            case VT::HFT_LINK_ONTHEFLY:
            case VT::FMT::LINK_ID::I:
            case VT::FMT::LINK_URI::I:
            case VT::FMT::LINK_EVAL::I:
            //case VT::HFT_LINK_BROKEN:     formatting_tag = CS_LINK; break;
            case VT::FMT::BOLD::I:        formatting_tag = CS_BOLD; break;
            case VT::FMT::ITALIC::I:      formatting_tag = CS_ITALIC; break;
            case VT::FMT::UNDERLINE::I:   formatting_tag = CS_UNDERLINE; break;
            case VT::FMT::HIGHLIGHT::I:   formatting_tag = CS_HIGHLIGHT; break;
            case VT::FMT::STRIKETHRU::I:  formatting_tag = CS_STRIKETHROUGH; break;
            case VT::FMT::SUBSCRIPT::I:   formatting_tag = CS_SUBSCRIPT; break;
            case VT::FMT::SUPERSCRIPT::I: formatting_tag = CS_SUPERSCRIPT; break;
            case VT::FMT::COMMENT::I:     formatting_tag = CS_COMMENT; break;
            case VT::FMT::FADED::I:
            case VT::HFT_CODE_COMMENT:    formatting_tag = CS_FADED; break;
            //case VT::HFT_TAG_VALUE:       formatting_tag = ; break;
            case VT::FMT::TAG::I:         formatting_tag = CS_INLINE_TAG; break;
            case VT::HFT_CODE_KEYWORD:    formatting_tag = CS_BOLD; break;
            default:                    continue;
        }

        apply_format( formatting_tag,
                      m_format_map_offset + format->pos_bgn,
                      m_format_map_offset + format->pos_end );
    }

    // CHAR LOOP
    // <= is for handling the file end:
    for( ; i <= para_size; i++ )
    {
        m_format_cur = m_format_map[ i + m_format_map_offset ];

        if( m_format_cur & CS_SKIP )
            continue;

        if( m_format_cur != m_format_prev )
        {
            append_markups();

            if( ( m_format_cur & CF_FILTER ) == CF_TAG )
            {
                // TODO: 3.3: draw tag form around it (this is not so easy)
                // add_tag().....
                break;
            }
        } // end of if( format_cur != format_prev )

        // do not print the last char which is usually the \n
        if( i < para_size )
        {
            m_marked_up_text += Glib::Markup::escape_text(
                    m_source_text.substr( i + m_format_map_offset, 1 ) );
            m_format_prev = m_format_cur;
        }
    } // end of char loop
}

inline void
EntryParserPango::apply_format( int format, UstringSize begin, UstringSize end )
{
    for( auto i = begin; i < end; i++ )
        m_format_map[ i ] |= format;
}

inline Ustring
EntryParserPango::get_format_substr( UstringSize begin, int format )
{
    Ustring subtext;
    for( UstringSize size = 0; ( begin + size ) < m_format_map.size(); size++ )
        if( ( m_format_map[ begin + size ] & ( format|0xF ) ) != format )
            break;
        else
            subtext += m_source_text[ begin + size ];

    return subtext;
}
