3 #include "stylesheet.h"
15 #include "el_script.h"
16 #include "el_comment.h"
19 #include "el_anchor.h"
28 #include "utf8_strings.h"
30 litehtml::document::document(litehtml::document_container* objContainer, litehtml::context* ctx)
32 m_container = objContainer;
36 litehtml::document::~document()
41 for(fonts_map::iterator f = m_fonts.begin(); f != m_fonts.end(); f++)
43 m_container->delete_font(f->second.font);
48 litehtml::document::ptr litehtml::document::createFromString( const tchar_t* str, litehtml::document_container* objPainter, litehtml::context* ctx, litehtml::css* user_styles)
50 return createFromUTF8(litehtml_to_utf8(str), objPainter, ctx, user_styles);
53 litehtml::document::ptr litehtml::document::createFromUTF8(const char* str, litehtml::document_container* objPainter, litehtml::context* ctx, litehtml::css* user_styles)
55 // parse document into GumboOutput
56 GumboOutput* output = gumbo_parse((const char*) str);
58 // Create litehtml::document
59 litehtml::document::ptr doc = std::make_shared<litehtml::document>(objPainter, ctx);
61 // Create litehtml::elements.
62 elements_vector root_elements;
63 doc->create_node(output->root, root_elements);
64 if (!root_elements.empty())
66 doc->m_root = root_elements.back();
68 // Destroy GumboOutput
69 gumbo_destroy_output(&kGumboDefaultOptions, output);
71 // Let's process created elements tree
74 doc->container()->get_media_features(doc->m_media);
77 doc->m_root->apply_stylesheet(ctx->master_css());
79 // parse elements attributes
80 doc->m_root->parse_attributes();
82 // parse style sheets linked in document
83 media_query_list::ptr media;
84 for (css_text::vector::iterator css = doc->m_css.begin(); css != doc->m_css.end(); css++)
86 if (!css->media.empty())
88 media = media_query_list::create_from_string(css->media, doc);
94 doc->m_styles.parse_stylesheet(css->text.c_str(), css->baseurl.c_str(), doc, media);
96 // Sort css selectors using CSS rules.
97 doc->m_styles.sort_selectors();
99 // get current media features
100 if (!doc->m_media_lists.empty())
102 doc->update_media_lists(doc->m_media);
105 // Apply parsed styles.
106 doc->m_root->apply_stylesheet(doc->m_styles);
108 // Apply user styles if any
111 doc->m_root->apply_stylesheet(*user_styles);
114 // Parse applied styles in the elements
115 doc->m_root->parse_styles();
117 // Now the m_tabular_elements is filled with tabular elements.
118 // We have to check the tabular elements for missing table elements
119 // and create the anonymous boxes in visual table layout
120 doc->fix_tables_layout();
122 // Fanaly initialize elements
129 litehtml::uint_ptr litehtml::document::add_font( const tchar_t* name, int size, const tchar_t* weight, const tchar_t* style, const tchar_t* decoration, font_metrics* fm )
133 if( !name || (name && !t_strcasecmp(name, _t("inherit"))) )
135 name = m_container->get_default_font_name();
140 size = container()->get_default_font_size();
144 t_itoa(size, strSize, 20, 10);
156 if(m_fonts.find(key) == m_fonts.end())
158 font_style fs = (font_style) value_index(style, font_style_strings, fontStyleNormal);
159 int fw = value_index(weight, font_weight_strings, -1);
164 case litehtml::fontWeightBold:
167 case litehtml::fontWeightBolder:
170 case litehtml::fontWeightLighter:
186 unsigned int decor = 0;
190 std::vector<tstring> tokens;
191 split_string(decoration, tokens, _t(" "));
192 for(std::vector<tstring>::iterator i = tokens.begin(); i != tokens.end(); i++)
194 if(!t_strcasecmp(i->c_str(), _t("underline")))
196 decor |= font_decoration_underline;
197 } else if(!t_strcasecmp(i->c_str(), _t("line-through")))
199 decor |= font_decoration_linethrough;
200 } else if(!t_strcasecmp(i->c_str(), _t("overline")))
202 decor |= font_decoration_overline;
209 fi.font = m_container->create_font(name, size, fw, fs, decor, &fi.metrics);
220 litehtml::uint_ptr litehtml::document::get_font( const tchar_t* name, int size, const tchar_t* weight, const tchar_t* style, const tchar_t* decoration, font_metrics* fm )
222 if( !name || (name && !t_strcasecmp(name, _t("inherit"))) )
224 name = m_container->get_default_font_name();
229 size = container()->get_default_font_size();
233 t_itoa(size, strSize, 20, 10);
245 fonts_map::iterator el = m_fonts.find(key);
247 if(el != m_fonts.end())
251 *fm = el->second.metrics;
253 return el->second.font;
255 return add_font(name, size, weight, style, decoration, fm);
258 int litehtml::document::render( int max_width, render_type rt )
263 if(rt == render_fixed_only)
265 m_fixed_boxes.clear();
266 m_root->render_positioned(rt);
269 ret = m_root->render(0, 0, max_width);
270 if(m_root->fetch_positioned())
272 m_fixed_boxes.clear();
273 m_root->render_positioned(rt);
277 m_root->calc_document_size(m_size);
283 void litehtml::document::draw( uint_ptr hdc, int x, int y, const position* clip )
287 m_root->draw(hdc, x, y, clip);
288 m_root->draw_stacking_context(hdc, x, y, clip, true);
292 int litehtml::document::cvt_units( const tchar_t* str, int fontSize, bool* is_percent/*= 0*/ ) const
298 if(is_percent && val.units() == css_units_percentage && !val.is_predefined())
302 return cvt_units(val, fontSize);
305 int litehtml::document::cvt_units( css_length& val, int fontSize, int size ) const
307 if(val.is_predefined())
314 case css_units_percentage:
315 ret = val.calc_percent(size);
318 ret = round_f(val.val() * fontSize);
319 val.set_value((float) ret, css_units_px);
322 ret = m_container->pt_to_px((int) val.val());
323 val.set_value((float) ret, css_units_px);
326 ret = m_container->pt_to_px((int) (val.val() * 72));
327 val.set_value((float) ret, css_units_px);
330 ret = m_container->pt_to_px((int) (val.val() * 0.3937 * 72));
331 val.set_value((float) ret, css_units_px);
334 ret = m_container->pt_to_px((int) (val.val() * 0.3937 * 72) / 10);
335 val.set_value((float) ret, css_units_px);
338 ret = (int)((double)m_media.width * (double)val.val() / 100.0);
341 ret = (int)((double)m_media.height * (double)val.val() / 100.0);
344 ret = (int)((double)std::min(m_media.height, m_media.width) * (double)val.val() / 100.0);
347 ret = (int)((double)std::max(m_media.height, m_media.width) * (double)val.val() / 100.0);
350 ret = (int) val.val();
356 int litehtml::document::width() const
361 int litehtml::document::height() const
363 return m_size.height;
366 void litehtml::document::add_stylesheet( const tchar_t* str, const tchar_t* baseurl, const tchar_t* media )
370 m_css.push_back(css_text(str, baseurl, media));
374 bool litehtml::document::on_mouse_over( int x, int y, int client_x, int client_y, position::vector& redraw_boxes )
381 element::ptr over_el = m_root->get_element_by_point(x, y, client_x, client_y);
383 bool state_was_changed = false;
385 if(over_el != m_over_element)
389 if(m_over_element->on_mouse_leave())
391 state_was_changed = true;
394 m_over_element = over_el;
397 const tchar_t* cursor = 0;
401 if(m_over_element->on_mouse_over())
403 state_was_changed = true;
405 cursor = m_over_element->get_cursor();
408 m_container->set_cursor(cursor ? cursor : _t("auto"));
410 if(state_was_changed)
412 return m_root->find_styles_changes(redraw_boxes, 0, 0);
417 bool litehtml::document::on_mouse_leave( position::vector& redraw_boxes )
425 if(m_over_element->on_mouse_leave())
427 return m_root->find_styles_changes(redraw_boxes, 0, 0);
433 bool litehtml::document::on_lbutton_down( int x, int y, int client_x, int client_y, position::vector& redraw_boxes )
440 element::ptr over_el = m_root->get_element_by_point(x, y, client_x, client_y);
442 bool state_was_changed = false;
444 if(over_el != m_over_element)
448 if(m_over_element->on_mouse_leave())
450 state_was_changed = true;
453 m_over_element = over_el;
456 if(m_over_element->on_mouse_over())
458 state_was_changed = true;
463 const tchar_t* cursor = 0;
467 if(m_over_element->on_lbutton_down())
469 state_was_changed = true;
471 cursor = m_over_element->get_cursor();
474 m_container->set_cursor(cursor ? cursor : _t("auto"));
476 if(state_was_changed)
478 return m_root->find_styles_changes(redraw_boxes, 0, 0);
484 bool litehtml::document::on_lbutton_up( int x, int y, int client_x, int client_y, position::vector& redraw_boxes )
492 if(m_over_element->on_lbutton_up())
494 return m_root->find_styles_changes(redraw_boxes, 0, 0);
500 litehtml::element::ptr litehtml::document::create_element(const tchar_t* tag_name, const string_map& attributes)
503 document::ptr this_doc = shared_from_this();
506 newTag = m_container->create_element(tag_name, attributes, this_doc);
510 if(!t_strcmp(tag_name, _t("br")))
512 newTag = std::make_shared<litehtml::el_break>(this_doc);
513 } else if(!t_strcmp(tag_name, _t("p")))
515 newTag = std::make_shared<litehtml::el_para>(this_doc);
516 } else if(!t_strcmp(tag_name, _t("img")))
518 newTag = std::make_shared<litehtml::el_image>(this_doc);
519 } else if(!t_strcmp(tag_name, _t("table")))
521 newTag = std::make_shared<litehtml::el_table>(this_doc);
522 } else if(!t_strcmp(tag_name, _t("td")) || !t_strcmp(tag_name, _t("th")))
524 newTag = std::make_shared<litehtml::el_td>(this_doc);
525 } else if(!t_strcmp(tag_name, _t("link")))
527 newTag = std::make_shared<litehtml::el_link>(this_doc);
528 } else if(!t_strcmp(tag_name, _t("title")))
530 newTag = std::make_shared<litehtml::el_title>(this_doc);
531 } else if(!t_strcmp(tag_name, _t("a")))
533 newTag = std::make_shared<litehtml::el_anchor>(this_doc);
534 } else if(!t_strcmp(tag_name, _t("tr")))
536 newTag = std::make_shared<litehtml::el_tr>(this_doc);
537 } else if(!t_strcmp(tag_name, _t("style")))
539 newTag = std::make_shared<litehtml::el_style>(this_doc);
540 } else if(!t_strcmp(tag_name, _t("base")))
542 newTag = std::make_shared<litehtml::el_base>(this_doc);
543 } else if(!t_strcmp(tag_name, _t("body")))
545 newTag = std::make_shared<litehtml::el_body>(this_doc);
546 } else if(!t_strcmp(tag_name, _t("div")))
548 newTag = std::make_shared<litehtml::el_div>(this_doc);
549 } else if(!t_strcmp(tag_name, _t("script")))
551 newTag = std::make_shared<litehtml::el_script>(this_doc);
552 } else if(!t_strcmp(tag_name, _t("font")))
554 newTag = std::make_shared<litehtml::el_font>(this_doc);
557 newTag = std::make_shared<litehtml::html_tag>(this_doc);
563 newTag->set_tagName(tag_name);
564 for (string_map::const_iterator iter = attributes.begin(); iter != attributes.end(); iter++)
566 newTag->set_attr(iter->first.c_str(), iter->second.c_str());
573 void litehtml::document::get_fixed_boxes( position::vector& fixed_boxes )
575 fixed_boxes = m_fixed_boxes;
578 void litehtml::document::add_fixed_box( const position& pos )
580 m_fixed_boxes.push_back(pos);
583 bool litehtml::document::media_changed()
585 if(!m_media_lists.empty())
587 container()->get_media_features(m_media);
588 if (update_media_lists(m_media))
590 m_root->refresh_styles();
591 m_root->parse_styles();
598 bool litehtml::document::lang_changed()
600 if(!m_media_lists.empty())
603 container()->get_language(m_lang, culture);
606 m_culture = m_lang + _t('-') + culture;
612 m_root->refresh_styles();
613 m_root->parse_styles();
619 bool litehtml::document::update_media_lists(const media_features& features)
621 bool update_styles = false;
622 for(media_query_list::vector::iterator iter = m_media_lists.begin(); iter != m_media_lists.end(); iter++)
624 if((*iter)->apply_media_features(features))
626 update_styles = true;
629 return update_styles;
632 void litehtml::document::add_media_list( media_query_list::ptr list )
636 if(std::find(m_media_lists.begin(), m_media_lists.end(), list) == m_media_lists.end())
638 m_media_lists.push_back(list);
643 void litehtml::document::create_node(GumboNode* node, elements_vector& elements)
647 case GUMBO_NODE_ELEMENT:
650 GumboAttribute* attr;
651 for (unsigned int i = 0; i < node->v.element.attributes.length; i++)
653 attr = (GumboAttribute*)node->v.element.attributes.data[i];
654 attrs[tstring(litehtml_from_utf8(attr->name))] = litehtml_from_utf8(attr->value);
659 const char* tag = gumbo_normalized_tagname(node->v.element.tag);
662 ret = create_element(litehtml_from_utf8(tag), attrs);
666 if (node->v.element.original_tag.data && node->v.element.original_tag.length)
669 gumbo_tag_from_original_text(&node->v.element.original_tag);
670 strA.append(node->v.element.original_tag.data, node->v.element.original_tag.length);
671 ret = create_element(litehtml_from_utf8(strA.c_str()), attrs);
676 elements_vector child;
677 for (unsigned int i = 0; i < node->v.element.children.length; i++)
680 create_node(static_cast<GumboNode*> (node->v.element.children.data[i]), child);
681 std::for_each(child.begin(), child.end(),
682 [&ret](element::ptr& el)
684 ret->appendChild(el);
688 elements.push_back(ret);
692 case GUMBO_NODE_TEXT:
695 std::wstring str_in = (const wchar_t*) (utf8_to_wchar(node->v.text.text));
697 for (size_t i = 0; i < str_in.length(); i++)
699 c = (ucode_t) str_in[i];
700 if (c <= ' ' && (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f'))
704 elements.push_back(std::make_shared<el_text>(litehtml_from_wchar(str.c_str()), shared_from_this()));
708 elements.push_back(std::make_shared<el_space>(litehtml_from_wchar(str.c_str()), shared_from_this()));
711 // CJK character range
712 else if (c >= 0x4E00 && c <= 0x9FCC)
716 elements.push_back(std::make_shared<el_text>(litehtml_from_wchar(str.c_str()), shared_from_this()));
720 elements.push_back(std::make_shared<el_text>(litehtml_from_wchar(str.c_str()), shared_from_this()));
730 elements.push_back(std::make_shared<el_text>(litehtml_from_wchar(str.c_str()), shared_from_this()));
734 case GUMBO_NODE_CDATA:
736 element::ptr ret = std::make_shared<el_cdata>(shared_from_this());
737 ret->set_data(litehtml_from_utf8(node->v.text.text));
738 elements.push_back(ret);
741 case GUMBO_NODE_COMMENT:
743 element::ptr ret = std::make_shared<el_comment>(shared_from_this());
744 ret->set_data(litehtml_from_utf8(node->v.text.text));
745 elements.push_back(ret);
748 case GUMBO_NODE_WHITESPACE:
750 tstring str = litehtml_from_utf8(node->v.text.text);
751 for (size_t i = 0; i < str.length(); i++)
753 elements.push_back(std::make_shared<el_space>(str.substr(i, 1).c_str(), shared_from_this()));
762 void litehtml::document::fix_tables_layout()
765 while (i < m_tabular_elements.size())
767 element::ptr el_ptr = m_tabular_elements[i];
769 switch (el_ptr->get_display())
771 case display_inline_table:
773 fix_table_children(el_ptr, display_table_row_group, _t("table-row-group"));
775 case display_table_footer_group:
776 case display_table_row_group:
777 case display_table_header_group:
778 fix_table_parent(el_ptr, display_table, _t("table"));
779 fix_table_children(el_ptr, display_table_row, _t("table-row"));
781 case display_table_row:
782 fix_table_parent(el_ptr, display_table_row_group, _t("table-row-group"));
783 fix_table_children(el_ptr, display_table_cell, _t("table-cell"));
785 case display_table_cell:
786 fix_table_parent(el_ptr, display_table_row, _t("table-row"));
788 // TODO: make table layout fix for table-caption, table-column etc. elements
789 case display_table_caption:
790 case display_table_column:
791 case display_table_column_group:
799 void litehtml::document::fix_table_children(element::ptr& el_ptr, style_display disp, const tchar_t* disp_str)
802 elements_vector::iterator first_iter = el_ptr->m_children.begin();
803 elements_vector::iterator cur_iter = el_ptr->m_children.begin();
805 auto flush_elements = [&]()
807 element::ptr annon_tag = std::make_shared<html_tag>(shared_from_this());
809 st.add_property(_t("display"), disp_str, 0, false);
810 annon_tag->add_style(st);
811 annon_tag->parent(el_ptr);
812 annon_tag->parse_styles();
813 std::for_each(tmp.begin(), tmp.end(),
814 [&annon_tag](element::ptr& el)
816 annon_tag->appendChild(el);
819 first_iter = el_ptr->m_children.insert(first_iter, annon_tag);
820 cur_iter = first_iter + 1;
821 while (cur_iter != el_ptr->m_children.end() && (*cur_iter)->parent() != el_ptr)
823 cur_iter = el_ptr->m_children.erase(cur_iter);
825 first_iter = cur_iter;
829 while (cur_iter != el_ptr->m_children.end())
831 if ((*cur_iter)->get_display() != disp)
833 if (!(*cur_iter)->is_white_space() || ((*cur_iter)->is_white_space() && !tmp.empty()))
837 first_iter = cur_iter;
839 tmp.push_back((*cur_iter));
843 else if (!tmp.empty())
858 void litehtml::document::fix_table_parent(element::ptr& el_ptr, style_display disp, const tchar_t* disp_str)
860 element::ptr parent = el_ptr->parent();
862 if (parent->get_display() != disp)
864 elements_vector::iterator this_element = std::find_if(parent->m_children.begin(), parent->m_children.end(),
865 [&](element::ptr& el)
874 if (this_element != parent->m_children.end())
876 style_display el_disp = el_ptr->get_display();
877 elements_vector::iterator first = this_element;
878 elements_vector::iterator last = this_element;
879 elements_vector::iterator cur = this_element;
881 // find first element with same display
884 if (cur == parent->m_children.begin()) break;
886 if ((*cur)->is_white_space() || (*cur)->get_display() == el_disp)
896 // find last element with same display
901 if (cur == parent->m_children.end()) break;
903 if ((*cur)->is_white_space() || (*cur)->get_display() == el_disp)
913 // extract elements with the same display and wrap them with anonymous object
914 element::ptr annon_tag = std::make_shared<html_tag>(shared_from_this());
916 st.add_property(_t("display"), disp_str, 0, false);
917 annon_tag->add_style(st);
918 annon_tag->parent(parent);
919 annon_tag->parse_styles();
920 std::for_each(first, last + 1,
921 [&annon_tag](element::ptr& el)
923 annon_tag->appendChild(el);
926 first = parent->m_children.erase(first, last + 1);
927 parent->m_children.insert(first, annon_tag);