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