#include "html.h"
#include "line_box.h"
#include "element.h"
#include "render_item.h"
#include
//////////////////////////////////////////////////////////////////////////////////////////
litehtml::line_box_item::~line_box_item() = default;
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& 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();
}
litehtml::lbi_start::~lbi_start() = default;
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& element) : lbi_start(element)
{
m_pos.height = m_element->src_el()->css().get_font_metrics().height;
m_pos.width = m_element->content_offset_right();
}
litehtml::lbi_end::~lbi_end() = default;
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& element) : lbi_start(element)
{
m_pos.height = m_element->src_el()->css().get_font_metrics().height;
m_pos.width = 0;
}
litehtml::lbi_continue::~lbi_continue() = default;
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 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::finish(bool last_box, const containing_block_context &containing_block_size)
{
std::list< std::unique_ptr > 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 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 element;
position box;
inline_item_box() = default;
explicit inline_item_box(const std::shared_ptr& el) : element(el) {}
};
std::list 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(new lbi_continue(iter->element)));
}
return ret_items;
}
std::shared_ptr 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::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& 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()->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 the break item is float clearing
if (last_el && last_el->src_el()->is_break() && last_el->css().get_clear() == clear_none)
{
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::new_width( int left, int right)
{
std::list< std::unique_ptr > 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;
}