Packages

Generate OG images from Lustre elements

Current section

Files

Jump to
og_image src og_image@transform.erl
Raw

src/og_image@transform.erl

-module(og_image@transform).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/og_image/transform.gleam").
-export([node_type/1, extract_styles/1, collect_image_urls/1, to_takumi_json/1, to_takumi_json_with_root/1]).
-export_type([node_type/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type node_type() :: container | text | image | {unsupported, binary()}.
-file("src/og_image/transform.gleam", 20).
?DOC(" Map HTML tag to Takumi node type\n").
-spec node_type(binary()) -> node_type().
node_type(Tag) ->
case Tag of
<<"div"/utf8>> ->
container;
<<"section"/utf8>> ->
container;
<<"header"/utf8>> ->
container;
<<"footer"/utf8>> ->
container;
<<"nav"/utf8>> ->
container;
<<"main"/utf8>> ->
container;
<<"aside"/utf8>> ->
container;
<<"article"/utf8>> ->
container;
<<"span"/utf8>> ->
text;
<<"p"/utf8>> ->
text;
<<"h1"/utf8>> ->
text;
<<"h2"/utf8>> ->
text;
<<"h3"/utf8>> ->
text;
<<"h4"/utf8>> ->
text;
<<"h5"/utf8>> ->
text;
<<"h6"/utf8>> ->
text;
<<"strong"/utf8>> ->
text;
<<"em"/utf8>> ->
text;
<<"a"/utf8>> ->
text;
<<"label"/utf8>> ->
text;
<<"code"/utf8>> ->
text;
<<"pre"/utf8>> ->
text;
<<"img"/utf8>> ->
image;
Other ->
{unsupported, Other}
end.
-file("src/og_image/transform.gleam", 69).
-spec parse_style_string(binary()) -> list({binary(), binary()}).
parse_style_string(S) ->
_pipe = S,
_pipe@1 = gleam@string:split(_pipe, <<";"/utf8>>),
gleam@list:filter_map(
_pipe@1,
fun(Part) ->
Trimmed = gleam@string:trim(Part),
case Trimmed of
<<""/utf8>> ->
{error, nil};
_ ->
case gleam@string:split_once(Trimmed, <<":"/utf8>>) of
{ok, {Property, Value}} ->
{ok,
{gleam@string:trim(Property),
gleam@string:trim(Value)}};
{error, _} ->
{error, nil}
end
end
end
).
-file("src/og_image/transform.gleam", 57).
?DOC(" Extract style tuples from a list of attributes\n").
-spec extract_styles(list(lustre@vdom@vattr:attribute(any()))) -> list({binary(),
binary()}).
extract_styles(Attrs) ->
_pipe = Attrs,
_pipe@1 = gleam@list:filter_map(_pipe, fun(Attr) -> case Attr of
{attribute, _, <<"style"/utf8>>, Style_str} ->
{ok, parse_style_string(Style_str)};
_ ->
{error, nil}
end end),
lists:append(_pipe@1).
-file("src/og_image/transform.gleam", 130).
-spec text_to_json(binary()) -> gleam@json:json().
text_to_json(Content) ->
gleam@json:object(
[{<<"type"/utf8>>, gleam@json:string(<<"text"/utf8>>)},
{<<"text"/utf8>>, gleam@json:string(Content)}]
).
-file("src/og_image/transform.gleam", 147).
?DOC(" Convert an SVG element to an image node with the SVG content as src\n").
-spec svg_to_image_json(
lustre@vdom@vnode:element(PPH),
list(lustre@vdom@vattr:attribute(PPH))
) -> gleam@json:json().
svg_to_image_json(Svg_element, Attrs) ->
Styles = extract_styles(Attrs),
Style_json = og_image@style:to_json(Styles),
Svg_string = lustre@element:to_string(Svg_element),
gleam@json:object(
[{<<"type"/utf8>>, gleam@json:string(<<"image"/utf8>>)},
{<<"src"/utf8>>, gleam@json:string(Svg_string)},
{<<"style"/utf8>>, Style_json}]
).
-file("src/og_image/transform.gleam", 165).
-spec extract_src(list(lustre@vdom@vattr:attribute(any()))) -> binary().
extract_src(Attrs) ->
_pipe = Attrs,
_pipe@1 = gleam@list:find_map(_pipe, fun(Attr) -> case Attr of
{attribute, _, <<"src"/utf8>>, Src} ->
{ok, Src};
_ ->
{error, nil}
end end),
gleam@result:unwrap(_pipe@1, <<""/utf8>>).
-file("src/og_image/transform.gleam", 134).
-spec image_to_json(list(lustre@vdom@vattr:attribute(any()))) -> gleam@json:json().
image_to_json(Attrs) ->
Styles = extract_styles(Attrs),
Style_json = og_image@style:to_json(Styles),
Src = extract_src(Attrs),
gleam@json:object(
[{<<"type"/utf8>>, gleam@json:string(<<"image"/utf8>>)},
{<<"src"/utf8>>, gleam@json:string(Src)},
{<<"style"/utf8>>, Style_json}]
).
-file("src/og_image/transform.gleam", 258).
-spec collect_text_content(list(lustre@vdom@vnode:element(any()))) -> binary().
collect_text_content(Children) ->
_pipe = Children,
_pipe@1 = gleam@list:map(_pipe, fun(Child) -> case Child of
{text, _, _, _, Content} ->
Content;
{element, _, _, _, _, _, _, Nested, _, _, _} ->
collect_text_content(Nested);
{fragment, _, _, _, Nested@1, _} ->
collect_text_content(Nested@1);
_ ->
<<""/utf8>>
end end),
erlang:list_to_binary(_pipe@1).
-file("src/og_image/transform.gleam", 278).
-spec collect_urls_recursive(lustre@vdom@vnode:element(any()), list(binary())) -> list(binary()).
collect_urls_recursive(El, Acc) ->
case El of
{element, _, _, _, _, <<"img"/utf8>>, Attrs, _, _, _, _} ->
Src = extract_src(Attrs),
case gleam_stdlib:string_starts_with(Src, <<"http://"/utf8>>) orelse gleam_stdlib:string_starts_with(
Src,
<<"https://"/utf8>>
) of
true ->
[Src | Acc];
false ->
Acc
end;
{element, _, _, _, _, _, _, Children, _, _, _} ->
gleam@list:fold(
Children,
Acc,
fun(Acc@1, Child) -> collect_urls_recursive(Child, Acc@1) end
);
{fragment, _, _, _, Children@1, _} ->
gleam@list:fold(
Children@1,
Acc,
fun(Acc@2, Child@1) ->
collect_urls_recursive(Child@1, Acc@2)
end
);
_ ->
Acc
end.
-file("src/og_image/transform.gleam", 273).
?DOC(
" Collect all image URLs from a Lustre element tree\n"
" Returns URLs that start with http:// or https://\n"
).
-spec collect_image_urls(lustre@vdom@vnode:element(any())) -> list(binary()).
collect_image_urls(El) ->
_pipe = collect_urls_recursive(El, []),
gleam@list:unique(_pipe).
-file("src/og_image/transform.gleam", 176).
-spec element_to_json(
binary(),
list(lustre@vdom@vattr:attribute(PPO)),
list(lustre@vdom@vnode:element(PPO))
) -> gleam@json:json().
element_to_json(Tag, Attrs, Children) ->
Styles = extract_styles(Attrs),
case node_type(Tag) of
text ->
Parent_styles = [{<<"display"/utf8>>, <<"block"/utf8>>} | Styles],
Style_json = og_image@style:to_json(Parent_styles),
gleam@json:object(
[{<<"type"/utf8>>, gleam@json:string(<<"container"/utf8>>)},
{<<"style"/utf8>>, Style_json},
{<<"children"/utf8>>,
gleam@json:array(Children, fun child_to_inline_json/1)}]
);
_ ->
Style_json@1 = og_image@style:to_json(Styles),
gleam@json:object(
[{<<"type"/utf8>>, gleam@json:string(<<"container"/utf8>>)},
{<<"style"/utf8>>, Style_json@1},
{<<"children"/utf8>>,
gleam@json:array(Children, fun to_takumi_json/1)}]
)
end.
-file("src/og_image/transform.gleam", 103).
?DOC(" Transform a Lustre element to Takumi JSON format\n").
-spec to_takumi_json(lustre@vdom@vnode:element(any())) -> gleam@json:json().
to_takumi_json(El) ->
case El of
{text, _, _, _, Content} ->
text_to_json(Content);
{element, _, _, _, _, <<"img"/utf8>>, Attrs, _, _, _, _} ->
image_to_json(Attrs);
{element, _, _, _, _, <<"svg"/utf8>>, Attrs@1, _, _, _, _} ->
svg_to_image_json(El, Attrs@1);
{element, _, _, _, _, Tag, Attrs@2, Children, _, _, _} ->
element_to_json(Tag, Attrs@2, Children);
{fragment, _, _, _, Children@1, _} ->
gleam@json:object(
[{<<"type"/utf8>>, gleam@json:string(<<"container"/utf8>>)},
{<<"style"/utf8>>, gleam@json:object([])},
{<<"children"/utf8>>,
gleam@json:array(Children@1, fun to_takumi_json/1)}]
);
{unsafe_inner_html, _, _, _, _, _, _, _} ->
gleam@json:object(
[{<<"type"/utf8>>, gleam@json:string(<<"text"/utf8>>)},
{<<"text"/utf8>>, gleam@json:string(<<""/utf8>>)}]
)
end.
-file("src/og_image/transform.gleam", 88).
?DOC(
" Transform a Lustre element to Takumi JSON format, wrapped in a root container\n"
" that fills the viewport (width: 100%, height: 100%)\n"
).
-spec to_takumi_json_with_root(lustre@vdom@vnode:element(any())) -> gleam@json:json().
to_takumi_json_with_root(El) ->
gleam@json:object(
[{<<"type"/utf8>>, gleam@json:string(<<"container"/utf8>>)},
{<<"style"/utf8>>,
gleam@json:object(
[{<<"width"/utf8>>, gleam@json:string(<<"100%"/utf8>>)},
{<<"height"/utf8>>, gleam@json:string(<<"100%"/utf8>>)}]
)},
{<<"children"/utf8>>, gleam@json:array([El], fun to_takumi_json/1)}]
).
-file("src/og_image/transform.gleam", 212).
?DOC(
" Convert a child element to inline text JSON\n"
" Plain text becomes inline text nodes, elements get their styles preserved\n"
).
-spec child_to_inline_json(lustre@vdom@vnode:element(any())) -> gleam@json:json().
child_to_inline_json(El) ->
case El of
{text, _, _, _, Content} ->
gleam@json:object(
[{<<"type"/utf8>>, gleam@json:string(<<"text"/utf8>>)},
{<<"text"/utf8>>, gleam@json:string(Content)},
{<<"style"/utf8>>,
og_image@style:to_json(
[{<<"display"/utf8>>, <<"inline"/utf8>>}]
)}]
);
{element, _, _, _, _, Tag, Attrs, Children, _, _, _} ->
case node_type(Tag) of
text ->
Styles = extract_styles(Attrs),
Inline_styles = [{<<"display"/utf8>>, <<"inline"/utf8>>} |
Styles],
Text_content = collect_text_content(Children),
gleam@json:object(
[{<<"type"/utf8>>, gleam@json:string(<<"text"/utf8>>)},
{<<"text"/utf8>>, gleam@json:string(Text_content)},
{<<"style"/utf8>>,
og_image@style:to_json(Inline_styles)}]
);
_ ->
to_takumi_json(El)
end;
{fragment, _, _, _, Children@1, _} ->
gleam@json:object(
[{<<"type"/utf8>>, gleam@json:string(<<"container"/utf8>>)},
{<<"style"/utf8>>,
og_image@style:to_json(
[{<<"display"/utf8>>, <<"inline"/utf8>>}]
)},
{<<"children"/utf8>>,
gleam@json:array(Children@1, fun child_to_inline_json/1)}]
);
_ ->
gleam@json:object(
[{<<"type"/utf8>>, gleam@json:string(<<"text"/utf8>>)},
{<<"text"/utf8>>, gleam@json:string(<<""/utf8>>)},
{<<"style"/utf8>>,
og_image@style:to_json(
[{<<"display"/utf8>>, <<"inline"/utf8>>}]
)}]
)
end.