summaryrefslogtreecommitdiff
path: root/libs/litehtml/src/line_box.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libs/litehtml/src/line_box.cpp')
-rw-r--r--libs/litehtml/src/line_box.cpp717
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;
+}
+