LayoutText.cpp 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. #include <AK/StringBuilder.h>
  2. #include <AK/Utf8View.h>
  3. #include <LibCore/CDirIterator.h>
  4. #include <LibDraw/Font.h>
  5. #include <LibGUI/GPainter.h>
  6. #include <LibHTML/Layout/LayoutBlock.h>
  7. #include <LibHTML/Layout/LayoutText.h>
  8. #include <ctype.h>
  9. LayoutText::LayoutText(const Text& text)
  10. : LayoutInline(text, {})
  11. {
  12. }
  13. LayoutText::~LayoutText()
  14. {
  15. }
  16. void LayoutText::load_font()
  17. {
  18. auto font_family = style().string_or_fallback("font-family", "Katica");
  19. auto font_weight = style().string_or_fallback("font-weight", "normal");
  20. String weight;
  21. if (font_weight == "lighter")
  22. weight = "Thin";
  23. else if (font_weight == "normal")
  24. weight = "";
  25. else if (font_weight == "bold")
  26. weight = "Bold";
  27. else
  28. ASSERT_NOT_REACHED();
  29. auto look_for_file = [](const StringView& expected_name) -> String {
  30. // TODO: handle font sizes properly?
  31. CDirIterator it { "/res/fonts/", CDirIterator::Flags::SkipDots };
  32. while (it.has_next()) {
  33. String name = it.next_path();
  34. ASSERT(name.ends_with(".font"));
  35. if (!name.starts_with(expected_name))
  36. continue;
  37. // Check that a numeric size immediately
  38. // follows the font name. This prevents,
  39. // for example, matching KaticaBold when
  40. // the regular Katica is requested.
  41. if (!isdigit(name[expected_name.length()]))
  42. continue;
  43. return name;
  44. }
  45. return {};
  46. };
  47. String file_name = look_for_file(String::format("%s%s", font_family.characters(), weight.characters()));
  48. if (file_name.is_null() && weight == "")
  49. file_name = look_for_file(String::format("%sRegular", font_family.characters()));
  50. if (file_name.is_null()) {
  51. dbg() << "Failed to find a font for family " << font_family << " weight " << font_weight;
  52. dbg() << "My text is " << node().data();
  53. ASSERT_NOT_REACHED();
  54. }
  55. #ifdef HTML_DEBUG
  56. dbg() << "Found font " << file_name << " for family " << font_family << " weight " << font_weight;
  57. #endif
  58. m_font = Font::load_from_file(String::format("/res/fonts/%s", file_name.characters()));
  59. }
  60. static bool is_all_whitespace(const String& string)
  61. {
  62. for (int i = 0; i < string.length(); ++i) {
  63. if (!isspace(string[i]))
  64. return false;
  65. }
  66. return true;
  67. }
  68. const String& LayoutText::text_for_style(const StyleProperties& style) const
  69. {
  70. static String one_space = " ";
  71. if (is_all_whitespace(node().data())) {
  72. if (style.string_or_fallback("white-space", "normal") == "normal")
  73. return one_space;
  74. }
  75. return node().data();
  76. }
  77. void LayoutText::render_fragment(RenderingContext& context, const LineBoxFragment& fragment) const
  78. {
  79. auto& painter = context.painter();
  80. painter.set_font(*m_font);
  81. auto color = style().color_or_fallback("color", Color::Black);
  82. auto text_decoration = style().string_or_fallback("text-decoration", "none");
  83. bool is_underline = text_decoration == "underline";
  84. if (is_underline)
  85. painter.draw_line(fragment.rect().bottom_left().translated(0, -1), fragment.rect().bottom_right().translated(0, -1), color);
  86. painter.draw_text(fragment.rect(), node().data().substring_view(fragment.start(), fragment.length()), TextAlignment::TopLeft, color);
  87. }
  88. template<typename Callback>
  89. void LayoutText::for_each_word(Callback callback) const
  90. {
  91. Utf8View view(node().data());
  92. if (view.is_empty())
  93. return;
  94. auto start_of_word = view.begin();
  95. auto commit_word = [&](auto it) {
  96. int start = view.byte_offset_of(start_of_word);
  97. int length = view.byte_offset_of(it) - view.byte_offset_of(start_of_word);
  98. if (length > 0) {
  99. callback(view.substring_view(start, length), start, length);
  100. }
  101. start_of_word = it;
  102. };
  103. bool last_was_space = isspace(*view.begin());
  104. for (auto it = view.begin(); it != view.end();) {
  105. bool is_space = isspace(*it);
  106. if (is_space == last_was_space) {
  107. ++it;
  108. continue;
  109. }
  110. last_was_space = is_space;
  111. commit_word(it);
  112. ++it;
  113. }
  114. if (start_of_word != view.end())
  115. commit_word(view.end());
  116. }
  117. template<typename Callback>
  118. void LayoutText::for_each_source_line(Callback callback) const
  119. {
  120. Utf8View view(node().data());
  121. if (view.is_empty())
  122. return;
  123. auto start_of_line = view.begin();
  124. auto commit_line = [&](auto it) {
  125. int start = view.byte_offset_of(start_of_line);
  126. int length = view.byte_offset_of(it) - view.byte_offset_of(start_of_line);
  127. if (length > 0) {
  128. callback(view.substring_view(start, length), start, length);
  129. }
  130. };
  131. for (auto it = view.begin(); it != view.end();) {
  132. bool did_commit = false;
  133. if (*it == '\n') {
  134. commit_line(it);
  135. did_commit = true;
  136. }
  137. ++it;
  138. if (did_commit)
  139. start_of_line = it;
  140. }
  141. if (start_of_line != view.end())
  142. commit_line(view.end());
  143. }
  144. void LayoutText::split_into_lines(LayoutBlock& container)
  145. {
  146. if (!m_font)
  147. load_font();
  148. int space_width = m_font->glyph_width(' ') + m_font->glyph_spacing();
  149. // FIXME: Allow overriding the line-height. We currently default to 140% which seems to look nice.
  150. int line_height = (int)(m_font->glyph_height() * 1.4f);
  151. auto& line_boxes = container.line_boxes();
  152. if (line_boxes.is_empty())
  153. line_boxes.append(LineBox());
  154. int available_width = container.rect().width() - line_boxes.last().width();
  155. bool is_preformatted = style().string_or_fallback("white-space", "normal") == "pre";
  156. if (is_preformatted) {
  157. for_each_source_line([&](const Utf8View& view, int start, int length) {
  158. line_boxes.last().add_fragment(*this, start, length, m_font->width(view), line_height);
  159. line_boxes.append(LineBox());
  160. });
  161. return;
  162. }
  163. struct Word {
  164. Utf8View view;
  165. int start;
  166. int length;
  167. };
  168. Vector<Word> words;
  169. for_each_word([&](const Utf8View& view, int start, int length) {
  170. words.append({ Utf8View(view), start, length });
  171. });
  172. for (int i = 0; i < words.size(); ++i) {
  173. auto& word = words[i];
  174. int word_width;
  175. bool is_whitespace = isspace(*word.view.begin());
  176. if (is_whitespace)
  177. word_width = space_width;
  178. else
  179. word_width = m_font->width(word.view) + m_font->glyph_spacing();
  180. if (word_width > available_width) {
  181. line_boxes.append(LineBox());
  182. available_width = container.rect().width();
  183. }
  184. if (is_whitespace && line_boxes.last().fragments().is_empty())
  185. continue;
  186. line_boxes.last().add_fragment(*this, word.start, word.length, word_width, line_height);
  187. available_width -= word_width;
  188. if (available_width < 0) {
  189. line_boxes.append(LineBox());
  190. available_width = container.rect().width();
  191. }
  192. }
  193. }