mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-05-06 19:22:53 +00:00
LibHTML: Implement LayoutText
This commit is contained in:
parent
9f8d776c70
commit
93003bfda1
Notes:
sideshowbarker
2024-07-19 11:55:53 +09:00
Author: https://github.com/bugaevc
Commit: 93003bfda1
Pull-request: https://github.com/SerenityOS/serenity/pull/609
Reviewed-by: https://github.com/awesomekling ✅
4 changed files with 190 additions and 4 deletions
|
@ -78,8 +78,10 @@ void dump_tree(const LayoutNode& layout_node)
|
||||||
layout_node.style().border().bottom.to_px(),
|
layout_node.style().border().bottom.to_px(),
|
||||||
layout_node.style().margin().bottom.to_px());
|
layout_node.style().margin().bottom.to_px());
|
||||||
|
|
||||||
if (layout_node.is_text())
|
if (layout_node.is_text()) {
|
||||||
printf(" \"%s\"", static_cast<const LayoutText&>(layout_node).text().characters());
|
const LayoutText& layout_text = static_cast<const LayoutText&>(layout_node);
|
||||||
|
printf(" \"%s\", %d runs", layout_text.text().characters(), layout_text.runs().size());
|
||||||
|
}
|
||||||
|
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
|
||||||
|
|
|
@ -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 <LibHTML/Layout/LayoutText.h>
|
||||||
#include <ctype.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)
|
static bool is_all_whitespace(const String& string)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < string.length(); ++i) {
|
for (int i = 0; i < string.length(); ++i) {
|
||||||
|
@ -23,16 +76,143 @@ const String& LayoutText::text() const
|
||||||
{
|
{
|
||||||
static String one_space = " ";
|
static String one_space = " ";
|
||||||
if (is_all_whitespace(node().data()))
|
if (is_all_whitespace(node().data()))
|
||||||
|
if (style_properties().string_or_fallback("white-space", "normal") == "normal")
|
||||||
return one_space;
|
return one_space;
|
||||||
return node().data();
|
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()
|
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()
|
void LayoutText::layout()
|
||||||
{
|
{
|
||||||
ASSERT(!has_children());
|
ASSERT(!has_children());
|
||||||
compute_runs();
|
|
||||||
|
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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
#include <LibHTML/DOM/Text.h>
|
#include <LibHTML/DOM/Text.h>
|
||||||
#include <LibHTML/Layout/LayoutNode.h>
|
#include <LibHTML/Layout/LayoutNode.h>
|
||||||
|
|
||||||
|
class Font;
|
||||||
|
|
||||||
class LayoutText : public LayoutNode {
|
class LayoutText : public LayoutNode {
|
||||||
public:
|
public:
|
||||||
LayoutText(const Text&, StyleProperties&&);
|
LayoutText(const Text&, StyleProperties&&);
|
||||||
|
@ -24,7 +26,9 @@ public:
|
||||||
const Vector<Run>& runs() const { return m_runs; }
|
const Vector<Run>& runs() const { return m_runs; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void load_font();
|
||||||
void compute_runs();
|
void compute_runs();
|
||||||
|
|
||||||
Vector<Run> m_runs;
|
Vector<Run> m_runs;
|
||||||
|
RefPtr<Font> m_font;
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,7 +7,7 @@ all: $(LIBRARY) tho
|
||||||
include Makefile.shared
|
include Makefile.shared
|
||||||
|
|
||||||
tho: $(TEST_OBJS) $(LIBRARY)
|
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)
|
$(LIBRARY): $(LIBHTML_OBJS)
|
||||||
@echo "LIB $@"; $(AR) rcs $@ $(LIBHTML_OBJS)
|
@echo "LIB $@"; $(AR) rcs $@ $(LIBHTML_OBJS)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue