diff --git a/.azure/azure-linux-template.yml b/.azure/azure-linux-template.yml index a343c5a..2911dac 100644 --- a/.azure/azure-linux-template.yml +++ b/.azure/azure-linux-template.yml @@ -17,17 +17,14 @@ jobs: stack-lts-14: BUILD: stack STACK_YAML: stack-lts-14.yaml - stack-lts-13: - BUILD: stack - STACK_YAML: stack-lts-13.yaml - stack-nightly: - BUILD: stack - ARGS: --resolver nightly --no-run-tests + #stack-nightly: + # BUILD: stack + # ARGS: --resolver nightly --no-run-tests maxParallel: 6 steps: - task: Cache@2 inputs: - key: ${{ parameters.name }} | ${{ parameters.vmImage }} | $(ARGS) | $(STACK_YAML) | stack-root | $(Agent.OS) + key: ${{ parameters.name }} | ${{ parameters.vmImage }} | $(ARGS) | $(STACK_YAML) | stack-root | $(Agent.OS) | v2 path: $(STACK_ROOT) cacheHitVar: CACHE_RESTORED displayName: Cache stack root diff --git a/.azure/azure-osx-template.yml b/.azure/azure-osx-template.yml index 0484a91..0274139 100644 --- a/.azure/azure-osx-template.yml +++ b/.azure/azure-osx-template.yml @@ -11,15 +11,12 @@ jobs: BUILD: stack STACK_YAML: stack.yaml ARGS: --pedantic - stack-lts-15: + #stack-lts-16: + # BUILD: stack + # STACK_YAML: stack-lts-16.yaml + stack-lts-17: BUILD: stack - STACK_YAML: stack-lts-14.yaml - stack-lts-14: - BUILD: stack - STACK_YAML: stack-lts-14.yaml - stack-lts-13: - BUILD: stack - STACK_YAML: stack-lts-13.yaml + STACK_YAML: stack-lts-17.yaml maxParallel: 6 steps: - task: Cache@2 diff --git a/.azure/azure-windows-template.yml b/.azure/azure-windows-template.yml index f4429e2..e8c85af 100644 --- a/.azure/azure-windows-template.yml +++ b/.azure/azure-windows-template.yml @@ -7,10 +7,11 @@ jobs: vmImage: ${{ parameters.vmImage }} strategy: matrix: + stack: + BUILD: stack + STACK_YAML: stack.yaml + ARGS: --pedantic # lts-16.12 fails intermittently on windows. :( - # stack: - # BUILD: stack - # ARGS: --pedantic # lts-15 fails due to segfaults. Think it is a GHC issue. # stack-lts-15: # BUILD: stack @@ -20,10 +21,6 @@ jobs: BUILD: stack STACK_YAML: stack-lts-14.yaml ARGS: --no-run-tests - stack-lts-13: - BUILD: stack - STACK_YAML: stack-lts-13.yaml - ARGS: --no-run-tests maxParallel: 6 steps: - task: Cache@2 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f13892a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,20 @@ +version: 2 +updates: +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily + time: "11:00" + open-pull-requests-limit: 10 +- package-ecosystem: elm + directory: "/viewer-elm" + schedule: + interval: daily + time: "11:00" + open-pull-requests-limit: 10 +- package-ecosystem: elm + directory: "/playground/viewer-elm" + schedule: + interval: daily + time: "11:00" + open-pull-requests-limit: 10 diff --git a/.github/workflows/stackage-nightly.yml b/.github/disabled-workflows/stackage-nightly.yml similarity index 55% rename from .github/workflows/stackage-nightly.yml rename to .github/disabled-workflows/stackage-nightly.yml index 6bec136..85cba61 100644 --- a/.github/workflows/stackage-nightly.yml +++ b/.github/disabled-workflows/stackage-nightly.yml @@ -1,8 +1,6 @@ name: Stackage nightly on: - push: - branches: [ master ] pull_request: branches: [ master ] @@ -33,11 +31,27 @@ jobs: ${{ runner.os }}- - name: Build nightly + env: + ACTIONS_ALLOW_UNSECURE_COMMANDS: true run: | rm -fr discord-bot playground dist-newstyle - rm -f stack.yaml && stack init --resolver nightly - # Build twice because the stackage servers are incredibly unreliable. - stack build --resolver nightly --haddock --test --only-dependencies --bench --no-run-benchmarks || stack build --resolver nightly --haddock --test --only-dependencies --bench --no-run-benchmarks + rm -f stack.yaml + stack init --resolver nightly \ + && echo "::set-env name=passed::true" \ + || echo "::set-env name=passed::false" + stack build --resolver nightly --haddock --test --only-dependencies --bench --no-run-benchmarks \ + && echo "::set-env name=passed::true" \ + || echo "::set-env name=passed::false" - name: Test nightly + if: env.passed == 'true' run: | stack build --resolver nightly --haddock --test --bench --no-run-benchmarks + - name: Report status + if: env.passed == 'false' + uses: mshick/add-pr-comment@v1 + with: + message: | + Warning: Failed to build against stack nightly. + repo-token: ${{ secrets.GITHUB_TOKEN }} + repo-token-user-login: 'github-actions[bot]' # The user.login for temporary GitHub tokens + allow-repeats: false # This is the default diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index d6c497a..5992998 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -9,17 +9,17 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions/setup-haskell@v1 with: - ghc-version: '8.8' + ghc-version: 'latest' cabal-version: 'latest' - - uses: jorelali/setup-elm@v2 + - uses: jorelali/setup-elm@v3 with: elm-version: 0.19.1 - name: Cache - uses: actions/cache@v2 + uses: actions/cache@v3.0.11 with: path: | ~/.cabal @@ -32,58 +32,62 @@ jobs: - name: Install dependencies run: | cabal v2-update - cabal v2-build --enable-coverage --only-dependencies + cabal v2-build --only-dependencies + mkdir pages - name: Build run: | - cabal v2-build --enable-coverage + cabal v2-build - - name: Generate HPC files - run: | - set -o xtrace - for gold in examples/*.golden; do - cabal v2-exec --enable-coverage -- ghc -package reanimate "${gold%.golden}.hs" --make - reanimate_datadir=$(pwd) ${gold%.golden} test > /dev/null - done; + # - name: Generate HPC files + # run: | + # set -o xtrace + # for gold in examples/golden/*.golden; do + # BASE=examples/$(basename $gold) + # BASE=${BASE%.golden} + # cabal v2-exec --enable-coverage -- ghc -package reanimate "$BASE.hs" --make + # reanimate_datadir=$(pwd) ${BASE%.hs} test > /dev/null + # done; - - name: Merge HPC files - run: hpc sum --union *.tix --output=total.tix + # - name: Merge HPC files + # run: hpc sum --union *.tix --output=total.tix - - name: Report - run: | - hpc report --hpcdir $(find dist-newstyle -name 'hpc')/vanilla/mix/*/ total.tix + # - name: Report + # run: | + # hpc report --hpcdir $(find dist-newstyle -name 'hpc')/vanilla/mix/*/ total.tix - - name: Markup - run: | - hpc markup --destdir=hpc --hpcdir $(find dist-newstyle -name 'hpc')/vanilla/mix/*/ total.tix + # - name: Markup + # run: | + # hpc markup --destdir=hpc --hpcdir $(find dist-newstyle -name 'hpc')/vanilla/mix/*/ total.tix - - name: HPC Badge - run: | - COVERAGE=$(cat hpc/hpc_index.html | tr '\n' ' ' | sed "s/.*Program Coverage Total.*>\([0-9]\{1,3\}\)%.*>\([0-9]\{1,3\}\)%.*>\([0-9]\{1,3\}\)%.*/\1/") - echo "\ - { \"schemaVersion\": 1, \ - \"label\": \"api tests\", \ - \"message\": \"$COVERAGE%\", \ - \"color\": \"success\" \ - }" > hpc/hpc_badge.json + # - name: HPC Badge + # run: | + # COVERAGE=$(cat hpc/hpc_index.html | tr '\n' ' ' | sed "s/.*Program Coverage Total.*>\([0-9]\{1,3\}\)%.*>\([0-9]\{1,3\}\)%.*>\([0-9]\{1,3\}\)%.*/\1/") + # echo "\ + # { \"schemaVersion\": 1, \ + # \"label\": \"api tests\", \ + # \"message\": \"$COVERAGE%\", \ + # \"color\": \"success\" \ + # }" > pages/hpc_badge.json - name: Haddock Badge run: | # Stack is required because cabal doesn't build docs for dependencies. # Without those, coverage will not reach 100% stack build --haddock --only-dependencies - stack haddock 2>&1 | grep ") in " | sort -hr > hpc/haddock.txt - COVERAGE=$(awk '{good += $3; total += $5} END {printf "%.0f", good/total*100}' hpc/haddock.txt) + stack haddock 2>&1 | grep ") in " | sort -hr > pages/haddock.txt + COVERAGE=$(awk '{good += $3; total += $5} END {printf "%.0f", good/total*100}' pages/haddock.txt) echo "\ { \"schemaVersion\": 1, \ \"label\": \"api docs\", \ \"message\": \"$COVERAGE%\", \ \"color\": \"success\" \ - }" > hpc/haddock_badge.json + }" > pages/haddock_badge.json # Is there a way to reuse the script from playground.yml? - name: Playground run: | + sudo apt-get update sudo apt-get -y install texlive texlive-latex-base texlive-latex-extra CWD=`pwd` stack build @@ -94,12 +98,12 @@ jobs: cd viewer-elm npm install npm run build - mkdir $CWD/hpc/playground - cp dist/* $CWD/hpc/playground/ + mkdir $CWD/pages/playground + cp dist/* $CWD/pages/playground/ - name: Deploy 🚀 - uses: JamesIves/github-pages-deploy-action@3.5.9 + uses: JamesIves/github-pages-deploy-action@v4.4.0 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} BRANCH: gh-pages # The branch the action should deploy to. - FOLDER: hpc # The folder the action should deploy. + FOLDER: pages # The folder the action should deploy. diff --git a/.github/workflows/haskell.yml b/.github/workflows/haskell.yml index b4a6a01..ee97e90 100644 --- a/.github/workflows/haskell.yml +++ b/.github/workflows/haskell.yml @@ -12,14 +12,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions/setup-haskell@v1 with: - ghc-version: '8.8.2' + ghc-version: 'latest' cabal-version: '3.0' - name: Cache - uses: actions/cache@v1 + uses: actions/cache@v3.0.11 env: cache-name: cache-cabal with: diff --git a/.github/workflows/playground.yml b/.github/workflows/playground.yml index ed794c2..851d679 100644 --- a/.github/workflows/playground.yml +++ b/.github/workflows/playground.yml @@ -10,17 +10,17 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions/setup-haskell@v1 with: ghc-version: '8.8' cabal-version: 'latest' - - uses: jorelali/setup-elm@v2 + - uses: jorelali/setup-elm@v3 with: elm-version: 0.19.1 - name: Cache - uses: actions/cache@v2 + uses: actions/cache@v3.0.11 with: path: | ~/.stack @@ -30,6 +30,7 @@ jobs: - name: Install dependencies run: | + sudo apt-get update sudo apt-get -y install texlive texlive-latex-base texlive-latex-extra stack build --only-dependencies (cd playground && stack build --only-dependencies) diff --git a/.github/workflows/site-checks.yml b/.github/workflows/site-checks.yml deleted file mode 100644 index 0f1e781..0000000 --- a/.github/workflows/site-checks.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Site checker - -on: - schedule: - - cron: "0 * * * *" - push: - branches: [ master, feature-ci ] - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Use Node.js 10.x - uses: actions/setup-node@v1 - with: - node-version: 10.x - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get -y install w3c-linkchecker - - name: Check websocket connection - run: | - set -o xtrace - wget https://github.com/vi/websocat/releases/download/v1.6.0/websocat_amd64-linux-static -O websocat - chmod +x websocat - ./websocat -q -uU "wss://reanimate.clozecards.com/ws/" - - name: Check links at reanimate.github.io - run: checklink --quiet --broken "https://reanimate.github.io/" >> failures.txt - - name: Check links at reanimate.readthedocs.io - run: checklink --quiet --broken "https://reanimate.readthedocs.io/" >> failures.txt - # Haddock generates 404 links by default. :( - #- name: Check links in haddock docs - # run: checklink --quiet --broken "https://hackage.haskell.org/package/reanimate/docs/Reanimate.html" >> failures.txt - - name: Check for failures - run: | - if [ -s failures.txt ]; then - cat failures.txt - exit 1 - fi diff --git a/.gitignore b/.gitignore index 8ee1bf9..db30022 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,23 @@ -.stack-work +dist +dist-* +cabal-dev +*.o +*.hi +*.hie +*.chi +*.chs.h +*.dyn_o +*.dyn_hi +.hpc +.hsenv +.cabal-sandbox/ +cabal.sandbox.config +*.prof +*.aux +*.hp +*.eventlog +.stack-work/ +cabal.project.local +cabal.project.local~ +.HTF/ +.ghc.environment.* \ No newline at end of file diff --git a/ChangeLog.md b/ChangeLog.md index c0ace79..cff4d03 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,60 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and the project adheres to the [Haskell Package Versioning Policy (PVP)](https://pvp.haskell.org) +## 1.1.7.0 -- 2022-10-15 + + * Derive `Eq` for APolygon + +## 1.1.6.0 -- 2022-06-02 + + * Drop hgeometry dependency. + +## 1.1.4.0 -- 2021-03-11 + + * Fix build issue on M1 MacOS. + * Improve documentation. + * Fix bug in 'reanimateLiveEntry'. + * Simplify 'mkRect' definition. + +## 1.1.3.2 -- 2021-01-14 + + * Add flag for disabling the HGeometry dependency. + +## 1.1.3.1 -- 2021-01-04 + + * Fixed performance issue on Windows. + * Fixed live viewer with Firefox. + +## 1.1.3.0 -- 2021-01-04 + +## Added + * Reanimate.reanimateLive + * Reanimate.reanimateLiveEntry + +## 1.1.2.0 -- 2020-10-27 + +### Added + + * Reanimate.LaTeX.latexCfgChunks. + +### Other/Non-visible + + * xeLaTeX bug fixes. + * Documentation for external data sets. + +## 1.1.1.0 -- 2020-10-10 + +### Added + + * External icon sets. + * Font configurations. + +### Other/Non-visible + + * Improved unicode support. + * Improved support for filter effects. + * Bug fix in the path parser. + ## 1.1.0.0 -- 2020-10-07 ### Added diff --git a/Dockerfile.base b/Dockerfile.base new file mode 100644 index 0000000..d837f01 --- /dev/null +++ b/Dockerfile.base @@ -0,0 +1,26 @@ +FROM haskell:8.8.4 + +ENV LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 \ + PATH=/root/.local/bin:$PATH \ + CUDA_PATH=/usr/local/cuda-$CUDA_VERSION \ + CPATH=$JVM_PATH/include:$JVM_PATH/include/linux:$LLVM_PATH/include \ + DEBIAN_FRONTEND=noninteractive + + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + libblas-dev liblapack-dev ffmpeg \ + curl texlive texlive-latex-base texlive-latex-extra \ + texlive-fonts-extra texlive-science texlive-xetex \ + texlive-latex-recommended texlive-lang-english \ + texlive-lang-chinese texlive-plain-generic \ + unzip + +WORKDIR /src + +# Install reanimate dependencies and cache the layer +ADD reanimate.cabal stack-lts-16.yaml ./ +ADD stack-lts-16.yaml ./stack.yaml +RUN stack build --only-dependencies --no-install-ghc --system-ghc --haddock +RUN rm reanimate.cabal stack.yaml diff --git a/Dockerfile.discord b/Dockerfile.discord index dbb12c8..0a6ec6b 100644 --- a/Dockerfile.discord +++ b/Dockerfile.discord @@ -1,29 +1,11 @@ -FROM haskell:8.8.4 - -ENV LANG=C.UTF-8 \ - LC_ALL=C.UTF-8 \ - PATH=/root/.local/bin:$PATH \ - CUDA_PATH=/usr/local/cuda-$CUDA_VERSION \ - CPATH=$JVM_PATH/include:$JVM_PATH/include/linux:$LLVM_PATH/include \ - DEBIAN_FRONTEND=noninteractive - - -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - libblas-dev liblapack-dev ffmpeg \ - curl blender povray texlive texlive-latex-base texlive-latex-extra \ - texlive-fonts-extra texlive-science texlive-xetex \ - texlive-latex-recommended texlive-lang-english \ - texlive-lang-chinese texlive-plain-generic +FROM reanimate/base WORKDIR /src # Install reanimate dependencies and cache the layer -ADD reanimate.cabal stack.yaml ./ -RUN for i in 1 2 3; \ - do \ - stack build --only-dependencies --no-install-ghc --system-ghc --haddock && break; \ - done +ADD reanimate.cabal ./ +ADD stack-lts-16.yaml ./stack.yaml +RUN stack build --only-dependencies --no-install-ghc --system-ghc --haddock # Install discord-bot dependencies and cache the layer ADD discord-bot/discord-bot.cabal discord-bot/stack.yaml ./discord-bot/ @@ -33,6 +15,8 @@ RUN cd discord-bot && \ # Add source after dependencies have been installed as to not invalidate the caches ADD data data ADD src src +ADD unix unix +ADD windows windows ADD .git .git ADD viewer-elm viewer-elm ADD Setup.hs ./ diff --git a/Dockerfile.playground b/Dockerfile.playground index c52800e..ea03415 100644 --- a/Dockerfile.playground +++ b/Dockerfile.playground @@ -1,32 +1,13 @@ -FROM haskell:8.8.4 - -ENV LANG=C.UTF-8 \ - LC_ALL=C.UTF-8 \ - PATH=/root/.local/bin:$PATH \ - CUDA_PATH=/usr/local/cuda-$CUDA_VERSION \ - CPATH=$JVM_PATH/include:$JVM_PATH/include/linux:$LLVM_PATH/include \ - DEBIAN_FRONTEND=noninteractive - - -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - libblas-dev liblapack-dev ffmpeg \ - curl blender povray texlive texlive-latex-base texlive-latex-extra \ - texlive-fonts-extra texlive-science texlive-xetex \ - texlive-latex-recommended texlive-lang-english \ - texlive-lang-chinese texlive-plain-generic \ - unzip +FROM reanimate/base WORKDIR /src # Install reanimate dependencies and cache the layer -ADD reanimate.cabal stack.yaml ./ -RUN for i in 1 2 3; \ - do \ - stack build --only-dependencies --no-install-ghc --system-ghc --haddock && break || echo "retry"; \ - done +ADD reanimate.cabal ./ +ADD stack-lts-16.yaml ./stack.yaml +RUN stack build --only-dependencies --no-install-ghc --system-ghc --haddock -# Install discord-bot dependencies and cache the layer +# Install playground dependencies and cache the layer ADD playground/playground.cabal playground/stack.yaml ./playground/ RUN cd playground && \ stack build --only-dependencies --no-install-ghc --system-ghc @@ -34,6 +15,8 @@ RUN cd playground && \ # Add source after dependencies have been installed as to not invalidate the caches ADD data data ADD src src +ADD unix unix +ADD windows windows ADD .git .git ADD viewer-elm viewer-elm ADD Setup.hs ./ diff --git a/README.md b/README.md index a6fb481..674d7eb 100644 --- a/README.md +++ b/README.md @@ -63,11 +63,26 @@ Optionally, you can install one or more of these programs to enable additional f I highly recommend that you install at least 'ffmpeg' and 'latex'. -# Installing / Running an example +# Getting started / Running an example -Reanimate ships with a web-based viewer and automatic code reloading. To get a small demo -up and running, clone the repository, run one of the examples (this will install the library), -and wait for a browser window to open: +Reanimate offers stack templates for getting started with a minimal example and +automatic code reloading. Running the commands below will put a one-line animation +in the 'animate' folder and then display the animation in a browser window. You can +then edit the animation source code and watch the animation update in real time: + +```console +$ stack new animate github:reanimate/plain +$ cd animate/ +$ # both 'cabal repl' and 'stack repl' can be used here: +$ cabal repl +:cmd reanimateLive +``` + +## Running examples from the repository + +Reanimate has a large collection of small examples which are both used for regression testing +and for GIFs in the API reference documentation. You can run these examples by first cloning the +repository and then running the examples as if they were executables: ```console $ git clone https://github.com/reanimate/reanimate.git @@ -76,11 +91,11 @@ $ stack build $ stack ./examples/doc_drawCircle.hs ``` -This should render the `doc_drawCircle` example in a new browser window. If you then change the -animation source code, the browser window will automatically reload and show the updated animation. +This should render the `doc_drawCircle` example in a new browser window. Automatic code reloading +will not be enabled unless you run `:cmd reanimateLive` from a GHCi session. -## Using Cabal +## Running examples from the repository using Cabal It's also possible to use cabal instead of stack: @@ -90,7 +105,7 @@ $ cd reanimate/ $ cabal v2-build $ # Workaround for a cabal bug: https://github.com/haskell/cabal/issues/6235 $ export reanimate_datadir=`pwd` -$ cabal v2-exec -- runhaskell examples/doc_drawCircle.hs --ghc ghc +$ cabal v2-exec -- runhaskell examples/doc_drawCircle.hs ``` ## Using Nix @@ -113,15 +128,15 @@ to pick up reanimate. Now, still within the `nix-shell` you can run: ```console -[nix-shell:./reanimate]$ reanimate_datadir=. runhaskell examples/doc_drawCircle.hs --ghc `which ghc` +[nix-shell:./reanimate]$ reanimate_datadir=. runhaskell examples/doc_drawCircle.hs ``` # Documentation * API reference: https://hackage.haskell.org/package/reanimate/docs/Reanimate.html - * Core concepts: https://reanimate.readthedocs.io/en/latest/introduction/ - * Design overview: https://reanimate.readthedocs.io/en/latest/glue_tut/ + * Core concepts: https://reanimate.readthedocs.io/en/latest/introduction.html + * Design overview: https://reanimate.readthedocs.io/en/latest/glue_tut.html * N-Queens tutorial (somewhat dated, uses reanimate from October 2019): https://williamyaoh.com/posts/2020-05-31-reanimate-nqueens-tutorial.html * You can also ask questions in the discord channel: diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a54cc2e..528a629 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -21,11 +21,11 @@ jobs: vmImage: ubuntu-latest os: linux -- template: ./.azure/azure-osx-template.yml - parameters: - name: macOS - vmImage: macOS-10.14 - os: osx +#- template: ./.azure/azure-osx-template.yml +# parameters: +# name: macOS +# vmImage: macOS-11 +# os: osx - template: ./.azure/azure-windows-template.yml parameters: diff --git a/discord-bot/stack.yaml b/discord-bot/stack.yaml index d3d2f62..5d7e552 100644 --- a/discord-bot/stack.yaml +++ b/discord-bot/stack.yaml @@ -1,4 +1,4 @@ -resolver: lts-16.12 +resolver: lts-16.31 allow-newer: false diff --git a/discord-bot/stack.yaml.lock b/discord-bot/stack.yaml.lock index 787704f..6582b4e 100644 --- a/discord-bot/stack.yaml.lock +++ b/discord-bot/stack.yaml.lock @@ -27,7 +27,7 @@ packages: hackage: req-3.1.0@sha256:b058fd588ccdbced9e7d33b9112cd41960f29ce90acc7d77e2dd7518da171a01,4926 snapshots: - completed: - size: 532377 - url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/16/12.yaml - sha256: f914cfa23fef85bdf895e300a8234d9d0edc2dbec67f4bc9c53f85867c50eab6 - original: lts-16.12 + size: 534126 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/16/31.yaml + sha256: 637fb77049b25560622a224845b7acfe81a09fdb6a96a3c75997a10b651667f6 + original: lts-16.31 diff --git a/docs/changelog-2020-10.md b/docs/changelog-2020-10.md index 1617c37..99f8f3a 100644 --- a/docs/changelog-2020-10.md +++ b/docs/changelog-2020-10.md @@ -8,3 +8,4 @@ Made it easier to use built-in LaTeX fonts as well as TTF/OTF font files. ![Inconsolata](changelog/inconsolata.svg) ![ComputerModern](changelog/computermodern.svg) ![Magnolia](changelog/magnolia.svg) +![Chalkduster](changelog/chalkduster.svg) diff --git a/docs/changelog/chalkduster.svg b/docs/changelog/chalkduster.svg new file mode 100644 index 0000000..8df3377 --- /dev/null +++ b/docs/changelog/chalkduster.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/gifs/doc_biolinum.gif b/docs/gifs/doc_biolinum.gif new file mode 100644 index 0000000..b91c57c Binary files /dev/null and b/docs/gifs/doc_biolinum.gif differ diff --git a/docs/gifs/doc_droidSans.gif b/docs/gifs/doc_droidSans.gif new file mode 100644 index 0000000..3cc28a1 Binary files /dev/null and b/docs/gifs/doc_droidSans.gif differ diff --git a/docs/gifs/doc_droidSerif.gif b/docs/gifs/doc_droidSerif.gif new file mode 100644 index 0000000..ddeb9ea Binary files /dev/null and b/docs/gifs/doc_droidSerif.gif differ diff --git a/docs/gifs/doc_renderObject.gif b/docs/gifs/doc_renderObject.gif new file mode 100644 index 0000000..c885055 Binary files /dev/null and b/docs/gifs/doc_renderObject.gif differ diff --git a/docs/gifs/doc_simpleIcon.gif b/docs/gifs/doc_simpleIcon.gif new file mode 100644 index 0000000..9f7880d Binary files /dev/null and b/docs/gifs/doc_simpleIcon.gif differ diff --git a/docs/gifs/doc_svgLogo.gif b/docs/gifs/doc_svgLogo.gif index 59f14cc..6c2fada 100644 Binary files a/docs/gifs/doc_svgLogo.gif and b/docs/gifs/doc_svgLogo.gif differ diff --git a/docs/gifs/doc_svgLogo2.gif b/docs/gifs/doc_svgLogo2.gif new file mode 100644 index 0000000..78b3ef1 Binary files /dev/null and b/docs/gifs/doc_svgLogo2.gif differ diff --git a/docs/index.md b/docs/index.md index 9b608ba..4fdd2c7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,7 +7,20 @@ # Getting started -## Viewing a basic animation +Reanimate offers stack templates for getting started with a minimal example and +automatic code reloading. Running the commands below will put a one-line animation +in the 'animate' folder and then display the animation in a browser window. You can +then edit the animation source code and watch the animation update in real time: + +```console +$ stack new animate github:reanimate/plain +$ cd animate/ +$ # both 'cabal repl' and 'stack repl' can be used here: +$ cabal repl +:cmd reanimateLive +``` + +## Running examples from the repository ```console $ git clone https://github.com/reanimate/reanimate.git @@ -59,6 +72,38 @@ be able to generate stand-alone video files (but can still view animations in a browser window). Without 'LaTeX' and 'dvisvgm', you won't be able use LaTeX for typesetting. +### Obtaining the dependencies on Windows + +The easiest way to obtain the [FFmpeg](https://www.ffmpeg.org/), +[rsvg-convert](https://wiki.gnome.org/Projects/LibRsvg) and +[ImageMagick](https://imagemagick.org/index.php) dependencies on Windows is to +use the [MSYS2](https://www.msys2.org/) installation that comes with `stack` and +is on the path in the `stack exec` environment, and its package manager +`pacman`. The MSYS2 `librsvg` package provides `rsvg-convert.exe`. + +``` +> stack exec -- pacman --sync --refresh +> stack exec -- pacman --sync mingw64/mingw-w64-x86_64-ffmpeg +> stack exec -- pacman --sync mingw64/mingw-w64-x86_64-librsvg +> stack exec -- pacman --sync mingw64/mingw-w64-x86_64-imagemagick +``` + +However, readily-available versions of FFmpeg on Windows have not been built +with the `--enable-librsvg` option. So, the `Has ffmpeg(rsvg)` dependency will +be `no` on Windows. + +Windows versions of [POV-Ray](http://www.povray.org/), +[Inkscape](https://inkscape.org/), and [Blender](https://www.blender.org/) can +be downloaded and installed and the folders containing the executables added to +the `PATH` environment variable. + +The [MiKTeX project](https://miktex.org/) provides a modern TeX distribution for +Windows, which also includes the [`dvisvgm` tool](https://dvisvgm.de/). The +folder containing the executables must be added to the `PATH` environment +variable. The `ctex` package (for Chinese typesetting) requires the `SimHei` +font, which is provided by the Windows Language Pack for Chinese (Simplified, +China). + ## Rendering animations **reanimate** has builtin support for rendering animations to video files and @@ -93,6 +138,6 @@ $ stack ./examples/doc_andThen.hs render --format gif # Learning resources * [API documentation](https://hackage.haskell.org/package/reanimate/docs/Reanimate.html) -* [Core concepts](introduction) +* [Core concepts](introduction.md) * [Examples](https://github.com/reanimate/reanimate/tree/master/examples) -* [Design overview](glue_tut) +* [Design overview](glue_tut.md) diff --git a/docs/introduction.md b/docs/introduction.md index f228cfd..29d1993 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -3,7 +3,7 @@ This document will introduce the foundamental concepts used in **reanimate**. It is assumed that you are already familiar with Haskell. After reading, you will be able to understand all the concepts used in the [API reference documentation](https://hackage.haskell.org/package/reanimate/docs/Reanimate.html). This is not a tutorial, though, and you may have to look at the examples before you can turn these concepts into beautiful animations. -For a bird's eye view on the design philosophy and capabilities of **reanimate**, see [Gluing together animations](/glue_tut/). For help installing **reanimate**, see [Getting started](/#getting-started). +For a bird's eye view on the design philosophy and capabilities of **reanimate**, see [Gluing together animations](glue_tut.md). For help installing **reanimate**, see [Getting started](/#getting-started). ## Animations as Executables diff --git a/docs/tut_equation.md b/docs/tut_equation.md index dcfd215..f294211 100644 --- a/docs/tut_equation.md +++ b/docs/tut_equation.md @@ -363,7 +363,7 @@ API references: [oScaleIn](https://hackage.haskell.org/package/reanimate/docs/Reanimate-Scene.html#v:oScaleIn), [oGrow](https://hackage.haskell.org/package/reanimate/docs/Reanimate-Scene.html#v:oGrow). -Let's use the `oDraw` function to draw text on the screen and let's make sure the text is right-aligned: +Let's use the `oDraw` function to draw text on the screen and let's make sure the text is left-aligned:
 animation :: Animation
@@ -521,4 +521,4 @@ env = addStatic (mkBackground "lightblue") .
       delay += 0;
     }
   },0);
