-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.