diff --git a/.github/scripts/chmod note.txt b/.github/scripts/chmod note.txt new file mode 100644 index 0000000..f3f1268 --- /dev/null +++ b/.github/scripts/chmod note.txt @@ -0,0 +1,3 @@ +From https://github.community/t/action-showing-permission-denied/134957 + +git update-index --chmod=+x ./.github/scripts/.sh \ No newline at end of file diff --git a/.github/scripts/install.ps1 b/.github/scripts/install.ps1 new file mode 100644 index 0000000..a9be627 --- /dev/null +++ b/.github/scripts/install.ps1 @@ -0,0 +1,4 @@ +Invoke-WebRequest "http://ftp.gnome.org/pub/GNOME/binaries/win64/gtk+/2.22/gtk+-bundle_2.22.1-20101229_win64.zip" -OutFile "gtk.zip" +Expand-Archive gtk.zip -DestinationPath "C:\GTK" +Invoke-WebRequest "https://downloads.sourceforge.net/project/libjpeg-turbo/2.0.4/libjpeg-turbo-2.0.4-vc64.exe" -OutFile "libjpeg.exe" -UserAgent NativeHost +.\libjpeg.exe /S \ No newline at end of file diff --git a/.github/scripts/install.sh b/.github/scripts/install.sh new file mode 100755 index 0000000..99b494a --- /dev/null +++ b/.github/scripts/install.sh @@ -0,0 +1 @@ +sudo apt-get install build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev \ No newline at end of file diff --git a/scripts/publish.sh b/.github/scripts/publish.sh old mode 100644 new mode 100755 similarity index 97% rename from scripts/publish.sh rename to .github/scripts/publish.sh index 9fea0d3..0a0492f --- a/scripts/publish.sh +++ b/.github/scripts/publish.sh @@ -1,12 +1,12 @@ -#!/bin/bash - -packageName=$(node -p "require('./package.json').name") -packageVersion=$(node -p "require('./package.json').version") -publishVersion=$(npm view "$packageName" version) -if [ "$packageVersion" != "$publishVersion" ] -then - npm config set //registry.npmjs.org/:_authToken=$NPM_AUTH_TOKEN - npm publish -else - echo "Existing version $packageVersion for $packageName is already published...skipping" +#!/bin/bash + +packageName=$(node -p "require('./package.json').name") +packageVersion=$(node -p "require('./package.json').version") +publishVersion=$(npm view "$packageName" version) +if [ "$packageVersion" != "$publishVersion" ] +then + npm config set //registry.npmjs.org/:_authToken=$NPM_AUTH_TOKEN + npm publish +else + echo "Existing version $packageVersion for $packageName is already published...skipping" fi \ No newline at end of file diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 01c645f..ce10309 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -10,23 +10,28 @@ on: jobs: build-and-test: - runs-on: windows-latest strategy: + fail-fast: false matrix: + os: [ubuntu-latest, windows-latest] node-version: ['10.x', '12.x', '14.x', '16.x'] + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} - - name: Install Cairo dependencies - run: | - Invoke-WebRequest "http://ftp.gnome.org/pub/GNOME/binaries/win64/gtk+/2.22/gtk+-bundle_2.22.1-20101229_win64.zip" -OutFile "gtk.zip" - Expand-Archive gtk.zip -DestinationPath "C:\GTK" - Invoke-WebRequest "https://downloads.sourceforge.net/project/libjpeg-turbo/2.0.4/libjpeg-turbo-2.0.4-vc64.exe" -OutFile "libjpeg.exe" -UserAgent NativeHost - .\libjpeg.exe /S + - name: Install Cairo dependencies (Windows) + if: matrix.os == 'windows-latest' + shell: powershell + run: ./.github/scripts/install.ps1 + - name: Install Cairo dependencies (Linux) + if: matrix.os == 'ubuntu-latest' + shell: bash + run: ./.github/scripts/install.sh - name: Add msbuild to PATH + if: matrix.os == 'windows-latest' uses: microsoft/setup-msbuild@v1.1 with: vs-version: '15.0' @@ -53,6 +58,13 @@ jobs: # with: # name: Node v${{ matrix.node-version }} # path: '*.tgz' + - name: 'Upload Test Data Artifact' + if: failure() + uses: actions/upload-artifact@v3 + with: + name: test-data + path: ./testData/ + retention-days: 5 publish: if: github.ref == 'refs/heads/master' needs: build-and-test @@ -74,7 +86,7 @@ jobs: run: npm run-script package-size - name: Publish shell: bash - run: ./scripts/publish.sh + run: ./.github/scripts/publish.sh env: CI: true NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 731d49c..27c5238 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,47 +1,64 @@ -version: "3" - -services: - base: - image: chartjs-node-canvas-base - build: - context: . - dockerfile: docker/base/Dockerfile - container_name: chartjs-node-canvas-base - build: - image: chartjs-node-canvas-build - depends_on: - - base - build: - context: . - dockerfile: docker/build/Dockerfile - container_name: chartjs-node-canvas-build - test: - image: chartjs-node-canvas-build - container_name: chartjs-node-canvas-test - depends_on: - - build - environment: - - NODE_ENV=test - - FONTCONFIG_PATH=/etc/fonts - command: npm run test - production: - image: chartjs-node-canvas - depends_on: - - test - build: - context: . - dockerfile: docker/production/Dockerfile - container_name: chartjs-node-canvas - environment: - - NODE_ENV=production - -#<< Docker Commands >> - -# Build Images -#docker-compose build - -# Run Containers -#docker-compose up - -# Run Tests specifically -#docker-compose up test +version: "3" + +services: + base-slim: + image: chartjs-node-canvas-base-slim + build: + context: . + dockerfile: docker/slim/base.Dockerfile + container_name: chartjs-node-canvas-base-slim + build-slim: + image: chartjs-node-canvas-build-slim + depends_on: + - base-slim + build: + context: . + dockerfile: docker/slim/build.Dockerfile + container_name: chartjs-node-canvas-build-slim + test-slim: + image: chartjs-node-canvas-build-slim + container_name: chartjs-node-canvas-test-slim + volumes: + - ./testData:/usr/server/testData + depends_on: + - build-slim + environment: + - NODE_ENV=test + - FONTCONFIG_PATH=/etc/fonts + command: npm run test + production-slim: + image: chartjs-node-canvas-slim + depends_on: + - test-slim + build: + context: . + dockerfile: docker/slim/production.Dockerfile + container_name: chartjs-node-canvas-slim + environment: + - NODE_ENV=production + + base-ubuntu: + image: chartjs-node-canvas-base-ubuntu + build: + context: . + dockerfile: docker/ubuntu/base.Dockerfile + container_name: chartjs-node-canvas-base-ubuntu + build-ubuntu: + image: chartjs-node-canvas-build-ubuntu + depends_on: + - base-ubuntu + build: + context: . + dockerfile: docker/ubuntu/build.Dockerfile + container_name: chartjs-node-canvas-build-ubuntu + test-ubuntu: + image: chartjs-node-canvas-build-ubuntu + container_name: chartjs-node-canvas-test-ubuntu + volumes: + - ./testData:/usr/server/testData + depends_on: + - build-ubuntu + environment: + - NODE_ENV=test + - FONTCONFIG_PATH=/etc/fonts + command: npm run test \ No newline at end of file diff --git a/docker.txt b/docker.txt new file mode 100644 index 0000000..51fd43b --- /dev/null +++ b/docker.txt @@ -0,0 +1,11 @@ +#<< Docker Commands >> + +# Build Images +docker-compose build + +# Run Containers +docker-compose up + +# Run Tests specifically +docker-compose up test-slim +docker-compose up test-ubuntu diff --git a/docker/base/Dockerfile b/docker/slim/base.Dockerfile similarity index 93% rename from docker/base/Dockerfile rename to docker/slim/base.Dockerfile index bec9197..ed8d3fe 100644 --- a/docker/base/Dockerfile +++ b/docker/slim/base.Dockerfile @@ -1,4 +1,4 @@ -FROM node:14.18.0-slim +FROM node:16.15.0-slim LABEL maintainer = "sean.m.sobey@gmail.com" diff --git a/docker/build/Dockerfile b/docker/slim/build.Dockerfile similarity index 78% rename from docker/build/Dockerfile rename to docker/slim/build.Dockerfile index 67d4e53..c5d229b 100644 --- a/docker/build/Dockerfile +++ b/docker/slim/build.Dockerfile @@ -1,13 +1,13 @@ -FROM chartjs-node-canvas-base - -LABEL maintainer = "sean.m.sobey@gmail.com" - -WORKDIR /usr/server - -RUN npm ci --no-color - -COPY . . - -RUN npm run build - +FROM chartjs-node-canvas-base-slim + +LABEL maintainer = "sean.m.sobey@gmail.com" + +WORKDIR /usr/server + +RUN npm ci --no-color + +COPY . . + +RUN npm run build + CMD ["/bin/bash", "-c", "echo base image complete"] \ No newline at end of file diff --git a/docker/production/Dockerfile b/docker/slim/production.Dockerfile similarity index 52% rename from docker/production/Dockerfile rename to docker/slim/production.Dockerfile index 90362f6..0c4f0ef 100644 --- a/docker/production/Dockerfile +++ b/docker/slim/production.Dockerfile @@ -1,9 +1,9 @@ -FROM chartjs-node-canvas-base - -LABEL maintainer = "sean.m.sobey@gmail.com" - -WORKDIR /usr/server - -COPY --from=chartjs-node-canvas-build /usr/server/dist/ ./dist/ - +FROM chartjs-node-canvas-base-slim + +LABEL maintainer = "sean.m.sobey@gmail.com" + +WORKDIR /usr/server + +COPY --from=chartjs-node-canvas-build-slim /usr/server/dist/ ./dist/ + CMD ["/bin/bash", "-c", "echo production image complete"] \ No newline at end of file diff --git a/docker/ubuntu/base.Dockerfile b/docker/ubuntu/base.Dockerfile new file mode 100644 index 0000000..1853259 --- /dev/null +++ b/docker/ubuntu/base.Dockerfile @@ -0,0 +1,37 @@ +FROM ubuntu:22.10 + +LABEL maintainer = "sean.m.sobey@gmail.com" + +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev + +# TODO: move into above... +RUN apt-get install --yes curl + +# nvm environment variables +ENV NVM_DIR /usr/local/nvm +#TODO read from .nvmrc +ENV NODE_VERSION 16.13.0 + +# install nvm +# https://github.com/creationix/nvm#install-script +RUN mkdir -p $NVM_DIR \ + && curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash + +# install node and npm +RUN . ~/.bashrc $NVM_DIR/nvm.sh \ + && nvm install $NODE_VERSION \ + && nvm alias default $NODE_VERSION \ + && nvm use default + +# add node and npm to path so the commands are available +ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules +ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH + +WORKDIR /usr/server + +COPY package.json package-lock.json ./ + +RUN npm ci --production --no-color + +CMD ["/bin/bash", "-c", "echo base image complete"] \ No newline at end of file diff --git a/docker/ubuntu/build.Dockerfile b/docker/ubuntu/build.Dockerfile new file mode 100644 index 0000000..4ee1fb9 --- /dev/null +++ b/docker/ubuntu/build.Dockerfile @@ -0,0 +1,13 @@ +FROM chartjs-node-canvas-base-ubuntu + +LABEL maintainer = "sean.m.sobey@gmail.com" + +WORKDIR /usr/server + +RUN npm ci --no-color + +COPY . . + +RUN npm run build + +CMD ["/bin/bash", "-c", "echo base image complete"] \ No newline at end of file diff --git a/src/index.e2e.spec.ts b/src/index.e2e.spec.ts index 5bf91dc..c5a97b8 100644 --- a/src/index.e2e.spec.ts +++ b/src/index.e2e.spec.ts @@ -1,7 +1,7 @@ import { AssertionError } from 'assert'; import { promises as fs } from 'fs'; -import { platform, EOL } from 'os'; -import { join } from 'path'; +import os, { platform, EOL } from 'os'; +import path, { join } from 'path'; import { Readable } from 'stream'; import { describe, it } from 'mocha'; import { Stream } from 'stream'; @@ -81,7 +81,7 @@ describe(ChartJSNodeCanvas.name, () => { const extension = '.txt'; const fileName = 'render-to-data-URL'; const fileNameWithExtension = fileName + extension; - const expectedDataPath = join(process.cwd(), 'testData', platform(), fileName + extension); + const expectedDataPath = join(getTestDataPath(), fileName + extension); const expected = await fs.readFile(expectedDataPath, 'utf8'); // const result = actual === expected; const compareData = await compareImages(actual, expected, { output: { useCrossOrigin: false } }); @@ -374,10 +374,16 @@ describe(ChartJSNodeCanvas.name, () => { } */ + function getTestDataPath(): string { + + const paths = [process.cwd(), 'testData', platform()]; + return path.join(...paths); + } + async function assertImage(actual: Buffer, fileName: string): Promise { const extension = '.png'; const fileNameWithExtension = fileName + extension; - const testDataPath = join(process.cwd(), 'testData', platform(), fileNameWithExtension); + const testDataPath = join(getTestDataPath(), fileNameWithExtension); const exists = await pathExists(testDataPath); if (!exists) { console.error(`Warning: expected image path does not exist!, creating '${testDataPath}'`); @@ -416,16 +422,16 @@ describe(ChartJSNodeCanvas.name, () => { // resemblejs/compareImages //function compareImages(image1: string | Buffer, image2: string | Buffer, options?: ResembleSingleCallbackComparisonOptions): Promise { function compareImages(image1: string | Buffer, image2: string | Buffer, options?: any): Promise { - return new Promise((resolve, reject) => { - //resemble.compare(image1, image2, options || {}, (err, data) => { - resemble.compare(image1, image2, options || {}, (err: any, data: any) => { - if (err) { - reject(err); - } else { - resolve(data); - } - }); + return new Promise((resolve, reject) => { + //resemble.compare(image1, image2, options || {}, (err, data) => { + resemble.compare(image1, image2, options || {}, (err: any, data: any) => { + if (err) { + reject(err); + } else { + resolve(data); + } }); + }); } function streamToBuffer(stream: Readable): Promise { diff --git a/testData/linux/background-color.png b/testData/linux/background-color.png new file mode 100644 index 0000000..5c42d3a Binary files /dev/null and b/testData/linux/background-color.png differ diff --git a/testData/linux/chartjs-plugin-annotation.png b/testData/linux/chartjs-plugin-annotation.png index 7f1f56b..2536ac7 100644 Binary files a/testData/linux/chartjs-plugin-annotation.png and b/testData/linux/chartjs-plugin-annotation.png differ diff --git a/testData/linux/chartjs-plugin-datalabels.png b/testData/linux/chartjs-plugin-datalabels.png index a20552a..44a24bb 100644 Binary files a/testData/linux/chartjs-plugin-datalabels.png and b/testData/linux/chartjs-plugin-datalabels.png differ diff --git a/testData/linux/font.png b/testData/linux/font.png index 8c1e9d0..eb3674a 100644 Binary files a/testData/linux/font.png and b/testData/linux/font.png differ diff --git a/testData/linux/no-background-color.png b/testData/linux/no-background-color.png new file mode 100644 index 0000000..c5cf327 Binary files /dev/null and b/testData/linux/no-background-color.png differ diff --git a/testData/linux/render-to-buffer.png b/testData/linux/render-to-buffer.png new file mode 100644 index 0000000..39dbe20 Binary files /dev/null and b/testData/linux/render-to-buffer.png differ diff --git a/testData/linux/render-to-data-URL.txt b/testData/linux/render-to-data-URL.txt new file mode 100644 index 0000000..03af47c --- /dev/null +++ b/testData/linux/render-to-data-URL.txt @@ -0,0 +1 @@ +data:image/png;base64,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RURkIPnMQHbs2MHChQsZPnw4s2fP5gc/+IE3PLrj8XjYtWsXOTk5rFmzhhUrVnDkyJE+L1pERAaezwwkJSWFpUuX4na7TznD8KehoYHW1lZef/11srOzaW9v5/e//z0//vGPGTduXKe2FRUVXfbjcDhITEz022awcTgcftc7nRbATO2xWtwhnv4pyoD6VhNgwel0UlHR0GW77sY7GA21MWu8g59PgPz4xz9m1apVbN26lfvuu4+cnBy+853vYLVae9zpOeecwwUXXACAxWLh/fff9wmQ5OTkbvvpSZvBxN947UecQBPRMdHEhAbu2dfmZjfQgN1uJznZ7rftUHt+YeiNWeMd3HzeiSIiIrj66quZOXMmv/zlL6mpqWHNmjU96iwiIgKLxcLw4cO9y4YPH86xY8f6rmIREQkIPgHS0tLi/Tk2NpaxY8fS0ND1rohOnZnNTJ06lQMHDniXHThwYMilsojIUOCzC+v+++8nKyuLiooK3nrrLbZv386dd97pXb9z50527drFgQMH8Hg8vPLKK0yaNIns7GwALr/8cp544gkqKytpb2/nwIED3HXXXf03IhER6Rc+AbJo0SL27NlDTU0No0aN4vLLLyc6Otq7PioqirS0NNLS0rzLTl4/YsQIFi1aRFFREcHBwVx//fXY7f73hYuIyJnHJ0BsNhvZ2dkcPHiQnJwcbDZbp/WjRo1i1KhRfjuNjIw85RXqIiIyeHR5Ok92drbPbUxERERO6PJeWN3NMkREZGgL3AsKREQkoClARETEEAWIiIgYogARERFDFCAiImKIAkRERAxRgIiIiCEKEBERMUQBIiIihihARETEEAWIiIgYogARERFDFCAiImKIAkRERAxRgIiIiCEKEBERMUQBIiIihihARETEEAWIiIgYogARERFDugyQHTt20Nra2p+1iIjIGaTLADl48CDt7e0+y8vLy1m7di3/+Mc/2Lx5c5cdu1wuXn/9ddauXds3lYqISEAJ+uoCl8vFtm3b2L9/PyaTieTkZHJycggK6mi6bt06GhoaOHr0KACzZs06ZcerV69m8+bNpKWlcf7555/GIYiIyEDwCZAXXngBl8tFREQEsbGx7N+/n4kTJxIREQHAwoULAViyZAkul+uUnR49epRPP/2UefPmcfDgwdNYvoiIDBSfXVi7du3immuuITExkZycHL773e96w6OnXnvtNRYsWEBISEifFSoiIoHFZwaSlpbGkiVLMJlMtLa2YrPZetXhF198QVtbG9OnT2flypVdtquoqOhyncPhIDEx0W+bwcbhcPhd73RaADO1x2pxh3j6pygD6ltNgAWn00lFRUOX7bob72A01Mas8Q5+PgFy2223sXbtWjZt2sSvfvUrpk2bxg033EBoaGi3nbW2tvLmm2/yk5/8pNu2ycnJfdJmMPE3XvsRJ9BEdEw0MaGBe/a1udkNNGC320lOtvttO9SeXxh6Y9Z4BzefdyKbzcbll19OTk4Ov/71r3E6naxatapHnW3btg2Px8OGDRtYunQpBQUFOBwO3nnnnT4vXEREBpbPDMTpdGK3d3xyjIqKYtSoUTidzh51NnLkSM455xzv/4ODg7FYLL3eDSYiIoHPJ0AWLVrExIkTqa6u5tixYxQUFHDXXXd512/dupUdO3ZQVlaGx+Ph2WefJSsri5kzZzJy5EhGjhzpbevxdOyrv+iii/phKCIi0p98AuShhx7iwIEDfPTRR0yePJlrrrmGyMhI7/r4+HgmTJjAhAkTvMsSEhJO2fmkSZMYMWLEaShbREQGmk+AWK1WJk6cyJ49e5gyZYrP7qdRo0YxatSoHnX+1RmJiIgMHl2ezpOdna3rOEREpEs+M5ATejrLEBGRoSlwLygQEZGApgARERFDFCAiImKIAkRERAxRgIiIiCEKEBERMUQBIiIihihARETEEAWIiIgYogARERFDFCAiImKIAkRERAxRgIiIiCEKEBERMUQBIiIihihARETEEAWIiIgYogARERFDFCAiImKIAkRERAzpMkC2bNlCS0tLf9YiIiJnkKCuVpSXlzN58mSf5YcOHaKoqIjy8nIyMzPJy8vzrvN4POzfv5/8/Hzq6+tJSEhg3rx52O3201O9iIgMGJ8ZSFtbG+vXr2fv3r0sW7aMTz75hPb2du/6zZs3c+TIEaqqqigtLe302KamJp5//nncbjdpaWns37+fhx9+mObm5tM/EhER6Vc+M5Dnn38eq9VKdHQ0aWlplJaW0tTUREREBADXXnstAEuWLMHlcnV6bFhYGA8//DAWiwWAuXPncu+997J7926ysrJO91hERKQf+cxAioqKWLBgAfHx8UyZMoVrrrnGGx7dMZlM3vA48f/29nZCQ0P7rmIREQkIPjOQ0aNH8/LLL2OxWDh+/Dg2m81w5++++y4JCQlkZmb6rKuoqOjycQ6Hg8TERL9tbFuKMLe0d7k+0DRljcEV0fXf0uFw+H2802kBzNQeq8Ud4unj6vpOfasJsOB0OqmoaOiyXXfjHYyG2pg13sHPJ0Buu+021q1bx4YNG1i0aBHjxo3jhz/8Ya+DZP369WzZsoV77rkHs9n3ZK/k5ORu+/DbZsObcKzrN6hAE5E7CboZs7/x2o84gSaiY6KJCQ3cs6/NzW6gAbvdTnKy/5MnerINDDZDbcwa7+DmEyBWq5WLL74Yp9NJXl4er776KqtWrWLBggU97nTTpk0sW7aMe+65h9jY2D4tuJPEGLj8nNPXf1/IL4bN+QNdhYhIn/MJkIaGBiIjIwGw2WykpKT06nqQrVu38tZbb3H33XeTkJDQd5WeSnAQJMed3t/xdZVWDnQFIiKnhU+A/O53v2P06NEcO3YMh8PB/v37ufvuu73rt27dyo4dOygrK8Pj8fDss8+SlZXFzJkzaWho4IUXXiAlJYX33nvP+5izzz77lNeUiIjImeuUAXLo0CFWrVrFrFmzuOGGGzod/0hJScFsNpOTk+NdlpSUBHTs/rr55pt9fslpn4mIiEi/8wmQoKAgxowZwxdffMG4ceN8Dp4PHz6c4cOHn7Izq9XaKVhERGTw6vJ0npkzZ2K1WvuzFhEROYN0eS+slJSU/qxDRETOMIF7QYGIiAQ0BYiIiBiiABEREUMUICIiYogCREREDFGAiIiIIQoQERExRAEiIiKGKEBERMQQBYiIiBiiABEREUMUICIiYogCREREDFGAiIiIIQoQERExRAEiIiKGKEBERMQQBYiIiBiiABEREUMUICIiYkiXAbJlyxZaWlr6sxYRETmDBHW1ory8nMmTJ/ssP3DgAAUFBTgcDsaPH09eXp5Pm02bNpGfn09oaCjnn38+w4cP79uqRURkwPnMQFpaWlizZg1FRUW8/fbbrFmzhra2Nu/6nTt30tDQQG1tLaWlpT4drlu3jqVLlzJx4kTCwsJ4/PHHqa+vP72jEBGRfuczA3nuueeIjIwkKSmJiRMnUlpaSktLC8HBwQBcffXVACxZsgSXy9XpsR6Ph9WrV3PNNdcwY8YMoGMm88knn3DZZZed7rGIiEg/8pmB7N27l8suu4zo6GjGjRvHggULCA8P71FnTqeTqqoqxo8f7102YcIEiouL+65iEREJCD4zkIyMDF588UWCgoKor6/HZrP1uLOGhgZMJhN2u927LCIi4pS7sCoqKrrsx+FwkJiY6LdNgsuFu62N+pqaHtc3EMKcTuxATU0NbWFdn/TmcDj89uN0WgAztcdqcYd4+rbIPlTfagIsOJ1OKioaumzX3XgHo6E2Zo138PMJkB/96Eds2LCBdevW8fDDD5Oens4tt9zSo1lIcHAwHo8Hl8uF2dzxZtnW1kZISIhP2+Tk5G7789vGYsESHExcXFy3/QwoeyVAR53djNnfeO1HnEAT0THRxIQG7tnX5mY30IDdbic52e63bU+2gcFmqI1Z4x3cfN6JgoODOffcc5k6dSr/+Z//SWhoKKtXr+5RZ8OGDcNsNnP06FHvsqNHjxITE9N3FYuISEDwCZBjx455f7ZarcTHx9Pa2tqjzoKDg5k8eTKffvop0HFG17Zt25g+fXoflSsiIoHCZxfWY489RkpKCvX19ZSVlXH48GHuuece7/pPP/2ULVu2UFnZsWvmqaeeYsaMGcyZMweABQsW8Kc//Yni4mKOHTtGSkoKWVlZ/TQcERHpLz4B8vDDD1NWVsaqVauYPXs2Y8eOxWq1etePGjWq0/8BkpKSvD+PGDGChx56iIMHDxIWFsbIkSMxmUyncQgiIjIQfALEbDaTlpZGbGws6enpPmExfPjwbq8st1qtjBs3rm8rFRGRgNLl6TwzZ870CQ8REZETurwXVkpKSn/WISIiZ5jAvaBAREQCmgJEREQMUYCIiIghChARETFEASIiIoYoQERExBAFiIiIGKIAERERQxQgIiJiiAJEREQMUYCIiIghChARETFEASIiIoYoQERExBAFiIiIGKIAERERQxQgIiJiiAJEREQMUYCIiIghChARETHEcIBs2LCBlpaWvqxFRETOIEFGH1hVVYXL5fJZ3tzczNq1aykpKSE0NJRZs2YxefLkr1WkiIgEnl7PQI4fP86KFSvIz8/ntddeY8WKFbS1tXnX//3vf2fnzp3k5eWRkZHBs88+S2FhYZ8WLSIiA6/XAfLcc89RX1/PyJEjmTFjBgCtra3e9bt37+aqq65i6tSpzJ07l5ycHPLz8/uuYhERCQi9DpADBw5w4YUXEhUVxejRo7nkkkuw2+3e9ZmZmVRUVADgdrupqKggMTGx7yoWEZGA0OtjIOPHj+e5554jJCSE6upq0tLSOq3/7ne/y5NPPsmWLVtoaGhg4sSJ5OXl+fRzImROxeFwkJiY6LdNgsuFu62N+pqa3g6hX4U5ndiBmpoa2sK6zmuHw+G3H6fTApipPVaLO8TTt0X2ofpWE2DB6XRSUdHQZbvuxjsYDbUxa7yDX68D5NZbb2Xz5s2sXbuWJ598ksTERH7yk58QGRmJ2+3m6aefJj4+nrlz59LY2Mh7773Hrl27mDZtWqd+kpOTu/1dfttYLFiCg4mLi+vtEPqXvRKgo85uxuxvvPYjTqCJ6JhoYkID9+xrc7MbaMBut5OcbPfbtifbwGAz1Mas8Q5uvQ4Qi8XC2WefjcPh4IILLuCf//wnq1ev5uqrr6ampoaysjLuvvtubDYbAE6nk9WrV/sEiIiInNl6/VG2qqrK+3NQUBBRUVHe03mjoqKwWCyUl5d725SXlxMeHt4HpYqISCDp9QzkT3/6EzExMTQ2NnLgwAFqamq45557ALBarVx33XX813/9FyNGjKCxsZHW1lZuv/32Pi9cREQGVq8D5KGHHsLhcPDvf/+bc845h/T0dIKDg73r586dS25uLpWVlYSGhpKYmIjFYunTokVEZOD1OkBMJhNJSUlER0czYsSITuFxgs1mY/To0X1SoIiIBCbDp/PMmTOH0NDQvqxFRETOIIbvhZWQkNCXdYiIyBkmcC8oEBGRgKYAERERQxQgIiJiiAJEREQMUYCIiIghChARETFEASIiIoYoQERExBAFiIiIGKIAERERQxQgIiJiiAJEREQMUYCIiIghChARETFEASIiIoYoQERExBAFiIiIGKIAERERQxQgIiJiiAJEREQMMRwgH374Ic3NzX1Zi4iInEGCjD6wrq4Ot9t9ynVlZWWsWrWK2tpaYmJiuOyyy0hISDBcpIiIBJ5eB0hjYyNr1qxh586dVFVVkZSUxDe+8Q2sVisABw8e5KmnnuK8884jNzeXY8eOaaYiIjII9TpAnnnmGdLT0xkzZgyzZs2irKyM9vZ2b4C8+eabzJ8/n0suuaTPixURkcDR62Mghw4dYu7cuYSHhzNixAguuOAC7HY7AG63m+LiYuLj43nxxRf5n//5HzZv3tznRYuIyMDr9QxkypQp/PWvf8VqtTJ58mTGjh2LyWQCOo6LuFwuli9fzpVXXkl7eztLliyhvb2dOXPmdOqnoqKiy9/hcDhITEz02ybB5cLd1kZ9TU1vh9CvwpxO7EBNTQ1tYV3ntcPh8NuP02kBzNQeq8Ud4unbIvtQfasJsOB0OqmoaOiyXXfjHYyG2pg13sGv1wHywx/+kO3bt7Nq1SqeeeYZIiMjueOOO4iOjvbuxpo/fz7Tpk0DoLa2lo0bN/oESHJycre/y28biwVLcDBxcXG9HUL/soDHJMYAABomSURBVFcCdNTZzZj9jdd+xAk0ER0TTUxo4J59bW52Aw3Y7XaSk+1+2/ZkGxhshtqYNd7BrdfvRGazmdzcXMaPH89vfvMbhg8fzpo1awCw2WyEhoYSERHhbR8REcHx48f7rmIREQkIvQ6Qw4cPe3+2WCyEhYV5d2GZTCZmzpzJtm3b8Hg8uN1uPvvsM0aPHt13FYuISEDo9S6s559/HqvVSlNTE0VFRTQ1NXHXXXd5119++eX8+c9/5oEHHsDtdhMZGcn111/fp0WLiMjA63WALFq0iNraWt59913mzZtHamoqZvP/TWSioqL49a9/TWVlJcFnwjEKERExxNCV6NHR0URGRpKQkNApPE4wmUxD7mCSiMhQY/h0nvPOO4/Q0NC+rEVERM4ghu+FNWzYsL6sQ0REzjCBe0GBiIgENAWIiIgYogARERFDFCAiImKIAkRERAxRgIiIiCEKEBERMUQBIiIihhi+kFBE+tbikhK+bGsb6DJ67JrUVBJ1N4ohTQEiEiA+cjiobmkZ6DJ67MKkJAXIEKcAEQkg6eHh3JmZOdBl+PVRVRX/Oul7gWTo0jEQERExRAEiIiKGKEBERMQQBYiIiBiiABEREUMUICIiYogCREREDFGAiIiIIQoQERExxHCAfPjhhzQ3N/dlLSIicgYxHCB1dXW43e4u1x8/fpzHH3+cJUuWGP0VIiISwHp9L6z6+no++OADvvjiCw4fPkxSUhJXXHEFVqu1U7t//etffPnll1RVVfVZsSIiEjh6PQN59tlnCQ0NZcKECXzzm98kKSkJl8vVqc3Bgwc5dOgQs2fP7rNCRUQksPQ6QMrKypg1axZhYWEkJCSQl5eHzWbzrne73bz66qtcd911mEymPi1WREQCR693YWVnZ/OXv/yFsLAwMjMzmTBhAmbz/+XQunXrGDlyJOnp6ezZs6fLfioqKrpc53A4SExM9NsmweXC3dZGfU1Nb4fQr8KcTuxATU0NbWFd57XD4fDbj9NpAczUHqvFHeLp2yL7UH2rCbDgdDqpqGjosl134x2Muhuzy+WijY5tJZA5GxuBjjornM4u2w2153iojRcMBMiNN97Irl27WLlyJS+//DIhISHceeedxMbGUl9fz+rVq7n33nu77Sc5OfnrtbFYsAQHExcX15vy+5+9EqCjzm7G7G+89iNOoInomGhiQgP37GtzsxtowG63k5xs99u2J9vAYONvzJZDhwg+A7Zpu9sNdXXExcWRHBnpt+1Qe46H2nh7/U5kMpmYOnUqGRkZ3H///YwePZq1a9cCsHPnTtra2nj++ed56qmn2LBhAwcPHuTpp5/u88JFRGRg9XoGUlJSQnp6OtARJmazGYvFAsCUKVOIj4/3tt26dSvl5eVcdNFFfVSuiIgEil4HyKuvvkp7ezutra3s3LkTk8nEnXfeCUB0dDTR0dHetocOHaKuro7x48f3XcUiIhIQeh0gv/rVr3A6nbz99tucf/75JCcnd3m21dlnn8306dO/dpEiIhJ4eh0gAHa7HbvdzrBhw/yeqhsZGUlkNwfZRETkzGT4dJ7zzjuP0NDQvqxFRETOIIZmIADDhg3ryzpEROQME7gXFIiISEBTgIiIiCEKEBERMUQBIiIihihARETEEAWIiIgYogARERFDFCAiImKI4QsJRU6rlgo4smSgq+i5oAgYefNAVyGBrOEwbP/bQFfRc9ZImH2X3yYKEAlM7U6o2zTQVfRcSOxAVyCBruVLOPgRELjfKOrlAcITu22mAJHAFn8ZxJwz0FX4V/o/4Goc6CrkTDHxGhh94UBX4d8nj0Pbl9020zEQERExRAEiIiKGKEBERMQQBYiIiBiiABEREUMUICIiYogCREREDFGAiIiIIQoQERExxHCAfPDBBzQ3N/dlLSIicgYxfCsTp9OJ2+3utMztdvPhhx+ya9cuGhoaSEhI4JJLLiEtLe1rFyoiIoGl1wFy7NgxVqxYQUFBASUlJSQmJvKtb32L0NBQWlpaKCwsZM6cOcTExPD555/zxBNP8Nvf/pZhw4adjvpFRGSA9DpAnn32WbKyspg6dSpz586lrKzMOxMJCwvjjjvu8LYdM2YM27dvZ//+/eTm5vZd1SIiMuB6fQzkyJEjZGdnY7VaGTZsGLNmzcJms52ybVNTE/X19cTHx3/tQkVEJLD0egYyc+ZM/vSnP2Gz2UhPT2fSpEkEBwf7tPN4PLz00ktMmzbtlMdAKioquvwdDoeDxMREv20SXC7cbW3U19T0dgj9KszpxA7U1NTQFtZ1XjscDr/9OJ0WwEztsVrcIYH7fQL1rSbAgtPppKKioct23Y03uLWGODqOtTW5A/s5jmprw+xxUeVne4Xux+xyuWijY1sJZM7GjlvX19TUUOF0dtmuu/GW7Qyjam9on9Z2OiVkNjNyWlOX67vdpuv+d5tudNIU4M9xVFsbZlf323SvA+R73/seu3fvZvny5bz11lu8/vrr/OxnPyMhIcHbxuPxsGTJEurr67nzzjtP2U9ycnK3v8tvG4sFS3AwcXFxvR1C/7JXAnTU2c2Y/Y3XfsQJNBEdE01MaOCefW1udgMN2O12kpPtftv6fX6dTjgGdrsde0yAP8fHg8Fl+drbtOXQIYLPgG3a7nZDXR1xcXEkR0b6betvvIc2wJH8vq7u9IkbHkpysv9juX63gaA6AOzhduwB/hwTHAx0v00bOgtr/PjxFBUVMX/+fN566y0++ugjrr32Wu/6N998k4MHD/Kzn/0Mq9Vq5FeIyBDgMcElt0OY/xwaUE0NsOIvA11FYOr1R9m9e/d2+n97ezshISHe/7/99tvs3r2bn/70p4SFhX39CkVEJCD1egbyzjvvUF9fT3t7O9u2bcNut/Ptb38b6DjFd+XKlURGRvLwww97H3PZZZdx9tln913VIiIy4HodIL/4xS9obW3lzTff5KKLLup0htWwYcP43e9+5/MYu93/vnARETnzGDoGEhISQlhYmE8wmM3mgD8AKCIifcPw6TwXX3yxjnGIiAxhhu+Fpd1SIiJDW+BeUCAiIgFNASIiIoYoQERExBAFiIiIGKIAERERQxQgIiJiiAJEREQMUYCIiIghChARETFEASIiIoYoQERExBAFiIiIGKIAERERQxQgIiJiiAJEREQMUYCIiIghChARETFEASIiIoYoQERExBAFiIiIGGI4QD744AOam5v7shYRETmDBBl9oNPpxO12+yxva2vj7bffZteuXYSFhXHJJZeQnZ39tYoUEZHA0+sAqa6uZtmyZezevZs9e/aQmJjId77zHUJDQwFYvnw5+/bt49Zbb6WyspIXXniB++67j+Tk5D4vXkREBk6vd2E999xzpKamMmPGDG6++WaysrLweDwAuN1uPvnk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 \ No newline at end of file diff --git a/testData/linux/render-to-stream.png b/testData/linux/render-to-stream.png new file mode 100644 index 0000000..39dbe20 Binary files /dev/null and b/testData/linux/render-to-stream.png differ