-
\ No newline at end of file
+
diff --git a/examples/bug_quad.golden b/examples/bug_quad.golden
deleted file mode 100644
index 5b42cc1..0000000
--- a/examples/bug_quad.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_addStatic.golden b/examples/doc_addStatic.golden
deleted file mode 100644
index daef1e9..0000000
--- a/examples/doc_addStatic.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_andThen.golden b/examples/doc_andThen.golden
deleted file mode 100644
index 9ec648d..0000000
--- a/examples/doc_andThen.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_applyVar.golden b/examples/doc_applyVar.golden
deleted file mode 100644
index 4064f8e..0000000
--- a/examples/doc_applyVar.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_bellS.golden b/examples/doc_bellS.golden
deleted file mode 100644
index a6edf6a..0000000
--- a/examples/doc_bellS.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_biolinum.hs b/examples/doc_biolinum.hs
new file mode 100755
index 0000000..ed07987
--- /dev/null
+++ b/examples/doc_biolinum.hs
@@ -0,0 +1,13 @@
+#!/usr/bin/env stack
+-- stack runghc --package reanimate
+{-# LANGUAGE OverloadedStrings #-}
+module Main (main) where
+
+import           Reanimate
+import           Reanimate.LaTeX
+import           Reanimate.Builtin.Documentation
+
+main :: IO ()
+main = reanimate $ docEnv $ staticFrame 1 $
+  center $ withStrokeWidth 0 $ withFillOpacity 1 $ scale 4 $
+  latexCfg biolinum "biolinum"
diff --git a/examples/doc_chainT.golden b/examples/doc_chainT.golden
deleted file mode 100644
index 8b2fe0f..0000000
--- a/examples/doc_chainT.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_cividis.golden b/examples/doc_cividis.golden
deleted file mode 100644
index 2a61337..0000000
--- a/examples/doc_cividis.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_constantS.golden b/examples/doc_constantS.golden
deleted file mode 100644
index d0e5437..0000000
--- a/examples/doc_constantS.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_cubicBezierS.golden b/examples/doc_cubicBezierS.golden
deleted file mode 100644
index d8c3aa2..0000000
--- a/examples/doc_cubicBezierS.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_curveS.golden b/examples/doc_curveS.golden
deleted file mode 100644
index 7a9f5f3..0000000
--- a/examples/doc_curveS.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_destroySprite.golden b/examples/doc_destroySprite.golden
deleted file mode 100644
index 71036d6..0000000
--- a/examples/doc_destroySprite.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_drawBox.golden b/examples/doc_drawBox.golden
deleted file mode 100644
index b9eaaa5..0000000
--- a/examples/doc_drawBox.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_drawCircle.golden b/examples/doc_drawCircle.golden
deleted file mode 100644
index 348e1bf..0000000
--- a/examples/doc_drawCircle.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_drawProgress.golden b/examples/doc_drawProgress.golden
deleted file mode 100644
index e5e8ccd..0000000
--- a/examples/doc_drawProgress.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_droidSans.hs b/examples/doc_droidSans.hs
new file mode 100755
index 0000000..91c1d04
--- /dev/null
+++ b/examples/doc_droidSans.hs
@@ -0,0 +1,13 @@
+#!/usr/bin/env stack
+-- stack runghc --package reanimate
+{-# LANGUAGE OverloadedStrings #-}
+module Main (main) where
+
+import           Reanimate
+import           Reanimate.LaTeX
+import           Reanimate.Builtin.Documentation
+
+main :: IO ()
+main = reanimate $ docEnv $ staticFrame 1 $
+  center $ withStrokeWidth 0 $ withFillOpacity 1 $ scale 3 $
+  latexCfg droidSans "droidSans"
diff --git a/examples/doc_droidSerif.hs b/examples/doc_droidSerif.hs
new file mode 100755
index 0000000..035b50d
--- /dev/null
+++ b/examples/doc_droidSerif.hs
@@ -0,0 +1,13 @@
+#!/usr/bin/env stack
+-- stack runghc --package reanimate
+{-# LANGUAGE OverloadedStrings #-}
+module Main (main) where
+
+import           Reanimate
+import           Reanimate.LaTeX
+import           Reanimate.Builtin.Documentation
+
+main :: IO ()
+main = reanimate $ docEnv $ staticFrame 1 $
+  center $ withStrokeWidth 0 $ withFillOpacity 1 $ scale 3 $
+  latexCfg droidSerif "droidSerif"
diff --git a/examples/doc_fadeT.golden b/examples/doc_fadeT.golden
deleted file mode 100644
index d45b7ff..0000000
--- a/examples/doc_fadeT.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_fork.golden b/examples/doc_fork.golden
deleted file mode 100644
index 86778d4..0000000
--- a/examples/doc_fork.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_fromToS.golden b/examples/doc_fromToS.golden
deleted file mode 100644
index 4e84b31..0000000
--- a/examples/doc_fromToS.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_githubIcon.golden b/examples/doc_githubIcon.golden
deleted file mode 100644
index 452ca37..0000000
--- a/examples/doc_githubIcon.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0        
-1        
-2        
-3        
-4        
-5        
-6        
-7        
-8        
-9        
diff --git a/examples/doc_greyscale.golden b/examples/doc_greyscale.golden
deleted file mode 100644
index e20f1e2..0000000
--- a/examples/doc_greyscale.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_haskellLogo.golden b/examples/doc_haskellLogo.golden
deleted file mode 100644
index 0d9a773..0000000
--- a/examples/doc_haskellLogo.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0                    
-1                    
-2                    
-3                    
-4                    
-5                    
-6                    
-7                    
-8                    
-9                    
diff --git a/examples/doc_hsv.golden b/examples/doc_hsv.golden
deleted file mode 100644
index c83286b..0000000
--- a/examples/doc_hsv.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_hsvComponents.golden b/examples/doc_hsvComponents.golden
deleted file mode 100644
index 44d1898..0000000
--- a/examples/doc_hsvComponents.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_hsvMatlab.golden b/examples/doc_hsvMatlab.golden
deleted file mode 100644
index bfaf290..0000000
--- a/examples/doc_hsvMatlab.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_inferno.golden b/examples/doc_inferno.golden
deleted file mode 100644
index 28f0705..0000000
--- a/examples/doc_inferno.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_jet.golden b/examples/doc_jet.golden
deleted file mode 100644
index 81e85bd..0000000
--- a/examples/doc_jet.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_labComponents.golden b/examples/doc_labComponents.golden
deleted file mode 100644
index eb4a66b..0000000
--- a/examples/doc_labComponents.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_latex.golden b/examples/doc_latex.golden
deleted file mode 100644
index 00cef20..0000000
--- a/examples/doc_latex.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0$e^{i\pi}+1=0$
-1$e^{i\pi}+1=0$
-2$e^{i\pi}+1=0$
-3$e^{i\pi}+1=0$
-4$e^{i\pi}+1=0$
-5$e^{i\pi}+1=0$
-6$e^{i\pi}+1=0$
-7$e^{i\pi}+1=0$
-8$e^{i\pi}+1=0$
-9$e^{i\pi}+1=0$
diff --git a/examples/doc_latexAlign.golden b/examples/doc_latexAlign.golden
deleted file mode 100644
index b6c83db..0000000
--- a/examples/doc_latexAlign.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0\begin{align*}R = \frac{{\Delta x}}{{kA}}\end{align*}
-1\begin{align*}R = \frac{{\Delta x}}{{kA}}\end{align*}
-2\begin{align*}R = \frac{{\Delta x}}{{kA}}\end{align*}
-3\begin{align*}R = \frac{{\Delta x}}{{kA}}\end{align*}
-4\begin{align*}R = \frac{{\Delta x}}{{kA}}\end{align*}
-5\begin{align*}R = \frac{{\Delta x}}{{kA}}\end{align*}
-6\begin{align*}R = \frac{{\Delta x}}{{kA}}\end{align*}
-7\begin{align*}R = \frac{{\Delta x}}{{kA}}\end{align*}
-8\begin{align*}R = \frac{{\Delta x}}{{kA}}\end{align*}
-9\begin{align*}R = \frac{{\Delta x}}{{kA}}\end{align*}
diff --git a/examples/doc_lchComponents.golden b/examples/doc_lchComponents.golden
deleted file mode 100644
index ca175a9..0000000
--- a/examples/doc_lchComponents.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_magma.golden b/examples/doc_magma.golden
deleted file mode 100644
index 9b5f30d..0000000
--- a/examples/doc_magma.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_mapA.golden b/examples/doc_mapA.golden
deleted file mode 100644
index 3118183..0000000
--- a/examples/doc_mapA.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_mkText.golden b/examples/doc_mkText.golden
deleted file mode 100644
index c3df0f0..0000000
--- a/examples/doc_mkText.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1te
-2tex
-3text
-4text
-5text
-6text
-7text
-8text
-9text
diff --git a/examples/doc_newSprite.golden b/examples/doc_newSprite.golden
deleted file mode 100644
index 05009fd..0000000
--- a/examples/doc_newSprite.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_newSpriteA'.golden b/examples/doc_newSpriteA'.golden
deleted file mode 100644
index bf9319f..0000000
--- a/examples/doc_newSpriteA'.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_newSpriteA.golden b/examples/doc_newSpriteA.golden
deleted file mode 100644
index 8d03a34..0000000
--- a/examples/doc_newSpriteA.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_newSpriteSVG.golden b/examples/doc_newSpriteSVG.golden
deleted file mode 100644
index daef1e9..0000000
--- a/examples/doc_newSpriteSVG.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_oscillateS.golden b/examples/doc_oscillateS.golden
deleted file mode 100644
index 68b4400..0000000
--- a/examples/doc_oscillateS.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_overlapT.golden b/examples/doc_overlapT.golden
deleted file mode 100644
index e71e783..0000000
--- a/examples/doc_overlapT.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_parA.golden b/examples/doc_parA.golden
deleted file mode 100644
index 0cee818..0000000
--- a/examples/doc_parA.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_parDropA.golden b/examples/doc_parDropA.golden
deleted file mode 100644
index 2fd6829..0000000
--- a/examples/doc_parDropA.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_parLoopA.golden b/examples/doc_parLoopA.golden
deleted file mode 100644
index 79f6254..0000000
--- a/examples/doc_parLoopA.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_parula.golden b/examples/doc_parula.golden
deleted file mode 100644
index 0ae2d05..0000000
--- a/examples/doc_parula.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_pathify.golden b/examples/doc_pathify.golden
deleted file mode 100644
index 007d459..0000000
--- a/examples/doc_pathify.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_pause.golden b/examples/doc_pause.golden
deleted file mode 100644
index c06748d..0000000
--- a/examples/doc_pause.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_pauseAround.golden b/examples/doc_pauseAround.golden
deleted file mode 100644
index 8aa39f8..0000000
--- a/examples/doc_pauseAround.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_pauseAtBeginning.golden b/examples/doc_pauseAtBeginning.golden
deleted file mode 100644
index ba640e8..0000000
--- a/examples/doc_pauseAtBeginning.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_pauseAtEnd.golden b/examples/doc_pauseAtEnd.golden
deleted file mode 100644
index eef1896..0000000
--- a/examples/doc_pauseAtEnd.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_plasma.golden b/examples/doc_plasma.golden
deleted file mode 100644
index f980204..0000000
--- a/examples/doc_plasma.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_play.golden b/examples/doc_play.golden
deleted file mode 100644
index 6fbd8b7..0000000
--- a/examples/doc_play.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_playThenReverseA.golden b/examples/doc_playThenReverseA.golden
deleted file mode 100644
index da1142a..0000000
--- a/examples/doc_playThenReverseA.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_queryNow.golden b/examples/doc_queryNow.golden
deleted file mode 100644
index b726f1b..0000000
--- a/examples/doc_queryNow.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6Now=2.0
-7Now=2.0
-8Now=2.0
-9Now=2.0
diff --git a/examples/doc_repeatA.golden b/examples/doc_repeatA.golden
deleted file mode 100644
index 754e976..0000000
--- a/examples/doc_repeatA.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_reverseA.golden b/examples/doc_reverseA.golden
deleted file mode 100644
index 52181fc..0000000
--- a/examples/doc_reverseA.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_reverseS.golden b/examples/doc_reverseS.golden
deleted file mode 100644
index 1583a0f..0000000
--- a/examples/doc_reverseS.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_rgbComponents.golden b/examples/doc_rgbComponents.golden
deleted file mode 100644
index 1e65709..0000000
--- a/examples/doc_rgbComponents.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_seqA.golden b/examples/doc_seqA.golden
deleted file mode 100644
index 289d74b..0000000
--- a/examples/doc_seqA.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_signalA.golden b/examples/doc_signalA.golden
deleted file mode 100644
index 923213c..0000000
--- a/examples/doc_signalA.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_signalO.hs b/examples/doc_signalO.hs
new file mode 100644
index 0000000..62cc466
--- /dev/null
+++ b/examples/doc_signalO.hs
@@ -0,0 +1,27 @@
+#!/usr/bin/env stack
+-- stack runghc --package reanimate
+module Main(main) where
+
+import Reanimate
+import Reanimate.Scene
+import Reanimate.Builtin.Documentation
+import Control.Lens
+import Control.Monad
+
+main :: IO ()
+main = reanimate $ docEnv $ scene $ do
+  objs <- waitOn $ replicateM 3 $ do
+    obj1 <- newObject $ mkCircle 1
+    oModifyS obj1 $ oEasing .= id
+    oModifyS obj1 $ oRightX .= screenRight
+    fork $ oShowWith obj1 oFadeIn
+    fork $ oTweenS obj1 2 $ \t ->
+      oLeftX %= \origin -> fromToS origin screenLeft t
+    wait 1
+    oHideWith obj1 oFadeOut
+    wait (-1.5)
+    return obj1
+  dur <- queryNow
+  wait (-dur)
+  forM_ objs $ \obj -> do
+    signalO obj dur $ curveS 2
diff --git a/examples/doc_signalS.hs b/examples/doc_signalS.hs
new file mode 100755
index 0000000..51d1a0f
--- /dev/null
+++ b/examples/doc_signalS.hs
@@ -0,0 +1,13 @@
+#!/usr/bin/env stack
+-- stack runghc --package reanimate
+module Main(main) where
+
+import Reanimate
+import Reanimate.Builtin.Documentation
+
+main :: IO ()
+main = reanimate $ docEnv $ playThenReverseA $ scene $ do
+  sprite <- fork $ newSpriteA $ drawCircle
+  signalS sprite 1 (curveS 2)
+  wait 1
+  signalS sprite 1 (powerS 2)
diff --git a/examples/doc_simpleIcon.hs b/examples/doc_simpleIcon.hs
new file mode 100755
index 0000000..6f8bc54
--- /dev/null
+++ b/examples/doc_simpleIcon.hs
@@ -0,0 +1,13 @@
+#!/usr/bin/env stack
+-- stack runghc --package reanimate
+module Main(main) where
+
+import Reanimate
+import Reanimate.External
+import Codec.Picture.Types
+
+main :: IO ()
+main = reanimate $ staticFrame 1 $
+  let icon = "cplusplus" in mkGroup
+  [ mkBackgroundPixel (promotePixel $ simpleIconColor icon)
+  , withFillOpacity 1 $ simpleIcon icon ]
diff --git a/examples/doc_simpleVar.golden b/examples/doc_simpleVar.golden
deleted file mode 100644
index e1a2b1b..0000000
--- a/examples/doc_simpleVar.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_sinebow.golden b/examples/doc_sinebow.golden
deleted file mode 100644
index 917e4df..0000000
--- a/examples/doc_sinebow.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_slideDownT.golden b/examples/doc_slideDownT.golden
deleted file mode 100644
index 1c95696..0000000
--- a/examples/doc_slideDownT.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_slideLeftT.golden b/examples/doc_slideLeftT.golden
deleted file mode 100644
index 419ccac..0000000
--- a/examples/doc_slideLeftT.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_slideUpT.golden b/examples/doc_slideUpT.golden
deleted file mode 100644
index cc99cce..0000000
--- a/examples/doc_slideUpT.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_spriteE.golden b/examples/doc_spriteE.golden
deleted file mode 100644
index 224fa0e..0000000
--- a/examples/doc_spriteE.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_spriteMap.golden b/examples/doc_spriteMap.golden
deleted file mode 100644
index 7e8c6f6..0000000
--- a/examples/doc_spriteMap.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_spriteTween.golden b/examples/doc_spriteTween.golden
deleted file mode 100644
index 5e489b6..0000000
--- a/examples/doc_spriteTween.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_spriteVar.golden b/examples/doc_spriteVar.golden
deleted file mode 100644
index 4064f8e..0000000
--- a/examples/doc_spriteVar.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_spriteZ.golden b/examples/doc_spriteZ.golden
deleted file mode 100644
index fedd525..0000000
--- a/examples/doc_spriteZ.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_svgLogo.golden b/examples/doc_svgLogo.golden
deleted file mode 100644
index 7411ba7..0000000
--- a/examples/doc_svgLogo.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0                                                                          
-1                                                                          
-2                                                                          
-3                                                                          
-4                                                                          
-5                                                                          
-6                                                                          
-7                                                                          
-8                                                                          
-9                                                                          
diff --git a/examples/doc_svgLogo.hs b/examples/doc_svgLogo.hs
old mode 100755
new mode 100644
diff --git a/examples/doc_svgLogo2.hs b/examples/doc_svgLogo2.hs
new file mode 100755
index 0000000..519fe24
--- /dev/null
+++ b/examples/doc_svgLogo2.hs
@@ -0,0 +1,15 @@
+#!/usr/bin/env stack
+-- stack runghc --package reanimate
+
+module Main (main) where
+
+import Reanimate
+import Reanimate.Builtin.Documentation
+import Reanimate.External
+
+main :: IO ()
+main =
+  reanimate $
+    docEnv $
+      staticFrame 1 $
+        withFillOpacity 1 $ svgLogo "cassandra"
diff --git a/examples/doc_turbo.golden b/examples/doc_turbo.golden
deleted file mode 100644
index 4c56b19..0000000
--- a/examples/doc_turbo.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_unVar.golden b/examples/doc_unVar.golden
deleted file mode 100644
index d113099..0000000
--- a/examples/doc_unVar.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_viridis.golden b/examples/doc_viridis.golden
deleted file mode 100644
index 9d7823c..0000000
--- a/examples/doc_viridis.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_wait.golden b/examples/doc_wait.golden
deleted file mode 100644
index aa22cfe..0000000
--- a/examples/doc_wait.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_waitOn.golden b/examples/doc_waitOn.golden
deleted file mode 100644
index 6fbd8b7..0000000
--- a/examples/doc_waitOn.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_writeVar.golden b/examples/doc_writeVar.golden
deleted file mode 100644
index 7e87235..0000000
--- a/examples/doc_writeVar.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/doc_xyzComponents.golden b/examples/doc_xyzComponents.golden
deleted file mode 100644
index a1668bc..0000000
--- a/examples/doc_xyzComponents.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/expression.hs b/examples/expression.hs
new file mode 100644
index 0000000..ed090ad
--- /dev/null
+++ b/examples/expression.hs
@@ -0,0 +1,101 @@
+#!/usr/bin/env stack
+-- stack runghc --package reanimate
+{-# LANGUAGE OverloadedStrings #-}
+module Main (main) where
+
+import           Codec.Picture                   (PixelRGBA8 (PixelRGBA8))
+import           Control.Arrow                   ((&&&))
+import           Control.Lens                    ((.~))
+import           Control.Monad                   (when)
+import           Data.Foldable                   (forM_)
+import qualified Data.Text                       as T
+import           Reanimate
+import           Reanimate.Builtin.Documentation (docEnv)
+import           Reanimate.LaTeX                 (mathChunks)
+import           Reanimate.Scene
+
+import           Data.Bifoldable                 (Bifoldable (..), biforM_)
+import           Data.Bifunctor                  (Bifunctor (..))
+import           Data.Bitraversable              (Bitraversable (..))
+
+data Expr op v
+  = Var v
+  | BinOp (Expr op v) op (Expr op v)
+  | Paren op (Expr op v) op
+  deriving (Show, Eq)
+
+data Op = Add | Minus | LeftParen | RightParen deriving (Show, Eq)
+
+oNewExpr :: Scene s (Expr (Op, Object s SVG) (T.Text, Object s SVG))
+oNewExpr = bothAlongside (mapM (oNew . translate (-4) 0 . scale 2) . mathChunks)
+         $ bimap (id &&& op2LaTeX) (id &&& id) expr
+  where bothAlongside f = fmap runBiAlongside . fmap runBoth . f . Both . BiAlongside
+        expr = Var "x" `minus` paren (Var "y" `add` Var "x")
+
+main :: IO ()
+main = reanimate $ docEnv $ mapA (withFillOpacity 1 . withStrokeWidth 0) $ scene $ do
+  expr <- oNewExpr
+  forM_ (Both $ BiAlongside expr) (`oShowWith` oDraw)
+  let changeColor t = oContext .~ withFillColorPixel (PixelRGBA8 (round (t * 255)) 0 0 255)
+  let changeColorRev t = changeColor (1 - t)
+  waitOn $ biforM_ expr pure $ \(x, o) ->
+    when (x == "x") $ fork $ do
+      oTween o 1 changeColor
+      oTween o 1 changeColorRev
+  forM_ (Both $ BiAlongside expr) (fork . (`oHideWith` oFadeOut))
+
+-- Some supportive functions.
+
+add, minus :: Expr Op v -> Expr Op v -> Expr Op v
+add x y = BinOp x Add y
+minus x y = BinOp x Minus y
+
+paren :: Expr Op v -> Expr Op v
+paren x = Paren LeftParen x RightParen
+
+op2LaTeX :: Op -> T.Text
+op2LaTeX Add        = "+"
+op2LaTeX Minus      = "-"
+op2LaTeX LeftParen  = "("
+op2LaTeX RightParen = ")"
+
+-- Some supportive types and instances.
+
+instance Bifunctor Expr where
+  bimap _ g (Var x)        = Var (g x)
+  bimap f g (BinOp x op y) = BinOp (bimap f g x) (f op) (bimap f g y)
+  bimap f g (Paren l x r)  = Paren (f l) (bimap f g x) (f r)
+
+instance Bifoldable Expr where
+  bifoldMap _ g (Var x)        = g x
+  bifoldMap f g (BinOp x op y) = bifoldMap f g x <> f op <> bifoldMap f g y
+  bifoldMap f g (Paren l x r)  = f l <> bifoldMap f g x <> f r
+
+instance Bitraversable Expr where
+  bitraverse _ g (Var x)        = Var <$> g x
+  bitraverse f g (BinOp x op y) = BinOp <$> bitraverse f g x <*> f op <*> bitraverse f g y
+  bitraverse f g (Paren l x r)  = Paren <$> f l <*> bitraverse f g x <*> f r
+
+-- | Given a bifunctor @f@, make a functor @Both f@ where @Both f a ~ f a a@.
+newtype Both f a = Both { runBoth :: f a a }
+
+instance Bifunctor p => Functor (Both p) where
+  fmap f = Both . bimap f f . runBoth
+
+instance Bifoldable p => Foldable (Both p) where
+  foldMap f = bifoldMap f f . runBoth
+
+instance Bitraversable p => Traversable (Both p) where
+  traverse f = fmap Both . bitraverse f f . runBoth
+
+-- | Make a bifunctor @f (a, -) (b, -)@ (namely @BiAlongside a b f@) out of @f@.
+newtype BiAlongside a b f x y = BiAlongside { runBiAlongside :: f (a, x) (b, y) }
+
+instance Bifunctor p => Bifunctor (BiAlongside a b p) where
+  bimap f g = BiAlongside . bimap (second f) (second g) . runBiAlongside
+
+instance Bifoldable p => Bifoldable (BiAlongside a b p) where
+  bifoldMap f g = bifoldMap (f . snd) (g . snd) . runBiAlongside
+
+instance Bitraversable p => Bitraversable (BiAlongside a b p) where
+  bitraverse f g = fmap BiAlongside . bitraverse (traverse f) (traverse g) . runBiAlongside
diff --git a/examples/fe_bleed.hs b/examples/fe_bleed.hs
index df2e3c7..21a0e32 100755
--- a/examples/fe_bleed.hs
+++ b/examples/fe_bleed.hs
@@ -18,7 +18,6 @@ import           Codec.Picture.Types
 import           Control.Lens                    hiding (magma)
 import           Control.Monad
 import           Data.Fixed
-import           Data.Monoid
 import           Graphics.SvgTree
 import           Linear.V2
 import           Linear.Vector
@@ -129,7 +128,7 @@ main = reanimate $ scene $ do
         & filterWidth .~ pure (Percent 1)
         & filterHeight .~ pure (Percent 1)
       , FETurbulence $ defaultSvg
-        & turbulenceBaseFrequency .~ (2, Last Nothing)
+        & turbulenceBaseFrequency .~ (2, Nothing)
         & turbulenceType .~ FractalNoiseType
         & turbulenceNumOctaves .~ 1
         & turbulenceSeed .~ 1
diff --git a/examples/bug_play1.golden b/examples/golden/bug_play1.golden
similarity index 100%
rename from examples/bug_play1.golden
rename to examples/golden/bug_play1.golden
diff --git a/examples/bug_play2.golden b/examples/golden/bug_play2.golden
similarity index 67%
rename from examples/bug_play2.golden
rename to examples/golden/bug_play2.golden
index 5f6b3d7..67e6df8 100644
--- a/examples/bug_play2.golden
+++ b/examples/golden/bug_play2.golden
@@ -1,10 +1,10 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/bug_play3.golden b/examples/golden/bug_play3.golden
similarity index 67%
rename from examples/bug_play3.golden
rename to examples/golden/bug_play3.golden
index 5f6b3d7..67e6df8 100644
--- a/examples/bug_play3.golden
+++ b/examples/golden/bug_play3.golden
@@ -1,10 +1,10 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/bug_quad.golden b/examples/golden/bug_quad.golden
new file mode 100644
index 0000000..b78508b
--- /dev/null
+++ b/examples/golden/bug_quad.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/counter.golden b/examples/golden/counter.golden
similarity index 100%
rename from examples/counter.golden
rename to examples/golden/counter.golden
diff --git a/examples/golden/doc_addStatic.golden b/examples/golden/doc_addStatic.golden
new file mode 100644
index 0000000..10cdbf1
--- /dev/null
+++ b/examples/golden/doc_addStatic.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_andThen.golden b/examples/golden/doc_andThen.golden
new file mode 100644
index 0000000..14d72ab
--- /dev/null
+++ b/examples/golden/doc_andThen.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_applyVar.golden b/examples/golden/doc_applyVar.golden
new file mode 100644
index 0000000..d67944a
--- /dev/null
+++ b/examples/golden/doc_applyVar.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_bellS.golden b/examples/golden/doc_bellS.golden
new file mode 100644
index 0000000..4af9bb4
--- /dev/null
+++ b/examples/golden/doc_bellS.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_chainT.golden b/examples/golden/doc_chainT.golden
new file mode 100644
index 0000000..8b9fc53
--- /dev/null
+++ b/examples/golden/doc_chainT.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_cividis.golden b/examples/golden/doc_cividis.golden
new file mode 100644
index 0000000..67e3d13
--- /dev/null
+++ b/examples/golden/doc_cividis.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_constantS.golden b/examples/golden/doc_constantS.golden
new file mode 100644
index 0000000..94d3a67
--- /dev/null
+++ b/examples/golden/doc_constantS.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_cubicBezierS.golden b/examples/golden/doc_cubicBezierS.golden
new file mode 100644
index 0000000..7b89f86
--- /dev/null
+++ b/examples/golden/doc_cubicBezierS.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_curveS.golden b/examples/golden/doc_curveS.golden
new file mode 100644
index 0000000..94ad79c
--- /dev/null
+++ b/examples/golden/doc_curveS.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_destroySprite.golden b/examples/golden/doc_destroySprite.golden
new file mode 100644
index 0000000..b90da20
--- /dev/null
+++ b/examples/golden/doc_destroySprite.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_drawBox.golden b/examples/golden/doc_drawBox.golden
new file mode 100644
index 0000000..77bd964
--- /dev/null
+++ b/examples/golden/doc_drawBox.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_drawCircle.golden b/examples/golden/doc_drawCircle.golden
new file mode 100644
index 0000000..3007b89
--- /dev/null
+++ b/examples/golden/doc_drawCircle.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_drawProgress.golden b/examples/golden/doc_drawProgress.golden
new file mode 100644
index 0000000..4e399ac
--- /dev/null
+++ b/examples/golden/doc_drawProgress.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_fadeT.golden b/examples/golden/doc_fadeT.golden
new file mode 100644
index 0000000..b1b1d17
--- /dev/null
+++ b/examples/golden/doc_fadeT.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_fork.golden b/examples/golden/doc_fork.golden
new file mode 100644
index 0000000..8dd954b
--- /dev/null
+++ b/examples/golden/doc_fork.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_fromToS.golden b/examples/golden/doc_fromToS.golden
new file mode 100644
index 0000000..352ac21
--- /dev/null
+++ b/examples/golden/doc_fromToS.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_githubIcon.golden b/examples/golden/doc_githubIcon.golden
new file mode 100644
index 0000000..79dc46d
--- /dev/null
+++ b/examples/golden/doc_githubIcon.golden
@@ -0,0 +1,10 @@
+0        
+1        
+2        
+3        
+4        
+5        
+6        
+7        
+8        
+9        
diff --git a/examples/golden/doc_greyscale.golden b/examples/golden/doc_greyscale.golden
new file mode 100644
index 0000000..ca009e8
--- /dev/null
+++ b/examples/golden/doc_greyscale.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_haskellLogo.golden b/examples/golden/doc_haskellLogo.golden
new file mode 100644
index 0000000..c5725b3
--- /dev/null
+++ b/examples/golden/doc_haskellLogo.golden
@@ -0,0 +1,10 @@
+0                    
+1                    
+2                    
+3                    
+4                    
+5                    
+6                    
+7                    
+8                    
+9                    
diff --git a/examples/golden/doc_hsv.golden b/examples/golden/doc_hsv.golden
new file mode 100644
index 0000000..11ba77d
--- /dev/null
+++ b/examples/golden/doc_hsv.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_hsvComponents.golden b/examples/golden/doc_hsvComponents.golden
new file mode 100644
index 0000000..229a007
--- /dev/null
+++ b/examples/golden/doc_hsvComponents.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_hsvMatlab.golden b/examples/golden/doc_hsvMatlab.golden
new file mode 100644
index 0000000..17d187d
--- /dev/null
+++ b/examples/golden/doc_hsvMatlab.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_inferno.golden b/examples/golden/doc_inferno.golden
new file mode 100644
index 0000000..26b2183
--- /dev/null
+++ b/examples/golden/doc_inferno.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_jet.golden b/examples/golden/doc_jet.golden
new file mode 100644
index 0000000..58ac93a
--- /dev/null
+++ b/examples/golden/doc_jet.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_labComponents.golden b/examples/golden/doc_labComponents.golden
new file mode 100644
index 0000000..6afc8bf
--- /dev/null
+++ b/examples/golden/doc_labComponents.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_latex.golden b/examples/golden/doc_latex.golden
new file mode 100644
index 0000000..184056f
--- /dev/null
+++ b/examples/golden/doc_latex.golden
@@ -0,0 +1,10 @@
+0$e^{i\pi}+1=0$
+1$e^{i\pi}+1=0$
+2$e^{i\pi}+1=0$
+3$e^{i\pi}+1=0$
+4$e^{i\pi}+1=0$
+5$e^{i\pi}+1=0$
+6$e^{i\pi}+1=0$
+7$e^{i\pi}+1=0$
+8$e^{i\pi}+1=0$
+9$e^{i\pi}+1=0$
diff --git a/examples/golden/doc_latexAlign.golden b/examples/golden/doc_latexAlign.golden
new file mode 100644
index 0000000..2c428ff
--- /dev/null
+++ b/examples/golden/doc_latexAlign.golden
@@ -0,0 +1,10 @@
+0\begin{align*}R = \frac{{\Delta x}}{{kA}}\end{align*}
+1\begin{align*}R = \frac{{\Delta x}}{{kA}}\end{align*}
+2\begin{align*}R = \frac{{\Delta x}}{{kA}}\end{align*}
+3\begin{align*}R = \frac{{\Delta x}}{{kA}}\end{align*}
+4\begin{align*}R = \frac{{\Delta x}}{{kA}}\end{align*}
+5\begin{align*}R = \frac{{\Delta x}}{{kA}}\end{align*}
+6\begin{align*}R = \frac{{\Delta x}}{{kA}}\end{align*}
+7\begin{align*}R = \frac{{\Delta x}}{{kA}}\end{align*}
+8\begin{align*}R = \frac{{\Delta x}}{{kA}}\end{align*}
+9\begin{align*}R = \frac{{\Delta x}}{{kA}}\end{align*}
diff --git a/examples/golden/doc_lchComponents.golden b/examples/golden/doc_lchComponents.golden
new file mode 100644
index 0000000..3d3876a
--- /dev/null
+++ b/examples/golden/doc_lchComponents.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_magma.golden b/examples/golden/doc_magma.golden
new file mode 100644
index 0000000..148b872
--- /dev/null
+++ b/examples/golden/doc_magma.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_mapA.golden b/examples/golden/doc_mapA.golden
new file mode 100644
index 0000000..e53e712
--- /dev/null
+++ b/examples/golden/doc_mapA.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/doc_mkBackground.golden b/examples/golden/doc_mkBackground.golden
similarity index 59%
rename from examples/doc_mkBackground.golden
rename to examples/golden/doc_mkBackground.golden
index 087f82f..6c82cb3 100644
--- a/examples/doc_mkBackground.golden
+++ b/examples/golden/doc_mkBackground.golden
@@ -1,10 +1,10 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_mkText.golden b/examples/golden/doc_mkText.golden
new file mode 100644
index 0000000..1f02386
--- /dev/null
+++ b/examples/golden/doc_mkText.golden
@@ -0,0 +1,10 @@
+0
+1te
+2tex
+3text
+4text
+5text
+6text
+7text
+8text
+9text
diff --git a/examples/golden/doc_newSprite.golden b/examples/golden/doc_newSprite.golden
new file mode 100644
index 0000000..3b86914
--- /dev/null
+++ b/examples/golden/doc_newSprite.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_newSpriteA'.golden b/examples/golden/doc_newSpriteA'.golden
new file mode 100644
index 0000000..ed2f497
--- /dev/null
+++ b/examples/golden/doc_newSpriteA'.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_newSpriteA.golden b/examples/golden/doc_newSpriteA.golden
new file mode 100644
index 0000000..2797ecf
--- /dev/null
+++ b/examples/golden/doc_newSpriteA.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_newSpriteSVG.golden b/examples/golden/doc_newSpriteSVG.golden
new file mode 100644
index 0000000..10cdbf1
--- /dev/null
+++ b/examples/golden/doc_newSpriteSVG.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_oscillateS.golden b/examples/golden/doc_oscillateS.golden
new file mode 100644
index 0000000..4f15a5b
--- /dev/null
+++ b/examples/golden/doc_oscillateS.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_overlapT.golden b/examples/golden/doc_overlapT.golden
new file mode 100644
index 0000000..5e2101f
--- /dev/null
+++ b/examples/golden/doc_overlapT.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_parA.golden b/examples/golden/doc_parA.golden
new file mode 100644
index 0000000..efd8be2
--- /dev/null
+++ b/examples/golden/doc_parA.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_parDropA.golden b/examples/golden/doc_parDropA.golden
new file mode 100644
index 0000000..a4a9ce2
--- /dev/null
+++ b/examples/golden/doc_parDropA.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_parLoopA.golden b/examples/golden/doc_parLoopA.golden
new file mode 100644
index 0000000..f1aa6d8
--- /dev/null
+++ b/examples/golden/doc_parLoopA.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_parula.golden b/examples/golden/doc_parula.golden
new file mode 100644
index 0000000..963b35c
--- /dev/null
+++ b/examples/golden/doc_parula.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_pathify.golden b/examples/golden/doc_pathify.golden
new file mode 100644
index 0000000..c46d978
--- /dev/null
+++ b/examples/golden/doc_pathify.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_pause.golden b/examples/golden/doc_pause.golden
new file mode 100644
index 0000000..f73f325
--- /dev/null
+++ b/examples/golden/doc_pause.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_pauseAround.golden b/examples/golden/doc_pauseAround.golden
new file mode 100644
index 0000000..800743d
--- /dev/null
+++ b/examples/golden/doc_pauseAround.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_pauseAtBeginning.golden b/examples/golden/doc_pauseAtBeginning.golden
new file mode 100644
index 0000000..5620c53
--- /dev/null
+++ b/examples/golden/doc_pauseAtBeginning.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_pauseAtEnd.golden b/examples/golden/doc_pauseAtEnd.golden
new file mode 100644
index 0000000..ab41042
--- /dev/null
+++ b/examples/golden/doc_pauseAtEnd.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_plasma.golden b/examples/golden/doc_plasma.golden
new file mode 100644
index 0000000..4db16b5
--- /dev/null
+++ b/examples/golden/doc_plasma.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_play.golden b/examples/golden/doc_play.golden
new file mode 100644
index 0000000..ca0d781
--- /dev/null
+++ b/examples/golden/doc_play.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_playThenReverseA.golden b/examples/golden/doc_playThenReverseA.golden
new file mode 100644
index 0000000..cdc2829
--- /dev/null
+++ b/examples/golden/doc_playThenReverseA.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_queryNow.golden b/examples/golden/doc_queryNow.golden
new file mode 100644
index 0000000..e1d33e2
--- /dev/null
+++ b/examples/golden/doc_queryNow.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6Now=2.0
+7Now=2.0
+8Now=2.0
+9Now=2.0
diff --git a/examples/golden/doc_repeatA.golden b/examples/golden/doc_repeatA.golden
new file mode 100644
index 0000000..7822583
--- /dev/null
+++ b/examples/golden/doc_repeatA.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_reverseA.golden b/examples/golden/doc_reverseA.golden
new file mode 100644
index 0000000..e457d9f
--- /dev/null
+++ b/examples/golden/doc_reverseA.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_reverseS.golden b/examples/golden/doc_reverseS.golden
new file mode 100644
index 0000000..c7c2133
--- /dev/null
+++ b/examples/golden/doc_reverseS.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_rgbComponents.golden b/examples/golden/doc_rgbComponents.golden
new file mode 100644
index 0000000..7cf0e3b
--- /dev/null
+++ b/examples/golden/doc_rgbComponents.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_seqA.golden b/examples/golden/doc_seqA.golden
new file mode 100644
index 0000000..4ca4299
--- /dev/null
+++ b/examples/golden/doc_seqA.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_signalA.golden b/examples/golden/doc_signalA.golden
new file mode 100644
index 0000000..9a9c276
--- /dev/null
+++ b/examples/golden/doc_signalA.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_simpleVar.golden b/examples/golden/doc_simpleVar.golden
new file mode 100644
index 0000000..fd32604
--- /dev/null
+++ b/examples/golden/doc_simpleVar.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_sinebow.golden b/examples/golden/doc_sinebow.golden
new file mode 100644
index 0000000..475b005
--- /dev/null
+++ b/examples/golden/doc_sinebow.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_slideDownT.golden b/examples/golden/doc_slideDownT.golden
new file mode 100644
index 0000000..c3563d7
--- /dev/null
+++ b/examples/golden/doc_slideDownT.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_slideLeftT.golden b/examples/golden/doc_slideLeftT.golden
new file mode 100644
index 0000000..7c46484
--- /dev/null
+++ b/examples/golden/doc_slideLeftT.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_slideUpT.golden b/examples/golden/doc_slideUpT.golden
new file mode 100644
index 0000000..f78076b
--- /dev/null
+++ b/examples/golden/doc_slideUpT.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_spriteE.golden b/examples/golden/doc_spriteE.golden
new file mode 100644
index 0000000..7429921
--- /dev/null
+++ b/examples/golden/doc_spriteE.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_spriteMap.golden b/examples/golden/doc_spriteMap.golden
new file mode 100644
index 0000000..0f1d597
--- /dev/null
+++ b/examples/golden/doc_spriteMap.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_spriteTween.golden b/examples/golden/doc_spriteTween.golden
new file mode 100644
index 0000000..0e4f409
--- /dev/null
+++ b/examples/golden/doc_spriteTween.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_spriteVar.golden b/examples/golden/doc_spriteVar.golden
new file mode 100644
index 0000000..d67944a
--- /dev/null
+++ b/examples/golden/doc_spriteVar.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_spriteZ.golden b/examples/golden/doc_spriteZ.golden
new file mode 100644
index 0000000..37ad852
--- /dev/null
+++ b/examples/golden/doc_spriteZ.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_svgLogo.golden b/examples/golden/doc_svgLogo.golden
new file mode 100644
index 0000000..fa1042d
--- /dev/null
+++ b/examples/golden/doc_svgLogo.golden
@@ -0,0 +1,10 @@
+0                                                                          
+1                                                                          
+2                                                                          
+3                                                                          
+4                                                                          
+5                                                                          
+6                                                                          
+7                                                                          
+8                                                                          
+9                                                                          
diff --git a/examples/golden/doc_turbo.golden b/examples/golden/doc_turbo.golden
new file mode 100644
index 0000000..4125a56
--- /dev/null
+++ b/examples/golden/doc_turbo.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_unVar.golden b/examples/golden/doc_unVar.golden
new file mode 100644
index 0000000..42a4bcc
--- /dev/null
+++ b/examples/golden/doc_unVar.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_viridis.golden b/examples/golden/doc_viridis.golden
new file mode 100644
index 0000000..61284eb
--- /dev/null
+++ b/examples/golden/doc_viridis.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_wait.golden b/examples/golden/doc_wait.golden
new file mode 100644
index 0000000..ed8dee0
--- /dev/null
+++ b/examples/golden/doc_wait.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_waitOn.golden b/examples/golden/doc_waitOn.golden
new file mode 100644
index 0000000..ca0d781
--- /dev/null
+++ b/examples/golden/doc_waitOn.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/doc_withViewBox.golden b/examples/golden/doc_withViewBox.golden
similarity index 70%
rename from examples/doc_withViewBox.golden
rename to examples/golden/doc_withViewBox.golden
index af4fe0d..a549f61 100644
--- a/examples/doc_withViewBox.golden
+++ b/examples/golden/doc_withViewBox.golden
@@ -1,10 +1,10 @@
-0        
-1        
-2        
-3        
-4        
-5        
-6        
-7        
-8        
-9        
+0  
+1  
+2  
+3  
+4  
+5  
+6  
+7  
+8  
+9  
diff --git a/examples/golden/doc_writeVar.golden b/examples/golden/doc_writeVar.golden
new file mode 100644
index 0000000..91a10a6
--- /dev/null
+++ b/examples/golden/doc_writeVar.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/doc_xyzComponents.golden b/examples/golden/doc_xyzComponents.golden
new file mode 100644
index 0000000..bd337b2
--- /dev/null
+++ b/examples/golden/doc_xyzComponents.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/golden/goo.golden b/examples/golden/goo.golden
new file mode 100644
index 0000000..59e8990
--- /dev/null
+++ b/examples/golden/goo.golden
@@ -0,0 +1,10 @@
+0        
+1        
+2        
+3        
+4        
+5        
+6        
+7        
+8        
+9        
diff --git a/examples/golden/intro_canvas.golden b/examples/golden/intro_canvas.golden
new file mode 100644
index 0000000..09198cf
--- /dev/null
+++ b/examples/golden/intro_canvas.golden
@@ -0,0 +1,10 @@
+016 units16 units9 units9 units0.0,0.00.0,0.0
+116 units16 units9 units9 units2.0,1.42.0,1.4
+216 units16 units9 units9 units2.8,1.62.8,1.6
+316 units16 units9 units9 units-4.0,3.0-4.0,3.0
+416 units16 units9 units9 units-2.8,-1.8-2.8,-1.8
+516 units16 units9 units9 units-2.4,-2.0-2.4,-2.0
+616 units16 units9 units9 units5.0,-2.05.0,-2.0
+716 units16 units9 units9 units2.9,1.22.9,1.2
+816 units16 units9 units9 units1.0,0.51.0,0.5
+916 units16 units9 units9 units0.0,0.00.0,0.0
diff --git a/examples/golden/intro_canvas_square.golden b/examples/golden/intro_canvas_square.golden
new file mode 100644
index 0000000..e7b2f1b
--- /dev/null
+++ b/examples/golden/intro_canvas_square.golden
@@ -0,0 +1,10 @@
+0                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          8 units                                                          8 units                                                                                                            8 units                                                                    8 units                                                                                                                                              0.0,0.0                                                                              0.0,0.0                                                                                
+1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          8 units                                                          8 units                                                                                                            8 units                                                                    8 units                                                                                                                                              1.7,1.0                                                                              1.7,1.0                                                                                
+2                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          8 units                                                          8 units                                                                                                            8 units                                                                    8 units                                                                                                                                              0.4,0.6                                                                              0.4,0.6                                                                                
+3                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          8 units                                                          8 units                                                                                                            8 units                                                                    8 units                                                                                                                                              -1.0,1.5                                                                              -1.0,1.5                                                                                
+4                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          8 units                                                          8 units                                                                                                            8 units                                                                    8 units                                                                                                                                              -1.7,-2.5                                                                              -1.7,-2.5                                                                                
+5                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          8 units                                                          8 units                                                                                                            8 units                                                                    8 units                                                                                                                                              -0.7,-1.5                                                                              -0.7,-1.5                                                                                
+6                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          8 units                                                          8 units                                                                                                            8 units                                                                    8 units                                                                                                                                              2.5,-1.0                                                                              2.5,-1.0                                                                                
+7                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          8 units                                                          8 units                                                                                                            8 units                                                                    8 units                                                                                                                                              1.7,1.1                                                                              1.7,1.1                                                                                
+8                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          8 units                                                          8 units                                                                                                            8 units                                                                    8 units                                                                                                                                              0.7,0.5                                                                              0.7,0.5                                                                                
+9                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          8 units                                                          8 units                                                                                                            8 units                                                                    8 units                                                                                                                                              0.0,0.0                                                                              0.0,0.0                                                                                
diff --git a/examples/golden/intro_hello.golden b/examples/golden/intro_hello.golden
new file mode 100644
index 0000000..6b666f6
--- /dev/null
+++ b/examples/golden/intro_hello.golden
@@ -0,0 +1,10 @@
+0Hello world
+1Hello world
+2Hello world
+3Hello world
+4Hello world
+5Hello world
+6Hello world
+7Hello world
+8Hello world
+9Hello world
diff --git a/examples/golden/latex_basic.golden b/examples/golden/latex_basic.golden
new file mode 100644
index 0000000..6e4811e
--- /dev/null
+++ b/examples/golden/latex_basic.golden
@@ -0,0 +1,10 @@
+0\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}
+1\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}
+2\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}
+3\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}
+4\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}
+5\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}
+6\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}
+7\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}
+8\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}
+9\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}
diff --git a/examples/golden/raster.golden b/examples/golden/raster.golden
new file mode 100644
index 0000000..6827c47
--- /dev/null
+++ b/examples/golden/raster.golden
@@ -0,0 +1,10 @@
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
diff --git a/examples/goo.golden b/examples/goo.golden
deleted file mode 100644
index ac5ca02..0000000
--- a/examples/goo.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0        
-1        
-2        
-3        
-4        
-5        
-6        
-7        
-8        
-9        
diff --git a/examples/intro_canvas.golden b/examples/intro_canvas.golden
deleted file mode 100644
index f27de81..0000000
--- a/examples/intro_canvas.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-016 units16 units9 units9 units0.0,0.00.0,0.0
-116 units16 units9 units9 units2.0,1.42.0,1.4
-216 units16 units9 units9 units2.8,1.62.8,1.6
-316 units16 units9 units9 units-4.0,3.0-4.0,3.0
-416 units16 units9 units9 units-2.8,-1.8-2.8,-1.8
-516 units16 units9 units9 units-2.4,-2.0-2.4,-2.0
-616 units16 units9 units9 units5.0,-2.05.0,-2.0
-716 units16 units9 units9 units2.9,1.22.9,1.2
-816 units16 units9 units9 units1.0,0.51.0,0.5
-916 units16 units9 units9 units0.0,0.00.0,0.0
diff --git a/examples/intro_canvas_square.golden b/examples/intro_canvas_square.golden
deleted file mode 100644
index d3eba6a..0000000
--- a/examples/intro_canvas_square.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    8 units                                                          8 units                                                                                                            8 units                                                                    8 units                                                                                                                                              0.0,0.0                                                                              0.0,0.0                                                                                
-1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    8 units                                                          8 units                                                                                                            8 units                                                                    8 units                                                                                                                                              1.7,1.0                                                                              1.7,1.0                                                                                
-2                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    8 units                                                          8 units                                                                                                            8 units                                                                    8 units                                                                                                                                              0.4,0.6                                                                              0.4,0.6                                                                                
-3                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    8 units                                                          8 units                                                                                                            8 units                                                                    8 units                                                                                                                                              -1.0,1.5                                                                              -1.0,1.5                                                                                
-4                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    8 units                                                          8 units                                                                                                            8 units                                                                    8 units                                                                                                                                              -1.7,-2.5                                                                              -1.7,-2.5                                                                                
-5                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    8 units                                                          8 units                                                                                                            8 units                                                                    8 units                                                                                                                                              -0.7,-1.5                                                                              -0.7,-1.5                                                                                
-6                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    8 units                                                          8 units                                                                                                            8 units                                                                    8 units                                                                                                                                              2.5,-1.0                                                                              2.5,-1.0                                                                                
-7                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    8 units                                                          8 units                                                                                                            8 units                                                                    8 units                                                                                                                                              1.7,1.1                                                                              1.7,1.1                                                                                
-8                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    8 units                                                          8 units                                                                                                            8 units                                                                    8 units                                                                                                                                              0.7,0.5                                                                              0.7,0.5                                                                                
-9                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    8 units                                                          8 units                                                                                                            8 units                                                                    8 units                                                                                                                                              0.0,0.0                                                                              0.0,0.0                                                                                
diff --git a/examples/intro_hello.golden b/examples/intro_hello.golden
deleted file mode 100644
index 968040f..0000000
--- a/examples/intro_hello.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0Hello world
-1Hello world
-2Hello world
-3Hello world
-4Hello world
-5Hello world
-6Hello world
-7Hello world
-8Hello world
-9Hello world
diff --git a/examples/latex_basic.golden b/examples/latex_basic.golden
deleted file mode 100644
index 8225fb9..0000000
--- a/examples/latex_basic.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}
-1\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}
-2\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}
-3\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}
-4\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}
-5\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}
-6\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}
-7\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}
-8\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}
-9\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}\begin{align*}\sum_{k=1}^\infty {1 \over k^2} = {\pi^2 \over 6}\end{align*}
diff --git a/examples/raster.golden b/examples/raster.golden
deleted file mode 100644
index c27a08d..0000000
--- a/examples/raster.golden
+++ /dev/null
@@ -1,10 +0,0 @@
-0
-1
-2
-3
-4
-5
-6
-7
-8
-9
diff --git a/examples/tut_glue_blender.hs b/examples/tut_glue_blender.hs
index 6b71825..bf891af 100755
--- a/examples/tut_glue_blender.hs
+++ b/examples/tut_glue_blender.hs
@@ -12,7 +12,6 @@ import           Reanimate.Builtin.Documentation
 import           Codec.Picture.Types
 import           Control.Lens                    ((&), (^.))
 import           Control.Monad
