diff options
author | Sergey Bugaev <bugaevc@gmail.com> | 2019-09-25 12:36:44 +0300 |
---|---|---|
committer | Andreas Kling <awesomekling@gmail.com> | 2019-09-28 18:29:42 +0200 |
commit | 93003bfda1211288a1b9720e646d7f79dfa2f77c (patch) | |
tree | 46be823af3471e495588523c6f0ada8d6ba606a6 /Libraries/LibHTML | |
parent | 9f8d776c70cb7eb15f6e5e2b4f1ca17bd55f5997 (diff) | |
download | serenity-93003bfda1211288a1b9720e646d7f79dfa2f77c.zip |
LibHTML: Implement LayoutText
Diffstat (limited to 'Libraries/LibHTML')
-rw-r--r-- | Libraries/LibHTML/Dump.cpp | 6 | ||||
-rw-r--r-- | Libraries/LibHTML/Layout/LayoutText.cpp | 182 | ||||
-rw-r--r-- | Libraries/LibHTML/Layout/LayoutText.h | 4 | ||||
-rw-r--r-- | Libraries/LibHTML/Makefile | 2 |
4 files changed, 190 insertions, 4 deletions
diff --git a/Libraries/LibHTML/Dump.cpp b/Libraries/LibHTML/Dump.cpp index 928cbc0c94..aae5a57692 100644 --- a/Libraries/LibHTML/Dump.cpp +++ b/Libraries/LibHTML/Dump.cpp @@ -78,8 +78,10 @@ void dump_tree(const LayoutNode& layout_node) layout_node.style().border().bottom.to_px(), layout_node.style().margin().bottom.to_px()); - if (layout_node.is_text()) - printf(" \"%s\"", static_cast<const LayoutText&>(layout_node).text().characters()); + if (layout_node.is_text()) { + const LayoutText& layout_text = static_cast<const LayoutText&>(layout_node); + printf(" \"%s\", %d runs", layout_text.text().characters(), layout_text.runs().size()); + } printf("\n"); diff --git a/Libraries/LibHTML/Layout/LayoutText.cpp b/Libraries/LibHTML/Layout/LayoutText.cpp index a54d9e9344..2976194428 100644 --- a/Libraries/LibHTML/Layout/LayoutText.cpp +++ b/Libraries/LibHTML/Layout/LayoutText.cpp @@ -1,3 +1,7 @@ +#include <AK/StringBuilder.h> +#include <LibCore/CDirIterator.h> +#include <LibDraw/Font.h> +#include <LibHTML/Layout/LayoutBlock.h> #include <LibHTML/Layout/LayoutText.h> #include <ctype.h> @@ -10,6 +14,55 @@ LayoutText::~LayoutText() { } +void LayoutText::load_font() +{ + auto font_family = style_properties().string_or_fallback("font-family", "Katica"); + auto font_weight = style_properties().string_or_fallback("font-weight", "normal"); + + String weight; + if (font_weight == "lighter") + weight = "Thin"; + else if (font_weight == "normal") + weight = ""; + else if (font_weight == "bold") + weight = "Bold"; + else + ASSERT_NOT_REACHED(); + + auto look_for_file = [](const StringView& expected_name) -> String { + // TODO: handle font sizes properly? + CDirIterator it { "/res/fonts/", CDirIterator::Flags::SkipDots }; + while (it.has_next()) { + String name = it.next_path(); + ASSERT(name.ends_with(".font")); + if (!name.starts_with(expected_name)) + continue; + + // Check that a numeric size immediately + // follows the font name. This prevents, + // for example, matching KaticaBold when + // the regular Katica is requested. + if (!isdigit(name[expected_name.length()])) + continue; + + return name; + } + return {}; + }; + + String file_name = look_for_file(String::format("%s%s", font_family.characters(), weight.characters())); + if (file_name.is_null() && weight == "") + file_name = look_for_file(String::format("%sRegular", font_family.characters())); + + if (file_name.is_null()) { + dbg() << "Failed to find a font for family " << font_family << " weight " << font_weight; + dbg() << "My text is " << node().data(); + ASSERT_NOT_REACHED(); + } + dbg() << "Found font " << file_name << " for family " << font_family << " weight " << font_weight; + m_font = Font::load_from_file(String::format("/res/fonts/%s", file_name.characters())); +} + static bool is_all_whitespace(const String& string) { for (int i = 0; i < string.length(); ++i) { @@ -23,16 +76,143 @@ const String& LayoutText::text() const { static String one_space = " "; if (is_all_whitespace(node().data())) - return one_space; + if (style_properties().string_or_fallback("white-space", "normal") == "normal") + return one_space; return node().data(); } +static void split_first_word(const StringView& str, StringView& out_space, StringView& out_word) +{ + int first_nonspace = -1; + for (int i = 0; i < str.length(); i++) + if (!isspace(str[i])) { + first_nonspace = i; + break; + } + + if (first_nonspace == -1) { + out_space = str; + out_word = {}; + return; + } + + int first_space = str.length(); + for (int i = first_nonspace + 1; i < str.length(); i++) + if (isspace(str[i])) { + first_space = i; + break; + } + + out_space = str.substring_view(0, first_nonspace); + out_word = str.substring_view(first_nonspace, first_space - first_nonspace); +} + void LayoutText::compute_runs() { + StringView remaining_text = node().data(); + if (remaining_text.is_empty()) + return; + + int right_border = containing_block()->rect().x() + containing_block()->rect().width(); + + StringBuilder builder; + Point run_origin = rect().location(); + + int total_right_margin = style().full_margin().right; + bool is_preformatted = style_properties().string_or_fallback("white-space", "normal") != "normal"; + + while (!remaining_text.is_empty()) { + String saved_text = builder.string_view(); + + // Try to append a new word. + StringView space; + StringView word; + split_first_word(remaining_text, space, word); + + int forced_line_break_index = -1; + if (is_preformatted) + for (int i = 0; i < space.length(); i++) + if (space[i] == '\n') { + forced_line_break_index = i; + break; + } + + if (!space.is_empty()) { + if (!is_preformatted) { + builder.append(' '); + } else if (forced_line_break_index != -1) { + builder.append(space.substring_view(0, forced_line_break_index)); + } else { + builder.append(space); + } + } + if (forced_line_break_index == -1) + builder.append(word); + + if (forced_line_break_index != -1) + remaining_text = remaining_text.substring_view(forced_line_break_index + 1, remaining_text.length() - forced_line_break_index - 1); + else if (!word.is_null()) + remaining_text = remaining_text.substring_view_starting_after_substring(word); + else + remaining_text = {}; + + // See if that fits. + int width = m_font->width(builder.string_view()); + if (forced_line_break_index == -1 && run_origin.x() + width + total_right_margin < right_border) + continue; + + // If it doesn't, create a run from + // what we had there previously. + if (forced_line_break_index == -1) + m_runs.append({ run_origin, move(saved_text) }); + else + m_runs.append({ run_origin, builder.string_view() }); + + // Start a new run at the new line. + int line_spacing = 4; + run_origin.set_x(containing_block()->rect().x() + style().full_margin().left); + run_origin.move_by(0, m_font->glyph_height() + line_spacing); + builder = StringBuilder(); + if (forced_line_break_index != -1) + continue; + if (is_preformatted) + builder.append(space); + builder.append(word); + } + + // Add the last run. + m_runs.append({ run_origin, builder.build() }); } void LayoutText::layout() { ASSERT(!has_children()); + + if (!m_font) + load_font(); + + int origin_x = -1; + int origin_y = -1; + if (previous_sibling() != nullptr) { + auto& previous_sibling_rect = previous_sibling()->rect(); + auto& previous_sibling_style = previous_sibling()->style(); + origin_x = previous_sibling_rect.x() + previous_sibling_rect.width(); + origin_x += previous_sibling_style.full_margin().right; + origin_y = previous_sibling_rect.y() + previous_sibling_rect.height() - m_font->glyph_height() - previous_sibling_style.full_margin().top; + } else { + origin_x = parent()->rect().x(); + origin_y = parent()->rect().y(); + } + rect().set_x(origin_x + style().full_margin().left); + rect().set_y(origin_y + style().full_margin().top); + + m_runs.clear(); compute_runs(); + + if (m_runs.is_empty()) + return; + + const Run& last_run = m_runs[m_runs.size() - 1]; + rect().set_right(last_run.pos.x() + m_font->width(last_run.text)); + rect().set_bottom(last_run.pos.y() + m_font->glyph_height()); } diff --git a/Libraries/LibHTML/Layout/LayoutText.h b/Libraries/LibHTML/Layout/LayoutText.h index ad1e556e91..8a16237d9b 100644 --- a/Libraries/LibHTML/Layout/LayoutText.h +++ b/Libraries/LibHTML/Layout/LayoutText.h @@ -3,6 +3,8 @@ #include <LibHTML/DOM/Text.h> #include <LibHTML/Layout/LayoutNode.h> +class Font; + class LayoutText : public LayoutNode { public: LayoutText(const Text&, StyleProperties&&); @@ -24,7 +26,9 @@ public: const Vector<Run>& runs() const { return m_runs; } private: + void load_font(); void compute_runs(); Vector<Run> m_runs; + RefPtr<Font> m_font; }; diff --git a/Libraries/LibHTML/Makefile b/Libraries/LibHTML/Makefile index cc0848d309..1cc579355b 100644 --- a/Libraries/LibHTML/Makefile +++ b/Libraries/LibHTML/Makefile @@ -7,7 +7,7 @@ all: $(LIBRARY) tho include Makefile.shared tho: $(TEST_OBJS) $(LIBRARY) - $(LD) -o $@ $(LDFLAGS) -L. $(TEST_OBJS) -lhtml -lgui -lcore -lc + $(LD) -o $@ $(LDFLAGS) -L. $(TEST_OBJS) -lhtml -lgui -ldraw -lcore -lc $(LIBRARY): $(LIBHTML_OBJS) @echo "LIB $@"; $(AR) rcs $@ $(LIBHTML_OBJS) |