Browse Source

LibHTML: Implement LayoutText

Sergey Bugaev 5 năm trước cách đây
mục cha
commit
93003bfda1

+ 4 - 2
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");
 

+ 181 - 1
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());
 }

+ 4 - 0
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;
 };

+ 1 - 1
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)