Initial release
This commit is contained in:
commit
32a4e658f0
4
.dockerignore
Normal file
4
.dockerignore
Normal file
@ -0,0 +1,4 @@
|
||||
Dockerfile
|
||||
README.md
|
||||
Makefile
|
||||
logo.png
|
14
Dockerfile
Normal file
14
Dockerfile
Normal file
@ -0,0 +1,14 @@
|
||||
FROM golang:1.16-alpine AS builder
|
||||
RUN apk update && apk add --no-cache git && apk add -U --no-cache ca-certificates
|
||||
WORKDIR /app/
|
||||
ADD go.mod go.sum ./
|
||||
RUN go mod download
|
||||
ADD . .
|
||||
RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-w -s" -o gopengraph-image-gen .
|
||||
|
||||
FROM westy92/headless-chrome-alpine
|
||||
WORKDIR /app/
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=builder /app/gopengraph-image-gen /app/gopengraph-image-gen
|
||||
RUN mkdir -p /tmp
|
||||
ENTRYPOINT ["/app/gopengraph-image-gen"]
|
61
Makefile
Normal file
61
Makefile
Normal file
@ -0,0 +1,61 @@
|
||||
.DEFAULT_GOAL := default
|
||||
|
||||
IMAGE ?= docker.cluster.fun/averagemarcus/opengraph-image-gen:latest
|
||||
|
||||
.PHONY: test # Run all tests, linting and format checks
|
||||
test: lint check-format run-tests
|
||||
|
||||
.PHONY: lint # Perform lint checks against code
|
||||
lint:
|
||||
@go vet && golint -set_exit_status ./...
|
||||
|
||||
.PHONY: check-format # Checks code formatting and returns a non-zero exit code if formatting errors found
|
||||
check-format:
|
||||
@gofmt -e -l .
|
||||
|
||||
.PHONY: format # Performs automatic format fixes on all code
|
||||
format:
|
||||
@gofmt -s -w .
|
||||
|
||||
.PHONY: run-tests # Runs all tests
|
||||
run-tests:
|
||||
@echo "⚠️ 'run-tests' unimplemented"
|
||||
|
||||
.PHONY: fetch-deps # Fetch all project dependencies
|
||||
fetch-deps:
|
||||
@go mod tidy
|
||||
|
||||
.PHONY: build # Build the project
|
||||
build: lint check-format fetch-deps
|
||||
@go build -o opengraph-image-gen main.go
|
||||
|
||||
.PHONY: docker-build # Build the docker image
|
||||
docker-build:
|
||||
@docker build -t $(IMAGE) .
|
||||
|
||||
.PHONY: docker-publish # Push the docker image to the remote registry
|
||||
docker-publish:
|
||||
@docker push $(IMAGE)
|
||||
|
||||
.PHONY: run # Run the application
|
||||
run:
|
||||
@go run .
|
||||
|
||||
.PHONY: ci # Perform CI specific tasks to perform on a pull request
|
||||
ci:
|
||||
@echo "⚠️ 'ci' unimplemented"
|
||||
|
||||
.PHONY: release # Release the latest version of the application
|
||||
release:
|
||||
@kubectl --namespace opengraph-image-gen set image deployment opengraph-image-gen web=docker.cluster.fun/averagemarcus/opengraph-image-gen:$(SHA)
|
||||
|
||||
.PHONY: help # Show this list of commands
|
||||
help:
|
||||
@echo "opengraph-image-gen"
|
||||
@echo "Usage: make [target]"
|
||||
@echo ""
|
||||
@echo "target description" | expand -t20
|
||||
@echo "-----------------------------------"
|
||||
@grep '^.PHONY: .* #' Makefile | sed 's/\.PHONY: \(.*\) # \(.*\)/\1 \2/' | expand -t20
|
||||
|
||||
default: test build
|
47
README.md
Normal file
47
README.md
Normal file
@ -0,0 +1,47 @@
|
||||
![OpenGraph-Image-Gen](logo.png)
|
||||
|
||||
Dynamically generate OpenGraph social share images
|
||||
|
||||
Available at https://opengraph.cluster.fun/
|
||||
|
||||
## Example
|
||||
|
||||
```html
|
||||
<img src="https://opengraph.cluster.fun/opengraph/?siteTitle=Marcus%20Noble&title=OpenGraph-Image-Gen&tags=opengraph%2Cgolang%2Ctwitter%2Cshare%2Csocial&image=https%3A%2F%2Fmarcusnoble.co.uk%2Fimages%2Fmarcus.jpg&twitter=Marcus_Noble_&github=AverageMarcus&website=www.MarcusNoble.co.uk&bgColor=%23ffffff&fgColor=%23263943" />
|
||||
```
|
||||
|
||||
![Preview Image](https://opengraph.cluster.fun/opengraph/?siteTitle=Marcus%20Noble&title=OpenGraph-Image-Gen&tags=opengraph%2Cgolang%2Ctwitter%2Cshare%2Csocial&image=https%3A%2F%2Fmarcusnoble.co.uk%2Fimages%2Fmarcus.jpg&twitter=Marcus_Noble_&github=AverageMarcus&website=www.MarcusNoble.co.uk&bgColor=%23ffffff&fgColor=%23263943)
|
||||
|
||||
## Features
|
||||
|
||||
* Dynamically generate a PNG image for use as an OpenGraph share image
|
||||
* Ideally sized for Twitter previews
|
||||
* All text elements configurable
|
||||
* Configurable colours
|
||||
* All text fields optional
|
||||
|
||||
## Building from source
|
||||
|
||||
With Docker:
|
||||
|
||||
```sh
|
||||
make docker-build
|
||||
```
|
||||
|
||||
Standalone:
|
||||
|
||||
```sh
|
||||
make build
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
If you find a bug or have an idea for a new feature please [raise an issue](issues/new) to discuss it.
|
||||
|
||||
Pull requests are welcomed but please try and follow similar code style as the rest of the project and ensure all tests and code checkers are passing.
|
||||
|
||||
Thank you 💛
|
||||
|
||||
## License
|
||||
|
||||
See [LICENSE](LICENSE)
|
12
go.mod
Normal file
12
go.mod
Normal file
@ -0,0 +1,12 @@
|
||||
module opengraph-image-gen
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/canhlinh/svg2png v0.0.0-20201124065332-6ba87c82371f
|
||||
github.com/gofiber/fiber/v2 v2.9.0
|
||||
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/stretchr/testify v1.7.0 // indirect
|
||||
)
|
48
go.sum
Normal file
48
go.sum
Normal file
@ -0,0 +1,48 @@
|
||||
github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc=
|
||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/canhlinh/svg2png v0.0.0-20201124065332-6ba87c82371f h1:Km7aXA1/+77OZ6mq8VV/QJ9nP6y4OUwxj+GQ5nW7X5Y=
|
||||
github.com/canhlinh/svg2png v0.0.0-20201124065332-6ba87c82371f/go.mod h1:u13M4umOwLc1fTX2itKxGff/6S+YWc7l15kJGtm2IJY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gofiber/fiber/v2 v2.9.0 h1:sZsTKlbyGGZ0UdTUn3ItQv5J9FTQUc4J3OS+03lE5m0=
|
||||
github.com/gofiber/fiber/v2 v2.9.0/go.mod h1:Ah3IJikrKNRepl/HuVawppS25X7FWohwfCSRn7kJG28=
|
||||
github.com/klauspost/compress v1.11.8/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.11.13 h1:eSvu8Tmq6j2psUJqJrLcWH6K3w5Dwc+qipbaA6eVEN4=
|
||||
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2 h1:YocNLcTBdEdvY3iDK6jfWXvEaM5OCKkjxPKoJRdB3Gg=
|
||||
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.23.0 h1:0ufwSD9BhWa6f8HWdmdq4FHQ23peRo3Ng/Qs8m5NcFs=
|
||||
github.com/valyala/fasthttp v1.23.0/go.mod h1:0mw2RjXGOzxf4NL2jni3gUQ7LfjjUSiG5sskOUUSEpU=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a h1:0R4NLDRDZX6JcmhJgXi5E4b8Wg84ihbmUKp/GvSPEzc=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20210226101413-39120d07d75e/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073 h1:8qxJSnu+7dRq6upnbntrmriWByIakBuct5OM/MdQC1M=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
146
index.html
Normal file
146
index.html
Normal file
@ -0,0 +1,146 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>OpenGraph-Image-Gen</title>
|
||||
|
||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 256 245' style='height: 50px; fill: %2371cad1; '%3E%3Cpath d='M253.5 121.1a9.3 9.3 0 00-14.6-4.3l-42.4-12 30.4-10a9.3 9.3 0 10-3-9.3l-30.8 10 26.7-34.9a9.2 9.2 0 0010.2-12 9.3 9.3 0 00-17.7 5.7l-29.8 38.9a11.2 11.2 0 00-11.1 9.4l-33.5 10.9a13 13 0 00-5-3.7V74.6a11.2 11.2 0 005.6-14l26.8-39.9h.8a9.3 9.3 0 10-8.6-5.8l-24.6 36.7V19.4a9.3 9.3 0 10-9.8 0v32.5l-25-36.3A9.2 9.2 0 0090 2.2a9.3 9.3 0 000 18.6l27.5 40a11.2 11.2 0 005.7 13.8v35.2c-2 .8-3.7 2-5 3.7l-33.5-10.9a11.2 11.2 0 00-11.3-9.4L43.3 55l.4-.8a9.3 9.3 0 10-8.3 6.4l27.2 34.7-30.5-9.9a9.3 9.3 0 10-3 9.3l30.7 10-42 12.6c-1-1-2.2-1.8-3.7-2.2a9.3 9.3 0 106 11.7L66.4 113a11.2 11.2 0 0015.5-1.1l33.2 10.8c.1 2.1.8 4.1 1.9 5.9l-20.8 28.6a11.2 11.2 0 00-12.4 7.7l-45.4 16.7a9.3 9.3 0 102.9 9.3l41.4-15.2-18.9 26a9.3 9.3 0 108 5.8l19-26.2-1.1 43.9a9.2 9.2 0 00-1.2 15.7 9.3 9.3 0 0011-15l1.1-48.2a11.2 11.2 0 003.7-15l20.6-28.4a13 13 0 006.2 0l20.7 28.5a11.2 11.2 0 003.1 14.5l2.3 48.2-.7.4a9.3 9.3 0 1010.5-.3l-2.1-44.8 19.4 26.7a9.3 9.3 0 107.9-5.8l-19-26 41.4 14.4c.2 1.4.7 2.7 1.6 4a9.3 9.3 0 102-13l-45.9-16a11.2 11.2 0 00-12.5-7.9L139 128.7c1-1.8 1.8-3.8 2-6L174 112a11.2 11.2 0 0015.7 1l45.9 13 .2.9a9.3 9.3 0 1017.6-5.8z'/%3E%3C/svg%3E">
|
||||
|
||||
<meta property="og:title" content="OpenGraph-Image-Gen">
|
||||
<meta property="og:site_name" content="OpenGraph-Image-Gen">
|
||||
<meta property="og:url" content="https://opengraph.cluster.fun">
|
||||
<meta property="og:description" content="Dynamically generate OpenGraph social share images">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://opengraph.cluster.fun/opengraph/?siteTitle=Marcus%20Noble&title=OpenGraph-Image-Gen&tags=opengraph%2Cgolang%2Ctwitter%2Cshare%2Csocial&image=https%3A%2F%2Fmarcusnoble.co.uk%2Fimages%2Fmarcus.jpg&twitter=Marcus_Noble_&github=AverageMarcus&website=www.MarcusNoble.co.uk">
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:creator" content="@Marcus_Noble_" />
|
||||
|
||||
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.css">
|
||||
<link rel="stylesheet" href="https://githubraw.com/AverageMarcus/milligram/master/dist/milligram.min.css">
|
||||
<style>
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
textarea {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
fieldset label {
|
||||
width: 49%;
|
||||
padding: 0 5px;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1 class="heading-fancy">
|
||||
OpenGraph-Image-Gen
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 256 245" style="height: 50px; fill: #71cad1; border: 3px solid #ee70a8; border-radius: 100px;"><path d="M253.5 121.1a9.3 9.3 0 00-14.6-4.3l-42.4-12 30.4-10a9.3 9.3 0 10-3-9.3l-30.8 10 26.7-34.9a9.2 9.2 0 0010.2-12 9.3 9.3 0 00-17.7 5.7l-29.8 38.9a11.2 11.2 0 00-11.1 9.4l-33.5 10.9a13 13 0 00-5-3.7V74.6a11.2 11.2 0 005.6-14l26.8-39.9h.8a9.3 9.3 0 10-8.6-5.8l-24.6 36.7V19.4a9.3 9.3 0 10-9.8 0v32.5l-25-36.3A9.2 9.2 0 0090 2.2a9.3 9.3 0 000 18.6l27.5 40a11.2 11.2 0 005.7 13.8v35.2c-2 .8-3.7 2-5 3.7l-33.5-10.9a11.2 11.2 0 00-11.3-9.4L43.3 55l.4-.8a9.3 9.3 0 10-8.3 6.4l27.2 34.7-30.5-9.9a9.3 9.3 0 10-3 9.3l30.7 10-42 12.6c-1-1-2.2-1.8-3.7-2.2a9.3 9.3 0 106 11.7L66.4 113a11.2 11.2 0 0015.5-1.1l33.2 10.8c.1 2.1.8 4.1 1.9 5.9l-20.8 28.6a11.2 11.2 0 00-12.4 7.7l-45.4 16.7a9.3 9.3 0 102.9 9.3l41.4-15.2-18.9 26a9.3 9.3 0 108 5.8l19-26.2-1.1 43.9a9.2 9.2 0 00-1.2 15.7 9.3 9.3 0 0011-15l1.1-48.2a11.2 11.2 0 003.7-15l20.6-28.4a13 13 0 006.2 0l20.7 28.5a11.2 11.2 0 003.1 14.5l2.3 48.2-.7.4a9.3 9.3 0 1010.5-.3l-2.1-44.8 19.4 26.7a9.3 9.3 0 107.9-5.8l-19-26 41.4 14.4c.2 1.4.7 2.7 1.6 4a9.3 9.3 0 102-13l-45.9-16a11.2 11.2 0 00-12.5-7.9L139 128.7c1-1.8 1.8-3.8 2-6L174 112a11.2 11.2 0 0015.7 1l45.9 13 .2.9a9.3 9.3 0 1017.6-5.8z"/></svg>
|
||||
</h1>
|
||||
|
||||
<blockquote>
|
||||
Dynamically generate OpenGraph social share images
|
||||
</blockquote>
|
||||
|
||||
<div class="row">
|
||||
<form class="column column-80 column-offset-10">
|
||||
<fieldset>
|
||||
<label>Site Title: <input id="siteTitle" type="text" value="Example" /></label>
|
||||
<label>Heading: <input id="title" type="text" value="Heading" /></label>
|
||||
<label>Tags: <input id="tags" type="text" value="example,sample,foo,bar" /></label>
|
||||
<label>Image: <input id="image" type="text" value="https://marcusnoble.co.uk/images/marcus.jpg" /></label>
|
||||
<label>Twitter Handle: <input id="twitter" type="text" value="Marcus_Noble_" /></label>
|
||||
<label>GitHub Username: <input id="github" type="text" value="AverageMarcus" /></label>
|
||||
<label>Website: <input id="website" type="text" value="www.MarcusNoble.co.uk" /></label>
|
||||
<br />
|
||||
<label>Background Colour: <input id="bgColor" type="color" value="#FFFFFF" /></label>
|
||||
<label>Foreground Colour: <input id="fgColor" type="color" value="#263943" /></label>
|
||||
</fieldset>
|
||||
|
||||
<p>All values are optional and their elements can be hidden from the image by leaving the value blank.</p>
|
||||
|
||||
<figure class="text-center">
|
||||
<img id="exampleImage" src="/opengraph" />
|
||||
<figcaption>Live Preview</figcaption>
|
||||
</figure>
|
||||
|
||||
<label>
|
||||
URL:
|
||||
<input type="text" id="imageURL" readonly />
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Source code available on <a href="https://github.com/AverageMarcus/OpenGraph-Image-Gen" target="_blank" rel="noopener noreferrer">GitHub</a>, <a href="https://gitlab.com/AverageMarcus/OpenGraph-Image-Gen" target="_blank" rel="noopener noreferrer">GitLab</a>, <a href="https://bitbucket.org/AverageMarcus/OpenGraph-Image-Gen/" target="_blank" rel="noopener noreferrer">Bitbucket</a> & <a href="https://git.cluster.fun/AverageMarcus/OpenGraph-Image-Gen" target="_blank" rel="noopener noreferrer">my own Gitea server</a>.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="column column-60 column-offset-20">
|
||||
<footer>
|
||||
Made with
|
||||
<svg height="20" class="fill-primary" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 449.3 449.3" xmlns:xlink="http://www.w3.org/1999/xlink"><title>love</title><g><path d="M0 162.7c1.5-7.7 2.7-15.4 4.5-23A125.5 125.5 0 0132 88a136.3 136.3 0 0162.7-40.6c8.3-2.9 17.7-3.2 26.6-3.7a134 134 0 0155.6 6.6c14.9 5.7 30 11 41 23.6 17-20 36.4-36.3 60-46.4 12-5.2 25.7-6.9 38.7-9.4a79.4 79.4 0 0140.3 3.2 96.4 96.4 0 0143.2 26 209.8 209.8 0 0137.8 55.4 133.2 133.2 0 0111 65.7c-3.2 42.2-21 79-41.5 114.8a431.2 431.2 0 01-47.6 64.3c-19.6 23-39.7 45.7-59.6 68.5-3.7 4.3-7.2 9-11.7 12.4-7.3 5.4-15.9 4.9-23.8 1.5-21.9-9.2-43.8-18.5-65.3-28.5a520.1 520.1 0 01-98-58.7c-28.2-21.5-55.5-44.3-74-75.3a183.8 183.8 0 01-25-61.4c-1-6.2-1.6-12.6-2.4-18.8v-24.5zM138 281l2.5.1c0 2.1.3 4.3 0 6.3l-9 55.5c-.3 1.6 0 4.2 1 5 5.8 4.1 11.8 7.8 17.9 11.8l11-59.7 2.5.3c3.7 21.6-6.3 42-7.6 63.5l19.4 9.6c.5-18 2.2-35 6.9-51.6 1.3 4 1.5 8 1.2 12-.7 11.2-1 22.5-2.6 33.7-1 7.3 1.9 10.6 8 13.3 27 11.7 54.1 23.5 81 35.7 5.5 2.5 8.8 1.5 12.6-2.9 12.5-14.4 25.7-28.1 38-42.7 17.2-20.2 34.5-40.5 50.5-61.7 29.5-39.2 52.7-81.7 61-131 3.6-21.7 2-43-5.6-63.5a176 176 0 00-33.9-54.5 84.8 84.8 0 00-38-26 91.2 91.2 0 00-44.2-2.5c-13 2-25.6 5.1-37.2 12.3a208.6 208.6 0 00-45.9 37c-5.7 6.3-7.8 6.4-15 1.4-5.7-4-11-8.8-17.2-11.9-15.1-7.5-30.7-14.4-48-14.9-13.7-.4-28-2-41.2 1-36 8.4-62.7 30-80.3 62.8a111 111 0 00-11.7 56.6c.3 10 1.4 20 2.2 29.9 7-18.9 11-38.3 19.7-56.3.6 3 .6 6 0 8.7l-14.5 53.2c-.7 2.6-2 5.7-1.2 8 2.7 8.4 6.2 16.6 9.4 24.8 0-20 13.7-63.9 21.8-66.8 0 .6.3 1.1.2 1.6a11936 11936 0 01-17 64c-.3 1.4-1.6 2.6-2.9 4.5l9.7 17a573 573 0 0120-67.3l2.9.7c-1 5-1.5 10-2.8 14.9-4.7 17.2-9.6 34.3-14.2 51.5-.7 2.5-1.5 6-.3 7.8 3.6 5.6 8 10.7 12.4 16.1 1.8-8.6 3.1-16.6 5.2-24.4 3.1-11.5 6.6-22.9 10.2-34.3.8-2.6 2.7-5 4-7.4l2 .8c-.1 1.6-.1 3.4-.5 5l-12.2 47.4c-4.6 17.9-4.3 18.9 7.8 29.4 2.2 1.8 4.5 3.5 7 5.3 3.7-31 12.5-64.7 18.4-68-.3 3.5-.3 6.6-.9 9.6l-12.7 58.8c-.3 1.2-.7 3-.1 3.5 4.7 4.4 9.6 8.6 14.5 12.9 3.5-21.4 7.6-41.7 13.6-61.6 3.4 8.7.6 17-.8 25.2-1.9 11.6-4.5 23.1-6.5 34.7-.4 2.3-.4 6 1 7 4.8 4.2 10.3 7.4 16 11.3 3.9-21.5 5.5-42.6 12.6-62.5z"/><path d="M323.2 180.5a24 24 0 01-24.5-19.7c-2-9.7.7-20.2 15-27 16.6-7.8 38.3 2.3 41.6 19.5 1.2 6.6-1.6 12.1-6 16.3a35.1 35.1 0 01-26 11z"/><path d="M138.9 167.1a31 31 0 0125 12.7 22 22 0 01-13.6 34.9 29.9 29.9 0 01-31-9.9c-6-7-7.6-15.3-4.1-24.1 3.7-9.5 12-12 21-13.5.8-.2 1.8 0 2.7 0z"/><path d="M233.2 202c-18.5-.4-33.7-13.6-34-29.7 0-4.2.4-8 5.4-8.7 4.5-.7 6.6 2.3 8 6 2.3 6.6 5.5 12.4 12.4 15.4 7.5 3.2 14 1.5 20.4-3.3 6.2-4.7 6.4-11 4.7-17.5-1.4-5.2.4-8.4 4.7-10.2 4.3-1.7 9 1.3 10.9 6.2 7.2 19.8-5.7 34.6-22.8 40.2-3 1-6.5 1-9.7 1.6z"/><path d="M201.2 384.7c-.9-2-2.7-4.2-2.5-6.2 1.2-14 2.8-28.1 4.4-42.2 0-.9.7-1.7 1-2.6l2.6 1.1-3.1 49.1-2.4.8z"/><path d="M222 387.4c-4.8-5.7-5-15-1-37.8 3.1 3 4 30.5 1 37.8z"/><path d="M240.9 361.7l-1.4 20.2c-4.8-3.3-4.6-11.9-.2-20.4l1.6.2z"/><path d="M257.4 380.4c-4.1-5.2-3.7-9.7 1-14.7l-1 14.7z"/></g></svg>
|
||||
by <a href="https://marcusnoble.co.uk" class="fancy-link">Marcus Noble</a>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function debounce(func, wait, immediate) {
|
||||
var timeout;
|
||||
return function() {
|
||||
var context = this, args = arguments;
|
||||
var later = function() {
|
||||
timeout = null;
|
||||
if (!immediate) func.apply(context, args);
|
||||
};
|
||||
var callNow = immediate && !timeout;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
if (callNow) func.apply(context, args);
|
||||
};
|
||||
}
|
||||
|
||||
function loadimage() {
|
||||
let siteTitle = encodeURIComponent(document.getElementById('siteTitle').value);
|
||||
let title = encodeURIComponent(document.getElementById('title').value);
|
||||
let tags = encodeURIComponent(document.getElementById('tags').value);
|
||||
let image = encodeURIComponent(document.getElementById('image').value);
|
||||
let twitter = encodeURIComponent(document.getElementById('twitter').value);
|
||||
let github = encodeURIComponent(document.getElementById('github').value);
|
||||
let website = encodeURIComponent(document.getElementById('website').value);
|
||||
let bgColor = encodeURIComponent(document.getElementById('bgColor').value);
|
||||
let fgColor = encodeURIComponent(document.getElementById('fgColor').value);
|
||||
|
||||
let url = `/opengraph/?siteTitle=${siteTitle}&title=${title}&tags=${tags}&image=${image}&twitter=${twitter}&github=${github}&website=${website}&bgColor=${bgColor}&fgColor=${fgColor}`;
|
||||
document.getElementById('exampleImage').src = url;
|
||||
document.getElementById('imageURL').value = `https://opengraph.cluster.fun${url}`;
|
||||
}
|
||||
|
||||
[...document.querySelectorAll('fieldset input')].forEach(el => {
|
||||
el.addEventListener('keyup', debounce(function() {
|
||||
loadimage();
|
||||
}, 1000));
|
||||
el.addEventListener('change', function() {
|
||||
loadimage();
|
||||
});
|
||||
})
|
||||
|
||||
loadimage()
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
92
main.go
Normal file
92
main.go
Normal file
@ -0,0 +1,92 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/canhlinh/svg2png"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
//go:embed index.html svg.tmpl
|
||||
|
||||
var content embed.FS
|
||||
|
||||
func main() {
|
||||
app := fiber.New()
|
||||
ch := cache.New(5*24*time.Hour, 7*24*time.Hour)
|
||||
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
c.Type("html", "UTF8")
|
||||
body, _ := content.ReadFile("index.html")
|
||||
return c.Send(body)
|
||||
})
|
||||
|
||||
app.Get("/opengraph", func(c *fiber.Ctx) error {
|
||||
vars := map[string]string{
|
||||
"siteTitle": c.Query("siteTitle", ""),
|
||||
"title": c.Query("title", ""),
|
||||
"tags": c.Query("tags", ""),
|
||||
"image": c.Query("image", ""),
|
||||
"twitter": c.Query("twitter", ""),
|
||||
"github": c.Query("github", ""),
|
||||
"website": c.Query("website", ""),
|
||||
"bgColor": c.Query("bgColor", c.Query("bgColour", "#fff")),
|
||||
"fgColor": c.Query("fgColor", c.Query("fgColour", "#2B414D")),
|
||||
}
|
||||
|
||||
key := generateKey(vars)
|
||||
|
||||
png, found := ch.Get(key)
|
||||
if !found {
|
||||
var err error
|
||||
png, err = generateImage(vars)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ch.Set(key, png, -1)
|
||||
}
|
||||
|
||||
c.Type("png")
|
||||
return c.Send(png.([]byte))
|
||||
})
|
||||
|
||||
app.Listen(":3000")
|
||||
}
|
||||
|
||||
func generateKey(vars map[string]string) string {
|
||||
varsByte, _ := json.Marshal(vars)
|
||||
return base64.StdEncoding.EncodeToString(varsByte)
|
||||
}
|
||||
|
||||
func generateImage(vars map[string]string) ([]byte, error) {
|
||||
file, err := os.CreateTemp(os.TempDir(), "img-*.html")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.Remove(file.Name())
|
||||
|
||||
t := template.Must(template.New("svg.tmpl").Funcs(template.FuncMap{
|
||||
"split": func(input string) []string {
|
||||
return strings.Split(input, ",")
|
||||
},
|
||||
}).ParseFS(content, "svg.tmpl"))
|
||||
t.Execute(file, vars)
|
||||
|
||||
imageFile, err := os.CreateTemp(os.TempDir(), "img-*.png")
|
||||
|
||||
chrome := svg2png.NewChrome().SetHeight(600).SetWith(1200)
|
||||
if err := chrome.Screenshoot(fmt.Sprintf("file://%s", file.Name()), imageFile.Name()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.Remove(imageFile.Name())
|
||||
|
||||
return os.ReadFile(imageFile.Name())
|
||||
}
|
190
svg.tmpl
Normal file
190
svg.tmpl
Normal file
@ -0,0 +1,190 @@
|
||||
<DOCTYPE html>
|
||||
<html>
|
||||
<head><style>html, head, body { margin: 0; padding: 0; background-color: {{ .fgColor }}; } * { box-sizing: border-box; }</style></head>
|
||||
<body>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1200px" height="600px" style="border: 15px solid {{ .fgColor }}; border-radius: 20px;">
|
||||
<foreignObject x="0" y="0" width="100%" height="100%">
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "Roboto Bold";
|
||||
src: url("https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmYUtfBBc4AMP6lQ.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
unicode-range: U+0-FF, U+131, U+152-153, U+2BB-2BC, U+2C6, U+2DA, U+2DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Roboto Medium";
|
||||
src: url("https://fonts.gstatic.com/s/roboto/v18/KFOlCnqEu92Fr1MmEU9fBBc4AMP6lQ.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
unicode-range: U+0-FF, U+131, U+152-153, U+2BB-2BC, U+2C6, U+2DA, U+2DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Roboto Light";
|
||||
src: url("https://fonts.gstatic.com/s/roboto/v18/KFOmCnqEu92Fr1Mu4mxKKTU1Kg.woff2") format("woff2");
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
unicode-range: U+0-FF, U+131, U+152-153, U+2BB-2BC, U+2C6, U+2DA, U+2DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
* {
|
||||
font-family: 'Roboto Light';
|
||||
}
|
||||
:root {
|
||||
--background-color: {{ .bgColor }};
|
||||
--foreground-color: {{ .fgColor }};
|
||||
}
|
||||
#main {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--background-color);
|
||||
color: var(--foreground-color);
|
||||
}
|
||||
h1 {
|
||||
font-family: 'Roboto Bold';
|
||||
margin: 0;
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
left: 50px;
|
||||
height: 300px;
|
||||
width: 800px;
|
||||
font-size: 70px;
|
||||
font-weight: 900;
|
||||
line-height: 70px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
h2 {
|
||||
font-family: 'Roboto Medium';
|
||||
margin: 0;
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
left: 50px;
|
||||
font-weight: 900;
|
||||
}
|
||||
#profilePic {
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
right: 50px;
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
.tags {
|
||||
position: fixed;
|
||||
left: 50px;
|
||||
top: 400px;
|
||||
width: 800px;
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
font-size: 15px;
|
||||
font-weight: 200;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.tags span {
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
.social {
|
||||
position: fixed;
|
||||
left: 50px;
|
||||
right: 50px;
|
||||
bottom: 25px;
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
font-size: 15px;
|
||||
font-weight: 200;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
color: var(--foreground-color);
|
||||
fill: var(--foreground-color);
|
||||
}
|
||||
.social span {
|
||||
margin: 0 3px;
|
||||
}
|
||||
.social svg:not(:first-of-type) {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
hr {
|
||||
position: fixed;
|
||||
height: 15px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 65px;
|
||||
border: none;
|
||||
outline: none;
|
||||
background-color: var(--foreground-color);
|
||||
}
|
||||
</style>
|
||||
<div id="main">
|
||||
<h2>{{ .siteTitle }}</h2>
|
||||
<h1>{{ .title }}</h1>
|
||||
<img id="profilePic" src="{{ with .image }}{{ . }}{{ end }}" />
|
||||
|
||||
<div class="tags">
|
||||
{{ with .tags }}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-tag meta-icon">
|
||||
<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path>
|
||||
<line x1="7" y1="7" x2="7" y2="7"></line>
|
||||
</svg>
|
||||
{{ range (split .) }}
|
||||
<span>#{{ . }}</span>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="social">
|
||||
{{ with .twitter}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 310 310" height="25px" width="25px">
|
||||
<path d="M302.973,57.388c-4.87,2.16-9.877,3.983-14.993,5.463c6.057-6.85,10.675-14.91,13.494-23.73
|
||||
c0.632-1.977-0.023-4.141-1.648-5.434c-1.623-1.294-3.878-1.449-5.665-0.39c-10.865,6.444-22.587,11.075-34.878,13.783
|
||||
c-12.381-12.098-29.197-18.983-46.581-18.983c-36.695,0-66.549,29.853-66.549,66.547c0,2.89,0.183,5.764,0.545,8.598
|
||||
C101.163,99.244,58.83,76.863,29.76,41.204c-1.036-1.271-2.632-1.956-4.266-1.825c-1.635,0.128-3.104,1.05-3.93,2.467
|
||||
c-5.896,10.117-9.013,21.688-9.013,33.461c0,16.035,5.725,31.249,15.838,43.137c-3.075-1.065-6.059-2.396-8.907-3.977
|
||||
c-1.529-0.851-3.395-0.838-4.914,0.033c-1.52,0.871-2.473,2.473-2.513,4.224c-0.007,0.295-0.007,0.59-0.007,0.889
|
||||
c0,23.935,12.882,45.484,32.577,57.229c-1.692-0.169-3.383-0.414-5.063-0.735c-1.732-0.331-3.513,0.276-4.681,1.597
|
||||
c-1.17,1.32-1.557,3.16-1.018,4.84c7.29,22.76,26.059,39.501,48.749,44.605c-18.819,11.787-40.34,17.961-62.932,17.961
|
||||
c-4.714,0-9.455-0.277-14.095-0.826c-2.305-0.274-4.509,1.087-5.294,3.279c-0.785,2.193,0.047,4.638,2.008,5.895
|
||||
c29.023,18.609,62.582,28.445,97.047,28.445c67.754,0,110.139-31.95,133.764-58.753c29.46-33.421,46.356-77.658,46.356-121.367
|
||||
c0-1.826-0.028-3.67-0.084-5.508c11.623-8.757,21.63-19.355,29.773-31.536c1.237-1.85,1.103-4.295-0.33-5.998
|
||||
C307.394,57.037,305.009,56.486,302.973,57.388z"/>
|
||||
</svg>
|
||||
<span>@{{ . }}</span>
|
||||
{{ end }}
|
||||
|
||||
{{ with .github }}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 291.32 291.32" height="25px" width="25px">
|
||||
<path d="M145.66,0C65.219,0,0,65.219,0,145.66c0,80.45,65.219,145.66,145.66,145.66
|
||||
s145.66-65.21,145.66-145.66C291.319,65.219,226.1,0,145.66,0z M186.462,256.625c-0.838-11.398-1.775-25.518-1.83-31.235
|
||||
c-0.364-4.388-0.838-15.549-11.434-22.677c42.068-3.523,62.087-26.774,63.526-57.499c1.202-17.497-5.754-32.883-18.107-45.3
|
||||
c0.628-13.282-0.401-29.023-1.256-35.941c-9.486-2.731-31.608,8.949-37.79,13.947c-13.037-5.062-44.945-6.837-64.336,0
|
||||
c-13.747-9.668-29.396-15.64-37.926-13.974c-7.875,17.452-2.813,33.948-1.275,35.914c-10.142,9.268-24.289,20.675-20.447,44.572
|
||||
c6.163,35.04,30.816,53.94,70.508,58.564c-8.466,1.73-9.896,8.048-10.606,10.788c-26.656,10.997-34.275-6.791-37.644-11.425
|
||||
c-11.188-13.847-21.23-9.832-21.849-9.614c-0.601,0.218-1.056,1.092-0.992,1.511c0.564,2.986,6.655,6.018,6.955,6.263
|
||||
c8.257,6.154,11.316,17.27,13.2,20.438c11.844,19.473,39.374,11.398,39.638,11.562c0.018,1.702-0.191,16.032-0.355,27.184
|
||||
C64.245,245.992,27.311,200.2,27.311,145.66c0-65.365,52.984-118.348,118.348-118.348S264.008,80.295,264.008,145.66
|
||||
C264.008,196.668,231.69,239.992,186.462,256.625z"/>
|
||||
</svg>
|
||||
<span>{{ . }}</span>
|
||||
{{ end }}
|
||||
|
||||
{{ with .website }}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" height="25px" width="25px">
|
||||
<polygon points="478.609,55.652 478.609,155.826 33.391,155.826 33.391,55.652 0,55.652 0,456.348 33.391,456.348 33.391,189.217 478.609,189.217 478.609,456.348 512,456.348 512,55.652"/>
|
||||
<rect x="33.391" y="22.261" width="445.217" height="33.391"/>
|
||||
<rect x="33.391" y="456.348" width="445.217" height="33.391"/>
|
||||
<rect x="411.826" y="89.044" width="33.391" height="33.391"/>
|
||||
<rect x="345.043" y="89.044" width="33.391" height="33.391"/>
|
||||
<rect x="77.913" y="233.739" width="356.174" height="33.391"/>
|
||||
<rect x="77.913" y="311.652" width="178.087" height="33.391"/>
|
||||
</svg>
|
||||
<span>{{ . }}</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
</svg>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user