summaryrefslogtreecommitdiff
path: root/Libraries/LibHTML
diff options
context:
space:
mode:
authorSergey Bugaev <bugaevc@gmail.com>2019-09-25 12:36:44 +0300
committerAndreas Kling <awesomekling@gmail.com>2019-09-28 18:29:42 +0200
commit93003bfda1211288a1b9720e646d7f79dfa2f77c (patch)
tree46be823af3471e495588523c6f0ada8d6ba606a6 /Libraries/LibHTML
parent9f8d776c70cb7eb15f6e5e2b4f1ca17bd55f5997 (diff)
downloadserenity-93003bfda1211288a1b9720e646d7f79dfa2f77c.zip
LibHTML: Implement LayoutText
Diffstat (limited to 'Libraries/LibHTML')
-rw-r--r--Libraries/LibHTML/Dump.cpp6
-rw-r--r--Libraries/LibHTML/Layout/LayoutText.cpp182
-rw-r--r--Libraries/LibHTML/Layout/LayoutText.h4
-rw-r--r--Libraries/LibHTML/Makefile2
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)