4 #include "render_item.h"
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();
60 int litehtml::lbi_start::width() const
65 int litehtml::lbi_start::top() const
70 int litehtml::lbi_start::bottom() const
72 return m_pos
.y
+ m_pos
.height
;
75 int litehtml::lbi_start::right() const
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
)
99 int litehtml::lbi_end::right() const
101 return m_pos
.x
+ m_pos
.width
;
104 int litehtml::lbi_end::left() const
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
;
117 void litehtml::lbi_continue::place_to(int x
, int y
)
123 int litehtml::lbi_continue::right() const
128 int litehtml::lbi_continue::left() const
133 int litehtml::lbi_continue::width() const
138 //////////////////////////////////////////////////////////////////////////////////////////
140 void litehtml::line_box::add_item(std::unique_ptr
<line_box_item
> item
)
142 item
->get_el()->skip(false);
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();
152 case line_box_item::type_inline_start
:
153 case line_box_item::type_inline_end
:
154 case line_box_item::type_inline_continue
:
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
));
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
)
175 return current
.baseline
- current
.fm
.height
/ 3;
177 return current
.baseline
+ current
.fm
.height
/ 3;
179 return current
.baseline
- current
.fm
.x_height
/ 2;
181 return current
.baseline
- (current
.fm
.height
- current
.fm
.base_line()) +
182 new_font
.height
- new_font
.base_line();
184 return current
.baseline
+ current
.fm
.base_line() - new_font
.base_line();
186 return top
+ new_font
.height
- new_font
.base_line();
188 return bottom
- new_font
.height
+ new_font
.base_line();
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
;
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);
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()));
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())
247 (*r_iter
)->pos().x
-= (*iter
)->width();
248 if (r_iter
== m_items
.rbegin())
255 // erase white space element
256 iter
= decltype(iter
) (m_items
.erase( std::next(iter
).base() ));
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();
280 case text_align_right
:
281 if(m_width
< (m_right
- m_left
))
283 add_x
= (m_right
- m_left
) - m_width
;
286 case text_align_center
:
287 if(m_width
< (m_right
- m_left
))
289 add_x
= ((m_right
- m_left
) - m_width
) / 2;
292 case text_align_justify
:
293 if (m_width
< (m_right
- m_left
))
296 spc_x
= (m_right
- m_left
) - m_width
;
297 if (spc_x
> m_width
/4)
306 float offj
= float(spc_x
) / std::max(1.f
, float(m_items
.size())-1.f
);
312 va_context current_context
;
313 std::list
<va_context
> contexts
;
315 current_context
.baseline
= 0;
316 current_context
.fm
= m_font_metrics
;
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
)
327 if ((counter
+ 1) == int(m_items
.size()))
329 lbi
->pos().x
+= int(cixx
);
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
;
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();
360 switch(lbi
->get_el()->css().get_vertical_align())
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();
371 lbi
->pos().y
= line_bottom
- lbi
->get_el()->height() + lbi
->get_el()->content_offset_top();
374 lbi
->pos().y
= line_top
+ lbi
->get_el()->content_offset_top();
377 lbi
->pos().y
= current_context
.baseline
- lbi
->get_el()->get_last_baseline() +
378 lbi
->get_el()->content_offset_top();
381 lbi
->pos().y
= current_context
.baseline
- current_context
.fm
.height
+ current_context
.fm
.base_line() +
382 lbi
->get_el()->content_offset_top();
385 lbi
->pos().y
= current_context
.baseline
+ current_context
.fm
.base_line() - lbi
->get_el()->height() +
386 lbi
->get_el()->content_offset_top();
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();
395 if (lbi
->get_type() == line_box_item::type_inline_end
)
397 if(!contexts
.empty())
399 current_context
= contexts
.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
;
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
;
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
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();
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
)
508 inlines
.back().box
.width
= lbi
->right() - inlines
.back().box
.x
;
509 inlines
.back().element
->add_inline_box(inlines
.back().box
);
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();
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();
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
)
564 auto last_el
= get_last_text_part();
566 // the first word is always can be hold
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)
579 // line break should stay in current line box
580 if (item
->get_el()->src_el()->is_break())
585 if (ws
== white_space_nowrap
|| ws
== white_space_pre
||
586 (ws
== white_space_pre_wrap
&& item
->get_el()->src_el()->is_space()))
591 if (m_left
+ m_width
+ item
->width() > m_right
)
600 bool litehtml::line_box::have_last_space() const
602 auto last_el
= get_last_text_part();
605 return last_el
->src_el()->is_white_space() || last_el
->src_el()->is_break();
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
)
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())
632 int litehtml::line_box::baseline() const
637 int litehtml::line_box::top_margin() const
642 int litehtml::line_box::bottom_margin() const
647 void litehtml::line_box::y_shift( int 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())
669 } else if(!(*iter
)->get_el()->skip())
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
;
687 auto remove_begin
= m_items
.end();
688 auto i
= m_items
.begin();
690 while (i
!= m_items
.end())
692 if(!(*i
)->get_el()->skip())
694 if(m_left
+ m_width
+ (*i
)->width() > m_right
)
700 (*i
)->pos().x
+= add
;
701 m_width
+= (*i
)->get_el()->width();
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());