Skip to content

Commit 126e0da

Browse files
authored
feat: raster support (#22)
1 parent a81f5b3 commit 126e0da

8 files changed

+482
-196
lines changed

Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ COPY package.json yarn.lock /app/
5656
RUN yarn --production
5757

5858
COPY --from=html2svg-js /app/build /app/build
59-
COPY --from=html2svg-binaries /runtime /runtime
59+
COPY --from=html2svg-binaries /runtime /app/build/runtime
6060
COPY /scripts/docker-entrypoint.sh /app/scripts/docker-entrypoint.sh
6161

6262
ENTRYPOINT ["/app/scripts/docker-entrypoint.sh"]

readme.md

+36-6
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
# `html2svg`
22

3-
Convert HTML and `<canvas>` to SVG or PDF using Chromium. [Read the blog post](https://fathy.fr/html2svg).
3+
Convert HTML and `<canvas>` to vector (SVG, PDF) or bitmap (PNG, JPEG, WebP) images using Chromium. [Read the blog post](https://fathy.fr/html2svg).
44

55
## Usage
66

77
```shell
8-
# export to SVG
8+
# Export to SVG
99
$ docker run fathyb/html2svg https://google.com > google.svg
1010
$ docker run fathyb/html2svg https://google.com --format svg > google.svg
11-
# export to PDF
11+
# Export to PDF
1212
$ docker run fathyb/html2svg https://google.com --format pdf > google.pdf
13-
# show help
13+
# Export to PNG
14+
$ docker run fathyb/html2svg https://google.com --format png > google.png
15+
# Display help
1416
$ docker run fathyb/html2svg --help
15-
Usage: html2svg [options] <url>
17+
Usage: html2svg [options] [command] <url>
1618

1719
Arguments:
1820
url URL to the web page to render
@@ -22,8 +24,36 @@ Options:
2224
-w, --wait <seconds> set the amount of seconds to wait between the page loaded event and taking the screenshot (default: 1)
2325
-w, --width <width> set the viewport width in pixels (default: 1920)
2426
-h, --height <height> set the viewport height in pixels (default: 1080)
25-
-f, --format <format> set the output format, should one of these values: svg, pdf (default: "svg")
27+
-f, --format <format> set the output format, should one of these values: svg, pdf, png, jpg, webp (default: "svg")
2628
--help display help for command
29+
30+
Commands:
31+
serve [options]
32+
```
33+
34+
### Server
35+
36+
An HTTP server is also provided, all CLI options are supported:
37+
38+
```shell
39+
# Start a server on port 8080
40+
$ docker run -p 8080:8080 fathyb/html2svg serve
41+
# Export to SVG
42+
$ curl -d http://google.fr http://localhost:8080 > google.svg
43+
$ curl -d '{"url": "http://google.fr", "format": "svg"}' http://localhost:8080 > google.svg
44+
# Export to PDF
45+
$ curl -d '{"url": "http://google.fr", "format": "pdf"}' http://localhost:8080 > google.pdf
46+
# Export to PNG
47+
$ curl -d '{"url": "http://google.fr", "format": "png"}' http://localhost:8080 > google.png
48+
# Display help
49+
$ docker run fathyb/html2svg serve --help
50+
Usage: html2svg serve [options]
51+
52+
Options:
53+
-H, --host <hostname> set the hostname to listen on (default: "localhost")
54+
-p, --port <hostname> set the port to listen on (default: 8080)
55+
-u, --unix <path> set the unix socket to listen on
56+
-h, --help display help for command
2757
```
2858

2959
## Development

scripts/docker-entrypoint.sh

+1-2
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,4 @@ set -e
55
export DISPLAY=:99
66

77
Xvfb $DISPLAY -screen 0 1920x1080x24 &
8-
/runtime/electron --no-sandbox --headless --disable-audio-output --mute-audio --force-color-profile=srgb --disable-dev-shm-usage /app/build/html2svg.js "$@"
9-
8+
node /app/build/html2svg.cli.js "$@"

src/chromium.patch

+50-56
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,31 @@
11
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
2-
index 97cf24ad5f4a6..ab3f90752b11a 100644
2+
index 97cf24ad5f4a6..ce12415534d20 100644
33
--- a/content/renderer/render_frame_impl.cc
44
+++ b/content/renderer/render_frame_impl.cc
5-
@@ -256,6 +256,15 @@
5+
@@ -256,6 +256,18 @@
66
#include "content/renderer/java/gin_java_bridge_dispatcher.h"
77
#endif
88

99
+// html2svg includes
10+
+#include <stdlib.h>
1011
+#include <iostream>
1112
+#include "cc/paint/paint_recorder.h"
1213
+#include "cc/paint/skia_paint_canvas.h"
14+
+#include "third_party/skia/include/core/SkEncodedImageFormat.h"
1315
+#include "third_party/skia/include/core/SkStream.h"
16+
+#include "third_party/skia/include/core/SkSurface.h"
1417
+#include "third_party/skia/include/docs/SkPDFDocument.h"
1518
+#include "third_party/skia/include/svg/SkSVGCanvas.h"
1619
+#include "third_party/skia/include/svg/SkSVGCanvas.h"
1720
+
1821
using base::Time;
1922
using blink::ContextMenuData;
2023
using blink::WebContentDecryptionModule;
21-
@@ -3822,6 +3831,135 @@ void RenderFrameImpl::DidClearWindowObject() {
24+
@@ -3822,6 +3834,126 @@ void RenderFrameImpl::DidClearWindowObject() {
2225

2326
for (auto& observer : observers_)
2427
observer.DidClearWindowObject();
2528
+
26-
+ // A Skia stream writing to stdout
27-
+ class StdoutStream : public SkWStream {
28-
+ public:
29-
+ ~StdoutStream() override {
30-
+ flush();
31-
+
32-
+ delete[] fBytes;
33-
+ }
34-
+
35-
+ bool write(const void* data, size_t size) override {
36-
+ auto* buffer = static_cast<const char*>(data);
37-
+ size_t remaining = size;
38-
+ size_t bufferSize = 8 * 1024;
39-
+
40-
+ while (remaining != 0) {
41-
+ ssize_t length = std::min(bufferSize - fBytesBuffered, remaining);
42-
+
43-
+ std::memcpy(&fBytes[fBytesBuffered], &buffer[size - remaining], length);
44-
+
45-
+ remaining -= length;
46-
+ fBytesWritten += length;
47-
+ fBytesBuffered += length;
48-
+
49-
+ if (fBytesBuffered == bufferSize) {
50-
+ flush();
51-
+ }
52-
+ }
53-
+
54-
+ return true;
55-
+ }
56-
+
57-
+ void flush() override {
58-
+ if (::write(1, fBytes, fBytesBuffered) != -1) {
59-
+ fBytesBuffered = 0;
60-
+ fflush(stdout);
61-
+ }
62-
+ }
63-
+
64-
+ size_t bytesWritten() const override {
65-
+ return fBytesWritten;
66-
+ }
67-
+
68-
+ private:
69-
+ char* fBytes = new char[8 * 1024];
70-
+ size_t fBytesWritten = 0;
71-
+ size_t fBytesBuffered = 0;
72-
+ };
73-
+
7429
+ // Get access to the JS VM for this process (each tab is a process)
7530
+ v8::Isolate *isolate = blink::MainThreadIsolate();
7631
+ // Auto-clean v8 handles
@@ -114,11 +69,12 @@ index 97cf24ad5f4a6..ab3f90752b11a 100644
11469
+ );
11570
+
11671
+ // Create a memory stream to save the SVG content
117-
+ StdoutStream stream;
72+
+ SkDynamicMemoryWStream stream;
11873
+ // Get the recording data
11974
+ auto picture = recorder.finishRecordingAsPicture();
75+
+ auto mode = args[1]->ToUint32(context).ToLocalChecked()->Value();
12076
+
121-
+ switch(args[1]->ToUint32(context).ToLocalChecked()->Value()) {
77+
+ switch(mode) {
12278
+ // SVG
12379
+ case 0: {
12480
+ picture->Playback(SkSVGCanvas::Make(rect, &stream).get());
@@ -141,16 +97,54 @@ index 97cf24ad5f4a6..ab3f90752b11a 100644
14197
+
14298
+ break;
14399
+ }
100+
+ default: {
101+
+ auto surface = SkSurface::MakeRasterN32Premul(width, height);
102+
+
103+
+ picture->Playback(surface->getCanvas());
104+
+
105+
+ auto img = surface->makeImageSnapshot();
106+
+
107+
+ assert(img != nullptr);
108+
+
109+
+ auto result = img->encodeToData(
110+
+ [mode]() -> SkEncodedImageFormat {
111+
+ switch(mode) {
112+
+ case 3:
113+
+ return SkEncodedImageFormat::kJPEG;
114+
+ case 4:
115+
+ return SkEncodedImageFormat::kWEBP;
116+
+ default:
117+
+ return SkEncodedImageFormat::kPNG;
118+
+ }
119+
+ }(),
120+
+ 100
121+
+ );
122+
+
123+
+ assert(result != nullptr);
124+
+
125+
+ stream.write(result->data(), result->size());
126+
+
127+
+ break;
128+
+ }
144129
+ }
130+
+
131+
+ auto buffer = v8::ArrayBuffer::New(isolate, stream.bytesWritten());
132+
+
133+
+ stream.copyTo(buffer->Data());
134+
+ args.GetReturnValue().Set(buffer);
145135
+ }
146136
+ );
147137
+
148138
+ // Register the function as "getPageContentsAsSVG"
149139
+ global->Set(
150-
+ context,
151-
+ v8::String::NewFromUtf8(isolate, "getPageContentsAsSVG").ToLocalChecked(),
152-
+ fn->GetFunction(context).ToLocalChecked()
140+
+ context,
141+
+ v8::String::NewFromUtf8(isolate, "getPageContentsAsSVG").ToLocalChecked(),
142+
+ fn->GetFunction(context).ToLocalChecked()
153143
+ ).Check();
144+
+
145+
+ if (command_line.HasSwitch("html2svg-svg-mode")) {
146+
+ setenv("html2svg_svg_mode", "true", 1);
147+
+ }
154148
}
155149

156150
void RenderFrameImpl::DidCreateDocumentElement() {

0 commit comments

Comments
 (0)