(This is part 1 in a set of failed experiments trying to generate SVG tweets in Hugo. I eventually abandoned this goal, and generate HTML archives of tweets instead.)
I can create an SVG that works pretty well, but it doesn’t get displayed properly. The browser just cuts it off at some point and doesn’t display the whole lthing.
I tried messing with viewBox
but wasn’t able to solve my problem.
Included here are some things I tried:
A shortcode to generate the SVG
{{/* Given a tweet JSON from scripts/tweet, make an SVG embed of it */}}
{{- $tweetId := .Get 0 -}}
{{- $tweet := index .Site.Data.tweets $tweetId -}}
{{- $tweetUri := printf "https://twitter.com/%s/status/%s" $tweet.username $tweetId -}}
{{- $userUri := printf "https://twitter.com/%s" $tweet.user_displayname -}}
<div style="">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" viewBox="0 0 1000 1000" >
<foreignObject
width="100%" height="100%"
fill="#eade52">
<style>
.tweetsvg{
clear:none;
}
a.tweetsvg {
color: rgb(27, 149, 224);
text-decoration:none;
}
blockquote.tweetsvg {
margin:0;
background-color:#fefefe;
border-radius:2%;
border-style:solid;
border-width:.1em;
border-color:#ddd;
padding:1em;
font-family:sans-serif;
width:17rem
}
.avatar-tweetsvg {
float:left;
width:4rem;
height:4rem;
border-radius:50%;
margin-right:.5rem;
margin-bottom:.5rem;
border-style: solid;
border-width:.1em;
border-color:#ddd;
}
h1.tweetsvg {
margin:0;
font-size:1rem;
text-decoration:none;
color:#000;
}
h2.tweetsvg {
margin:0;
font-size:1rem;
font-weight:normal;
text-decoration:none;
color:rgb(101, 119, 134);
}
p.tweetsvg {
font-size:1rem;
clear:both;
}
hr.tweetsvg {
color:#ddd;
}
time.tweetsvg {
font-size:.9rem;
font-family:sans-serif;
margin:0;
padding-bottom:1rem;
color:rgb(101, 119, 134);
text-decoration:none;
}
.media-tweetsvg {
max-width: 100%;
border-radius:2%;
border-radius: 2%;
border-style: solid;
border-width: .1em;
border-color: #ddd;
}
ol.media-tweetsvg-list {
list-style-type: none;
padding: 0;
}
ol.media-tweetsvg-list li {
display: inline-block;
}
ol.media-tweetsvg-list-1 li {
max-width: 100%;
}
ol.media-tweetsvg-list-2 li {
max-width: 45%;
}
ol.media-tweetsvg-list-3 li {
max-width: 30%;
}
ol.media-tweetsvg-list-4 li {
max-width: 20%;
}
</style>
<blockquote class="tweetsvg" xmlns="http://www.w3.org/1999/xhtml">
<a class="tweetsvg" href="{{ $userUri }}">
<img class="avatar-tweetsvg" alt="" src="data:image/jpeg;base64,{{ $tweet.user_pfp }}" />
<h1 class="tweetsvg">{{ $tweet.user_displayname }}</h1>
<h2 class="tweetsvg">@{{ $tweet.username }}</h2>
</a>
<p class="tweetsvg">{{ $tweet.full_html | safeHTML }}</p>
<ol class="media-tweetsvg-list media-tweetsvg-list-{{ len $tweet.media }}">
{{- range $tweet.media -}}
<li>
<a href="{{ .url }}">
<img class="media-tweetsvg" width="{{ .width }}" src="data:image/jpeg;base64,{{ .data }}" alt="{{ .alttext }}" />
</a>
</li>
{{- end -}}
</ol>
<a class="tweetsvg" href="{{ $tweetUri }}">
<time class="tweetsvg" datetime="{{ $tweet.date }}">{{ $tweet.date_original_format }}</time>
</a>
</blockquote>
</foreignObject>
</svg>
</div>
A layout section to generate SVGs as a separate output format
This requires a section in the config:
outputFormats:
# ... snip ...
SVG:
mediatype: image/svg
suffix: svg
isPlainText: true
notAlternative: true
# ... snip ...
And it requires adding tweets as metadata in (otherwise empty) markdown files.
I created a new section called svgtweet
(that is, a directory content/svgtweet/
),
with its section index (content/svgtweet/_index.md
) containing this frontmatter:
---
cascade:
outputs:
- SVG
- HTML
---
That section index was rendered via the layout file themes/micahrl/layouts/svgtweet/section.html
,
which just listed the HTML page for each post in the section:
{{ partial "header.html" . }}
<section id=content>
{{ .Content }}
<ul class=posts_listing>
{{ range .Data.Pages.ByPublishDate.Reverse }}
<li><h3><a href="{{ .Permalink }}">Tweet ID {{ .Params.tweet.id }}</a></h3>
<p class="posts-listing-desc">{{ .Params.tweet.full_text }}</p>
<div id=date>
<time>{{ .Params.tweet.date }}</time>
</div>
</li>
{{ end }}
</ul>
</section>
{{ partial "taxonomyList.html" . }}
{{ partial "footer.html" . }}
Tweets were saved to JSON files,
and the JSON placed in front matter as tweet:
, with the path
content/svgtweet/<tweetID>.md
,
e.g. content/svgtweet/1401685221947420675.md
:
---
"tweet": {
"id": "1401685221947420675",
"date": "2021-06-06T23:39:33+00:00",
"date_original_format": "Sun Jun 06 23:39:33 +0000 2021",
"full_text": "Holy shit, that\u2019s amazing. Pins is a truly great app - I started working on a Pinboard app something like 9 months ago (because alternatives were hot garbage) and have abandoned it completely because of how great Pins is. I can\u2019t believe something this good is only 8 months old. https://t.co/PIhJp28csk",
"full_html": "Holy shit, that\u2019s amazing. Pins is a truly great app - I started working on a Pinboard app something like 9 months ago (because alternatives were hot garbage) and have abandoned it completely because of how great Pins is. I can\u2019t believe something this good is only 8 months old. <a href=\"https://twitter.com/GetPinsApp/status/1401634722212630533\">twitter.com/GetPinsApp/sta\u2026</a>",
"media": [],
"entities": {
"hashtags": [],
"symbols": [],
"user_mentions": [],
"urls": [
{
"url": "https://t.co/PIhJp28csk",
"expanded_url": "https://twitter.com/GetPinsApp/status/1401634722212630533",
"display_url": "twitter.com/GetPinsApp/sta\u2026",
"indices": [
280,
303
]
}
]
},
"username": "mrled",
"user_displayname": "Micah R Ledbetter / m \u0279\u0329 \u02c8 l e d /",
"user_pfp": "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"
}
---
Finally, that was rendered via an SVG layout file themes/micahrl/layouts/svgtweet/single.svg
:
{{- $tweet := .Params.tweet -}}
{{- $tweetUri := printf "https://twitter.com/%s/status/%s" $tweet.username $tweet.id -}}
{{- $userUri := printf "https://twitter.com/%s" $tweet.user_displayname -}}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" >
<foreignObject x="0" y="0"
width="20em" height="100%"
fill="#eade52">
<style>
.tweetsvg{clear:none;}
a.tweetsvg{color: rgb(27, 149, 224); text-decoration:none;}
blockquote.tweetsvg{margin:0; background-color:#fefefe; border-radius:2%; border-style:solid; border-width:.1em; border-color:#ddd; padding:1em; font-family:sans-serif; width:17rem}
.avatar-tweetsvg{float:left; width:4rem; height:4rem; border-radius:50%;margin-right:.5rem;;margin-bottom:.5rem;border-style: solid; border-width:.1em; border-color:#ddd;}
h1.tweetsvg{margin:0;font-size:1rem;text-decoration:none;color:#000;}
h2.tweetsvg{margin:0;font-size:1rem;font-weight:normal;text-decoration:none;color:rgb(101, 119, 134);}
p.tweetsvg{font-size:1rem; clear:both;}
hr.tweetsvg{color:#ddd;}
time.tweetsvg{font-size:.9rem; font-family:sans-serif; margin:0; padding-bottom:1rem;color:rgb(101, 119, 134);text-decoration:none;}
.media-tweetsvg {max-width: 100%; border-radius:2%; border-radius: 2%; border-style: solid; border-width: .1em; border-color: #ddd;}
ol.media-tweetsvg-list {list-style-type: none; padding: 0;}
ol.media-tweetsvg-list li {display: inline-block;}
ol.media-tweetsvg-list-1 li {max-width: 100%;}
ol.media-tweetsvg-list-2 li {max-width: 45%;}
ol.media-tweetsvg-list-3 li {max-width: 30%;}
ol.media-tweetsvg-list-4 li {max-width: 20%;}
</style>
<blockquote class="tweetsvg" xmlns="http://www.w3.org/1999/xhtml">
<a class="tweetsvg" href="{{ $userUri }}">
<img class="avatar-tweetsvg" alt="" src="data:image/jpeg;base64,{{ $tweet.user_pfp }}" />
<h1 class="tweetsvg">{{ $tweet.user_displayname }}</h1>
<h2 class="tweetsvg">@{{ $tweet.username }}</h2>
</a>
<p class="tweetsvg">{{ $tweet.full_html }}</p>
<ol class="media-tweetsvg-list media-tweetsvg-list-{{ len $tweet.media }}">
{{- range $tweet.media -}}
<li>
<a href="{{ .url }}">
<img class="media-tweetsvg" width="{{ .width }}" src="data:image/jpeg;base64,{{ .data }}" alt="{{ .alttext }}" />
</a>
</li>
{{- end -}}
</ol>
<a class="tweetsvg" href="{{ $tweetUri }}">
<time class="tweetsvg" datetime="{{ $tweet.date }}">{{ $tweet.date_original_format }}</time>
</a>
</blockquote>
</foreignObject>
</svg>
That resulted in index.svg
files getting created.
I also made HTML pages that referenced for each one:
{{ partial "header.html" . }}
<section id=content>
<h1>Tweet ID {{ .Params.tweet.id }}</h1>
<a href="index.svg"><img src="index.svg" height="500px" /></a>
</section>
{{ partial "taxonomyList.html" . }}
{{ partial "footer.html" . }}