add support for Ayatana indicator to Notification plugin
[claws.git] / src / plugins / litehtml_viewer / litehtml / line_box.cpp
blobe739e63c8c06b84904d0f437c10894e8d563e0db
1 #include "html.h"
2 #include "line_box.h"
3 #include "element.h"
4 #include "render_item.h"
5 #include <algorithm>
7 //////////////////////////////////////////////////////////////////////////////////////////
9 void litehtml::line_box_item::place_to(int x, int y)
11 m_element->pos().x = x + m_element->content_offset_left();
12 m_element->pos().y = y + m_element->content_offset_top();
15 litehtml::position& litehtml::line_box_item::pos()
17 return m_element->pos();
21 int litehtml::line_box_item::width() const
23 return m_element->width();
26 int litehtml::line_box_item::top() const
28 return m_element->top();
31 int litehtml::line_box_item::bottom() const
33 return m_element->bottom();
36 int litehtml::line_box_item::right() const
38 return m_element->right();
41 int litehtml::line_box_item::left() const
43 return m_element->left();
46 //////////////////////////////////////////////////////////////////////////////////////////
48 litehtml::lbi_start::lbi_start(const std::shared_ptr<render_item>& element) : line_box_item(element)
50 m_pos.height = m_element->src_el()->css().get_font_metrics().height;
51 m_pos.width = m_element->content_offset_left();
54 void litehtml::lbi_start::place_to(int x, int y)
56 m_pos.x = x + m_element->content_offset_left();
57 m_pos.y = y;
60 int litehtml::lbi_start::width() const
62 return m_pos.width;
65 int litehtml::lbi_start::top() const
67 return m_pos.y;
70 int litehtml::lbi_start::bottom() const
72 return m_pos.y + m_pos.height;
75 int litehtml::lbi_start::right() const
77 return m_pos.x;
80 int litehtml::lbi_start::left() const
82 return m_pos.x - m_element->content_offset_left();
85 //////////////////////////////////////////////////////////////////////////////////////////
87 litehtml::lbi_end::lbi_end(const std::shared_ptr<render_item>& element) : lbi_start(element)
89 m_pos.height = m_element->src_el()->css().get_font_metrics().height;
90 m_pos.width = m_element->content_offset_right();
93 void litehtml::lbi_end::place_to(int x, int y)
95 m_pos.x = x;
96 m_pos.y = y;
99 int litehtml::lbi_end::right() const
101 return m_pos.x + m_pos.width;
104 int litehtml::lbi_end::left() const
106 return m_pos.x;
109 //////////////////////////////////////////////////////////////////////////////////////////
111 litehtml::lbi_continue::lbi_continue(const std::shared_ptr<render_item>& element) : lbi_start(element)
113 m_pos.height = m_element->src_el()->css().get_font_metrics().height;
114 m_pos.width = 0;
117 void litehtml::lbi_continue::place_to(int x, int y)
119 m_pos.x = x;
120 m_pos.y = y;
123 int litehtml::lbi_continue::right() const
125 return m_pos.x;
128 int litehtml::lbi_continue::left() const
130 return m_pos.x;
133 int litehtml::lbi_continue::width() const
135 return 0;
138 //////////////////////////////////////////////////////////////////////////////////////////
140 void litehtml::line_box::add_item(std::unique_ptr<line_box_item> item)
142 item->get_el()->skip(false);
143 bool add = true;
144 switch (item->get_type())
146 case line_box_item::type_text_part:
147 if(item->get_el()->src_el()->is_white_space())
149 add = !is_empty() && !have_last_space();
151 break;
152 case line_box_item::type_inline_start:
153 case line_box_item::type_inline_end:
154 case line_box_item::type_inline_continue:
155 add = true;
156 break;
158 if(add)
160 item->place_to(m_left + m_width, m_top);
161 m_width += item->width();
162 m_height = std::max(m_height, item->get_el()->height());
163 m_items.emplace_back(std::move(item));
164 } else
166 item->get_el()->skip(true);
170 int litehtml::line_box::calc_va_baseline(const va_context& current, vertical_align va, const font_metrics& new_font, int top, int bottom)
172 switch(va)
174 case va_super:
175 return current.baseline - current.fm.height / 3;
176 case va_sub:
177 return current.baseline + current.fm.height / 3;
178 case va_middle:
179 return current.baseline - current.fm.x_height / 2;
180 case va_text_top:
181 return current.baseline - (current.fm.height - current.fm.base_line()) +
182 new_font.height - new_font.base_line();
183 case va_text_bottom:
184 return current.baseline + current.fm.base_line() - new_font.base_line();
185 case va_top:
186 return top + new_font.height - new_font.base_line();
187 case va_bottom:
188 return bottom - new_font.height + new_font.base_line();
189 default:
190 return current.baseline;
194 std::list< std::unique_ptr<litehtml::line_box_item> > litehtml::line_box::finish(bool last_box, const containing_block_context &containing_block_size)
196 std::list< std::unique_ptr<line_box_item> > ret_items;
198 if(!last_box)
200 while(!m_items.empty())
202 if (m_items.back()->get_type() == line_box_item::type_text_part)
204 // remove trailing spaces
205 if (m_items.back()->get_el()->src_el()->is_break() ||
206 m_items.back()->get_el()->src_el()->is_white_space())
208 m_width -= m_items.back()->width();
209 m_items.back()->get_el()->skip(true);
210 m_items.pop_back();
211 } else
213 break;
215 } else if (m_items.back()->get_type() == line_box_item::type_inline_start)
217 // remove trailing empty inline_start markers
218 // these markers will be added at the beginning of the next line box
219 m_width -= m_items.back()->width();
220 ret_items.emplace_back(std::move(m_items.back()));
221 m_items.pop_back();
222 } else
224 break;
227 } else
229 // remove trailing spaces
230 auto iter = m_items.rbegin();
231 while(iter != m_items.rend())
233 if ((*iter)->get_type() == line_box_item::type_text_part)
235 if((*iter)->get_el()->src_el()->is_white_space())
237 (*iter)->get_el()->skip(true);
238 m_width -= (*iter)->width();
239 // Space can be between text and inline_end marker
240 // We have to shift all items on the right side
241 if(iter != m_items.rbegin())
243 auto r_iter = iter;
244 r_iter--;
245 while (true)
247 (*r_iter)->pos().x -= (*iter)->width();
248 if (r_iter == m_items.rbegin())
250 break;
252 r_iter--;
255 // erase white space element
256 iter = decltype(iter) (m_items.erase( std::next(iter).base() ));
257 } else
259 break;
261 } else
263 iter++;
268 if( is_empty() || (!is_empty() && last_box && is_break_only()) )
270 m_height = m_default_line_height;
271 m_baseline = m_font_metrics.base_line();
272 return ret_items;
275 int spc_x = 0;
277 int add_x = 0;
278 switch(m_text_align)
280 case text_align_right:
281 if(m_width < (m_right - m_left))
283 add_x = (m_right - m_left) - m_width;
285 break;
286 case text_align_center:
287 if(m_width < (m_right - m_left))
289 add_x = ((m_right - m_left) - m_width) / 2;
291 break;
292 case text_align_justify:
293 if (m_width < (m_right - m_left))
295 add_x = 0;
296 spc_x = (m_right - m_left) - m_width;
297 if (spc_x > m_width/4)
298 spc_x = 0;
300 break;
301 default:
302 add_x = 0;
305 int counter = 0;
306 float offj = float(spc_x) / std::max(1.f, float(m_items.size())-1.f);
307 float cixx = 0.0f;
309 int line_top = 0;
310 int line_bottom = 0;
312 va_context current_context;
313 std::list<va_context> contexts;
315 current_context.baseline = 0;
316 current_context.fm = m_font_metrics;
318 m_min_width = 0;
320 for (const auto& lbi : m_items)
322 m_min_width += lbi->get_rendered_min_width();
323 { // start text_align_justify
324 if (spc_x && counter)
326 cixx += offj;
327 if ((counter + 1) == int(m_items.size()))
328 cixx += 0.99f;
329 lbi->pos().x += int(cixx);
331 counter++;
332 if ((m_text_align == text_align_right || spc_x) && counter == int(m_items.size()))
334 // Forcible justify the last element to the right side for text align right and justify;
335 lbi->pos().x = m_right - lbi->pos().width;
336 } else if (add_x)
338 lbi->pos().x += add_x;
340 } // end text_align_justify
342 if (lbi->get_type() == line_box_item::type_inline_start || lbi->get_type() == line_box_item::type_inline_continue)
344 contexts.push_back(current_context);
345 current_context.baseline = calc_va_baseline(current_context,
346 lbi->get_el()->css().get_vertical_align(),
347 lbi->get_el()->css().get_font_metrics(),
348 line_top, line_bottom);
349 current_context.fm = lbi->get_el()->css().get_font_metrics();
352 // Align elements vertically by baseline.
353 if(lbi->get_el()->src_el()->css().get_display() == display_inline_text || lbi->get_el()->src_el()->css().get_display() == display_inline)
355 // inline elements and text are aligned by baseline only
356 // at this point the baseline for text is properly aligned already
357 lbi->pos().y = current_context.baseline - lbi->get_el()->css().get_font_metrics().height + lbi->get_el()->css().get_font_metrics().base_line();
358 } else
360 switch(lbi->get_el()->css().get_vertical_align())
362 case va_sub:
363 case va_super:
365 int bl = calc_va_baseline(current_context, lbi->get_el()->css().get_vertical_align(), current_context.fm, line_top, line_bottom);
366 lbi->pos().y = bl - lbi->get_el()->get_last_baseline() +
367 lbi->get_el()->content_offset_top();
369 break;
370 case va_bottom:
371 lbi->pos().y = line_bottom - lbi->get_el()->height() + lbi->get_el()->content_offset_top();
372 break;
373 case va_top:
374 lbi->pos().y = line_top + lbi->get_el()->content_offset_top();
375 break;
376 case va_baseline:
377 lbi->pos().y = current_context.baseline - lbi->get_el()->get_last_baseline() +
378 lbi->get_el()->content_offset_top();
379 break;
380 case va_text_top:
381 lbi->pos().y = current_context.baseline - current_context.fm.height + current_context.fm.base_line() +
382 lbi->get_el()->content_offset_top();
383 break;
384 case va_text_bottom:
385 lbi->pos().y = current_context.baseline + current_context.fm.base_line() - lbi->get_el()->height() +
386 lbi->get_el()->content_offset_top();
387 break;
388 case va_middle:
389 lbi->pos().y = current_context.baseline - current_context.fm.x_height / 2 - lbi->get_el()->height() / 2 +
390 lbi->get_el()->content_offset_top();
391 break;
395 if (lbi->get_type() == line_box_item::type_inline_end)
397 if(!contexts.empty())
399 current_context = contexts.back();
400 contexts.pop_back();
404 // calculate line height
405 line_top = std::min(line_top, lbi->top());
406 line_bottom = std::max(line_bottom, lbi->bottom());
408 if(lbi->get_el()->src_el()->css().get_display() == display_inline_text)
410 m_line_height = std::max(m_line_height, lbi->get_el()->css().get_line_height());
414 m_height = line_bottom - line_top;
415 int top_shift = line_top;
416 if(m_height < m_line_height)
418 top_shift -= (m_line_height - m_height) / 2;
419 m_height = m_line_height;
421 m_baseline = line_bottom;
423 struct inline_item_box
425 std::shared_ptr<render_item> element;
426 position box;
428 inline_item_box() = default;
429 explicit inline_item_box(const std::shared_ptr<render_item>& el) : element(el) {}
432 std::list<inline_item_box> inlines;
434 contexts.clear();
436 current_context.baseline = 0;
437 current_context.fm = m_font_metrics;
438 bool va_top_bottom = false;
440 for (const auto& lbi : m_items)
442 // Calculate baseline. Now we calculate baseline for vertical alignment top and bottom
443 if (lbi->get_type() == line_box_item::type_inline_start || lbi->get_type() == line_box_item::type_inline_continue)
445 contexts.push_back(current_context);
446 va_top_bottom = lbi->get_el()->css().get_vertical_align() == va_bottom || lbi->get_el()->css().get_vertical_align() == va_top;
447 current_context.baseline = calc_va_baseline(current_context,
448 lbi->get_el()->css().get_vertical_align(),
449 lbi->get_el()->css().get_font_metrics(),
450 top_shift, top_shift + m_height);
451 current_context.fm = lbi->get_el()->css().get_font_metrics();
454 // Align inlines and text by baseline if current vertical alignment is top or bottom
455 if(va_top_bottom)
457 if (lbi->get_el()->src_el()->css().get_display() == display_inline_text ||
458 lbi->get_el()->src_el()->css().get_display() == display_inline)
460 // inline elements and text are aligned by baseline only
461 // at this point the baseline for text is properly aligned already
462 lbi->pos().y = current_context.baseline - lbi->get_el()->css().get_font_metrics().height +
463 lbi->get_el()->css().get_font_metrics().base_line();
467 // Pop the prev context
468 if (lbi->get_type() == line_box_item::type_inline_end)
470 if(!contexts.empty())
472 current_context = contexts.back();
473 contexts.pop_back();
477 // move element to the correct position
478 lbi->pos().y += m_top - top_shift;
480 // Perform vertical align top and bottom for inline boxes
481 if(lbi->get_el()->css().get_display() != display_inline_text && lbi->get_el()->css().get_display() != display_inline)
483 if(lbi->get_el()->css().get_vertical_align() == va_top)
485 lbi->pos().y = m_top + lbi->get_el()->content_offset_top();
486 } else if(lbi->get_el()->css().get_vertical_align() == va_bottom)
488 lbi->pos().y = m_top + m_height - lbi->get_el()->height() + lbi->get_el()->content_offset_top();
491 lbi->get_el()->apply_relative_shift(containing_block_size);
493 // Calculate and push inline box into the render item element
494 if(lbi->get_type() == line_box_item::type_inline_start || lbi->get_type() == line_box_item::type_inline_continue)
496 if(lbi->get_type() == line_box_item::type_inline_start)
498 lbi->get_el()->clear_inline_boxes();
500 inlines.emplace_back(lbi->get_el());
501 inlines.back().box.x = lbi->left();
502 inlines.back().box.y = lbi->top() - lbi->get_el()->content_offset_top();
503 inlines.back().box.height = lbi->bottom() - lbi->top() + lbi->get_el()->content_offset_height();
504 } else if(lbi->get_type() == line_box_item::type_inline_end)
506 if(!inlines.empty())
508 inlines.back().box.width = lbi->right() - inlines.back().box.x;
509 inlines.back().element->add_inline_box(inlines.back().box);
510 inlines.pop_back();
515 for(auto iter = inlines.rbegin(); iter != inlines.rend(); ++iter)
517 iter->box.width = m_items.back()->right() - iter->box.x;
518 iter->element->add_inline_box(iter->box);
520 ret_items.emplace_front(std::unique_ptr<line_box_item>(new lbi_continue(iter->element)));
523 return std::move(ret_items);
526 std::shared_ptr<litehtml::render_item> litehtml::line_box::get_first_text_part() const
528 for(const auto & item : m_items)
530 if(item->get_type() == line_box_item::type_text_part)
532 return item->get_el();
535 return nullptr;
539 std::shared_ptr<litehtml::render_item> litehtml::line_box::get_last_text_part() const
541 for(auto iter = m_items.rbegin(); iter != m_items.rend(); iter++)
543 if((*iter)->get_type() == line_box_item::type_text_part)
545 return (*iter)->get_el();
548 return nullptr;
552 bool litehtml::line_box::can_hold(const std::unique_ptr<line_box_item>& item, white_space ws) const
554 if(!item->get_el()->src_el()->is_inline()) return false;
556 if(item->get_type() == line_box_item::type_text_part)
558 // force new line on floats clearing
559 if (item->get_el()->src_el()->is_break() && item->get_el()->src_el()->css().get_clear() != clear_none)
561 return false;
564 auto last_el = get_last_text_part();
566 // the first word is always can be hold
567 if(!last_el)
569 return true;
572 // force new line if the last placed element was line break
573 // Skip If there are the only break item - this is float clearing
574 if (last_el && last_el->src_el()->is_break() && m_items.size() > 1)
576 return false;
579 // line break should stay in current line box
580 if (item->get_el()->src_el()->is_break())
582 return true;
585 if (ws == white_space_nowrap || ws == white_space_pre ||
586 (ws == white_space_pre_wrap && item->get_el()->src_el()->is_space()))
588 return true;
591 if (m_left + m_width + item->width() > m_right)
593 return false;
597 return true;
600 bool litehtml::line_box::have_last_space() const
602 auto last_el = get_last_text_part();
603 if(last_el)
605 return last_el->src_el()->is_white_space() || last_el->src_el()->is_break();
607 return false;
610 bool litehtml::line_box::is_empty() const
612 if(m_items.empty()) return true;
613 if(m_items.size() == 1 &&
614 m_items.front()->get_el()->src_el()->is_break() &&
615 m_items.front()->get_el()->src_el()->css().get_clear() != clear_none)
617 return true;
619 for (const auto& el : m_items)
621 if(el->get_type() == line_box_item::type_text_part)
623 if (!el->get_el()->skip() || el->get_el()->src_el()->is_break())
625 return false;
629 return true;
632 int litehtml::line_box::baseline() const
634 return m_baseline;
637 int litehtml::line_box::top_margin() const
639 return 0;
642 int litehtml::line_box::bottom_margin() const
644 return 0;
647 void litehtml::line_box::y_shift( int shift )
649 m_top += shift;
650 for (auto& el : m_items)
652 el->pos().y += shift;
656 bool litehtml::line_box::is_break_only() const
658 if(m_items.empty()) return false;
660 bool break_found = false;
662 for (auto iter = m_items.rbegin(); iter != m_items.rend(); iter++)
664 if((*iter)->get_type() == line_box_item::type_text_part)
666 if((*iter)->get_el()->src_el()->is_break())
668 break_found = true;
669 } else if(!(*iter)->get_el()->skip())
671 return false;
675 return break_found;
678 std::list< std::unique_ptr<litehtml::line_box_item> > litehtml::line_box::new_width( int left, int right)
680 std::list< std::unique_ptr<line_box_item> > ret_items;
681 int add = left - m_left;
682 if(add)
684 m_left = left;
685 m_right = right;
686 m_width = 0;
687 auto remove_begin = m_items.end();
688 auto i = m_items.begin();
689 i++;
690 while (i != m_items.end())
692 if(!(*i)->get_el()->skip())
694 if(m_left + m_width + (*i)->width() > m_right)
696 remove_begin = i;
697 break;
698 } else
700 (*i)->pos().x += add;
701 m_width += (*i)->get_el()->width();
704 i++;
706 if(remove_begin != m_items.end())
708 while(remove_begin != m_items.end())
710 ret_items.emplace_back(std::move(*remove_begin));
712 m_items.erase(remove_begin, m_items.end());
715 return ret_items;