LayoutText.cpp 7.0 KB

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