-import           Data.Monoid
 import qualified Data.Text                       as T
 import           Graphics.SvgTree
 import           NeatInterpolation
@@ -252,7 +251,7 @@ drawAnimation' :: Maybe Int -> Double -> Double -> SVG -> Animation
 drawAnimation' mbSeed fillDur step svg = scene $ do
   forM_ (zip [0..] $ shuf $ svgGlyphs svg) $ \(n, (fn, attr, tree)) -> do
     let sWidth =
-          case toUserUnit defaultDPI <$> getLast (attr ^. strokeWidth) of
+          case toUserUnit defaultDPI <$> (attr ^. strokeWidth) of
             Just (Num d) -> d
             _            -> defaultStrokeWidth
     fork $ do
diff --git a/examples/tut_glue_latex.hs b/examples/tut_glue_latex.hs
index 4397351..0d0a74a 100755
--- a/examples/tut_glue_latex.hs
+++ b/examples/tut_glue_latex.hs
@@ -11,7 +11,6 @@ import           Codec.Picture
 import           Codec.Picture.Types
 import           Control.Lens          ((&), (^.))
 import           Control.Monad
-import           Data.Monoid
 import           Graphics.SvgTree
 import           System.Random
 import "random-shuffle" System.Random.Shuffle
@@ -75,7 +74,7 @@ drawAnimation' :: Maybe Int -> Double -> Double -> SVG -> Animation
 drawAnimation' mbSeed fillDur step svg = scene $ do
   forM_ (zip [0..] $ shuf $ svgGlyphs svg) $ \(n, (fn, attr, tree)) -> do
     let sWidth =
-          case toUserUnit defaultDPI <$> getLast (attr ^. strokeWidth) of
+          case toUserUnit defaultDPI <$> (attr ^. strokeWidth) of
             Just (Num d) -> d
             _            -> defaultStrokeWidth
     fork $ do
diff --git a/examples/tut_glue_povray.hs b/examples/tut_glue_povray.hs
index 796e561..edbc29d 100755
--- a/examples/tut_glue_povray.hs
+++ b/examples/tut_glue_povray.hs
@@ -13,7 +13,6 @@ import           Codec.Picture
 import           Codec.Picture.Types
 import           Control.Lens          ((&), (^.))
 import           Control.Monad
-import           Data.Monoid
 import           Data.Text             (Text)
 import qualified Data.Text             as T
 import           Graphics.SvgTree      hiding (Text)
