ladybird/Userland/Applications/Presenter/SlideObject.cpp

158 lines
6.0 KiB
C++

/*
* Copyright (c) 2022, kleines Filmröllchen <filmroellchen@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "SlideObject.h"
#include "Presentation.h"
#include <AK/JsonObject.h>
#include <AK/URL.h>
#include <LibGUI/PropertyDeserializer.h>
#include <LibGfx/Font/FontStyleMapping.h>
#include <LibGfx/Rect.h>
static ByteString to_css_length(float design_value, Presentation const& presentation)
{
float length_in_vw = design_value / static_cast<float>(presentation.normative_size().width()) * 100.0f;
return ByteString::formatted("{}vw", length_in_vw);
}
ErrorOr<NonnullRefPtr<SlideObject>> SlideObject::parse_slide_object(JsonObject const& slide_object_json, unsigned slide_index)
{
auto frame = slide_object_json.get_u32("frame"sv).value_or(0);
auto maybe_type = slide_object_json.get_byte_string("type"sv);
if (!maybe_type.has_value())
return Error::from_string_view("Slide object must have a type"sv);
auto type = maybe_type.value();
RefPtr<SlideObject> object;
if (type == "text"sv)
object = TRY(try_make_ref_counted<Text>(Index { slide_index, frame }));
else if (type == "image"sv)
object = TRY(try_make_ref_counted<Image>(Index { slide_index, frame }));
else
return Error::from_string_view("Unsupported slide object type"sv);
slide_object_json.for_each_member([&](auto const& key, auto const& value) {
object->set_property(key, value);
});
return object.release_nonnull();
}
void SlideObject::set_property(StringView name, JsonValue value)
{
if (name == "rect"sv)
m_rect = GUI::PropertyDeserializer<Gfx::IntRect> {}(value).release_value_but_fixme_should_propagate_errors();
m_properties.set(name, move(value));
}
void GraphicsObject::set_property(StringView name, JsonValue value)
{
if (name == "color"sv) {
if (auto color = Gfx::Color::from_string(value.as_string()); color.has_value()) {
m_color = color.release_value();
}
}
SlideObject::set_property(name, move(value));
}
void Text::set_property(StringView name, JsonValue value)
{
if (name == "text"sv) {
m_text = value.as_string();
} else if (name == "font"sv) {
m_font_family = value.as_string();
} else if (name == "font-weight"sv) {
m_font_weight = Gfx::name_to_weight(value.as_string());
} else if (name == "font-size"sv) {
m_font_size_in_pt = value.get_float_with_precision_loss().value();
} else if (name == "text-alignment"sv) {
m_text_align = value.as_string();
}
GraphicsObject::set_property(name, move(value));
}
void Image::set_property(StringView name, JsonValue value)
{
if (name == "path"sv) {
m_src = value.as_string();
} else if (name == "scaling-mode"sv) {
if (value.as_string() == "nearest-neighbor"sv)
m_image_rendering = "crisp-edges"sv;
else if (value.as_string() == "smooth-pixels"sv)
m_image_rendering = "pixelated"sv;
}
SlideObject::set_property(name, move(value));
}
ErrorOr<HTMLElement> Text::render(Presentation const& presentation) const
{
HTMLElement div;
div.tag_name = "div"sv;
TRY(div.attributes.try_set("class"sv, ByteString::formatted("frame slide{}-frame{}", m_slide_index, m_frame_index)));
div.style.set("color"sv, m_color.to_byte_string());
div.style.set("font-family"sv, ByteString::formatted("'{}'", m_font_family));
div.style.set("font-size"sv, to_css_length(m_font_size_in_pt * 1.33333333f, presentation));
div.style.set("font-weight"sv, ByteString::number(m_font_weight));
div.style.set("text-align"sv, m_text_align);
div.style.set("white-space"sv, "pre-wrap"sv);
div.style.set("width"sv, to_css_length(m_rect.width(), presentation));
div.style.set("height"sv, to_css_length(m_rect.height(), presentation));
div.style.set("position"sv, "absolute"sv);
div.style.set("left"sv, to_css_length(m_rect.left(), presentation));
div.style.set("top"sv, to_css_length(m_rect.top(), presentation));
div.inner_text = m_text;
return div;
}
ErrorOr<HTMLElement> Image::render(Presentation const& presentation) const
{
HTMLElement img;
img.tag_name = "img"sv;
img.attributes.set("src"sv, URL::create_with_file_scheme(m_src).to_byte_string());
img.style.set("image-rendering"sv, m_image_rendering);
if (m_rect.width() > m_rect.height())
img.style.set("height"sv, "100%"sv);
else
img.style.set("width"sv, "100%"sv);
HTMLElement image_wrapper;
image_wrapper.tag_name = "div"sv;
TRY(image_wrapper.attributes.try_set("class"sv, ByteString::formatted("frame slide{}-frame{}", m_slide_index, m_frame_index)));
image_wrapper.children.append(move(img));
image_wrapper.style.set("position"sv, "absolute"sv);
image_wrapper.style.set("left"sv, to_css_length(m_rect.left(), presentation));
image_wrapper.style.set("top"sv, to_css_length(m_rect.top(), presentation));
image_wrapper.style.set("width"sv, to_css_length(m_rect.width(), presentation));
image_wrapper.style.set("height"sv, to_css_length(m_rect.height(), presentation));
image_wrapper.style.set("text-align"sv, "center"sv);
return image_wrapper;
}
ErrorOr<void> HTMLElement::serialize(StringBuilder& builder) const
{
TRY(builder.try_appendff("<{}", tag_name));
for (auto const& [key, value] : attributes) {
// FIXME: Escape the value string as necessary.
TRY(builder.try_appendff(" {}='{}'", key, value));
}
if (!style.is_empty()) {
TRY(builder.try_append(" style=\""sv));
for (auto const& [key, value] : style) {
// FIXME: Escape the value string as necessary.
TRY(builder.try_appendff(" {}: {};", key, value));
}
TRY(builder.try_append("\""sv));
}
TRY(builder.try_append(">"sv));
if (!inner_text.is_empty())
TRY(builder.try_append(inner_text));
for (auto const& child : children) {
TRY(child.serialize(builder));
}
TRY(builder.try_appendff("</{}>", tag_name));
return {};
}