/*
 * File Name: text_view.cpp
 */

/*
 * This file is part of uds-plugin-plaintext.
 *
 * uds-plugin-plaintext 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 2 of the License, or
 * (at your option) any later version.
 *
 * uds-plugin-plaintext 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 this program. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * Copyright (C) 2008 iRex Technologies B.V.
 * All rights reserved.
 */

#include "log.h"
#include "utils.h"
#include "font_cache.h"
#include "pango_renderer.h"

#include "text_config.h"
#include "text_view.h"
#include "text_tasks.h"

namespace text
{

TextView::TextView(TextModel *m)
: model(m),
  ctrl(0),
  font_family(DEFAULT_ASCII_FONT),
  font_size(DEFAULT_FONT_SIZE),
  display_width(DEFAULT_SURFACE_WIDTH),
  display_height(DEFAULT_SURFACE_HEIGHT),
  dpi(DEFAULT_DPI),
  color_depth(DEFAULT_COLOR_DEPTH),
  line_height(0),
  lines_per_page(0),
  left_margin(DEFAULT_LEFT_MARGIN),
  right_margin(DEFAULT_RIGHT_MARGIN),
  top_margin(DEFAULT_TOP_MARGIN),
  bottom_margin(DEFAULT_BOTTOM_MARGIN),
  client_width(display_width - left_margin - right_margin),
  client_height(display_height - top_margin - bottom_margin),
  pango_context(0),
  pango_mutex(0),
  font_hash_code(0),
  is_repagination_needed(false),
  blank_lines(0)
{
    // Special handling for Chinese font.
    const char *enc = model->get_encoding().c_str();
#ifdef WIN32
    if (_stricmp(enc, "GB2312") == 0 || _stricmp(enc, "GBK") == 0)
#else
    if (strcasecmp(enc, "GB2312") == 0 || strcasecmp(enc, "GBK") == 0)
#endif
    {
        font_family = DEFAULT_CHINESE_FONT;
    }
    initialize();
}

TextView::~TextView()
{
    deinitialize();
}

void TextView::initialize()
{
    // Create mutex for synchronous access to pango stuff.
    pango_mutex = g_mutex_new();

    // Initialize pango context.
    pango_context_init();

    calculate_lines_per_page();
}

void TextView::deinitialize()
{
    // Release pango stuff
    pango_context_final();
    g_mutex_free(pango_mutex);
}

void TextView::pango_context_init()
{
    // Create PangoFT2FontMap
    PangoFT2FontMap *font_map = PANGO_FT2_FONT_MAP(pango_ft2_font_map_new());

    // Set dpi information.
    pango_ft2_font_map_set_resolution(font_map, dpi, dpi);

    // Create pango context
    pango_context = pango_ft2_font_map_create_context(font_map);

    // Create pango font description
    PangoFontDescription* font_desc = pango_font_description_new();
    pango_font_description_set_family(font_desc, font_family.c_str());
    pango_font_description_set_size(font_desc, font_size * PANGO_SCALE);
    pango_context_set_font_description(pango_context, font_desc);

    char *desc_str = pango_font_description_to_string(font_desc);
    font_hash_code = make_hash_code(desc_str, 
        static_cast<unsigned int>(strlen(desc_str)));
    g_free(desc_str);

    // Since pango_context has copied the font description from us,
    // our font description can be freed safely.
    pango_font_description_free(font_desc);
}

void TextView::pango_context_final()
{
    // Release font caches in font map.
    PangoFT2FontMap* font_map = PANGO_FT2_FONT_MAP(pango_context_get_font_map(pango_context));
    pango_ft2_font_map_substitute_changed(font_map);

    // Release pango context, this will release font map and font description as well.
    if (pango_context != NULL)
    {
        g_object_unref(pango_context);
        pango_context = NULL;
    }
}

bool TextView::paginate(const Position& start_pos)
{

    Position in_pos(0, 0);
    Position out_pos;
    bool forward = true;
    
    size_t page_count = pages.size();
    if (page_count == 0)
    {
        in_pos = start_pos;
    }
    else if (pages.back().end != Position(0, 0))
    {
        // The forward pagination is not finished yet.
        in_pos = pages.back().end;
    }
    else if (pages.front().start != Position(0, 0))
    {
        // The backward pagination is not finished yet.
        in_pos = pages.front().start;
        forward = false;
    }
    else
    {
        // The pagination is complete.
        return true;
    }

    if (forward)
    {
        calculate_next_page_pos(in_pos, out_pos);
        pages.push_back(PageInfo(in_pos, out_pos));
    }
    else
    {
        calculate_prev_page_pos(in_pos, out_pos);
        pages.push_front(PageInfo(out_pos, in_pos));
    }
    return false;
}

bool TextView::get_anchor_by_page(unsigned int page_index, Position& pos)
{
    if (pages.size() == 0)
    {
        // Pagination hasn't been started yet.
        if (page_index == 0)
        {
            // We can render page 0 despite pagination is not ready.
            pos = Position(0, 0);
            return true;
        }
        else
        {
            return false;
        }
    }
    else
    {
        // Pagination starts, but maybe partial is successful.
        if (page_index >= pages.size())
        {
            return false;
        }
        else
        {
            pos = pages[page_index].start;
            return true;
        }
    }
}

PangoLayout* TextView::create_layout(const char *str, int len)
{
    PangoLayout *layout = pango_layout_new(pango_context);
    assert(layout != NULL);

    pango_layout_set_width(layout, client_width * PANGO_SCALE);
    pango_layout_set_spacing(layout, DEFAULT_LINE_SPACING * PANGO_SCALE);
    pango_layout_set_wrap(layout, PANGO_WRAP_WORD);
    pango_layout_set_justify(layout, TRUE);
    pango_layout_set_text(layout, str, len);
    pango_layout_set_single_paragraph_mode(layout, TRUE);
    return layout;
}

void TextView::calculate_lines_per_page()
{
    // Calculate lines per page
    const std::string *str = model->get_paragraph(0);
    unsigned int sample_len = MIN(32, static_cast<int>(str->size()));
    PangoLayout *layout = create_layout(str->c_str(), sample_len);

    PangoRectangle  rect;
    PangoLayoutIter *iter = pango_layout_get_iter(layout);
    PangoLayoutLine *pango_line = pango_layout_iter_get_line_readonly(iter);
    pango_layout_line_get_pixel_extents(pango_line, NULL, &rect);
    line_height = rect.height;
    lines_per_page = (client_height + DEFAULT_LINE_SPACING) / (line_height + DEFAULT_LINE_SPACING);
    pango_layout_iter_free(iter);

    // Release PangoLayout object
    g_object_unref(layout);
}

bool TextView::calculate_prev_page_pos(const Position& in_pos, Position& out_pos)
{
    // Check if we are already the first page.
    if (in_pos == Position(0, 0))
    {
        return false;
    }

    // Check if we can get next page anchor from page table.
    size_t page_count = pages.size();
    for (unsigned int i=0; i<page_count; i++)
    {
        if (pages[i].end == in_pos)
        {
            out_pos = pages[i].start;
            return true;
        }
    }

    g_mutex_lock(pango_mutex);
    unsigned int start_paragraph = in_pos.paragraph;
    unsigned int start_offset    = in_pos.offset;
    PangoLayout *layout = NULL;
    if (start_offset == 0)
    {
        start_paragraph--;
    }

    unsigned int total_lines = 0;
    while (total_lines < lines_per_page)
    {
        if (static_cast<int>(start_paragraph) < 0)
        {
            if (in_pos > Position(0, 0))
            {
                blank_lines = lines_per_page - total_lines;
            }
            break;
        }

        if (layout != NULL)
        {
            g_object_unref(layout);
            layout = NULL;
        }
        const std::string *str = model->get_paragraph(start_paragraph);
        layout = create_layout(str->c_str(), static_cast<int>(str->size()));

        int line_count = pango_layout_get_line_count(layout);
        if (start_offset != 0)
        {
            // We are calculating from the second half part of a paragraph
            int line_offset, x_pos;
            pango_layout_index_to_line_x(layout, start_offset, FALSE, &line_offset, &x_pos);
            line_count = line_offset;
        }

        if (total_lines + line_count < lines_per_page)
        {
            // We have enough space to load more paragraphs for current page
            total_lines += line_count;
            start_paragraph--;
            start_offset = 0;
        }
        else if (total_lines + line_count == lines_per_page)
        {
            out_pos.paragraph = start_paragraph;
            out_pos.offset = 0;
            break;
        }
        else
        {
            // The remained space is not enough for current paragraph
            PangoLayoutIter *iter = pango_layout_get_iter(layout);
            unsigned int line_index = line_count - (lines_per_page - total_lines);
            for (unsigned int i=0; i<line_index; i++)
            {
                pango_layout_iter_next_line(iter);
            }

            out_pos.paragraph = start_paragraph;
            out_pos.offset = pango_layout_iter_get_index(iter);
            pango_layout_iter_free(iter);
            break;
        }
    }

    if (layout != NULL)
    {
        g_object_unref(layout);
        layout = NULL;
    }
    g_mutex_unlock(pango_mutex);
    return (static_cast<int>(start_paragraph) >= 0);
}

bool TextView::calculate_next_page_pos(const Position& in_pos, Position& out_pos)
{
    /// Check if we can get next page anchor from page table.
    size_t page_count = pages.size();
    for (unsigned int i=0; i<page_count; i++)
    {
        if (pages[i].start == in_pos)
        {
            out_pos = pages[i].end;
            return out_pos != Position(0, 0);
        }
    }

    unsigned int start_paragraph = in_pos.paragraph;
    unsigned int start_offset    = in_pos.offset;
    unsigned int total_lines = 0;

    g_mutex_lock(pango_mutex);
    PangoLayout *layout = NULL;
    while (total_lines < lines_per_page)
    {
        if (start_paragraph >= model->get_paragraph_count())
        {
            break;
        }

        if (layout != NULL)
        {
            g_object_unref(layout);
            layout = NULL;
        }
        const std::string *str = model->get_paragraph(start_paragraph);
        layout = create_layout(str->c_str(), static_cast<int>(str->size()));

        int line_count = pango_layout_get_line_count(layout);
        int line_offset = 0;
        if (start_offset != 0)
        {
            // We are calculating from the second half part of a paragraph
            int x_pos;
            pango_layout_index_to_line_x(layout, start_offset, FALSE, &line_offset, &x_pos);
            line_count -= line_offset;
        }

        if (total_lines + line_count < lines_per_page)
        {
            // We have enough space to load more paragraphs for current page
            total_lines += line_count;
            start_paragraph++;
            start_offset = 0;
        }
        else if (total_lines + line_count == lines_per_page)
        {
            if (++start_paragraph < model->get_paragraph_count())
            {
                out_pos.paragraph = start_paragraph;
                out_pos.offset = 0;
            }
            break;
        }
        else
        {
            // The remained space is not enough for current paragraph
            PangoLayoutIter *iter = pango_layout_get_iter(layout);
            for (unsigned int i=0; i<lines_per_page-total_lines+line_offset; i++)
            {
                pango_layout_iter_next_line(iter);
            }

            out_pos.offset = pango_layout_iter_get_index(iter);
            out_pos.paragraph = start_paragraph;
            pango_layout_iter_free(iter);
            break;
        }
    }

    if (layout != NULL)
    {
        g_object_unref(layout);
        layout = NULL;
    }
    g_mutex_unlock(pango_mutex);
    return (start_paragraph < model->get_paragraph_count());
}

void TextView::check_page_table(const Position& rendering_pos)
{
    size_t page_count = pages.size();
    for (unsigned int i=0; i<page_count; i++)
    {
        if (pages[i].start == rendering_pos)
        {
            return;
        }
    }

    // Can't find page boundaries for specified anchor.
    // Re-pagination is needed.
    is_repagination_needed = true;
}

unsigned int TextView::get_page_index_by_anchor(const Position& anchor)
{
    /// Check if we can get next page anchor from page table.
    size_t page_count = pages.size();

    // Always return 0 if pagination is not complete.
    if (page_count == 0 || pages[page_count-1].end != Position(0, 0))
    {
        return 0;
    }

    for (unsigned int i=0; i<page_count; i++)
    {
        if (pages[i].end > anchor && pages[i].start <= anchor)
        {
            return i;
        }
    }

    return static_cast<unsigned int>(page_count-1);
}

Position TextView::get_page_anchor_by_anchor(const Position& anchor)
{
    /// Check if we can get next page anchor from page table.
    size_t page_count = pages.size();
    for (unsigned int i=0; i<page_count; i++)
    {
        if (pages[i].end > anchor && pages[i].start <= anchor)
        {
            return pages[i].start;
        }
    }

    if (page_count>0 && pages[page_count-1].end == Position(0, 0))
    {
        return pages[page_count-1].start;
    }

    return Position(anchor.paragraph, 0);
}

unsigned int TextView::get_current_page_index()
{
    return get_page_index_by_anchor(rendering_pos);
}

Position TextView::render(unsigned char *bmp, const Position& start_pos)
{
    if (is_repagination_needed)
    {
        pages.clear();
        blank_lines = 0;
        ctrl->paginate(start_pos, true /* Send start notification */);
        is_repagination_needed = false;
    }

    g_mutex_lock(pango_mutex);
    unsigned int start_paragraph = start_pos.paragraph;
    unsigned int start_offset    = start_pos.offset;
    PangoLayout *layout = NULL;

    rendering_pos = start_pos;

    unsigned int total_lines = 0;
    if (start_pos == Position(0, 0))
    {
        total_lines = blank_lines;
    }
    int y_offset = (line_height + DEFAULT_LINE_SPACING)*total_lines*PANGO_SCALE;

    // If we don't start with a new paragraph, then we must render the
    // remained part of current paragraph first.
    if (start_offset != 0)
    {
        if (layout != NULL)
        {
            g_object_unref(layout);
            layout = NULL;
        }
        const std::string *str = model->get_paragraph(start_paragraph);
        layout = create_layout(str->c_str(), static_cast<int>(str->size()));

        PangoLayoutIter *iter = pango_layout_get_iter(layout);

        int line_offset, x_pos;
        pango_layout_index_to_line_x(layout, start_offset, FALSE, &line_offset, &x_pos);
        y_offset = (line_height + DEFAULT_LINE_SPACING)*line_offset*PANGO_SCALE;

        int baseline = 0;
        for (int i=0; i<line_offset; i++)
        {
            pango_layout_iter_next_line(iter);
        }

        do
        {
            baseline = pango_layout_iter_get_baseline(iter) - y_offset;
            PangoLayoutLine *pango_line = pango_layout_iter_get_line_readonly(iter);
            render_single_line(bmp,
                               pango_line,
                               font_hash_code,
                               left_margin * PANGO_SCALE,
                               baseline+top_margin*PANGO_SCALE,
                               (display_width+3)/4*4);
            if (++total_lines == lines_per_page)
            {
                // Calculate the offset of the rightmost character.
                int index, trailing;
                pango_layout_line_x_to_index(pango_line, client_width - 1, &index, &trailing);
                start_offset = index;
                pango_layout_iter_free(iter);
                goto RenderEnd;
            }
        } while (pango_layout_iter_next_line(iter));
        
        // Release iter since we finish with it.
        pango_layout_iter_free(iter);

        start_paragraph++;
        y_offset = (line_height + DEFAULT_LINE_SPACING)*total_lines*PANGO_SCALE;
    }

    // Render the remained paragraphs
    while (start_paragraph < model->get_paragraph_count())
    {
        // Render current paragraph
        if (layout != NULL)
        {
            g_object_unref(layout);
            layout = NULL;
        }
        const std::string *str = model->get_paragraph(start_paragraph);
        layout = create_layout(str->c_str(), static_cast<int>(str->size()));

        PangoLayoutIter *iter = pango_layout_get_iter(layout);
        int baseline = 0;
        do 
        {
            baseline = pango_layout_iter_get_baseline(iter) + y_offset;
            PangoLayoutLine *pango_line = pango_layout_iter_get_line_readonly(iter);
            render_single_line(bmp, 
                               pango_line,
                               font_hash_code,
                               left_margin * PANGO_SCALE,
                               baseline+top_margin*PANGO_SCALE,
                               (display_width+3)/4*4);
            if (++total_lines == lines_per_page)
            {
                int index, trailing;
                pango_layout_line_x_to_index(pango_line, client_width - 1, &index, &trailing);
                start_offset = index;
                pango_layout_iter_free(iter);
                goto RenderEnd;
            }
        } while (pango_layout_iter_next_line(iter));
        pango_layout_iter_free(iter);

        start_paragraph++;
        y_offset = (line_height + DEFAULT_LINE_SPACING)*total_lines*PANGO_SCALE;
    }

RenderEnd:
    // Release pango layout generated by render.
    if (layout != NULL)
    {
        g_object_unref(layout);
        layout = NULL;
    }
    g_mutex_unlock(pango_mutex);

    return Position(start_paragraph, start_offset);
}

bool TextView::set_font_size(int font_size)
{
    if (this->font_size == font_size)
    {
        return true;
    }

    this->font_size = font_size;

    g_mutex_lock(pango_mutex);

    // Release font caches in font map.
    PangoFT2FontMap* font_map = PANGO_FT2_FONT_MAP(pango_context_get_font_map(pango_context));
    pango_ft2_font_map_substitute_changed(font_map);

    // Create new pango font description and copy it to pango context.
    PangoFontDescription* font_desc = pango_font_description_new();
    pango_font_description_set_family(font_desc, font_family.c_str());
    pango_font_description_set_size(font_desc, font_size * PANGO_SCALE);
    pango_context_set_font_description(pango_context, font_desc);

    char *desc_str = pango_font_description_to_string(font_desc);
    font_hash_code = make_hash_code(desc_str, 
        static_cast<unsigned int>(strlen(desc_str)));
    g_free(desc_str);

    // Since pango_context has copied the font description from us,
    // our font description can be freed safely.
    pango_font_description_free(font_desc);
    calculate_lines_per_page();

    // Change the repagination flag to true.
    is_repagination_needed = true;

    // Clear font cache.
    FontCache& font_cache = FontCache::instance();
    font_cache.clear();

    g_mutex_unlock(pango_mutex);
    return true;
}

void TextView::set_display_size(unsigned int width, unsigned int height)
{
    LOGPRINTF("\n\nwidth=%d, height=%d\n\n", width, height);
    if (width == display_width && height == display_height)
    {
        return;
    }

	g_mutex_lock(pango_mutex);
    if (height != display_height)
    {
        // Display height changed. The current layout can be reused.
        // But we need to re-calculate lines per page.
        display_height = height;
        client_height = display_height - top_margin - bottom_margin;
        calculate_lines_per_page();
    }

    if (width != display_width)
    {
        display_width = width;
        client_width = display_width - left_margin - right_margin;
    }

    // Change the repagination flag to true.
    is_repagination_needed = true;
    g_mutex_unlock(pango_mutex);
}

bool TextView::map_doc_pos_to_view_pos(const Position& page_anchor,
                                       const Position& doc_pos,
                                       ViewPosition&   view_pos,
                                       bool            trailing)
{
    if (doc_pos < page_anchor)
    {
        return false;
    }

    unsigned int line_height_with_spacing = line_height + DEFAULT_LINE_SPACING;

    // Calculate the line offset and x offset inside layout corresponding to doc pos.
    const std::string *str = model->get_paragraph(doc_pos.paragraph);
    PangoLayout *layout = create_layout(str->c_str(), static_cast<int>(str->size()));

    int line_offset_pos, x_offset_pos;
    pango_layout_index_to_line_x(layout, doc_pos.offset, trailing, &line_offset_pos, &x_offset_pos);

    unsigned int total_lines = 0;
    unsigned int start_paragraph = page_anchor.paragraph;
    unsigned int start_offset    = page_anchor.offset;
    if (start_paragraph == 0 && start_offset == 0)
    {
        total_lines += blank_lines;
    }

    if (start_offset != 0)
    {
        // The page does not start with a new paragraph.
        if (layout != NULL)
        {
            g_object_unref(layout);
            layout = NULL;
        }
        const std::string *str = model->get_paragraph(start_paragraph);
        layout = create_layout(str->c_str(), static_cast<int>(str->size()));

        int line_offset_start, x_pos;
        pango_layout_index_to_line_x(layout, start_offset, FALSE, &line_offset_start, &x_pos);

        if (doc_pos.paragraph == start_paragraph)
        {
            total_lines = line_offset_pos - line_offset_start;
            goto MapEnd;
        }
        else
        {
            total_lines = pango_layout_get_line_count(layout) - line_offset_start;
            start_paragraph++;
        }
    }

    for (; start_paragraph != doc_pos.paragraph; start_paragraph++)
    {
        if (layout != NULL)
        {
            g_object_unref(layout);
            layout = NULL;
        }
        const std::string *str = model->get_paragraph(start_paragraph);
        layout = create_layout(str->c_str(), static_cast<int>(str->size()));

        total_lines += pango_layout_get_line_count(layout);
    }

    total_lines += line_offset_pos;

MapEnd:
    if (layout != NULL)
    {
        g_object_unref(layout);
        layout = NULL;
    }
    if (total_lines < lines_per_page)
    {
        view_pos.x = PANGO_PIXELS(x_offset_pos) + left_margin;
        view_pos.y = top_margin + line_height_with_spacing * total_lines;
        return true;
    }
    else
    {
        return false;
    }
}

bool TextView::map_view_pos_to_doc_pos(const Position&     page_anchor,
                                       const ViewPosition& view_pos,
                                       Position&           doc_pos)
{
    int x_offset_pos = view_pos.x - left_margin;
    int y_offset_pos = view_pos.y - top_margin;

    unsigned int start_paragraph = page_anchor.paragraph;
    unsigned int start_offset    = page_anchor.offset;
    unsigned int line_height_with_spacing = line_height + DEFAULT_LINE_SPACING;

    if (start_paragraph == 0 && start_offset == 0)
    {
        y_offset_pos -= blank_lines * line_height_with_spacing;
    }

    PangoLayout *layout = NULL;
    bool found = false;

    if (start_offset != 0)
    {
        const std::string *str = model->get_paragraph(start_paragraph);
        layout = create_layout(str->c_str(), static_cast<int>(str->size()));

        int line_offset_start, x_pos;
        pango_layout_index_to_line_x(layout, start_offset, FALSE, &line_offset_start, &x_pos);

        int lines_left = pango_layout_get_line_count(layout) - line_offset_start;
        if (static_cast<int>(lines_left * line_height_with_spacing) > y_offset_pos)
        {
            // The corresponding doc pos is inside this paragraph.
            doc_pos.paragraph = start_paragraph;

            // Calculate offset.
            int bytes_index, trailing;
            pango_layout_xy_to_index(layout, 
                                     x_offset_pos * PANGO_SCALE,
                                     (line_offset_start*line_height_with_spacing+y_offset_pos) * PANGO_SCALE,
                                     &bytes_index, &trailing);
            doc_pos.offset = bytes_index;
            found = true;
            goto MapEnd;
        }
        else
        {
            y_offset_pos -= lines_left * line_height_with_spacing;
            start_paragraph++;
        }
    }

    for (; start_paragraph < model->get_paragraph_count(); start_paragraph++)
    {
        if (layout != NULL)
        {
            g_object_unref(layout);
            layout = NULL;
        }
        const std::string *str = model->get_paragraph(start_paragraph);
        layout = create_layout(str->c_str(), static_cast<int>(str->size()));

        int line_count = pango_layout_get_line_count(layout);
        if (static_cast<int>(line_count * line_height_with_spacing) > y_offset_pos)
        {
            break;
        }
        else
        {
            y_offset_pos -= line_count * line_height_with_spacing;
        }
    }

    if (start_paragraph == model->get_paragraph_count())
    {
        goto MapEnd;
    }

    int bytes_index, trailing;
    pango_layout_xy_to_index(layout, x_offset_pos * PANGO_SCALE, y_offset_pos * PANGO_SCALE, &bytes_index, &trailing);
    doc_pos.paragraph = start_paragraph;
    doc_pos.offset    = bytes_index;
    found = true;

MapEnd:
    if (layout)
    {
        g_object_unref(layout);
    }
    return found;
}

bool TextView::get_bounding_rectangles(const Position& page_anchor,
                                       std::vector<Rect>& bounding_rect,
                                       const Range& range)
{
    // A flag indicating that the start position and end position of specified
    // range is in different pages.
    bool span_pages = false;
    ViewPosition view_pos_start, view_pos_end;
      
    if (!map_doc_pos_to_view_pos(page_anchor, range.start, view_pos_start, false))
    {
        // The range's start position is not inside the page which starts with
        // page_anchor.
        return false;
    }

    if (!map_doc_pos_to_view_pos(page_anchor, range.end, view_pos_end, true))
    {
        // The range's end position is not inside the page which starts with
        // page_anchor.
        span_pages = true;
    }

    unsigned int line_height_with_spacing = line_height + DEFAULT_LINE_SPACING;

    // Check if thhe start position and end position are in the same line.
    if (view_pos_start.y == view_pos_end.y)
    {
        // The start position and end position are in the same line.
        bounding_rect.push_back(Rect(view_pos_start.x,
                                     view_pos_start.y + DEFAULT_LINE_SPACING,
                                     view_pos_end.x - view_pos_start.x,
                                     line_height));
    }
    else
    {
        // More lines.
        bounding_rect.push_back(Rect(view_pos_start.x,
                                     view_pos_start.y + DEFAULT_LINE_SPACING,
                                     display_width - view_pos_start.x - right_margin,
                                     line_height));

        for (unsigned int y_offset = view_pos_start.y+line_height_with_spacing;
             static_cast<int>(y_offset) < view_pos_end.y &&
             y_offset < top_margin + line_height_with_spacing * lines_per_page;
             y_offset += line_height_with_spacing)
        {
            bounding_rect.push_back(Rect(left_margin,
                                         y_offset + DEFAULT_LINE_SPACING,
                                         client_width,
                                         line_height));
        }

        if (!span_pages)
        {
            bounding_rect.push_back(Rect(left_margin,
                                         view_pos_end.y + DEFAULT_LINE_SPACING,
                                         view_pos_end.x - left_margin,
                                         line_height));
        }
    }

    return true;
}

}
