diff options
Diffstat (limited to 'libs/litehtml/src/line_box.cpp')
-rw-r--r-- | libs/litehtml/src/line_box.cpp | 717 |
1 files changed, 717 insertions, 0 deletions
diff --git a/libs/litehtml/src/line_box.cpp b/libs/litehtml/src/line_box.cpp new file mode 100644 index 0000000000..e8c5034633 --- /dev/null +++ b/libs/litehtml/src/line_box.cpp @@ -0,0 +1,717 @@ +#include "html.h" +#include "line_box.h" +#include "element.h" +#include "render_item.h" +#include <algorithm> + +////////////////////////////////////////////////////////////////////////////////////////// + +void litehtml::line_box_item::place_to(int x, int y) +{ + m_element->pos().x = x + m_element->content_offset_left(); + m_element->pos().y = y + m_element->content_offset_top(); +} + +litehtml::position& litehtml::line_box_item::pos() +{ + return m_element->pos(); +} + + +int litehtml::line_box_item::width() const +{ + return m_element->width(); +} + +int litehtml::line_box_item::top() const +{ + return m_element->top(); +} + +int litehtml::line_box_item::bottom() const +{ + return m_element->bottom(); +} + +int litehtml::line_box_item::right() const +{ + return m_element->right(); +} + +int litehtml::line_box_item::left() const +{ + return m_element->left(); +} + +////////////////////////////////////////////////////////////////////////////////////////// + +litehtml::lbi_start::lbi_start(const std::shared_ptr<render_item>& element) : line_box_item(element) +{ + m_pos.height = m_element->src_el()->css().get_font_metrics().height; + m_pos.width = m_element->content_offset_left(); +} + +void litehtml::lbi_start::place_to(int x, int y) +{ + m_pos.x = x + m_element->content_offset_left(); + m_pos.y = y; +} + +int litehtml::lbi_start::width() const +{ + return m_pos.width; +} + +int litehtml::lbi_start::top() const +{ + return m_pos.y; +} + +int litehtml::lbi_start::bottom() const +{ + return m_pos.y + m_pos.height; +} + +int litehtml::lbi_start::right() const +{ + return m_pos.x; +} + +int litehtml::lbi_start::left() const +{ + return m_pos.x - m_element->content_offset_left(); +} + +////////////////////////////////////////////////////////////////////////////////////////// + +litehtml::lbi_end::lbi_end(const std::shared_ptr<render_item>& element) : lbi_start(element) +{ + m_pos.height = m_element->src_el()->css().get_font_metrics().height; + m_pos.width = m_element->content_offset_right(); +} + +void litehtml::lbi_end::place_to(int x, int y) +{ + m_pos.x = x; + m_pos.y = y; +} + +int litehtml::lbi_end::right() const +{ + return m_pos.x + m_pos.width; +} + +int litehtml::lbi_end::left() const +{ + return m_pos.x; +} + +////////////////////////////////////////////////////////////////////////////////////////// + +litehtml::lbi_continue::lbi_continue(const std::shared_ptr<render_item>& element) : lbi_start(element) +{ + m_pos.height = m_element->src_el()->css().get_font_metrics().height; + m_pos.width = 0; +} + +void litehtml::lbi_continue::place_to(int x, int y) +{ + m_pos.x = x; + m_pos.y = y; +} + +int litehtml::lbi_continue::right() const +{ + return m_pos.x; +} + +int litehtml::lbi_continue::left() const +{ + return m_pos.x; +} + +int litehtml::lbi_continue::width() const +{ + return 0; +} + +////////////////////////////////////////////////////////////////////////////////////////// + +void litehtml::line_box::add_item(std::unique_ptr<line_box_item> item) +{ + item->get_el()->skip(false); + bool add = true; + switch (item->get_type()) + { + case line_box_item::type_text_part: + if(item->get_el()->src_el()->is_white_space()) + { + add = !is_empty() && !have_last_space(); + } + break; + case line_box_item::type_inline_start: + case line_box_item::type_inline_end: + case line_box_item::type_inline_continue: + add = true; + break; + } + if(add) + { + item->place_to(m_left + m_width, m_top); + m_width += item->width(); + m_height = std::max(m_height, item->get_el()->height()); + m_items.emplace_back(std::move(item)); + } else + { + item->get_el()->skip(true); + } +} + +int litehtml::line_box::calc_va_baseline(const va_context& current, vertical_align va, const font_metrics& new_font, int top, int bottom) +{ + switch(va) + { + case va_super: + return current.baseline - current.fm.height / 3; + case va_sub: + return current.baseline + current.fm.height / 3; + case va_middle: + return current.baseline - current.fm.x_height / 2; + case va_text_top: + return current.baseline - (current.fm.height - current.fm.base_line()) + + new_font.height - new_font.base_line(); + case va_text_bottom: + return current.baseline + current.fm.base_line() - new_font.base_line(); + case va_top: + return top + new_font.height - new_font.base_line(); + case va_bottom: + return bottom - new_font.height + new_font.base_line(); + default: + return current.baseline; + } +} + +std::list< std::unique_ptr<litehtml::line_box_item> > litehtml::line_box::finish(bool last_box, const containing_block_context &containing_block_size) +{ + std::list< std::unique_ptr<line_box_item> > ret_items; + + if(!last_box) + { + while(!m_items.empty()) + { + if (m_items.back()->get_type() == line_box_item::type_text_part) + { + // remove trailing spaces + if (m_items.back()->get_el()->src_el()->is_break() || + m_items.back()->get_el()->src_el()->is_white_space()) + { + m_width -= m_items.back()->width(); + m_items.back()->get_el()->skip(true); + m_items.pop_back(); + } else + { + break; + } + } else if (m_items.back()->get_type() == line_box_item::type_inline_start) + { + // remove trailing empty inline_start markers + // these markers will be added at the beginning of the next line box + m_width -= m_items.back()->width(); + ret_items.emplace_back(std::move(m_items.back())); + m_items.pop_back(); + } else + { + break; + } + } + } else + { + // remove trailing spaces + auto iter = m_items.rbegin(); + while(iter != m_items.rend()) + { + if ((*iter)->get_type() == line_box_item::type_text_part) + { + if((*iter)->get_el()->src_el()->is_white_space()) + { + (*iter)->get_el()->skip(true); + m_width -= (*iter)->width(); + // Space can be between text and inline_end marker + // We have to shift all items on the right side + if(iter != m_items.rbegin()) + { + auto r_iter = iter; + r_iter--; + while (true) + { + (*r_iter)->pos().x -= (*iter)->width(); + if (r_iter == m_items.rbegin()) + { + break; + } + r_iter--; + } + } + // erase white space element + iter = decltype(iter) (m_items.erase( std::next(iter).base() )); + } else + { + break; + } + } else + { + iter++; + } + } + } + + if( is_empty() || (!is_empty() && last_box && is_break_only()) ) + { + m_height = m_default_line_height; + m_baseline = m_font_metrics.base_line(); + return ret_items; + } + + int spc_x = 0; + + int add_x = 0; + switch(m_text_align) + { + case text_align_right: + if(m_width < (m_right - m_left)) + { + add_x = (m_right - m_left) - m_width; + } + break; + case text_align_center: + if(m_width < (m_right - m_left)) + { + add_x = ((m_right - m_left) - m_width) / 2; + } + break; + case text_align_justify: + if (m_width < (m_right - m_left)) + { + add_x = 0; + spc_x = (m_right - m_left) - m_width; + if (spc_x > m_width/4) + spc_x = 0; + } + break; + default: + add_x = 0; + } + + int counter = 0; + float offj = float(spc_x) / std::max(1.f, float(m_items.size())-1.f); + float cixx = 0.0f; + + int line_top = 0; + int line_bottom = 0; + + va_context current_context; + std::list<va_context> contexts; + + current_context.baseline = 0; + current_context.fm = m_font_metrics; + + m_min_width = 0; + + for (const auto& lbi : m_items) + { + m_min_width += lbi->get_rendered_min_width(); + { // start text_align_justify + if (spc_x && counter) + { + cixx += offj; + if ((counter + 1) == int(m_items.size())) + cixx += 0.99f; + lbi->pos().x += int(cixx); + } + counter++; + if ((m_text_align == text_align_right || spc_x) && counter == int(m_items.size())) + { + // Forcible justify the last element to the right side for text align right and justify; + lbi->pos().x = m_right - lbi->pos().width; + } else if (add_x) + { + lbi->pos().x += add_x; + } + } // end text_align_justify + + if (lbi->get_type() == line_box_item::type_inline_start || lbi->get_type() == line_box_item::type_inline_continue) + { + contexts.push_back(current_context); + current_context.baseline = calc_va_baseline(current_context, + lbi->get_el()->css().get_vertical_align(), + lbi->get_el()->css().get_font_metrics(), + line_top, line_bottom); + current_context.fm = lbi->get_el()->css().get_font_metrics(); + } + + // Align elements vertically by baseline. + if(lbi->get_el()->src_el()->css().get_display() == display_inline_text || lbi->get_el()->src_el()->css().get_display() == display_inline) + { + // inline elements and text are aligned by baseline only + // at this point the baseline for text is properly aligned already + lbi->pos().y = current_context.baseline - lbi->get_el()->css().get_font_metrics().height + lbi->get_el()->css().get_font_metrics().base_line(); + } else + { + switch(lbi->get_el()->css().get_vertical_align()) + { + case va_sub: + case va_super: + { + int bl = calc_va_baseline(current_context, lbi->get_el()->css().get_vertical_align(), current_context.fm, line_top, line_bottom); + lbi->pos().y = bl - lbi->get_el()->get_last_baseline() + + lbi->get_el()->content_offset_top(); + } + break; + case va_bottom: + lbi->pos().y = line_bottom - lbi->get_el()->height() + lbi->get_el()->content_offset_top(); + break; + case va_top: + lbi->pos().y = line_top + lbi->get_el()->content_offset_top(); + break; + case va_baseline: + lbi->pos().y = current_context.baseline - lbi->get_el()->get_last_baseline() + + lbi->get_el()->content_offset_top(); + break; + case va_text_top: + lbi->pos().y = current_context.baseline - current_context.fm.height + current_context.fm.base_line() + + lbi->get_el()->content_offset_top(); + break; + case va_text_bottom: + lbi->pos().y = current_context.baseline + current_context.fm.base_line() - lbi->get_el()->height() + + lbi->get_el()->content_offset_top(); + break; + case va_middle: + lbi->pos().y = current_context.baseline - current_context.fm.x_height / 2 - lbi->get_el()->height() / 2 + + lbi->get_el()->content_offset_top(); + break; + } + } + + if (lbi->get_type() == line_box_item::type_inline_end) + { + if(!contexts.empty()) + { + current_context = contexts.back(); + contexts.pop_back(); + } + } + + // calculate line height + line_top = std::min(line_top, lbi->top()); + line_bottom = std::max(line_bottom, lbi->bottom()); + + if(lbi->get_el()->src_el()->css().get_display() == display_inline_text) + { + m_line_height = std::max(m_line_height, lbi->get_el()->css().get_line_height()); + } + } + + m_height = line_bottom - line_top; + int top_shift = line_top; + if(m_height < m_line_height) + { + top_shift -= (m_line_height - m_height) / 2; + m_height = m_line_height; + } + m_baseline = line_bottom; + + struct inline_item_box + { + std::shared_ptr<render_item> element; + position box; + + inline_item_box() = default; + explicit inline_item_box(const std::shared_ptr<render_item>& el) : element(el) {} + }; + + std::list<inline_item_box> inlines; + + contexts.clear(); + + current_context.baseline = 0; + current_context.fm = m_font_metrics; + bool va_top_bottom = false; + + for (const auto& lbi : m_items) + { + // Calculate baseline. Now we calculate baseline for vertical alignment top and bottom + if (lbi->get_type() == line_box_item::type_inline_start || lbi->get_type() == line_box_item::type_inline_continue) + { + contexts.push_back(current_context); + va_top_bottom = lbi->get_el()->css().get_vertical_align() == va_bottom || lbi->get_el()->css().get_vertical_align() == va_top; + current_context.baseline = calc_va_baseline(current_context, + lbi->get_el()->css().get_vertical_align(), + lbi->get_el()->css().get_font_metrics(), + top_shift, top_shift + m_height); + current_context.fm = lbi->get_el()->css().get_font_metrics(); + } + + // Align inlines and text by baseline if current vertical alignment is top or bottom + if(va_top_bottom) + { + if (lbi->get_el()->src_el()->css().get_display() == display_inline_text || + lbi->get_el()->src_el()->css().get_display() == display_inline) + { + // inline elements and text are aligned by baseline only + // at this point the baseline for text is properly aligned already + lbi->pos().y = current_context.baseline - lbi->get_el()->css().get_font_metrics().height + + lbi->get_el()->css().get_font_metrics().base_line(); + } + } + + // Pop the prev context + if (lbi->get_type() == line_box_item::type_inline_end) + { + if(!contexts.empty()) + { + current_context = contexts.back(); + contexts.pop_back(); + } + } + + // move element to the correct position + lbi->pos().y += m_top - top_shift; + + // Perform vertical align top and bottom for inline boxes + if(lbi->get_el()->css().get_display() != display_inline_text && lbi->get_el()->css().get_display() != display_inline) + { + if(lbi->get_el()->css().get_vertical_align() == va_top) + { + lbi->pos().y = m_top + lbi->get_el()->content_offset_top(); + } else if(lbi->get_el()->css().get_vertical_align() == va_bottom) + { + lbi->pos().y = m_top + m_height - lbi->get_el()->height() + lbi->get_el()->content_offset_top(); + } + } + lbi->get_el()->apply_relative_shift(containing_block_size); + + // Calculate and push inline box into the render item element + if(lbi->get_type() == line_box_item::type_inline_start || lbi->get_type() == line_box_item::type_inline_continue) + { + if(lbi->get_type() == line_box_item::type_inline_start) + { + lbi->get_el()->clear_inline_boxes(); + } + inlines.emplace_back(lbi->get_el()); + inlines.back().box.x = lbi->left(); + inlines.back().box.y = lbi->top() - lbi->get_el()->content_offset_top(); + inlines.back().box.height = lbi->bottom() - lbi->top() + lbi->get_el()->content_offset_height(); + } else if(lbi->get_type() == line_box_item::type_inline_end) + { + if(!inlines.empty()) + { + inlines.back().box.width = lbi->right() - inlines.back().box.x; + inlines.back().element->add_inline_box(inlines.back().box); + inlines.pop_back(); + } + } + } + + for(auto iter = inlines.rbegin(); iter != inlines.rend(); ++iter) + { + iter->box.width = m_items.back()->right() - iter->box.x; + iter->element->add_inline_box(iter->box); + + ret_items.emplace_front(std::unique_ptr<line_box_item>(new lbi_continue(iter->element))); + } + + return ret_items; +} + +std::shared_ptr<litehtml::render_item> litehtml::line_box::get_first_text_part() const +{ + for(const auto & item : m_items) + { + if(item->get_type() == line_box_item::type_text_part) + { + return item->get_el(); + } + } + return nullptr; +} + + +std::shared_ptr<litehtml::render_item> litehtml::line_box::get_last_text_part() const +{ + for(auto iter = m_items.rbegin(); iter != m_items.rend(); iter++) + { + if((*iter)->get_type() == line_box_item::type_text_part) + { + return (*iter)->get_el(); + } + } + return nullptr; +} + + +bool litehtml::line_box::can_hold(const std::unique_ptr<line_box_item>& item, white_space ws) const +{ + if(!item->get_el()->src_el()->is_inline()) return false; + + if(item->get_type() == line_box_item::type_text_part) + { + // force new line on floats clearing + if (item->get_el()->src_el()->is_break() && item->get_el()->src_el()->css().get_clear() != clear_none) + { + return false; + } + + auto last_el = get_last_text_part(); + + // the first word is always can be hold + if(!last_el) + { + return true; + } + + // force new line if the last placed element was line break + // Skip If there are the only break item - this is float clearing + if (last_el && last_el->src_el()->is_break() && m_items.size() > 1) + { + return false; + } + + // line break should stay in current line box + if (item->get_el()->src_el()->is_break()) + { + return true; + } + + if (ws == white_space_nowrap || ws == white_space_pre || + (ws == white_space_pre_wrap && item->get_el()->src_el()->is_space())) + { + return true; + } + + if (m_left + m_width + item->width() > m_right) + { + return false; + } + } + + return true; +} + +bool litehtml::line_box::have_last_space() const +{ + auto last_el = get_last_text_part(); + if(last_el) + { + return last_el->src_el()->is_white_space() || last_el->src_el()->is_break(); + } + return false; +} + +bool litehtml::line_box::is_empty() const +{ + if(m_items.empty()) return true; + if(m_items.size() == 1 && + m_items.front()->get_el()->src_el()->is_break() && + m_items.front()->get_el()->src_el()->css().get_clear() != clear_none) + { + return true; + } + for (const auto& el : m_items) + { + if(el->get_type() == line_box_item::type_text_part) + { + if (!el->get_el()->skip() || el->get_el()->src_el()->is_break()) + { + return false; + } + } + } + return true; +} + +int litehtml::line_box::baseline() const +{ + return m_baseline; +} + +int litehtml::line_box::top_margin() const +{ + return 0; +} + +int litehtml::line_box::bottom_margin() const +{ + return 0; +} + +void litehtml::line_box::y_shift( int shift ) +{ + m_top += shift; + for (auto& el : m_items) + { + el->pos().y += shift; + } +} + +bool litehtml::line_box::is_break_only() const +{ + if(m_items.empty()) return false; + + bool break_found = false; + + for (auto iter = m_items.rbegin(); iter != m_items.rend(); iter++) + { + if((*iter)->get_type() == line_box_item::type_text_part) + { + if((*iter)->get_el()->src_el()->is_break()) + { + break_found = true; + } else if(!(*iter)->get_el()->skip()) + { + return false; + } + } + } + return break_found; +} + +std::list< std::unique_ptr<litehtml::line_box_item> > litehtml::line_box::new_width( int left, int right) +{ + std::list< std::unique_ptr<line_box_item> > ret_items; + int add = left - m_left; + if(add) + { + m_left = left; + m_right = right; + m_width = 0; + auto remove_begin = m_items.end(); + auto i = m_items.begin(); + i++; + while (i != m_items.end()) + { + if(!(*i)->get_el()->skip()) + { + if(m_left + m_width + (*i)->width() > m_right) + { + remove_begin = i; + break; + } else + { + (*i)->pos().x += add; + m_width += (*i)->get_el()->width(); + } + } + i++; + } + if(remove_begin != m_items.end()) + { + while(remove_begin != m_items.end()) + { + ret_items.emplace_back(std::move(*remove_begin)); + } + m_items.erase(remove_begin, m_items.end()); + } + } + return ret_items; +} + |