3 #include "stylesheet.h"
15 #include "el_script.h"
16 #include "el_comment.h"
19 #include "el_anchor.h"
28 #include "utf8_strings.h"
29 #include "render_item.h"
30 #include "render_table.h"
31 #include "render_block.h"
33 litehtml::document::document(document_container
* objContainer
)
35 m_container
= objContainer
;
38 litehtml::document::~document()
40 m_over_element
= nullptr;
43 for(auto& font
: m_fonts
)
45 m_container
->delete_font(font
.second
.font
);
50 litehtml::document::ptr
litehtml::document::createFromString( const char* str
, document_container
* objPainter
, const char* master_styles
, const char* user_styles
)
52 // parse document into GumboOutput
53 GumboOutput
* output
= gumbo_parse(str
);
55 // Create litehtml::document
56 document::ptr doc
= std::make_shared
<document
>(objPainter
);
58 // Create litehtml::elements.
59 elements_list root_elements
;
60 doc
->create_node(output
->root
, root_elements
, true);
61 if (!root_elements
.empty())
63 doc
->m_root
= root_elements
.back();
65 // Destroy GumboOutput
66 gumbo_destroy_output(&kGumboDefaultOptions
, output
);
68 if (master_styles
&& *master_styles
)
70 doc
->m_master_css
.parse_stylesheet(master_styles
, nullptr, doc
, nullptr);
71 doc
->m_master_css
.sort_selectors();
73 if (user_styles
&& *user_styles
)
75 doc
->m_user_css
.parse_stylesheet(user_styles
, nullptr, doc
, nullptr);
76 doc
->m_user_css
.sort_selectors();
79 // Let's process created elements tree
82 doc
->container()->get_media_features(doc
->m_media
);
84 doc
->m_root
->set_pseudo_class(_root_
, true);
87 doc
->m_root
->apply_stylesheet(doc
->m_master_css
);
89 // parse elements attributes
90 doc
->m_root
->parse_attributes();
92 // parse style sheets linked in document
93 media_query_list::ptr media
;
94 for (const auto& css
: doc
->m_css
)
96 if (!css
.media
.empty())
98 media
= media_query_list::create_from_string(css
.media
, doc
);
104 doc
->m_styles
.parse_stylesheet(css
.text
.c_str(), css
.baseurl
.c_str(), doc
, media
);
106 // Sort css selectors using CSS rules.
107 doc
->m_styles
.sort_selectors();
109 // get current media features
110 if (!doc
->m_media_lists
.empty())
112 doc
->update_media_lists(doc
->m_media
);
115 // Apply parsed styles.
116 doc
->m_root
->apply_stylesheet(doc
->m_styles
);
118 // Apply user styles if any
119 doc
->m_root
->apply_stylesheet(doc
->m_user_css
);
122 doc
->m_root
->compute_styles();
124 // Create rendering tree
125 doc
->m_root_render
= doc
->m_root
->create_render_item(nullptr);
127 // Now the m_tabular_elements is filled with tabular elements.
128 // We have to check the tabular elements for missing table elements
129 // and create the anonymous boxes in visual table layout
130 doc
->fix_tables_layout();
132 // Finally initialize elements
133 // init() return pointer to the render_init element because it can change its type
134 doc
->m_root_render
= doc
->m_root_render
->init();
140 litehtml::uint_ptr
litehtml::document::add_font( const char* name
, int size
, const char* weight
, const char* style
, const char* decoration
, font_metrics
* fm
)
146 name
= m_container
->get_default_font_name();
150 t_itoa(size
, strSize
, 20, 10);
162 if(m_fonts
.find(key
) == m_fonts
.end())
164 font_style fs
= (font_style
) value_index(style
, font_style_strings
, font_style_normal
);
165 int fw
= value_index(weight
, font_weight_strings
, -1);
170 case litehtml::font_weight_bold
:
173 case litehtml::font_weight_bolder
:
176 case litehtml::font_weight_lighter
:
179 case litehtml::font_weight_normal
:
182 case litehtml::font_weight_100
:
185 case litehtml::font_weight_200
:
188 case litehtml::font_weight_300
:
191 case litehtml::font_weight_400
:
194 case litehtml::font_weight_500
:
197 case litehtml::font_weight_600
:
200 case litehtml::font_weight_700
:
203 case litehtml::font_weight_800
:
206 case litehtml::font_weight_900
:
219 unsigned int decor
= 0;
223 std::vector
<string
> tokens
;
224 split_string(decoration
, tokens
, " ");
225 for(auto & token
: tokens
)
227 if(!t_strcasecmp(token
.c_str(), "underline"))
229 decor
|= font_decoration_underline
;
230 } else if(!t_strcasecmp(token
.c_str(), "line-through"))
232 decor
|= font_decoration_linethrough
;
233 } else if(!t_strcasecmp(token
.c_str(), "overline"))
235 decor
|= font_decoration_overline
;
242 fi
.font
= m_container
->create_font(name
, size
, fw
, fs
, decor
, &fi
.metrics
);
253 litehtml::uint_ptr
litehtml::document::get_font( const char* name
, int size
, const char* weight
, const char* style
, const char* decoration
, font_metrics
* fm
)
261 name
= m_container
->get_default_font_name();
265 t_itoa(size
, strSize
, 20, 10);
277 auto el
= m_fonts
.find(key
);
279 if(el
!= m_fonts
.end())
283 *fm
= el
->second
.metrics
;
285 return el
->second
.font
;
287 return add_font(name
, size
, weight
, style
, decoration
, fm
);
290 int litehtml::document::render( int max_width
, render_type rt
)
296 m_container
->get_client_rect(client_rc
);
297 containing_block_context cb_context
;
298 cb_context
.width
= max_width
;
299 cb_context
.width
.type
= containing_block_context::cbc_value_type_absolute
;
300 cb_context
.height
= client_rc
.height
;
301 cb_context
.height
.type
= containing_block_context::cbc_value_type_absolute
;
303 if(rt
== render_fixed_only
)
305 m_fixed_boxes
.clear();
306 m_root_render
->render_positioned(rt
);
309 ret
= m_root_render
->render(0, 0, cb_context
, nullptr);
310 if(m_root_render
->fetch_positioned())
312 m_fixed_boxes
.clear();
313 m_root_render
->render_positioned(rt
);
317 m_content_size
.width
= 0;
318 m_content_size
.height
= 0;
319 m_root_render
->calc_document_size(m_size
, m_content_size
);
325 void litehtml::document::draw( uint_ptr hdc
, int x
, int y
, const position
* clip
)
327 if(m_root
&& m_root_render
)
329 m_root
->draw(hdc
, x
, y
, clip
, m_root_render
);
330 m_root_render
->draw_stacking_context(hdc
, x
, y
, clip
, true);
334 int litehtml::document::to_pixels( const char* str
, int fontSize
, bool* is_percent
/*= 0*/ ) const
340 if(is_percent
&& val
.units() == css_units_percentage
&& !val
.is_predefined())
344 return to_pixels(val
, fontSize
);
347 int litehtml::document::to_pixels( const css_length
& val
, int fontSize
, int size
) const
349 if(val
.is_predefined())
356 case css_units_percentage
:
357 ret
= val
.calc_percent(size
);
360 ret
= round_f(val
.val() * (float) fontSize
);
363 ret
= m_container
->pt_to_px((int) val
.val());
366 ret
= m_container
->pt_to_px((int) (val
.val() * 72));
369 ret
= m_container
->pt_to_px((int) (val
.val() * 0.3937 * 72));
372 ret
= m_container
->pt_to_px((int) (val
.val() * 0.3937 * 72) / 10);
375 ret
= (int)((double)m_media
.width
* (double)val
.val() / 100.0);
378 ret
= (int)((double)m_media
.height
* (double)val
.val() / 100.0);
381 ret
= (int)((double)std::min(m_media
.height
, m_media
.width
) * (double)val
.val() / 100.0);
384 ret
= (int)((double)std::max(m_media
.height
, m_media
.width
) * (double)val
.val() / 100.0);
387 ret
= (int) ((double) m_root
->css().get_font_size() * (double) val
.val());
390 ret
= (int) val
.val();
396 void litehtml::document::cvt_units( css_length
& val
, int fontSize
, int size
) const
398 if(val
.is_predefined())
406 ret
= round_f(val
.val() * (float) fontSize
);
407 val
.set_value((float) ret
, css_units_px
);
410 ret
= m_container
->pt_to_px((int) val
.val());
411 val
.set_value((float) ret
, css_units_px
);
414 ret
= m_container
->pt_to_px((int) (val
.val() * 72));
415 val
.set_value((float) ret
, css_units_px
);
418 ret
= m_container
->pt_to_px((int) (val
.val() * 0.3937 * 72));
419 val
.set_value((float) ret
, css_units_px
);
422 ret
= m_container
->pt_to_px((int) (val
.val() * 0.3937 * 72) / 10);
423 val
.set_value((float) ret
, css_units_px
);
428 int litehtml::document::width() const
433 int litehtml::document::height() const
435 return m_size
.height
;
438 int litehtml::document::content_width() const
440 return m_content_size
.width
;
443 int litehtml::document::content_height() const
445 return m_content_size
.height
;
449 void litehtml::document::add_stylesheet( const char* str
, const char* baseurl
, const char* media
)
453 m_css
.push_back(css_text(str
, baseurl
, media
));
457 bool litehtml::document::on_mouse_over( int x
, int y
, int client_x
, int client_y
, position::vector
& redraw_boxes
)
459 if(!m_root
|| !m_root_render
)
464 element::ptr over_el
= m_root_render
->get_element_by_point(x
, y
, client_x
, client_y
);
466 bool state_was_changed
= false;
468 if(over_el
!= m_over_element
)
472 if(m_over_element
->on_mouse_leave())
474 state_was_changed
= true;
477 m_over_element
= over_el
;
484 if(m_over_element
->on_mouse_over())
486 state_was_changed
= true;
488 cursor
= m_over_element
->css().get_cursor();
491 m_container
->set_cursor(cursor
.c_str());
493 if(state_was_changed
)
495 return m_root
->find_styles_changes(redraw_boxes
);
500 bool litehtml::document::on_mouse_leave( position::vector
& redraw_boxes
)
502 if(!m_root
|| !m_root_render
)
508 if(m_over_element
->on_mouse_leave())
510 return m_root
->find_styles_changes(redraw_boxes
);
516 bool litehtml::document::on_lbutton_down( int x
, int y
, int client_x
, int client_y
, position::vector
& redraw_boxes
)
518 if(!m_root
|| !m_root_render
)
523 element::ptr over_el
= m_root_render
->get_element_by_point(x
, y
, client_x
, client_y
);
525 bool state_was_changed
= false;
527 if(over_el
!= m_over_element
)
531 if(m_over_element
->on_mouse_leave())
533 state_was_changed
= true;
536 m_over_element
= over_el
;
539 if(m_over_element
->on_mouse_over())
541 state_was_changed
= true;
550 if(m_over_element
->on_lbutton_down())
552 state_was_changed
= true;
554 cursor
= m_over_element
->css().get_cursor();
557 m_container
->set_cursor(cursor
.c_str());
559 if(state_was_changed
)
561 return m_root
->find_styles_changes(redraw_boxes
);
567 bool litehtml::document::on_lbutton_up( int x
, int y
, int client_x
, int client_y
, position::vector
& redraw_boxes
)
569 if(!m_root
|| !m_root_render
)
575 if(m_over_element
->on_lbutton_up())
577 return m_root
->find_styles_changes(redraw_boxes
);
583 litehtml::element::ptr
litehtml::document::create_element(const char* tag_name
, const string_map
& attributes
)
586 document::ptr this_doc
= shared_from_this();
589 newTag
= m_container
->create_element(tag_name
, attributes
, this_doc
);
593 if(!strcmp(tag_name
, "br"))
595 newTag
= std::make_shared
<litehtml::el_break
>(this_doc
);
596 } else if(!strcmp(tag_name
, "p"))
598 newTag
= std::make_shared
<litehtml::el_para
>(this_doc
);
599 } else if(!strcmp(tag_name
, "img"))
601 newTag
= std::make_shared
<litehtml::el_image
>(this_doc
);
602 } else if(!strcmp(tag_name
, "table"))
604 newTag
= std::make_shared
<litehtml::el_table
>(this_doc
);
605 } else if(!strcmp(tag_name
, "td") || !strcmp(tag_name
, "th"))
607 newTag
= std::make_shared
<litehtml::el_td
>(this_doc
);
608 } else if(!strcmp(tag_name
, "link"))
610 newTag
= std::make_shared
<litehtml::el_link
>(this_doc
);
611 } else if(!strcmp(tag_name
, "title"))
613 newTag
= std::make_shared
<litehtml::el_title
>(this_doc
);
614 } else if(!strcmp(tag_name
, "a"))
616 newTag
= std::make_shared
<litehtml::el_anchor
>(this_doc
);
617 } else if(!strcmp(tag_name
, "tr"))
619 newTag
= std::make_shared
<litehtml::el_tr
>(this_doc
);
620 } else if(!strcmp(tag_name
, "style"))
622 newTag
= std::make_shared
<litehtml::el_style
>(this_doc
);
623 } else if(!strcmp(tag_name
, "base"))
625 newTag
= std::make_shared
<litehtml::el_base
>(this_doc
);
626 } else if(!strcmp(tag_name
, "body"))
628 newTag
= std::make_shared
<litehtml::el_body
>(this_doc
);
629 } else if(!strcmp(tag_name
, "div"))
631 newTag
= std::make_shared
<litehtml::el_div
>(this_doc
);
632 } else if(!strcmp(tag_name
, "script"))
634 newTag
= std::make_shared
<litehtml::el_script
>(this_doc
);
635 } else if(!strcmp(tag_name
, "font"))
637 newTag
= std::make_shared
<litehtml::el_font
>(this_doc
);
640 newTag
= std::make_shared
<litehtml::html_tag
>(this_doc
);
646 newTag
->set_tagName(tag_name
);
647 for (const auto & attribute
: attributes
)
649 newTag
->set_attr(attribute
.first
.c_str(), attribute
.second
.c_str());
656 void litehtml::document::get_fixed_boxes( position::vector
& fixed_boxes
)
658 fixed_boxes
= m_fixed_boxes
;
661 void litehtml::document::add_fixed_box( const position
& pos
)
663 m_fixed_boxes
.push_back(pos
);
666 bool litehtml::document::media_changed()
668 container()->get_media_features(m_media
);
669 if (update_media_lists(m_media
))
671 m_root
->refresh_styles();
672 m_root
->compute_styles();
678 bool litehtml::document::lang_changed()
680 if(!m_media_lists
.empty())
683 container()->get_language(m_lang
, culture
);
686 m_culture
= m_lang
+ '-' + culture
;
692 m_root
->refresh_styles();
693 m_root
->compute_styles();
699 bool litehtml::document::update_media_lists(const media_features
& features
)
701 bool update_styles
= false;
702 for(auto & m_media_list
: m_media_lists
)
704 if(m_media_list
->apply_media_features(features
))
706 update_styles
= true;
709 return update_styles
;
712 void litehtml::document::add_media_list( const media_query_list::ptr
& list
)
716 if(std::find(m_media_lists
.begin(), m_media_lists
.end(), list
) == m_media_lists
.end())
718 m_media_lists
.push_back(list
);
723 void litehtml::document::create_node(void* gnode
, elements_list
& elements
, bool parseTextNode
)
725 auto* node
= (GumboNode
*)gnode
;
728 case GUMBO_NODE_ELEMENT
:
731 GumboAttribute
* attr
;
732 for (unsigned int i
= 0; i
< node
->v
.element
.attributes
.length
; i
++)
734 attr
= (GumboAttribute
*)node
->v
.element
.attributes
.data
[i
];
735 attrs
[attr
->name
] = attr
->value
;
740 const char* tag
= gumbo_normalized_tagname(node
->v
.element
.tag
);
743 ret
= create_element(tag
, attrs
);
747 if (node
->v
.element
.original_tag
.data
&& node
->v
.element
.original_tag
.length
)
750 gumbo_tag_from_original_text(&node
->v
.element
.original_tag
);
751 strA
.append(node
->v
.element
.original_tag
.data
, node
->v
.element
.original_tag
.length
);
752 ret
= create_element(strA
.c_str(), attrs
);
755 if (!strcmp(tag
, "script"))
757 parseTextNode
= false;
762 for (unsigned int i
= 0; i
< node
->v
.element
.children
.length
; i
++)
765 create_node(static_cast<GumboNode
*> (node
->v
.element
.children
.data
[i
]), child
, parseTextNode
);
766 std::for_each(child
.begin(), child
.end(),
767 [&ret
](element::ptr
& el
)
769 ret
->appendChild(el
);
773 elements
.push_back(ret
);
777 case GUMBO_NODE_TEXT
:
781 elements
.push_back(std::make_shared
<el_text
>(node
->v
.text
.text
, shared_from_this()));
785 m_container
->split_text(node
->v
.text
.text
,
786 [this, &elements
](const char* text
) { elements
.push_back(std::make_shared
<el_text
>(text
, shared_from_this())); },
787 [this, &elements
](const char* text
) { elements
.push_back(std::make_shared
<el_space
>(text
, shared_from_this())); });
791 case GUMBO_NODE_CDATA
:
793 element::ptr ret
= std::make_shared
<el_cdata
>(shared_from_this());
794 ret
->set_data(node
->v
.text
.text
);
795 elements
.push_back(ret
);
798 case GUMBO_NODE_COMMENT
:
800 element::ptr ret
= std::make_shared
<el_comment
>(shared_from_this());
801 ret
->set_data(node
->v
.text
.text
);
802 elements
.push_back(ret
);
805 case GUMBO_NODE_WHITESPACE
:
807 string str
= node
->v
.text
.text
;
808 for (size_t i
= 0; i
< str
.length(); i
++)
810 elements
.push_back(std::make_shared
<el_space
>(str
.substr(i
, 1).c_str(), shared_from_this()));
819 void litehtml::document::fix_tables_layout()
821 for (const auto& el_ptr
: m_tabular_elements
)
823 switch (el_ptr
->src_el()->css().get_display())
825 case display_inline_table
:
827 fix_table_children(el_ptr
, display_table_row_group
, "table-row-group");
829 case display_table_footer_group
:
830 case display_table_row_group
:
831 case display_table_header_group
:
833 auto parent
= el_ptr
->parent();
836 if (parent
->src_el()->css().get_display() != display_inline_table
)
837 fix_table_parent(el_ptr
, display_table
, "table");
839 fix_table_children(el_ptr
, display_table_row
, "table-row");
842 case display_table_row
:
843 fix_table_parent(el_ptr
, display_table_row_group
, "table-row-group");
844 fix_table_children(el_ptr
, display_table_cell
, "table-cell");
846 case display_table_cell
:
847 fix_table_parent(el_ptr
, display_table_row
, "table-row");
849 // TODO: make table layout fix for table-caption, table-column etc. elements
850 case display_table_caption
:
851 case display_table_column
:
852 case display_table_column_group
:
859 void litehtml::document::fix_table_children(const std::shared_ptr
<render_item
>& el_ptr
, style_display disp
, const char* disp_str
)
861 std::list
<std::shared_ptr
<render_item
>> tmp
;
862 auto first_iter
= el_ptr
->children().begin();
863 auto cur_iter
= el_ptr
->children().begin();
865 auto flush_elements
= [&]()
867 element::ptr annon_tag
= std::make_shared
<html_tag
>(el_ptr
->src_el(), string("display:") + disp_str
);
868 std::shared_ptr
<render_item
> annon_ri
;
869 if(annon_tag
->css().get_display() == display_table_cell
)
871 annon_tag
->set_tagName("table_cell");
872 annon_ri
= std::make_shared
<render_item_block
>(annon_tag
);
873 } else if(annon_tag
->css().get_display() == display_table_row
)
875 annon_ri
= std::make_shared
<render_item_table_row
>(annon_tag
);
878 annon_ri
= std::make_shared
<render_item_table_part
>(annon_tag
);
880 for(const auto& el
: tmp
)
882 annon_ri
->add_child(el
);
884 // add annon item as tabular for future processing
885 add_tabular(annon_ri
);
886 annon_ri
->parent(el_ptr
);
887 first_iter
= el_ptr
->children().insert(first_iter
, annon_ri
);
888 cur_iter
= std::next(first_iter
);
889 while (cur_iter
!= el_ptr
->children().end() && (*cur_iter
)->parent() != el_ptr
)
891 cur_iter
= el_ptr
->children().erase(cur_iter
);
893 first_iter
= cur_iter
;
897 while (cur_iter
!= el_ptr
->children().end())
899 if ((*cur_iter
)->src_el()->css().get_display() != disp
)
901 if (!(*cur_iter
)->src_el()->is_table_skip() || ((*cur_iter
)->src_el()->is_table_skip() && !tmp
.empty()))
903 if (disp
!= display_table_row_group
|| (*cur_iter
)->src_el()->css().get_display() != display_table_caption
)
907 first_iter
= cur_iter
;
909 tmp
.push_back((*cur_iter
));
914 else if (!tmp
.empty())
929 void litehtml::document::fix_table_parent(const std::shared_ptr
<render_item
>& el_ptr
, style_display disp
, const char* disp_str
)
931 auto parent
= el_ptr
->parent();
933 if (parent
->src_el()->css().get_display() != disp
)
935 auto this_element
= std::find_if(parent
->children().begin(), parent
->children().end(),
936 [&](const std::shared_ptr
<render_item
>& el
)
945 if (this_element
!= parent
->children().end())
947 style_display el_disp
= el_ptr
->src_el()->css().get_display();
948 auto first
= this_element
;
949 auto last
= this_element
;
950 auto cur
= this_element
;
952 // find first element with same display
955 if (cur
== parent
->children().begin()) break;
957 if ((*cur
)->src_el()->is_table_skip() || (*cur
)->src_el()->css().get_display() == el_disp
)
967 // find last element with same display
972 if (cur
== parent
->children().end()) break;
974 if ((*cur
)->src_el()->is_table_skip() || (*cur
)->src_el()->css().get_display() == el_disp
)
984 // extract elements with the same display and wrap them with anonymous object
985 element::ptr annon_tag
= std::make_shared
<html_tag
>(parent
->src_el(), string("display:") + disp_str
);
986 std::shared_ptr
<render_item
> annon_ri
;
987 if(annon_tag
->css().get_display() == display_table
|| annon_tag
->css().get_display() == display_inline_table
)
989 annon_ri
= std::make_shared
<render_item_table
>(annon_tag
);
990 } else if(annon_tag
->css().get_display() == display_table_row
)
992 annon_ri
= std::make_shared
<render_item_table_row
>(annon_tag
);
995 annon_ri
= std::make_shared
<render_item_table_part
>(annon_tag
);
997 std::for_each(first
, std::next(last
, 1),
998 [&annon_ri
](std::shared_ptr
<render_item
>& el
)
1000 annon_ri
->add_child(el
);
1003 first
= parent
->children().erase(first
, std::next(last
));
1004 parent
->children().insert(first
, annon_ri
);
1005 add_tabular(annon_ri
);
1006 annon_ri
->parent(parent
);
1011 void litehtml::document::append_children_from_string(element
& parent
, const char* str
)
1013 // parent must belong to this document
1014 if (parent
.get_document().get() != this)
1019 // parse document into GumboOutput
1020 GumboOutput
* output
= gumbo_parse(str
);
1022 // Create litehtml::elements.
1023 elements_list child_elements
;
1024 create_node(output
->root
, child_elements
, true);
1026 // Destroy GumboOutput
1027 gumbo_destroy_output(&kGumboDefaultOptions
, output
);
1029 // Let's process created elements tree
1030 for (const auto& child
: child_elements
)
1032 // Add the child element to parent
1033 parent
.appendChild(child
);
1036 child
->apply_stylesheet(m_master_css
);
1038 // parse elements attributes
1039 child
->parse_attributes();
1041 // Apply parsed styles.
1042 child
->apply_stylesheet(m_styles
);
1044 // Apply user styles if any
1045 child
->apply_stylesheet(m_user_css
);
1048 child
->compute_styles();
1050 // Now the m_tabular_elements is filled with tabular elements.
1051 // We have to check the tabular elements for missing table elements
1052 // and create the anonymous boxes in visual table layout
1053 fix_tables_layout();
1055 // Finally initialize elements
1060 void litehtml::document::dump(dumper
& cout
)
1064 m_root_render
->dump(cout
);