LayoutText.cpp 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. #include <AK/StringBuilder.h>
  2. #include <LibCore/CDirIterator.h>
  3. #include <LibDraw/Font.h>
  4. #include <LibGUI/GPainter.h>
  5. #include <LibHTML/Layout/LayoutBlock.h>
  6. #include <LibHTML/Layout/LayoutText.h>
  7. #include <ctype.h>
  8. LayoutText::LayoutText(const Text& text, StyleProperties&& style_properties)
  9. : LayoutNode(&text, move(style_properties))
  10. {
  11. }
  12. LayoutText::~LayoutText()
  13. {
  14. }
  15. void LayoutText::load_font()
  16. {
  17. auto font_family = style_properties().string_or_fallback("font-family", "Katica");
  18. auto font_weight = style_properties().string_or_fallback("font-weight", "normal");
  19. String weight;
  20. if (font_weight == "lighter")
  21. weight = "Thin";
  22. else if (font_weight == "normal")
  23. weight = "";
  24. else if (font_weight == "bold")
  25. weight = "Bold";
  26. else
  27. ASSERT_NOT_REACHED();
  28. auto look_for_file = [](const StringView& expected_name) -> String {
  29. // TODO: handle font sizes properly?
  30. CDirIterator it { "/res/fonts/", CDirIterator::Flags::SkipDots };
  31. while (it.has_next()) {
  32. String name = it.next_path();
  33. ASSERT(name.ends_with(".font"));
  34. if (!name.starts_with(expected_name))
  35. continue;
  36. // Check that a numeric size immediately
  37. // follows the font name. This prevents,
  38. // for example, matching KaticaBold when
  39. // the regular Katica is requested.
  40. if (!isdigit(name[expected_name.length()]))
  41. continue;
  42. return name;
  43. }
  44. return {};
  45. };
  46. String file_name = look_for_file(String::format("%s%s", font_family.characters(), weight.characters()));
  47. if (file_name.is_null() && weight == "")
  48. file_name = look_for_file(String::format("%sRegular", font_family.characters()));
  49. if (file_name.is_null()) {
  50. dbg() << "Failed to find a font for family " << font_family << " weight " << font_weight;
  51. dbg() << "My text is " << node().data();
  52. ASSERT_NOT_REACHED();
  53. }
  54. #ifdef HTML_DEBUG
  55. dbg() << "Found font " << file_name << " for family " << font_family << " weight " << font_weight;
  56. #endif
  57. m_font = Font::load_from_file(String::format("/res/fonts/%s", file_name.characters()));
  58. }
  59. static bool is_all_whitespace(const String& string)
  60. {
  61. for (int i = 0; i < string.length(); ++i) {
  62. if (!isspace(string[i]))
  63. return false;
  64. }
  65. return true;
  66. }
  67. const String& LayoutText::text() const
  68. {
  69. static String one_space = " ";
  70. if (is_all_whitespace(node().data()))
  71. if (style_properties().string_or_fallback("white-space", "normal") == "normal")
  72. return one_space;
  73. return node().data();
  74. }
  75. static void split_first_word(const StringView& str, StringView& out_space, StringView& out_word)
  76. {
  77. int first_nonspace = -1;
  78. for (int i = 0; i < str.length(); i++)
  79. if (!isspace(str[i])) {
  80. first_nonspace = i;
  81. break;
  82. }
  83. if (first_nonspace == -1) {
  84. out_space = str;
  85. out_word = {};
  86. return;
  87. }
  88. int first_space = str.length();
  89. for (int i = first_nonspace + 1; i < str.length(); i++)
  90. if (isspace(str[i])) {
  91. first_space = i;
  92. break;
  93. }
  94. out_space = str.substring_view(0, first_nonspace);
  95. out_word = str.substring_view(first_nonspace, first_space - first_nonspace);
  96. }
  97. void LayoutText::compute_runs()
  98. {
  99. StringView remaining_text = node().data();
  100. if (remaining_text.is_empty())
  101. return;
  102. int right_border = containing_block()->rect().x() + containing_block()->rect().width();
  103. StringBuilder builder;
  104. Point run_origin = rect().location();
  105. int total_right_margin = style().full_margin().right;
  106. bool is_preformatted = style_properties().string_or_fallback("white-space", "normal") != "normal";
  107. while (!remaining_text.is_empty()) {
  108. String saved_text = builder.string_view();
  109. // Try to append a new word.
  110. StringView space;
  111. StringView word;
  112. split_first_word(remaining_text, space, word);
  113. int forced_line_break_index = -1;
  114. if (is_preformatted)
  115. for (int i = 0; i < space.length(); i++)
  116. if (space[i] == '\n') {
  117. forced_line_break_index = i;
  118. break;
  119. }
  120. if (!space.is_empty()) {
  121. if (!is_preformatted) {
  122. builder.append(' ');
  123. } else if (forced_line_break_index != -1) {
  124. builder.append(space.substring_view(0, forced_line_break_index));
  125. } else {
  126. builder.append(space);
  127. }
  128. }
  129. if (forced_line_break_index == -1)
  130. builder.append(word);
  131. if (forced_line_break_index != -1)
  132. remaining_text = remaining_text.substring_view(forced_line_break_index + 1, remaining_text.length() - forced_line_break_index - 1);
  133. else if (!word.is_null())
  134. remaining_text = remaining_text.substring_view_starting_after_substring(word);
  135. else
  136. remaining_text = {};
  137. // See if that fits.
  138. int width = m_font->width(builder.string_view());
  139. if (forced_line_break_index == -1 && run_origin.x() + width + total_right_margin < right_border)
  140. continue;
  141. // If it doesn't, create a run from
  142. // what we had there previously.
  143. if (forced_line_break_index == -1)
  144. m_runs.append({ run_origin, move(saved_text) });
  145. else
  146. m_runs.append({ run_origin, builder.string_view() });
  147. // Start a new run at the new line.
  148. int line_spacing = 4;
  149. run_origin.set_x(containing_block()->rect().x() + style().full_margin().left);
  150. run_origin.move_by(0, m_font->glyph_height() + line_spacing);
  151. builder = StringBuilder();
  152. if (forced_line_break_index != -1)
  153. continue;
  154. if (is_preformatted)
  155. builder.append(space);
  156. builder.append(word);
  157. }
  158. // Add the last run.
  159. m_runs.append({ run_origin, builder.build() });
  160. }
  161. void LayoutText::layout()
  162. {
  163. ASSERT(!has_children());
  164. if (!m_font)
  165. load_font();
  166. int origin_x = -1;
  167. int origin_y = -1;
  168. if (previous_sibling() != nullptr) {
  169. auto& previous_sibling_rect = previous_sibling()->rect();
  170. auto& previous_sibling_style = previous_sibling()->style();
  171. origin_x = previous_sibling_rect.x() + previous_sibling_rect.width();
  172. origin_x += previous_sibling_style.full_margin().right;
  173. origin_y = previous_sibling_rect.y() + previous_sibling_rect.height() - m_font->glyph_height() - previous_sibling_style.full_margin().top;
  174. } else {
  175. origin_x = parent()->rect().x();
  176. origin_y = parent()->rect().y();
  177. }
  178. rect().set_x(origin_x + style().full_margin().left);
  179. rect().set_y(origin_y + style().full_margin().top);
  180. m_runs.clear();
  181. compute_runs();
  182. if (m_runs.is_empty())
  183. return;
  184. const Run& last_run = m_runs[m_runs.size() - 1];
  185. rect().set_right(last_run.pos.x() + m_font->width(last_run.text));
  186. rect().set_bottom(last_run.pos.y() + m_font->glyph_height());
  187. }
  188. template<typename Callback>
  189. void LayoutText::for_each_run(Callback callback) const
  190. {
  191. for (auto& run : m_runs) {
  192. Rect rect {
  193. run.pos.x(),
  194. run.pos.y(),
  195. m_font->width(run.text),
  196. m_font->glyph_height()
  197. };
  198. if (callback(run, rect) == IterationDecision::Break)
  199. break;
  200. }
  201. }
  202. void LayoutText::render(RenderingContext& context)
  203. {
  204. auto& painter = context.painter();
  205. painter.set_font(*m_font);
  206. auto color = style_properties().color_or_fallback("color", Color::Black);
  207. auto text_decoration = style_properties().string_or_fallback("text-decoration", "none");
  208. bool is_underline = text_decoration == "underline";
  209. for_each_run([&](auto& run, auto& rect) {
  210. painter.draw_text(rect, run.text, TextAlignment::TopLeft, color);
  211. if (is_underline)
  212. painter.draw_line(rect.bottom_left().translated(0, 1), rect.bottom_right().translated(0, 1), color);
  213. return IterationDecision::Continue;
  214. });
  215. }
  216. HitTestResult LayoutText::hit_test(const Point& position) const
  217. {
  218. HitTestResult result;
  219. for_each_run([&](auto&, auto& rect) {
  220. if (rect.contains(position)) {
  221. result.layout_node = this;
  222. return IterationDecision::Break;
  223. }
  224. return IterationDecision::Continue;
  225. });
  226. return result;
  227. }