@@ -54,6 +53,8 @@ script png transZ rotX rotZ =
       transZ_ = T.pack $ show transZ
       rotZ_ = T.pack $ show rotZ
   in [text|
+#version 3.7;
+//Files with predefined colors and textures
 #include "colors.inc"
 
 //Place the camera
@@ -65,6 +66,8 @@ camera {
   right x*16/9
 }
 
+global_settings { assumed_gamma 1.0 }
+
 //Ambient light to "brighten up" darker pictures
 global_settings { ambient_light White*3 }
 
@@ -72,15 +75,15 @@ global_settings { ambient_light White*3 }
 background { color rgbt <0, 0, 0, 1> } // transparent
 
 polygon {
-  4,
-  <0, 0>, <0, 1>, <1, 1>, <1, 0>
+  5,
+  <0, 0>, <0, 1>, <1, 1>, <1, 0>, <0, 0>
   texture {
     pigment{
       image_map{ png "${png_}" }
     }
   }
   translate <-1/2,-1/2>
-  scale <16,9>
+  scale <16,9,1>
   rotate <0,${rotX_},${rotZ_}>
   translate <0,0,${transZ_}>
 }
@@ -146,7 +149,7 @@ drawAnimation' :: Maybe Int -> Double -> Double -> SVG -> Animation
 drawAnimation' mbSeed fillDur step svg = scene $ do
   forM_ (zip [0..] $ shuf $ svgGlyphs svg) $ \(n, (fn, attr, tree)) -> do
     let sWidth =
-          case toUserUnit defaultDPI <$> getLast (attr ^. strokeWidth) of
+          case toUserUnit defaultDPI <$> (attr ^. strokeWidth) of
             Just (Num d) -> d
             _            -> defaultStrokeWidth
     fork $ do
diff --git a/examples/tut_glue_povray_ortho.hs b/examples/tut_glue_povray_ortho.hs
index 941a7cc..b6c69c0 100755
--- a/examples/tut_glue_povray_ortho.hs
+++ b/examples/tut_glue_povray_ortho.hs
@@ -13,7 +13,6 @@ import           Codec.Picture
 import           Codec.Picture.Types
 import           Control.Lens          ((^.),(&))
 import           Control.Monad
-import           Data.Monoid
 import           Data.Text             (Text)
 import qualified Data.Text             as T
 import           Graphics.SvgTree      hiding (Text)
@@ -61,6 +60,7 @@ script png rotX rotY rotZ =
       rotY_ = T.pack $ show rotY
       rotZ_ = T.pack $ show rotZ
   in [text|
+# version 3.7;
 //Files with predefined colors and textures
 #include "colors.inc"
 
@@ -75,6 +75,7 @@ camera {
   right x*16
 }
 
+global_settings { assumed_gamma 1.0 }
 
 //Ambient light to "brighten up" darker pictures
 global_settings { ambient_light White*3 }
@@ -183,7 +184,7 @@ drawAnimation' :: Maybe Int -> Double -> Double -> SVG -> Animation
 drawAnimation' mbSeed fillDur step svg = scene $ do
   forM_ (zip [0..] $ shuf $ svgGlyphs svg) $ \(n, (fn, attr, tree)) -> do
     let sWidth =
-          case toUserUnit defaultDPI <$> getLast (attr ^. strokeWidth) of
+          case toUserUnit defaultDPI <$> (attr ^. strokeWidth) of
             Just (Num d) -> d
             _            -> defaultStrokeWidth
     fork $ do
diff --git a/examples/waitpid_simulator.hs b/examples/waitpid_simulator.hs
new file mode 100644
index 0000000..959d340
--- /dev/null
+++ b/examples/waitpid_simulator.hs
@@ -0,0 +1,129 @@
+#!/usr/bin/env stack
+-- stack runghc --package reanimate
+module Main (main) where
+ 
+import           Reanimate
+import           Reanimate.Builtin.Documentation
+import           Reanimate.Scene
+import           Data.Text                       (Text)
+import qualified Data.Text                       as T
+import Control.Lens
+import           Graphics.SvgTree                hiding (Point, Text)
+
+customDuration :: Duration
+customDuration = 3
+data Point = Point Double Double
+data Segment = Segment Point Point
+
+-- Helper functions creating an Animation from two coordinates
+animatePart :: Segment -> Animation
+animatePart (Segment (Point startx starty) (Point endx endy)) = signalA (curveS 2) $ setDuration customDuration $
+  animate $ \t ->
+    partialSvg t $ pathify (mkLine (startx, starty) (endx, endy) & strokeLineCap .~ (pure CapSquare))
+
+-- Helper function to create coordinates for a specific line
+splitSegments :: Segment -> Int -> [Segment]
+splitSegments seg numSegs =
+              let firstSeg = getFirstSeg seg numSegs
+              in take numSegs (iterate shiftSeg firstSeg)
+
+-- Helper function to create the first smaller segment in a large segment
+getFirstSeg :: Segment -> Int -> Segment
+getFirstSeg (Segment (Point startx starty) (Point endx endy)) numSegs
+             = Segment (Point startx starty) (Point ( startx + ((endx - startx)/(fromIntegral numSegs))) ( starty + ((endy - starty)/(fromIntegral numSegs))))
+
+-- Helper function to shift a segment to the right by the size of itself
+shiftSeg :: Segment -> Segment
+shiftSeg (Segment (Point startx starty) (Point endx endy))
+       = Segment (Point endx endy) (Point (endx + (endx-startx)) (endy + (endy-starty)))
+
+-- Helper function to animate the line segment that represents a fork
+animateFork :: Segment -> Animation
+animateFork (Segment (Point startx starty) (Point endx endy))
+       = oFadeIn (mkLine (startx,starty) (endx, endy))
+
+-- Helper function to label a proccess with a pid
+labelPids :: Segment -> Int -> SVG
+labelPids (Segment (Point startx starty) (Point _ _)) i = withStrokeWidth (defaultStrokeWidth * 0.5) $ withFillOpacity 1 $ mkGroup
+           [translate (startx - 0.75) (starty) $ scale 0.25 $ outlineText $T.pack("pid: " ++ (show i))]
+
+-- Helper function to position a line of text
+mkTextLabel:: [Char] -> Point -> SVG
+mkTextLabel txt (Point startx starty) = withStrokeWidth (defaultStrokeWidth * 0.5) $ withFillOpacity 1 $ mkGroup
+           [translate (startx) (starty) $ scale 0.2 $ outlineText $ T.pack(txt)]
+
+mkPrintLabelFromPnt :: Point -> [Char] -> Animation
+mkPrintLabelFromPnt pnt txt = 
+                             oFadeIn $ mkTextLabel ("printf(" ++ (show txt) ++ ")" ) pnt
+-- Helper function to speed up an animation
+speedUpAnimation :: Animation -> Animation
+speedUpAnimation anim = adjustDuration (*0.5) anim 
+ 
+-- Helper function to label a fork() call and its return value
+mkForkLabel :: Point -> Int -> Animation
+mkForkLabel point val = oFadeIn (mkTextLabel ("fork() = " ++ (show val) ) point)
+
+-- Helper function that creates waitpid() call label
+mkWaitpidLabel :: Point -> Animation
+mkWaitpidLabel pnt = oFadeIn $ mkTextLabel "waitpid(1001, NULL, 0)" pnt
+
+-- Helper function that creates an SVG of outlined text
+outlineText :: Text -> SVG
+outlineText txt = mkGroup
+      [ center
+      $ withStrokeColorPixel rtfdBackgroundColor
+      $ withStrokeWidth (defaultStrokeWidth * 8)
+      $ withFillOpacity 0
+      $ latex txt
+      , withStrokeWidth 0 $ withFillOpacity 1 $ center $ latex txt
+      ]
+
+main :: IO ()
+main = reanimate 
+       $ docEnv
+       $ ((pidLabelAnimations !! 0) `parA` (parentAnimations !! 0)) 
+        `andThen` ((forkLabelAnims !! 0) `parA` (forkAnimation)) 
+        `andThen` (forkLabelAnims !! 1) 
+        `andThen` ((pidLabelAnimations !! 1) `parA` (childAnimations !! 0) `parA` waitpidLabelAnim) 
+        `andThen` (childPrintfLabelAnim)
+        `andThen` (parentAnimations !! 1) 
+        `andThen` (parentPrintfLabelAnim)
+
+--Lists of segments that represent each proccess 
+parentSegments :: [Segment]
+parentSegments = splitSegments (Segment (Point (-6) 0) (Point 6 0)) 2
+
+childSegments :: [Segment]
+childSegments = splitSegments (Segment (Point 0 (-3)) (Point 6 (-3))) 1
+
+forkSegment :: Segment
+forkSegment = Segment (Point 0 0) (Point 0 (-3))
+
+--Lists of animations for each line
+parentAnimations :: [Animation] 
+parentAnimations = map animatePart parentSegments
+
+childAnimations :: [Animation]
+childAnimations = map animatePart childSegments
+
+forkAnimation :: Animation
+forkAnimation = animateFork forkSegment
+
+--List of animations to label each proccess with its PID
+pidLabelAnimations :: [Animation]
+pidLabelAnimations = [speedUpAnimation (oFadeIn (labelPids (parentSegments !! 0) 1000)), speedUpAnimation (oFadeIn (labelPids (childSegments !! 0) 1001))]
+
+--List of animations to label the fork() calls
+forkLabelAnims :: [Animation]
+forkLabelAnims = [mkForkLabel (Point (0) 0.25) 1001, mkForkLabel (Point (0) (-2.75)) 0]
+
+-- Animation to label the waitpid() call
+waitpidLabelAnim :: Animation
+waitpidLabelAnim = mkWaitpidLabel (Point (0.75) (0.5))
+
+-- Animation to label printf() 
+parentPrintfLabelAnim :: Animation
+parentPrintfLabelAnim = mkPrintLabelFromPnt (Point (5) (0.25)) "I'm the parent, and I'm done waiting!"
+
+childPrintfLabelAnim :: Animation
+childPrintfLabelAnim = mkPrintLabelFromPnt (Point (5) (-2.75)) "I'm the child!"
diff --git a/hie.yaml b/hie.yaml
index c9b9cbf..de54401 100644
--- a/hie.yaml
+++ b/hie.yaml
@@ -1,11 +1,20 @@
 cradle:
   multi:
     - path: "./src"
-      config: {cradle: {stack: {component: "reanimate:lib"}} }
+      config: 
+        cradle: 
+          stack: {component: "reanimate:lib"}
+          cabal: {component: "lib:reanimate"}
     - path: "./videos"
-      config: {cradle: {stack: {component: "reanimate:lib"}} }
+      config: 
+        cradle: 
+          stack: {component: "reanimate:lib"}
+          cabal: {component: "lib:reanimate"}
     - path: "./examples"
-      config: {cradle: {stack: {component: "reanimate:lib"}} }
+      config: 
+        cradle: 
+          stack: {component: "reanimate:lib"}
+          cabal: {component: "lib:reanimate"}
     - path: "./test"
       config: {cradle: {none: }}
     - path: "./discord-bot"
diff --git a/playground/nginx.conf b/playground/nginx.conf
index 9185c57..996597f 100644
--- a/playground/nginx.conf
+++ b/playground/nginx.conf
@@ -1,10 +1,14 @@
+upstream rgeometry {
+  server rgeometry-server:20162;
+}
+
 upstream wsbackend {
   server playground:10161;
 }
 
 server {
   listen 80;
-  server_name reanimate.clozecards.com;
+  server_name reanimate.clozecards.com _;
 
   location ~ /.well-known/acme-challenge {
         allow all; 
@@ -40,3 +44,52 @@ server {
     proxy_set_header Host $host;
   }
 }
+
+server {
+  include /etc/nginx/mime.types;
+
+  listen [::]:443 ssl http2;
+  listen 443 ssl http2;
+
+  server_name web.rgeometry.org;
+
+  # SSL code
+  ssl_certificate /etc/nginx/ssl/live/web.rgeometry.org/fullchain.pem;
+  ssl_certificate_key /etc/nginx/ssl/live/web.rgeometry.org/privkey.pem;
+
+  root /var/www/html;
+
+  location / {
+      index index.html;
+      autoindex on;
+      add_header Access-Control-Allow-Origin '*';
+      location ~* .(wasm|js|png)$ {
+        add_header Cache-Control "public, max-age=31536000, immutable";
+      }
+  }
+
+  location /wasm/ {
+    rewrite ^/wasm/(.*) /$1  break;
+    proxy_read_timeout 1h;
+    proxy_pass http://rgeometry;
+    proxy_http_version 1.1;
+    proxy_set_header Upgrade $http_upgrade;
+    proxy_set_header Connection "Upgrade";
+    proxy_set_header Host $host;
+  }
+
+  location /fetch-gist/ {
+    #add_header Access-Control-Allow-Origin '*';
+    if ($request_method = OPTIONS) {
+      add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS';
+      return 204;
+    }
+    proxy_pass https://gist.githubusercontent.com/;
+  }
+
+  location ~ /.well-known/acme-challenge {
+        allow all;
+        root /var/www/certbot;
+    }
+
+}
diff --git a/playground/snippets/10_Fonts.hs b/playground/snippets/10_Fonts.hs
index 4fd22d5..4f99afe 100644
--- a/playground/snippets/10_Fonts.hs
+++ b/playground/snippets/10_Fonts.hs
@@ -4,8 +4,7 @@ fonts =
   , ("Calligra", calligra)
   , ("Noto", noto)
   , ("Helvetica", helvet)
-  , ("Liberine", libertine)
-  , ("Chalkduster", chalkduster) ]
+  , ("Liberine", libertine) ]
 
 animation :: Animation
 animation = env $
diff --git a/playground/stack.yaml b/playground/stack.yaml
index bba3065..10b523e 100644
--- a/playground/stack.yaml
+++ b/playground/stack.yaml
@@ -1,4 +1,4 @@
-resolver: lts-16.12
+resolver: lts-16.31
 
 allow-newer: false
 
diff --git a/playground/viewer-elm/dist/index.html b/playground/viewer-elm/dist/index.html
index af56454..1b8bf73 100644
--- a/playground/viewer-elm/dist/index.html
+++ b/playground/viewer-elm/dist/index.html
@@ -114,7 +114,7 @@
             
             
Core Concepts
- + reanimate.rtfd.io/en/latest/introduction/
@@ -256,4 +256,4 @@ - \ No newline at end of file + diff --git a/reanimate.cabal b/reanimate.cabal index e165a9c..3b6fdb3 100644 --- a/reanimate.cabal +++ b/reanimate.cabal @@ -3,7 +3,7 @@ cabal-version: 2.0 -- see http://haskell.org/cabal/users-guide/ name: reanimate -version: 1.1.0.0 +version: 1.1.7.0 -- synopsis: -- description: license: PublicDomain @@ -14,7 +14,7 @@ synopsis: Animation library based on SVGs. homepage: https://reanimate.github.io bug-reports: https://github.com/reanimate/reanimate/issues build-type: Simple -extra-source-files: ChangeLog.md, examples/*.hs, examples/*.golden +extra-source-files: ChangeLog.md, examples/*.hs, examples/golden/*.golden extra-doc-files: docs/gifs/*.gif @@ -39,6 +39,11 @@ Source-Repository head library hs-source-dirs: src + if os(windows) + hs-source-dirs: windows + else + hs-source-dirs: unix + build-depends: unix default-language: Haskell2010 default-extensions: PackageImports, PatternSynonyms exposed-modules: Reanimate @@ -91,18 +96,21 @@ library Reanimate.Driver.CLI Reanimate.Driver.Magick Reanimate.Driver.Server - Reanimate.Driver.Compile + Reanimate.Driver.Daemon Reanimate.Misc Paths_reanimate Reanimate.Scene.Core Reanimate.Scene.Var Reanimate.Scene.Sprite Reanimate.Scene.Object + Detach + POVRay autogen-modules: Paths_reanimate + cpp-options: -DNO_HGEOMETRY build-depends: base >=4.10 && <5, JuicyPixels >=3.3.3, - aeson >=1.3.0.0, + aeson >=1.3.0.0 && <2.2, ansi-terminal >=0.8.0.4, array >=0.5.2.0, attoparsec >=0.13.2.0, @@ -119,10 +127,7 @@ library fingertree >=0.1.0.0, fsnotify >=0.3.0.1, geojson >=3.0.4, - ghcid >=0.7, hashable >=1.3.0.0, - hgeometry >=0.11.0.0, - hgeometry-combinatorial >=0.11.0.0, lens >=4.16.1, linear >=1.20.8, matrix >=0.3.6.1, @@ -134,7 +139,7 @@ library process >=1.6.3.0, random >=1.1, random-shuffle >=0.0.4, - reanimate-svg >=0.12.2.0, + reanimate-svg >=0.13.0.1, split >=0.2.3.3, temporary >=1.3, text >=1.2.3.0, @@ -145,9 +150,11 @@ library websockets >=0.12.7.0, xml >=1.3.14, cryptohash-sha256, - base64-bytestring + base64-bytestring, + network >=3.1.0.0 ghc-options: -Wall -fno-ignore-asserts + test-suite spec type: exitcode-stdio-1.0 main-is: Spec.hs @@ -159,6 +166,7 @@ test-suite spec Helpers hs-source-dirs: test ghc-options: -w + cpp-options: -DNO_HGEOMETRY build-depends: base, directory, diff --git a/src/Reanimate.hs b/src/Reanimate.hs index 28de64b..990ed15 100644 --- a/src/Reanimate.hs +++ b/src/Reanimate.hs @@ -35,6 +35,8 @@ no other parameters are given. Key features: -} module Reanimate ( reanimate, + reanimateLive, + reanimateLiveEntry, -- * Animations SVG, Time, @@ -101,13 +103,16 @@ module Reanimate , unVar -- :: Var s a -> Frame s a , spriteT -- :: Frame s Time , spriteDuration -- :: Frame s Duration + , signalS -- :: Sprite s -> Duration -> Signal -> Scene s () , newSprite -- :: Frame s SVG -> Scene s (Sprite s) + , newSpritePart -- :: Frame s SVG -> Scene s (Sprite s) , newSprite_ -- :: Frame s SVG -> Scene s () , newSpriteA -- :: Animation -> Scene s (Sprite s) , newSpriteA' -- :: Sync -> Animation -> Scene s (Sprite s) , newSpriteSVG -- :: SVG -> Scene s (Sprite s) , newSpriteSVG_ -- :: SVG -> Scene s () , destroySprite -- :: Sprite s -> Scene s () + , renderSprite -- :: Sprite s -> Frame s SVG , applyVar -- :: Var s a -> Sprite s -> (a -> SVG -> SVG) -> Scene s () , spriteModify -- :: Sprite s -> Frame s ((SVG,ZIndex) -> (SVG, ZIndex)) -> Scene s () , spriteMap -- :: Sprite s -> (SVG -> SVG) -> Scene s () @@ -196,6 +201,7 @@ import Reanimate.Blender import Reanimate.ColorMap import Reanimate.Constants import Reanimate.Driver +import Reanimate.Driver.Daemon import Reanimate.LaTeX import Reanimate.Parameters import Reanimate.Povray diff --git a/src/Reanimate/Animation.hs b/src/Reanimate/Animation.hs index 43e55c1..9ba7b84 100644 --- a/src/Reanimate/Animation.hs +++ b/src/Reanimate/Animation.hs @@ -17,6 +17,7 @@ module Reanimate.Animation , Animation -- * Creating animations , mkAnimation + , unsafeMkAnimation , animate , staticFrame , pause @@ -53,14 +54,13 @@ module Reanimate.Animation , renderSvg ) where -import Control.Arrow () import Data.Fixed (mod') import Graphics.SvgTree -import Graphics.SvgTree.Printer -import Reanimate.Constants -import Reanimate.Ease -import Reanimate.Svg.Constructors -import Text.XML.Light.Output +import Graphics.SvgTree.Printer (ppDocument) +import Reanimate.Constants (defaultStrokeWidth) +import Reanimate.Ease (Signal, reverseS) +import Reanimate.Svg.Constructors (mkGroup, scaleXY, withStrokeWidth) +import Text.XML.Light.Output (ppElement) -- | Duration of an animation or effect. Usually measured in seconds. type Duration = Double @@ -73,17 +73,26 @@ type SVG = Tree -- | Animations are SVGs over a finite time. data Animation = Animation Duration (Time -> SVG) --- | Construct an animation with a given duration. +-- | Construct an animation with a given duration. If the duration is not +-- positive throws 'Prelude.error'. mkAnimation :: Duration -> (Time -> SVG) -> Animation -mkAnimation = Animation +mkAnimation d f + | d > 0 = unsafeMkAnimation d f + | otherwise = error $ "Animation duration (" ++ show d ++ ") is not positive." + +-- | Construct an animation with a given duration, without checking that the +-- duration is positive. +unsafeMkAnimation :: Duration -> (Time -> SVG) -> Animation +unsafeMkAnimation = Animation -- | Construct an animation with a duration of @1@. animate :: (Time -> SVG) -> Animation animate = Animation 1 --- | Create an animation with provided @duration@, which consists of stationary frame displayed for its entire duration. +-- | Create an animation with provided @duration@, which consists of stationary +-- frame displayed for its entire duration. staticFrame :: Duration -> SVG -> Animation -staticFrame d svg = Animation d (const svg) +staticFrame d svg = mkAnimation d (const svg) -- | Query the duration of an animation. duration :: Animation -> Duration @@ -149,7 +158,7 @@ parLoopA (Animation d1 f1) (Animation d2 f2) = -- -- Example: -- --- @'Reanimate.Builtin.Documentation.drawBox' `'parLoopA'` 'adjustDuration' (*2) 'Reanimate.Builtin.Documentation.drawCircle'@ +-- @'Reanimate.Builtin.Documentation.drawBox' `'parDropA'` 'adjustDuration' (*2) 'Reanimate.Builtin.Documentation.drawCircle'@ -- -- <> parDropA :: Animation -> Animation -> Animation @@ -163,7 +172,8 @@ parDropA (Animation d1 f1) (Animation d2 f2) = where totalD = max d1 d2 --- | Empty animation (no SVG output) with a fixed duration. +-- | Empty animation (no SVG output) with a fixed duration. If the duration is +-- not positive throws 'Prelude.error'. -- -- Example: -- @@ -171,7 +181,7 @@ parDropA (Animation d1 f1) (Animation d2 f2) = -- -- <> pause :: Duration -> Animation -pause d = Animation d (const None) +pause d = mkAnimation d (const None) -- | Play left animation and freeze on the last frame, then play the right -- animation. New duration is '@duration lhs + duration rhs@'. @@ -184,9 +194,10 @@ pause d = Animation d (const None) andThen :: Animation -> Animation -> Animation andThen a b = a `parA` (pause (duration a) `seqA` b) --- | Calculate the frame that would be displayed at given point in @time@ of running @animation@. +-- | Calculate the frame that would be displayed at given point in @time@ of +-- running @animation@. -- --- The provided time parameter is clamped between 0 and animation duration. +-- The provided time parameter is clamped between 0 and animation duration. frameAt :: Time -> Animation -> SVG frameAt t (Animation d f) = f t' where @@ -197,8 +208,12 @@ renderTree :: SVG -> String renderTree t = maybe "" ppElement $ xmlOfTree t -- | Helper function for pretty-printing SVG nodes as SVG documents. -renderSvg :: Maybe Number -- ^ The number to use as value of the @width@ attribute of the resulting top-level svg element. If @Nothing@, the width attribute won't be rendered. - -> Maybe Number -- ^ Similar to previous argument, but for @height@ attribute. +renderSvg :: Maybe Number -- ^ The number to use as value of the @width@ + -- attribute of the resulting top-level svg element. + -- If @Nothing@, the width attribute won't be + -- rendered. + -> Maybe Number -- ^ Similar to previous argument, but for @height@ + -- attribute. -> SVG -- ^ SVG to render -> String -- ^ String representation of SVG XML markup renderSvg w h t = ppDocument doc @@ -227,7 +242,8 @@ renderSvg w h t = ppDocument doc mapA :: (SVG -> SVG) -> Animation -> Animation mapA fn (Animation d f) = Animation d (fn . f) --- | Freeze the last frame for @t@ seconds at the end of the animation. +-- | Freeze the last frame for @t@ seconds at the end of the animation. If the +-- duration is negative throws 'Prelude.error'. -- -- Example: -- @@ -235,9 +251,12 @@ mapA fn (Animation d f) = Animation d (fn . f) -- -- <> pauseAtEnd :: Duration -> Animation -> Animation -pauseAtEnd t a = a `andThen` pause t +pauseAtEnd t a + | t == 0 = a + | otherwise = a `andThen` pause t -- | Freeze the first frame for @t@ seconds at the beginning of the animation. +-- If the duration is negative throws 'Prelude.error'. -- -- Example: -- @@ -245,10 +264,12 @@ pauseAtEnd t a = a `andThen` pause t -- -- <> pauseAtBeginning :: Duration -> Animation -> Animation -pauseAtBeginning t a = - Animation t (freezeFrame 0 a) `seqA` a +pauseAtBeginning t a + | t == 0 = a + | otherwise = mkAnimation t (freezeFrame 0 a) `seqA` a --- | Freeze the first and the last frame of the animation for a specified duration. +-- | Freeze the first and the last frame of the animation for a specified +-- duration. If a duration is negative throws 'Prelude.error'. -- -- Example: -- @@ -263,13 +284,14 @@ freezeFrame :: Time -> Animation -> (Time -> SVG) freezeFrame t (Animation d f) = const $ f (t/d) -- | Change the duration of an animation. Animates are stretched or squished --- (rather than truncated) to fit the new duration. +-- (rather than truncated) to fit the new duration. If a changed duration is +-- not positive throws 'Prelude.error'. adjustDuration :: (Duration -> Duration) -> Animation -> Animation -adjustDuration fn (Animation d gen) = - Animation (fn d) gen +adjustDuration fn (Animation d gen) = mkAnimation (fn d) gen -- | Set the duration of an animation by adjusting its playback rate. The --- animation is still played from start to finish without being cropped. +-- animation is still played from start to finish without being cropped. If +-- the duration is not positive throws 'Prelude.error'. setDuration :: Duration -> Animation -> Animation setDuration newD = adjustDuration (const newD) @@ -296,8 +318,8 @@ playThenReverseA :: Animation -> Animation playThenReverseA a = a `seqA` reverseA a -- | Loop animation @n@ number of times. This number may be fractional and it --- may be less than 1. It must be greater than or equal to 0, though. --- New duration is @n*duration input@. +-- may be less than 1. It must be greater than 0, though. New duration is +-- @n*duration input@. If the duration is not positive throws 'Prelude.error'. -- -- Example: -- @@ -305,16 +327,21 @@ playThenReverseA a = a `seqA` reverseA a -- -- <> repeatA :: Double -> Animation -> Animation -repeatA n (Animation d f) = Animation (d*n) $ \t -> - f ((t*n) `mod'` 1) +repeatA n (Animation d f) = mkAnimation (d*n) $ \t -> f ((t*n) `mod'` 1) - --- | @freezeAtPercentage time animation@ creates an animation consisting of stationary frame, --- that would be displayed in the provided @animation@ at given @time@. --- The duration of the new animation is the same as the duration of provided @animation@. -freezeAtPercentage :: Time -- ^ value between 0 and 1. The frame displayed at this point in the original animation will be displayed for the duration of the new animation - -> Animation -- ^ original animation, from which the frame will be taken - -> Animation -- ^ new animation consisting of static frame displayed for the duration of the original animation +-- | @freezeAtPercentage time animation@ creates an animation consisting of +-- stationary frame, that would be displayed in the provided @animation@ at +-- given @time@. The duration of the new animation is the same as the duration +-- of provided @animation@. +freezeAtPercentage :: Time -- ^ value between 0 and 1. The frame displayed + -- at this point in the original animation + -- will be displayed for the duration of the + -- new animation + -> Animation -- ^ original animation, from which the frame + -- will be taken + -> Animation -- ^ new animation consisting of static frame + -- displayed for the duration of the original + -- animation freezeAtPercentage frac (Animation d genFrame) = Animation d $ const $ genFrame frac @@ -338,31 +365,35 @@ addStatic static = mapA (\frame -> mkGroup [static, frame]) signalA :: Signal -> Animation -> Animation signalA fn (Animation d gen) = Animation d $ gen . fn --- | @takeA duration animation@ creates a new animation consisting of initial segment of --- @animation@ of given @duration@, played at the same rate as the original animation. +-- | @takeA duration animation@ creates a new animation consisting of initial +-- segment of @animation@ of given @duration@, played at the same rate as the +-- original animation. If the duration is not positive throws 'Prelude.error'. -- --- The @duration@ parameter is clamped to be between 0 and @animation@'s duration. --- New animation duration is equal to (eventually clamped) @duration@. +-- The @duration@ parameter is clamped to be up to the @animation@'s duration. +-- New animation duration is equal to (eventually clamped) @duration@. takeA :: Duration -> Animation -> Animation -takeA len (Animation d gen) = Animation len' $ \t -> - gen (t * len'/d) - where - len' = clamp 0 d len +takeA len a@(Animation d gen) + | len >= d = a + | otherwise = mkAnimation len $ \t -> gen (t * len/d) --- | @dropA duration animation@ creates a new animation by dropping initial segment --- of length @duration@ from the provided @animation@, played at the same rate as the original animation. +-- | @dropA duration animation@ creates a new animation by dropping initial +-- segment of length @duration@ from the provided @animation@, played at the +-- same rate as the original animation. If the duration greater than or equal +-- to the @animation@'s duration throws 'Prelude.error'. -- --- The @duration@ parameter is clamped to be between 0 and @animation@'s duration. --- The duration of the resulting animation is duration of provided @animation@ minus (eventually clamped) @duration@. +-- The @duration@ parameter is clamped to be non-negative. The duration of the +-- resulting animation is duration of provided @animation@ minus @duration@. dropA :: Duration -> Animation -> Animation -dropA len (Animation d gen) = Animation len' $ \t -> - gen (t * len'/d + len/d) +dropA len a@(Animation d gen) + | len <= 0 = a + | otherwise = mkAnimation rest $ \t -> gen (t * rest/d + len/d) where - len' = d - clamp 0 d len + rest = d - len --- | @lastA duration animation@ return the last @duration@ seconds of the animation. +-- | @lastA duration animation@ return the last @duration@ seconds of the +-- animation. If the duration is not positive throws 'Prelude.error'. lastA :: Duration -> Animation -> Animation -lastA len a = dropA (duration a - len) a +lastA len a@(Animation d _) = dropA (d - len) a clamp :: Double -> Double -> Double -> Double clamp a b number diff --git a/src/Reanimate/Blender.hs b/src/Reanimate/Blender.hs index 6935339..2e66375 100644 --- a/src/Reanimate/Blender.hs +++ b/src/Reanimate/Blender.hs @@ -36,17 +36,17 @@ module Reanimate.Blender , blender' ) where -import Data.Hashable +import Data.Hashable (Hashable (hash)) import Data.Text (Text) import qualified Data.Text.IO as T import Graphics.SvgTree (Tree) -import Reanimate.Animation -import Reanimate.Cache -import Reanimate.Constants -import Reanimate.Misc -import Reanimate.Parameters -import Reanimate.Raster -import Reanimate.Svg.Constructors +import Reanimate.Animation (SVG) +import Reanimate.Cache (cacheFile, encodeInt) +import Reanimate.Constants (screenHeight, screenWidth) +import Reanimate.Misc (requireExecutable, runCmd) +import Reanimate.Parameters (pNoExternals) +import Reanimate.Raster (mkImage) +import Reanimate.Svg.Constructors (mkText) import System.FilePath (replaceExtension, (<.>)) import System.IO.Unsafe (unsafePerformIO) diff --git a/src/Reanimate/Builtin/CirclePlot.hs b/src/Reanimate/Builtin/CirclePlot.hs index c736ea1..e95185c 100644 --- a/src/Reanimate/Builtin/CirclePlot.hs +++ b/src/Reanimate/Builtin/CirclePlot.hs @@ -9,13 +9,15 @@ Portability : POSIX Convenience module for rendering circle plots. -} -module Reanimate.Builtin.CirclePlot where +module Reanimate.Builtin.CirclePlot + ( circlePlot + ) where -import Codec.Picture -import Graphics.SvgTree (Tree) -import Reanimate.Raster -import Reanimate.Svg -import Reanimate.Constants +import Codec.Picture (PixelRGBA8 (..), generateImage) +import Graphics.SvgTree (Tree) +import Reanimate.Constants (screenHeight) +import Reanimate.Raster (embedImage) +import Reanimate.Svg (flipYAxis, scaleToHeight) -- | Circle plots are scaled to 'screenHeight'. -- diff --git a/src/Reanimate/Builtin/Documentation.hs b/src/Reanimate/Builtin/Documentation.hs index d708fcb..d4dba53 100644 --- a/src/Reanimate/Builtin/Documentation.hs +++ b/src/Reanimate/Builtin/Documentation.hs @@ -12,11 +12,11 @@ GIFs for a consistent look and feel. -} module Reanimate.Builtin.Documentation where -import Reanimate.Animation -import Reanimate.Svg -import Reanimate.Raster -import Reanimate.Constants -import Codec.Picture +import Codec.Picture (PixelRGB8, PixelRGBA8 (..), generateImage) +import Reanimate.Animation (Animation, SVG, mapA, mkAnimation) +import Reanimate.Constants (screenHeight, screenWidth) +import Reanimate.Raster (embedImage) +import Reanimate.Svg -- | Default environment for API documentation GIFs. docEnv :: Animation -> Animation diff --git a/src/Reanimate/Builtin/Images.hs b/src/Reanimate/Builtin/Images.hs index d8fbe38..fc7bce1 100644 --- a/src/Reanimate/Builtin/Images.hs +++ b/src/Reanimate/Builtin/Images.hs @@ -1,24 +1,24 @@ -{-| -Module : Reanimate.Builtin.Images -Copyright : Written by David Himmelstrup -License : Unlicense -Maintainer : lemmih@gmail.com -Stability : experimental -Portability : POSIX - -Collection of built-in images. - --} +-- | +-- Module : Reanimate.Builtin.Images +-- Copyright : Written by David Himmelstrup +-- License : Unlicense +-- Maintainer : lemmih@gmail.com +-- Stability : experimental +-- Portability : POSIX +-- +-- Collection of built-in images. module Reanimate.Builtin.Images - ( svgLogo - , haskellLogo - , githubIcon - , githubWhiteIcon - , smallEarth - ) where + ( svgLogo, + haskellLogo, + githubIcon, + githubWhiteIcon, + smallEarth, + ) +where import Codec.Picture import qualified Data.ByteString as B +import qualified Data.Text.IO as T import Graphics.SvgTree (parseSvgFile) import Paths_reanimate import Reanimate.Animation @@ -28,7 +28,7 @@ import System.IO.Unsafe embedImage :: FilePath -> IO SVG embedImage key = do svg_file <- getDataFileName key - svg_data <- B.readFile svg_file + svg_data <- T.readFile svg_file case parseSvgFile svg_file svg_data of Nothing -> error "Malformed svg" Just svg -> return $ embedDocument svg @@ -42,21 +42,25 @@ loadJPG key = unsafePerformIO $ do Right img -> return $ convertRGBA8 img {- HLINT ignore svgLogo -} + -- | <> svgLogo :: SVG svgLogo = unsafePerformIO $ embedImage "data/svg-logo.svg" {- HLINT ignore haskellLogo -} + -- | <> haskellLogo :: SVG haskellLogo = unsafePerformIO $ embedImage "data/haskell.svg" {- HLINT ignore githubIcon -} + -- | <> githubIcon :: SVG githubIcon = unsafePerformIO $ embedImage "data/github-icon.svg" {-# NOINLINE githubWhiteIcon #-} + -- | <> githubWhiteIcon :: SVG githubWhiteIcon = unsafePerformIO $ embedImage "data/github-icon-white.svg" diff --git a/src/Reanimate/Builtin/Slide.hs b/src/Reanimate/Builtin/Slide.hs index ffb5b2c..cd43c22 100644 --- a/src/Reanimate/Builtin/Slide.hs +++ b/src/Reanimate/Builtin/Slide.hs @@ -7,10 +7,10 @@ Portability : POSIX -} module Reanimate.Builtin.Slide where -import Reanimate.Transition -import Reanimate.Constants -import Reanimate.Svg -import Reanimate.Effect +import Reanimate.Constants (screenHeight, screenWidth) +import Reanimate.Effect (constE, translateE) +import Reanimate.Svg (translate) +import Reanimate.Transition (Transition, effectT) -- | <> slideLeftT :: Transition diff --git a/src/Reanimate/Builtin/TernaryPlot.hs b/src/Reanimate/Builtin/TernaryPlot.hs index 683330f..93db600 100644 --- a/src/Reanimate/Builtin/TernaryPlot.hs +++ b/src/Reanimate/Builtin/TernaryPlot.hs @@ -21,10 +21,10 @@ module Reanimate.Builtin.TernaryPlot , fromCartesianCoords ) where -import Codec.Picture +import Codec.Picture (PixelRGBA8 (..), generateImage) import Graphics.SvgTree (Tree) -import Reanimate.Raster -import Reanimate.Svg +import Reanimate.Raster (embedImage) +import Reanimate.Svg (flipYAxis, scaleToWidth, translate) -- a+b+c=1 -- | Left-most coordinate. diff --git a/src/Reanimate/Cache.hs b/src/Reanimate/Cache.hs index 91a6a23..0144ce6 100644 --- a/src/Reanimate/Cache.hs +++ b/src/Reanimate/Cache.hs @@ -8,24 +8,24 @@ module Reanimate.Cache , encodeInt ) where -import Control.Exception +import Control.Exception (evaluate) import Control.Monad (unless) -import Data.Bits -import Data.Hashable -import Data.IORef +import Data.Bits (Bits (shiftR)) +import Data.Hashable (Hashable (hash)) +import Data.IORef (IORef, atomicModifyIORef, newIORef, readIORef) import Data.Map (Map) import qualified Data.Map as Map import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.IO as T -import Graphics.SvgTree (Tree, unparse, pattern None) +import Graphics.SvgTree (Tree, pattern None, unparse) import Reanimate.Animation (renderTree) -import Reanimate.Misc (renameOrCopyFile,getReanimateCacheDirectory) -import System.Directory -import System.FilePath -import System.IO -import System.IO.Temp -import System.IO.Unsafe +import Reanimate.Misc (getReanimateCacheDirectory, renameOrCopyFile) +import System.Directory (doesFileExist) +import System.FilePath ((<.>), ()) +import System.IO (hClose) +import System.IO.Temp (openTempFile, withSystemTempFile) +import System.IO.Unsafe (unsafePerformIO) import Text.XML.Light (Content (..), parseXML) -- Memory cache and disk cache @@ -104,6 +104,6 @@ encodeInt i = worker (fromIntegral i) 60 worker key sh | sh < 0 = [] | otherwise = - case (key `shiftR` sh) `mod` 64 of - idx -> alphabet !! fromIntegral idx : worker key (sh-6) - alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+$" + case (key `shiftR` sh) `mod` 32 of + idx -> alphabet !! fromIntegral idx : worker key (sh-5) + alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" diff --git a/src/Reanimate/ColorComponents.hs b/src/Reanimate/ColorComponents.hs index 456eb2c..203e653 100644 --- a/src/Reanimate/ColorComponents.hs +++ b/src/Reanimate/ColorComponents.hs @@ -22,16 +22,16 @@ module Reanimate.ColorComponents , fromRGB8 ) where -import Codec.Picture -import Codec.Picture.Types -import Data.Colour -import Data.Colour.CIE +import Codec.Picture (Pixel (pixelOpacity), PixelRGB8 (..), PixelRGBA8 (..)) +import Codec.Picture.Types (TransparentPixel (dropTransparency)) +import Data.Colour (Colour) +import Data.Colour.CIE (cieLAB, cieLABView, cieXYZ, cieXYZView) import Data.Colour.CIE.Illuminant (d65) -import Data.Colour.RGBSpace -import Data.Colour.RGBSpace.HSV -import Data.Colour.SRGB -import Data.Fixed -import Reanimate.Ease +import Data.Colour.RGBSpace (RGB (RGB), uncurryRGB) +import Data.Colour.RGBSpace.HSV (hsv, hsvView) +import Data.Colour.SRGB (sRGB, sRGB24, toSRGB, toSRGBBounded) +import Data.Fixed (mod') +import Reanimate.Ease (fromToS) -- | Constructor and destructor for color's three components. data ColorComponents = ColorComponents diff --git a/src/Reanimate/ColorMap.hs b/src/Reanimate/ColorMap.hs index f7f0226..72c7bef 100644 --- a/src/Reanimate/ColorMap.hs +++ b/src/Reanimate/ColorMap.hs @@ -19,15 +19,15 @@ module Reanimate.ColorMap , greyscale ) where -import Data.Text (Text) -import Data.Vector (Vector) -import qualified Data.Text as T -import qualified Data.Vector as V -import Codec.Picture -import Data.Char -import Data.Bits +import Codec.Picture (Pixel8, PixelRGB8 (..)) +import Data.Bits (Bits (shiftL)) +import Data.Char (digitToInt) +import Data.Colour.RGBSpace (RGB (RGB)) import qualified Data.Colour.RGBSpace.HSV as HSV -import Data.Colour.RGBSpace +import Data.Text (Text) +import qualified Data.Text as T +import Data.Vector (Vector) +import qualified Data.Vector as V -- | Given a number t in the range [0,1], returns the corresponding color from -- the “turbo” color scheme by Anton Mikhailov. diff --git a/src/Reanimate/Constants.hs b/src/Reanimate/Constants.hs index b12b125..3198cc4 100644 --- a/src/Reanimate/Constants.hs +++ b/src/Reanimate/Constants.hs @@ -14,7 +14,7 @@ module Reanimate.Constants , defaultStrokeWidth ) where -import Graphics.SvgTree +import Graphics.SvgTree (Dpi) -- | Number of units from the left-most point to the right-most point on the screen. screenWidth :: Fractional a => a diff --git a/src/Reanimate/Debug.hs b/src/Reanimate/Debug.hs index 91e7571..df8d60a 100644 --- a/src/Reanimate/Debug.hs +++ b/src/Reanimate/Debug.hs @@ -12,15 +12,16 @@ module Reanimate.Debug ) where -import Data.IORef -import System.IO.Unsafe -import Control.Exception -import Reanimate.Animation -import Reanimate.Svg -import Reanimate.LaTeX -import Reanimate.Constants -import Text.Printf -import qualified Data.Text as T +import Control.Exception (evaluate) +import Data.IORef (IORef, atomicModifyIORef', modifyIORef', newIORef) +import qualified Data.Text as T +import Reanimate.Animation (Animation, SVG, duration, parA, pause, seqA, staticFrame) +import Reanimate.Constants (defaultStrokeWidth) +import Reanimate.LaTeX (latex) +import Reanimate.Svg (center, mkGroup, translate, withFillColor, withStrokeColor, + withStrokeWidth) +import System.IO.Unsafe (unsafePerformIO) +import Text.Printf (printf) {-# NOINLINE traceBuffer #-} traceBuffer :: IORef [Animation] diff --git a/src/Reanimate/Driver.hs b/src/Reanimate/Driver.hs index a25e793..70524c0 100644 --- a/src/Reanimate/Driver.hs +++ b/src/Reanimate/Driver.hs @@ -4,18 +4,19 @@ module Reanimate.Driver ) where -import Control.Applicative ((<|>)) +import Control.Applicative ((<|>)) +import Control.Concurrent import Control.Monad -import Data.Maybe import Data.Either -import Reanimate.Animation (Animation) -import Reanimate.Driver.Check +import Data.Maybe +import Paths_reanimate (getDataFileName) +import Reanimate.Animation (Animation, duration) import Reanimate.Driver.CLI -import Reanimate.Driver.Compile -import Reanimate.Driver.Server +import Reanimate.Driver.Check +import Reanimate.Driver.Daemon import Reanimate.Parameters -import Reanimate.Render (render, renderSnippets, renderSvgs, - selectRaster) +import Reanimate.Render (render, renderSnippets, renderSvgs, renderSvgs_, + selectRaster) import System.Directory import System.Exit import System.FilePath @@ -116,7 +117,7 @@ reanimate animation = do -- hSetBinaryMode stdout True renderSnippets animation Check -> checkEnvironment - View {..} -> serve viewVerbose viewGHCPath viewGHCOpts viewOrigin + View {..} -> viewAnimation viewDetach animation Render {..} -> do let fmt = guessParameter renderFormat (fmap presetFormat renderPreset) @@ -132,7 +133,7 @@ reanimate animation = do target <- case renderTarget of Nothing -> do - mbSelf <- findOwnSource + mbSelf <- pure Nothing let ext = formatExtension fmt self = fromMaybe "output" mbSelf pure $ replaceExtension self ext @@ -161,47 +162,26 @@ reanimate animation = do exitWith (ExitFailure 1) return raster else selectRaster renderRaster + setRaster raster + setFPS fps + setWidth width + setHeight height + printf + "Animation options:\n\ + \ fps: %d\n\ + \ width: %d\n\ + \ height: %d\n\ + \ fmt: %s\n\ + \ target: %s\n\ + \ raster: %s\n" + fps + width + height + (showFormat fmt) + target + (show raster) - if renderCompile - then compile $ - [ "render" - , "--fps" - , show fps - , "--width" - , show width - , "--height" - , show height - , "--format" - , showFormat fmt - , "--raster" - , showRaster raster - , "--target" - , target - , "+RTS" - , "-N" - , "-RTS" - ] ++ [ "--partial" | renderPartial ] - else do - setRaster raster - setFPS fps - setWidth width - setHeight height - printf - "Animation options:\n\ - \ fps: %d\n\ - \ width: %d\n\ - \ height: %d\n\ - \ fmt: %s\n\ - \ target: %s\n\ - \ raster: %s\n" - fps - width - height - (showFormat fmt) - target - (show raster) - - render animation target raster fmt width height fps renderPartial + render animation target raster fmt width height fps renderPartial guessParameter :: Maybe a -> Maybe a -> a -> a guessParameter a b def = fromMaybe def (a <|> b) @@ -220,3 +200,31 @@ userPreferredDimensions Nothing Nothing = Nothing makeEven :: Int -> Int makeEven x | even x = x | otherwise = x - 1 + +sanityCheck :: IO () +sanityCheck = do + url <- getDataFileName "viewer-elm/dist/index.html" + hasClient <- doesFileExist url + unless hasClient $ do + hPutStrLn stderr $ "Couldn't find web client at: " ++ url + hPutStrLn stderr $ "You may need to run:" + hPutStrLn stderr $ " $ export reanimate_datadir=`pwd`" + hPutStrLn stderr $ "For more information, see this cabal issue: https://github.com/haskell/cabal/issues/6235" + exitFailure + +-- serve viewVerbose viewGHCPath viewGHCOpts viewOrigin +viewAnimation :: Bool -> Animation -> IO () +viewAnimation _detach animation = do + sanityCheck + detached <- ensureDaemon + + let rate = 60 + count = round (duration animation * rate) :: Int + sendCommand $ DaemonCount count + renderSvgs_ animation $ \nth path -> do + sendCommand $ DaemonFrame nth path + + unless detached $ do + putStrLn "Daemon mode. Hit ctrl-c to terminate." + forever $ threadDelay (10^(6::Int)) + diff --git a/src/Reanimate/Driver/CLI.hs b/src/Reanimate/Driver/CLI.hs index 82ea0a1..f8976ec 100644 --- a/src/Reanimate/Driver/CLI.hs +++ b/src/Reanimate/Driver/CLI.hs @@ -9,12 +9,10 @@ module Reanimate.Driver.CLI , showRaster ) where -import Data.Char -import Data.Monoid +import Data.Char (toLower) import Options.Applicative import Prelude -import Reanimate.Render (FPS, Format (..), Height, Raster (..), - Width) +import Reanimate.Render (FPS, Format (..), Height, Raster (..), Width) newtype Options = Options { optsCommand :: Command @@ -29,10 +27,7 @@ data Command | Test | Check | View - { viewVerbose :: Bool - , viewGHCPath :: Maybe FilePath - , viewGHCOpts :: [String] - , viewOrigin :: Maybe FilePath + { viewDetach :: Bool } | Render { renderTarget :: Maybe String @@ -54,12 +49,12 @@ data Preset = Youtube | ExampleGif | Quick | MediumQ | HighQ | LowFPS readRaster :: String -> Maybe Raster readRaster raster = case map toLower raster of - "none" -> Just RasterNone - "auto" -> Just RasterAuto - "inkscape" -> Just RasterInkscape - "rsvg" -> Just RasterRSvg - "imagemagick" -> Just RasterMagick - _ -> Nothing + "none" -> Just RasterNone + "auto" -> Just RasterAuto + "inkscape" -> Just RasterInkscape + "rsvg" -> Just RasterRSvg + "imagemagick" -> Just RasterMagick + _ -> Nothing showRaster :: Raster -> String showRaster RasterNone = "none" @@ -156,16 +151,7 @@ viewCommand = info parse where parse = View <$> switch - (long "verbose" <> short 'v') - <*> optional (strOption (long "ghc" - <> metavar "PATH" - <> help "Path to GHC binary")) - <*> many (strOption (long "ghc-opt" - <> short 'G' - <> help "Additional option to pass to ghc")) - <*> optional (strOption (long "self" - <> metavar "PATH" - <> help "Source file used for live-reloading")) + (long "detach" <> short 'd') renderCommand :: ParserInfo Command renderCommand = info parse diff --git a/src/Reanimate/Driver/Check.hs b/src/Reanimate/Driver/Check.hs index b738cba..66ae5c4 100644 --- a/src/Reanimate/Driver/Check.hs +++ b/src/Reanimate/Driver/Check.hs @@ -8,17 +8,18 @@ module Reanimate.Driver.Check ) where import Control.Exception (SomeException, handle) -import Control.Monad -import Data.Maybe -import Data.Version -import Reanimate.Misc (runCmd_) +import Control.Monad (forM_) +import Data.Maybe (fromMaybe, listToMaybe) +import Data.Version (Version (Version), parseVersion, showVersion) +import POVRay (povrayApp) import Reanimate.Driver.Magick (magickCmd) +import Reanimate.Misc (runCmd_) import System.Console.ANSI.Codes import System.Directory (findExecutable) -import System.IO -import System.IO.Temp -import Text.ParserCombinators.ReadP -import Text.Printf +import System.IO (hClose, hPutStr) +import System.IO.Temp (withSystemTempDirectory, withTempFile) +import Text.ParserCombinators.ReadP (readP_to_S) +import Text.Printf (printf) -------------------------------------------------------------------------- -- Check environment @@ -88,7 +89,7 @@ hasDvisvgm :: IO (Either String String) hasDvisvgm = hasProgram "dvisvgm" hasPovray :: IO (Either String String) -hasPovray = hasProgram "povray" +hasPovray = hasProgram povrayApp hasFFmpeg :: IO (Either String String) hasFFmpeg = checkMinVersion minVersion <$> ffmpegVersion @@ -120,7 +121,7 @@ hasRSvg = checkMinVersion minVersion <$> rsvgVersion hasInkscape :: IO (Either String String) hasInkscape = checkMinVersion minVersion <$> inkscapeVersion where - minVersion = Version [0,92] [] + minVersion = Version [1,0] [] hasMagick :: IO (Either String String) hasMagick = checkMinVersion minVersion <$> magickVersion diff --git a/src/Reanimate/Driver/Compile.hs b/src/Reanimate/Driver/Compile.hs deleted file mode 100644 index 272766d..0000000 --- a/src/Reanimate/Driver/Compile.hs +++ /dev/null @@ -1,35 +0,0 @@ -module Reanimate.Driver.Compile ( compile ) where - -import Reanimate.Driver.Server (findOwnSource) -import System.Directory -import System.Exit -import System.FilePath -import System.Process -import System.IO - -compile :: [String] -> IO () -compile opts = do - mbSelf <- findOwnSource - case mbSelf of - Nothing -> do - hPutStrLn stderr - "Failed to find source code. Did you already compile the animations?\n\ - \Try running again without the --compile flag." - exitFailure - Just self -> do - let selfDir = takeDirectory self - selfName = takeBaseName self - outDir = selfDir ".reanimate" selfName - target = outDir selfName - ghcOptions = - ["-rtsopts", "--make", "-threaded", "-O2"] ++ - ["-odir", outDir, "-hidir", outDir] ++ - [self, "-o", target] - createDirectoryIfMissing True outDir - withCurrentDirectory selfDir $ do - checkExitCode =<< rawSystem "stack" (["ghc", "--"] ++ ghcOptions) - checkExitCode =<< rawSystem target opts - -checkExitCode :: ExitCode -> IO () -checkExitCode ExitSuccess = return () -checkExitCode (ExitFailure n) = exitWith (ExitFailure n) diff --git a/src/Reanimate/Driver/Daemon.hs b/src/Reanimate/Driver/Daemon.hs new file mode 100644 index 0000000..a7ea7b0 --- /dev/null +++ b/src/Reanimate/Driver/Daemon.hs @@ -0,0 +1,182 @@ +module Reanimate.Driver.Daemon where + +import Control.Concurrent +import Control.Exception as E +import Control.Monad +import qualified Data.ByteString.Char8 as BS +import Network.Socket +import Network.Socket.ByteString +import qualified Reanimate.Driver.Server as Server +import System.FSNotify +import System.FilePath +import System.Environment + +import Detach + +{- +Main run message: + + Reanimate has gone into daemon mode and will block until you hit + ctrl-c. While Reanimate is in daemon mode, you can open a new + console or terminal and execute your animation code again. It'll + automatically send the new animation to the browser window. + + Linux users can pass --daemon to reanimate to run the process in + the background. Windows users have to use PowerShell and explicitly + fork the process: + Start-Process -NoNewWindow ./reanimate_exe + + Connection to the browser window will be lost if you hit ctrl-c. + + +Executing with daemon: + Send animation to daemon and exit. +Executing without daemon: + Run daemon locally. + Render animation once. + Wait without exiting. + Detach if given --daemon flag. Only for Linux. + +Executing on Linux: + Running 'main' will start the daemon if it isn't already running. + Then it'll render the animation and send it to the daemon. + The daemon will open a browser window. + Daemon stops after 30 minutes of inactivity. + +Executing on Windows: + 'main' will act as daemon and not return. + Powershell command for running in the background: + Start-Process -NoNewWindow ./reanimate_exe + + Subsequent runs will send animation to daemon and quit. + +In GHCi: + Start local daemon thread if necessary. + :cmd reanimateLive + 1. wait for changes + 2. ":r" + 3. ":main" + 4. ":cmd Reanimate.reanimateLive" + + + +Web port: 9161 +Daemon port: 9162? + +-} + +{- + Improvements over previous infrastructure: + - Multiple browser windows can be open. + - Browser window won't get stuck trying to open a connection. + - GHCi is robust and works with both cabal and stack. + - Refreshing the code will re-open closed browser windows. +-} + +-- | Load a reanimate program in GHCi and make sure 'main' is available. +-- Then run: +-- @ +-- :cmd reanimateLive +-- @ +-- +-- This works by sending the commands ':r' and ':main' to your GHCi instance +-- when any source file is changed. +reanimateLive :: IO String +reanimateLive = do + void ensureDaemon + args <- getArgs + case args of + ["primed"] -> waitForChanges + _ -> return () + return $ unlines + [ ":r" + , ":main" + , ":cmd System.Environment.withArgs [\"primed\"] Reanimate.reanimateLive" ] + +-- | Load an animation in GHCi. Anything of type 'Animation' can be live reloaded. +-- +-- @ +-- :cmd reanimateLiveEntry "drawCircle" +-- @ +-- +-- This works by sending the commands ':r' and 'Reanimate.reanimate {entry}' to your +-- GHCi instance when any source file is changed. +reanimateLiveEntry :: String -> IO String +reanimateLiveEntry animation = do + void ensureDaemon + args <- getArgs + case args of + ["primed"] -> waitForChanges + _ -> return () + return $ unlines + [ ":r" + , "Reanimate.reanimate (" ++ animation ++ ")" + , ":cmd System.Environment.withArgs [\"primed\"] (Reanimate.reanimateLiveEntry " ++ show animation ++ ")"] + +waitForChanges :: IO () +waitForChanges = withManager $ \mgr -> do + lock <- newEmptyMVar + stop <- watchTree mgr "." check (const $ putMVar lock ()) + takeMVar lock + stop + where + check event = + takeExtension (eventPath event) `elem` sourceExtensions || + takeExtension (eventPath event) `elem` dataExtensions + sourceExtensions = [".hs", ".lhs"] + dataExtensions = [".jpg", ".png", ".bmp", ".pov", ".tex", ".csv"] + + +data DaemonCommand + = DaemonCount Int + | DaemonFrame Int FilePath + | DaemonStop + deriving (Show) + +sendCommand :: DaemonCommand -> IO () +sendCommand cmd = withSocketsDo $ handle (\SomeException{} -> return ()) $ do + addr <- resolve + E.bracket (open addr) close $ \sock -> do + void $ send sock $ case cmd of + DaemonCount count -> BS.pack $ unwords ["frame_count", show count] + DaemonFrame nth path -> BS.pack $ unwords ["frame", show nth, path] + DaemonStop -> BS.pack $ unwords ["stop"] + return () + where + resolve = do + let hints = defaultHints { addrSocketType = Stream } + head <$> getAddrInfo (Just hints) (Just "127.0.0.1") (Just "9162") + open addr = E.bracketOnError (oSocket addr) close $ \sock -> do + connect sock $ addrAddress addr + return sock + +hasDaemon :: IO Bool +hasDaemon = withSocketsDo $ handle (\SomeException{} -> return False) $ do + addr <- resolve + E.bracket (open addr) close (const $ return True) + where + resolve = do + let hints = defaultHints { addrSocketType = Stream } + head <$> getAddrInfo (Just hints) (Just "127.0.0.1") (Just "9162") + open addr = E.bracketOnError (oSocket addr) close $ \sock -> do + connect sock $ addrAddress addr + return sock + +oSocket :: AddrInfo -> IO Socket +oSocket addr = socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr) + +ensureDaemon :: IO Bool +ensureDaemon = do + daemon <- hasDaemon + if daemon + then pure True + else localDaemon + +killDaemon :: IO () +killDaemon = sendCommand DaemonStop + +localDaemon :: IO Bool +localDaemon = do + killDaemon + detach Server.daemon + diff --git a/src/Reanimate/Driver/Server.hs b/src/Reanimate/Driver/Server.hs index 9f97d96..f3f8c79 100644 --- a/src/Reanimate/Driver/Server.hs +++ b/src/Reanimate/Driver/Server.hs @@ -1,286 +1,132 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} module Reanimate.Driver.Server - ( serve - , findOwnSource + ( daemon ) where import Control.Concurrent -import Control.Exception (SomeException, catch, finally) -import Control.Monad -import Data.IORef -import Data.Text (Text) -import qualified Data.Text as T -import qualified Data.Text.Read as T -import Data.Time -import GHC.Environment (getFullArgs) -import Language.Haskell.Ghcid +import Control.Exception (finally) +import qualified Control.Exception as E +import Control.Monad (forM_, forever, unless, void, when) +import qualified Data.ByteString.Char8 as BS +import qualified Data.Foldable as F +import qualified Data.Map as Map +import qualified Data.Text as T +import Network.Socket (AddrInfo (..), AddrInfoFlag (..), SocketOption (..), + SocketType (Stream), accept, bind, close, defaultHints, + getAddrInfo, listen, setCloseOnExecIfNeeded, + setSocketOption, socket, withFdSocket, withSocketsDo) +import Network.Socket.ByteString (recv) import Network.WebSockets -import Paths_reanimate -import Reanimate.Misc (runCmdLazy, runCmd_) -import System.Directory (createDirectoryIfMissing, - doesFileExist, findFile, listDirectory, - makeAbsolute, - withCurrentDirectory) -import System.Environment (getProgName) -import System.Exit -import System.FilePath -import System.FSNotify -import System.IO -import System.IO.Temp -import System.Process -import Web.Browser (openBrowser) +import Paths_reanimate (getDataFileName) +import System.Exit (exitFailure) +import System.IO (hPutStrLn, stderr) +import Web.Browser (openBrowser) opts :: ConnectionOptions opts = defaultConnectionOptions { connectionCompressionOptions = PermessageDeflateCompression defaultPermessageDeflate } -serve :: Bool -> Maybe FilePath -> [String] -> Maybe FilePath -> IO () -serve verbose mbGHCPath extraGHCOpts mbSelfPath = withManager $ \watch -> do - hSetBuffering stdin NoBuffering - self <- maybe requireOwnSource pure mbSelfPath - when verbose $ - logMsg $ "Found own source code at: " ++ self - hasConnectionVar <- newMVar False - ghci <- ghciBackend mbGHCPath self - -- There might already browser window open. Wait 2s to see if that window - -- connects to us. If not, open a new window. - _ <- forkIO $ do - threadDelay (2*10^(6::Int)) - hasConn <- readMVar hasConnectionVar - unless hasConn openViewer - logMsg "Listening..." +daemon :: IO () +daemon = do + state <- newMVar (0, Map.empty) + connsRef <- newMVar Map.empty + + self <- myThreadId + + dTid <- daemonReceive self $ \msg -> + case msg of + WebStatus _status -> return () + WebError _err -> return () + WebFrameCount count -> do + void $ swapMVar state (count, Map.empty) + conns <- readMVar connsRef + F.forM_ conns $ \(conn) -> do + sendWebMessage conn (WebFrameCount count) + when (Map.null conns) openViewer + WebFrame nth path -> do + modifyMVar_ state $ \(count, frames) -> + pure (count, Map.insert nth path frames) + conns <- readMVar connsRef + F.forM_ conns $ \conn -> do + sendWebMessage conn (WebFrame nth path) + + openViewer + let options = ServerOptions { serverHost = "127.0.0.1" , serverPort = 9161 , serverConnectionOptions = opts , serverRequirePong = Nothing } - withSystemTempDirectory "reanimate-svgs" $ \tmpDir -> - runServerWithOptions options $ \pending -> do - logMsg "New connection received." - hasConn <- swapMVar hasConnectionVar True - if hasConn - then do - logMsg "Already connected to browser. Rejecting." - rejectRequestWith pending defaultRejectRequest - else do - createDirectoryIfMissing True tmpDir - conn <- acceptRequest pending - slave <- newEmptyMVar - let handler = modifyMVar_ slave $ \tid -> do - logMsg "Reloading code..." - killThread tid - forkIO $ ignoreErrors $ slaveHandler verbose mbGHCPath extraGHCOpts conn ghci self tmpDir - killSlave = do - tid <- takeMVar slave - killThread tid - stop <- watchFile watch self handler - putMVar slave =<< forkIO (return ()) - handler - let loop = do - -- FIXME: We don't use msg here. - _msg <- receiveData conn :: IO T.Text - handler - loop - cleanup = do - stop - killSlave - _ <- swapMVar hasConnectionVar False - return () - loop `finally` cleanup -ignoreErrors :: IO () -> IO () -ignoreErrors action = action `catch` \(_::SomeException) -> return () + runServerWithOptions options (\pending -> do + tid <- myThreadId + + conn <- acceptRequest pending + + modifyMVar_ connsRef $ pure . Map.insert tid conn + + (count, frames) <- readMVar state + when (count > 0) $ do + sendWebMessage conn (WebFrameCount count) + forM_ (Map.toList frames) $ \(nth, path) -> + sendWebMessage conn (WebFrame nth path) + + let loop = do + -- FIXME: We don't use msg here. + _msg <- receiveData conn :: IO T.Text + loop + cleanup = do + modifyMVar_ connsRef $ pure . Map.delete tid + nConns <- Map.size <$> readMVar connsRef + when (nConns == 0) $ do + threadDelay (second * 5) + nConns' <- Map.size <$> readMVar connsRef + when (nConns'==0) $ killThread self + loop `finally` cleanup) + `finally` (killThread dTid) + +second :: Int +second = 10^(6::Int) + +daemonReceive :: ThreadId -> (WebMessage -> IO ()) -> IO ThreadId +daemonReceive parent cb = withSocketsDo $ do + addr <- resolve + sock <- open addr + forkIO $ handler sock `finally` close sock + where + handler sock = forever $ E.bracketOnError (accept sock) (close . fst) $ \(conn, _peer) -> do + inp <- BS.unpack <$> recv conn 4096 + case words inp of + ["frame_count", n] -> cb $ WebFrameCount (read n) + ["frame", nth, path] -> cb $ WebFrame (read nth) path + ["stop"] -> killThread parent + [] -> return () + _ -> error $ "Bad message: " ++ inp + close conn + resolve = do + let hints = defaultHints { + addrFlags = [AI_PASSIVE] + , addrSocketType = Stream + } + head <$> getAddrInfo (Just hints) (Just "127.0.0.1") (Just "9162") + oSocket addr = socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr) + open addr = E.bracketOnError (oSocket addr) close $ \sock -> do + setSocketOption sock ReuseAddr 1 + withFdSocket sock setCloseOnExecIfNeeded + bind sock $ addrAddress addr + listen sock 1024 + return sock openViewer :: IO () openViewer = do url <- getDataFileName "viewer-elm/dist/index.html" - logMsg "Opening browser..." bSucc <- openBrowser url - if bSucc - then logMsg "Browser opened." - else hPutStrLn stderr $ "Failed to open browser. Manually visit: " ++ url - -slaveHandler :: Bool -> Maybe FilePath -> [String] -> Connection -> GhciBackend - -> FilePath -> FilePath -> IO () -slaveHandler verbose mbGHCPath extraGHCOpts conn ghci self svgDir = - withCurrentDirectory (takeDirectory self) $ - withSystemTempDirectory "reanimate" $ \tmpDir -> - withTempFile tmpDir "reanimate.exe" $ \tmpExecutable handle -> do - outputFolder <- createTempDirectory svgDir "svgs" - let frameFileName frameIdx = - outputFolder show frameIdx <.> "svg" - - sentFrameCount <- newMVar False - hClose handle - lock <- newMVar () - sendWebMessage conn $ WebStatus "Compiling" - ghciThread <- forkIO $ do - firstFrame <- newIORef True - ghciReload ghci - logMsg "GHCi reload done." - ghciGenerate ghci outputFolder $ \frameIdx -> do - first <- readIORef firstFrame - writeIORef firstFrame False - if first - then - modifyMVar_ sentFrameCount $ \sent -> do - unless sent $ - sendWebMessage conn $ WebFrameCount frameIdx - logMsg "Framecount sent." - return True - else - withMVar lock $ \_ -> - sendWebMessage conn $ WebFrame frameIdx (frameFileName frameIdx) - logMsg "GHCi render done." - ret <- case mbGHCPath of - Nothing -> do - let args = ["ghc", "--"] ++ ghcOptions tmpDir ++ extraGHCOpts ++ [takeFileName self, "-o", tmpExecutable] - when verbose $ - logMsg $ "Running: " ++ showCommandForUser "stack" args - runCmd_ "stack" args - Just ghc -> do - let args = ghcOptions tmpDir ++ extraGHCOpts ++ [takeFileName self, "-o", tmpExecutable] - when verbose $ - logMsg $ "Running: " ++ showCommandForUser ghc args - runCmd_ ghc args - logMsg "Compile done." - case ret of - Left err -> - sendWebMessage conn $ WebError $ unlines (lines err) - Right{} -> runCmdLazy tmpExecutable (execOpts outputFolder) $ \getFrame -> do - frameCount <- expectFrame =<< getFrame - modifyMVar_ sentFrameCount $ \sent -> do - unless sent $ - sendWebMessage conn $ WebFrameCount frameCount - return True - replicateM_ frameCount $ do - frameIdx <- expectFrame =<< getFrame - withMVar lock $ \_ -> - sendWebMessage conn $ WebFrame frameIdx (frameFileName frameIdx) - logMsg "Optimized render done." - killThread ghciThread - where - execOpts output = - [ "raw", "--output", output, "--offset", "1" - , "+RTS", "-N", "-M2G", "-RTS"] - expectFrame :: Either String Text -> IO Int - expectFrame (Left "") = do - sendWebMessage conn $ WebStatus "Done" - exitSuccess - expectFrame (Left err) = do - sendWebMessage conn $ WebError err - exitWith (ExitFailure 1) - expectFrame (Right frame) = - case T.decimal frame of - Left err -> do - hPutStrLn stderr (T.unpack frame) - raiseError conn err - Right (frameNumber, "") -> - pure frameNumber - Right {} -> do - let err = "Unexpected output" - hPutStrLn stderr (T.unpack frame) - raiseError conn err - -raiseError :: Connection -> String -> IO a -raiseError conn err = do - hPutStrLn stderr $ "expectFrame: " ++ err - sendWebMessage conn $ WebError err - exitWith (ExitFailure 1) - -watchFile :: WatchManager -> FilePath -> IO () -> IO StopListening -watchFile watch file action = watchTree watch (takeDirectory file) check (const action) - where - check event = - takeFileName (eventPath event) == takeFileName file || - takeExtension (eventPath event) `elem` sourceExtensions || - takeExtension (eventPath event) `elem` dataExtensions - sourceExtensions = [".hs", ".lhs"] - dataExtensions = [".jpg", ".png", ".bmp", ".pov", ".tex", ".csv"] - -ghcOptions :: FilePath -> [String] -ghcOptions tmpDir = - ["-rtsopts", "--make", "-threaded", "-O2"] ++ - ["-odir", tmpDir, "-hidir", tmpDir] - --- FIXME: Move to a different module -requireOwnSource :: IO FilePath -requireOwnSource = do - mbSelf <- findOwnSource - case mbSelf of - Nothing -> do - hPutStrLn stderr - "Rendering in browser window is only available when interpreting.\n\ - \To render a video file, use the 'render' command or run again with --help\n\ - \to see all available options." - exitFailure - Just self -> pure self - -findOwnSource :: IO (Maybe FilePath) -findOwnSource = do - fullArgs <- getFullArgs - stackSource <- makeAbsolute (last fullArgs) - exist <- doesFileExist stackSource - if exist && isHaskellFile stackSource - then return (Just stackSource) - else do - prog <- getProgName - let hsProg - | isHaskellFile prog = prog - | otherwise = replaceExtension prog "hs" - lst <- listDirectory "." - findFile ("." : lst) hsProg - -isHaskellFile :: FilePath -> Bool -isHaskellFile path = takeExtension path `elem` [".hs", ".lhs"] - -logMsg :: String -> IO () -logMsg msg = do - now <- getCurrentTime - putStrLn $ formatTime defaultTimeLocale fmt now ++ ": " ++ msg - where - fmt = "%F %T%2Q" - -------------------------------------------------------------------------------- --- Ghci interface - --- stack --- cabal --- raw --- none? -newtype GhciBackend = GhciBackend (MVar Ghci) - -ghciBackend :: Maybe FilePath -> FilePath -> IO GhciBackend -ghciBackend mbGHCPath self = do - let ghciProc = - case mbGHCPath of - Just ghcPath -> - proc ghcPath $ ["--interactive", "+RTS"] ++ words memoryLimit ++ ["-RTS"] - Nothing -> - proc "stack" ["exec", "ghci", "--rts-options="++memoryLimit] - (ghci, _loads) <- startGhciProcess ghciProc $ \_stream _msg -> return () - void $ exec ghci $ ":load " ++ self - ref <- newMVar ghci - return $ GhciBackend ref - -ghciReload :: GhciBackend -> IO () -ghciReload (GhciBackend ref) = - withMVar ref $ \ghci -> - void $ reload ghci - -ghciGenerate :: GhciBackend -> FilePath -> (Int -> IO ()) -> IO () -ghciGenerate (GhciBackend ref) target cb = withMVar ref $ \ghci -> - execStream ghci (":main raw --output=" ++ target ++ " --offset=1") - $ \_ msg -> - case reads msg of - [(frameIdx,"")] -> cb frameIdx - _ -> return () - -memoryLimit :: String -memoryLimit = "-M1G" + unless bSucc $ do + hPutStrLn stderr "Failed to open browser." + exitFailure ------------------------------------------------------------------------------- -- Websocket API diff --git a/src/Reanimate/External.hs b/src/Reanimate/External.hs index ac70493..9475748 100644 --- a/src/Reanimate/External.hs +++ b/src/Reanimate/External.hs @@ -3,19 +3,37 @@ module Reanimate.External SHA256, zipArchive, tarball, + + -- * External Icon Datasets + simpleIcon, + simpleIconColor, + simpleIcons, + svgLogo, + svgLogos, ) where -import Control.Monad (unless) -import Crypto.Hash.SHA256 (hash) -import qualified Data.ByteString as B (readFile) -import Data.ByteString.Base64 (encode) -import qualified Data.ByteString.Char8 as B8 (unpack) -import Reanimate.Misc (getReanimateCacheDirectory, withTempFile) -import System.Directory (doesDirectoryExist, findExecutable) -import System.FilePath (()) -import System.IO.Unsafe (unsafePerformIO) -import System.Process (callProcess) +import Codec.Picture (PixelRGB8 (..)) +import Control.Monad (unless) +import Crypto.Hash.SHA256 (hash) +import Data.Aeson (decodeFileStrict) +import qualified Data.ByteString as B (readFile) +import Data.ByteString.Base64 (encode) +import qualified Data.ByteString.Char8 as B8 (unpack) +import Data.Char (isSpace, toLower) +import Data.List (sort) +import Data.Map (Map) +import qualified Data.Map as M +import Numeric (readHex) +import Reanimate.Animation (SVG) +import Reanimate.Constants (screenHeight, screenWidth) +import Reanimate.Misc (getReanimateCacheDirectory, withTempFile) +import Reanimate.Raster (mkImage) +import System.Directory (doesDirectoryExist, doesFileExist, findExecutable, + getDirectoryContents) +import System.FilePath (splitExtension, (<.>), ()) +import System.IO.Unsafe (unsafePerformIO) +import System.Process (callProcess) -- | Resource address type URL = String @@ -59,15 +77,15 @@ zipArchive url sha256 = unsafePerformIO $ tarball :: URL -> SHA256 -> FilePath tarball url sha256 = unsafePerformIO $ fetchStaticFile url sha256 $ \folder tarfile -> - callProcess "tar" ["--overwrite", "--one-top-level=" ++ folder, "-xf", tarfile] + callProcess "tar" ["--overwrite", "--one-top-level=" ++ folder, "-xzf", tarfile] downloadFile :: URL -> (FilePath -> IO a) -> IO a downloadFile url action = do mbCurl <- findExecutable "curl" mbWget <- findExecutable "wget" case (mbCurl, mbWget) of - (Just curl, _) -> downloadFileCurl curl url action - (_, Just wget) -> downloadFileWget wget url action + (Just curl, _) -> downloadFileCurl curl url action + (_, Just wget) -> downloadFileWget wget url action (Nothing, Nothing) -> error "curl/wget required to download files" downloadFileCurl :: FilePath -> URL -> (FilePath -> IO a) -> IO a @@ -75,12 +93,15 @@ downloadFileCurl curl url action = withTempFile "dl" $ \path -> do callProcess curl [ url, + "--location", "--output", path, "--silent", "--show-error", "--max-filesize", - "10M" + "10M", + "--max-time", + "60" ] action path @@ -93,3 +114,129 @@ downloadFileWget wget url action = withTempFile "dl" $ \path -> do "--quiet" ] action path + + + +------------------------------------------------------------------------------- +-- SimpleIcons + +simpleIconsFolder :: FilePath +simpleIconsFolder = + tarball + "https://github.com/simple-icons/simple-icons/archive/3.11.0.tar.gz" + "NXa8TrHHuQofrPbqTf0pBGt1GDRfuQ4IcQ7kNEk9OcQ=" + "simple-icons-3.11.0" + +{-# NOINLINE simpleIconPath #-} +simpleIconPath :: String -> FilePath +simpleIconPath key = unsafePerformIO $ do + let path = simpleIconsFolder "icons" key <.> "svg" + hit <- doesFileExist path + if hit + then pure path + else error $ "Key not found in simple-icons dataset: " ++ show key + +-- | Icons from . Version 3.11.0. License: CC0 +-- +-- @ +-- let icon = "cplusplus" in `Reanimate.mkGroup` +-- [ `Reanimate.mkBackgroundPixel` (`Codec.Picture.Types.promotePixel` $ `simpleIconColor` icon) +-- , `Reanimate.withFillOpacity` 1 $ `simpleIcon` icon ] +-- @ +-- +-- <> +simpleIcon :: String -> SVG +simpleIcon = mkImage screenWidth screenHeight . simpleIconPath + +-- | Simple Icons svgs do not contain color. Instead, each icon has an associated color value. +simpleIconColor :: String -> PixelRGB8 +simpleIconColor key = + case M.lookup key simpleIconColors of + Nothing -> error $ "Key not found in simple-icons dataset: " ++ show key + Just pixel -> pixel + +simpleIconColors :: Map String PixelRGB8 +simpleIconColors = unsafePerformIO $ do + let path = simpleIconsFolder "_data" "simple-icons.json" + mbRet <- decodeFileStrict path + let parsed = do + m <- mbRet + icons <- M.lookup "icons" m + pure $ + M.fromList + [ (fromTitle title, parseHex hex) | icon <- icons, Just title <- [M.lookup "title" icon], Just hex <- [M.lookup "hex" icon] + ] + case parsed of + Nothing -> error "Invalid json in simpleIcons" + Just v -> pure v + where + fromTitle :: String -> String + fromTitle = replaceChars . map toLower + + replaceChars :: String -> String + replaceChars ('.' : x : xs) = "dot-" ++ replaceChars (x : xs) + replaceChars "." = "dot" + replaceChars (x : '.' : []) = replaceChars (x : "-dot") + replaceChars (x : '.' : xs) = replaceChars (x : "-dot-" ++ xs) + replaceChars (x : xs) + | isSpace x || x `elem` "!:'’" = replaceChars xs + replaceChars ('&' : xs) = "-and-" ++ replaceChars xs + replaceChars ('+' : xs) = "plus" ++ replaceChars xs + replaceChars (x : xs) + | x `elem` "àáâãä" = 'a' : replaceChars xs + | x `elem` "ìíîï" = 'i' : replaceChars xs + | x `elem` "èéêë" = 'e' : replaceChars xs + | x `elem` "šś" = 's' : replaceChars xs + replaceChars (x : xs) = x : replaceChars xs + replaceChars [] = [] + parseHex :: String -> PixelRGB8 + parseHex hex = PixelRGB8 (p 0) (p 2) (p 4) + where + p offset = case readHex (take 2 $ drop offset hex) of + [(num, "")] -> num + _ -> error $ "Invalid hex: " ++ (take 2 $ drop offset hex) + +{-# NOINLINE simpleIcons #-} +-- | Complete list of all Simple Icons. +simpleIcons :: [String] +simpleIcons = unsafePerformIO $ do + let folder = simpleIconsFolder "icons" + files <- getDirectoryContents folder + return $ + sort + [key | file <- files, let (key, ext) = splitExtension file, ext == ".svg"] + + +svgLogosFolder :: FilePath +svgLogosFolder = tarball + "https://github.com/gilbarbara/logos/archive/2018.01.tar.gz" + "kRRA0cF6sVOyqtfVW8EMew4OB4WJcY81DEGS3FLEY8Y=" + "logos-2018.01" "logos" + +{-# NOINLINE svgLogoPath #-} +svgLogoPath :: String -> FilePath +svgLogoPath key = unsafePerformIO $ do + let path = svgLogosFolder key <.> "svg" + hit <- doesFileExist path + if hit + then pure path + else error $ "Key not found in svg logos dataset: " ++ show key + +-- | Icons from . Version 2018.01. License: CC0 +-- +-- @ +-- `svgLogo` "cassandra" +-- @ +-- +-- <> +svgLogo :: String -> SVG +svgLogo = mkImage screenWidth screenHeight . svgLogoPath + +{-# NOINLINE svgLogos #-} +-- | Complete list of all SVG Icons. +svgLogos :: [String] +svgLogos = unsafePerformIO $ do + files <- getDirectoryContents svgLogosFolder + return $ + sort + [key | file <- files, let (key, ext) = splitExtension file, ext == ".svg"] diff --git a/src/Reanimate/LaTeX.hs b/src/Reanimate/LaTeX.hs index 63b00d3..bdc158b 100644 --- a/src/Reanimate/LaTeX.hs +++ b/src/Reanimate/LaTeX.hs @@ -1,9 +1,8 @@ -{-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ScopedTypeVariables #-} - -- | -- Copyright : Written by David Himmelstrup -- License : Unlicense @@ -17,6 +16,9 @@ module Reanimate.LaTeX latex, latexWithHeaders, latexChunks, + latexCfgChunks, + latexCfgChunksTrans, + mathChunks, xelatex, xelatexWithHeaders, ctex, @@ -29,39 +31,32 @@ module Reanimate.LaTeX noto, helvet, libertine, + biolinum, + droidSerif, + droidSans, ) where -import Control.Lens -import qualified Data.ByteString as B -import Data.Hashable -import Data.Monoid -import Data.Text (Text) -import qualified Data.Text as T -import qualified Data.Text.Encoding as T -import GHC.Generics (Generic) -import Graphics.SvgTree - ( Tree, - clipPathRef, - clipRule, - mapTree, - parseSvgFile, - strokeColor, - pattern ClipPathTree, - pattern None, - ) -import Reanimate.Animation (SVG) -import Reanimate.Cache -import Reanimate.External -import Reanimate.Misc -import Reanimate.Parameters -import Reanimate.Svg -import System.FilePath - ( replaceExtension, - takeFileName, - (), - ) -import System.IO.Unsafe (unsafePerformIO) +import Control.Lens ((&), (.~)) +import Control.Monad.State (runState, state) +import qualified Data.ByteString as B +import Data.Foldable (Foldable (fold)) +import Data.Hashable (Hashable) +import Data.Text (Text) +import qualified Data.Text as T +import qualified Data.Text.Encoding as T +import qualified Data.Text.IO as T +import GHC.Generics (Generic) +import Graphics.SvgTree (Tree, clipPathRef, clipRule, mapTree, parseSvgFile, + pattern ClipPathTree, pattern None, strokeColor) +import Reanimate.Animation (SVG) +import Reanimate.Cache (cacheDiskSvg, cacheMem) +import Reanimate.External (zipArchive) +import Reanimate.Misc (requireExecutable, runCmd, withTempDir, withTempFile) +import Reanimate.Parameters (pNoExternals) +import Reanimate.Svg +import System.FilePath (replaceExtension, takeFileName, ()) +import System.IO.Unsafe (unsafePerformIO) -- | TeX backends. They have different features and capabilities. data TexEngine = LaTeX | XeLaTeX | LuaLaTeX @@ -69,21 +64,24 @@ data TexEngine = LaTeX | XeLaTeX | LuaLaTeX -- | TeX configurations can load packages and set up environments for tex scripts. data TexConfig = TexConfig - { texConfigEngine :: TexEngine, - texConfigHeaders :: [T.Text], + { texConfigEngine :: TexEngine, + texConfigHeaders :: [T.Text], texConfigPostScript :: [T.Text] } deriving (Generic, Hashable, Read, Show, Eq, Ord) +defaultTexConfig :: TexConfig +defaultTexConfig = TexConfig LaTeX [] [] + -- | Render TeX script using a given configuration. latexCfg :: TexConfig -> T.Text -> SVG latexCfg (TexConfig engine headers postscript) = - gen headers postscript + gen postscript headers where gen = case engine of - LaTeX -> someTexWithHeaders engine "latex" "dvi" [] - XeLaTeX -> someTexWithHeaders engine "xelatex" "xdv" ["-no-pdf"] + LaTeX -> someTexWithHeaders engine "latex" "dvi" [] + XeLaTeX -> someTexWithHeaders engine "xelatex" "xdv" ["-no-pdf"] LuaLaTeX -> someTexWithHeaders engine "lualatex" "pdf" [] -- | Invoke latex and import the result as an SVG object. SVG objects are @@ -110,25 +108,42 @@ someTexWithHeaders :: [T.Text] -> T.Text -> Tree -someTexWithHeaders _engine _exec _dvi _args _headers _postscript tex +someTexWithHeaders _engine _exec _dvi _args _postscript _headers tex | pNoExternals = mkText tex -someTexWithHeaders engine exec dvi args headers postscript tex = +someTexWithHeaders engine exec dvi args postscript headers tex = (unsafePerformIO . (cacheMem . cacheDiskSvg) (latexToSVG engine dvi exec args)) script where script = mkTexScript exec args headers (T.unlines (postscript ++ [tex])) --- | Invoke latex and separate results. -latexChunks :: [T.Text] -> [Tree] -latexChunks chunks | pNoExternals = map mkText chunks -latexChunks chunks = worker (svgGlyphs $ latex $ T.concat chunks) chunks +-- | Invoke latex using a given configuration and separate results. +-- Apply the transformation to the LaTeX segments. +-- See also 'mathChunks', the transformation is @(\s -> "$" <> s <> "$")@. +latexCfgChunksTrans :: Traversable t => TexConfig -> (T.Text -> T.Text) -> t T.Text -> t Tree +latexCfgChunksTrans _cfg f chunks | pNoExternals = fmap (mkText . f) chunks +latexCfgChunksTrans cfg f chunks = worker $ svgGlyphs $ tex $ f $ fold chunks where + tex = latexCfg cfg merge lst = mkGroup [fmt svg | (fmt, _, svg) <- lst] - worker [] [] = [] - worker _ [] = error "latex chunk mismatch" - worker everything (x : xs) = - let width = length $ svgGlyphs (latex x) - in merge (take width everything) : worker (drop width everything) xs + checkResult (r, []) = r + checkResult (_, _) = error "latex chunk mismatch" + worker = checkResult . runState (mapM (state . workerSingle) (fmap f chunks)) + workerSingle x everything = + let width = length $ svgGlyphs (tex x) + (first, rest) = splitAt width everything + in (merge first, rest) + +-- | Render math formula and separate results. +mathChunks :: Traversable t => t T.Text -> t Tree +mathChunks = latexCfgChunksTrans defaultTexConfig (\s -> "$" <> s <> "$") + +-- | Invoke latex using a given configuration and separate results. +latexCfgChunks :: Traversable t => TexConfig -> t T.Text -> t Tree +latexCfgChunks cfg = latexCfgChunksTrans cfg id + +-- | Invoke latex and separate results. +latexChunks :: Traversable t => t T.Text -> t Tree +latexChunks = latexCfgChunksTrans defaultTexConfig id -- | Invoke xelatex and import the result as an SVG object. SVG objects are -- cached to improve performance. Xelatex has support for non-western scripts. @@ -137,7 +152,7 @@ xelatex = xelatexWithHeaders [] -- | Invoke xelatex with extra script headers. xelatexWithHeaders :: [T.Text] -> T.Text -> Tree -xelatexWithHeaders = someTexWithHeaders XeLaTeX "xelatex" "xdv" [] ["-no-pdf"] +xelatexWithHeaders = someTexWithHeaders XeLaTeX "xelatex" "xdv" ["-no-pdf"] [] -- | Invoke xelatex with "\usepackage[UTF8]{ctex}" and import the result as an -- SVG object. SVG objects are cached to improve performance. Xelatex has @@ -176,17 +191,17 @@ postprocess = . lowerIds . mapTree clearDrawAttr where - clearDrawAttr t = t & strokeColor .~ Last Nothing + clearDrawAttr t = t & strokeColor .~ Nothing enginePostprocess :: TexEngine -> Tree -> Tree enginePostprocess LuaLaTeX svg = translate 0 (svgHeight svg) svg -enginePostprocess _ svg = svg +enginePostprocess _ svg = svg removeClipPaths :: SVG -> SVG removeClipPaths = mapTree worker where worker ClipPathTree {} = None - worker t = t & clipRule .~ Last Nothing & clipPathRef .~ Last Nothing + worker t = t & clipRule .~ Nothing & clipPathRef .~ Nothing -- executable, arguments, header, tex latexToSVG :: TexEngine -> String -> String -> [String] -> Text -> IO Tree @@ -224,7 +239,7 @@ latexToSVG engine dviExt latexExec latexArgs tex = do "-o", svg_file ] - svg_data <- B.readFile svg_file + svg_data <- T.readFile svg_file case parseSvgFile svg_file svg_data of Nothing -> error "Malformed svg" Just svg -> @@ -267,11 +282,9 @@ mkTexScript latexExec latexArgs texHeaders tex = \\\usepackage{microtype}\n\ -} --- | Chalkduster configuration. Depends on lualatex. +-- | Chalkduster font. Depends on lualatex. -- Font files are automatically downloaded. -- --- Example: --- -- @ -- `latexCfg` `chalkduster` "chalkduster" -- @ @@ -295,10 +308,7 @@ chalkduster = "https://www.ffonts.net/Chalkduster.font.zip" "Wplv4RjuFiI0hDQnAM5MVHl2evrZqWstRLdVAfBomCM=" --- | Calligra configuration. --- --- Example: --- +-- | -- @ -- `latexCfg` `calligra` "calligra" -- @ @@ -312,10 +322,7 @@ calligra = texConfigPostScript = ["\\calligra"] } --- | Noto configuration. --- --- Example: --- +-- | -- @ -- `latexCfg` `noto` "noto" -- @ @@ -329,10 +336,7 @@ noto = texConfigPostScript = [] } --- | Helvetica configuration. --- --- Example: --- +-- | -- @ -- `latexCfg` `helvet` "helvet" -- @ @@ -346,10 +350,7 @@ helvet = texConfigPostScript = [] } --- | Libertine configuration. --- --- Example: --- +-- | -- @ -- `latexCfg` `libertine` "libertine" -- @ @@ -362,3 +363,51 @@ libertine = texConfigHeaders = ["\\usepackage{libertine}"], texConfigPostScript = [] } + +-- | +-- @ +-- `latexCfg` `biolinum` "biolinum" +-- @ +-- +-- <> +biolinum :: TexConfig +biolinum = + TexConfig + { texConfigEngine = LaTeX, + texConfigHeaders = + ["\\usepackage{libertine}" + ,"\\renewcommand{\\familydefault}{\\sfdefault}"], + texConfigPostScript = [] + } + +-- | +-- @ +-- `latexCfg` `droidSerif` "droidSerif" +-- @ +-- +-- <> +droidSerif :: TexConfig +droidSerif = + TexConfig + { texConfigEngine = LaTeX, + texConfigHeaders = + ["\\usepackage[default]{droidserif}" + ,"\\let\\varepsilon\\epsilon"], + texConfigPostScript = [] + } + +-- | +-- @ +-- `latexCfg` `droidSans` "droidSans" +-- @ +-- +-- <> +droidSans :: TexConfig +droidSans = + TexConfig + { texConfigEngine = LaTeX, + texConfigHeaders = + ["\\usepackage[default]{droidsans}" + ,"\\let\\varepsilon\\epsilon"], + texConfigPostScript = [] + } diff --git a/src/Reanimate/Math/Polygon.hs b/src/Reanimate/Math/Polygon.hs index 2a62276..2b79c54 100644 --- a/src/Reanimate/Math/Polygon.hs +++ b/src/Reanimate/Math/Polygon.hs @@ -115,7 +115,7 @@ data APolygon a = Polygon , polygonOffset :: Int , polygonTriangulation :: Triangulation , polygonSSSP :: Vector SSSP - } + } deriving Eq type Polygon = APolygon Rational type P = V2 Double diff --git a/src/Reanimate/Math/Triangulate.hs b/src/Reanimate/Math/Triangulate.hs index 3cdc57d..27d9ced 100644 --- a/src/Reanimate/Math/Triangulate.hs +++ b/src/Reanimate/Math/Triangulate.hs @@ -1,5 +1,6 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE CPP #-} {-# OPTIONS_HADDOCK hide #-} module Reanimate.Math.Triangulate ( Triangulation @@ -11,6 +12,32 @@ module Reanimate.Math.Triangulate ) where +#if NO_HGEOMETRY + +import qualified Data.Vector as V +import Control.Monad.ST +import Reanimate.Math.Common + +type Triangulation = V.Vector [Int] + +edgesToTriangulation :: Int -> [(Int, Int)] -> Triangulation +edgesToTriangulation = error "no hgeometry" + +edgesToTriangulationM :: Int -> [(Int, Int)] -> ST s (V.MVector s [Int]) +edgesToTriangulationM = error "no hgeometry" + +trianglesToTriangulation :: Int -> V.Vector (Int, Int, Int) -> Triangulation +trianglesToTriangulation = error "no hgeometry" + +trianglesToTriangulationM + :: Int -> V.Vector (Int, Int, Int) -> ST s (V.MVector s [Int]) +trianglesToTriangulationM = error "no hgeometry" + +triangulate :: forall a. (Fractional a, Ord a) => Ring a -> Triangulation +triangulate = error "no hgeometry" + +#else + import Algorithms.Geometry.PolygonTriangulation.Triangulate (triangulate') import Algorithms.Geometry.PolygonTriangulation.Types import Control.Lens @@ -19,7 +46,7 @@ import Control.Monad.ST import Data.Ext import Data.Geometry.PlanarSubdivision (PolygonFaceData) import Data.Geometry.Point -import Data.Geometry.Polygon +import Data.Geometry.Polygon (SimplePolygon, fromPoints) import qualified Data.IntSet as ISet import qualified Data.PlaneGraph as Geo import Data.Proxy @@ -83,3 +110,5 @@ triangulate r = edgesToTriangulation (ringSize r) ds [ Point2 x y :+ n | (n,V2 x y) <- zip [0..] (V.toList (ringUnpack r)) ] -- ringUnpack + +#endif diff --git a/src/Reanimate/Memo.hs b/src/Reanimate/Memo.hs index 63ed926..92d2d22 100644 --- a/src/Reanimate/Memo.hs +++ b/src/Reanimate/Memo.hs @@ -23,13 +23,13 @@ module Reanimate.Memo , memo ) where -import Data.Dynamic -import Data.IORef -import qualified Data.Map as Map -import Data.Maybe -import Data.Typeable -import System.IO.Unsafe -import System.Mem.StableName +import Data.Dynamic (Dynamic, fromDynamic, toDyn) +import Data.IORef (IORef, atomicModifyIORef', newIORef) +import qualified Data.Map as Map +import Data.Maybe (fromMaybe) +import Data.Typeable (Typeable, cast, typeOf) +import System.IO.Unsafe (unsafePerformIO) +import System.Mem.StableName (StableName, eqStableName, hashStableName, makeStableName) data DynamicName = forall a. DynamicName !(StableName a) | forall a. (Eq a, Ord a, Typeable a) => DynamicKey a instance Eq DynamicName where diff --git a/src/Reanimate/Misc.hs b/src/Reanimate/Misc.hs index f802969..c8f094f 100644 --- a/src/Reanimate/Misc.hs +++ b/src/Reanimate/Misc.hs @@ -7,49 +7,29 @@ module Reanimate.Misc withTempFile, renameOrCopyFile, getReanimateCacheDirectory, + fileUri ) where -import Control.Concurrent -import Control.Exception (catch, evaluate, finally, throw) -import qualified Data.Text as T -import qualified Data.Text.IO as T -import Foreign.C.Error -import GHC.IO.Exception -import System.Directory - ( XdgDirectory (XdgCache), - copyFile, - createDirectoryIfMissing, - findExecutable, - getXdgDirectory, - removeFile, - renameFile, - ) -import System.FilePath ((<.>), ()) -import System.IO - ( hClose, - hGetContents, - hIsEOF, - hPutStr, - stderr, - ) -import System.IO.Temp - ( withSystemTempDirectory, - withSystemTempFile, - ) -import System.Process - ( readProcessWithExitCode, - runInteractiveProcess, - showCommandForUser, - terminateProcess, - waitForProcess, - ) +import Control.Concurrent (forkIO) +import Control.Exception (catch, evaluate, finally, throw) +import qualified Data.Text as T +import qualified Data.Text.IO as T +import Foreign.C.Error (Errno (Errno), eXDEV) +import GHC.IO.Exception (ExitCode (ExitFailure, ExitSuccess), IOException (ioe_errno)) +import System.Directory (XdgDirectory (XdgCache), copyFile, createDirectoryIfMissing, + findExecutable, getXdgDirectory, removeFile, renameFile) +import System.FilePath ((<.>), ()) +import System.IO (hClose, hGetContents, hIsEOF, hPutStr, stderr) +import System.IO.Temp (withSystemTempDirectory, withSystemTempFile) +import System.Process (readProcessWithExitCode, runInteractiveProcess, + showCommandForUser, terminateProcess, waitForProcess) requireExecutable :: String -> IO FilePath requireExecutable exec = do mbPath <- findExecutable exec case mbPath of - Nothing -> error $ "Couldn't find executable: " ++ exec + Nothing -> error $ "Couldn't find executable: " ++ exec Just path -> return path runCmd :: FilePath -> [String] -> IO () @@ -65,18 +45,18 @@ runCmd_ exec args = do _ <- evaluate (length stdout + length errMsg) case ret of ExitSuccess -> return (Right stdout) - ExitFailure err - | False -> - return $ - Left $ - "Failed to run: " - ++ showCommandForUser exec args - ++ "\n" - ++ "Error code: " - ++ show err - ++ "\n" - ++ "stderr: " - ++ errMsg + -- ExitFailure err + -- | False -> + -- return $ + -- Left $ + -- "Failed to run: " + -- ++ showCommandForUser exec args + -- ++ "\n" + -- ++ "Error code: " + -- ++ show err + -- ++ "\n" + -- ++ "stderr: " + -- ++ errMsg ExitFailure {} | null errMsg -> -- LaTeX prints errors to stdout. :( return $ Left stdout @@ -96,7 +76,7 @@ runCmdLazy exec args handler = do _ <- evaluate (length errOutput) ret <- waitForProcess pid case ret of - ExitSuccess -> return (Left "") + ExitSuccess -> return (Left "") ExitFailure {} -> return (Left errOutput) else {-ExitFailure errMsg -> do return $ Left $ @@ -139,3 +119,12 @@ getReanimateCacheDirectory = do -- Incrementing this value invalidates all cached results. cacheVersion :: Int cacheVersion = 0 + +-- | A valid file URI is file:///. If is absent, it +-- is file:///. On Windows, absolute paths begin (for example) "C:\". +fileUri :: FilePath -> String +fileUri path = "file://" <> path' + where + path' = case path of + '/' : _ -> path + _ -> '/' : path diff --git a/src/Reanimate/Parameters.hs b/src/Reanimate/Parameters.hs index a048ce4..62a4059 100644 --- a/src/Reanimate/Parameters.hs +++ b/src/Reanimate/Parameters.hs @@ -21,8 +21,8 @@ module Reanimate.Parameters , setRootDirectory ) where -import System.IO.Unsafe -import Data.IORef +import Data.IORef (IORef, newIORef, readIORef, writeIORef) +import System.IO.Unsafe (unsafePerformIO) -- | Width of animation in pixels. type Width = Int diff --git a/src/Reanimate/PolyShape.hs b/src/Reanimate/PolyShape.hs index 054469b..9dc7264 100644 --- a/src/Reanimate/PolyShape.hs +++ b/src/Reanimate/PolyShape.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} {-| Module : Reanimate.PolyShape Copyright : Written by David Himmelstrup @@ -40,15 +41,8 @@ module Reanimate.PolyShape , plGroupTouching ) where -import Algorithms.Geometry.PolygonTriangulation.Triangulate (triangulate') -import Control.Lens ((&), (.~), (^.)) -import Data.Ext -import Data.Geometry.PlanarSubdivision (PolygonFaceData (..)) -import qualified Data.Geometry.Point as Geo -import qualified Data.Geometry.Polygon as Geo +import Control.Lens ((&), (.~)) import Data.List (nub, partition, sortOn) -import qualified Data.PlaneGraph as Geo -import Data.Proxy import qualified Data.Vector as V import Geom2D.CubicBezier.Linear (ClosedPath (..), CubicBezier (..), @@ -72,6 +66,18 @@ import Reanimate.Math.Polygon (Polygon, pIsCCW) import Reanimate.Svg +#if !defined(NO_HGEOMETRY) +import Control.Lens ((^.)) +import Algorithms.Geometry.PolygonTriangulation.Triangulate (triangulate') + +import Data.Ext +import Data.Geometry.PlanarSubdivision (PolygonFaceData (..)) +import qualified Data.Geometry.Point as Geo +import qualified Data.Geometry.Polygon as Geo +import qualified Data.PlaneGraph as Geo +import Data.Proxy (Proxy (Proxy)) +#endif + -- | Shape drawn by continuous line. May have overlap, may be convex. newtype PolyShape = PolyShape { unPolyShape :: ClosedPath Double } deriving (Show) @@ -201,6 +207,9 @@ plDecompose' tol = -- | Split polygon into smaller, convex polygons. decomposePolygon :: [RPoint] -> [[RPoint]] +#if defined(NO_HGEOMETRY) +decomposePolygon = error "no hgeometry" +#else decomposePolygon poly = [ [ V2 x y | v <- V.toList (Geo.boundaryVertices f pg) @@ -212,6 +221,7 @@ decomposePolygon poly = p = Geo.fromPoints $ [ Geo.Point2 x y :+ () | V2 x y <- poly ] +#endif plPolygonify :: Double -> PolyShape -> [RPoint] plPolygonify tol shape = diff --git a/src/Reanimate/Povray.hs b/src/Reanimate/Povray.hs index 6878c56..954d42b 100644 --- a/src/Reanimate/Povray.hs +++ b/src/Reanimate/Povray.hs @@ -1,7 +1,25 @@ {-# LANGUAGE OverloadedStrings #-} {-| - [Povray](http://povray.org/) is a scriptable raytracer. All povray functions - are cached and will reuse images when scripts stay the same. + [POV-Ray](http://povray.org/) (the Persistance of Vision Raytracer) is a + scriptable raytracer. All POV-Ray functions are cached and will reuse images + when scripts stay the same. + + The functions come in two versions, for example 'povray' and 'povray''. The + versions without an apostrophe character yield a 'Tree'. The versions with a + final apostrophe character yield a 'FilePath' of a PNG file containing the + resulting image. + + The functions differ in their use of the POV-Ray @+W@ (width), @+H@ (height) + and @+A@ or @-A@ (anti-aliasing) switches, as set out below. Resolutions are + \'width x height\'. + + Example of use: + + > povray args script + + where @args@ is a list of other POV-Ray command-line switches (if any), and + @script@ is a POV-Ray scene description specified in the POV-Ray /scene/ + /description language/. -} module Reanimate.Povray ( povray @@ -14,20 +32,22 @@ module Reanimate.Povray , povrayExtreme' ) where -import Data.Hashable +import Data.Hashable (Hashable (hash)) import Data.Text (Text) -import qualified Data.Text as T -import qualified Data.Text.IO as T +import qualified Data.Text as T (concat, pack) +import qualified Data.Text.IO as T (writeFile) import Graphics.SvgTree (Tree) -import Reanimate.Cache -import Reanimate.Constants -import Reanimate.Misc -import Reanimate.Parameters -import Reanimate.Raster -import Reanimate.Svg.Constructors +import POVRay (runPOVRay) +import Reanimate.Cache (cacheFile, encodeInt) +import Reanimate.Constants (screenHeight, screenWidth) +import Reanimate.Parameters (pNoExternals) +import Reanimate.Raster (mkImage) +import Reanimate.Svg.Constructors (mkText) import System.FilePath (replaceExtension, (<.>)) import System.IO.Unsafe (unsafePerformIO) + + povrayRaw :: [String] -> Text -> Tree povrayRaw args script = unsafePerformIO $ mkPovrayImage args script @@ -36,53 +56,53 @@ povrayRaw' :: [String] -> Text -> FilePath povrayRaw' args script = unsafePerformIO $ mkPovrayImage' args script --- | Run the povray raytracer with a default resolution of 320x180 +-- | Run the POV-Ray raytracer with a default resolution of 320x180 -- and antialiasing enabled. The resulting image is scaled to fit -- the screen exactly. povray :: [String] -> Text -> Tree -povray args = povrayRaw (["+H180","+W320", "+A"] ++ args) +povray args = povrayRaw (["+H180", "+W320", "+A"] ++ args) --- | Run the povray raytracer with a default resolution of 320x180 +-- | Run the POV-Ray raytracer with a default resolution of 320x180 -- and antialiasing enabled. The FilePath points to a PNG file -- containing the resulting image. povray' :: [String] -> Text -> FilePath -povray' args = povrayRaw' (["+H180","+W320", "+A"] ++ args) +povray' args = povrayRaw' (["+H180", "+W320", "+A"] ++ args) --- | Run the povray raytracer with a default resolution of 320x180 +-- | Run the POV-Ray raytracer with a default resolution of 320x180 -- but without antialiasing. The resulting image is scaled to fit -- the screen exactly. povrayQuick :: [String] -> Text -> Tree -povrayQuick args = povrayRaw (["+H180","+W320"] ++ args) +povrayQuick args = povrayRaw (["+H180", "+W320"] ++ args) --- | Run the povray raytracer with a default resolution of 320x180 +-- | Run the POV-Ray raytracer with a default resolution of 320x180 -- but without antialiasing. The FilePath points to a PNG file -- containing the resulting image. povrayQuick' :: [String] -> Text -> FilePath -povrayQuick' args = povrayRaw' (["+H180","+W320"] ++ args) +povrayQuick' args = povrayRaw' (["+H180", "+W320"] ++ args) --- | Run the povray raytracer with a default resolution of 1440x2560 --- and antialiasing enabled. The FilePath points to a PNG file --- containing the resulting image. +-- | Run the POV-Ray raytracer with a default resolution of 2560x1440 +-- and antialiasing enabled. The resulting image is scaled to fit +-- the screen exactly. povraySlow :: [String] -> Text -> Tree -povraySlow args = povrayRaw (["+H1440","+W2560", "+A"] ++ args) +povraySlow args = povrayRaw (["+H1440", "+W2560", "+A"] ++ args) --- | Run the povray raytracer with a default resolution of 1440x2560 +-- | Run the POV-Ray raytracer with a default resolution of 2560x1440 -- and antialiasing enabled. The FilePath points to a PNG file -- containing the resulting image. povraySlow' :: [String] -> Text -> FilePath -povraySlow' args = povrayRaw' (["+H1440","+W2560", "+A"] ++ args) +povraySlow' args = povrayRaw' (["+H1440", "+W2560", "+A"] ++ args) --- | Run the povray raytracer with a default resolution of 2160x3840 --- and antialiasing enabled. The FilePath points to a PNG file --- containing the resulting image. +-- | Run the POV-Ray raytracer with a default resolution of 3840x2160 +-- and antialiasing enabled. The resulting image is scaled to fit +-- the screen exactly. povrayExtreme :: [String] -> Text -> Tree -povrayExtreme args = povrayRaw (["+H2160","+W3840", "+A"] ++ args) +povrayExtreme args = povrayRaw (["+H2160", "+W3840", "+A"] ++ args) --- | Run the povray raytracer with a default resolution of 2160x3840 +-- | Run the POV-Ray raytracer with a default resolution of 3840x2160 -- and antialiasing enabled. The FilePath points to a PNG file -- containing the resulting image. povrayExtreme' :: [String] -> Text -> FilePath -povrayExtreme' args = povrayRaw' (["+H2160","+W3840", "+A"] ++ args) +povrayExtreme' args = povrayRaw' (["+H2160", "+W3840", "+A"] ++ args) mkPovrayImage :: [String] -> Text -> IO Tree mkPovrayImage _ script | pNoExternals = pure $ mkText script @@ -92,10 +112,10 @@ mkPovrayImage args script = mkPovrayImage' :: [String] -> Text -> IO FilePath mkPovrayImage' _ _ | pNoExternals = pure "/povray/has/been/disabled" mkPovrayImage' args script = cacheFile template $ \target -> do - exec <- requireExecutable "povray" - let pov_file = replaceExtension target "pov" - T.writeFile pov_file script - runCmd exec (args ++ ["-D","+UA", pov_file, "+o"++target]) - where - template = encodeInt (hash key) <.> "png" - key = T.concat (script:map T.pack args) + let pov_file = replaceExtension target "pov" + otherArgs = ["-D", "+UA", "+I" ++ pov_file, "+O" ++ target] + T.writeFile pov_file script + runPOVRay $ args ++ otherArgs + where + template = encodeInt (hash key) <.> "png" + key = T.concat (script:map T.pack args) diff --git a/src/Reanimate/Raster.hs b/src/Reanimate/Raster.hs index e43b777..465364b 100644 --- a/src/Reanimate/Raster.hs +++ b/src/Reanimate/Raster.hs @@ -26,35 +26,36 @@ module Reanimate.Raster ) where -import Codec.Picture -import Control.Lens ( (&) - , (.~) - ) -import Control.Monad -import qualified Data.ByteString as B -import qualified Data.ByteString.Base64.Lazy as Base64 -import qualified Data.ByteString.Lazy.Char8 as LBS -import Data.Hashable -import qualified Data.Text as T -import Graphics.SvgTree ( Number(..) - , defaultSvg - , parseSvgFile - ) -import qualified Graphics.SvgTree as Svg -import Reanimate.Animation -import Reanimate.Cache -import Reanimate.Driver.Magick -import Reanimate.Misc -import Reanimate.Render -import Reanimate.Parameters -import Reanimate.Constants -import Reanimate.Svg.Constructors -import Reanimate.Svg.Unuse -import System.Directory -import System.FilePath -import System.IO -import System.IO.Temp -import System.IO.Unsafe +import Codec.Picture (DynamicImage, Image (imageHeight, imageWidth), + PngSavable (encodePng), decodePng, dynamicMap, + encodeDynamicPng, writePng) +import Control.Lens ((&), (.~)) +import Control.Monad (unless) +import qualified Data.ByteString as B +import qualified Data.ByteString.Base64.Lazy as Base64 +import qualified Data.ByteString.Lazy.Char8 as LBS +import Data.Hashable (Hashable (hash)) +import qualified Data.Text as T +import qualified Data.Text.IO as T +import Graphics.SvgTree (Number (..), defaultSvg, parseSvgFile) +import qualified Graphics.SvgTree as Svg +import Reanimate.Animation (SVG, renderSvg) +import Reanimate.Cache (cacheFile, encodeInt) +import Reanimate.Constants (screenHeight, screenWidth) +import Reanimate.Driver.Magick (magickCmd) +import Reanimate.Misc (fileUri, getReanimateCacheDirectory, + renameOrCopyFile, requireExecutable, + runCmd) +import Reanimate.Parameters (Height, Raster (RasterNone), Width, pHeight, + pNoExternals, pRaster, pRootDirectory, pWidth) +import Reanimate.Render (applyRaster, requireRaster) +import Reanimate.Svg.Constructors (flipYAxis, mkText, scaleXY) +import Reanimate.Svg.Unuse (replaceUses, unbox, unboxFit) +import System.Directory (copyFile, doesFileExist, removeFile) +import System.FilePath (replaceExtension, takeExtension, (<.>), ()) +import System.IO (hClose) +import System.IO.Temp (withSystemTempFile) +import System.IO.Unsafe (unsafePerformIO) -- | Load an external image. Width and height must be specified, -- ignoring the image's aspect ratio. The center of the image is @@ -81,13 +82,13 @@ mkImage -> FilePath -- ^ Path to external image file. -> SVG mkImage width height path | takeExtension path == ".svg" = unsafePerformIO $ do - svg_data <- B.readFile path + svg_data <- T.readFile path case parseSvgFile path svg_data of Nothing -> error "Malformed svg" Just svg -> return $ scaleXY (width / screenWidth) (height / screenHeight) - $ embedDocument svg + $ unboxFit svg mkImage width height path | pRaster == RasterNone = unsafePerformIO $ do inp <- LBS.readFile path let imgData = LBS.unpack $ Base64.encode inp @@ -122,7 +123,7 @@ mkImage width height path = unsafePerformIO $ do & Svg.imageHeight .~ Svg.Num height & Svg.imageHref - .~ ("file://" ++ target) + .~ (fileUri target) & Svg.imageCornerUpperLeft .~ (Svg.Num (-width / 2), Svg.Num (-height / 2)) & Svg.imageAspectRatio @@ -273,7 +274,7 @@ vectorize_ args path = unsafePerformIO $ do runCmd magick [path, "-flatten", tmpBmpPath] runCmd potrace (args ++ ["--svg", "--output", tmpSvgPath, tmpBmpPath]) renameOrCopyFile tmpSvgPath svgPath - svg_data <- B.readFile svgPath + svg_data <- T.readFile svgPath case parseSvgFile svgPath svg_data of Nothing -> do removeFile svgPath diff --git a/src/Reanimate/Render.hs b/src/Reanimate/Render.hs index a7bf75d..9dfa2c7 100644 --- a/src/Reanimate/Render.hs +++ b/src/Reanimate/Render.hs @@ -13,6 +13,7 @@ to ever directly use the functions in this module. module Reanimate.Render ( render , renderSvgs + , renderSvgs_ , renderSnippets -- :: Animation -> IO () , renderLimitedFrames , Format(..) @@ -26,24 +27,25 @@ module Reanimate.Render import Control.Concurrent import Control.Exception import Control.Monad (forM_, forever, unless, void, when) -import Data.Either -import Data.Function +import Data.Either (isRight) +import Data.Function (fix) import qualified Data.Text as T import qualified Data.Text.IO as T import Data.Time import Graphics.SvgTree (Number (..)) -import Numeric -import Reanimate.Animation -import Reanimate.Driver.Check -import Reanimate.Driver.Magick +import Numeric (showFFloat) +import Reanimate.Animation (Animation, duration, frameAt, renderSvg) +import Reanimate.Driver.Check (hasInkscape, hasMagick, hasRSvg) +import Reanimate.Driver.Magick (magickCmd) import Reanimate.Misc import Reanimate.Parameters -import System.Console.ANSI.Codes -import System.Exit -import System.FileLock (withTryFileLock, SharedExclusive(..), unlockFile) -import System.Directory +import System.Console.ANSI.Codes (clearFromCursorToLineEndCode) +import System.Directory (doesFileExist, removeFile) +import System.Exit (ExitCode (ExitFailure), exitWith) +import System.FileLock (SharedExclusive (..), unlockFile, withTryFileLock) import System.FilePath (replaceExtension, (<.>), ()) import System.IO +import System.IO.Temp (createTempDirectory, getCanonicalTemporaryDirectory) import Text.Printf (printf) idempotentFile :: FilePath -> IO () -> IO () @@ -82,6 +84,28 @@ renderSvgs folder offset _prettyPrint ani = do hPutStrLn stderr msg exitWith (ExitFailure 1) +renderSvgs_ :: Animation -> (Int -> FilePath -> IO ()) -> IO () +renderSvgs_ ani cb = do + tmp <- getCanonicalTemporaryDirectory + tmpDir <- createTempDirectory tmp "reanimate" + lock <- newMVar () + handle errHandler $ concurrentForM_ (frameOrder rate frameCount) $ \nth' -> do + let nth = (nth') `mod` frameCount + now = (duration ani / (fromIntegral frameCount - 1)) * fromIntegral nth + frame = frameAt (if frameCount <= 1 then 0 else now) ani + path = tmpDir show nth <.> "svg" + svg = renderSvg Nothing Nothing frame + + idempotentFile path $ + writeFile path svg + withMVar lock $ \_ -> cb nth path + where + rate = 60 + frameCount = round (duration ani * fromIntegral rate) :: Int + errHandler (ErrorCall msg) = do + hPutStrLn stderr msg + exitWith (ExitFailure 1) + -- | Render as many frames as possible in 2 seconds. Limited to 20 frames. renderLimitedFrames :: FilePath -> Int -> Bool -> Int -> Animation -> IO () renderLimitedFrames folder offset _prettyPrint rate ani = do @@ -389,9 +413,8 @@ applyRaster RasterNone _ = return () applyRaster RasterAuto _ = return () applyRaster RasterInkscape path = runCmd "inkscape" - [ "--without-gui" - , "--file=" ++ path - , "--export-png=" ++ replaceExtension path "png" + ["--export-type=png" + , path ] applyRaster RasterRSvg path = runCmd "rsvg-convert" diff --git a/src/Reanimate/Scene.hs b/src/Reanimate/Scene.hs index 6f7a565..0a29305 100644 --- a/src/Reanimate/Scene.hs +++ b/src/Reanimate/Scene.hs @@ -20,6 +20,7 @@ module Reanimate.Scene waitOn, -- :: Scene s a -> Scene s a adjustZ, -- :: (ZIndex -> ZIndex) -> Scene s a -> Scene s a withSceneDuration, -- :: Scene s () -> Scene s Duration + signalS, -- Signal -> Scene s a -> Scene s a -- * Variables Var, @@ -38,12 +39,14 @@ module Reanimate.Scene spriteT, -- :: Frame s Time spriteDuration, -- :: Frame s Duration newSprite, -- :: Frame s SVG -> Scene s (Sprite s) + newSpritePart, -- :: Frame s SVG -> Scene s (Sprite s) newSprite_, -- :: Frame s SVG -> Scene s () newSpriteA, -- :: Animation -> Scene s (Sprite s) newSpriteA', -- :: Sync -> Animation -> Scene s (Sprite s) newSpriteSVG, -- :: SVG -> Scene s (Sprite s) newSpriteSVG_, -- :: SVG -> Scene s () destroySprite, -- :: Sprite s -> Scene s () + renderSprite, -- :: Sprite s -> Frame s SVG applyVar, -- :: Var s a -> Sprite s -> (a -> SVG -> SVG) -> Scene s () spriteModify, -- :: Sprite s -> Frame s ((SVG,ZIndex) -> (SVG, ZIndex)) -> Scene s () spriteMap, -- :: Sprite s -> (SVG -> SVG) -> Scene s () @@ -56,8 +59,11 @@ module Reanimate.Scene -- * Object API Object, ObjectData, + signalO, oNew, newObject, + newObjectPart, + renderObject, oModify, oModifyS, oRead, diff --git a/src/Reanimate/Scene/Core.hs b/src/Reanimate/Scene/Core.hs index 20f717b..38678f6 100644 --- a/src/Reanimate/Scene/Core.hs +++ b/src/Reanimate/Scene/Core.hs @@ -2,11 +2,11 @@ module Reanimate.Scene.Core where -import Control.Monad.Fix (MonadFix (..)) -import Control.Monad.ST -import Data.List -import Reanimate.Animation -import Reanimate.Svg.Constructors +import Control.Monad.Fix (MonadFix (..)) +import Control.Monad.ST (ST, runST) +import Data.List (sortOn) +import Reanimate.Animation (Animation, Duration, SVG, Time, mkAnimation) +import Reanimate.Svg.Constructors (mkGroup) -- | The ZIndex property specifies the stack order of sprites and animations. Elements -- with a higher ZIndex will be drawn on top of elements with a lower index. @@ -73,6 +73,24 @@ scene action = ) ) +-- -- | Apply easing function to all render elements created by the scene +-- -- in the timespan from now to the scene duration. +-- -- +-- -- Note that this does not affect time as seen by `queryNow` or any +-- -- time-dependent variables or object properties. +-- signalS :: Signal -> Scene s a -> Scene s a +-- signalS signal (M action) = M $ \now -> do +-- (a, s, p, gens) <- action now +-- let action_dur = max s p +-- modify_t t +-- | t < now = t +-- | t > now+action_dur = t +-- | otherwise = now + signal ((t-now) / action_dur) * action_dur +-- let applyS gen = do +-- fn <- gen +-- return $ \dur t -> fn dur (modify_t t) +-- return (a, s, p, map applyS gens) + -- | Execute actions in a scene without advancing the clock. Note that scenes do not end before -- all forked actions have completed. -- diff --git a/src/Reanimate/Scene/Object.hs b/src/Reanimate/Scene/Object.hs index 4d33d85..660eb00 100644 --- a/src/Reanimate/Scene/Object.hs +++ b/src/Reanimate/Scene/Object.hs @@ -1,32 +1,25 @@ -{-# LANGUAGE ApplicativeDo #-} +{-# LANGUAGE ApplicativeDo #-} {-# LANGUAGE RecordWildCards #-} module Reanimate.Scene.Object where -import Linear.V2 -import Linear.Vector -import Control.Lens -import Control.Monad (forM_, void) -import Control.Monad.State (State, execState) -import Data.Monoid ( Last(getLast) ) -import Graphics.SvgTree - ( Number (..), - Tree, - strokeWidth, - toUserUnit, - pattern None, - ) -import Reanimate.Animation -import Reanimate.Constants (defaultDPI, defaultStrokeWidth) -import Reanimate.Ease (Signal, curveS, fromToS) -import Reanimate.Effect ( applyE, fadeLineOutE, overEnding ) -import Reanimate.Math.Balloon ( balloon ) -import Reanimate.Morph.Common (morph) -import Reanimate.Morph.Linear (linear) -import Reanimate.Svg +import Control.Lens +import Control.Monad (forM_, void) +import Control.Monad.State (State, execState) +import Graphics.SvgTree (Number (..), Tree, pattern None, strokeWidth, toUserUnit) +import Linear.V2 (R1 (_x), R2 (_y), V2 (..)) +import Linear.Vector (Additive (lerp), (^*)) +import Reanimate.Animation +import Reanimate.Constants (defaultDPI, defaultStrokeWidth) +import Reanimate.Ease (Signal, curveS, fromToS) +import Reanimate.Effect (applyE, fadeLineOutE, overEnding) +import Reanimate.Math.Balloon (balloon) +import Reanimate.Morph.Common (morph) +import Reanimate.Morph.Linear (linear) +import Reanimate.Svg -import Reanimate.Scene.Core -import Reanimate.Scene.Sprite -import Reanimate.Scene.Var +import Reanimate.Scene.Core (Scene, fork, scene, wait) +import Reanimate.Scene.Sprite (Frame, Sprite, addPartToScene, newSpritePart, newSpriteA', play, spriteModify, unVar, signalS, renderSprite) +import Reanimate.Scene.Var (Var, modifyVar, newVar, readVar, tweenVar) ------------------------------------------------------- -- Objects @@ -43,23 +36,23 @@ instance Renderable Tree where -- change over time. data Object s a = Object { objectSprite :: Sprite s, - objectData :: Var s (ObjectData a) + objectData :: Var s (ObjectData a) } -- | Container for object properties. data ObjectData a = ObjectData - { _oTranslate :: V2 Double, - _oValueRef :: a, - _oSVG :: SVG, - _oContext :: SVG -> SVG, + { _oTranslate :: V2 Double, + _oValueRef :: a, + _oSVG :: SVG, + _oContext :: SVG -> SVG, -- | Top, right, bottom, left - _oMargin :: (Double, Double, Double, Double), - _oBB :: (Double, Double, Double, Double), - _oOpacity :: Double, - _oShown :: Bool, - _oZIndex :: Int, - _oEasing :: Signal, - _oScale :: Double, + _oMargin :: (Double, Double, Double, Double), + _oBB :: (Double, Double, Double, Double), + _oOpacity :: Double, + _oShown :: Bool, + _oZIndex :: Int, + _oEasing :: Signal, + _oScale :: Double, _oScaleOrigin :: V2 Double } @@ -252,6 +245,10 @@ oBBHeight = oBB . _4 ------------------------------------------------------------------------------- -- Object modifiers +-- | Apply easing function before rendering object. +signalO :: Object s a -> Duration -> Signal -> Scene s () +signalO obj dur signal = signalS (objectSprite obj) dur signal + -- | Modify object properties. oModify :: Object s a -> (ObjectData a -> ObjectData a) -> Scene s () oModify o = modifyVar (objectData o) @@ -293,6 +290,15 @@ oNew = newObject -- | Create new object. newObject :: Renderable a => a -> Scene s (Object s a) newObject val = do + obj <- newObjectPart val + addPartToScene $ objectSprite obj + return obj + +-- | Create new object, but not showing as part of the scene, Such +-- objects can be used hierachically within the definitions of +-- sprites, via the function renderObject. +newObjectPart :: Renderable a => a -> Scene s (Object s a) +newObjectPart val = do ref <- newVar ObjectData @@ -309,7 +315,7 @@ newObject val = do _oScale = 1, _oScaleOrigin = V2 0 0 } - sprite <- newSprite $ do + sprite <- newSpritePart $ do ~obj@ObjectData {..} <- unVar ref pure $ if _oShown @@ -330,6 +336,32 @@ newObject val = do where svg = toSVG val +-- | Create a frame context from an object for use within a sprite definition. +-- +-- Example: +-- +-- @ +-- do opacityVar <- 'newVar' 1 +-- textObj1 <- 'newObjectPart' $ 'scale' 3 $ 'center' $ 'latex' "Fade" +-- 'oModify' textObj1 $ 'oContext' .~ 'withFillColor' "red" +-- textObj2 <- 'newObjectPart' $ 'scale' 3 $ 'center' $ 'latex' "Overlap" +-- 'oModify' textObj2 $ 'oContext' .~ 'withFillColor' "blue" +-- sprite <- 'newSprite' $ do +-- opacity <- 'unVar' opacityVar +-- text1 <- 'renderObject' textObj1 +-- text2 <- 'renderObject' textObj2 +-- 'return' $ 'withGroupOpacity' opacity $ 'mkGroup' [text1, text2] +-- 'fork' $ 'oShowWith' textObj1 $ 'setDuration' 1 . 'oDraw' +-- 'wait' 0.2 +-- 'fork' $ 'oShowWith' textObj2 $ 'setDuration' 1 . 'oDraw' +-- 'wait' 0.2 +-- 'tweenVar' opacityVar 1 $ 'const' $ 'fromToS' 1 0 +-- @ +-- +-- <> +renderObject :: Object s a -> Frame s SVG +renderObject obj = renderSprite $ objectSprite obj + oScaleApply :: ObjectData a -> (SVG -> SVG) oScaleApply ObjectData {..} = uncurryV2 translate (negate _oScaleOrigin) @@ -506,9 +538,9 @@ oDraw :: SVG -> Animation oDraw = oStagger $ \svg -> scene $ forM_ (svgGlyphs $ pathify svg) $ \(ctx, attr, node) -> do let sWidth = - case toUserUnit defaultDPI <$> getLast (attr ^. strokeWidth) of + case toUserUnit defaultDPI <$> (attr ^. strokeWidth) of Just (Num d) -> max defaultStrokeWidth d - _ -> defaultStrokeWidth + _ -> defaultStrokeWidth -- wait 1 play $ mapA ctx $ diff --git a/src/Reanimate/Scene/Sprite.hs b/src/Reanimate/Scene/Sprite.hs index 8fda6dc..f93d711 100644 --- a/src/Reanimate/Scene/Sprite.hs +++ b/src/Reanimate/Scene/Sprite.hs @@ -1,39 +1,22 @@ -{-# LANGUAGE ApplicativeDo #-} +{-# LANGUAGE ApplicativeDo #-} {-# LANGUAGE PatternSynonyms #-} -{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE RankNTypes #-} module Reanimate.Scene.Sprite where -import Control.Monad (void) -import Control.Monad.ST (ST) -import Data.Bifunctor (Bifunctor (first)) -import Data.STRef (STRef, modifySTRef, newSTRef, readSTRef) -import Graphics.SvgTree - ( pattern None, - ) -import Reanimate.Animation - ( Animation, - Duration, - SVG, - Sync (SyncStretch), - Time, - dropA, - duration, - getAnimationFrame, - ) -import Reanimate.Effect (Effect, delayE) -import Reanimate.Scene.Core - ( Scene (M), - ZIndex, - addGen, - fork, - liftST, - queryNow, - scene, - wait, - ) -import Reanimate.Scene.Var (unpackVar, Var (..), newVar, readVar) -import Reanimate.Transition (Transition, overlapT) +import Control.Monad (void) +import Control.Monad.ST (ST) +import Data.Bifunctor (Bifunctor (first)) +import Data.STRef (STRef, modifySTRef, newSTRef, readSTRef) +import Graphics.SvgTree (pattern None) +import Reanimate.Animation (Animation, Duration, SVG, Sync (SyncStretch), Time, dropA, + duration, getAnimationFrame) +import Reanimate.Effect (Effect, delayE) +import Reanimate.Scene.Core (Gen, Scene (M), ZIndex, addGen, fork, liftST, queryNow, scene, + wait) +import Reanimate.Scene.Var (Var (..), newVar, readVar, unpackVar) +import Reanimate.Transition (Transition, overlapT) +import Reanimate.Ease (Signal) -- | Create and render a variable. The rendering will be born at the current timestamp -- and will persist until the end of the scene. @@ -75,22 +58,22 @@ play ani = newSpriteA ani >>= destroySprite -- | Sprites are animations with a given time of birth as well as a time of death. -- They can be controlled using variables, tweening, and effects. -data Sprite s = Sprite Time (STRef s (Duration, ST s (Duration -> Time -> SVG -> (SVG, ZIndex)))) +data Sprite s = Sprite Time (STRef s (Time -> Time)) (STRef s (Duration, ST s (Duration -> Duration -> Time -> SVG -> (SVG, ZIndex)))) (Gen s) -- | Sprite frame generator. Generates frames over time in a stateful environment. -newtype Frame s a = Frame {unFrame :: ST s (Time -> Duration -> Time -> a)} +newtype Frame s a = Frame {unFrame :: ST s (Duration -> Time -> Duration -> Time -> a)} instance Functor (Frame s) where fmap fn (Frame gen) = Frame $ do m <- gen - return (\real_t d t -> fn $ m real_t d t) + return (\scene_d real_t d t -> fn $ m scene_d real_t d t) instance Applicative (Frame s) where - pure v = Frame $ return (\_ _ _ -> v) + pure v = Frame $ return (\_ _ _ _ -> v) Frame f <*> Frame g = Frame $ do m1 <- f m2 <- g - return $ \real_t d t -> m1 real_t d t (m2 real_t d t) + return $ \scene_d real_t d t -> m1 scene_d real_t d t (m2 scene_d real_t d t) -- | Dereference a variable as a Sprite frame. -- @@ -107,15 +90,15 @@ instance Applicative (Frame s) where unVar :: Var s a -> Frame s a unVar var = Frame $ do fn <- unpackVar var - return $ \real_t _d _t -> fn real_t + return $ \_scene_d real_t _d _t -> fn real_t -- | Dereference seconds since sprite birth. spriteT :: Frame s Time -spriteT = Frame $ return (\_real_t _d t -> t) +spriteT = Frame $ return (\_scene_d _real_t _d t -> t) -- | Dereference duration of the current sprite. spriteDuration :: Frame s Duration -spriteDuration = Frame $ return (\_real_t d _t -> d) +spriteDuration = Frame $ return (\_scene_d _real_t d _t -> d) -- | Create new sprite defined by a frame generator. Unless otherwise specified using -- 'destroySprite', the sprite will die at the end of the scene. @@ -130,14 +113,26 @@ spriteDuration = Frame $ return (\_real_t d _t -> d) -- <> newSprite :: Frame s SVG -> Scene s (Sprite s) newSprite render = do + s <- newSpritePart render + addPartToScene s + return s + +-- | Create a new sprite defined by a frame generator, but not showing +-- as part of the scene. Such sprites can be used hierarchically within +-- the definitions of other sprites, via the function renderSprite. +newSpritePart :: Frame s SVG -> Scene s (Sprite s) +newSpritePart render = do now <- queryNow - ref <- liftST $ newSTRef (-1, return $ \_d _t svg -> (svg, 0)) - addGen $ do + tmod <- liftST $ newSTRef id + ref <- liftST $ newSTRef (-1, return $ \_ad _d _t svg -> (svg, 0)) + return $ Sprite now tmod ref $ do fn <- unFrame render + time_fn <- readSTRef tmod (spriteDur, spriteEffectGen) <- readSTRef ref spriteEffect <- spriteEffectGen - return $ \d absT -> - let relD = (if spriteDur < 0 then d else spriteDur) - now + return $ \absD absT_ -> + let absT = time_fn absT_ + relD = (if spriteDur < 0 then absD else spriteDur) - now relT = absT - now -- Sprite is live [now;duration[ -- If we're at the end of a scene, sprites @@ -145,11 +140,13 @@ newSprite render = do -- This behavior is difficult to get right. See the 'bug_*' examples for -- automated tests. inTimeSlice = relT >= 0 && relT < relD - isLastFrame = d == absT && relT == relD - in if inTimeSlice || isLastFrame - then spriteEffect relD relT (fn absT relD relT) + isLastFrame = absD == absT && relT == relD + in if inTimeSlice || isLastFrame + then spriteEffect absD relD relT (fn absD absT relD relT) else (None, 0) - return $ Sprite now ref + +addPartToScene :: Sprite s -> Scene s () +addPartToScene (Sprite _ _ _ gen) = addGen gen -- | Create new sprite defined by a frame generator. The sprite will die at -- the end of the scene. @@ -211,6 +208,15 @@ newSpriteSVG = newSprite . pure newSpriteSVG_ :: SVG -> Scene s () newSpriteSVG_ = void . newSpriteSVG +-- | Create a frame context from the source sprite for use within another. Use of this +-- function allows several sprites to be combined into a single one, with the +-- method of combination controled by variables. +renderSprite :: Sprite s -> Frame s SVG +renderSprite (Sprite _ _ _ gen) = + Frame $ do + genFn <- gen + return (\absD absT _ _ -> fst $ genFn absD absT) + -- | Change the rendering of a sprite using data from a variable. If data from several variables -- is needed, use a frame generator instead. -- @@ -242,7 +248,7 @@ applyVar var sprite fn = spriteModify sprite $ do -- -- <> destroySprite :: Sprite s -> Scene s () -destroySprite (Sprite _ ref) = do +destroySprite (Sprite _ _tmod ref _) = do now <- queryNow liftST $ modifySTRef ref $ \(ttl, render) -> @@ -250,16 +256,29 @@ destroySprite (Sprite _ ref) = do -- | Low-level frame modifier. spriteModify :: Sprite s -> Frame s ((SVG, ZIndex) -> (SVG, ZIndex)) -> Scene s () -spriteModify (Sprite born ref) modFn = liftST $ +spriteModify (Sprite born _tmod ref _) modFn = liftST $ modifySTRef ref $ \(ttl, renderGen) -> ( ttl, do render <- renderGen modRender <- unFrame modFn - return $ \relD relT -> - let absT = relT + born in modRender absT relD relT . render relD relT + return $ \absD relD relT -> + let absT = relT + born in modRender absD absT relD relT . render absD relD relT ) +-- | Apply easing function before rendering sprite. +signalS :: Sprite s -> Duration -> Signal -> Scene s () +signalS (Sprite _born tmod _ref _) dur signal = do + now <- queryNow + let modify_t t + | t < now = t + | t > now+dur = t + | otherwise = now + signal ((t-now) / dur) * dur + liftST $ + modifySTRef tmod $ \fn -> modify_t . fn + + + -- | Map the SVG output of a sprite. -- -- Example: @@ -272,7 +291,7 @@ spriteModify (Sprite born ref) modFn = liftST $ -- -- <> spriteMap :: Sprite s -> (SVG -> SVG) -> Scene s () -spriteMap sprite@(Sprite born _) fn = do +spriteMap sprite@(Sprite born _ _ _) fn = do now <- queryNow let tDelta = now - born spriteModify sprite $ do @@ -290,7 +309,7 @@ spriteMap sprite@(Sprite born _) fn = do -- -- <> spriteTween :: Sprite s -> Duration -> (Double -> SVG -> SVG) -> Scene s () -spriteTween sprite@(Sprite born _) dur fn = do +spriteTween sprite@(Sprite born _ _ _) dur fn = do now <- queryNow let tDelta = now - born spriteModify sprite $ do @@ -332,15 +351,15 @@ spriteVar sprite def fn = do -- -- <> spriteE :: Sprite s -> Effect -> Scene s () -spriteE (Sprite born ref) effect = do +spriteE (Sprite born _tmod ref _) effect = do now <- queryNow liftST $ modifySTRef ref $ \(ttl, renderGen) -> ( ttl, do render <- renderGen - return $ \d t svg -> - let (svg', z) = render d t svg + return $ \ad d t svg -> + let (svg', z) = render ad d t svg in (delayE (max 0 $ now - born) effect d t svg', z) ) @@ -358,15 +377,15 @@ spriteE (Sprite born ref) effect = do -- -- <> spriteZ :: Sprite s -> ZIndex -> Scene s () -spriteZ (Sprite born ref) zindex = do +spriteZ (Sprite born _tmod ref _) zindex = do now <- queryNow liftST $ modifySTRef ref $ \(ttl, renderGen) -> ( ttl, do render <- renderGen - return $ \d t svg -> - let (svg', z) = render d t svg in (svg', if t < now - born then z else zindex) + return $ \ad d t svg -> + let (svg', z) = render ad d t svg in (svg', if t < now - born then z else zindex) ) -- | Destroy all local sprites at the end of a scene. diff --git a/src/Reanimate/Scene/Var.hs b/src/Reanimate/Scene/Var.hs index 64a659f..65f7c3d 100644 --- a/src/Reanimate/Scene/Var.hs +++ b/src/Reanimate/Scene/Var.hs @@ -1,13 +1,13 @@ -{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE RecordWildCards #-} module Reanimate.Scene.Var where -import Control.Monad.ST (ST) -import qualified Data.Map as M -import Data.STRef -import Reanimate.Animation (Duration, Time) -import Reanimate.Scene.Core (Scene, liftST, queryNow, wait) +import Control.Monad.ST (ST) +import qualified Data.Map as M +import Data.STRef (STRef, modifySTRef, newSTRef, readSTRef) +import Reanimate.Animation (Duration, Time) +import Reanimate.Scene.Core (Scene, liftST, queryNow, wait) -- | Time dependent variable. newtype Var s a = Var (STRef s (VarData a)) @@ -18,9 +18,9 @@ newtype Var s a = Var (STRef s (VarData a)) -- it shouldn't be Nothing again. -- 3. isNothing (evarLastTime var) => M.null (evarTimeline var) data VarData a = VarData - { evarDefault :: a, - evarTimeline :: Timeline a, - evarLastTime :: Maybe Time, + { evarDefault :: a, + evarTimeline :: Timeline a, + evarLastTime :: Maybe Time, evarLastValue :: a } @@ -103,7 +103,7 @@ modifyVarData now fn var = let before = keepBefore now var after = keepFrom now var timeline = flip M.map (evarTimeline after) $ \case - StaticValue s -> StaticValue $ fn s + StaticValue s -> StaticValue $ fn s TweenValue dur f -> TweenValue dur $ \a t -> fn (f a t) in after {evarTimeline = timeline, evarLastValue = fn $ evarLastValue after} `elseVar` before @@ -165,5 +165,5 @@ keepBefore nd var@VarData {..} = _ -> timeline' lastTime = case lastModifier of Just (t, TweenValue dur _) -> Just $ min nd (t + dur) - _ -> min nd <$> evarLastTime + _ -> min nd <$> evarLastTime in VarData evarDefault timeline'' lastTime (maybe evarDefault (readVarData var) lastTime) diff --git a/src/Reanimate/Svg.hs b/src/Reanimate/Svg.hs index 13dbfef..e890d37 100644 --- a/src/Reanimate/Svg.hs +++ b/src/Reanimate/Svg.hs @@ -14,17 +14,17 @@ module Reanimate.Svg , module Reanimate.Svg.Unuse ) where -import Control.Lens ((%~), (&), (.~), (^.), (?~)) +import Control.Lens ((%~), (&), (.~), (?~), (^.)) import Control.Monad.State import Graphics.SvgTree -import Linear.V2 hiding (angle) -import Reanimate.Constants -import Reanimate.Animation (SVG) +import Linear.V2 (V2 (V2)) +import Reanimate.Animation (SVG) +import Reanimate.Constants (defaultDPI) +import Reanimate.Svg.BoundingBox (boundingBox, svgHeight, svgWidth) import Reanimate.Svg.Constructors import Reanimate.Svg.LineCommand -import Reanimate.Svg.BoundingBox -import Reanimate.Svg.Unuse -import qualified Reanimate.Transform as Transform +import Reanimate.Svg.Unuse (embedDocument, replaceUses, unbox, unboxFit) +import qualified Reanimate.Transform as Transform -- | Remove transformations (such as translations, rotations, scaling) -- and apply them directly to the SVG nodes. Note, this function @@ -343,9 +343,9 @@ mapSvgLines fn = mapSvgPaths (lineToPath . fn . toLineCommands) mapSvgPoints :: (RPoint -> RPoint) -> SVG -> SVG mapSvgPoints fn = mapSvgLines (map worker) where - worker (LineMove p) = LineMove (fn p) + worker (LineMove p) = LineMove (fn p) worker (LineBezier ps) = LineBezier (map fn ps) - worker (LineEnd p) = LineEnd (fn p) + worker (LineEnd p) = LineEnd (fn p) -- | Convert coordinate system from degrees to radians. svgPointsToRadians :: SVG -> SVG diff --git a/src/Reanimate/Svg/BoundingBox.hs b/src/Reanimate/Svg/BoundingBox.hs index 52c6a66..0331e73 100644 --- a/src/Reanimate/Svg/BoundingBox.hs +++ b/src/Reanimate/Svg/BoundingBox.hs @@ -11,25 +11,27 @@ module Reanimate.Svg.BoundingBox , svgWidth ) where -import Control.Arrow ((***)) -import Control.Lens ((^.)) -import Data.List -import Data.Maybe (mapMaybe) -import qualified Data.Vector.Unboxed as V +import Control.Arrow ((***)) +import Control.Lens ((^.)) +import Data.List (foldl') +import Data.Maybe (mapMaybe) +import qualified Data.Vector.Unboxed as V import qualified Geom2D.CubicBezier.Linear as Bezier import Graphics.SvgTree -import Linear.V2 hiding (angle) -import Linear.Vector -import Reanimate.Constants -import Reanimate.Svg.LineCommand -import qualified Reanimate.Transform as Transform +import Linear.V2 (V2 (V2)) +import Linear.Vector (Additive (zero)) +import Reanimate.Constants (defaultDPI) +import Reanimate.Svg.LineCommand (LineCommand (..), toLineCommands) +import qualified Reanimate.Transform as Transform -- | Return bounding box of SVG tree. -- The four numbers returned are (minimal X-coordinate, minimal Y-coordinate, width, height) -- -- Note: Bounding boxes are computed on a best-effort basis and will not work -- in all cases. The only supported SVG nodes are: path, circle, polyline, --- ellipse, line, rectangle, image. All other nodes return (0,0,0,0). +-- ellipse, line, rectangle, image and svg. All other nodes return (0,0,0,0). +-- The box for the svg node is based on the document's width and height +-- (if both are present). boundingBox :: Tree -> (Double, Double, Double, Double) boundingBox t = case svgBoundingPoints t of @@ -76,36 +78,36 @@ linePoints = worker zero svgBoundingPoints :: Tree -> [RPoint] svgBoundingPoints t = map (Transform.transformPoint m) $ case t of - None -> [] - UseTree{} -> [] - GroupTree g -> concatMap svgBoundingPoints (g^.groupChildren) - SymbolTree g -> concatMap svgBoundingPoints (g^.groupChildren) - FilterTree{} -> [] + None -> [] + UseTree{} -> [] + GroupTree g -> concatMap svgBoundingPoints (g ^. groupChildren) + SymbolTree g -> concatMap svgBoundingPoints (g ^. groupChildren) + FilterTree{} -> [] DefinitionTree{} -> [] - PathTree p -> linePoints $ toLineCommands (p^.pathDefinition) - CircleTree c -> circleBoundingPoints c - PolyLineTree pl -> pl ^. polyLinePoints - EllipseTree e -> ellipseBoundingPoints e - LineTree line -> map pointToRPoint [line^.linePoint1, line^.linePoint2] - RectangleTree rect -> - case pointToRPoint (rect^.rectUpperLeftCorner) of - V2 x y -> V2 x y : - case mapTuple (fmap $ toUserUnit defaultDPI) (rect^.rectWidth, rect^.rectHeight) of - (Just (Num w), Just (Num h)) -> [V2 (x+w) (y+h)] - _ -> [] - TextTree{} -> [] - ImageTree img -> - case (img^.imageCornerUpperLeft, img^.imageWidth, img^.imageHeight) of - ((Num x, Num y), Num w, Num h) -> - [V2 x y, V2 (x+w) (y+h)] - _ -> [] + PathTree p -> linePoints $ toLineCommands (p ^. pathDefinition) + CircleTree c -> circleBoundingPoints c + PolyLineTree pl -> pl ^. polyLinePoints + EllipseTree e -> ellipseBoundingPoints e + LineTree l -> map pointToRPoint [l ^. linePoint1, l ^. linePoint2] + RectangleTree r -> + let p = pointToRPoint (r ^. rectUpperLeftCorner) + mDims = (r ^. rectWidth, r ^. rectHeight) + in rectPoints p mDims + TextTree{} -> [] + ImageTree img -> + let p = pointToRPoint (img ^. imageCornerUpperLeft) + dims = (img ^. imageWidth, img ^. imageHeight) + in rectPoints' p dims MeshGradientTree{} -> [] - _ -> [] + SvgTree d -> let mDims = (d ^. documentWidth, d ^. documentHeight) + in rectPoints (V2 0 0) mDims + _ -> [] where - m = Transform.mkMatrix (t^.transform) + m = Transform.mkMatrix (t ^. transform) mapTuple f = f *** f + toUserUnit' = toUserUnit defaultDPI pointToRPoint p = - case mapTuple (toUserUnit defaultDPI) p of + case mapTuple toUserUnit' p of (Num x, Num y) -> V2 x y _ -> error "Reanimate.Svg.svgBoundingPoints: Unrecognized number format." @@ -113,18 +115,31 @@ svgBoundingPoints t = map (Transform.transformPoint m) $ let (xnum, ynum) = circ ^. circleCenter rnum = circ ^. circleRadius in case mapMaybe unpackNumber [xnum, ynum, rnum] of - [x, y, r] -> [ V2 (x + r * cos angle) (y + r * sin angle) | angle <- [0, pi/10 .. 2 * pi]] - _ -> [] + [x, y, r] -> ellipsePoints x y r r + _ -> [] ellipseBoundingPoints e = let (xnum,ynum) = e ^. ellipseCenter xrnum = e ^. ellipseXRadius yrnum = e ^. ellipseYRadius in case mapMaybe unpackNumber [xnum, ynum, xrnum, yrnum] of - [x,y,xr,yr] -> [V2 (x + xr * cos angle) (y + yr * sin angle) | angle <- [0, pi/10 .. 2 * pi]] - _ -> [] + [x, y, xr, yr] -> ellipsePoints x y xr yr + _ -> [] + + ellipsePoints x y xr yr = [ V2 (x + xr * cos angle) (y + yr * sin angle) + | angle <- [0, pi/10 .. 2 * pi] ] + + rectPoints p mDims = case mDims of + (Just w, Just h) -> rectPoints' p (w, h) + _ -> [p] + + rectPoints' p@(V2 x y) dims = + p : case mapTuple toUserUnit' dims of + ((Num w), (Num h)) -> let (x', y') = (x + w, y + h) + in [V2 x' y, V2 x' y', V2 x y'] + _ -> [] unpackNumber n = - case toUserUnit defaultDPI n of + case toUserUnit' n of Num d -> Just d _ -> Nothing diff --git a/src/Reanimate/Svg/Constructors.hs b/src/Reanimate/Svg/Constructors.hs index fe4b287..27b2e5b 100644 --- a/src/Reanimate/Svg/Constructors.hs +++ b/src/Reanimate/Svg/Constructors.hs @@ -20,6 +20,7 @@ module Reanimate.Svg.Constructors , withId , withStrokeColor , withStrokeColorPixel + , withStrokeOpacity , withStrokeDashArray , withStrokeLineJoin , withFillColor @@ -63,11 +64,11 @@ import Data.Attoparsec.Text (parseOnly) import qualified Data.Map as Map import qualified Data.Text as T import Graphics.SvgTree -import Graphics.SvgTree.NamedColors -import Graphics.SvgTree.PathParser -import Linear.V2 hiding (angle) -import Reanimate.Constants -import Reanimate.Svg.BoundingBox +import Graphics.SvgTree.NamedColors (svgNamedColors) +import Graphics.SvgTree.PathParser (pathParser) +import Linear.V2 (V2 (V2)) +import Reanimate.Constants (screenHeight, screenWidth) +import Reanimate.Svg.BoundingBox (boundingBox) -- | Apply list of transformations to given image. withTransformations :: [Transformation] -> Tree -> Tree @@ -192,10 +193,12 @@ centerUsing a = translate (-x-w/2) (-y-h/2) where (x, y, w, h) = boundingBox a --- | Create 'Texture' based on SVG color name. --- See for the list of available names. --- If the provided name doesn't correspond to valid SVG color name, white-ish color is used. +-- | Create 'Texture' based on a SVG color name or @\"none\"@. If the provided +-- 'String' is not a valid value, a white-ish color is used. +-- See for the list +-- of available color names. mkColor :: String -> Texture +mkColor "none" = FillNone mkColor name = case Map.lookup (T.pack name) svgNamedColors of Nothing -> ColorRef (PixelRGBA8 240 248 255 255) @@ -209,6 +212,10 @@ withStrokeColor color = strokeColor .~ pure (mkColor color) withStrokeColorPixel :: PixelRGBA8 -> Tree -> Tree withStrokeColorPixel color = strokeColor .~ pure (ColorRef color) +-- | See +withStrokeOpacity :: Double -> Tree -> Tree +withStrokeOpacity opacity = strokeOpacity ?~ realToFrac opacity + -- | See withStrokeDashArray :: [Double] -> Tree -> Tree withStrokeDashArray arr = strokeDashArray .~ pure (map Num arr) @@ -217,7 +224,10 @@ withStrokeDashArray arr = strokeDashArray .~ pure (map Num arr) withStrokeLineJoin :: LineJoin -> Tree -> Tree withStrokeLineJoin ljoin = strokeLineJoin .~ pure ljoin --- | See +-- | See . +-- +-- @withFillColor color@ takes the same approach to @color@ as 'mkColor'. For +-- RGB color values, use 'withFillColorPixel'. withFillColor :: String -> Tree -> Tree withFillColor color = fillColor .~ pure (mkColor color) @@ -250,8 +260,8 @@ withId idTag = attrId ?~ idTag -- | @mkRect width height@ creates a rectangle with given @with@ and @height@, centered at @(0, 0)@. -- See mkRect :: Double -> Double -> Tree -mkRect width height = translate (-width/2) (-height/2) $ rectangleTree $ defaultSvg - & rectUpperLeftCorner .~ (Num 0, Num 0) +mkRect width height = rectangleTree $ defaultSvg + & rectUpperLeftCorner .~ (Num (-width/2), Num (-height/2)) & rectWidth ?~ Num width & rectHeight ?~ Num height diff --git a/src/Reanimate/Svg/LineCommand.hs b/src/Reanimate/Svg/LineCommand.hs index bd8470d..d928ce5 100644 --- a/src/Reanimate/Svg/LineCommand.hs +++ b/src/Reanimate/Svg/LineCommand.hs @@ -15,17 +15,20 @@ module Reanimate.Svg.LineCommand ) where -import Control.Lens ((%~), (&), (.~)) -import Control.Monad.Fix -import Control.Monad.State -import Data.Functor -import Data.Maybe -import qualified Data.Vector.Unboxed as V +import Control.Lens ((%~), (&), (.~)) +import Control.Monad.Fix (MonadFix (mfix)) +import Control.Monad.State (MonadState (get, put), State, evalState, forM, gets, + modify) +import Data.Functor (($>)) +import Data.Maybe (mapMaybe) +import qualified Data.Vector.Unboxed as V import qualified Geom2D.CubicBezier.Linear as Bezier -import Graphics.SvgTree -import Linear.Metric -import Linear.V2 hiding (angle) -import Linear.Vector +import Graphics.SvgTree (Coord, Origin (OriginAbsolute, OriginRelative), + PathCommand (..), RPoint, Tree, mapTree, pathDefinition, + pattern PathTree) +import Linear.Metric (Metric (distance)) +import Linear.V2 (R1 (_x), R2 (_y), V2 (V2)) +import Linear.Vector (Additive (lerp, zero)) -- | Line command monad used for keeping track of the current location. type CmdM a = State RPoint a @@ -42,13 +45,13 @@ data LineCommand lineToPath :: [LineCommand] -> [PathCommand] lineToPath = map worker where - worker (LineMove p) = MoveTo OriginAbsolute [p] + worker (LineMove p) = MoveTo OriginAbsolute [p] -- worker (LineDraw p) = LineTo OriginAbsolute [p] worker (LineBezier [a, b, c]) = CurveTo OriginAbsolute [(a, b, c)] - worker (LineBezier [a, b]) = QuadraticBezier OriginAbsolute [(a, b)] - worker (LineBezier [a]) = LineTo OriginAbsolute [a] - worker LineBezier {} = error "Reanimate.Svg.lineToPath: invalid bezier curve" - worker LineEnd {} = EndPath + worker (LineBezier [a, b]) = QuadraticBezier OriginAbsolute [(a, b)] + worker (LineBezier [a]) = LineTo OriginAbsolute [a] + worker LineBezier {} = error "Reanimate.Svg.lineToPath: invalid bezier curve" + worker LineEnd {} = EndPath -- | Using @n@ control points, approximate the path of the curves. lineToPoints :: Int -> [LineCommand] -> [RPoint] @@ -56,9 +59,9 @@ lineToPoints nPoints cmds = mapMaybe lineEnd lineSegments where lineSegments = [partialLine (fromIntegral n / fromIntegral nPoints) cmds | n <- [0 .. nPoints -1]] - lineEnd [] = Nothing + lineEnd [] = Nothing lineEnd [LineBezier pts] = Just (last pts) - lineEnd (_ : xs) = lineEnd xs + lineEnd (_ : xs) = lineEnd xs partialLine :: Double -> [LineCommand] -> [LineCommand] partialLine alpha cmds = evalState (worker 0 cmds) zero @@ -78,9 +81,9 @@ adjustLineLength :: Double -> RPoint -> LineCommand -> LineCommand adjustLineLength alpha from cmd = case cmd of LineBezier points -> LineBezier $ drop 1 $ partialBezierPoints (from : points) 0 alpha - LineMove p -> LineMove p + LineMove p -> LineMove p -- LineDraw t -> LineDraw (lerp alpha t from) - LineEnd p -> LineBezier [lerp alpha p from] + LineEnd p -> LineBezier [lerp alpha p from] -- | Estimated length of all segments in a line. lineLength :: LineCommand -> CmdM Double @@ -101,10 +104,10 @@ lineLength cmd = rpointsToBezier :: [RPoint] -> Bezier.CubicBezier Double rpointsToBezier lst = case lst of - [a, b] -> Bezier.CubicBezier a a b b - [a, b, c] -> Bezier.quadToCubic (Bezier.QuadBezier a b c) + [a, b] -> Bezier.CubicBezier a a b b + [a, b, c] -> Bezier.quadToCubic (Bezier.QuadBezier a b c) [a, b, c, d] -> Bezier.CubicBezier a b c d - _ -> error $ "rpointsToBezier: Invalid list of points: " ++ show lst + _ -> error $ "rpointsToBezier: Invalid list of points: " ++ show lst -- | Convert from path commands to line commands. toLineCommands :: [PathCommand] -> [LineCommand] @@ -116,12 +119,12 @@ toLineCommands ps = evalState (worker zero Nothing ps) zero let startPos' = case lcmds of [LineMove pos] -> pos - _ -> startPos + _ -> startPos (lcmds ++) <$> worker startPos' (cmdToControlPoint $ last lcmds) cmds cmdToControlPoint :: LineCommand -> Maybe RPoint cmdToControlPoint (LineBezier points) = Just (last (init points)) -cmdToControlPoint _ = Nothing +cmdToControlPoint _ = Nothing mkStraightLine :: RPoint -> LineCommand mkStraightLine p = LineBezier [p] @@ -180,7 +183,7 @@ toLineCommand startPos mbPrevControlPt cmd = adjustPosition OriginRelative p = modify (+ p) adjustPosition OriginAbsolute p = put p makeAbsolute OriginAbsolute _from p = p - makeAbsolute OriginRelative from p = from + p + makeAbsolute OriginRelative from p = from + p calculateVectorAngle :: Double -> Double -> Double -> Double -> Double calculateVectorAngle ux uy vx vy diff --git a/src/Reanimate/Svg/Unuse.hs b/src/Reanimate/Svg/Unuse.hs index 288a5f8..220ef0c 100644 --- a/src/Reanimate/Svg/Unuse.hs +++ b/src/Reanimate/Svg/Unuse.hs @@ -8,23 +8,25 @@ Portability : POSIX module Reanimate.Svg.Unuse ( replaceUses , unbox + , unboxFit , embedDocument ) where import Control.Lens ((%~), (&), (.~), (?~), (^.)) import qualified Data.Map as Map -import Data.Maybe +import Data.Maybe (fromMaybe) import Graphics.SvgTree -import Reanimate.Constants -import Reanimate.Svg.Constructors +import Reanimate.Constants (defaultDPI, screenHeight, screenWidth) +import Reanimate.Svg.Constructors (flipYAxis, mkGroup, scaleXY, translate, + withFillOpacity, withStrokeWidth) -- | Replace all @@ nodes with their definition. replaceUses :: Document -> Document replaceUses doc = doc & documentElements %~ map (mapTree replace) where - replaceDefinition PathTree{} = None + replaceDefinition PathTree{} = None replaceDefinition SymbolTree{} = None - replaceDefinition t = t + replaceDefinition t = t replace t@DefinitionTree{} = mapTree replaceDefinition t replace (UseTree _ Just{}) = error "replaceUses: subtree in use?" @@ -56,13 +58,29 @@ replaceUses doc = doc & documentElements %~ map (mapTree replace) -- FIXME: the viewbox is ignored. Can we use the viewbox as a mask? -- | Transform out viewbox. Definitions and CSS rules are discarded. unbox :: Document -> Tree -unbox doc@Document{_documentViewBox = Just (_minx, _minw, _width, _height)} = +unbox doc@Document{_documentViewBox = Just (_minx, _miny, _width, _height)} = groupTree $ defaultSvg & groupChildren .~ doc^.documentElements unbox doc = groupTree $ defaultSvg & groupChildren .~ doc^.documentElements +-- | Transform out viewbox and fit image to screen size. +unboxFit :: Document -> Tree +unboxFit doc@Document{_documentViewBox = Just (minx, miny, width, height)} = + let widthScale = screenWidth/width + heightScale = screenHeight/height + scaler = min widthScale heightScale + in + scaleXY scaler (-scaler) $ + translate (-minx-width/2) (-miny-height/2) $ + groupTree $ defaultSvg + & groupChildren .~ doc^.documentElements +unboxFit doc = + groupTree $ defaultSvg + & groupChildren .~ doc^.documentElements + + -- | Embed 'Document'. This keeps the entire document intact but makes -- it more difficult to use, say, `Reanimate.Svg.pathify` on it. embedDocument :: Document -> Tree diff --git a/src/Reanimate/Transform.hs b/src/Reanimate/Transform.hs index 9eef5cd..c4e69cb 100644 --- a/src/Reanimate/Transform.hs +++ b/src/Reanimate/Transform.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE BangPatterns #-} {-| 2D transformation matrices capable of translating, scaling, rotating, and skewing. @@ -11,12 +11,12 @@ module Reanimate.Transform ) where -- XXX: Use Linear.Matrix instead of Data.Matrix to drop the 'matrix' dependency. -import Data.List -import Data.Matrix (Matrix) -import qualified Data.Matrix as M -import Data.Maybe -import Graphics.SvgTree -import Linear.V2 +import Data.List (foldl') +import Data.Matrix (Matrix) +import qualified Data.Matrix as M +import Data.Maybe (fromMaybe) +import Graphics.SvgTree (Coord, RPoint, Transformation (..)) +import Linear.V2 (V2 (V2)) -- | Identity matrix. -- diff --git a/src/Reanimate/Transition.hs b/src/Reanimate/Transition.hs index 0e92554..dc78895 100644 --- a/src/Reanimate/Transition.hs +++ b/src/Reanimate/Transition.hs @@ -15,9 +15,10 @@ module Reanimate.Transition , fadeT ) where -import Reanimate.Animation -import Reanimate.Ease -import Reanimate.Effect +import Reanimate.Animation (Animation, dropA, duration, lastA, parA, pause, seqA, signalA, + takeA) +import Reanimate.Ease (Signal) +import Reanimate.Effect (Effect, applyE, fadeInE, fadeOutE) -- | A transition transforms one animation into another. type Transition = Animation -> Animation -> Animation @@ -66,7 +67,7 @@ effectT eA eB a b = applyE eA a `parA` applyE eB b -- -- <> chainT :: Transition -> [Animation] -> Animation -chainT _ [] = pause 0 +chainT _ [] = pause 0 chainT t (x:xs) = foldl t x xs -- | Fade out left-hand-side animation while fading in right-hand-side animation. diff --git a/src/Reanimate/Voice.hs b/src/Reanimate/Voice.hs index dd98c60..865ad22 100644 --- a/src/Reanimate/Voice.hs +++ b/src/Reanimate/Voice.hs @@ -1,6 +1,6 @@ +{-# LANGUAGE ApplicativeDo #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ApplicativeDo #-} -{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE RecordWildCards #-} {-| Reanimate can automatically synchronize animations to your voice if you have a transcript and an audio recording. This works with the help of Gentle @@ -20,27 +20,27 @@ module Reanimate.Voice ) where +import Control.Monad (forM_) import Data.Aeson -import Data.Char -import System.IO.Unsafe ( unsafePerformIO ) -import System.Directory -import System.FilePath -import System.Process -import System.Exit -import Control.Monad -import Data.List -import Data.Maybe -import qualified Data.Map as Map -import Data.Map ( Map ) -import Data.Text ( Text ) -import qualified Data.Text as T -import qualified Data.Text.IO as T -import Reanimate.Misc -import Reanimate.LaTeX -import Reanimate.Scene -import Reanimate.Animation -import Reanimate.Svg -import Reanimate.Constants +import Data.Char (isAlphaNum, isSpace) +import Data.List (sortOn) +import Data.Map (Map) +import qualified Data.Map as Map +import Data.Maybe (listToMaybe) +import Data.Text (Text) +import qualified Data.Text as T +import qualified Data.Text.IO as T +import Reanimate.Animation (SVG, staticFrame) +import Reanimate.Constants (defaultStrokeWidth, screenHeight, screenWidth) +import Reanimate.LaTeX (latex, latexChunks) +import Reanimate.Misc (withTempFile) +import Reanimate.Scene (Scene, play, waitUntil) +import Reanimate.Svg (mkGroup, scale, translate, withStrokeColor, withStrokeWidth) +import System.Directory (doesFileExist) +import System.Exit (ExitCode (ExitFailure, ExitSuccess)) +import System.FilePath (replaceExtension) +import System.IO.Unsafe (unsafePerformIO) +import System.Process (rawSystem, showCommandForUser) -- | Aligned transcript. Contains the transcript text as well as -- timing data for each word. diff --git a/stack-lts-13.yaml b/stack-lts-13.yaml deleted file mode 100644 index 90307a9..0000000 --- a/stack-lts-13.yaml +++ /dev/null @@ -1,23 +0,0 @@ -resolver: lts-13.19 - -allow-newer: true - -packages: -- . - -extra-deps: -- reanimate-svg-0.12.2.0 -- chiphunk-0.1.2.1 -- cubicbezier-0.6.0.6@sha256:2191ff47144d9a13a2784651a33d340cd31be1926a6c188925143103eb3c8db3 -- fast-math-1.0.2@sha256:91181eb836e54413cc5a841e797c42b2264954e893ea530b6fc4da0dccf6a8b7 -- matrices-0.5.0@sha256:b2761813f6a61c84224559619cc60a16a858ac671c8436bbac8ec89e85473058 -- hmatrix-0.20.0.0@sha256:d79a9218e314f1a2344457c3851bd1d2536518ecb5f1a2fcd81daa45e46cd025,4870 -- websockets-0.12.7.0@sha256:fc96169cc8268d3efb93bdc1e0b060dd88ce932237b837904118777a90d6db80,7863 -- clock-0.8@sha256:b4ae207e2d3761450060a0d0feb873269233898039c76fceef9cc1a544067767,4113 -- earcut-0.1.0.4@sha256:d5118b3eecf24d130263d81fb30f1ff56b1db43036582bfd1d8cc9ba3adae8be,1010 -- tasty-rerun-1.1.17@sha256:d4a3ccb0f63f499f36edc71b33c0f91c850eddb22dd92b928aa33b8459f3734a,1373 -- hgeometry-0.11.0.0@sha256:09ead201a6ac3492c0be8dda5a6b32792b9ae87cab730b8362d46ee8d5c2acb4,11714 -- hgeometry-combinatorial-0.11.0.0@sha256:03176f235a1c49a415fe1266274dafca84deb917cbcbf9a654452686b4cd2bfe,8286 -- vinyl-0.13.0@sha256:0f247cd3f8682b30881a07de18e6fec52d540646fbcb328420049cc8d63cd407,3724 -- data-clist-0.1.2.3@sha256:1e26251c8921821c8a1c7e168955449822f1bacf03d056cc59c84fd2863a0f8e,983 -- hashable-1.3.0.0@sha256:4c70f1407881059e93550d3742191254296b2737b793a742bd901348fb3e1fb1,5206 diff --git a/stack-lts-14.yaml b/stack-lts-14.yaml index 76bbcfb..1c40eb5 100644 --- a/stack-lts-14.yaml +++ b/stack-lts-14.yaml @@ -6,7 +6,7 @@ packages: - . extra-deps: -- reanimate-svg-0.12.2.0 +- reanimate-svg-0.13.0.1 - chiphunk-0.1.2.1 - cubicbezier-0.6.0.6@sha256:2191ff47144d9a13a2784651a33d340cd31be1926a6c188925143103eb3c8db3 - fast-math-1.0.2@sha256:91181eb836e54413cc5a841e797c42b2264954e893ea530b6fc4da0dccf6a8b7 @@ -14,7 +14,6 @@ extra-deps: - hmatrix-0.20.0.0@sha256:d79a9218e314f1a2344457c3851bd1d2536518ecb5f1a2fcd81daa45e46cd025,4870 - earcut-0.1.0.4@sha256:d5118b3eecf24d130263d81fb30f1ff56b1db43036582bfd1d8cc9ba3adae8be,1010 - tasty-rerun-1.1.17@sha256:d4a3ccb0f63f499f36edc71b33c0f91c850eddb22dd92b928aa33b8459f3734a,1373 -- hgeometry-0.11.0.0@sha256:09ead201a6ac3492c0be8dda5a6b32792b9ae87cab730b8362d46ee8d5c2acb4,11714 -- hgeometry-combinatorial-0.11.0.0@sha256:03176f235a1c49a415fe1266274dafca84deb917cbcbf9a654452686b4cd2bfe,8286 - vinyl-0.13.0@sha256:0f247cd3f8682b30881a07de18e6fec52d540646fbcb328420049cc8d63cd407,3724 - hashable-1.3.0.0@sha256:4c70f1407881059e93550d3742191254296b2737b793a742bd901348fb3e1fb1,5206 +- network-3.1.2.1@sha256:188d6daea8cd91bc3553efd5a90a1e7c6d0425fa66a53baa74db5b6d9fd75c8b,4968 diff --git a/stack-lts-15.yaml b/stack-lts-15.yaml index aea97b5..8f8e83e 100644 --- a/stack-lts-15.yaml +++ b/stack-lts-15.yaml @@ -8,7 +8,5 @@ packages: extra-deps: - chiphunk-0.1.2.1 - earcut-0.1.0.4@sha256:d5118b3eecf24d130263d81fb30f1ff56b1db43036582bfd1d8cc9ba3adae8be,1010 -- reanimate-svg-0.12.2.0 -- hgeometry-0.11.0.0@sha256:09ead201a6ac3492c0be8dda5a6b32792b9ae87cab730b8362d46ee8d5c2acb4,11714 -- hgeometry-combinatorial-0.11.0.0@sha256:03176f235a1c49a415fe1266274dafca84deb917cbcbf9a654452686b4cd2bfe,8286 +- reanimate-svg-0.13.0.1 - vinyl-0.13.0@sha256:0f247cd3f8682b30881a07de18e6fec52d540646fbcb328420049cc8d63cd407,3724 diff --git a/stack-lts-16.yaml b/stack-lts-16.yaml new file mode 100644 index 0000000..77144f3 --- /dev/null +++ b/stack-lts-16.yaml @@ -0,0 +1,11 @@ +resolver: lts-16.31 + +allow-newer: false + +packages: +- . + +extra-deps: +- earcut-0.1.0.4 +- reanimate-svg-0.13.0.1 +- vinyl-0.13.0@sha256:0f247cd3f8682b30881a07de18e6fec52d540646fbcb328420049cc8d63cd407,3724 diff --git a/stack-lts-17.yaml b/stack-lts-17.yaml new file mode 100644 index 0000000..2135ef1 --- /dev/null +++ b/stack-lts-17.yaml @@ -0,0 +1,11 @@ +resolver: lts-17.4 + +allow-newer: false + +packages: +- . + +extra-deps: +- earcut-0.1.0.4 +- reanimate-svg-0.13.0.1 +- vinyl-0.13.0@sha256:0f247cd3f8682b30881a07de18e6fec52d540646fbcb328420049cc8d63cd407,3724 diff --git a/stack.yaml b/stack.yaml index 520043a..4db2d71 100644 --- a/stack.yaml +++ b/stack.yaml @@ -1,13 +1,8 @@ -resolver: lts-16.12 +resolver: lts-18.15 allow-newer: false packages: - . -extra-deps: -- earcut-0.1.0.4 -- reanimate-svg-0.12.2.0 -- hgeometry-0.11.0.0@sha256:09ead201a6ac3492c0be8dda5a6b32792b9ae87cab730b8362d46ee8d5c2acb4,11714 -- hgeometry-combinatorial-0.11.0.0@sha256:03176f235a1c49a415fe1266274dafca84deb917cbcbf9a654452686b4cd2bfe,8286 -- vinyl-0.13.0@sha256:0f247cd3f8682b30881a07de18e6fec52d540646fbcb328420049cc8d63cd407,3724 +extra-deps: [] diff --git a/test/Properties.hs b/test/Properties.hs index 53a4e7f..21dcf20 100644 --- a/test/Properties.hs +++ b/test/Properties.hs @@ -1,13 +1,17 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleInstances #-} {-# OPTIONS_GHC -w #-} {-# LANGUAGE TemplateHaskell #-} module Properties where +import Test.Tasty + +#if !defined(NO_HGEOMETRY) + import qualified Data.Vector as V import Linear.V2 import Linear.Vector import Test.QuickCheck -import Test.Tasty import Test.Tasty.QuickCheck import Reanimate.Math.Common @@ -18,6 +22,8 @@ import Helpers import Debug.Trace + + prop_pGenerate (PolyParam a) (PolyParam b) (PolyParam c) (PolyParam d) = pIsSimple $ pGenerate [a,b,c,d] @@ -87,3 +93,7 @@ prop_dualCycle p = forAll (choose (0,pSize p-1)) $ \n -> return [] all_props :: TestTree all_props = testProperties "properties" $allProperties +#else +all_props :: TestTree +all_props = testGroup "properties" [] +#endif diff --git a/test/UnitTests.hs b/test/UnitTests.hs index 4e7dcdf..ad36b0f 100644 --- a/test/UnitTests.hs +++ b/test/UnitTests.hs @@ -47,7 +47,8 @@ unitTestsDisabled = unitTestFolder :: FilePath -> IO TestTree unitTestFolder _ | unitTestsDisabled = return $ testGroup "animate (disabled)" [] unitTestFolder path = do - files <- sort <$> getDirectoryContents path + let goldenPath = path "golden" + files <- sort <$> getDirectoryContents goldenPath mbWDiff <- findExecutable "wdiff" let diff = case mbWDiff of Nothing -> ["diff", "--strip-trailing-cr"] @@ -55,8 +56,8 @@ unitTestFolder path = do return $ testGroup "animate" [ goldenVsStringDiff file (\ref new -> diff ++ [ref, new]) fullPath (genGolden hsPath) | file <- files - , let fullPath = path file - hsPath = replaceExtension fullPath "hs" + , let fullPath = goldenPath file + hsPath = path replaceExtension file "hs" , takeExtension fullPath == ".golden" ] @@ -81,6 +82,7 @@ compileTestFolder :: FilePath -> IO TestTree compileTestFolder _ | unitTestsDisabled = return $ testGroup "compile (disabled)" [] compileTestFolder path = do files <- sort <$> getDirectoryContents path + goldenFiles <- getDirectoryContents $ path "golden" return $ testGroup "compile" [ testCase file $ do (ret, _stdout, err) <- @@ -95,7 +97,7 @@ compileTestFolder path = do | file <- files , let fullPath = path file , takeExtension file == ".hs" || takeExtension file == ".lhs" - , notElem (replaceExtension file "golden") files + , notElem (replaceExtension file "golden") goldenFiles ] where ghcOpts = ["-fno-code", "-O0", "-Werror", "-Wall"] diff --git a/unix/Detach.hs b/unix/Detach.hs new file mode 100644 index 0000000..c38ba8f --- /dev/null +++ b/unix/Detach.hs @@ -0,0 +1,19 @@ +module Detach (detach) where + +import Control.Concurrent +import Control.Monad +import System.Posix.IO +import System.Posix.Process (createSession, forkProcess) + +detach :: IO () -> IO Bool +detach daemon = do + void $ forkProcess $ do + devnull <- openFd "/dev/null" ReadWrite Nothing defaultFileFlags + void $ dupTo devnull stdInput + void $ dupTo devnull stdOutput + void $ dupTo devnull stdError + closeFd devnull + void createSession + void $ forkProcess daemon + threadDelay (10^(6::Int)) + return True diff --git a/unix/POVRay.hs b/unix/POVRay.hs new file mode 100644 index 0000000..c72e545 --- /dev/null +++ b/unix/POVRay.hs @@ -0,0 +1,16 @@ +module POVRay + ( povrayApp + , runPOVRay + ) where + +import Reanimate.Misc (requireExecutable, runCmd) + +-- | Name of the POV-Ray executable +povrayApp :: String +povrayApp = "povray" + +-- | Run the POV-Ray executable with arguments +runPOVRay :: [String] -> IO () +runPOVRay args = do + exec <- requireExecutable povrayApp + runCmd exec args diff --git a/videos/showcase/showcase.hs b/videos/showcase/showcase.hs index 2ef0f4d..a588733 100755 --- a/videos/showcase/showcase.hs +++ b/videos/showcase/showcase.hs @@ -294,7 +294,7 @@ drawAnimation' :: Double -> Double -> SVG -> Animation drawAnimation' fillDur step svg = scene $ do forM_ (zip [0..] $ svgGlyphs svg) $ \(n, (fn, attr, tree)) -> do let sWidth = - case toUserUnit defaultDPI <$> getLast (attr ^. strokeWidth) of + case toUserUnit defaultDPI <$> (attr ^. strokeWidth) of Just (Num d) -> d _ -> defaultStrokeWidth fork $ do diff --git a/viewer-elm/README.md b/viewer-elm/README.md index a6c5ac6..b7686f2 100644 --- a/viewer-elm/README.md +++ b/viewer-elm/README.md @@ -2,7 +2,7 @@ ## Build it ```bash -cd reanimate/elm-viewer +cd reanimate/viewer-elm npm install npm run build npm run minify diff --git a/viewer-elm/dist/elm.js b/viewer-elm/dist/elm.js index 8f4ba65..63c9718 100644 --- a/viewer-elm/dist/elm.js +++ b/viewer-elm/dist/elm.js @@ -1 +1 @@ -!function(n){"use strict";function r(n,r,t){return t.a=n,t.f=r,t}function o(t){return r(2,t,function(r){return function(n){return t(r,n)}})}function b(e){return r(3,e,function(t){return function(r){return function(n){return e(t,r,n)}}})}function v(u){return r(4,u,function(e){return function(t){return function(r){return function(n){return u(e,t,r,n)}}}})}function t(a){return r(5,a,function(u){return function(e){return function(t){return function(r){return function(n){return a(u,e,t,r,n)}}}}})}function e(i){return r(6,i,function(a){return function(u){return function(e){return function(t){return function(r){return function(n){return i(a,u,e,t,r,n)}}}}}})}function u(o){return r(7,o,function(i){return function(a){return function(u){return function(e){return function(t){return function(r){return function(n){return o(i,a,u,e,t,r,n)}}}}}}})}function g(n,r,t){return 2===n.a?n.f(r,t):n(r)(t)}function p(n,r,t,e){return 3===n.a?n.f(r,t,e):n(r)(t)(e)}function w(n,r,t,e,u){return 4===n.a?n.f(r,t,e,u):n(r)(t)(e)(u)}function s(n,r,t,e,u,a){return 5===n.a?n.f(r,t,e,u,a):n(r)(t)(e)(u)(a)}function l(n,r,t,e,u,a,i){return 6===n.a?n.f(r,t,e,u,a,i):n(r)(t)(e)(u)(a)(i)}function f(n,r,t,e,u,a,i,o){return 7===n.a?n.f(r,t,e,u,a,i,o):n(r)(t)(e)(u)(a)(i)(o)}function k(n,r){for(var t,e=[],u=a(n,r,0,e);u&&(t=e.pop());u=a(t.a,t.b,0,e));return u}function a(n,r,t,e){if(n===r)return!0;if("object"!=typeof n||null===n||null===r)return"function"==typeof n&&R(5),!1;if(100i)return u}var l=t.$;if(4===l){for(var d=t.k;4===d.$;)d=d.k;return n(r,d,e,u,a+1,i,r.elm_event_node_ref)}var $=t.e;var h=r.childNodes;for(var m=0;m<$.length;m++){var g=1===l?$[m]:$[m].b,p=++a+(g.b||0);if(a<=c&&c<=p&&(u=n(h[m],g,e,u,a,p,o),!(f=e[u])||(c=f.r)>i))return u;a=p}return u}(n,r,t,0,0,r.b,e)}function mr(n,r,t,e){return 0===t.length?n:(hr(n,r,t,e),gr(n,t))}function gr(n,r){for(var t=0;t>1,A(r,r),1&n?A(t,r):t):t}),Oa=o(function(n,r){return p(Ma,n,r,"")}),Fa=b(function(n,r,t){return A(g(Oa,n-zt(t),function(n){return g(Ra,n,"")}(r)),t)}),Pa=an,xa=o(function(n,r){return g(Kn,n,Pa(r))})("disabled"),za=$a("id"),Sa=$a("title"),Ba=Jn("pre"),Ia=Jn("label"),qa=$a("max"),Ga=Jn("progress"),Ja=Jn("span"),Ua=$a("value"),Wa=o(function(n,r){return g(Ia,j,N([g(Ja,j,N([Ta(" | Loading frames ")])),g(Ga,N([Ua(Vr(n)),qa(Vr(r))]),j)]))}),Ka=v(function(n,r,t,e){for(;;){if(!e.b)return t/r;var u=n+r,a=e.a*n+t;n=.9*n,r=u,t=a,e=e.b}}),Ha=P,Va=o(function(n,r){for(;;){if(-2===r.$)return n;var t=r.d;n=g(Va,n+1,r.e),r=t}}),Ya=u(function(n,r,t,e,u,a,i){var o,f,c,v,s,b=k(o=g(Va,0,e),t)?Ta(""):g(Wa,o,t),l=n.$?Ta(""):g(La,N([(f=n.a,g($a,"src",/^\s*(javascript:|data:text\/html)/i.test(c=f)?"":c))]),j),d=u?Da:g(pa,N([ha("help-button"),ja(ga)]),N([Ta("?")])),$=Vr(t),h=zt($),m=g(Ba,N([ha("bar")]),N([(s=i,g(ma,N([ha("media-controls")]),N([g(pa,N([ja(Cu(-10)),xa(!s),Sa("10 frames back")]),N([Ta("<<")])),g(pa,N([ja(Cu(-1)),xa(!s),Sa("1 frame back")]),N([Ta("<")])),s?g(pa,N([ja(Tu),za("play-or-pause")]),N([Ta("Play")])):g(pa,N([ja(Nu),za("play-or-pause")]),N([Ta("Pause")])),g(pa,N([ja(Cu(1)),xa(!s),Sa("1 frame forward")]),N([Ta(">")])),g(pa,N([ja(Cu(10)),xa(!s),Sa("10 frames forward")]),N([Ta(">>")]))]))),g(Ja,j,N([Ta(" Frame: "+p(Fa,h,"0",Vr(r+1))+" / "+$+(i?" ":(v=a," @ "+Vr(Ha(1e3/w(Ka,1,0,0,v)))+" FPS")))])),b,d]));return g(ma,N([ha("viewer")]),N([l,m]))}),Za=Jn("h1"),Qa=Jn("li"),Xa=Jn("ul"),ni=re({aD:function(){return $(te,ae)},aK:function(n){return se(N([du((t=fe,function(n){return t(g(lu,bu,n))})),(r=oe,Se(g(Rt,r,de))),function(){n:for(;;)switch(n.$){case 3:return 1===n.a.j.$?vu(ce):ru;case 4:if(1!==n.a.$)break n;return g(nu,2e3,ve(ie));default:break n}return ru}()]));var r,t},aM:da,aN:function(i){return g(ma,N([ha("app")]),N([function(){switch(i.$){case 0:return Ta("Disconnected");case 1:return Ta("Connected");case 2:return Ta("Compiling ...");case 4:return function(n){switch(n.$){case 0:var r=n.a;return g(ma,j,N([g(Za,j,N([Ta("Compilation failed")])),g(Ba,j,N([Ta(r)]))]));case 1:return g(ma,j,N([Ta("Failed to establish connection. Possible causes include: "),g(Xa,j,N([g(Qa,j,N([Ta("The reanimate script is not running")])),g(Qa,j,N([Ta("At most one viewer window can connect at time. Maybe there's another browser window/tab already connected?")]))]))]));case 2:return Ta("Failed to decode Port message. The error was: "+lt(n.a));default:return Ta("Unexpected message: "+n.a)}}(i.a);default:var n=i.a.z,r=i.a.u,t=i.a.p,e=i.a.D,u=i.a.G,a=i.a.E;return f(Ya,e,t,n,r,u,a,!i.a.j.$)}}()]))}});fu={Main:{init:ni(Ot(0))(0)}},n.Elm?function n(r,t){for(var e in t)e in r?"init"==e?R(6):n(r[e],t[e]):r[e]=t[e]}(n.Elm,fu):n.Elm=fu}(this); \ No newline at end of file +!function(n){"use strict";function r(n,r,t){return t.a=n,t.f=r,t}function o(t){return r(2,t,function(r){return function(n){return t(r,n)}})}function b(e){return r(3,e,function(t){return function(r){return function(n){return e(t,r,n)}}})}function s(u){return r(4,u,function(e){return function(t){return function(r){return function(n){return u(e,t,r,n)}}}})}function t(a){return r(5,a,function(u){return function(e){return function(t){return function(r){return function(n){return a(u,e,t,r,n)}}}}})}function e(i){return r(6,i,function(a){return function(u){return function(e){return function(t){return function(r){return function(n){return i(a,u,e,t,r,n)}}}}}})}function u(o){return r(7,o,function(i){return function(a){return function(u){return function(e){return function(t){return function(r){return function(n){return o(i,a,u,e,t,r,n)}}}}}}})}function a(f){return r(8,f,function(o){return function(i){return function(a){return function(u){return function(e){return function(t){return function(r){return function(n){return f(o,i,a,u,e,t,r,n)}}}}}}}})}function i(c){return r(9,c,function(f){return function(o){return function(i){return function(a){return function(u){return function(e){return function(t){return function(r){return function(n){return c(f,o,i,a,u,e,t,r,n)}}}}}}}}})}function $(n,r,t){return 2===n.a?n.f(r,t):n(r)(t)}function g(n,r,t,e){return 3===n.a?n.f(r,t,e):n(r)(t)(e)}function p(n,r,t,e,u){return 4===n.a?n.f(r,t,e,u):n(r)(t)(e)(u)}function l(n,r,t,e,u,a){return 5===n.a?n.f(r,t,e,u,a):n(r)(t)(e)(u)(a)}function d(n,r,t,e,u,a,i){return 6===n.a?n.f(r,t,e,u,a,i):n(r)(t)(e)(u)(a)(i)}function f(n,r,t,e,u,a,i,o){return 7===n.a?n.f(r,t,e,u,a,i,o):n(r)(t)(e)(u)(a)(i)(o)}function m(n,r){for(var t,e=[],u=c(n,r,0,e);u&&(t=e.pop());u=c(t.a,t.b,0,e));return u}function c(n,r,t,e){if(n===r)return!0;if("object"!=typeof n||null===n||null===r)return"function"==typeof n&&F(5),!1;if(100"}function F(n){throw Error("https://github.com/elm/core/blob/1.0.0/hints/"+n+".md")}o(function(n,r){return n+r}),o(function(n,r){return n-r}),o(function(n,r){return n*r}),o(function(n,r){return n/r}),o(function(n,r){return n/r|0}),o(Math.pow),o(function(n,r){return r%n});var S=o(function(n,r){var t=r%n;return 0===n?F(11):0i)return u}var l=t.$;if(4===l){for(var d=t.k;4===d.$;)d=d.k;return n(r,d,e,u,a+1,i,r.elm_event_node_ref)}var h=t.e;var $=r.childNodes;for(var g=0;gi))return u;a=m}return u}(n,r,t,0,0,r.b,e)}function _r(n,r,t,e){return 0===t.length?n:(Nr(n,r,t,e),Cr(n,t))}function Cr(n,r){for(var t=0;t>n}),o(function(n,r){return r>>>n});function Gr(n){return $($t,"\n ",$(gt,"\n",n))}function Jr(n){return g(pt,o(function(n,r){return r+1}),0,n)}function Wr(n){var r=kt(n);return 97<=r&&r<=122}function Ur(n){var r=kt(n);return r<=90&&65<=r}function Kr(n){return Wr(n)||Ur(n)}function Qr(n){return Wr(n)||Ur(n)||function(n){var r=kt(n);return r<=57&&48<=r}(n)}function Yr(n){return n}function Hr(n){return""===n}function Vr(n){return g(be,$e(rt),ce(N),n)}var Zr=1,Xr=2,nt=0,rt=C,tt=b(function(n,r,t){for(;;){if(-2===t.$)return r;var e=t.d,u=n,a=g(n,t.b,t.c,g(tt,n,r,t.e));n=u,r=a,t=e}}),et=function(n){return g(tt,b(function(n,r,t){return $(rt,y(n,r),t)}),N,n)},ut=R,at=(b(function(t,n,r){var e=r.c,u=r.d,a=o(function(n,r){return g(ut,n.$?t:a,r,n.a)});return g(ut,a,g(ut,t,n,u),e)}),function(n){return{$:1,a:n}}),it=o(function(n,r){return{$:3,a:n,b:r}}),ot=o(function(n,r){return{$:0,a:n,b:r}}),ft=o(function(n,r){return{$:1,a:n,b:r}}),ct=function(n){return{$:0,a:n}},vt=function(n){return{$:2,a:n}},st=function(n){return{$:0,a:n}},bt={$:1},lt=U,dt=bn,ht=function(n){return n+""},$t=o(function(n,r){return $(J,n,O(r))}),gt=o(function(n,r){return T($(G,n,r))}),pt=b(function(n,r,t){for(;;){if(!t.b)return r;var e=t.b,u=n,a=$(n,t.a,r);n=u,r=a,t=e}}),mt=L,wt=b(function(n,r,t){for(;;){if(1<=h(n,r))return t;var e=n,u=r-1,a=$(rt,r,t);n=e,r=u,t=a}}),yt=o(function(n,r){return g(wt,n,r,N)}),At=o(function(n,r){return g(mt,n,$(yt,0,Jr(r)-1),r)}),kt=function(n){var r=n.charCodeAt(0);return r<55296||56319>1,E(r,r),1&n?E(t,r):t):t}),si=o(function(n,r){return g(vi,n,r,"")}),bi=b(function(n,r,t){return E($(si,n-Xt(t),function(n){return $(ci,n,"")}(r)),t)}),li=ln,di=o(function(n,r){return $(rr,n,li(r))})("disabled"),hi=Ka("id"),$i=Ka("title"),gi=Vn("pre"),pi=Vn("label"),mi=Ka("max"),wi=Vn("progress"),yi=Vn("span"),Ai=Ka("value"),ki=o(function(n,r){return $(pi,N,T([$(yi,N,T([ai(" | Loading frames ")])),$(wi,T([Ai(ht(n)),mi(ht(r))]),N)]))}),ji=s(function(n,r,t,e){for(;;){if(!e.b)return t/r;var u=n+r,a=e.a*n+t;n=.9*n,r=u,t=a,e=e.b}}),Ei=B,Ni=o(function(n,r){for(;;){if(-2===r.$)return n;var t=r.d;n=$(Ni,n+1,r.e),r=t}}),_i=u(function(n,r,t,e,u,a,i){var o,f,c,v=m(o=$(Ni,0,e),t)?ai(""):$(ki,o,t),s=n.$?ai(""):$(fi,T([$(Ka,"src",/^\s*(javascript:|data:text\/html)/i.test(f="file://"+n.a)?"":f)]),N),b=u?oi:$(Va,T([Qa("help-button"),Uu(Ha)]),T([ai("?")])),l=ht(t),d=Xt(l),h=$(gi,T([Qa("bar")]),T([(c=i,$(Ya,T([Qa("media-controls")]),T([$(Va,T([Uu(zu(-10)),di(!c),$i("10 frames back")]),T([ai("<<")])),$(Va,T([Uu(zu(-1)),di(!c),$i("1 frame back")]),T([ai("<")])),c?$(Va,T([Uu(la),hi("play-or-pause")]),T([ai("Play")])):$(Va,T([Uu(ba),hi("play-or-pause")]),T([ai("Pause")])),$(Va,T([Uu(zu(1)),di(!c),$i("1 frame forward")]),T([ai(">")])),$(Va,T([Uu(zu(10)),di(!c),$i("10 frames forward")]),T([ai(">>")]))]))),$(yi,N,T([ai(" Frame: "+g(bi,d,"0",ht(r+1))+" / "+l+(i?" ":" @ "+ht(Ei(1e3/p(ji,1,0,0,a)))+" FPS"))])),v,b]));return $(Ya,T([Qa("viewer")]),T([s,h]))}),Ci=Vn("h1"),Ti=Vn("li"),Oi=Vn("ul"),Li=De({aD:function(){return y(Me,xe)},aK:function(n){return Pe(T([na((r=je,function(n){return r($(Xu,Zu,n))})),bu($(Ut,ke,Ie)),function(){n:for(;;)switch(n.$){case 3:return 1===n.a.j.$?Hu(Ee):Ou;case 4:if(1!==n.a.$)break n;return $(Tu,2e3,Se(Fe));default:break n}return Ou}()]));var r},aM:Ua,aN:function(i){return $(Ya,T([Qa("app")]),T([function(){switch(i.$){case 0:return ai("Disconnected");case 1:return ai("Connected");case 2:return ai("Compiling ...");case 4:return function(n){switch(n.$){case 0:var r=n.a;return $(Ya,N,T([$(Ci,N,T([ai("Compilation failed")])),$(gi,N,T([ai(r)]))]));case 1:return $(Ya,N,T([ai("Failed to establish connection. Possible causes include: "),$(Oi,N,T([$(Ti,N,T([ai("The reanimate script is not running")])),$(Ti,N,T([ai("At most one viewer window can connect at time. Maybe there's another browser window/tab already connected?")]))]))]));case 2:return ai("Failed to decode Port message. The error was: "+_t(n.a));default:return ai("Unexpected message: "+n.a)}}(i.a);default:var n=i.a.z,r=i.a.u,t=i.a.p,e=i.a.D,u=i.a.G,a=i.a.E;return f(_i,e,t,n,r,u,a,!i.a.j.$)}}()]))}});Qu={Main:{init:Li(Qt(0))(0)}},n.Elm?function n(r,t){for(var e in t)e in r?"init"==e?F(6):n(r[e],t[e]):r[e]=t[e]}(n.Elm,Qu):n.Elm=Qu}(this); \ No newline at end of file diff --git a/viewer-elm/package-lock.json b/viewer-elm/package-lock.json index 0b8b51d..186da44 100644 --- a/viewer-elm/package-lock.json +++ b/viewer-elm/package-lock.json @@ -5,14 +5,21 @@ "requires": true, "dependencies": { "ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "requires": { - "fast-deep-equal": "^2.0.1", + "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" + }, + "dependencies": { + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + } } }, "ansi-regex": { @@ -178,12 +185,6 @@ "readdirp": "^3.1.1" } }, - "chownr": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", - "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==", - "dev": true - }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -423,11 +424,6 @@ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" - }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", @@ -458,30 +454,10 @@ } }, "follow-redirects": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.9.0.tgz", - "integrity": "sha512-CRcPzsSIbXyVDl0QI01muNDu69S8trU4jArW9LpOt2WtC6LyUJetcIrmfHsRBx7/Jb6GHJUiuqyYxPooFfNt6A==", - "dev": true, - "requires": { - "debug": "^3.0.0" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } + "version": "1.14.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", + "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", + "dev": true }, "forever-agent": { "version": "0.6.1", @@ -538,9 +514,9 @@ } }, "glob-parent": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", - "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { "is-glob": "^4.0.1" @@ -727,9 +703,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "lru-cache": { @@ -773,9 +749,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "minipass": { @@ -950,9 +926,9 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" }, "range-parser": { "version": "1.2.1", @@ -1169,20 +1145,41 @@ "dev": true }, "tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "version": "4.4.19", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", + "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", "dev": true, "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" + "chownr": "^1.1.4", + "fs-minipass": "^1.2.7", + "minipass": "^2.9.0", + "minizlib": "^1.3.3", + "mkdirp": "^0.5.5", + "safe-buffer": "^5.2.1", + "yallist": "^3.1.1" }, "dependencies": { + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/viewer-elm/package.json b/viewer-elm/package.json index 4f9dbae..1645bcd 100644 --- a/viewer-elm/package.json +++ b/viewer-elm/package.json @@ -18,6 +18,6 @@ "elm-format": "^0.8.2", "elm-live": "^4.0.1", "uglify-js": "^3.7.2", - "minimist": ">=1.2.2" + "minimist": ">=1.2.6" } } diff --git a/viewer-elm/src/Main.elm b/viewer-elm/src/Main.elm index 798581d..2a3b5cc 100644 --- a/viewer-elm/src/Main.elm +++ b/viewer-elm/src/Main.elm @@ -378,7 +378,7 @@ frameView bestFrame frameIndex frameCount frames showingHelp frameDeltas isPause image = case bestFrame of Just svgUrl -> - Html.img [ src svgUrl ] [] + Html.img [ src ("file://" ++ svgUrl) ] [] Nothing -> Html.text "" diff --git a/windows/Detach.hs b/windows/Detach.hs new file mode 100644 index 0000000..90b3a10 --- /dev/null +++ b/windows/Detach.hs @@ -0,0 +1,10 @@ +module Detach (detach) where + +import Control.Monad +import Control.Concurrent + +detach :: IO () -> IO Bool +detach action = do + void $ forkIO action + threadDelay (10^(6::Int)) + return False diff --git a/windows/POVRay.hs b/windows/POVRay.hs new file mode 100644 index 0000000..f2fa004 --- /dev/null +++ b/windows/POVRay.hs @@ -0,0 +1,50 @@ +module POVRay + ( povrayApp + , runPOVRay + ) where + +import Reanimate.Misc (requireExecutable, runCmd) +import System.IO (hClose, hPutStrLn) +import System.IO.Temp (withSystemTempFile) + +-- | Name of the POV-Ray executable +povrayApp :: String +povrayApp = "pvengine64" -- Assumes 64 bit Windows + +-- | Run the POV-Ray executable with arguments +runPOVRay :: [String] -> IO () +runPOVRay args = do + exec <- requireExecutable povrayApp + let exec' = '"' : exec ++ "\"" -- Wrap exec in "" because the path is likely + -- to includes spaces + args' = "/EXIT" : args -- Note [/EXIT special command-line option] + command = concatMap (' ':) (exec' : args') + -- Note [Use of a batch file] + withSystemTempFile "pvcommand.bat" $ \path h -> do + hPutStrLn h command + hClose h + runCmd path [] + +{- +Note [/EXIT special command-line option] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +"The /EXIT command tells POV-Ray to perform the render given by the other +command-line options, combined with previously-set options such as internal +command-line settings, INI file, source file, and so forth, and then to exit. By +default, if this switch is not present, POV-Ray for Windows will remain running +after the render is complete. This switch only applies to renders started by +other options on the command-line. It will not affect renders started manually +from within POV-Ray for Windows itself." +source: https://www.povray.org/documentation/view/3.6.1/603/ + +Note [Use of batch file] +~~~~~~~~~~~~~~~~~~~~~~~~ + +POV-Ray for Windows assumes that anything on the command-line that is not a +switch is the name of an INI file. Switches are preceded by a plus or minus. +However, System.Process.createProcess_ wraps all arguments in "". POV_Ray for +Windows appears to interpret a switch wrapped in "" as the name of an INI file. + +The work around used here is to write the POV-Ray command line to a batch file. +-}