pax_global_header00006660000000000000000000000064147563424350014527gustar00rootroot0000000000000052 comment=4e4c811d19e03ba4f02932e5075615989d9e1f33 patat-0.14.1.0/000077500000000000000000000000001475634243500130615ustar00rootroot00000000000000patat-0.14.1.0/.github/000077500000000000000000000000001475634243500144215ustar00rootroot00000000000000patat-0.14.1.0/.github/workflows/000077500000000000000000000000001475634243500164565ustar00rootroot00000000000000patat-0.14.1.0/.github/workflows/build-and-release.yml000066400000000000000000000031251475634243500224570ustar00rootroot00000000000000on: ['push'] jobs: build: name: Build on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macOS-latest] ghc: ["9.4.8"] steps: - run: echo "$HOME/.local/bin" >> $GITHUB_PATH - if: matrix.os == 'macOS-latest' run: 'brew install pkg-config' - run: 'echo version=${GITHUB_REF#refs/tags/} >>$GITHUB_OUTPUT' id: get_version - uses: actions/checkout@v4 - uses: haskell-actions/setup@v2 id: setup with: ghc-version: ${{ matrix.ghc }} - uses: actions/cache@v3 with: path: ${{ steps.setup.outputs.cabal-store }} key: ${{ runner.os }}-${{ matrix.ghc }}-v9-${{ hashFiles('patat.cabal') }} restore-keys: | ${{ runner.os }}-${{ matrix.ghc }}-v9- - run: make build id: build - run: make test - if: startsWith(github.ref, 'refs/tags') run: make artifact env: PATAT_TAG: ${{ steps.get_version.outputs.version }} - uses: actions/upload-artifact@v4 if: startsWith(github.ref, 'refs/tags') with: path: artifacts/* name: artifacts-${{ runner.os }}-${{ matrix.ghc }} release: name: Release needs: build runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags') steps: - uses: actions/download-artifact@v4 with: pattern: artifacts-* - run: ls -R - run: 'sha256sum artifacts-*/patat-*' - uses: softprops/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: files: 'artifacts-*/patat-*' patat-0.14.1.0/.github/workflows/haskell-ci.yml000066400000000000000000000200221475634243500212110ustar00rootroot00000000000000# This GitHub workflow config has been generated by a script via # # haskell-ci 'github' 'patat.cabal' # # To regenerate the script (for example after adjusting tested-with) run # # haskell-ci regenerate # # For more information, see https://github.com/andreasabel/haskell-ci # # version: 0.17.20231112 # # REGENDATA ("0.17.20231112",["github","patat.cabal"]) # name: Haskell-CI on: - push - pull_request jobs: linux: name: Haskell-CI - Linux - ${{ matrix.compiler }} runs-on: ubuntu-20.04 timeout-minutes: 60 container: image: buildpack-deps:focal continue-on-error: ${{ matrix.allow-failure }} strategy: matrix: include: - compiler: ghc-9.8.1 compilerKind: ghc compilerVersion: 9.8.1 setup-method: ghcup allow-failure: false - compiler: ghc-9.6.3 compilerKind: ghc compilerVersion: 9.6.3 setup-method: ghcup allow-failure: false - compiler: ghc-9.4.8 compilerKind: ghc compilerVersion: 9.4.8 setup-method: ghcup allow-failure: false - compiler: ghc-9.2.8 compilerKind: ghc compilerVersion: 9.2.8 setup-method: ghcup allow-failure: false fail-fast: false steps: - name: apt run: | apt-get update apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5 mkdir -p "$HOME/.ghcup/bin" curl -sL https://downloads.haskell.org/ghcup/0.1.20.0/x86_64-linux-ghcup-0.1.20.0 > "$HOME/.ghcup/bin/ghcup" chmod a+x "$HOME/.ghcup/bin/ghcup" "$HOME/.ghcup/bin/ghcup" install ghc "$HCVER" || (cat "$HOME"/.ghcup/logs/*.* && false) "$HOME/.ghcup/bin/ghcup" install cabal 3.10.2.0 || (cat "$HOME"/.ghcup/logs/*.* && false) env: HCKIND: ${{ matrix.compilerKind }} HCNAME: ${{ matrix.compiler }} HCVER: ${{ matrix.compilerVersion }} - name: Set PATH and environment variables run: | echo "$HOME/.cabal/bin" >> $GITHUB_PATH echo "LANG=C.UTF-8" >> "$GITHUB_ENV" echo "CABAL_DIR=$HOME/.cabal" >> "$GITHUB_ENV" echo "CABAL_CONFIG=$HOME/.cabal/config" >> "$GITHUB_ENV" HCDIR=/opt/$HCKIND/$HCVER HC=$("$HOME/.ghcup/bin/ghcup" whereis ghc "$HCVER") HCPKG=$(echo "$HC" | sed 's#ghc$#ghc-pkg#') HADDOCK=$(echo "$HC" | sed 's#ghc$#haddock#') echo "HC=$HC" >> "$GITHUB_ENV" echo "HCPKG=$HCPKG" >> "$GITHUB_ENV" echo "HADDOCK=$HADDOCK" >> "$GITHUB_ENV" echo "CABAL=$HOME/.ghcup/bin/cabal-3.10.2.0 -vnormal+nowrap" >> "$GITHUB_ENV" HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))') echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV" echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV" echo "ARG_BENCH=--enable-benchmarks" >> "$GITHUB_ENV" echo "HEADHACKAGE=false" >> "$GITHUB_ENV" echo "ARG_COMPILER=--$HCKIND --with-compiler=$HC" >> "$GITHUB_ENV" echo "GHCJSARITH=0" >> "$GITHUB_ENV" env: HCKIND: ${{ matrix.compilerKind }} HCNAME: ${{ matrix.compiler }} HCVER: ${{ matrix.compilerVersion }} - name: env run: | env - name: write cabal config run: | mkdir -p $CABAL_DIR cat >> $CABAL_CONFIG <> $CABAL_CONFIG < cabal-plan.xz echo 'f62ccb2971567a5f638f2005ad3173dba14693a45154c1508645c52289714cb2 cabal-plan.xz' | sha256sum -c - xz -d < cabal-plan.xz > $HOME/.cabal/bin/cabal-plan rm -f cabal-plan.xz chmod a+x $HOME/.cabal/bin/cabal-plan cabal-plan --version - name: checkout uses: actions/checkout@v4 with: path: source - name: initial cabal.project for sdist run: | touch cabal.project echo "packages: $GITHUB_WORKSPACE/source/." >> cabal.project cat cabal.project - name: sdist run: | mkdir -p sdist $CABAL sdist all --output-dir $GITHUB_WORKSPACE/sdist - name: unpack run: | mkdir -p unpacked find sdist -maxdepth 1 -type f -name '*.tar.gz' -exec tar -C $GITHUB_WORKSPACE/unpacked -xzvf {} \; - name: generate cabal.project run: | PKGDIR_patat="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/patat-[0-9.]*')" echo "PKGDIR_patat=${PKGDIR_patat}" >> "$GITHUB_ENV" rm -f cabal.project cabal.project.local touch cabal.project touch cabal.project.local echo "packages: ${PKGDIR_patat}" >> cabal.project echo "package patat" >> cabal.project echo " ghc-options: -Werror=missing-methods" >> cabal.project cat >> cabal.project <> cabal.project.local cat cabal.project cat cabal.project.local - name: dump install plan run: | $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dry-run all cabal-plan - name: restore cache uses: actions/cache/restore@v3 with: key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }} path: ~/.cabal/store restore-keys: ${{ runner.os }}-${{ matrix.compiler }}- - name: install dependencies run: | $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks --dependencies-only -j2 all $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dependencies-only -j2 all - name: build w/o tests run: | $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all - name: build run: | $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --write-ghc-environment-files=always - name: tests run: | $CABAL v2-test $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --test-show-details=direct - name: cabal check run: | cd ${PKGDIR_patat} || false ${CABAL} -vnormal check - name: haddock run: | $CABAL v2-haddock --disable-documentation --haddock-all $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all - name: unconstrained build run: | rm -f cabal.project.local $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all - name: save cache uses: actions/cache/save@v3 if: always() with: key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }} path: ~/.cabal/store patat-0.14.1.0/.gitignore000066400000000000000000000001131475634243500150440ustar00rootroot00000000000000*.o *.hi extra/make-man extra/patat.1 .stack-work dist tags dist-newstyle/ patat-0.14.1.0/CHANGELOG.md000066400000000000000000000353511475634243500147010ustar00rootroot00000000000000# Changelog ## 0.14.1.0 (2024-02-22) * Add image support for WezTerm (#177). * Fix image support in Kitty (#179). * Fix image scroll issue for iTerm2. ## 0.14.0.0 (2024-02-06) * Align based on final layout for incremental lists and other fragments (#174). This avoids lists "jumping around" as they are revealed when using `auto` `margins`. * Rename `fragment` to `reveal` in eval settings. `fragment` will continue to be available for backwards-compatibility. * Use a temporary file to atomically write speaker notes. We weren't writing the file all-at-once before, so if you were using a simple tool like `tail -F` before, this could cause some speaker notes to not be displayed. * Refactor the internal AST to use our own derivation of the Pandoc AST. This is a major rework of the internals but should not cause any changes visible to the user. ## 0.13.0.0 (2024-10-30) * Incrementally display output of `eval` commands (#132) Rather than waiting for the process to complete and then displaying its output, `patat` now fetches the `stdout` and `stderr` as it becomes available and refreshes the display. This means that by default, **stderr is now displayed as well**. To disable displaying `stderr`, you can add `stderr: false` to the eval configuration, e.g.: ```yaml patat: eval: bash: command: bash stderr: false ``` ## 0.12.0.1 (2024-09-28) * Fix width of code blocks when using wide characters (#171) * Bump `pandoc` upper bound to 3.3. ## 0.12.0.0 (2024-02-27) * Render tabs in code blocks by expanding them to spaces. The amount of spaces a tab character aligns to is customizable using `tabStop`, e.g. `tabStop: 8`. The default is 4. * Rename eval.wrap to eval.container (#167) `wrap` is used at the top-level of settings for wrapping at a certain column, and inside `eval` to determine the type in which the result is "wrapped". Using the same name for both is confusing, so this adds `eval.container` as the new name for `eval.wrap`. `eval.wrap` will continue to be supported for the forseeable future, but its use will be discouraged. This also changes the values (again keeping the original ones for backwards-compat), so the complete changes to a configuration would be: - `wrap: code` becomes `container: code` - `wrap: raw` becomes `container: none` - `wrap: rawInline` becomes `container: inline` * Add a `type: matrix` transition effect, loosely inspired by the 1999 science fiction movie. ## 0.11.0.0 (2024-02-14) * Support wrapping at a specific column (#164) Using a specific wrap column, e.g. `wrap: 60`, works well together with `auto` margins (see below). * Support centering content with auto margins (#164) Configuration is done through the existing `margins` setting. To vertically center content, use `top: auto`. To horizontally center content, use both `left: auto` and `right: auto`. For example: ```markdown --- title: Centered presentation author: John Doe patat: margins: left: auto right: auto top: auto ... Hello world ``` Setting `wrap: true` is recommended when vertically centering content if there are any lines that are too wide for the terminal. ## 0.10.2.0 (2023-11-25) * Add eval.wrap option This adds a new `wrap` section to the `eval` configuration. By default, the output is wrapped in a code block again with the original syntax highlighting. You can customize this behaviour by setting `wrap` to: * `code`: the default setting. * `raw`: no formatting applied. * `rawInline`: no formatting applied and no trailing newline. You can use `rawInline` to draw graphics. In order to do that, for example, we could configure `kitten` code snippets to evaluate using [Kitty]'s command `icat`. This uses the `rawInline` code setting to ensure that the resulting output is not wrapped in a code block, and the `fragment` and `replace` settings immediately replace the snippet: --- patat: eval: kitten: command: sed 's/^/kitten /' | bash replace: true fragment: false wrap: rawInline ... See, for example: ```kitten icat --align left dank-meme.jpg ``` [Kitty]: https://sw.kovidgoyal.net/kitty/ ## 0.10.1.1 (2023-10-18) * Fix issues in text wrapping when starting a transition This could show transitions using different wrapping or dropped characters when a line extends past the terminal width. ## 0.10.1.0 (2023-10-15) * Add dissolve transition effect (#150) * Add random transitions (#151) Set transition `type` to `random` to randomly sample transition effects ## 0.10.0.0 (2023-10-12) * Add transition effects (#149) This adds a framework for setting transition effects in between slides. Only a single transition type is implemented at this point, `slideLeft`. Example configuration: patat: transition: type: slideLeft frames: 24 # Optional duration: 1 # Seconds, optional * Allow overriding certain settings in slides (#148) Configuration was typically done in the metadata block of the input file, or in a per-user configuration. These settings are applied to the entire presentation. We now allow selectively overriding these settings on a per-slide basis, by adding one or more config blocks to those slides. Config blocks are comments that start with `config:`. They can be placed anywhere in the slide. # This is a normal slide Normal slide content # This slide has a different colour header Wow, how did that happen? * Allow configuring top margin (#147) ## 0.9.2.0 (2023-09-26) * Read configuration from XDG standard directory (#146) The per-user patat configuration file was `$HOME/.patat.yaml`, which does not follow the XDG standard. We now support `$XDG_CONFIG_DIRECTORY/patat/config.yaml` (typically `$XDG_CONFIG_DIRECTORY` is set to `$HOME/.config`) which is compliant with the standard. Note that `$HOME/.patat.yaml` is still supported for backward-compatibility, but anything in `$XDG_CONFIG_DIRECTORY` takes precedence. * Support filenames in bash completion (#145) (#126) ## 0.9.1.0 (2023-09-25) * Fall back to forcing UTF-8 if decoding fails (#144) (#127) When we try to read a file that is encoded in UTF-8, and the system locale is not set to UTF-8, the GHC runtime system will throw an error. While this typically indicates that the user should update their system locale using e.g. the `LANG` environment variable, we want to provide a good initial experience for people unfamiliar with this, and in 2023 it's reasonable to assume files may be encoded in UTF-8. * Dependency updates: - Bump `skylighting` upper bound to 0.15 (#143) ## 0.9.0.0 (2023-09-13) * Add proper support for speaker notes (#142) You can configure `patat` to write the speaker notes for the current slide to a file whenever the slide changes: patat: speakerNotes: file: /tmp/notes.txt Then, you can display these in a second terminal (presumably on a second monitor) by just displaying this file whenever it changes. [entr] is one way to do that: echo /tmp/notes.txt | entr -s 'clear; cat /tmp/notes.txt' [entr]: http://eradman.com/entrproject/ Alternatively, just use a second `patat` instance with `--watch` enabled: patat -w /tmp/notes.txt * Add support for showing plain text files (#141) This isn't super useful on its own, it's meant to support speaker notes. * Add syntaxDefinitions to settings (#140) This allows users to add custom kate highlighting XML files in the settings: --- patat: syntaxDefinitions: - 'impurescript.xml' ... Here is some *im*purescript: ```impurescript ... ``` ## 0.8.9.0 (2023-06-27) * Apply block quote theming to entire block (#119) (#111) * Fix table header theming (#128) * Dependency updates: - `aeson` to 2.1 - `optparse-applicative` to 0.18 - `pandoc` to 3.1 - `pandoc-types` to 1.23 - `text` to 2.0 - `time` to 1.12 ## 0.8.8.0 (2022-10-26) * Allow hiding slide number (contribution by Paweł Dybiec) * Support additional markdown extensions (contribution by Spreadcat) * Dependency updates: - `aeson` to 2.0 - `ansi-terminal` to 0.11 - `base64-bytestring` to 1.2 - `bytestring` to 0.11 - `optparse-applicative` to 1.16 - `pandoc` to 2.19 - `skylighting` to 0.13 ## 0.8.7.0 (2021-03-12) * Fix alignment and display of CJK characters in presentation title, author and tables * Add support for showing images in Kitty terminal * Search in `$PATH` for `w3mimgdisplay` * Bump `pandoc` dependency to 2.11 * Refactor `Patat.Presentation.Display` module to make it pure ## 0.8.6.1 (2020-09-18) * Fix issue with laziness for evaluted code blocks, they should only be evaluated when we actually want to show them * Bump stack resolver to `lts-16.9` ## 0.8.6.0 (2020-09-11) * Allow evaluating code blocks (see README for more info) * Refactor implementation of fragments * Add breadcrumbs to title based on headers * Error out when YAML parsing fails ## 0.8.5.0 (2020-06-29) * Bump `pandoc` dependency to 2.9 * Switch to `goldplate` for testing ## 0.8.4.3 (2020-01-21) * Fix Haddock syntax in some comments (contribution by Asad Saeeduddin) ## 0.8.4.2 (2020-01-18) * Add builds for Mac OS * Refactor CircleCI config & Makefile ## 0.8.4.1 (2019-10-29) * Bump CircleCI configuration * Bump release script * Add slide seeking to `--help` output ## 0.8.4.0 (2019-10-09) * Add slide seeking (enter slide number + `enter`) * Fix turning tty echo off/on during presentation * Run `w3mimgdisplay` cleanup action, fixing image issues on some terminals ## 0.8.3.0 (2019-09-07) * Fix test failure again, and ensure that it works for multiple pandoc versions by slightly modifying test input * Include pandoc version info in `patat --version` ## 0.8.2.5 (2019-08-12) * Fix test failure caused by slightly different pandoc output for lists ## 0.8.2.4 (2019-08-12) * Bump `optparse-applicative` upper bound to 0.16 * Bump `skylighting` upper bound to 0.9 ## 0.8.2.3 (2019-06-25) * Bump upper `pandoc` dependency to 2.8 ## 0.8.2.2 (2019-02-04) * Bump lower `base` dependency to 4.8 ## 0.8.2.1 (2019-02-03) * Bump `pandoc` to 2.6 * Bump `ansi-terminal` to 0.10 ## 0.8.2.0 (2019-01-24) * GHC 7.8 compatibility ## 0.8.1.3 (2019-01-24) * Bump `pandoc` to 2.4 * Bump `yaml` to 0.11 ## 0.8.1.2 (2018-10-29) * Work around test failure caused by slightly different syntax highlighting in different pandoc versions ## 0.8.1.1 (2018-10-26) * Tickle CircleCI cache ## 0.8.1.0 (2018-10-26) * Add support for italic ansi code in themes * Fix centered titles not being centered (contribution by Hamza Haiken) ## 0.8.0.0 (2018-08-31) * Themed border rendering improvements (contribution by Hamza Haiken) * Add support for margins (contribution by Hamza Haiken) * Add RGB colour support for themes (contribution by Hamza Haiken) * Add experimental images support * Add images support for iTerm2 (contribution by @2mol) ## 0.7.2.0 (2018-05-08) * GHC 8.4 compatibility ## 0.7.1.0 (2018-05-08) * GHC 8.4 compatibility ## 0.7.0.0 (2018-05-04) * Support HTML-style comments ## 0.6.1.2 (2018-04-30) * Bump `pandoc` to 2.2 ## 0.6.1.1 (2018-04-27) * Bump `aeson` to 1.3 * Bump `skylighting` to 0.7 * Bump `time` to 1.9 * Bump `ansi-terminal` to 0.8 ## 0.6.1.0 (2018-01-28) * Bump `skylighting` to 0.6 * Bump `pandoc` to 2.1 * Bump `ansi-terminal` to 0.7 ## 0.6.0.1 (2017-12-24) * Automatically upload linux binary to GitHub ## 0.6.0.0 (2017-12-19) * Make pandoc extensions customizable in the configuration * Bump `pandoc` to 2.0 ## 0.5.2.2 (2017-06-14) * Add `network-uri` dependency to fix travis build ## 0.5.2.1 (2017-06-14) * Bump `optparse-applicative-0.14` dependency ## 0.5.2.0 (2017-05-16) * Add navigation using `PageUp` and `PageDown`. * Use `skylighting` instead of deprecated `highlighting-kate` for syntax highlighting. ## 0.5.1.2 (2017-04-26) * Make build reproducible even if timezone changes (patch by Félix Sipma) ## 0.5.1.1 (2017-04-23) * Include `README` in `Extra-source-files` so it gets displayed on Hackage ## 0.5.1.0 (2017-04-23) * Bump `aeson-1.2` dependency * Fix vertical alignment of title slides * Fix wrapping issue with inline code at end of line * Add bash-completion script generation to Makefile ## 0.5.0.0 (2017-02-06) * Add a `slideLevel` option & autodetect it. This changes the way `patat` splits slides. For more information, see the `README` or the `man` page. If you just want to get the old behavior back, just add: --- patat: slideLevel: 1 ... To the top of your presentation. * Clear the screen when finished with the presentation. ## 0.4.7.1 (2017-01-22) * Bump `directory-1.3` dependency * Bump `time-1.7` dependency ## 0.4.7.0 (2017-01-20) * Bump `aeson-1.1` dependency * Parse YAML for settings using `yaml` instead of pandoc * Clarify watch & autoAdvance combination in documentation. ## 0.4.6.0 (2016-12-28) * Redraw the screen on unknown commands to prevent accidental typing from showing up. * Make the cursor invisible during the presentation. * Move the footer down one more line to gain some screen real estate. ## 0.4.5.0 (2016-12-05) * Render the date in a locale-independent manner (patch by Daniel Shahaf). ## 0.4.4.0 (2016-12-03) * Force the use of UTF-8 when generating the man page. ## 0.4.3.0 (2016-12-02) * Use `SOURCE_DATE_EPOCH` if it is present instead of getting the date from `git log`. ## 0.4.2.0 (2016-12-01) * Fix issues with man page generation on Travis. ## 0.4.1.0 (2016-12-01) * Fix compatibility with `pandoc-1.18` and `pandoc-1.19`. * Add a man page. ## 0.4.0.0 (2016-11-15) * Add configurable auto advancing. * Support fragmented slides. ## 0.3.3.0 (2016-10-31) * Add a `--version` flag. * Add support for `pandoc-1.18` which includes a new `LineBlock` element. ## 0.3.2.0 (2016-10-20) * Keep running even if errors are encountered during reload. ## 0.3.1.0 (2016-10-18) * Fix compilation with `lts-6.22`. ## 0.3.0.0 (2016-10-17) * Add syntax highlighting support. * Fixed slide clipping after reload. ## 0.2.0.0 (2016-10-13) * Add theming support. * Fix links display. * Add support for wrapping. * Allow org mode as input format. ## 0.1.0.0 (2016-10-02) * Upload first version from hotel wifi in Kalaw. patat-0.14.1.0/LICENSE000066400000000000000000000431731475634243500140760ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. patat-0.14.1.0/Makefile000066400000000000000000000041361475634243500145250ustar00rootroot00000000000000ARCH=$(shell uname -m) UNAME=$(shell uname | tr 'A-Z' 'a-z') PATAT_BINARY=$(HOME)/.local/bin/patat PATAT_TAG?=v$(shell sed -n 's/^Version: *//p' *.cabal) PATAT_PACKAGE=patat-$(PATAT_TAG)-$(UNAME)-$(ARCH) UPX_VERSION=3.94 UPX_NAME=upx-$(UPX_VERSION)-amd64_$(UNAME) UPX_BINARY=$(HOME)/.local/bin/upx ifeq ($(UNAME), darwin) ARCHIVE=zip ARCHIVE_CREATE=zip -r ARCHIVE_EXTRACT=unzip else ARCHIVE=tar.gz ARCHIVE_CREATE=tar czf ARCHIVE_EXTRACT=tar xvzf endif SOURCE_DATE_EPOCH?=$(shell git log -1 --format=%cd --date=unix) ifeq ($(UNAME), darwin) COMPRESS_BIN_DEPS= COMPRESS_BIN=ls else COMPRESS_BIN_DEPS=$(UPX_BINARY) COMPRESS_BIN=upx endif # Default target. .PHONY: build build: $(PATAT_BINARY) # When we want to do a release. .PHONY: artifact artifact: $(PATAT_PACKAGE).$(ARCHIVE) mkdir -p artifacts cp $(PATAT_PACKAGE).$(ARCHIVE) artifacts/ $(PATAT_PACKAGE).$(ARCHIVE): $(PATAT_BINARY) extra/patat.1 $(COMPRESS_BIN_DEPS) mkdir -p $(PATAT_PACKAGE) cp $(PATAT_BINARY) $(PATAT_PACKAGE)/ $(COMPRESS_BIN) $(PATAT_PACKAGE)/patat cp README.md $(PATAT_PACKAGE)/ cp CHANGELOG.md $(PATAT_PACKAGE)/ cp LICENSE $(PATAT_PACKAGE)/ cp extra/patat.1 $(PATAT_PACKAGE)/ $(ARCHIVE_CREATE) $(PATAT_PACKAGE).$(ARCHIVE) $(PATAT_PACKAGE) $(PATAT_BINARY): cabal install --installdir="$(dir $(PATAT_BINARY))" # UPX is used to compress the resulting binary. We currently don't use this on # Mac OS. $(UPX_BINARY): curl -Lo /tmp/$(UPX_NAME).tar.xz \ https://github.com/upx/upx/releases/download/v$(UPX_VERSION)/$(UPX_NAME).tar.xz cd /tmp && tar xf $(UPX_NAME).tar.xz mv /tmp/$(UPX_NAME)/upx $(UPX_BINARY) upx --version # Man page. extra/patat.1: README.md $(PATAT_BINARY) SOURCE_DATE_EPOCH="$(SOURCE_DATE_EPOCH)" cabal exec -- patat-make-man >$@ # Bash completion. extra/patat.bash-completion: $(PATAT_BINARY) cabal exec -- patat --bash-completion-script patat >$@ .PHONY: completion completion: extra/patat.bash-completion .PHONY: man man: extra/patat.1 # Also check if we can generate the manual. .PHONY: test test: man cabal test .PHONY: clean clean: rm -f extra/patat.1 rm -f extra/make-man rm -f extra/patat.bash-completion patat-0.14.1.0/README.md000066400000000000000000000555151475634243500143530ustar00rootroot00000000000000🥔 patat ======== ![CI](https://github.com/jaspervdj/patat/actions/workflows/haskell-ci.yml/badge.svg) [![Hackage](https://img.shields.io/hackage/v/patat.svg)](https://hackage.haskell.org/package/patat) [![GitHub tag](https://img.shields.io/github/tag/jaspervdj/patat.svg)]() `patat` (**P**resentations **A**top **T**he **A**NSI **T**erminal) is a feature-rich presentation tool that runs in the terminal. - Understands most markdown extensions and many other input formats (rST, Org-mode...) by building on top of [Pandoc]. - [Evaluate code snippets and show the result](#evaluating-code). - Syntax highlighting for nearly one hundred languages generated from [Kate] syntax files. - [Automatically reload](#running) your slides as you edit them. - Display [speaker notes](#speaker-notes) in a second window or monitor. - [Incremental slide display](#fragmented-slides). - Experimental [images](#images) support. - [Transition effects](#transitions). - Supports [smart slide splitting](#input-format). - [Auto advancing](#auto-advancing) with configurable delay. - [Centering](#centering) and [re-wrapping](#line-wrapping) text to terminal width with proper indentation. - [Theming](#theming) support including 24-bit RGB. - Highly portable as it only requires an ANSI terminal as opposed to something like `ncurses`. ![screenshot](extra/demo.gif?raw=true) [Kate]: https://kate-editor.org/ [Pandoc]: http://pandoc.org/ Table of Contents ----------------- - [Table of Contents](#table-of-contents) - [Installation](#installation) - [Pre-built packages](#pre-built-packages) - [From source](#from-source) - [Running](#running) - [Options](#options) - [Controls](#controls) - [Input format](#input-format) - [Configuration](#configuration) - [Line wrapping](#line-wrapping) - [Tab stop](#tab-stop) - [Margins](#margins) - [Auto advancing](#auto-advancing) - [Advanced slide splitting](#advanced-slide-splitting) - [Fragmented slides](#fragmented-slides) - [Theming](#theming) - [Syntax Highlighting](#syntax-highlighting) - [Pandoc Extensions](#pandoc-extensions) - [Images](#images) - [Breadcrumbs](#breadcrumbs) - [Slide numbers](#slide-numbers) - [Evaluating code](#evaluating-code) - [Speaker notes](#speaker-notes) - [Transitions](#transitions) - [Trivia](#trivia) Installation ------------ ### Pre-built packages Linux: - [Archlinux](https://aur.archlinux.org/packages/patat-bin) - [Debian](https://packages.debian.org/unstable/patat) - [Fedora](https://src.fedoraproject.org/rpms/patat) - [NixOS](https://search.nixos.org/packages?show=haskellPackages.patat) - [openSUSE](https://build.opensuse.org/package/show/openSUSE:Factory:ARM/patat) - [Ubuntu](https://packages.ubuntu.com/bionic/patat) Mac OS: - [Homebrew](https://formulae.brew.sh/formula/patat) You can also find generic Linux and Mac OS binaries here: . ### From source `patat` is also available from [Hackage]. You can install it using [cabal] by running `cabal install patat`. [cabal]: https://www.haskell.org/cabal/ [Hackage]: https://hackage.haskell.org/package/patat Running ------- `patat [*options*] file` Options ------- `-w`, `--watch` : If you provide the `--watch` flag, `patat` will watch the presentation file for changes and reload automatically. This is very useful when you are writing the presentation. `-f`, `--force` : Run the presentation even if the terminal claims it does not support ANSI features. `-d`, `--dump` : Just dump all the slides to stdout. This is useful for debugging. `--version` : Display version information. Controls -------- - **Next slide**: `space`, `enter`, `l`, `→`, `PageDown` - **Previous slide**: `backspace`, `h`, `←`, `PageUp` - **Go forward 10 slides**: `j`, `↓` - **Go backward 10 slides**: `k`, `↑` - **First slide**: `0` - **Last slide**: `G` - **Jump to slide N**: `N` followed by `enter` - **Reload file**: `r` - **Quit**: `q` The `r` key is very useful since it allows you to preview your slides while you are writing them. You can also use this to fix artifacts when the terminal is resized. Input format ------------ The input format can be anything that Pandoc supports. Plain markdown is usually the most simple solution: ```markdown --- title: This is my presentation author: Jane Doe ... # This is a slide Slide contents. Yay. --- # Important title Things I like: - Markdown - Haskell - Pandoc ``` Horizontal rulers (`---`) are used to split slides. However, if you prefer not use these since they are a bit intrusive in the markdown, you can also start every slide with a header. In that case, the file should not contain a single horizontal ruler. `patat` will pick the most deeply nested header (e.g. `h2`) as the marker for a new slide. Headers _above_ the most deeply nested header (e.g. `h1`) will turn into title slides, which are displayed as as a slide containing only the centered title. This means the following document is equivalent to the one we saw before: ```markdown --- title: This is my presentation author: Jane Doe ... # This is a slide Slide contents. Yay. # Important title Things I like: - Markdown - Haskell - Pandoc ``` And that following document contains three slides: a title slide, followed by two content slides. ```markdown --- title: This is my presentation author: Jane Doe ... # Chapter 1 ## This is a slide Slide contents. Yay. ## Another slide Things I like: - Markdown - Haskell - Pandoc ``` For more information, see [Advanced slide splitting](#advanced-slide-splitting). Configuration ------------- `patat` is fairly configurable. The configuration is done using [YAML]. There are several places where you can put your configuration. 1. For per-user configuration you can use `$XDG_CONFIG_DIRECTORY/patat/config.yaml` (typically `$HOME/.config/patat/config.yaml`) or `$HOME/.patat.yaml`, for example: ```yaml slideNumber: false ``` 2. In the presentation file itself, using the [Pandoc metadata header]. These settings take precedence over anything specified in the per-user configuration file. They must be placed in a `patat:` section, so they don't conflict with metadata: ```markdown --- title: Presentation with options author: John Doe patat: slideNumber: false ... Hello world. ``` 3. In version 0.10 and later slides can be individually configured. Within a slide, using a comment starting with ` Slide numbers are turned off here. ``` The following settings can **not** be set in a slide configuration block, and doing so will result in an error: - `autoAdvanceDelay` - `eval` - `images` - `incrementalLists` - `pandocExtensions` - `slideLevel` - `speakerNotes` [YAML]: http://yaml.org/ [Pandoc metadata header]: http://pandoc.org/MANUAL.html#extension-yaml_metadata_block ### Line wrapping Line wrapping can be enabled by setting `wrap: true` in the configuration. This will re-wrap all lines to fit the terminal width better. You can also ask patat to wrap at a specific column using `wrap: number`, e.g. `wrap: 60`. ### Tab stop In version 0.12 and later, the amount of spaces a `\t` character in a code block aligns to can be customized by setting `tabStop: number` in the configuration. The default is `4`. ### Margins Margins can be enabled by setting a `margins` entry in the configuration: ```markdown --- title: Presentation with margins author: John Doe patat: wrap: true margins: left: 10 right: 10 top: 5 ... Lorem ipsum dolor sit amet, ... ``` This example configuration will generate slides with a margin of 10 columns on the left, and it will wrap long lines 10 columns before the right side of the terminal. Additionally, there will be 5 empty lines in between the title bar and slide content. [Line wrapping](#line-wrapping) should be enabled when using non-zero `right` margin. By default, the `left` and `right` margin are set to 0, and the `top` margin is set to 1. #### Centering Version 0.11 and later can center content. - To vertically center content, use `top: auto`. - To horizontally center content, use both `left: auto` and `right: auto`. For example: ```markdown --- title: Centered presentation author: John Doe patat: margins: left: auto right: auto top: auto ... Hello world ``` [Line wrapping](#line-wrapping) is recommended when vertically centering content if there are any lines that are too wide for the terminal. ### Auto advancing By setting `autoAdvanceDelay` to a number of seconds, `patat` will automatically advance to the next slide. ```markdown --- title: Auto-advance, yes please author: John Doe patat: autoAdvanceDelay: 2 ... Hello World! --- This slide will be shown two seconds after the presentation starts. ``` Note that changes to `autoAdvanceDelay` are not picked up automatically if you are running `patat --watch`. This requires restarting `patat`. ### Advanced slide splitting You can control the way slide splitting works by setting the `slideLevel` variable. This variable defaults to the least header that occurs before a non-header, but it can also be explicitly defined. For example, in the following document, the `slideLevel` defaults to **2**: ```markdown # This is a slide ## This is a nested header This is some content ``` With `slideLevel` 2, the `h1` will turn into a "title slide", and the `h2` will be displayed at the top of the second slide. We can customize this by setting `slideLevel` manually: ```markdown --- patat: slideLevel: 1 ... # This is a slide ## This is a nested header This is some content ``` Now, we will only see one slide, which contains a nested header. ### Fragmented slides By default, slides are always displayed "all at once". If you want to reveal them fragment by fragment, there are two ways to do that. The most common case is that lists should be displayed incrementally. This can be configured by settings `incrementalLists` to `true` in the metadata block: ```markdown --- title: Presentation with incremental lists author: John Doe patat: incrementalLists: true ... - This list - is displayed - item by item ``` Setting `incrementalLists` works on _all_ lists in the presentation. To flip the setting for a specific list, wrap it in a block quote. This will make the list incremental if `incrementalLists` is not set, and it will display the list all at once if `incrementalLists` is set to `true`. This example contains a sublist which is also displayed incrementally, and then a sublist which is displayed all at once (by merit of the block quote). ```markdown --- title: Presentation with incremental lists author: John Doe patat: incrementalLists: true ... - This list - is displayed * item * by item - Or sometimes > * all at > * once ``` Another way to break up slides is to use a pagraph only containing three dots separated by spaces. For example, this slide has two pauses: ```markdown Legen . . . wait for it . . . Dary! ``` ### Theming Colors and other properties can also be changed using this configuration. For example, we can have: ```markdown --- author: 'Jasper Van der Jeugt' title: 'This is a test' patat: wrap: true theme: emph: [vividBlue, onVividBlack, italic] strong: [bold] imageTarget: [onDullWhite, vividRed] ... # This is a presentation This is _emph_ text. ![Hello](foo.png) ``` The properties that can be given a list of styles are: `blockQuote`, `borders`, `bulletList`, `codeBlock`, `code`, `definitionList`, `definitionTerm`, `emph`, `header`, `imageTarget`, `imageText`, `linkTarget`, `linkText`, `math`, `orderedList`, `quoted`, `strikeout`, `strong`, `tableHeader`, `tableSeparator`, `underline` The accepted styles are: `bold`, `italic`, `dullBlack`, `dullBlue`, `dullCyan`, `dullGreen`, `dullMagenta`, `dullRed`, `dullWhite`, `dullYellow`, `onDullBlack`, `onDullBlue`, `onDullCyan`, `onDullGreen`, `onDullMagenta`, `onDullRed`, `onDullWhite`, `onDullYellow`, `onVividBlack`, `onVividBlue`, `onVividCyan`, `onVividGreen`, `onVividMagenta`, `onVividRed`, `onVividWhite`, `onVividYellow`, `underline`, `vividBlack`, `vividBlue`, `vividCyan`, `vividGreen`, `vividMagenta`, `vividRed`, `vividWhite`, `vividYellow` Also accepted are styles of the form `rgb#RrGgBb` and `onRgb#RrGgBb`, where `Rr` `Gg` and `Bb` are hexadecimal bytes (e.g. `rgb#f08000` for an orange foreground, and `onRgb#101060` for a deep purple background). Naturally, your terminal needs to support 24-bit RGB for this to work. When creating portable presentations, it might be better to stick with the named colours listed above. ### Syntax Highlighting `patat` uses [Kate] Syntax Highlighting files. `patat` ships with support for nearly one hundred languages thanks to Pandoc. However, if your language is not yet available, you can add the highlighting XML file in the settings: ```markdown --- patat: syntaxDefinitions: - 'impurescript.xml' ... ... ``` As part of theming, syntax highlighting is also configurable. This can be configured like this: ```markdown --- patat: theme: syntaxHighlighting: decVal: [bold, onDullRed] ... ... ``` `decVal` refers to "decimal values". This is known as a "token type". For a full list of token types, see [this list] -- the names are derived from there in an obvious way. [this list]: https://hackage.haskell.org/package/highlighting-kate-0.6.3/docs/Text-Highlighting-Kate-Types.html#t:TokenType Note that in order to get syntax highlighting to work, you should annotate code blocks with the language, e.g. using a fenced code block: ```ruby puts "Hello, world!" ``` ### Pandoc Extensions Pandoc comes with a fair number of extensions on top of markdown, listed [here](https://hackage.haskell.org/package/pandoc-2.0.5/docs/Text-Pandoc-Extensions.html). `patat` enables a number of them by default, but this is also customizable. In order to enable an additional extensions, e.g. `autolink_bare_uris`, add it to the `pandocExtensions` field in the YAML metadata: ```markdown --- patat: pandocExtensions: - patat_extensions - autolink_bare_uris ... Document content... ``` The `patat_extensions` in the above snippet refers to the default set of extensions enabled by `patat`. If you want to disable those and only use a select few extensions, simply leave it out and choose your own: ```markdown --- patat: pandocExtensions: - autolink_bare_uris - emoji ... Document content... ``` If you don't want to enable any extensions, simply set `pandocExtensions` to the empty list `[]`. ### Images #### Native Images support Version 0.8 and later include images support for some terminal emulators. ```markdown --- patat: images: backend: auto ... # A slide with only an image. ![](matterhorn.jpg) ``` `patat` can display full-size images on slides. For this to work `images` must be enabled in the configuration and the slide needs to contain only a single image and no other content. The image will be centered and resized to fit the terminal window. `images` is off by default in the configuration. `patat` supports the following image drawing backends: - `backend: iterm2`: uses [iTerm2](https://iterm2.com/)'s special escape sequence to render the image. This even works with animated GIFs! Note that it can have issues running under `tmux`. - `backend: kitty`: uses [Kitty's icat command](https://sw.kovidgoyal.net/kitty/kittens/icat.html). - `backend: wezterm`: uses the iTerm2 image protocol as implemented by WezTerm. - `backend: w3m`: uses the `w3mimgdisplay` executable to draw directly onto the window. This has been tested in `urxvt` and `xterm`, but is known to produce weird results in `tmux`. If `w3mimgdisplay` is in a non-standard location, you can specify that using `path`: ```yaml backend: 'w3m' path: '/home/jasper/.local/bin/w3mimgdisplay' ``` #### Images using Evaluation Rather than using the built-in image support, you can also use programs that write ASCII escape codes directly to the screen with [code evaluation](#evaluating-code). In order to do that, for example, we could configure `kitten` code snippets to evaluate using [Kitty]'s command `icat`. This uses the `none` container setting to ensure that the resulting output is not wrapped in a code block, and the `reveal` and `replace` settings immediately replace the snippet. --- patat: eval: kitten: command: sed 's/^/kitten /' | bash replace: true reveal: false container: none ... See, for example: ```kitten icat --align left dank-meme.jpg ``` ### Breadcrumbs By default, `patat` will print a breadcrumbs-style header, e.g.: example.md > This is a title > This is a subtitle This feature can be turned off by using: ```yaml patat: breadcrumbs: false ``` ### Slide numbers By default, `patat` will display slide number in bottom-right corner This feature can be turned off by using: ```yaml patat: slideNumber: false ``` ### Evaluating code `patat` can evaluate code blocks and show the result. You can register an _evaluator_ by specifying this in the YAML metadata: --- patat: eval: ruby: command: irb --noecho --noverbose reveal: true # Optional replace: false # Optional container: code # Optional ... Here is an example of a code block that is evaluated: ```ruby puts "Hi" ``` An arbitrary amount of evaluators can be specified, and whenever a a class attribute on a code block matches the evaluator, it will be used. **Note that executing arbitrary code is always dangerous**, so double check the code of presentations downloaded from the internet before running them if they contain `eval` settings. Aside from the command, there are four more options: - `reveal`: Introduce a pause (see [fragments](#fragmented-slides)) in between showing the original code block and the output. Defaults to `true`. - `replace`: Remove the original code block and replace it with the output rather than appending the output in a new code block. Defaults to `false`. - `container`: By default, the output is wrapped in a code block again with the original syntax highlighting. You can customize this behaviour by setting `container` to: * `code`: the default setting. * `none`: no formatting applied. * `inline`: no formatting applied and no trailing newline. - `stderr`: Include output from standard error. Defaults to `true`. - `wrap`: this is a deprecated name for `container`, used in version 0.11 and earlier. - `fragment`: this is a deprecated name for `reveal`, used in version 0.13 and earlier. Setting `reveal: false` and `replace: true` offers a way to "filter" code blocks, which can be used to render ASCII graphics. --- patat: eval: figlet: command: figlet reveal: false replace: true ... ```figlet Fancy Font ``` This feature works by simply by: 1. Spawn a process with the provided command 2. Write the contents of the code block to the `stdin` of the process 3. Wait for the process to exit 4. Render the `stdout` of the process ### Speaker Notes Version 0.9 and later support comments which can be used as speaker notes. ```markdown --- title: This is my presentation author: Jane Doe ... # Chapter 1 Slide contents. Yay. ``` You can also configure `patat` to write the speaker notes for the current slide to a file whenever the slide changes: ```yaml patat: speakerNotes: file: /tmp/notes.txt ``` Then, you can display these in a second terminal (presumably on a second monitor) by just displaying this file whenever it changes. `tail` is a primitive way of doing that: ```bash tail -F /tmp/notes.txt ``` Alternatively, just use a second `patat` instance with `--watch` enabled: ```bash patat -w /tmp/notes.txt ``` Note that speaker notes should not start with ` Slide two content. ``` Supported transitions `type`s: - `slideLeft`: slides the new slide in from right to left. - `dissolve`: changes characters over time. - `matrix`: loosely inspired by the 1999 science fiction movie. All transitions currently take these arguments: - `frameRate`: number of frames per second. Defaults to 24. - `duration`: duration of the animation in seconds. Defaults to 1. #### Random transitions You can set `type` to `random` to randomly pick a transition effect. ```yaml patat: transition: type: random items: - type: dissolve duration: 3 - type: slideLeft frameRate: 10 ``` You can optionally set `items` to a non-empty list of transition effects to randomly sample from. If `items` is not set, `patat` will simply sample from all transition effects using their respective default settings. Trivia ------ _"Patat"_ is the Flemish word for a simple potato. Dutch people also use it to refer to French Fries but I don't really do that -- in Belgium we just call fries _"Frieten"_. The idea of `patat` is largely based upon [MDP] which is in turn based upon [VTMC]. I wanted to write a clone using Pandoc because I ran into a markdown parsing bug in MDP which I could not work around. A second reason to do a Pandoc-based tool was that I would be able to use [Literate Haskell] as well. Lastly, I also prefer not to install Node.js on my machine if I can avoid it. [MDP]: https://github.com/visit1985/mdp [VTMC]: https://github.com/jclulow/vtmc [Literate Haskell]: https://wiki.haskell.org/Literate_programming [Alacritty]: https://alacritty.org/ [iTerm2]: https://iterm2.com/ [Kitty]: https://sw.kovidgoyal.net/kitty/ patat-0.14.1.0/Setup.hs000066400000000000000000000000561475634243500145160ustar00rootroot00000000000000import Distribution.Simple main = defaultMain patat-0.14.1.0/cabal.project000066400000000000000000000001071475634243500155110ustar00rootroot00000000000000packages: *.cabal package patat tests: True flags: +patat-make-man patat-0.14.1.0/extra/000077500000000000000000000000001475634243500142045ustar00rootroot00000000000000patat-0.14.1.0/extra/demo.gif000066400000000000000000003557651475634243500156440ustar00rootroot00000000000000GIF89a     "&+10(05!<%( 1,*6."9./80'53)60#>7,89.8/18049=1:2<*T3-Z6/^8'P01d;5j?@(3&C9(L7(I=+SJ=@G;@B.[@-WD0^G1bJ3fM4kP7rS8vV:{Y;O6p8MB7HA:SE>\I6l@8oA?`J9rC=yG>|INABSEDWIFZLG\NI_PJdTLiYNm]Pp_Q@cLBkN@KEsQI|TGxSCnPraR}kVzhUnYqZv]z^|_~`BNDPKWFRNZLXJVLYNZQ^P]Q^S`TaVdXfZhYg[i[j]l]l^l^l^l]l^l`nadefijlmnpqrĪvīwͳ{Ѷ|պ~ؼ[<_?`?bAeBjEnGnHqJuLzOwM~P{PRTćVɉW͊X΍Zӓ]ܐ[ڿƄɅ̇ψщՋ،ڍڎڍ! NETSCAPE2.0!! ICCRGBG1012applmntrRGB XYZ  )acspAPPLAPPL-appldescPbdscmcprtP#wtpttrXYZgXYZbXYZrTRC aarg vcgt0ndin >mmod`(vcgp8bTRC gTRC aabg aagg descDisplaymluc& hrHRkoKR nbNOid huHUcsCZ0daDKFnlNLbfiFIxitITesESroROfrCAarukUAheILzhTW $viVN.skSK;L>@>289 LCD LCD _irLCDLCD MuFarebn LCD&25B=>9 -48A?;59Colour LCDLCD couleurWarna LCD 0   @ ( LCDLCD *5LCD en colorFarb-LCDColor LCDLCD ColoridoKolor LCD  LCDFrg-LCDRenkli LCDLCD a cores000LCDtextCopyright Apple Inc., 2023XYZ QXYZ =XYZ J7 XYZ (8 ȹcurv #(-26;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmparaff Y [vcgtndin6QC&f\P T9333333mmodPbmbvcgpffffff334334334,H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ Jѣ!(]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sͻ >2dc͟NسkhLo_Ͼ7{)S|wqz'h:5wP0wS Y%hfᆩ՗S!zU5PU,]|'SB|7c(N%Љ@)$|ǃS<|=PF)TZ7H~7SPF_Q!r#[>A~nF5bxԚifLYw)U&袌6 Zv ّ&S=gG_a>hʠS?x*2TԪ譸}])S>jS*W1? xBsLe|7xKAAv~+nJM[R˭RނKߒkW肄jT-畧sDwd0UK.O= +pNIwqBAyvSϖ "NL-SRbKiTL @-lmXK)=YAwF(SUG>3eSYoqYmhlp-tmx|߀.n'xIGi:3pQ-8<_9`-.褗n騧ꬷ.wlhiQMwF^ַ؝C%D&e{L PI!gZ X=-SʳZř`d6񍳢2O})#:bk'a"N /8H=Bp$'iAO<ժN١Ow~ AxV0яi@2Xe+sXʒ 0)(-~SO-8iK ŦKS<5yk 8S⎋N9e)jUx(ӠjIXezrs)O>IЂJ Fªp` %Erь Ǡ(Ғ'%)JWҖ S.LgzB8ilӞj2JԢHMRԦ:TJժVg VͪVzr` XJֲhMZֶp\ʕ. y>կ]ȂW.^kS/x-}CY0׿**,.KN^ъV.[(jU{ % lOUv~h*ؒV--bM@+ڻض@miqC (pms.,-K[]Be{Kߟ1/("ۊ ,m Vw0>[:bIoZ; ]0.-/SH & ?6k0Z,cƼҰhB&LduҪOA2(L@i""()_fd\` )>`#q\A g cͤh!`@y)(g"PDr4Plg<;@!"=ix(ŚD* RKa«c=ZZ|S!jxA<:9[@b@ě-/ԩ^u/øeH[]Jf7U 2g5E[ղ .npS7ti vLn.4Rp"`>Wq`E!ҒBV"RVܼ9/-#܅CEN9tgCJW ]`:VUoCƕSղ 72S tW/0e \xq@eq`z|`a$(&adlo~Ya[~~ThJ.@&IvvVZeAPGzHBLp5bp La[KN|C'qGP`rnRvKZX`}N_HsEEsH`ݸ` NKaNQ`a8èeg\ZЌ0ٕ3@ofWF e(hߨ>Jaq1 EL!|X{]Z[PeqRN1yNM*ifaȍs)-9)q)MpX?*Q7:X8:K#ZJ; JG ׈(j'z"ZA75ZY%5i^Rx|_>JD*IolMYۆeǤPQ~U~~ ]*h67Zƥ7gs isfz끦JꦁyZsʨQxګMȧQq0HsbGji` dR_Jqଝʤ@KALZZqZuYt[ڃU|fJсӉdWpP8/:RM+L3fhNт*w9 ZbXES!Rx  K FW>ۤjOAW!)POLL (HqtڴU;d T&е : 'ICʚV!hcjVڷ[q`JVK{\K]ɥKAСZۘ=XJQ@["P_0x/pe0oeкQh̨"[|Zպ~|~ [ۨQQGk >72z+[aG+UIp{&6?0ؿ@Np G{kL@pסW1  L [\ZP wJ8t,P~%<(WK | \/\z2U [9 Lm]M_G0߆ΩIt:K{7ɫUWg\n$֡]` ,s≮QءN0F m yjZ|K̊JQ\xGZZh vAɾ|j\{>mEXȧnjgep`]pe๥~ ؂MؤazY` sZ-٠-VM `Ԩ1ڥ}ڰڡa'A۲ۼ۾=]}ȝʽ=]}؝ڽ=]}=]}>^~ >^~ ">$^&~(*,.02>4^6~8:<>@B>D^F~HJLNPR>T^V~XZ\^`b>d^f~hjlnpr>t^v~xz|~>^~芾>^~阞难>^~ꨞꪾ>^~븞뺾>^~Ȟʾ>^~؞ھ>^~>^~?_ ?_ "?$_&(*,.02?4_68:<>@B?D_FHJLNPR?T_VXZ\^`b?d_fhjlnpr?t_vxz|~?_?_?_?_?_ȟʿ?_؟ڿ?_?_A@ DPB >QD-^ĘQF=~RH%MDRJ-]SL5męSN=}TPEETRM>UTU^ŚUV]~VXe͞EVZmݾW\uśW^}X`… FXbƍ?Ydʕ-_ƜYfΝ=ZhҥMFZj֭][lڵmƝ[n޽}\pōG\r͝?]tխ_Ǟ]vݽ^x͟G^zݿ_|ǟ_~0@$@D0AdA0B 'B /0C 7C?1DG$DOD1EWdE_1FgFo1GwG2H!$H#D2I%dI'2J)J+2K-K/3L1$L3D3M5dM7߄3N9礳N;3O=O?4PA%PCE4QEeQG4RI'RK/ɑHhh8!8I.dҔӠ<4j%b, EI DQ.Q4وNe[5VVkiAXrA]+ׇzc5a1DMDhE(rE6_زe[[…uE6#Iu Gudz2XLŘ5~X~G`` ܩ Jd3W__2eV`ؠCfϳEq$}Mz]9 `>,h6KdV$m=6hG0L ٺ GDPNՈ! r$Kr {fpI$`N,[WuN8DUK 9'y֠ Gܠ՜rnno= uc/kɒOz^޲V#F({ Z"r!=W6mf}., $~b_A'qK~J AB|bK_A@&ج ^0l^ r \K1XWn r :H۲@!L} Y(PBX69؄<'BQL"`haE"ȁ#z%QXKl+/^b#A( 8b(<Άy(,DcF9w|!oP Bq=yZbPƃT #G\$֭n m;@li 2brt!d) Z 3$`*s$)́P3*Yjd̈́@69j (Q&c0lH9"N؎]tE` 8@@' b,PX(~)NT &H 2b ( EjjӘX1;8RQ UAHjΗ"U0!мW TXl6J\~岠 (9P`*ZJBTji[;kA ̻$QjPKOmጨR!@aey iEQOCU dgA:riBl@(+ Bm d]lr2TEʼnHl5!f YVSv`r^.~0+5+./;kV3ul LB@u६wX7'^v!H$FѬN>S>mZ=o[>e_M1f֌ |nY+`X0=`& l8nƕ yh, RD A ]qyK ӷs4Ǝ}Lg#wWβ트 cm3g=bYnrs"ɯߥ9)C mcW:ˆV\fYŊy]jT+<rCn趉 mլc$y%l0rB$M$Ae2mm,V\fAmQ+ |íkXZ\EȴEQmf|'Άo=2u.])?%V gb6ů68f:mK@Jexf!w戆6M^ah{Y+HL [ *iD Gpo8ɄޢL3+S:X$@3уoe)Bv9~t0=-7KeAg3²Ǩ@= k-~u xU|u܉(l{~ g'j /K {j}<ϼ֛š=>}[_?|Y~ˌEEa[C6><^KH?f;vc L諔#lL>8&?2r 1 TDX/{r?3vs{)ci.azr/4Eқh+AaGT&((Tz<O L'*Ԅ%BAfBT<;isxCXC$9t.8䴟qXAX@ċhC=?@A%B5CEDUEeFuGHIJKLMNOPQ%R5SETUUeVuWXYZ[\]^_`a%b5cEdUeefughijklmnopq%r5sEtUuevuwxyz{|}~؀؁%؂5؃E؄U؅e؆u؇؈؊؋،؍؎؏ِّ%ْ5ٓEٔUٕeٖuٗ٘ٙٚ]p'Y8V Ō `V>@ڤ݀`EehZ/MZ]Z8Z- ڏpVh$؈--X僨U B۷m#+[-#+ ?hU ۸]=H킅[U.8] [Vh[ ݚ\ܚ \m ZBTh@؈M PmB&Hݰ@V8\Tх ߕ\mQe&[$/V]YE0EhC8޾ [^]@P_ ׍ %.^ Cx[Shň#^mްV8V^@ޝ0`P`͈V8 _?PR D肬5 D RH>A# F`B-=AEC EȂ݂6">$fA``8APA&'bDbb,b.=&)b`c-$Vb&+hd+v`TM >2^^ B,EN%e&(U 'xEGv5&.&V`UV`pmXeudfY-'c8`a>cFf]`qfs&].ڂgfep&`@j_PuhVHD> BVM?-\UP]fA,e]V8,Bxe ^V Vjb[>@E؂.h AH\A(Fhhxn>i,YVh^i}viE 䭆鮦igNkj-@(jn/#X%i0S8b&V&l n_8mkh%ZfSmCxVف@-kcV cllV/Z@[冀6Fl dfmo]~>S^]\ֈnU5Nv B^.~V(APeS0&n߮^Ğ`q_%p0pp vnXY-f퀖pvpm.m  e^e^m0_6&^(o BuQ!7]Vq5qMe-gR&16MVp[7GtisaGOtXCtpP6e`p^i@uUguF VFp`D=.mbo_^gjDxp&1rdxgyXe Z>/'s`yu۳?n2oyvz tHoMy8EyU-~sL d&C.~z؀.hk.E y'y]7M~}%}Ӈ}~O}{zڿ}W̞smW'|^ Mwr`@m{m' BnfSlOЂn&Z@&2la 'R,j˖V$2jeHC?T1TʏC$)RDJaȑ ]d͜9 iN( 6hĀ)e:|Wy+ CEK8G+-dT1RA] p}U)Ђ58[mDTUBhCL8MuU`+2$R$ 9!C:V@8$DTȐ%a9&ey&ic!a%Vߖ HCxIVt8^a QJq i]guQ&C`"#%P8dW. TPCjСD5P[QE5TR޷YW*BU%CJZ Q(FAl2Ĭu eTB&j-z-r?UE VJ{VqPG|q_]ґu| DG8ۅD0b+X0@4p`D AljFEcUD\ay^UzdsD6<4W)9KA|xPH;6e}6ifrEWƊUZY66]X6X8 A"ʵ.!BAaDr"8'نЅ 8gy\ ~8 :> 8k<+)P*ԡF=*Rԥ2N}*T*թRV*Vխr^*X*ֱf=+ZӪֵn}+\*׹ҵv+^׽~+`+=,b2},d#+R,f3r,hC+ђ=-jSղ}-lc+Ҷ-ns򶷾-p+=.rKT;0}.t+Rֽ.vr.x+񒷼=/zӫ}/|+ҷ/~/,>03~0#, S03 s0C,&>1S.~1c,Ӹ61s >]7c1%3=)Sy "xZ,1f3W @9 CojwnM{g W7u+Ǜ`wxܼ7y{!e<}' (FolWiuѩȍw5]_-wAsm^- sw`9^҉ —rWW ^` w F: v)s5s!a\%!tA!i6W:`Y蚻]Zs t́U`v-t-@XfW}_Vu ym`}55bw5sA jŠa ɵ\)X^1}(ځ v "a2"t=wa!s"x c!ΡssIAsbsq́!st`1 $f`%6`&W*^# 6"t8"w)b""|us[x ; z⍙A"ԝbyQdz)1$=Bw# 5bYwc:2:v>"cw8:BWKuaI"J6u\3[sդu9^w 2J$y-_ :=AEevS"qY֗#tݜNVA|BҘ[RF$QUΘEiݥCnYZFajTBT:bdwU&EsMw`ա5f[6$e<7IJSrW`eY$$J6XA @byhb[@( 's 'r*'s"'с@ =l>m2?"r=qN!@z#z`HWy&\]'vRxfmUeAs&)o{tV',u:'tJq&XWns AgJ 8@|#rsru((<(6׆F~}!%@6) "8WLZ^ieyA(sM(bx:i~F~glJfkcnfk^W @Y(t6o&@ځ~#heBknמ6WcfY=ҙ%|g@Wkf$s,:!"Ҫs+tܤ"j\Exj'%ɚ쮞cFWFZn>F^p,wfnІa+?ȢɉlWV2NnQFmWǕ#s]١0[Z%ͮoႀVl d邚sfYw5]0E۲as):3kZ[\W#BrI.򔺰U[/x5Ksu3& njڛAΕm&=sat#og䇂vuArc Zt=qtu4iv)#7h|A2uvrp_ Nf3,0JrgfSh$nsh_u evNs~ڠIK#=ucA\[Po7v_MJ:H{g\n X7+x.ocpaŦdS61pud+d]Es{04?tE8Lxt%6L3.n>c1.tݵs$gRW2rw}Wj{dj2SMWZ{`sA:xb/gBwt]t!7So; B$ui"Ni_v(KYu:z f+Tfx3z"{@):y5kmte+%@rp>99S9g5z˱"Q$`zp+'9u |ss kA");Z+ J{ wŷo@p 绾{kؚ;v[ױ <_7Yw$~[W`@&f|>)u-{t| HW ^g"~mʃ8ͪ;C6Z\s8p ;:?Cub_ |Ӛ{|\J[|yW٬*<`W||}R=y={yû&;2=}c,ܯW̙u k!K%9t9Z^`W/`˽Ktt+r}c =xɁKϩC}gs/Rnw3{9+][&:1G;>?x&p_ .+@3 &TaC!F8bE1fԸ A#HA$xx@ʕ)Z"\S&Nj)SeR`43@O>=p΁< qȤ3GT"#K2\ B:5T;@d3rHmOO]k&Lpa;B^٢ǰ2[ݖUz3#TP"g-dDM$ћ L݂nu8B;cZhH`8u!s{=vg ~x\N\! ;G~ п$  BbP@a2#V"sHBA / phz!˯e ю( M1èG^`$`#aKMȍHh6j+6r(0k4|MKX0 3OܲDh!ADQ;"%M&5={Ċܨ/M$MUYmW;SH뤔P>t [*%DR45h h)H͉ȁBUHB0t%hTx%W}WʍF;mӝMXۑSMVD| OC>V[Ft3xȁְOL*jT!2#T !8َ&%(=l[ nTgyfWnhC{Kgo$`jZ@i"XmZ/D`h;Ozn/r9}fh>s>o0\ rK;%1@Af6&Cp(lx0lh(eSq!\صq[Q QVk:!;֝đ%}r#= XYS9uN8j(cuwc2;!36h9VI*l]=Α2/O;Go 3>1dinVև?ބ\Z9(pYZD>S hOT;lGN:5\`-@gX%dKH'16ġBԐoKeSC#P+49YB8u]Y\"tP;`E8Ȑ7IZX|G=|)D/v {;F>JH {0.!qC}eh"@0Sj  07@ad7;HpK^rR.&19FrQ[ܧJ2kXdS@VYЙNtz.fB>Ȑ4s$ Hgpas0CL6ۭ5AWrL-^ Ϥ1̉ü6$D#;(IϪsc.(ÑӣVOQ2S:ȧF7aCi,9NQvY<)QȥD @̩Nu yjC?vjdG_*FNWMI B)PT sCdUU`1rQ,UgB31AC>Qrqi ٰlvm hXq%S݂DVHEKنP)dݣݎ6!qn/{Busʆ%l,ikڑnCHyU~fǫ"+ۉ'ΖX/v!shIcCec>W٦w!mm w3>p_N nl+Ү0,D$g6̍w )nnʌۭ[_΁ #T,)]gj58M&(/"V5jAo|/`v2))4@P1xVjQ3TN}HL<#(kY2K.@j'R9ɏ/-a.8 `4pJDTAGcDPd됀g߅7/?im;rl ~y >Ś ; Bd9еVP[62(?:ֈ`RO和m3$:~So~ItPmpOе7_D*g <_3CW@uuOUzkwuYa.=im!@T25"R{C aw{;w@ ĬmЎHv;1ugKwzA1Tz'R@կ~9wazzm^qD&x|EHl 2g};_շvb\A~ߗ=џ~w߶ϟ !! ICCRGBG1012applmntrRGB XYZ  )acspAPPLAPPL-appldescPbdscmcprtP#wtpttrXYZgXYZbXYZrTRC aarg vcgt0ndin >mmod`(vcgp8bTRC gTRC aabg aagg descDisplaymluc& hrHRkoKR nbNOid huHUcsCZ0daDKFnlNLbfiFIxitITesESroROfrCAarukUAheILzhTW $viVN.skSK;L>@>289 LCD LCD _irLCDLCD MuFarebn LCD&25B=>9 -48A?;59Colour LCDLCD couleurWarna LCD 0   @ ( LCDLCD *5LCD en colorFarb-LCDColor LCDLCD ColoridoKolor LCD  LCDFrg-LCDRenkli LCDLCD a cores000LCDtextCopyright Apple Inc., 2023XYZ QXYZ =XYZ J7 XYZ (8 ȹcurv #(-26;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmparaff Y [vcgtndin6QC&f\P T9333333mmodPbmbvcgpffffff334334334,0a   -#)!9,!!= /( 1/&80&63)62%;3*;9,:? 0=1:676(*($E9?A6;+@6+C:4D>8I70A<+RL4MB=MH9CB/[W;UJ/Jn6QJ3gJ3gU9yO6pOABIJFSEDZLG\NIVIFYQKEAQJFVNKZPM\_@[TTQVUZOPKdTLiYNaA]m]Pp_QraR}kVygUcc]VTc^]lYXfhEbtNm`_m{Qqeeitslppgddrkmzhivsty_`Y?^qZv]z^qZY{ImPy~`v@_adezifjlmnprĪvīwͳ{պ~Ѷ|[=`?bAjEdBtvz~vzrK~QsK_]WbnkqRyca~^VȍZӓ]|ھֈֈӆ˂̇Ʌщԋ،ڍڎڍɺ;²Ƴȶʸ̾H*\ȰÇ#JH1/<*j4$F0yeȅ(Sq˂-_* /\d e#c P/f3e/XV6bf3?jHsVMKٳhӪ]+30lqJM*tU!ߏsU.UjW\K ]Y\-܇U}oNr։Xꅡ_˞M۸7ztרQƭ+T}s67~L%+#]2*myCWo~@ 0+lܶ`%&c[Aw aZ =RtUA&|y0(/qVVXX 8\HX8x[;@QHٸyk䆖C$nXihjXty會@Ef QetEUPӝi'6裐(n4]BDXvYau{n!aC[o%F/ujA`H魸knAk"U$I h/,@U!rkU:%$mF2! Ct;2ЊkjA*^1Ғ8`(i* G;aGa u,Mp1iqyJP<\ _LlnaPԫ@B_\9s]!y|uC2PF4A, ;, Aː/ݮAu1]݂lm[*"8̳vM- Q?g `F*wKAЊ^W^^Y^(|ABJY*rZ^^@ֻ" f%@bb?祼b>]P }f3H zVDM @Ȃ%; Lc W!+)2A 03,H3 Il^fǽ%{t^ ͳa5pK @=5A\ dL+h?!^탔q".v&U [Z! Kd2 A &02bP]@@._HEpİbGy$ 5? RU#w" B ,{myX ,.2,0ZZ>RVf`T$ K6;"d)ِZ&f+e%QJVrp0Z 46F7RҒS2\1OD&Pbv0z4c;}a&vC6$L`)Kf&`2/o YOVqI2߸MK%HLIJ& qT;IPb .ŧ@v:fTBeJΉ t@~BF BwH}D8蒓r_JQl+UWK +AЅvӏ*.y%]@vW/SuebYL峃@v(Sh BZilg "%},u؁.\j蕯]l\[i٭C=9f4bQ }k6\~hVR܁;7HkFK-kWn!^} ܪZ[+^y*A'ķy٥\Y AFͳq w P#'.Kj]ႜXV.D\e\y"L Ld $|0ғ:=V1Ȇ c2$yA`,NALg0J 3,a$+yL6&bXO@^QZ _9MIx tA֜dӋ-ujUε4x ku\keӜ/A" dtHwIr$C%%A({T++"e.1h0*LlykY"SwAMG>_= *H P d˶& rpnEil`;ɏ6]oV].]9])ngP?Z* R=IEڗTt.DK(g[+.>fvVA\s"Sl/a +p΃XȴYY2i@xh<<OX!/3nv\m{dIǽs~<ۂSy>b_1?Ͼngש~@AO"|Yw'$cXCc*xvx2SjZ eIdA``HHveSU VzAVP:@wqzhs ](Q~~Ij9~ с 7Cr~5(Fy|ǂEhsHV2}\؅}T}ow5 Hz)V A66؀=NwuhwwsHH 8]]QCwIrHB}6d'|oLjUC&qƱrgwg_=hU6xWvwxh6qj@i9u0b G $TZ6q:lnj*N8}ZRA Nj1)hz@8&G8ěJe^4v#!N6Z.0dl(gn|hh:֭B:?% 4y J ~Ү% )\5b >53sj)ʨX?H5xz:+BէJJѰA ۟i6{6#$ <0<=Ƴ ? M:37+Ncp$Nn AplJ,V =?{G=RkHۜ)\00 J_Rj&qKBN9c{I;tkTw8,+hԶEi~b' ׅ =HJ vk0[%8#;` INӟFr[ ɵ> +_iuX˹֋!;Z;:0 y+]f;2)jX;\5#Dg:hň>V(YZKBKѿfGe`wtp;_:i1̽3Yoz1@rI"$1:7#<KBKBWL,̈_QBB+*q5h$|AU| [”aBĔ:Y$tHÙ@?@mVBS?;:HmB g z,NJuwIe&B`:|\$ fHVCIHC?0VGƪp`i÷:=B>?iM"gsz1I`d0tXʹL<? s^ 8LL,1/ɢ-W зM ];  xg2/0 LG  M5./r*/@*1+a@F}vz^dk[Hh(P=TԨ =V)WՓ+]mZ]d$Xh֯DwEIֺ"n j!z~}Ap-p 7(=؅}؉(M؆MeזMϭvٚTe sz }+٪mڮ+=ҳ eӑ۳]۟۬ۯn˿=)ҺrS=]}؝ڽ=]}=]}>^~ >^~ ">$^&~(*,.02>4^6~8:<>@B>D^F~HJLNPR>T^V~XZ\^`b>d^f~hjlnpr>t^v~xz|~>^~芾>^~阞难鎒q ꡞrrda,> ! !p^1P~sxy0r@ ŮŞ .+Ş^.`N.Q^*Q> ^Mn()qN p`..1"$&@ONN.C_GK ?_(Zp^_?-_8s0s 0Npi3iO%n^n O,O `s?uw 1zPomq P_Şu/!.,3?c>EO_oW/o/%pƞc~Oǟ^]( Dh"E%.HhN"=+Vas RH0( 85Q9;(d8s! FPO2XĨƂ?$Qyю6J4#Ӈe͞EVZmݾW\uśW^}X`… F7C0xp  Cq U!Ey2D k*baDakЃCӺՄM%u[*\խ_Ǟ]vݽ^xN|9A<`DDA)^I72\ T; @@ AEx(C(E︴O 2T?ND1EWdE_1Fgt=+F2cH@k,4LZ $($TJH$Р,*iR)dM7߄3N9礳NtNÌ8S-%@Q=o8BMDb Is\CNQG%TSOE5UUóQN)s=LH2=hS=aeTV|J*}=8LCKY=P:BWZk6[m&(,",+z4a%HY0-E%Aw7Xݐ^\Y[ŕ[farbbzV~7.WG4VS"y^N&`#9bwg.$6+EҸ<[/њ%_h[_] eWkfm߆;k[`φbm(dn]#$ < bAsE 2-mWnG'tOnTZFX95r OPP: ,gOsH!2xGZQz>{u7{}_v 񏪈L$Ь'~֟jR·OL@6Ё̖^%%`WMzCH܏LIPJh "XPI *UCD)%`@X`4„!#,ÁԌ [WbEImak43/tRhrIw8;ah1jcqѐDd"X)H@rdK-GB2P(0B9PB$BPlIFRHSR,u;H,ib)NlDf21 !$\:h+3CͲLjf5Mq2ӜDg:չNvӝg<9OzӞg>O~ӟh@:PԠEhBP6ԡhD%:QVԢhF5QvԣiHE:RԤ'EiJURԥ/iLe:SԦ7iNuSԧ?jP:TըGEjRT6թOjT:UVժWjVUvի_kX:VլgEkZպVխok\:Wծwk^Wկl`;XְElbX6ֱld%;YVֲlf5YvֳmhE;ZҖִEmjUZֵֶmle;[ֶmnu[ַnp;\׸Enr\6׹υnt['DºHЂAϠ;e-Lۦ0B[(pN/aH;Bd p]8 ` >l7n% b׿vioa:|@qU*Nvwg"/4qa6,xF%B ӂTD~C9)bLO0pc¨SKA{A@@%@@A X@HQ*'?X"4#$HAC@&$dB$?<X@,B+B,*752C<,<@C@ăA =,4 AB@죊[a\b,m|C`GH@1`Gv4EسNx+kbE3OGr3:p&k>pg ;)+=GCM&uR%% DDˇQÂ`ԬLXU,ս̱4% LXLQWX]EK$VS3DV*LQeVWևX`mR{)VTX8Ks;]$[ P=SMST*R;ILQM}tEu=]yT{>M@ M#WdȅExKK OPܸT| -Q.95'-X=M#ٶOЎe \XK]{icl\ Y5d:{KJgxc<:Z#ڵH,[<O[<ܴ%}mO[5[5H8H9]Dsڷ@\Pܑ=T[>7CݴeڱMZ8BLEև0=2$P+m@5$=0?9@`]%$] KR0X7R9,2C84EU^)ݝ2]^ ^}L> _=߂H])]ו_x=݄^R^_}]_e]^܂"-]`XZݷ-V[ n v͕`V=g=M3^a:bܰ\YD3YOیk?ۙe=9b6NCd܂1cx+~-.K|,CC!6 .F/Rm[2bG:>;.?6@bc3pcոE6F^gb`>õRb@N nLNv@TKIWe?F@CccxA0\F#6|^X^S(9Qc]%lZ3^ނ@Vp]j^L@`e jnhC2m]q-8te)eSgԽBLvfwNxggRg`>%3=Sc0cȇ\H.IvUV^Ab>`\蓞 cH dD1 FGw)Ʀ$~ >j^p⃠F`ePʛ~ ܂xp~{.NAL!EǃxН=d"8&~&.YL4D1^kmk¾DkFem삠l!My m|E1K;lF1%+lZ2[m\KhB Qfh<[)Vn7<^){f[[n2,/L3DnnLxnV5XQ8hym} V>5nk FDVkl&>fg0@o n7lk^ xjhꥶ7lqOe;ʫ MOhPal|'R!XOd!^4."Ob}#ZC(H,@<@1G2(_aݜ\5?p[8+ 0=>g<Hmd25gH66@s]V̓@`EᦋJO)Q{5g^h^t6 nt(nu+$0nZu[\O;^2t4Iu)[5^'cGg>'Os _H3Gv-Wp;tUg!gw;SiqIX8Fgⷐq7rq-hjv \^S^E_Qu׮?a.(ZȅdFge+lD@R67ZxʼnDŽIa=xsj>3woN'zM G B:Cqܢ'?ygPvxxQPIJ7u{(u[N`Vv`7XLd7tB D]2-\nCXAh|| vRùd'fǷ_Ml g 'yh_Gr_yW /\8yox?_鷍x}L_:|8ܷ}ϲ:7gYgX"c0Mn`N~=zhx_tg%[`pAgDAOxAhbĉo4RшG.(E&W4IVr$N1&\a]YA>əI_PpR]4ԑC->`AE0:YM ɬhK7^YFhh($u1" \xdc KJ?x,ς8 x1c-c6$r0Y6եO3͛_1&e/]|7^8iGO z'CdRnǗm{Jc 1 '7YoT@S QUFu~gX#A3DL-3(HL.=# (#6F=!3D"Yi1T q!CkXI+@QNia j_afrʧ/PJg]Fv55ޘ 2gRbIl(";t./jd&E -4-ZL gyAwө ,Ő _(a3|ŵ@JP#%Da?3[CDAVjޟJm^nCd `1e?x\yX!^=XjJiU-\H':7G9tRi 2˚  Q b='@ gm䴛a3g c,83:>/ZR!8@wl3 enqr\T>Fݍv"@l֙H%+2;nqv;]LcĠ$zdOG' uTn_ 5H|T%=iJJ` 3j6xq8(~ uʼn";Yx#$Z/q ȘAldJOl"YɄV@q_h"/?̍E 0}ʴ673"JfA̐;CDϞBQ-dbQa \ޟ|YYTbi`:M$aߒ@#JО3A?m b=&fg][|1lO:R".tDaB n0` )-h˻bUӚ϶( ,ibdڻ =^:ѭnv[6a 5^lk7Wݍ;@%ArG/fgz>U>nŲ+7΃K 83$D6g -rg GHBiyHz槾H8"pvzTmjɠAjN@DxB`HCfGhvN l#UdP":LrgC $WDuug.wGiRx $Y cxn4-_ XApN+7@a3{L,]؄|gt?즧jOYa2p&A-Iq"CՅu2@rJUcoJgpwrmm^@kG n )LP.hIFpj)Xz$B 8@ llM`mhTSp@lBFBZ$^I"Aۇ2Q&~2Jg}.hp4gݤ jD@Ža&g.gsrW.%2Xb&{l@iOyp:fkB|e.@4zF2DV\VD$Z2,]'nv* '>9mNʦ ߠ$1@ ܀e :*ڀ <^!&@ zD^Հ  E@菫ªzREv*FTlj Ibr (U,w_Ʋ*]D@ :ʰ>8+jH} T*N$*뭆jjkL꼲k+)Z롒k+6>,: D,VHBlI\~ǶȎ,ɖɞ,&ʮ,˶˾,ƬZl U-/XJ~A2m].-6>n,^ĔSN-{d, iJ-ٖJ٦ڮ-ۂ,ښ@ @TZe۲,n-.&"J ǁ6x,vn.v.膮l/,߶T&Ӗ:%,>l:h.:.o/&./6>/FN/V^/fn/v~/////Ư/֯///00'/07?0GO0W_0go0w00 0 0 0 ǰ 0 װ 00011'/17?1GO1W_1go1w11111DZ1ױ111  2!!2"'"/2#7#?2$G$O2%W%_2&g&o2'w'2((r)2**2++2,Dz,2-ײ-2..2//20031132'3+3?34G4O35W5_36g6os03s3883993::77:3<dz<3=׳=s+33??3@@>/@4B'B/4C74,;DO4EWE_t5Ctc4GwG4H{F#pHI4J:K4LǴLw3KL4NNtMN4PP/tOP5R'RsQR?5TGT;sS T_5VgVrUV5XuTw5Y5Z4YoZ[G5]׵]'\]5_<5`6a3`oa'b/226dGdcd_6fg*W6jg6ds6i_j[v6l6X6ʶmXӶ6oPnpLw7r'I7*s?7F3B7uW7CKZvo7@cwr7x7={7y7:7{77|4w7~17/8x- /8+#x28CoWOwx3 {@{ X.xs2Y38x<3.P@Ixty3;8834C0l9yH븖w@oy30dzLs9PӲ_Y&\sy3Cr 4'dhPA,os(xl r3èr{{sOzz,k¦Wyw/oy,@4l8,s@Î;673C*W0{³;C+ks{,@4{;3*@{@׺<=ݿs}kt˿{}+so>ܟ2*g=`+;2S ;<>!\ rxg[د`;2_+K)s'3{r83@>!:W?!\*g?!l?w8&.l=$L@ 0Iv8ɦ]69I9Pk֤  q딘 QTX6jb&:2%](\9͜;!ٰIR|*B DժRլ \U2WM8u`M iG GFDJP㬾A1&@ 56,%]VA=tiӧQVukׯaǖ=vm۷qֽ71>ZPcCw>PF;Eoћ&<%3M-% NppI}홽cMK LȂ>LIL>O rB]>ff:CrHl1y-`q.#Qz R!,#LR%MaI`b)I@`RI˝dMpbBvf$b G@dy?wE[Df0n-CK$(Eʡ?4qFBD &Ӥ-H R!^,CTt';k)<|3O ÞkqDvdIPAx\ЩfA!">J ?z@B0 C!b H&-uK[)b$jmgtOT4МatMfl|;*R "NAiѶImR(IbFHlL)`4A*OPUT(DiWўP["~((޲>!%IIO ~< ]>%gAZϨiQ{=Me kԢvOR&ЄK r*xʓdt#Q8n2H뗚@ %]\o,`w{r[PHya*6%}Y:@3pBAOuN+;TW0, #>ok䀤^B3EWq&GG-a-G%-zEցHqkB:amšp`GRq]KY֒xB6FdCEֈF 6R1$gU₃Z7D fH{ Ldbf'݄Xc np&˲1Sy@ ޘfG 7gK@p9a=ݒ2ߊSrhT k ѱftXFh+LP% \vN3m0Tg|'9ddOzK"͒ϝu>@#":TA°]<0a0|eb쬲cx\P0,0HПAtc-M+BhS"x|u@߳'CS.\x C:C~`썙}g?Ub?>9џ~k+M8&$N(1%!SN+&Ԁn V $;dļ%>iF}h[Bb rLgP+?A^iv \-cCfFOP(2Pex}pHb)cp -VB),`JPLv s*%l+P ?< BB'Bp u!BO(*p%(*P!X' P8d O+$ePfȐ'Z~0Eqa1Q1@*jpU0s(xɃ t,/##i~i"VQ41%mdaD=UѱQ%1GKbpN?%Q#q 8@r!aD +%r"! F>-10<4#>`|O$Q2%[^f"a2&_M% &q'@ ^&&s2'w)`(s2L8())oR)yB&*)r+2%'dܰ+r, +,,r--RѲ.R2/r//R!20S0 031q01F1!32%s2)A21*s393=S2334E>4M4Q+Gs5YSHR5a36e6f37us7yjn75zs8839s499ws9s9::9s:8;;r;ų6<>o>1??Uf?.@ @c@,AAAbA%*!B-@'4C)B9CSCA=tDI;CDDUtEE]TY4Fe4:_FypFq4GQFy4tG2{tH.HHH␴I4/4JaIJRJˬtKT%KKtLLt4M4ϴM?kM4NEM4NtO)OO?O5P u<5QPQGQ!uR)5%"RIR5R1SfSAQ=uT&TMPI5UTYOUUեUeUNaVmVq5WuuWyW}W5XuXXX5YuYYY5ZuZZZ5[u[[[5\u\ɵ\\5]u]ٵ]]5^u^^^5_u___6`v` ` `6avaaa!6b%vb)b-b16c5vc9c=cA6dEvdIdMdQ6eUveYe]ea6fevfifmfq6guvgyg}g6hvhhh6iviii6jvjjj6kvkkk6lvlɶllѶ4 j8 4 @7nnͅn"!pRo6!!~ 7n]p7p 4$wr+m54qV6@tMh4<xA7VuuͅJLt!5hl6d*~zwQwsv47wm54xC76pt{*!J"4xY׍WItI>J7zMc}}vox)~Q}w~KCᗀ|M6t/!4 t!|776IrI4>8~JxHG4ZXmV4:4R[vrA0XO8v3xI@DySX8jn{KÊeG#mX58a4,]9X dzՍE@5,a4">sǐʎ/NU?yS@X5 8a89A74F r )=CF4:l|D 8j0!"F`4jsyGvlz8Fc9>9ey4@nq!X6@9 J@ٚ68M#99]cy_aAǗ4uC×9CÝn48oRږڙEC4y4Y49٥My7zYqcO9W٣9EHZgE 8zCCZ4o]XSkm`4c㥭ZչECE94Tz}z4>G4젚 i#t 5>xyMwDA'CyyAt1!NW4DK; (=E7wA]yuzgmwy{[4:A6^{y!]{s7O;Kw[7 (EøsCMW;w:ٻt37Mw;4Ic۴[K}50[ۼ4 5*3*s{M{4:CwMC # X4:5.ܾ3|~C㹗7xwo3$Ir7Gc0<˝3-J4ʱ<1aYA K|yx9!hw!8͵<42!1axZ]e-\1]9ѫ<|c/}zwПAtswA8tsܼ#raa}y 8}= ?9W Ay4}]}cˑ|_}]کڱ}ac)}[ݹ{CAC 4L4F^4ׇEoށ4 gA]=4~ګ݌r67H]u4R4;E1tz>448_>I?a_?cc3>AAS[C)÷3~4?SCm4t_J[sE{-?\?wɂ <0… :|1ĉ+Z1ƍq2H zzk!gz ɔ =t1B<!Ӡ΂ɞ;XY-(rzF \2}(aӧN2lô f5kΪ: fAswgСE g Y;LBwg~.[\Ews -8ni+[|?<0uV:XYbXIv7i y`#* 'Haw4Z}6d2L!mw`H`F6r/6(4BD%"43ETa` 3urP',B&C H#Z'5A7P\A7"`%L_Z[H,cA@B!TN*C;.Pggф^3cf{J%Qܜ86裑$WBw3w$[婨}]I(&JP%%k lkĄP5JDݲ@=R &"&3D3BA"AFt :vZYnz}3>$F~[P {PZm(>HW D2)U f\Pz'A̮6[<ܥB8OBV*M=#sC4;}HBM5M vbMvf oUԒKQ*&7&!pn9~?8t4 ]R/ѧ4F*0]PnDCw k,3^($ x7I{⊷xtn9 ~ϐo6rS /=([AAǝܦMmX;RsDz?yl l$ !D hn44aXݐ B F|ƻA"A#c8H ^0Ԡ/f@W.$YGppx+, 8EȊ|a CC yOb Cb  !ݘfY@W4)pW!W"HK"r"AWxg\@#SE8Jp'?Z,B3QH0bo)F~˖0ɲG5`}0MVwEtӘy ",wD s] ;$f^-xXd̅/>W.`Dge0SzZ$\T%+GuӝcٛS}ѩ$NU.f$ O< v~$: rsMoӜBVk[1. AH\P-F}R)fӥ  >ZZG#Mh.Xjͅ4QBH Tp^ n}U!T^LJʓ d Y Ңo}PzUVAW RS_-lV Y^C,D>G.֯uH^uv&0Қ$ Qnv ;eH\G]q`JX9X#/BMF$ER*bI!&bς4}P!lC"ޣ%vpyzGX"m.B I_Q^D%qn~\X§-d3`M$5N\.5XqBn$+yɼi ! eխU.4uOf̎uAL/#%#$5`eAy5q~ϕ47$VVa2HMQ'$;ylluuZt2h$:%L4S 2^>cy͘Cbi"h6H  :6ȖU-\%J+ꝍ^f"cjלuNtbLn bWMpU;DXg@,[\7Aj 9s ˴(ATdaеu^f8=Nq;4A82tLp |yA@[Dҹl3!(ΣA:/ kus}UGA.fiNkuJФ7gv'ɰ+r xY>~GrAXxK~/`rq =#,Hr1X7 $3G A8C?&aOlpgDbvf_~IT_/z D= DETdE1Q"H<|^KEN8?Q {K0@8n'&X?'d(f FUMW|{!A|OGD'#u04̧~~A~W}8ŗN7tzcz? q-l.~0}@(D8Zz74#~%(GڇNvwU;8|W~s~|ǂh|<8 ^ $gw{!XFB3) |!B$Xu^|t)8SbV(!b8EFhQBueSwis:5EHI-{j0B8 b !wƋ `hbxBXA98#L(P-E#Xee axpn1`IoXU9Q! SyQ GP֐7kO NAsK5-Y Pg yi 9#XMV !(x,m(鐾P@M(n'yHj ! k0"I4JA#IofqaYtUAJ]F7,i!!1vny MLl>◀@As, 1zeYw)y@%fb]鏹rI9y#p7cS 6q AEpbÙ+7`yٜ3`5 FIiYiiɚ9L+yۙ95PY)JjN zi *J*ACfz ! $%j\c&+ʢ-ꢻE/*579;ʣ?@$t@JEjGj ˈM,N*SJUjWY[ʥ]_ a*cJejgikʦmo q*sJujwy{ʧ} *Jjʨꨏ *Jjʩ꩟ *Jjʪꪯ *Jjʫ꫿ *JjNJɪʬ *Jj׊٪ʭ *Jj犮骮ʮ *Jjʯ +Kk ˰  +Kk˱ !+#K%k')+˲-/ 1+3K5k79;˳=? A+CKEkGIK˴MO Q+SKUkWY[˵]_ a+cKekgik˶mo q+sKukwy{˷} +Kk˸븏 +Kk˹빟 +Kk˺뺯 +Kk˻뻿 +KkNjɫ˼ +Kk׋٫˽ +Kk狾髾˾ +Kk ,Ll  ,Ll !,#L%l')+-/ 1,3L5l79;=? A,CLElGIKMO Q,SLUlWY[]_ a,cLelgikmo q,sLulwy{} ȁ,ȃLȅlȇȉȋȍȏ ɑ,ɓLɕlɗəɛɝɟ ʡ,ʣLʥlʧʩʫʭʯ ˱,˳L˵l˷˹˻˽˿ ,Llnjɬ ,Ll׌٬ ,Ll ,Ll -Mm  -Mm !-#M%m')+-/ 1-3M5m79;=? A-CMEmGIKMO Q-SMUmWY[]_ a-cMemgikmo q-sMumwy{} ؁-؃M؅m؇؉؋؍؏ ّ-ٓMٕmٟٗٙٛٝ ڡ-ڣMڥmڧکګ-$?ڎ ͨ _ۿ=4\@۾eX0\? Q_ph -@Am-AA` @Mm6 ] }WMޮ@ de  P1!=p9dpd@*n }!ۍ amN t =1' 0y W \^+>6iP hW EnlN? /Px4d ^Nm Wܫk _Qf/nhU^ifp2Y^BPBpqꩾn`ꮾ. n븾nb̎n%n^Wp  U_@ƞx{~!]ߩ]߬`P^ߪV?h^Q?@ /UP߭N/#O&=QQM/SO>l۝ d X@߫p߬`B0c۬f j B$1# h>Upo? ao> nQ jl|/ ⓟC jj /_Q^cPe$~>6a@^ۡ zݮ)Oۍ ?< $XРA!XdJA5R(Õ0W)RE+EGA. W,s$J Ӣ,]j&`q`ɗS6%MOYnWaŎ%[Yiծe[qΥ[n \qAkWEX0UϟA reWn q6JRuPpAUnBHU+'\|_\T9 =FC;si]a !ĠEO[sA筰ջŏ'_yկg m㪍ٮ:>v;  C!` Xk" 6b ,2(,p;W4ݾ@ł䫐 pbjV̱ {*TrI&tI(rJ*Ȳ ,4 ʮ .Z6Ƞb[5Wz8F]!C-[ Wj,ON$ND+ ;ߢ9Mj)8 EEJ*CuTRK5TTSU',("+oc6z >/r FԠ2]q32pٖMΕ+HЏ']y5S_]P L}ZئVG]h SU_x`EbϊH>eE@ĕBSb#!D clWӋKVI2[qέb Gp r3bXމ}y{yNW}`zjz`;;UWU7֭OA/V g!x V1 yd^oY" :[!ٮm.iݻ{ <ݢ3?T2ϼwiWd܂"zjc}vk2(2d VO @墏y<(o(תsw2/uYYz-O+|G]u}~IM}6mAOrR8WT 9^AlFw8Լbph Pt Pa`*0 -!ɧp4`TLp ,$H ]A ޸%QKdb,[[ i p@+\!"@6]$D hW2Hpk(D  @VōyC؈Q)X.-#A8 3{JMyJTRT r?|ɇn@hDNFdAb9cA6@FQ b- 34El5yEH i̦ s42K19AI 6̓TY^pVOT}LƲ?iOx`0pUhK;)ѲJ2B3G?p@xp!-A^mM˜gX$'^!c"QB*HTS0L=4KKZ8"/+QT:jA0А6\J)AVUb"ʷ 4i25?B W8PNF"IV .+P؟9gϪhКLggY{09WV9tQ-8V<>oyQ/0F9PS2u = ,gѪ) bPS̖Sw̙eU*;B3·}#EZVpi֘#nZ;@d#6yt xdbߥ-x Bc%, s,8+ISy_GSr,?m-!! ICCRGBG1012applmntrRGB XYZ  )acspAPPLAPPL-appldescPbdscmcprtP#wtpttrXYZgXYZbXYZrTRC aarg vcgt0ndin >mmod`(vcgp8bTRC gTRC aabg aagg descDisplaymluc& hrHRkoKR nbNOid huHUcsCZ0daDKFnlNLbfiFIxitITesESroROfrCAarukUAheILzhTW $viVN.skSK;L>@>289 LCD LCD _irLCDLCD MuFarebn LCD&25B=>9 -48A?;59Colour LCDLCD couleurWarna LCD 0   @ ( LCDLCD *5LCD en colorFarb-LCDColor LCDLCD ColoridoKolor LCD  LCDFrg-LCDRenkli LCDLCD a cores000LCDtextCopyright Apple Inc., 2023XYZ QXYZ =XYZ J7 XYZ (8 ȹcurv #(-26;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmparaff Y [vcgtndin6QC&f\P T9333333mmodPbmbvcgpffffff334334334,2(   +.+( 1,*6/&8./81*;/181306848:70592;<=>:4@>6/@:3D?9JB=N6E@7IA8MB:SE]I?`JQRMD@PJFVMJYRN]UUPWXRYYSRP_XQ_@dLBkNDoPEsQGzTI}TUSbYWeZXf_^l`_maaolldsrkdesijwklyoq~{{rJWLYN[O\Q^R_Q`S`VdWeYfZh[i^lwsvuyz~}˽οíƶʸͽH*\ȰÇ#JHŋ3jHǏ CIɒS\ɲ˗0cʜx͛7gɳϟ@yJthУH*]ʔeѧPG6Jի=jׯ`JسhӪZm͵pʝѭ]+)ػw9P0a, k\bb tP,U(y(P4*MkD6|vA~dsU3z#񋬋Cږo+.f'Ԯ / 4tc#DCE%B>4`PIؤ88$\zБ@d%B,Yd`dxaVo\_A5 Ĩ#Ad JJZWY @@ lAt®8бr@&"®bx [$D)Qg.ه"K:[P@uٷPAXR4#[#$s5<pT 48L 9#9Z"(q+Kc"/k|g"B"Q:\nJܬc!1 +pFf=(M7QC|o09ݳ.]=x{F\[fK{˴ Y#_03A^tEl40D=0L4q d@NS%+CH{9o@"?A,P)u#g,dς8ʳM%An@w~n3'yB؄j 0M:=W b!6a;@q@" E`VǮg}PoW Qz*!`37,  i?^Gc2ԚA! d$qA_D."v|0@4PtA6d`gReax =rK<E,P b a'|I` 7+# QEd'ibR{E_@K _,7BFǙQ 22 @0& {lHQ,8$6QT(U-d{, %}caBH{<"'x]nT H@ρH APE"`qC`e@F2ZDnlns$ R@ӚU܀4.$tz(! TlAaIʈˈܧ9՟keRzS cuSn4-x@Zт.6t 2̂P  6fRE @XZ 8ulQ~ HYF]HڮJocuꐫR @,dTuYUvVL7^DdVG)wo;8Gp N 0.< iLek|KMoO7 \ cS>ApeTX Ea\tڰd!@ƼY%.[UDJ=%}Y-h# 2$Y/׾6=off#ԟD Cg$ "px 23R 20HԂ:; qp4H<g d\a3Dcw7&t>2|R.:`tt[t0B= /r-lQ&Ć4<yXW}+5C@S3 Rf\}[hS~)5YD@/I트D10< HX?62ޏ7| 'M|Wd"G׀MDuv'w 7hZh l8"~%WHPq0jW0H'w7{ZvR t58u 1 @>@GSX 0QxOq PXPSHg #L 0 8"eL"!_ić}h|Mju@x'x]~u78t! Up~UX=~{{8^{؋o،)apʈ!! ICCRGBG1012applmntrRGB XYZ  )acspAPPLAPPL-appldescPbdscmcprtP#wtpttrXYZgXYZbXYZrTRC aarg vcgt0ndin >mmod`(vcgp8bTRC gTRC aabg aagg descDisplaymluc& hrHRkoKR nbNOid huHUcsCZ0daDKFnlNLbfiFIxitITesESroROfrCAarukUAheILzhTW $viVN.skSK;L>@>289 LCD LCD _irLCDLCD MuFarebn LCD&25B=>9 -48A?;59Colour LCDLCD couleurWarna LCD 0   @ ( LCDLCD *5LCD en colorFarb-LCDColor LCDLCD ColoridoKolor LCD  LCDFrg-LCDRenkli LCDLCD a cores000LCDtextCopyright Apple Inc., 2023XYZ QXYZ =XYZ J7 XYZ (8 ȹcurv #(-26;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmparaff Y [vcgtndin6QC&f\P T9333333mmodPbmbvcgpffffff334334334,20(  %'%')'/1/01/( 1/&82*<563685886=>:?@<@B>6/@70A:4D>8IAz#̘9}&Ө$nY:RCI]"Hj(ߜcoCn6twoGIG#\`cZeLg'Ro7+V͡Ve2e1?B-!P n}!o0GWkKq*di @}qxugP!f_!Ux GC7Xr(L@FJDӑgaYEJę E700Bji \AUm!FBdAcmhF:gfZ[Bj(tB:JuZ衉T!qez& pũn)gw"꧱F""Hg`"Uf`#5y!K54i+iSԝ#5`&A#foxu)AI G$]ƫfa>c7`/ $i2P%Ͱ+AFGp,rX@# )0MHh YB*w*4 4%f | %H]$fW,!$IL?nA\D:yTH4bzC7_ QŎ|ѹV bf6z ]xLs7 d6H@, Y(:RDG8Q$l?$H# GLeFiĬH!0dR% j< G 1 H`8x1(4IMAP4ȃ6 ] ҝ-J Hԏ U@}B8XvY-D>)sԡVĠmX$J P -HlδɓC #!nȐ 14i1#]9kSRMAO|wnr5Җ`ђfrޗdn%1q9~#4jzY!c)CQammod`(vcgp8bTRC gTRC aabg aagg descDisplaymluc& hrHRkoKR nbNOid huHUcsCZ0daDKFnlNLbfiFIxitITesESroROfrCAarukUAheILzhTW $viVN.skSK;L>@>289 LCD LCD _irLCDLCD MuFarebn LCD&25B=>9 -48A?;59Colour LCDLCD couleurWarna LCD 0   @ ( LCDLCD *5LCD en colorFarb-LCDColor LCDLCD ColoridoKolor LCD  LCDFrg-LCDRenkli LCDLCD a cores000LCDtextCopyright Apple Inc., 2023XYZ QXYZ =XYZ J7 XYZ (8 ȹcurv #(-26;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmparaff Y [vcgtndin6QC&f\P T9333333mmodPbmbvcgpffffff334334334,Z(  " #&$'('+.+/1/01/( 1/&82*;574785=?;?@8IB=ND?PDEAJKFOOJGIDOPKPQLEAQKFVNKZPM\TUPRP_[[Tdd^VTcYWeZXf^^l`_mgg`nme``npogqqixwoxxoddrijwklzoq~xwpwxp{{rpqv{tvvzz~}Ƹɻµ;²ƳǵʹͽH*\ȰÇ#Jŋ3jȱǏ -RIɓ(S\ɲ˗0cʜI͛8sɳO! J4ϣH*]ʴӧPJJUEj*ׯ`ÊKٳhӪȵۏkʝKݻx݋߿] LÈ+ qŐ#KLkkΊϠC=ziS^ͺaL`0! ״cfr[i? nG`8\h &IpL¤E_^~ܽ}ߡifݼH7~߁-U4 aQ'odFftaGI !D'RD'$"آ]'҇+ЉQȡN2d ؓaPa!Qd NN"ňOQҚ04Ҙ(iI6E9eO$D$DBB$JWj@_RiFk O/'DzlyȒ&J:Q*)NDM +R\]~I)W#ZH 1[jB/zиr;A>NZd7]A|x,{TF bK9ij~k*\ŷڙdC6'11P[9 [҆)_|znR4Ol3Ř1Lz!{B!iaAUpA8pQI q!ixFT$pG\ XhBB _-BL; Glh!Cs4bwPDIp"O=H~z@s׽.{^ _]]AA%Mw l{AYo]P_=6B.$َ;vTQ6= ԃ|_/Sm|Y+piSf"?jԃ@BDžx ImQ7N$}sAPp0H #xCy lb#Һ: P@ rE.HVhoy9`cEŠ"AJ/"~&@r:xhTA ," 5@B B~#!iDsP Al >?"h #8!!J"T$KOg &("ay@r<-)?2٦@ iHnHĖeAI$F6AϔmdQӨhU$G ` :FZL . dPz "a_AN{4A , ˡ@,DߎoG]jZ[dH5!m7H HN*Φ q &%{ێy-cs ӳi<qV2M"4uw|ϙ9O0Rze,OC鉅CJc;_392Y v3PxORa>^< k`u@>=)k'1RF`A#ðF+dq<w6ta`}מG _~4Arww{')IK >o}cX{.3c~"G3mX,rLVG @%rpa!zeWN`Hq4XHs'`KfHHG~3XCHB/q2z(a8x6881 ұqamV҈EҌMh(nRuI#S> 1! i':؉X脖"y  Z8-\ymFWz\ِ()芻-XX)8;((9p(KKp;n+ɏDؑ 90 -c9&HGi%w]UXN -!4a+q2/(emyi'9jiU1byVkٕ,)#R%xtn`ʼn q8Pg}0EYY}bqs6.3-%PW~gm< )i~LǩYCP9i9rmy)L2XtNiHzI3 i)P9$'鹞UYɕ ÕXY} YBqՉTzq l| zY$#ZV q ѹ6T0v 9rAݹ4E,ԚJy2Jys **3 9J ٹi-٦M*x|`xU]:cr۩[~D?`:jG IʟʢqXo,zjF :z1J4RH1ʤs~9gxIIkuڢ0@u !frL@ՙtȜ7' ^ I$?EeʢkRl0꺮Yl^}jÙCZ?*ڜ"gx`[f}Vir[Ap|TV)r: {Juүnt1Q^ƚzhrح F9X7J[г*{=Fo6@Kjt}٠ڠ*1ҫ.ГQ&@ᑌtLl ; qi˭ B`T0uB е3D,;#WY:\:+ubCdkz+HK8-c._(9듌ۨ˔"랍['e3Y++hz;qu !tkcqϫλ1;{wH[}i&Vfrhv6K5p`TB㾜g (0Pe5#4tK`%L֊}[7,OZ<!,WC¾kaU'-, r9l;=|rUeTr !5 /k-1 țK%u 7_5Ltl$\!רsT! !a ;Y5& P /l)kq4o$wMbH"8q!(U2LNl{\H76T}xX ZEZMt<D1AxAFƉx̂C <ANNR̲lhkuͧ q`,Sxlܿޜr#KC7Sll]Pq"㌇mn\ ،tw͙_q]}<q LT0ʭ,25='JdՈؠDM (}-^HZKg1́2ESVe!ƥ`}҈h=U\$^,Y6|;*\r \y ZYpLcTw[#LLvjb$ZWʞpkimC•i#rbYnL% q i4|p詫R&Q-M)96gas]L2wE~S&tgڨ-m==-m}Vݡ]׋f۸x ZfZQИkx]& mm,.`E-@,UV%Wq8PGK0@]L{W2N6.1@*XT+rQB=Ep Fp1 !0?ZdP = ,k`,M.A~kDXvu*xKVRN%~&$&._$,0sd$n+wQlanu%f|`PNK(* UPD.ܑvVpK~CJ%q"QU1w(rxFHNN.qI>.t~\nqQGD*/N 7OԾR?l~ $pEM$W Q5DRj3/=tkgT79`H?Jcs>ц BGDKGYDJ$\Gz -V0oQKil=Gw6)0+-v m^k mn;_ޕ3=K@?Qw τCxVO߿q>O$G9D?o sO$%)q ˟M4a'2"=BR1@IKy#oЅ zo܏ ޟ8DaH@GDB A:< Q=~1 O%@icMɒkl K,x$I (%ː$*Ih??23 U qYR|<@6֏US,{䍒/(y#-ҵdM4Ǚ5iطmWNca%s̹sՎFfܙ Iz|דݽ1-xլvJ@I{X'(Wh{ǯa~gII*JpPϨj57RkI8€0HH0(a´0Pa G 4 1&T8!"DfF `lDH$ǍRo( F$d5 l%ӂ C8U@1 S a+̈́J-DVl5"&T,D%|P8dEG$LH4O1TtC ;)aPOE5UUUW_5UgV[o5W]a<“8v%XcE6Yee&c6ZirZk6c&$ mE7]uej7^ݥ^{M+ š!dN %`F8aZeaj8bWKXD! > J2wbG&du8exOfeL0uh:jUƚX κk!! ICCRGBG1012applmntrRGB XYZ  )acspAPPLAPPL-appldescPbdscmcprtP#wtpttrXYZgXYZbXYZrTRC aarg vcgt0ndin >mmod`(vcgp8bTRC gTRC aabg aagg descDisplaymluc& hrHRkoKR nbNOid huHUcsCZ0daDKFnlNLbfiFIxitITesESroROfrCAarukUAheILzhTW $viVN.skSK;L>@>289 LCD LCD _irLCDLCD MuFarebn LCD&25B=>9 -48A?;59Colour LCDLCD couleurWarna LCD 0   @ ( LCDLCD *5LCD en colorFarb-LCDColor LCDLCD ColoridoKolor LCD  LCDFrg-LCDRenkli LCDLCD a cores000LCDtextCopyright Apple Inc., 2023XYZ QXYZ =XYZ J7 XYZ (8 ȹcurv #(-26;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmparaff Y [vcgtndin6QC&f\P T9333333mmodPbmbvcgpffffff334334334,  % -#)!:-!!&((= /( 1/&8-+62*;4'7/17343<=;57:01/ E#4C7>Q(=4A>AB>6*E:4D=7H<*QB=MJ=@B.ZY.Do6QJ3gK3hU9yO6p6E@8MB9GAZBudsBB@=CpiV:p5{ltm5tT,^S~бz,ӎJ1{|K9ޠ.*S^|wBm=yP,5_tw7@O7Hw1%O=|ׯPU46A&}.9”>io5pIA3Jw?D]y`G ZЦ&3 pָO]㝦5m5rۜ8̡w*vˇRJȿ!oG|No]"A.z`5@h<ㆊ,u{ܐ4&)CxC߱6&C}{!F:#C9 =q_W/O|ꀨJ>$)G Vt"E')&N lYJ%q|&MDNd) ]=|*ݐh̦6c*  x 3ܦ:v~ @JЂMBІ:D'JъZͨF7юz HGJҒ(MJWҖ0LgJӚ8ͩNwӞ@ PJԢHMRԦ:PTJժZXͪVծz` XJֲhMZֶp\J׺xͫ^׾ `KMb:d'KZͬf7z hGKҚMjWֺlgKT,n?EWE!&UAܕ,$ qۃhwBpc@%z(n"^B^> ~ϴ߄Dw+w+8TO*X ((+v `c"&~A q `%I0\^D d,v}{^T` 8qfs`$6K[t#9򙔜 Cb+NV1!cDL!`Ŕe\"(o**|]bp|y A^ R<-F\t b #JL_?!$]C"Ug+c:TZ>,"VdxbE~Q^c9Q0[Pz%&HU2c 6BP(;Ip 3%>rTE׆@9S}%ADwa CB 2? $𘩒퓼Y NIM| O(=dǤAB-IRoeVH(̼(,Dc"!@"BnLxpM*^/(AQ| Lp\h0%Aē2]>I~hȄA"lX'/ trI.+ҝ xq7rABs=4=uLjA& T ×+I4 P$8DVb9#Ld"Ϳ01:}G]w@.X{7=+{ !D&_o#%Z!!/d+$]C [HOT`wB_ l ` B]0g0W71|y7z :|7 gKw^8x a}PkQzw zz!g7h7p: _q{{WO`z7!y}> @(@ȧ}}#|$H4ȃgi)ȁ_8%(hk"}sy|]؄RSHNF(|IxPC]|SjiiGpjHȉ x- n|R芣{hЈXNX"vwXOȃBWHlh؁yqw'&xvib`jSV^P8egu10ebkj8xvv:P^I8eއ0ph9h/Phqcip9C)W9acb_'+aj&s89Np&Di!XS&A}TVE6U`EY^Gy-ǀn, ؓiTbt@I9eXpa8Hirjw8FYs~ 樘fy]bX!iblYfWpLiyap)цayɕ神}0Dip})SVX|Y0akٜ&FNIPWG s0P^!0 X %iwt. 0t 9vNj@ 9 f *'\Y4v7{]]js0ѐ+h[h 8DТ.~8swb5hbЀ[^)>zhb@UFZcpI*A yF :jQ (@zҙ f4Hy a{:  ŷJu!|Z*@%V|  jXh~Z`l:n p djZRQ mjzYqĊ}z *zMȨ 4(2z *n* Mko:Ϛ ))j:Z^jeZ"`aɯjjjXɩWzz *Š|zqZ\u_ٝQ9brGyֶw(p.[^0 J< 8i@p'@mY^EjW_rH+/Jxq-5j=&7.cC6A)e;CDc0) 97Ach -@c}*q€Ve,Ss[ph @y }gQ_BBK!ZfQ@gZbX.+H ѹ;Q;V[[^[y۸q˼JKۋZf  s޻+K lSjP3ka۾{KV[;[(˝+|taAaaX^8e@b > K{|7`aA$QGqhwK~]ۑ2Z&c^ip\[P)pv(tPLR f8& f2+ËXy Z^D!Ė[`.<Ì\@X¸ 5Lĭ<̯ű Llʘ섚<i6oajC˶ ̝,Xq,˜LDĬ,)D(ϽJrL|ȜL΅iȈ|+(uu"\Y^'˕\Q *ғKu@\ 0]6X 8# C5}\[p׀nۤ & q]<[`rrku{o3AaҦ, qQ׏t7)q}wprOpǰy| = ڎ ۘ}ݎM[G*zVʎM{ ma1ԡ80r5 I۟m Mٺ5-ܝ]q@=]js]w qmN8Gww on:mqqp v~.ݧ! a=\<ֶazkc;a /lLm9>]d\8νEppՌ ~ҜIQwF A ,OQnZ\!cnI.OG k>m [Y87=b~>6 i1]|>qd.}ҝ>dXWbeg>.~~.D͝pK`u\: t\*}; >D|a^k;iN,˘U;#E^(q;sk$n-n08F A0*9C:`0 #7pΟƞp )xXNǙP N޾Z>>hn8o嘎 ? _%͝DoN^I_<ϧXoZ+>(S& g#~|Tbe_X ŷ7_c/:ߔ nk*0!t_Ȟ'[J>l0 aɠ,a#Α&yN}uS}M 0-?(!ϵ FZ/zknVO`# ͊Hŷ1 uCP!B>pӱX ;c \E2aW^ 9!F9z4P D% #F@("Ȑ G$ȘBY /8 =&$ȝH!kIL>gTS~)ԣc펍P\ -j VkSϠ;ɷ+di; @jZw#Nh8ÞM_'R4H VH$KᤇB.ѥ#6 Eq̘ ]leH]ƈXA̘0vw~! z~1@@0ۮ@".cUT9fEI 9{h6.B-;$iŒCcnm#7NVRCqs3!;d3QΔ\CPB -p(Q DZ! &c6CiԍTJ &!`!`iLĭGDJ!TE%[ӶDmLKDSNuRH_H:aHz\a"&!>YuH8^bE$)2`Y"`_3v}h6,0=]֍o s! bc;J1&(9H u' uH3Wo%P)q]MOev!0Rf!Ry,s%(B]o~8+Xd? 2c0YϱX~p>Z:]0a.f!cDZW&m)W! j(&( UQ  jЂ&»GYLD+Ϗ ("*耈s! Vy:=u~?s?fwuK__r! g1Y'jB|ks|o}5ItW%HƓ1hi !KXe?$SW9V/}c*j_!, I:EtH)b+<[pw!2D#| 5I@ 2r!Ev*6-xCZ<Т3XJ 4bpHńA@Ҫc(t ٣`!ZD#|g \0Mr=t @ $BhDҤfD NhAƐŭD Z $OV9!x *ɯq3!yEHQ$[KPĎ15XdC'A  Qr  sŞ˨CnIW⮤! , -U4R+Hf#1D%X"‘j2;+:DTk ŴAIa 1fRy/1O{ R.k|uGPI+ ^%kX %bӛfw-%!+YPI=Fׁb4߅dЈ!(Rn,>},,` vl!W)y]*RwėH2 6æp*Whh e[Rdaζ;ֱT1rjv> HZJb}|uOTٖC@-Xq=B^&Hc_xz]ugȷDžr&_y"R*O$XJ+H0[ $̈́sU4vLˑ`9A5L5+jUOӚ4A ٰ%;g3H׼5St!  bxp nqS_30 la;Ʌ@}|. pz»<`G!fQbdG~S7i-ՒQ ڰLn!@Qڬ*urԙs)޸#L[FQd?1'Q>SΎ)UX|LHnr*/Ά llg6wd95-XY(!!ULhG;lZLO@ 5s|]tCZI"Ҋ%H j3v!|ދ hgK=YhN;? C = D! n - 1־iB& 0:$C&e (9Cw)?TПv_?"藂X?c?@bz!`1 `7@!AAx>鍅C辩?,>̾+2*'8AH [+!L"lL$>py=(AsA+{&D+4+ %t?+9x82DB@ô8 D0¢*"P),15A78|cA.LT ](H)/BB^HnٕDل{$lsKKa?WDah%a[+{YTUdf?S`d @a(@8 T l@p|@2&p!!n|pGE ycE@ƄP&!` Z3TTZI§pEۈV|EcH#ts^'FHULEFHHLJȔs,o Pv`x̏}^h >U%fUH+;M9 #J[D XeZ&vӏgZ1x571s.{ۂWsc8]`&v9;Tc0`b9Ac!/?6&}-IAhb6}HEi"؅+Y[ȏLa.5z 7:Vf6d|# o>%Ol.mΎ68XhF]a1X]fm  7y"c6Ei&cZ׸f-$gFvl®1{Xu@lisi1xc6X~P&} Xqjfjn\"{؂2fV׼r2ЛĊ6ط-ڱ i2&LiCgEN֞^.?!0l\~ގnʆ`i믞lv~KfVIPHii6z``jsl. .nFmjj%%piN&^WAmn1X{qz&/j@F䀋Ǝo&.o0oghϮnd" x&0'7qm5o7&2HrEFnqal~vp' ^' g_@&Te >+rfMqF2xƎqF %FV0(['#loVA;i?Egtb2[qʬ &nnƥ~tPsri"n>Rqpms0Hn[]}t2a;zC7uksi`h'v1/il&mk+/ug[r`g)˶tp\:4s77&q~u+X x2~%;Ks0xwV_wP&g XjWJZʴ2bdPYwZd5xd7iZ-VYSyyPp7ǣeޥ֌sxI_W{>myTk5@۽sy6zqK՞tNVX.\rU|r7oGgHzgOxQn|r7dc_[٧4u-B?slD@}ltR/`AVd-`~~x<'rP~aoj}c;m dNr@eLCw(IƠ&Ig^Ot0  1c DH` Ed!X,ؐȘ"G"0/b )#̘22ȲƐz<$![tE*tfd!2ĉ2˘Z;*Bհbǒ-k,ڴjײm-ܸrҭk.޼z/|CBpC . {ŒL1cg-p?{ԪWn5زgӮm6ܺɂ 7‡/n8ʗ3oj05:ڷs;2&hTɳo=ӯo1``} 8 x *H)y J8!Zx!bFD!!8"%xj|"-"18#5x#9#=#A 9$Ey$I*$M:$QJ9%UZy%Yj%]z%a9&ey&i&m&q9'uy'y'}' :(z(*(:(J:)Zz)j)z):*z***:+z+++ ;,{,*,:,J;-Z{-j-z-;.{.骻..;/{//~/ <0|0 +0 ;0K<1[|1k1{p~<2%|2)2-! 35|3936,nA =4E}4nM;4QK=uKK5Yk5]ol^=6e4ޞ6miw 7u}wrs7}7zo 8~˂k8;Ċg 9[^9ߝ>:^nm驫:ҧW:^~;\;Jm<Ϯ->櫿>/>w'??' " 2K| X,R ,  C3~P&4! Kxn0b! -_ŰlCW;z+ ~BU#JN4s)~/Nq P Wpыf<(; g|X+ ;X(>Zd#"&GZIhx0LW&3TNHD.rV|d$'ٲK(@0BBL#.]򌥔UHG\0B1NLs8f/X1Y< `1Q 'IW ]&YLlXZ1GZW%Pi)U>Y&``C, )'ІaM(C4ډBYh O.XDx)0pbsBOD%j̟K(GQ"}"IK0i沫 NUWt)p1`X1 z@Anz)HA=lfNqt'pVUkMXBdj4W^L>ʫEkkxgRK(&ZԼfj2`s@! A[ '=*ݒ@.K1mpmf5C 007昻aTp@ B+EEuOQ,x/PaWXc'2ee'vu : Ƽ{h+V d@;Ii0f}Ke>/????)?hT㯳GJ%6tO#$_ y)>~-9#-`640 -49N ` %| -|HA|@ ࢄ z ,1B8,0* `ǜbP 8)ta~!aǨ 1PM!Ġ &n1 da-^ ̀!Π!#_$J jLՌ&M~˄bT %), h8,#x%%T\"(¢  j'~ 6 v B8 "44 5Z 818-4A.HP#%486֣@C# `)C7#˼c!@3¹P0῀#1x av5jqdx$HV$T" (bJ- %#iLb!*ޤN#OONW3@ H~L$|%BfNnL!.?&e #d@' %\ ]IWJL8!%c^e\ %`^8>8 K&&,f]vM8Ѐ9^jf_Lg~f!&Ĭ%1jjIcF 'zl0\B8cfzfn~Lhh:LvbpFPqqB`R^\`9 }'ĜeX8LNP! Lr'8%(&bd 'x'HS@yzg>n:#lPa "̀h-`nKւQkBh}JP$L87r(d_R  *Č$e h{&(ި]nPT za9z )l>p: @L:6=btFfLj)k6!!Lgn! càa?B%<%>6cf@hv̡ :L>`- 4`N @!%1!>`F"'>)hU-ZLɪ:K^Bd8#`HBLX@jLu+1|X;8#1pೢjɴ: Y)kfU*Jԫޠ-x(zhQL#8-(d!bX,1`ǔ, fb+(TcނvL˾,%1…,&@-mt&ǒ ҦLB워23zfT-^Nm!hɐ~ӄNmF-m;-4⭚+m'.M l%@Imz%mU\n` d nnMk:RMMl R^An+L*:3.ЮѾ.&)Ԍo&/i/o.[/.&SJ;*c+%IG0&M0@S˒ogmpgn AR;a 3[0Kc0&0 11 (p즰 n C/+ف<@>dw: %Aq q1@JϨE=XEqp8 p5 $$c^cR$;2%k%s2M|jƄ/4ڿ$S!G ,!24lЛ 2/&s.c0/0r132;/F)sI*O %#,,2La1Hȸ2.?-3؛2<3==31W_dsx6#@`^9+ dDN74%3Zs0tOt?-[sW7l?usw:LK m36X[gfP-8 m fϵkەKtsQ7Ix+ xxv+7KsK7nw'6wd^8{wwp< /u#8l8xk6wQx6dRL†KANS '͋/w&QL O9I8x糎obOO /wB1#̒̓K 1?ö2I8 LSO̙/!-A?̛#M8F˸jK([gywzm[sCI+ NYF*=LLC grL`Ri{ r ;+ޱztwGɪ'hB_[g6]z]U Aٿ{߻Ȼv7ɵ7ZܺO:À  %]L|_ ƻ1h<#s|{ȃ|3I#̞Bw <Ę=s ,<s}=_}a׸=ԗt·3/I#Lؖ ڇ 1ܳ}} ýS{׳W +yєCLp"T0>2L/$Y>+L;{~+̠|[Ԕ}8 )(L~>”>>M'+׳M.l:]{9N{7?b!@@8`A&TaC!F8bE1fԸcGA9@'QTeK/aƔ9fM7qԹgO?:hQ4K&UiSOF:jU fպkW_;lYWѦUm[omjn]wջ]|x5WNʉo.39t%־{w裏iXyy|{SꃒoK˿_"o[LЖ\@dBPB k PI,D6I2fKlh SkVl1AD#LRIr b qqGB+&mzJ+(Ҧ%LS5 R11&2Lt&/eN= 2CMTѧiP<;̳Q"}i+/TLbI3MUUYhSSS_ui֕j\ETS emV^YU_WBA-@3MڕUZl52ikHgmU ]zhbpUm,4z2&}OX2qRwnaM N G,S)m pX`fR(0eP0@cs8Yb\&erNZi"F2vޟz@żK2Y`|.3 la2z($$l|wz93.ۅ7k'Œ&p$m z.+W;2n!fFpjaYo<\ A"h%XΙnb ? c|t^|pK 觗&ˁF>a_{I[K WL+S 9LcވEĀENhK0zdC0"#JGI#JzZz1^gPI' [*11nbQB<M:*]Аmt@lРXada ąc{R!8'y|kJ/]ٙYf$w2&ֶQhhG! <0r_=$"N;s ɶh6maBv3E&昌(Jx bM4җ(nSs. 38`B-`LO p04- go)Z&o}e:`ܝ{SsOo/x @-=8dK \a#^psM8=J}E|@4o-7kJQN0ӡQ:M9RoRջte7Ds#TxmvuCm]p;& W!y4u1THAzt,Q WaΖq>ѹ%{O7>|?«>A&pfj', b,Ϊ&00 0I/f!dN&eVF'Đ dPFexNOpf>F'2 !«_lb ] cncjb;AQ!T&˾cl4=fQ'FlT(xRRgue,!,a Q,qpe%p`w'qrgyQ&Aq+ q&0Gs8s@']Bo7s>2G!xn~g':a ̢"ѧ'q! RnG'2r#]#kIJ$''0%j&?!'rho"#?#y'2("iG8ȃv] e*U>'FrHA+2r)Z,:.waB(-',P'R%*h)0x(Ԯ(rB:$A#5@'s%&m i<%2%2 wp4!#!Κr32`2;2q5]Ӊ0}Yn6K6IS(&rwr3ij8O8i9SB.h'OR%3'Ip~3=| @.[i'33,`k :d>>Ӕ T&A!4B4%$4B5@tsi(AQTC ԳDxI,DEE T, nb9=9OGCLGkBHD4Q'4x@T{QUD V \U,hp' ˲cWR" #\.uZ+UWYDFsj*ZAgFW35\ c"P.5%uT{Z5Z,xt|[B_w~.@ֈAʵ,`,2R VtAs4+"vbybaYcpq'<_ddWt,~-KdΣ8Cg[@g!7\0*ZP6eMT(͉. /Ƥ*mj=CBkSUhv7B3-f"@m2 n6 nnl6M//pWp N(wqppcwrq-3c(wsr=w~8wtsM0N@IwuAt]1X7ve71^vbvq7wvy-tw,zwx"xxx*y'7zyzLz7)h:㺬{%1{ŷ$hC|ѷIw}E6:B>$}Bط~=B=n~W45m6-n4 ]E~ >~!>%~)-1>5~9=A>E~IMQ>U~Y]a>e~imq>u~y}>~艾>~陾>>K`Y Hޘ)H Ƞ!B`-ӂ ` =Hj`# ^!0 IA_ Bނ Ha!?B1 B7Ea#j``!8_ Q2 (J`;-p_+Uw ʾ;bH 6BGӠ!| CH Hݢy*H_}`#~`>#b^!8!)R2 <0… :|1bC5(dCJč9!5ȑ $Rb)[| 3̙4kڼ3Ν<{ 4С<ϐ*31̀Iqh ѭ MJ ׈Cul•պ} 7ܹtڽ7^(l1(RW_GR%Ժqݴp) :ѤK>:u] G"Q)P3Bd؄ j Ϝ-M6h '`8p+S:\C $) $o@ y.\sۜi2X#Į17nv!4BG8~4!SU`8"fHb&b*Hz!4)Q)CNAn!tR )ڐCk!VT8bAUCaWBAZy)EJHVXY $CJbЖ]*)VL B'|"l oAk,^iniv4'Qɕ1KQ=PUKq` bV1k VsGT)qhYB V .{%s D(lXQrdg`$AT뭹"AHntnɪk={*%Z&ʶ5/J k"Bw/efʕo|FLr&r:1P0))@Tya 2diP C âAQgIՋ4&vˤ@3W=P J(D!uR3Bp4BJC\Jԋ*R[%3BV{X7P$) qi7BY佐g*_yoι>2]05-0JB( A$M}Ay ek( FԮPT &A@b`Hi16k4+t|B5,ͿzP+5>Kߏ?Qd)`D\(RW7)im QF qMaD70 EяL*g)qLlȷńy_rPƒHAJN15Gyۊ5Ra&ҐQtH!F>rcâ cܣG ly%/LjREZC̶$Law7 Tz̰6Ȳ{RЬ4Fl~ LeiK3H8 Rl%(XFdL8B5Ka>3&9GMsl;7Dv#'^RHpcONfki=4ln| Q/I F6"^k[Y:FHUY %)-AY1ٷ#1ΥG'$%oJA;$0 jxHV1Py׼2 D)I/NRXGQ#`jsQFo_J5sJHAq&B.Bnm{xU/D\DG(nB;R4IbmBM;No+3Qe?VU 8@8,o=ܴ5e5ټ92 Dϓ q R4p' LMKTY=t [{RH+4w+)n.iSd:^uhMZ)/x1=%2 %ۜ+MQ)/(>. (LүT.YL2yQLM$D-G)B% }KZQ:/[I2Wd 4` )Z?2$;cB0|?B!yw|ѧ Ȁ*!! ICCRGBG1012applmntrRGB XYZ  )acspAPPLAPPL-appldescPbdscmcprtP#wtpttrXYZgXYZbXYZrTRC aarg vcgt0ndin >mmod`(vcgp8bTRC gTRC aabg aagg descDisplaymluc& hrHRkoKR nbNOid huHUcsCZ0daDKFnlNLbfiFIxitITesESroROfrCAarukUAheILzhTW $viVN.skSK;L>@>289 LCD LCD _irLCDLCD MuFarebn LCD&25B=>9 -48A?;59Colour LCDLCD couleurWarna LCD 0   @ ( LCDLCD *5LCD en colorFarb-LCDColor LCDLCD ColoridoKolor LCD  LCDFrg-LCDRenkli LCDLCD a cores000LCDtextCopyright Apple Inc., 2023XYZ QXYZ =XYZ J7 XYZ (8 ȹcurv #(-26;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmparaff Y [vcgtndin6QC&f\P T9333333mmodPbmbvcgpffffff334334334,@@x     # ' )!-#0&4(;.#%#')'+,+'*(= /.0.02/? 02417858:7;=9D"3I%7L&9O(:Q({ 11M}G,1D͐ @Sd2:Q/d>cDg=eHC&*+ @ 4*ĀbHXVЏ}qF x \` bFi@|9_ AP8 qU`R=hW@$]a BJZˆWŐ}"!3@ RZa `$vi(_A NAg+Hس0 Y4Fbjȑ9$J@B` v ʚX$@Ft$9!ɝL#dA8|` u#e:s$(BQ+"ƃi0a BfA.]!4IEZ=7Ar>0B| Ҁ978H;02#l@ԝDW !eFL@< d&H1 4X cAP])$٨Ct!EI 9=D2AY@FD6 6"bN 8$H@9xHʠFU@RTBnaeBĀC@-fG#htVvD 12r׆`5 y̪ljM86 % G8#T7:BB@ԧBn09$BC*2b ( Z>/B\P +.`^T [ˋ V (J`l@۵Hw51XWh{"A{_Pw!I!tAF8x!qÀT41!6<5pR{1%p2j[c^yYu"5VH3cy"K䚆  0:9IrB'S !R/',;^$CAP @VA N*!@!XËcB; gCK2H"ծ@0V `@'AiR_2y5@p1ՠEf]k`vVzfWF~mNw A!\Ӑeú@R~i GbSQ?e@mXir"d)HCOQb5p|'`64i1E-2j{ o r.`#('W@b G@0d=@ht=1>C"]iC/aM H ,0,{B&Q@VD,)<""x}PbZ[+;( LU2_f bߞʖ$DP "?wr{;x!oK7 *B(3P$df(ߨ- L =ٵT([1:|_>(?>A&~\>Eާ c>Mw^ wxzz0uJ@YoqRw 4,Ё,5eЁqKo v x @,7W+@,˳}PqVW,Gg[(XAqy=g?GXxzhCu!q%H&_}CxzB,g*a iOfA4{yx%9qysv(!qH9Q[~ݖWQyCVHUo`?oo`FU`WPdW@qK FNy/gK"Uz4>~XFBHW!T$z5h  !KQ!=0hzJ7vnt(>v`oIpoJd{ gQ0@f10O~ nA6i:Fx`n& cAQiЋ!+u9tiwX/u i ِa爉lzH2rv`CFFZ(Ax8eau@di!. E +^wky*Е!.CBjqq_)h4@ 1`[6>e7Zie|in@R(# "F!t`V)0 ~*`V #u\xdXxf351fzrhpU ) WePW6НuF6@ y5@ E !@06:Eo`#`c"QK#+{1XDPI.f)x%Rh5(PI(@y{xB0@:I$F&0S0JZ _~B^`]³Ҧr^W9\^aSZUJW5Wʠ`6pjzPxvʤ rCPpų!1"sp(SQ:"FS_7 @7.Y?P iyJ :_M,hᡧGګQ-hʬD/]3w゠AGtFz%emh⊮2zc GII pJ 'TF,[" jVS0sZ@u0+v!;w7o HS,aKkkd Zi&Ir A/nxqy&,[aX iLX!$>j;0 |p~@# #@B};patat-0.14.1.0/extra/demo.md000066400000000000000000000025611475634243500154560ustar00rootroot00000000000000--- title: 'Logic Programming in Haskell' author: 'Jasper Van der Jeugt' patat: incrementalLists: true wrap: true margins: left: 1 right: 1 eval: ghci: command: 'tr -d "\n" | ghci | tail -n2 | head -n1' ... # Filler ## Filler ## Filler ## Filler ## Filler # List Comprehensions ## Introduction If we want to keep things simple, **list comprehensions** can be a powerful tool. ~~~~~{.haskell} evens = [x | x <- [1..10], x `mod` 2 == 0] ~~~~~ Statements in list comprehensions are usually either: - _Pattern matches_ using `<-` - Expressions of type `Bool` . . . You can also `let` to introduce new bindings, but this is less common. ## Laziness Due to Haskell's _laziness_, we don't traverse the whole search space. Searching for _triangular numbers_: ~~~~~{.haskell .ghci} take 3 $ [ (a, b, c) | c <- [1 .. 100000] , b <- [1 .. c], a <- [1 .. b] , a * a + b * b == c * c ] ~~~~~ ## Laziness Yo! # Filler ## Filler Filler ## Filler Filler ## Filler Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler ## Filler patat-0.14.1.0/extra/demo.sh000066400000000000000000000006161475634243500154670ustar00rootroot00000000000000#!/usr/bin/env bash set -o nounset -o errexit -o pipefail mkdir -p cropped rm -f cropped/* for i in inputs/*; do convert \ -alpha remove \ -crop '1280x1000+122+138' "$i" "cropped/$(basename "$i")" done rm -f patat.gif convert \ -repage '0x0' \ -layers coalesce \ -layers optimize-plus \ +repage \ -delay 150x100 \ -loop 0 \ cropped/*.png patat.gif patat-0.14.1.0/extra/make-man.hs000066400000000000000000000106621475634243500162330ustar00rootroot00000000000000-- | This script generates a man page for patat. {-# LANGUAGE OverloadedStrings #-} import Control.Exception (throw) import Control.Monad (guard) import Control.Monad.Trans (liftIO) import Data.Char (isSpace, toLower) import Data.List (isPrefixOf) import qualified Data.Map as M import Data.Maybe (isJust) import qualified Data.Text as T import qualified Data.Text.IO as T import qualified Data.Time as Time import qualified GHC.IO.Encoding as Encoding import Prelude import System.Environment (getEnv) import qualified System.IO as IO import Text.DocTemplates as DT import qualified Text.Pandoc as Pandoc getVersion :: IO String getVersion = dropWhile isSpace . drop 1 . dropWhile (/= ':') . head . filter (\l -> "version:" `isPrefixOf` map toLower l) . map (dropWhile isSpace) . lines <$> readFile "patat.cabal" getPrettySourceDate :: IO String getPrettySourceDate = do epoch <- getEnv "SOURCE_DATE_EPOCH" utc <- Time.parseTimeM True locale "%s" epoch :: IO Time.UTCTime return $ Time.formatTime locale "%B %d, %Y" utc where locale = Time.defaultTimeLocale type Sections = [(Int, T.Text, [Pandoc.Block])] toSections :: Int -> [Pandoc.Block] -> Sections toSections level = go where go [] = [] go (h : xs) = case toSectionHeader h of Nothing -> go xs Just (l, title) -> let (section, cont) = break (isJust . toSectionHeader) xs in (l, title, section) : go cont toSectionHeader :: Pandoc.Block -> Maybe (Int, T.Text) toSectionHeader (Pandoc.Header l _ inlines) = do guard (l <= level) let doc = Pandoc.Pandoc Pandoc.nullMeta [Pandoc.Plain inlines] txt = case Pandoc.runPure (Pandoc.writeMarkdown Pandoc.def doc) of Left err -> throw err -- Bad! Right x -> T.strip x return (l, txt) toSectionHeader _ = Nothing fromSections :: Sections -> [Pandoc.Block] fromSections = concatMap $ \(level, title, blocks) -> Pandoc.Header level ("", [], []) [Pandoc.Str title] : blocks reorganizeSections :: Pandoc.Pandoc -> Pandoc.Pandoc reorganizeSections (Pandoc.Pandoc meta0 blocks0) = let sections0 = toSections 2 blocks0 in Pandoc.Pandoc meta0 $ fromSections $ [ (1, "NAME", nameSection) ] ++ [ (1, "SYNOPSIS", s) | (_, _, s) <- lookupSection "Running" sections0 ] ++ [ (1, "DESCRIPTION", []) ] ++ [ (2, n, s) | (_, n, s) <- lookupSection "Controls" sections0 ] ++ [ (2, n, s) | (_, n, s) <- lookupSection "Input format" sections0 ] ++ [ (2, n, s) | (_, n, s) <- lookupSection "Configuration" sections0 ] ++ [ (1, "OPTIONS", s) | (_, _, s) <- lookupSection "Options" sections0 ] ++ [ (1, "SEE ALSO", seeAlsoSection) ] where nameSection = mkPara "patat - Presentations Atop The ANSI Terminal" seeAlsoSection = mkPara "pandoc(1)" mkPara str = [Pandoc.Para [Pandoc.Str str]] lookupSection name sections = [section | section@(_, n, _) <- sections, name == n] simpleContext :: [(T.Text, T.Text)] -> DT.Context T.Text simpleContext = DT.toContext . M.fromList main :: IO () main = Pandoc.runIOorExplode $ do liftIO $ Encoding.setLocaleEncoding Encoding.utf8 let readerOptions = Pandoc.def { Pandoc.readerExtensions = Pandoc.pandocExtensions } source <- liftIO $ T.readFile "README.md" pandoc0 <- Pandoc.readMarkdown readerOptions source template <- Pandoc.compileDefaultTemplate "man" version <- T.pack <$> liftIO getVersion date <- T.pack <$> liftIO getPrettySourceDate let writerOptions = Pandoc.def { Pandoc.writerTemplate = Just template , Pandoc.writerVariables = simpleContext [ ("author", "Jasper Van der Jeugt") , ("title", "patat manual") , ("date", date) , ("footer", "patat v" <> version) , ("section", "1") ] } let pandoc1 = reorganizeSections $ pandoc0 txt <- Pandoc.writeMan writerOptions pandoc1 liftIO $ do T.putStr txt IO.hPutStrLn IO.stderr "Wrote man page." patat-0.14.1.0/extra/screenshot.png000066400000000000000000001455541475634243500171050ustar00rootroot00000000000000PNG  IHDRbsBITO IDATx^|Ugf{IqBn( <{(((@)& $B=&wٖFvf6Dٹs9sE# @5ñ@  V#Le+z9jw54k<~AN>}@<!@U̚x5JhpK]]wd'[D`$5 v>?%FͻO]9ʡ:vt@2w1*YMr^H2VfAkX6σe 4,@k7Wz=&!*cG{XNOGdQvAŃc?!и? }Y뎄nץ(G*1f31>@3ݒ#w8b4EvS-~WC_2V8wϑM?J_X+ĥNRvWtFݨjʄ[iWWG{#KCg Ɗ03kuy݈y5g7(a&T8{wCh22b`V>Gmn61uS*sP`l/g_G׸y-VLX|Q)8}ˍ5(0kB +Mӧṱxºyq}| nׯ^YJC%gzy>2QzEbעҖGUƻj9JVϙ>@U,iJq>p47wfΒ,5IbNcrMA72ASQd:z'<5%:esd!?Tpm:ҳ\3j8xV̋UU%5RE%oՇEYG[Eݭ{ȒDӎTdsZDﻟ*wuV"]Wr W>GcE*YNR'blᖌj#ªxɂUzgԲȭWubHUvs*|,'k ~sV* v2b*Bq5s{BUžVbC<|Έ9|a^3%g4}_CX7 HYYTjl@&G7yy]ݯ0˝Tjtۑ/dH!1j<|~TM#q(Q83zױ"y+x; _,a^kK֑dQmܐ*3 Ws0\%o5𰜓YGq t;B+PEJ-v+cgtYNO0=Ъ Ċ{ hصQߏy)okZT'6@&601֯.0Xv};۱z9i"hdK@g"Z~vvJ3N@rr:ZU=1 yc"@A6VӰkAR#S&~-wZFj̱fS@M?Lx̊TĦ:P$ Q ?_(5ll[Ԁ8:*G֩CHnr V X=)RDnt񒷶[::uvu+0ѭaKQαjFsD|zeyF܇v:7M QMx-w3+"{X!@>@ 5Tu"^NC`n^ֽ4ǯ9^u 8q:\H,OO<4Y*ϖhR %UOIEu +နrM5-!n dV^z`\a&EZ"hU-Ohv6lx ]sk:wK^᤬(lޕsL q2LZWWx۱ @3@ CN < | !Dg @y@@ :C P 9( @t2@ rP@<   ( :C d@<x@ P@tIuv@mP@ z(( :q @ S 08y|ˡlmjOݕI┱^/m,{*A7 d|C%:JcV'R)gm#@ - N).ñP3,,Htu$&\QN_pAvˁ@sPrPqTsX*U}@qCrUݦF! OĬT0yς@30n_[@?݇Tnm*4m`p6U^v'\ :Qy[<@/n_%VXs񋯗5fO-ݛ$1zrДHSU5ag{H ښ5WvULaQsV81jְװo"@KxSVҵNRcSז**Q5%%{EP"n(6^݋LN=фq;-x\]X/0/XҐ",ר̷d^sd=+?Pu=Y%ȹ&J5#U_^u"P]R+P19:`| G0 If:65:PT7դg6 >3M5 S*ɁjKn5LKw>&"6)W'oT# t1 M*Q-ELtQ\L5j =z;MJGXvjVqM~ҙ&B[n p( G e5(9w@CKr qkH6i&ܮTqħc[6۱J @0(C : 庠l1dj۱v~%$"k*4} pt5#Zey,J-[у(u8>/.Ӈ晼U*tN^ Pl'^ozBQ3(RMg @03Æ\vX[7ݓ%{@qދV}Bilо살)!$L"vB*v,9С|O'c":u܍JA0C)ގU^ ݇;V9|SxGkդ)_ͩϭtT9xi/ܰb{vhv #J,FZCE!uGn5d7UY&4gS]PpUl({zOÆr0ʼRr`ͲD+(TYx3G "S Luɡ -UߺF{=SK' ÙųfCvCZ\C :aچJSKTu -ctuU5_̽`@N^!K;hޔu{c{DLė(uRx]Vbà'w.kv-M[SKrp/i~hۘyIQ]^Y%֕+8ae1Q7g0]#NXG4ma@ @OYb tYU/ D}G|Olh{BVraՃԞiz.:,6pS[ڐX2S~0#A|HΆK08_4?/G)^[: d@؇KQ@p}g?˼Aq47"?u~@fAw e& |;> Qzzzuw- = ?8 @gS=;x @G#=>8@ @@ z*( zjρ@  (  |0 @ @@ z*( zjρ@  (  |0 @ @@ z*( zjρ@  (  |0 @ @@ z*( zjρ@  (  |0 @ @`>@` -@m}䘲Qf ۝@)-(Gf:&s'px BoCtt%iڽ;c4N_/.ۥbNyEݻCGyYPXywSv ol2vtF; !zR7YREI h=Y{EJ#h8؝50A^驢; w/KߓkzN%tP'\.q|1IU qk ل.7JȾ.&}laPO\gl3cn/>QYu*,ht:^'t1@U(O ؖe\ȈٍYdp1鏾c=l#/_ve]*?iG6ö}|L{L-9.[朢'x[?G?OmѲG%N)Pf8eV|q7}/h"6kϪ+Q^3 *roٱP#]u<}#tc#_!x5Yu1'=[K8xd-'ƇČW_kx_?Uw 5e Jʶ(;F>w,ؼbnGٿII?_X|he{k`%*6xpX^xkۆgW'GPg8<$,ͼZu3M//t%.5]afœUe55::k[eLCɊ0S =gl"[qC fYuqӷɛ֝ZKB˚/S) }u"Q]޶O(U/X8.1,'uj1 Y8Gyf^+2Y@苯-[_Lٰ|L03IW *mXe7|vƄ ꅷ '}M~+f +ђvڜ`KQtH""rڒ]7,ߕVQY&|8l["?g٪t`6)dI"w'+Jl8<~\|D+ìqO o~Ϯa.Zb8L,7h/rͤNxvew?W5uxg-Aڧ*&͘pn9%-^*9;i~~+MKhoVP~|"2qWz[k=֌@h6O:Ҁ74k v DK ;~Ѐң6+ ]oH=?%sin|Ehqap%(^Vzw6qu SbU\]!peht*ǼQ\ݒSQfq@#[\uQXNGxWnnGxڪBbe+ %Wإǣ~e:""¸nY]XX\dS =ķehf'fŻ=&6l:}SX>F{ǵ7JET,Eu~rrrl^ xQ#}YSv-kmYyGҶ|uk<'iG`+׶zz_Ϩl+ģW9Yj[y~֕v( ۾~__\OK|U_$ v' Cþʤ""~ ʰX}𾃹%MүWwTi+PȒE%^Iv Ҵsу4 lO]۹D3`yrS";.ܠӿ-5/5vh0Ik wF[Rd~mɎ=WOJfգ1On._.,|ȔȤ37# xSnFv1 ..rr?1~̂1s u(; z4y)k,.hD:u E</%b׃Vm*ݴ^[h98GԀ([v(62YvD (v4 |iՃ8FNܹ>"AHMCx;K~J%FGt\QwR8PV]-2\ppSm[1 nEQqo(#BC%0H,RQqZnAZnaF!v&)i 'P3 _Znm7U{UNꮯwԼ/?ؿl/sӊB T3,fX=dz$h8SZOOg:EGmjXNJ#NuQfV1^[dsKH%5%yFO4UyU:'/7ҠHT"7Bfez2"7=՞zA3C[RR!1V:: )Nu~5ӂ8.0O]V5]&qxD1sr1Ko_UISBQ9jմ*%rdp@y*=ܻ)=dSU> HI^QmVcj-pR-U s;CFX,ڛMZDTAT˱+ MwCA؄$3a\C^E2'uWO=ҒV^єB*; mqRX3Uܠ_ eV6jS<|燆9s|V[…ݕMh쾯h)}kSw93qi%Ƽ?noo 1{Fɋ# IbwOоQxP8_5{A#4)?2_؃Uk:WP$=G @o'TiW-sצO. 21eM5-qS:uFqgrbCW}K_)l3bYU]9RO;! O ,Gܟ4ɬ$"xDLSMrMMrDq*W/9W up۲+hs+Ҳgsk?PUr9əB͘^QIHr1'"\W ȼ:C@qGijdzCWyǭ\yG>uGW_Q]HUzOmܮ:-#O?|%cgE|jvҜ<靿 V{< SNnB#Ey?+ unrK%ڿ+%uDԆs甞p{V#xKEȒщ)sdžHQhٽ_~?ʸ1c8쿔o\BrB#9EY5Ƽ)˙ tflAL$]dXC\:z̀SG#zLvI=66X?zyDA W 9+!٤Hrɽ"o%_̥s]>neq^a)'=[1a˅E /G]P"X>!1trI ,.<؋fHhK(r9#^}ݹ~.48+AZ=iV-d7]޾nd֋$7%P˒%lh[!^것[TA m1),E{EJ޿xщ_wS/?*8sqi})°G絻O[2A^KZN[k)4DWw#X,ב[E9~,n]Hx(JPɅWc~n{_cOcϨp\A] PǨA> qs{ئl0{vrp]ki{ O`f@ @>w!\Po"0QX2o wD("=D` & hx0h@{<#@ =P@G @@XM @ @ `5( F@ @  P@@ Vjd @@9@XM @ @ `5( F@ @  P@@ Vjd @03@؝7e > >kVo/vwm`1^y&:f=HEu:QbjpE{) mȭO7x ӛ?8ݡrmOkvv.x#yyn_S r[U M1 gwιi* tu9}/|⣅ wsS R]C@'ϒ "k<@ pBbwo:#ѝMކPZцE#Y{V-8XA8MGsĨ L;_"$E/0\GLtdd &t|m!=j?$fDX×;f^Js[&iԬ>^n؋}qѹ?=Ud:II#v1'=[K,'@qJb1q8U*I>Ar/RK\{iRLҖ^eΖm[,gkB uUݽ/v$/9Ia6e"b&vjV3_UYdPc,}[fafiegK^:;rlPs `]OM bc$8ޙnϏvHҳk}YIMzL$s7lky 6 $5Wg)eY9Vu@ fYuqӷɛ֝ZfYEVE I*^B9҃I{~㍝Pr&1M=)W?=h-רZ"I4,H-bSdN*.kBLΨ0Q'$O*똡#y;\L-gIr*ȏT6f0H8TDS3 8'=Ps+ϺR&Ȇso'ŵ1K|8dfb/DٻW/RkB9B.2|-T͙ xDr%C[(dɝJohv-wڕmؤ.Ev.q>G]W8S)zZ٤QN$e8Bomٛ s?V=<MݑiJ4X&#sD-q^L=}?5[ΚdqPUH**)hHC64rQ[3^}liZsGG3HKt"b0Lsʘ~W vmXZT,Dd&:]Ŕ;S}n,Z~[$mY)+jTvݖ,[-5yeJnPE1'^Xb~ WJ5!##=]?B:iaqNvWX]ub+o4̞w)xDMVh~a)mWT49~}F YJg5w$1gIku۪mM(;riIZ>"?b9xU֔^a.~aE9~t¬tVk|lw~ y] IxzL`/?b /,U z,qDhNk7rYx~n^ ?˗{worB\s)Nm8wN .{m=K>"?ӲKEȒщ)sdžHD6N԰Q̼NVbѓ{n=/Hl9$8r1O-;GXݖa$[{\%˙C.m gp ˟tP-"vrD eRsUǓs,!k9s6ƃy3,^<4Ri<{k?"oZF]k|_+0\Zx௃=e+UvWiW&2=|{I5XiwM2}c` /OCt{=KA]:قjFT cL^M|I.|g겙w&MOZ*( yM(r9#^}ݹ~}7|Ƶ4OM<ÇzsAʒkW矽%=zFmۡFR&dQd|~gƿ`Hmэ2^~K0Ee?B*ЪG+|2J>#߈꿁f9U$-zqg9ײnGz^]; ˙CΔir }q/u-~mYyt2" g%^nḬVֱEa1}Ьxɯu>5\~9s=* 􉧎ΛU6?/T8t|"+By?j[{MʼnԨi祱N_p7 -+7X㱅ͻOҼ,[(uҼUpC2XݛHtgabΊ3Dž6|X(]`B/y7EPj+~RT\):=; vs/M @DoTQS;ps `חIJ558Ѡ>^n؋}M0aWݕO<|a}IÏp/wܽS-Q_X:!G[yz)ijgmޘ$wm?ߡ 2YC1konݾ;)`<UZ#(Ӫhsg>UU\߲cߡҖp #^:22̅P=h'k?Ee5g$Wo,h"Oڬ=0@#Ʈxb̨@GkBg_sBOΟ47nxdo5x=1q/?973qQn\V۟kڒOz U7/VMⓗ _1`0ikkI~¨;K S =O(U/X8.;f~̵KG]x̊Or1 {u5d%?$7qﯘ=EGX`}nmݹ"F|dǮ+=Òr$iuel\.9ۆe]zgj+5Wg)eY9V.^`P?xx?"Yc=鹏YyRSn`>󜀪S/Hܼ;̙nt|Ь]˺J]2vHubr@h;ڸ>HD78 +0, (=:oShyoSǩ#-kL!hKX;i[i7[{NprOK}iXOgʃ8嫳Xc >ΐC|+O/PAo6y~bVcatN xc$F&pT Tߎ7]ZdÀƄ%%i}3;:<޽j"$ֻVHd.q&i✬ @έu-nP$|ʨ-2۳ldžGE ixm/T$;R%Nxj_ǮbӁJEAֈ*0˗}+޺eyR{wFܣ@Rw 3ioO[Ww|= ؁N ":9MVCg>8uқ(KlQcOb4&j&c,1j,h{^kG*p-SYnV(^M}-o 7R >I 1\E}]P!w"AA4) _,fyAL<ۨPMmdG{B9p^JS2;8 #SS~E_tmC5'Y^rzpk.`c ) K{ cƍ:Nz)T@LW^_#tRŪS@uYò/4sް@@đ&v G.-̎J.;GA G'ɠu?$?PR8tWڅnC$ KKZaFFMeΉN)YH͌KvgiY]/*?:! ݗ뭝߻ϳgPY,IKK0 V51ݬ=MGYQY>[#,@\TǥEh3WX>qq9q}G7D-h+1"16eӽ&;+T^DѵiQkI K.8#;!/pA΅g1}$+3)KG{Wڮ0/a?@nȼg؜~~IOt&N耰xZϓ};O380a{! %.7qBTC>2}=oŹ@1;k3E"#f̨}<^g~ pqq㸎fR7Gܩ};Mt-lyG~%oKz΢YoYvO8bHo`82Ky-t@(h""a,}%уaJ.}Nyo?i'틮7= PB uޭZSE8o7Ga`}cF^x-5UCȰL>nW, !/A!^tl3 {]9~JXP@3htKPqZ LV`$?:(,kgL.=-i1Oϭ-` K2Njzz}1^s;w|N#a*lNu;oɎ3}IN8v.߫ =7!S'И~o_M0©T]fKTIS^\PWK2܎/ߙM~d"z,8R+F.ݣ 3?9ɓd[?zP?C:4k<;#ީKUT_-/K*Pϳ$Kĭl>/e=}ǘʯQC d2pd~d~Û܈Ry4.RJĵ2c>6l~ROUt@7=o{L ϊk߿Wt}?}6K,ع䗵nޱۗrW49+O ?+ߊKfzخ=Xwԭ ւhR@$Jd@o]0&:4wLK8D^Gl7~66x/'rˬýՋf6O#K+F+H^Rldu#ޯ1Rr';N}wA7Fࣗ)M H{7]~NxjFIyrzw )1̥wI>Hօ. IDAT9[YL/{.Y|Wa02:Ğo8ve#*J u>̊ց@ 1#Lk]qsZ#Tuko( ̢`x[pI8k?ae_^zYݙrC%'4v, K; 0 8xr܍>t5cƝɒȰTf7!a"VmtmcI 4cdOUTtݔk/R9\x)WY}eVRPIe~jELQR_9y%>Y'?!WrJwђU+ e ksgu) rJ0}=ޮLYR29:z::dvllS3R(oTPS׭LWf EO&b̠RCO--|c1 {=6c޹#=Ϯj )y'^5K}TQ= YYr_w{a Նܔr⒊,eW__-eDaQ6ԭLv`¬ʎ:Lٹq08v= Lj'AN Ifc/goPx’B˕[]a[>V|t޷rr\KD$NFx>*%E|BzLtbrYX)_P JRcVATH3H:K̹S 83'1jix}L,S$QMCMq?0fjw';C,S{[yVDգtn $^ӓ[W SQwUjTc^5~Fb՜´$RVUle?n0w:s|/ B#_\+d{f8u-0'#sofH}L:Ou_Tǟ((*t 1,EiF7 ~=oS~](2ܲODL?wLԽOKgeNx3kn#v|P\'me G׀FՎQ|a=@C,9wk%nhDFxcO ]N_wAڛ^%Ԝ%&4Z.r RL}b>f1O$x|Ht~V@X39Ϧ6Nţ=H8/O 5;8 #SS~Et5)$㒫&Wg[e8zZ˧-z@(\Z@$d,9|΢^lj=uBO_q3ګj/NI Jk:SݥAɁz,aٗT=Za+M T2i#?aR!oA[gX,8ip0o[S)s pGE%a$|@Z@ejaBdԳb(xKVuWl z(t# ,%:40J`=F36S{ĝzdnCEU\{dyıw ~Qy=ҷpp6m if߮;]I Ψ< EI Mt?դ琏L_[q.Pŝ K<<&56Ԇϯ6N"?_U\Z)+޷%"cs%=Mq8qV'=_wfqn#F-a²$0($Z3wP±D:SP5H\Itw߮ ]t:1f0 xrg|?X8d踸q\Y3}]p,ZU V}2b{7]@ӵ>~IJ%cZ'{m١(}n ^f/8Z@Ō{-/.+z1YG-"|K6J@u 0Y(U,%|Œ\(5 ̅ɗU5 ?lu%ݲժG:k 72!xuAC*Wi;/}2x^_*N|)wUO(R)mEWa}%YN~ʊw}ɴc{3fҴӷ8NӆY^]^rhW}wR Ȭ'V\8 ¸g/]i3Ic4 yd[~ΔGwmƄa/M+oN=}[~=zZ/^r+OԝQg/cozJY~a,t_ f]}URS5rҧ >MRgRX#kQ!f5͑.fXxtn7^/h5U$/2,~= H-P$ʆMTzMб=C04wW_o`N=fi>YR@ hDLH H~tPX:׶Ϙ>]zZb[yMXFΊ,M{.u`oh4 aoStm=շ5-ůw?l|V6̶ Xf\41Ɂ@S!<@@@dz-`  P v P v P v P v P v P v P v P vj,{ //.JB A@B*  B3*hZݼP9ЌqTA@@@ ʁf Ќ+   Z-V7/T@@4#f\!Uj yr    4 V @ @WH@@Zn^hF͸B   @huB@@@@3@hRB*  B3*hZݼP9Ќ@ pn6 @ !>_wX+ج>\5@S@V 7֘yjAS cl%Fg^ܞz@wYhOZB ˗:)2k7dָ2h̖@@_E0ݔJm;a dm/7zAѿO'=V[@@@KU%7x5NY.('LW/V%`\z2[gG+fO39mP{㒤G_c/sF^BCw-Vfk'çv`'<\ȸ5|8yI.]Xy1:(KFRQ~e7E֏8V2+p$phٱow={0D 9nֲX6"{l9uܽZ[VhfF=wg-ݶY(?np8'u'߸s_A9dLfgvQ~ݳ16_9WhrX⳹[v*> pQcQG_}0~ֱ~}JidΈkiҥOO[|YjkOc/JzbpI7z]8]V옴b{92c c=Wn~I*+c_׫g lFU~aͰ&9 {Of{uWJq@@@AeG32^ D#/8&, Oy2%€ޢ}gu7)=kt32sCEQ ^V+@FxS_zNbWm3F_?/7߈$0b }LU3ҳ2uC8)⚗l׎>-/DSOȬȊń'1H5.%e0Eά ;ݘVFQ'8i\8}~ŰdTa fh >1tlLD&p cH ˜We‚b[qi8'ǗIaJx,JM)KnюS1ّ*}FgуY9btZcuWˬxwܤ5ZvR'SQ;{sUj$MXr@Er0ê6XYo qӇ4U{Cqa-T? htU7P/V~QńB-d #˃DON|uUȎ1tXTPKLX?E,N]Wo jt$îwj ~_کUeV\o䲱R>RI^L+v&^6*X}δ1?5BN@@@>zƚN?os? Dπh`4;)8ɑ~y12 UHB^AOvyKg.n/8,yv|S v^ST!Ee֠ƎS&n^ƻ$UI U  M#Ь5l5D4D!&dS!KJ1KٯKfVuOeRlF%])@2_⫛t{aޣ uw1K>{˩+J<Ȓ԰)?p~{q eV\N_\.W!QN:0#^ V=m>C"_7]Lyւ@Kh9ԙښ|gqƉ(c"16eݾ ;JYS Dұx]$S1sK3ݲhv Cy:Nӥ{8]Z*Jk1`wt ~zԫtRXSJd)cI1 +WqDNX"d'2ҖK~nٽy\^7AiɛK~ zP@Z_)8kҔ]vՈFw4d F[goN HUi.dGRhxGc.X~'R2Llct[=Xv.ކDQZB`W|'5m@ K,yaӇ40413}p,‡ןyˡ6G6?W[r~g;_y~؀^wEߞƇ?0n7Sxo.OY3kP6hH~wkWKX$1-mh)lʤ(X  -O%"xџn ł{nkΏݰQ?+yCˢ:owi~5 g֓O8}̹ c|#e"r!V_Vy"!gƬY:DkԨS|wVVLxm6x1_^&T ӫ#K h-e`4Y)6F:߿u^cMVLH}1 IDATb) ^Zsk= m(7C;!hLjރH,j@b -\@rGL Cb$eվhCŎg3}r 1NP=*+}%(nݫZ@󻺹sn ^:^ȎOKzʌ_8ذI\<_mp~FdYvUh.[5V . w\-~W ;Y]ū{t4Na}%\[|o<ȿA3xqj˚Os/k#3ܿf܀~9ua\tڶ%f$X~aį~m:m\ȡ,=c!O7)0j /n٘G)6}mxxg tYô<]:c2Nեann_q2MdvNzL N`9Wv( ;)/ ;hU5\f2DXmn=e98XLWIN7ʣҼL,5;^ʼn~y6{<;uӣ$wkցܼl9RnF?y׿OD'<X[e& n{9wf_w㍑;vV&XfdfPZ_I%iz rٱ***gztFg8D2<Ǫz].:LܻoQq2c' 2-p]rEIH?ILlt˻R #Տg]0YŒ:$Wh;*\=&Xy6ޚRH~=lZCz<ޑHȳ'B/KTµkQr`9JK/-MRZgaL,T.{[T_V elDwQ,lUȕ%*YOJ$# 8(͖FvDaQ6UJJz~dcz\6V\RPo[Mi(/&"KZf~aVieg=cP]\qnm'~RحUi'9ېL^Jny#s5LMϯy9ArêxzQtQ"R-<%KҳSOl^?t\QM9YPp-U $(ɣ7ueVm!OuϚʊ|.~wi_X  Z@R"a:#Dұh{;8q}z7\2׸T-XQ6PI5 L=՛׭N/w+oME\6kڔc" xEz#1:7h / XI_?a 7d gTmx-5GBQWV _$}i84pu)8&9>U;zi'xqYCrہ>@4*n0y:g>xB疥}dnA1^2\x6(%+eWp{3-S?}./{qwF~,ٯ_fуk@"q]|Ded+cof b}d^d ".טFT:wgdv]ޤ @ UrHĞ:珯&&)81_!b26/95^b5tWW{}A^trNbF^VfAb,rݭ|z9]ƒNk v0=zqrnzFjl %\}߮ wyXǗIJjeܐ[QIG0;rn+@PT'6c4GU-G3WX>s؊jNFqSU4T_ 7l_|tCĩ߂ΚQ"` cSX6+&m]p㞻->n}8Q~W@/2.Zfe6Ut 'L 0QH5 T O3݆G_}S>fq.fQOTO J$b0Zc#$ T08f7ggD7l8ŗ& :$x9԰X2/~ pqq㸎fXtU!h@4@qZ dU0Y4W?թ};Mt-lyGJ>:EƳ?vq;mD'#b[QL! ʿ׀`i.{yӁ˶3=嗚F`ǹX<{$n}Ӭh+PUU{ԶЋwdeOgKu: rh@A;Wnn Uc;N,DIvɪ)9}[~=zZ/^r+OTwҫ_,+V/^"䅀y? vO3i(|(j1VI7|=l QŐnTgՠnX}PN}~~ʡ^|ۢ:\Uc" ^wR}i4zHr`7zTQIe)6G@LwUaBjeFV&n 7S4q@@F j4֚8n2߯5z4aeFa HCSmm@d;Wذ1Zp|e ^*G&hή@7}෉W~o#gk^)*9EB@ y++'y!A#}ձ˄0~ʢ"kڀby޾+'wBcM ;_zûWVWZni2tۺORFC2+x!YVu*& Т h!yiw <{|[WN$?MxvEܿg5FfZ7Ք-8HP6mP+ s,vRW뱣Q~ lX G0ZO[.)2032sdޏW{[]6z9/[ ab@Ըmyh8Mg]5^Xs?,tmN !3nm- (! Dr6tܠG2z߿\$avg\P@ =50]ߝ,muOwNn_,yۃqww 소eY~1w _0krhFnFz,  Ϩa H!i{čuC61tE#F"YJdTVGK_eۢ1ˤl La=r#<@h) ~ ='kƭ9}WOmkᦁj 8~I~e?R/nO=r+ơ#MyŠ͹1Gi[fnmk|-ٲMŭ'YT&lNLE15,<5KZ%s^r ?'jHfY@M#mt(oqI4' D#J(_pzR8{ y,.O*17D/?͋qǽ Q0o0QxAJz*"҃n>Տfi^ӗ/ؚS aC?,PO@Lm[3; \풕?Y0gIZobL?v6?߯O嗲8iƝS bfgvQ~ݳ16_9WI7z]8]V옴b{!I7C3ǝ=;V'2khˏ{-n=cH:@ TZBHV?Z;6EZ"z\&Z .H:$oJy FR":I{c4m18`Om$E8İdCnPb 0IW$} Mg s'}$SCX=+^VYF{o{rL * Cdջ<{~ 6OK|d`ٱ**ʪ|Y@`meBk߽E;L9ń'1HLܿuԕ.kxA$ 2 22#d=R8.ÐA:m$+ PUtҋ(C8'FFpLT4h5&JwT4B\<$.#ZoI U]&R%_RH3Eb9*UCH!Bܥ8"ϊ #q=)ڃuH;sZNybZE+(m_ib8L(8=%GZq1AjrxzQObd'al&HQzx29(_"MD˙Yc^IOe‚b̀0tCd'l IR&M=/X.D:_⌲BصjÊ-#n@14L<Ĉ,ˋhA6,TK5{Sҏ"=#sj/ CjU Ž2pznYDe?o#wa~Zb/@T˞t0+~IYr8ƫLd>0k #?4XV0QX    Hdԩ#N.ymX~-_ B\F F􂲜ϻbPQZm,.F a #*|aQ&WF*F?YTo oRR} KxU_Z%}DPV,zUԶq'?dqjąKwn2Eao97^dnA1^a '\U^)JcMbHd]H%I`T'FWiwjhIvTQBbI 3>(q_)NH5y5O% TA^Mq.']\pDlB™-TSגQ=Q.IA환')nSwoEKPŒ0q'1 FU@*(ypjlDlR\׋Xv@R!ɟL2w +Vm#l T n7ؠtywU(uTk_8e@Fj0Vu7Cҏ)hWw,#D&v֜ZAnD/K"b.ã<_u-FP"7ٝD{R,FfFd'gGKg gp㞻->n}U?T6x! xGw/FΓkՈFw4d F[goN HnP#?`oI\x%QFjⴀ ÿlL=Gx^L$BD3|4"EFR aO#FHyע'8+Y\ALA!qɍ =F HAO-P&;Ѣ$ߪ}+?5BD"?~NK"ȧ4/PVY b8{!<9D'c[Q հeaBrnyat=+ @5 p@qI'gGb?|[@@e @Ѳڣ!ᒂ=3$piH`_%7780!GEĄLQw#t+N]~2)$z$']E/R;1}'4 qWzE=,_Aȝ\>5 UցhQ֜Nͮ0\x9FK[YbY`]T%W} $=%(O9Fy8˷,wA#m_Syv=lC<ۑX,NCK%ozE"K,1FLő+NO V\LNf>   P_<ҍ{ŌJDTjo .)dBTBvѰAFɷ,eQa4!y̞%R%BޓiD/Ex[#ba8"Ij%2]5؅,Pi@  {@l^备/I} UNDMy_$C$V!0amH!^SqFUEEeiF\HUl  JB$K cԹW;cHA^2fILVhŘȅSmA@@y!O!e$?h/Tom@/Hh4}Na¨9d0ƯYEuwK,:<)]~r@:Hv|>eX`WCXd2jn)0,8۰*ѠpH'vMm1ywYAIFVN㞻->n}e<,PQz dBcۦZ'~]N尌SG)[9!7 'uLY%.ER&;]*ha923irJ IDATňy+A"̋Ґ횽$tknzÎbA?p*َC 7 4J  03+fAg2ODŽ_s; LVhEb!LiEE )";)6; {ӿ=$)`$f¨ BƤ\tt@Y@@@@@ @@4C[`C@@@@5 (HLZLZS3BYA@@i.EQFJπM{ @n дpFzCn -z |xMU:r[NR/qV|u?/$Oe   -Rn3~ƣ{J5Z|A@@Bx<>0z$>$jZPn~P`iaH:-vYjz/Uz4j_Q2[x"snr3fiwO4N:/lKِ`䐷s&8tqé@$}-.YopݩTFeo?nudZvVٔ_:Ma(o1;_JX" ~>;bOE5*+l̢-0@\\߫[.E|&uB>W|9LcZL~1.{뗯t%t%;#BW;Ee|4GQA 7 7%%Q'%Ŗ,Evlc'ݺ̦ILwmfg:kͶv6ٸ&YǒX%[HI&A$AA7pw %p$}?~w<"Kih@Q3 o\yl_f8ܐiK޻+07Yyy19_[ TMrmީ} oO-E$?[=}곦YJ3l[Z@yU>d??n;Jܫ|[wA9dO|K.|RP( Kxp۹da ӫ"OoԽ?=Qul 5k_j(B8n@   #>gXe/x0̂ge_`_8zI^Yx$sx#+'Z G(`)(<`P] Њp$si'%_<Ђ$6(T"glգBxGg^FN;1Nzͽ2Si<_oAܯj| Ģ{vN9 g4޲"H;<6 l[sϴ%xEP(&Nqzura7#W=l͍deM6tb952(K qq@@8 +͊Z옾$wX哯7qXaãN5>3Bro2NA?0xޅEP$,U.}րE?ڦUhl#XbFwM;%5`Flf)2F4w=d~qo}ZsrƵܵ W*x@ؽo SKa-ސR˟$z0l2vj cm 20AtY0Ls/H#Wxy|O~WԬaoNS(glYZޘ-L>o(Y?)j $p}ӗЂqX<a樻hˋ lDY]FFF`"cbc^S玘K׮JqmVrʭ_o]/QˠhhhExY+߽Sqqޗ̊%͏Jgd_8Β6{ٿEF 'Q"LA05@ϟǛ;KVnAۇ~0s<Px5e~(au#?`kJ{\0G~XC04@<4w)#y^=sj}Q,(|cDmq.VFFF`#wv444444#a-暜. F*Gŗ|t>G@,5ƁG͖F߁@=Wk.;'ѾGŷ44o6!Mxtd.4UE 9{ ?urxR{z/P,>k% S{/aS_1O 0Q bS)YXE74N<;pTh}'KH^%ꪯ ˍ#uvt)S'zihlI˜%n=EޙpL )K wbg/;ㇰ]WaL7H C_ԇb>F z渉 <҅m:CN4vxiQif4}|f3u%q*1h$e vgb4βvJKu5olSwF:>:lޮ3&ۥL2R@~DĔ5}\.i&l!d(ŋ t` &>6_w<oHɕYa1lS2(sz#2v(+U! J{^Rŧcݾ 'Q{v_yIRz|v@:q*1e;3JVGƻc*]0ʛ Ӓ8RlJ9* 5]0ŐRn(#7Z-AۜY lcwiy4.jdGn-x&e,k.Y@YwЇ(/)Lʱ#֚jc^;7ɠ.pV$3#1-gj| (0Rh?4PsoNƄG޺xV1mP(v!ՠ VQq b0@Q Da)xh$ Y+-yj AxR;Uj:W}łJVwMw# 9ҏ?TVΟglp~tJ MtCwœGZ>{ 󍞌vpm0L\1:M=SoŦTm:%B'_ #^9}DÕ QT5LQ;"d%>샫Q~ MQq(qyۖB_'tZܛ@e< ±CK 2bD8Iތ' Jqج';FDrnF[.X,KX%+?c57B2bU2paaX;!yz BYKݽ]O \60'N/q*5ݤ(LwK ١i ]o'huyMHqJn&8=rjl=<Ϲ ) :^{X-jjYФG*of  FׯLk^-e5'l~%6ׂ+6`.l!Frp%Givi-mBt'SZ:hV/2lj6+?Vަ;fCdx¶ӍՉLmT?QjI[?bgeNՖ/r+~=3rcwOI܎0{$}\fFkv-r#D&&@LU`F$D ~)BA` n*XyS,2z&IKڛ0&1V4 G0cX@ZV!>U@..t<(PƮ3QW^نFK1*g<PHxaG(7qd팇UV*"$q*@G_/>qd Q[oщN#.=4pHʋ7&1s#Q'z:DH) qD2GEEYsM>"_2^?p|?/"U|tKV+Z `ؤ 0vC\fӣniV9ܤ)XW'*@5/8-Cd"CiKZTŵwt{tB ~h^O E{'fMnED<0TOmw'2P-jy^o)ɒF}DԛˏU`.6|{PMst4.T`DpHBX]Hqx$3^}Nۄ@u 8 ڧy܊e"[BI@E'C>`_ǩу,X^;@FX\7LR'IzwY,&zqσŰb;>- IDATB>xM^f>T߄MGNφ?@2t +…o Σa\*'RhYG dP>/B X>v?7oah2=Q%N%g9HwcbZp[W1PN`F -3ڼASM] al (`ͩ"l&ݰ2LwGυ۷{Y tUNmLb?/(!6Яku1Xr#nG56N ׾ds?B7A0mx1jF(W@$W6ߣ~T?=!ʺ8Xe2yb.slO撻q&/Io/ߍvtҵv]1H}KXD?lvs(0|r>`pp@]g.%yOxs̰i+#? i{ΟmK+!I͡ɗ)i)/]SSioQe?_qyKK* A!- oN CūNuU2+0 R@1Ӧo֟hHye}^y5H,ˌ9=a8*-K'/ޞCѳƧnz +C:أCNuEĩJn(K:afZgߍT0`D s5s~*KOuS@lR囜 WOEɰ rة݆hTb9IʒeR7Lbz3PzX.2"8[.KeaޭT!Xl6MܒRˉe I%* A!]4 W]6x¶yՍ)Xcĩ[[<>V P`+X䬖7*F%&IH_lء唏~Q+y>gtؒBXANT8oI&pW-\Gp K.h QШz?(*B,9o>,sʬMbxHk_;۱gwYuFk۰F6@*̒Os8 DVjM.,I;" rn,Pf|])4B*T;ņWwTܶOo!vS%6Niٺq*deI2b.$-7#Vj?ZD9{;W3Oy|R%ߘK֣gL؊4E ZNgn קe& qLlS/juq8 /(ʢrxF1WߕރSB^Bp`L\RfgP 3I8 -sF&8J|4&]o8 Cc5،e#*Ek,l|iGvBsp@+:z8৐[=]YFalG18i֏EĂXH3xrk3*WD5[`wV  sASd>1.Z?&.<A\xYݺuU眽; ٺo WŖt'Q¸EUD7˨l]yV4qZ dҫtrcgxٓ'jF<\vq;"%9' I4!P7+]%-RۉZ*^_ ډk}Am(bհNc/IO2%ɓLT'1>ӬVjUmT͈k mjx79@sV~7~5~ǹG5؊Wf45"p(YkQ/Di) <wPGg2!@[` l/Ə;a9OAWZھ"Fa^#9l@yw>:vKt' b) -> Chan a -> (Chan b -> IO r) -> IO r withMapChan f chan cont = do new <- newChan Async.withAsync (forever $ readChan chan >>= writeChan new . f) (\_ -> cont new) patat-0.14.1.0/lib/Data/000077500000000000000000000000001475634243500145005ustar00rootroot00000000000000patat-0.14.1.0/lib/Data/Aeson/000077500000000000000000000000001475634243500155455ustar00rootroot00000000000000patat-0.14.1.0/lib/Data/Aeson/Extended.hs000066400000000000000000000015571475634243500176510ustar00rootroot00000000000000{-# LANGUAGE GeneralizedNewtypeDeriving #-} module Data.Aeson.Extended ( module Data.Aeson , FlexibleNum (..) , resultToEither ) where import Data.Aeson import qualified Data.Text as T import Prelude import Text.Read (readMaybe) -- | This can be parsed from a JSON string in addition to a JSON number. newtype FlexibleNum a = FlexibleNum {unFlexibleNum :: a} deriving (Eq, Show, ToJSON) instance (FromJSON a, Read a) => FromJSON (FlexibleNum a) where parseJSON (String str) = case readMaybe (T.unpack str) of Nothing -> fail $ "Could not parse " ++ T.unpack str ++ " as a number" Just x -> return (FlexibleNum x) parseJSON val = FlexibleNum <$> parseJSON val resultToEither :: Result a -> Either String a resultToEither (Success x) = Right x resultToEither (Error err) = Left err patat-0.14.1.0/lib/Data/Aeson/TH/000077500000000000000000000000001475634243500160605ustar00rootroot00000000000000patat-0.14.1.0/lib/Data/Aeson/TH/Extended.hs000066400000000000000000000012151475634243500201530ustar00rootroot00000000000000-------------------------------------------------------------------------------- module Data.Aeson.TH.Extended ( module Data.Aeson.TH , dropPrefixOptions ) where -------------------------------------------------------------------------------- import Data.Aeson.TH import Data.Char (isUpper, toLower) -------------------------------------------------------------------------------- dropPrefixOptions :: Options dropPrefixOptions = defaultOptions { fieldLabelModifier = dropPrefix } where dropPrefix str = case break isUpper str of (_, (y : ys)) -> toLower y : ys _ -> str patat-0.14.1.0/lib/Data/Char/000077500000000000000000000000001475634243500153555ustar00rootroot00000000000000patat-0.14.1.0/lib/Data/Char/WCWidth/000077500000000000000000000000001475634243500166665ustar00rootroot00000000000000patat-0.14.1.0/lib/Data/Char/WCWidth/Extended.hs000066400000000000000000000002761475634243500207670ustar00rootroot00000000000000module Data.Char.WCWidth.Extended ( module Data.Char.WCWidth , wcstrwidth ) where import Data.Char.WCWidth wcstrwidth :: String -> Int wcstrwidth = sum . map wcwidth patat-0.14.1.0/lib/Data/Sequence/000077500000000000000000000000001475634243500162505ustar00rootroot00000000000000patat-0.14.1.0/lib/Data/Sequence/Extended.hs000066400000000000000000000005621475634243500203470ustar00rootroot00000000000000module Data.Sequence.Extended ( module Data.Sequence , cons , safeIndex ) where import Data.Sequence cons :: a -> Seq a -> Seq a cons x xs = singleton x <> xs safeIndex :: Seq a -> Int -> Maybe a safeIndex s n | n < 0 = Nothing | n >= Data.Sequence.length s = Nothing | otherwise = Just $ index s n patat-0.14.1.0/lib/Patat/000077500000000000000000000000001475634243500147005ustar00rootroot00000000000000patat-0.14.1.0/lib/Patat/AutoAdvance.hs000066400000000000000000000052611475634243500174320ustar00rootroot00000000000000-------------------------------------------------------------------------------- module Patat.AutoAdvance ( maybeAutoAdvance , autoAdvance ) where -------------------------------------------------------------------------------- import Control.Concurrent (threadDelay) import qualified Control.Concurrent.Async as Async import qualified Control.Concurrent.Chan as Chan import Control.Monad (forever) import qualified Data.IORef as IORef import Data.Time (diffUTCTime, getCurrentTime) import Patat.Presentation (PresentationCommand (..)) -------------------------------------------------------------------------------- -- | Utility to make auto advancing optional. maybeAutoAdvance :: Maybe Int -> Chan.Chan PresentationCommand -> (Chan.Chan PresentationCommand -> IO a) -> IO a maybeAutoAdvance Nothing chan f = f chan maybeAutoAdvance (Just delaySeconds) chan f = autoAdvance delaySeconds chan f -------------------------------------------------------------------------------- -- | This function takes an existing channel for presentation commands -- (presumably coming from human input) and creates a new one that /also/ sends -- a 'Forward' command if nothing happens for N seconds. autoAdvance :: Int -> Chan.Chan PresentationCommand -> (Chan.Chan PresentationCommand -> IO a) -> IO a autoAdvance delaySeconds existingChan f = do let delay = delaySeconds * 1000 -- We are working with ms in this function newChan <- Chan.newChan latestCommandAt <- IORef.newIORef =<< getCurrentTime -- This is a thread that copies 'existingChan' to 'newChan', and writes -- whenever the latest command was to 'latestCommandAt'. (forever $ do cmd <- Chan.readChan existingChan getCurrentTime >>= IORef.writeIORef latestCommandAt Chan.writeChan newChan cmd) `Async.withAsync` \_ -> -- This is a thread that waits around 'delay' seconds and then checks if -- there's been a more recent command. If not, we write a 'Forward'. (forever $ do current <- getCurrentTime latest <- IORef.readIORef latestCommandAt let elapsed = floor $ 1000 * (current `diffUTCTime` latest) :: Int if elapsed >= delay then do Chan.writeChan newChan Forward IORef.writeIORef latestCommandAt current threadDelay (delay * 1000) else do let wait = delay - elapsed threadDelay (wait * 1000)) `Async.withAsync` \_ -> -- Continue main thread. f newChan patat-0.14.1.0/lib/Patat/Cleanup.hs000066400000000000000000000005061475634243500166240ustar00rootroot00000000000000-------------------------------------------------------------------------------- -- | Defines a cleanup action that needs to be run after we're done with a slide -- or image. module Patat.Cleanup ( Cleanup ) where -------------------------------------------------------------------------------- type Cleanup = IO () patat-0.14.1.0/lib/Patat/EncodingFallback.hs000066400000000000000000000041671475634243500204120ustar00rootroot00000000000000-- | When we try to read a file that is encoded in UTF-8, and the system locale -- is not set to UTF-8, the GHC runtime system will throw an error: -- -- -- -- However, we don't want to force people to use UTF-8 for everything. So what -- we do is provide a replacement readFile, which first tries to read the file -- in the system locale, and then falls back to forcing UTF-8. -- -- If we forced UTF-8, we also want to propagate that to the output handle; -- otherwise will get errors when we try to display these characters; so -- withHandle should be used on the output handle (typically stdout). module Patat.EncodingFallback ( EncodingFallback (..) , readFile , withHandle ) where -------------------------------------------------------------------------------- import Control.Exception (bracket, throwIO) import Control.Monad (when) import qualified Data.Text as T import qualified Data.Text.IO as T import Prelude hiding (readFile) import qualified System.IO as IO import qualified System.IO.Error as IO -------------------------------------------------------------------------------- data EncodingFallback = NoFallback | Utf8Fallback deriving (Eq, Show) -------------------------------------------------------------------------------- readFile :: FilePath -> IO (EncodingFallback, T.Text) readFile path = IO.catchIOError readSystem $ \ioe -> do when (IO.isDoesNotExistError ioe) $ throwIO ioe -- Don't retry on these readUtf8 where readSystem = ((,) NoFallback <$> T.readFile path) readUtf8 = IO.withFile path IO.ReadMode $ \h -> do IO.hSetEncoding h IO.utf8_bom (,) Utf8Fallback <$> T.hGetContents h -------------------------------------------------------------------------------- withHandle :: IO.Handle -> EncodingFallback -> IO a -> IO a withHandle _ NoFallback mx = mx withHandle h Utf8Fallback mx = bracket (do mbOld <- IO.hGetEncoding h IO.hSetEncoding h IO.utf8 pure mbOld) (\mbOld -> traverse (IO.hSetEncoding h) mbOld) (\_ -> mx) patat-0.14.1.0/lib/Patat/Eval.hs000066400000000000000000000165141475634243500161320ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Patat.Eval ( parseEvalBlocks , evalVar , evalActiveVars , evalAllVars ) where -------------------------------------------------------------------------------- import qualified Control.Concurrent.Async as Async import Control.Exception (IOException, catch, finally) import Control.Monad (foldM, when) import Control.Monad.State (StateT, runStateT, state) import Control.Monad.Writer (Writer, runWriter, tell) import Data.Foldable (for_) import qualified Data.HashMap.Strict as HMS import qualified Data.IORef as IORef import Data.List (foldl') import Data.Maybe (maybeToList) import qualified Data.Set as S import qualified Data.Text as T import qualified Data.Text.IO as T import Patat.Eval.Internal import Patat.Presentation.Internal import Patat.Presentation.Syntax import Patat.Unique import System.Exit (ExitCode (..)) import qualified System.IO as IO import qualified System.Process as Process -------------------------------------------------------------------------------- parseEvalBlocks :: Presentation -> Presentation parseEvalBlocks presentation = let ((pres, varGen), evalBlocks) = runWriter $ runStateT work (pUniqueGen presentation) in pres {pEvalBlocks = evalBlocks, pUniqueGen = varGen} where work = case psEval (pSettings presentation) of Nothing -> pure presentation Just settings -> do slides <- traverse (evalSlide settings) (pSlides presentation) pure presentation {pSlides = slides} -------------------------------------------------------------------------------- lookupSettings :: [T.Text] -> EvalSettingsMap -> [EvalSettings] lookupSettings classes settings = do c <- classes maybeToList $ HMS.lookup c settings -------------------------------------------------------------------------------- -- | Monad used for identifying and extracting the evaluation blocks from a -- presentation. type ExtractEvalM a = StateT UniqueGen (Writer (HMS.HashMap Var EvalBlock)) a -------------------------------------------------------------------------------- evalSlide :: EvalSettingsMap -> Slide -> ExtractEvalM Slide evalSlide settings slide = case slideContent slide of TitleSlide _ _ -> pure slide ContentSlide blocks -> do blocks1 <- dftBlocks (evalBlock settings) (pure . pure) blocks pure slide {slideContent = ContentSlide blocks1} -------------------------------------------------------------------------------- evalBlock :: EvalSettingsMap -> Block -> ExtractEvalM [Block] evalBlock settings orig@(CodeBlock attr@(_, classes, _) txt) | [s@EvalSettings {..}] <- lookupSettings classes settings = do var <- Var <$> state freshUnique tell $ HMS.singleton var $ EvalBlock s attr txt Nothing case (evalReveal, evalReplace) of (False, True) -> pure [VarBlock var] (False, False) -> pure [orig, VarBlock var] (True, True) -> do revealID <- RevealID <$> state freshUnique pure $ pure $ Reveal ConcatWrapper $ RevealSequence revealID [revealID] [ (S.singleton 0, [orig]) , (S.singleton 1, [VarBlock var]) ] (True, False) -> do revealID <- RevealID <$> state freshUnique pure $ pure $ Reveal ConcatWrapper $ RevealSequence revealID [revealID] [ (S.fromList [0, 1], [orig]) , (S.fromList [1], [VarBlock var]) ] | _ : _ : _ <- lookupSettings classes settings = let msg = "patat eval matched multiple settings for " <> T.intercalate "," classes in pure [CodeBlock attr msg] evalBlock _ block = pure [block] -------------------------------------------------------------------------------- newAccum :: Monoid m => (m -> IO ()) -> IO (m -> IO ()) newAccum f = do ref <- IORef.newIORef mempty pure $ \x -> IORef.atomicModifyIORef' ref (\y -> let z = y <> x in (z, z)) >>= f -------------------------------------------------------------------------------- evalVar :: Var -> ([Block] -> IO ()) -> Presentation -> IO Presentation evalVar var writeOutput presentation = case HMS.lookup var evalBlocks of Nothing -> pure presentation Just EvalBlock {..} | Just _ <- ebAsync -> pure presentation Just eb@EvalBlock {..} -> do let EvalSettings {..} = ebSettings writeChunk <- newAccum (writeOutput . renderEvalBlock eb) let drainLines copy h = do c <- catch (T.hGetChunk h) ((\_ -> pure "") :: IOException -> IO T.Text) when (c /= "") $ do when copy $ writeChunk c drainLines copy h let proc = (Process.shell $ T.unpack evalCommand) { Process.std_in = Process.CreatePipe , Process.std_out = Process.CreatePipe , Process.std_err = Process.CreatePipe } (Just hIn, Just hOut, Just hErr, hProc) <- Process.createProcess proc async <- Async.async $ Async.withAsync (T.hPutStr hIn ebInput `finally` IO.hClose hIn) $ \_ -> Async.withAsync (drainLines True hOut) $ \outAsync -> Async.withAsync (drainLines evalStderr hErr) $ \errAsync -> Async.withAsync (Process.waitForProcess hProc) $ \exitCodeAsync -> do erExitCode <- Async.wait exitCodeAsync _ <- Async.wait outAsync _ <- Async.wait errAsync case erExitCode of ExitSuccess -> pure () ExitFailure i -> writeChunk $ evalCommand <> ": exit code " <> T.pack (show i) <> "\n" pure presentation { pEvalBlocks = HMS.insert var eb {ebAsync = Just async} evalBlocks } where evalBlocks = pEvalBlocks presentation -------------------------------------------------------------------------------- evalActiveVars :: (Var -> [Block] -> IO ()) -> Presentation -> IO Presentation evalActiveVars update presentation = foldM (\p var -> evalVar var (update var) p) presentation (activeVars presentation) -------------------------------------------------------------------------------- evalAllVars :: Presentation -> IO Presentation evalAllVars pres = do updates <- IORef.newIORef [] let forceEvalVar pres0 var = do pres1 <- evalVar var (\u -> IORef.atomicModifyIORef' updates (\l -> (l ++ [u], ()))) pres0 case HMS.lookup var (pEvalBlocks pres1) of Nothing -> pure pres1 Just eb -> do for_ (ebAsync eb) Async.wait IORef.atomicModifyIORef' updates $ \l -> ([], foldl' (\p u -> updateVar var u p) pres1 l) foldM forceEvalVar pres (HMS.keys (pEvalBlocks pres)) patat-0.14.1.0/lib/Patat/Eval/000077500000000000000000000000001475634243500155675ustar00rootroot00000000000000patat-0.14.1.0/lib/Patat/Eval/Internal.hs000066400000000000000000000025251475634243500177030ustar00rootroot00000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Patat.Eval.Internal ( EvalBlocks , EvalBlock (..) , renderEvalBlock ) where -------------------------------------------------------------------------------- import qualified Control.Concurrent.Async as Async import qualified Data.HashMap.Strict as HMS import qualified Data.Text as T import Patat.Presentation.Settings import Patat.Presentation.Syntax import qualified Text.Pandoc as Pandoc -------------------------------------------------------------------------------- type EvalBlocks = HMS.HashMap Var EvalBlock -------------------------------------------------------------------------------- -- | Block that needs to be evaluated. data EvalBlock = EvalBlock { ebSettings :: !EvalSettings , ebAttr :: !Pandoc.Attr , ebInput :: !T.Text , ebAsync :: !(Maybe (Async.Async ())) } -------------------------------------------------------------------------------- renderEvalBlock :: EvalBlock -> T.Text -> [Block] renderEvalBlock EvalBlock {..} out = case evalContainer ebSettings of EvalContainerCode -> [CodeBlock ebAttr out] EvalContainerNone -> [RawBlock fmt out] EvalContainerInline -> [Plain [RawInline fmt out]] where fmt = "eval" patat-0.14.1.0/lib/Patat/Images.hs000066400000000000000000000044261475634243500164470ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE OverloadedStrings #-} module Patat.Images ( Backend , Handle , withHandle , drawImage ) where -------------------------------------------------------------------------------- import Control.Exception (catch) import qualified Data.Aeson as A import qualified Data.Text as T import Patat.Cleanup import Patat.Images.Internal import qualified Patat.Images.ITerm2 as ITerm2 import qualified Patat.Images.Kitty as Kitty import qualified Patat.Images.W3m as W3m import qualified Patat.Images.WezTerm as WezTerm import Patat.Presentation.Internal -------------------------------------------------------------------------------- withHandle :: ImageSettings -> (Handle -> IO a) -> IO a withHandle is f | isBackend is == "auto" = auto >>= f | Just (Backend b) <- lookup (isBackend is) backends = case A.fromJSON (A.Object $ isParams is) of A.Success c -> b (Explicit c) >>= f A.Error err -> fail $ "Patat.Images.new: Error parsing config for " ++ show (isBackend is) ++ " image backend: " ++ err withHandle is _ = fail $ "Patat.Images.new: Could not find " ++ show (isBackend is) ++ " image backend." -------------------------------------------------------------------------------- auto :: IO Handle auto = go [] backends where go names ((name, Backend b) : bs) = catch (b Auto) (\(BackendNotSupported _) -> go (name : names) bs) go names [] = fail $ "Could not find a supported backend, tried: " ++ T.unpack (T.intercalate ", " (reverse names)) -------------------------------------------------------------------------------- -- | All supported backends. We can use CPP to include or exclude some -- depending on platform availability. backends :: [(T.Text, Backend)] backends = [ ("iterm2", ITerm2.backend) , ("kitty", Kitty.backend) , ("w3m", W3m.backend) , ("wezterm", WezTerm.backend) ] -------------------------------------------------------------------------------- drawImage :: Handle -> FilePath -> IO Cleanup drawImage = hDrawImage patat-0.14.1.0/lib/Patat/Images/000077500000000000000000000000001475634243500161055ustar00rootroot00000000000000patat-0.14.1.0/lib/Patat/Images/ITerm2.hs000066400000000000000000000033451475634243500175500ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE TemplateHaskell #-} module Patat.Images.ITerm2 ( backend ) where -------------------------------------------------------------------------------- import Control.Exception (throwIO) import Control.Monad (unless, when) import qualified Data.Aeson as A import qualified Data.ByteString.Base64.Lazy as B64 import qualified Data.ByteString.Lazy as BL import Patat.Cleanup (Cleanup) import qualified Patat.Images.Internal as Internal import System.Environment (lookupEnv) -------------------------------------------------------------------------------- backend :: Internal.Backend backend = Internal.Backend new -------------------------------------------------------------------------------- data Config = Config deriving (Eq) instance A.FromJSON Config where parseJSON _ = return Config -------------------------------------------------------------------------------- new :: Internal.Config Config -> IO Internal.Handle new config = do when (config == Internal.Auto) $ do termProgram <- lookupEnv "TERM_PROGRAM" unless (termProgram == Just "iTerm.app") $ throwIO $ Internal.BackendNotSupported "TERM_PROGRAM not iTerm.app" return Internal.Handle {Internal.hDrawImage = drawImage} -------------------------------------------------------------------------------- drawImage :: FilePath -> IO Cleanup drawImage path = do content <- BL.readFile path Internal.withEscapeSequence $ do putStr "1337;File=inline=1;width=100%;height=100%:" BL.putStr (B64.encode content) return mempty patat-0.14.1.0/lib/Patat/Images/Internal.hs000066400000000000000000000036771475634243500202320ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE ExistentialQuantification #-} module Patat.Images.Internal ( Config (..) , Backend (..) , BackendNotSupported (..) , Handle (..) , withEscapeSequence ) where -------------------------------------------------------------------------------- import Control.Exception (Exception) import qualified Data.Aeson as A import Data.Data (Data) import qualified Data.List as L import Data.Typeable (Typeable) import Patat.Cleanup import System.Environment import qualified System.IO as IO -------------------------------------------------------------------------------- data Config a = Auto | Explicit a deriving (Eq) -------------------------------------------------------------------------------- data Backend = forall a. A.FromJSON a => Backend (Config a -> IO Handle) -------------------------------------------------------------------------------- data BackendNotSupported = BackendNotSupported String deriving (Data, Show, Typeable) -------------------------------------------------------------------------------- instance Exception BackendNotSupported -------------------------------------------------------------------------------- data Handle = Handle { hDrawImage :: FilePath -> IO Cleanup } -------------------------------------------------------------------------------- withEscapeSequence :: IO () -> IO () withEscapeSequence f = do term <- lookupEnv "TERM" let inScreen = maybe False ("screen" `L.isPrefixOf`) term putStr $ if inScreen then "\ESCPtmux;\ESC\ESC]" else "\ESC]" f putStr $ if inScreen then "\a\ESC\\" else "\a" -- Make sure we don't print a newline after the image, or it may scroll -- slightly out of view to the top. We flush instead. IO.hFlush IO.stdout patat-0.14.1.0/lib/Patat/Images/Kitty.hs000066400000000000000000000037021475634243500175470ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE TemplateHaskell #-} module Patat.Images.Kitty ( backend ) where -------------------------------------------------------------------------------- import Control.Exception (throwIO) import Control.Monad (unless, void, when) import qualified Data.Aeson as A import Data.Functor (($>)) import qualified Data.List as L import Patat.Cleanup (Cleanup) import qualified Patat.Images.Internal as Internal import System.Environment (lookupEnv) import qualified System.IO as IO import qualified System.Process as Process -------------------------------------------------------------------------------- backend :: Internal.Backend backend = Internal.Backend new -------------------------------------------------------------------------------- data Config = Config deriving (Eq) instance A.FromJSON Config where parseJSON _ = return Config -------------------------------------------------------------------------------- new :: Internal.Config Config -> IO Internal.Handle new config = do when (config == Internal.Auto) $ do term <- lookupEnv "TERM" unless (maybe False ("kitty" `L.isInfixOf`) term) $ throwIO $ Internal.BackendNotSupported "TERM does not indicate kitty" return Internal.Handle {Internal.hDrawImage = drawImage} -------------------------------------------------------------------------------- drawImage :: FilePath -> IO Cleanup drawImage path = icat ["--align=center", path] $> icat ["--clear"] where icat args = do (Just inh, _, _, ph) <- Process.createProcess (Process.proc "kitty" ("+kitten" : "icat" : "--transfer-mode=stream" : "--stdin=no" : args)) { Process.std_in = Process.CreatePipe } IO.hClose inh void $ Process.waitForProcess ph patat-0.14.1.0/lib/Patat/Images/W3m.hs000066400000000000000000000126451475634243500171170ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE LambdaCase #-} {-# LANGUAGE TemplateHaskell #-} module Patat.Images.W3m ( backend ) where -------------------------------------------------------------------------------- import Control.Exception (IOException, throwIO, try) import Control.Monad (unless, void) import qualified Data.Aeson.TH.Extended as A import Data.List (intercalate) import Patat.Cleanup (Cleanup) import qualified Patat.Images.Internal as Internal import qualified System.Directory as Directory import qualified System.Process as Process import Text.Read (readMaybe) -------------------------------------------------------------------------------- data Config = Config { cPath :: Maybe FilePath } deriving (Show) -------------------------------------------------------------------------------- $(A.deriveFromJSON A.dropPrefixOptions ''Config) -------------------------------------------------------------------------------- backend :: Internal.Backend backend = Internal.Backend new -------------------------------------------------------------------------------- new :: Internal.Config Config -> IO Internal.Handle new config = do w3m <- findW3m $ case config of Internal.Explicit c -> cPath c _ -> Nothing return Internal.Handle {Internal.hDrawImage = drawImage w3m} -------------------------------------------------------------------------------- newtype W3m = W3m FilePath deriving (Show) -------------------------------------------------------------------------------- findW3m :: Maybe FilePath -> IO W3m findW3m = \case -- Use the path specified by the user. Just path -> do exe <- isExecutable path if exe then pure $ W3m path else throwIO $ Internal.BackendNotSupported $ path ++ " is not executable" Nothing -> do let path = W3m "w3mimgdisplay" errOrSize <- try $ getTerminalSize path case errOrSize :: Either IOException (Int, Int) of Right _ -> pure path -- Found it. Left _ -> W3m <$> find paths -- Look in some hardcoded paths. where find [] = throwIO $ Internal.BackendNotSupported "w3mimgdisplay executable not found" find (p : ps) = do exe <- isExecutable p if exe then return p else find ps paths = [ "/usr/lib/w3m/w3mimgdisplay" , "/usr/libexec/w3m/w3mimgdisplay" , "/usr/lib64/w3m/w3mimgdisplay" , "/usr/libexec64/w3m/w3mimgdisplay" , "/usr/local/libexec/w3m/w3mimgdisplay" ] isExecutable path = do exists <- Directory.doesFileExist path if exists then do perms <- Directory.getPermissions path return (Directory.executable perms) else return False -------------------------------------------------------------------------------- -- | Parses something of the form " \n". parseWidthHeight :: String -> Maybe (Int, Int) parseWidthHeight output = case words output of [ws, hs] | Just w <- readMaybe ws, Just h <- readMaybe hs -> return (w, h) _ -> Nothing -------------------------------------------------------------------------------- getTerminalSize :: W3m -> IO (Int, Int) getTerminalSize (W3m w3mPath) = do output <- Process.readProcess w3mPath ["-test"] "" case parseWidthHeight output of Just wh -> return wh _ -> fail $ "Patat.Images.W3m.getTerminalSize: " ++ "Could not parse `w3mimgdisplay -test` output" -------------------------------------------------------------------------------- getImageSize :: W3m -> FilePath -> IO (Int, Int) getImageSize (W3m w3mPath) path = do output <- Process.readProcess w3mPath [] ("5;" ++ path ++ "\n") case parseWidthHeight output of Just wh -> return wh _ -> fail $ "Patat.Images.W3m.getImageSize: " ++ "Could not parse image size using `w3mimgdisplay` for " ++ path -------------------------------------------------------------------------------- drawImage :: W3m -> FilePath -> IO Cleanup drawImage w3m@(W3m w3mPath) path = do exists <- Directory.doesFileExist path unless exists $ fail $ "Patat.Images.W3m.drawImage: file does not exist: " ++ path tsize <- getTerminalSize w3m isize <- getImageSize w3m path let (x, y, w, h) = fit tsize isize command = "0;1;" ++ show x ++ ";" ++ show y ++ ";" ++ show w ++ ";" ++ show h ++ ";;;;;" ++ path ++ "\n4;\n3;\n" -- Draw image. _ <- Process.readProcess w3mPath [] command -- Return a 'Cleanup' that clears the image. return $ void $ Process.readProcess w3mPath [] $ "6;" ++ intercalate ";" (map show [x, y, w, h]) where fit :: (Int, Int) -> (Int, Int) -> (Int, Int, Int, Int) fit (tw, th) (iw0, ih0) = -- Scale down to width let iw1 = if iw0 > tw then tw else iw0 ih1 = if iw0 > tw then ((ih0 * tw) `div` iw0) else ih0 -- Scale down to height iw2 = if ih1 > th then ((iw1 * th) `div` ih1) else iw1 ih2 = if ih1 > th then th else ih1 -- Find position x = (tw - iw2) `div` 2 y = (th - ih2) `div` 2 in (x, y, iw2, ih2) patat-0.14.1.0/lib/Patat/Images/WezTerm.hs000066400000000000000000000044311475634243500200400ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE TemplateHaskell #-} module Patat.Images.WezTerm ( backend ) where -------------------------------------------------------------------------------- import Control.Exception (throwIO) import Control.Monad (unless, when) import Codec.Picture import qualified Data.Aeson as A import qualified Data.ByteString.Base64 as B64 import qualified Data.ByteString as B import Patat.Cleanup (Cleanup) import qualified Patat.Images.Internal as Internal import System.Environment (lookupEnv) -------------------------------------------------------------------------------- backend :: Internal.Backend backend = Internal.Backend new -------------------------------------------------------------------------------- data Config = Config deriving (Eq) instance A.FromJSON Config where parseJSON _ = return Config -------------------------------------------------------------------------------- new :: Internal.Config Config -> IO Internal.Handle new config = do when (config == Internal.Auto) $ do termProgram <- lookupEnv "TERM_PROGRAM" unless (termProgram == Just "WezTerm") $ throwIO $ Internal.BackendNotSupported "TERM_PROGRAM not WezTerm" return Internal.Handle {Internal.hDrawImage = drawImage} -------------------------------------------------------------------------------- drawImage :: FilePath -> IO Cleanup drawImage path = do content <- B.readFile path Internal.withEscapeSequence $ do putStr "1337;File=inline=1;doNotMoveCursor=1;" case decodeImage content of Left _ -> pure () Right img -> putStr $ getAspectRatio img putStr ":" B.putStr (B64.encode content) return mempty -------------------------------------------------------------------------------- getAspectRatio :: DynamicImage -> String getAspectRatio i | go_w i / go_h i < (1 :: Double) = "width=auto;height=95%;" | otherwise = "width=100%;height=auto;" where go_h = fromIntegral . (dynamicMap imageHeight) go_w = fromIntegral . (dynamicMap imageWidth) patat-0.14.1.0/lib/Patat/Main.hs000066400000000000000000000321451475634243500161250ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Patat.Main ( main ) where -------------------------------------------------------------------------------- import Control.Concurrent (forkIO, threadDelay) import qualified Control.Concurrent.Async as Async import Control.Concurrent.Chan.Extended (Chan) import qualified Control.Concurrent.Chan.Extended as Chan import Control.Exception (bracket) import Control.Monad (forever, unless, void, when) import qualified Data.Aeson.Extended as A import Data.Foldable (for_) import Data.Functor (($>)) import qualified Data.List.NonEmpty as NonEmpty import qualified Data.Sequence.Extended as Seq import Data.Version (showVersion) import qualified Options.Applicative as OA import qualified Options.Applicative.Help.Pretty as OA.PP import Patat.AutoAdvance import qualified Patat.EncodingFallback as EncodingFallback import qualified Patat.Eval as Eval import qualified Patat.Images as Images import Patat.Presentation import qualified Patat.Presentation.SpeakerNotes as SpeakerNotes import qualified Patat.PrettyPrint as PP import Patat.PrettyPrint.Matrix (hPutMatrix) import Patat.Transition import qualified Paths_patat import Prelude import qualified System.Console.ANSI as Ansi import System.Directory (doesFileExist, getModificationTime) import System.Environment (lookupEnv) import System.Exit (exitFailure, exitSuccess) import qualified System.IO as IO import qualified Text.Pandoc as Pandoc -------------------------------------------------------------------------------- data Options = Options { oFilePath :: !(Maybe FilePath) , oForce :: !Bool , oDump :: !Bool , oWatch :: !Bool , oVersion :: !Bool } deriving (Show) -------------------------------------------------------------------------------- parseOptions :: OA.Parser Options parseOptions = Options <$> (OA.optional $ OA.strArgument $ OA.metavar "FILENAME" <> OA.action "file" <> -- For bash file completion OA.help "Input file") <*> (OA.switch $ OA.long "force" <> OA.short 'f' <> OA.help "Force ANSI terminal" <> OA.hidden) <*> (OA.switch $ OA.long "dump" <> OA.short 'd' <> OA.help "Just dump all slides and exit" <> OA.hidden) <*> (OA.switch $ OA.long "watch" <> OA.short 'w' <> OA.help "Watch file for changes") <*> (OA.switch $ OA.long "version" <> OA.help "Display version info and exit" <> OA.hidden) -------------------------------------------------------------------------------- parserInfo :: OA.ParserInfo Options parserInfo = OA.info (OA.helper <*> parseOptions) $ OA.fullDesc <> OA.header ("patat v" <> showVersion Paths_patat.version) <> OA.progDescDoc (Just desc) where desc = OA.PP.vcat [ "Terminal-based presentations using Pandoc" , "" , "Controls:" , "- Next slide: space, enter, l, right, pagedown" , "- Previous slide: backspace, h, left, pageup" , "- Go forward 10 slides: j, down" , "- Go backward 10 slides: k, up" , "- First slide: 0" , "- Last slide: G" , "- Jump to slide N: N followed by enter" , "- Reload file: r" , "- Quit: q" ] -------------------------------------------------------------------------------- parserPrefs :: OA.ParserPrefs parserPrefs = OA.prefs OA.showHelpOnError -------------------------------------------------------------------------------- errorAndExit :: [String] -> IO a errorAndExit msg = do mapM_ (IO.hPutStrLn IO.stderr) msg exitFailure -------------------------------------------------------------------------------- assertAnsiFeatures :: IO () assertAnsiFeatures = do supports <- Ansi.hSupportsANSI IO.stdout unless supports $ errorAndExit [ "It looks like your terminal does not support ANSI codes." , "If you still want to run the presentation, use `--force`." ] -------------------------------------------------------------------------------- data App = App { aOptions :: Options , aImages :: Maybe Images.Handle , aSpeakerNotes :: Maybe SpeakerNotes.Handle , aCommandChan :: Chan AppCommand , aPresentation :: Presentation , aView :: AppView } -------------------------------------------------------------------------------- data AppView = PresentationView | ErrorView String | TransitionView TransitionInstance -------------------------------------------------------------------------------- data AppCommand = PresentationCommand PresentationCommand | TransitionTick TransitionId -------------------------------------------------------------------------------- main :: IO () main = do options <- OA.customExecParser parserPrefs parserInfo when (oVersion options) $ do putStrLn $ showVersion Paths_patat.version putStrLn $ "Using pandoc: " ++ showVersion Pandoc.pandocVersion exitSuccess filePath <- case oFilePath options of Just fp -> return fp Nothing -> OA.handleParseResult $ OA.Failure $ OA.parserFailure parserPrefs parserInfo (OA.ShowHelpText Nothing) mempty errOrPres <- readPresentation zeroUniqueGen filePath pres <- either (errorAndExit . return) return errOrPres let settings = pSettings pres unless (oForce options) assertAnsiFeatures if oDump options then EncodingFallback.withHandle IO.stdout (pEncodingFallback pres) $ do Eval.evalAllVars pres >>= dumpPresentation else -- (Maybe) initialize images backend. withMaybeHandle Images.withHandle (psImages settings) $ \images -> -- (Maybe) initialize speaker notes. withMaybeHandle SpeakerNotes.withHandle (psSpeakerNotes settings) $ \speakerNotes -> -- Read presentation commands interactively (readPresentationCommand) $ \commandChan0 -> -- If an auto delay is set, use 'autoAdvance' to create a new one. maybeAutoAdvance (A.unFlexibleNum <$> psAutoAdvanceDelay settings) commandChan0 $ \commandChan1 -> -- Change to AppCommand Chan.withMapChan PresentationCommand commandChan1 $ \commandChan -> -- Spawn a thread that adds 'Reload' commands based on the file time. withWatcher (oWatch options) commandChan (pFilePath pres) (PresentationCommand Reload) $ loop App { aOptions = options , aImages = images , aSpeakerNotes = speakerNotes , aCommandChan = commandChan , aPresentation = pres , aView = PresentationView } -------------------------------------------------------------------------------- loop :: App -> IO () loop app@App {..} = do for_ aSpeakerNotes $ \sn -> SpeakerNotes.write sn (pEncodingFallback aPresentation) (activeSpeakerNotes aPresentation) -- Start necessary eval blocks presentation <- Eval.evalActiveVars (\v -> Chan.writeChan aCommandChan . PresentationCommand . UpdateVar v) aPresentation size <- getPresentationSize presentation Ansi.clearScreen Ansi.setCursorPosition 0 0 cleanup <- case aView of PresentationView -> case displayPresentation size presentation of DisplayDoc doc -> drawDoc doc DisplayImage path -> drawImg size path ErrorView err -> drawDoc $ displayPresentationError size presentation err TransitionView tr -> do drawMatrix (tiSize tr) . fst . NonEmpty.head $ tiFrames tr pure mempty appCmd <- Chan.readChan aCommandChan cleanup case appCmd of TransitionTick eid -> case aView of PresentationView -> loop app ErrorView _ -> loop app TransitionView tr0 -> case stepTransition eid tr0 of Just tr1 -> do scheduleTransitionTick tr1 loop app {aView = TransitionView tr1} Nothing -> loop app {aView = PresentationView} PresentationCommand c -> do update <- updatePresentation c presentation case update of ExitedPresentation -> return () UpdatedPresentation pres | Just tgen <- mbTransition c size presentation pres -> do tr <- tgen scheduleTransitionTick tr loop app {aPresentation = pres, aView = TransitionView tr} | otherwise -> loop app {aPresentation = pres, aView = PresentationView} ErroredPresentation err -> loop app {aView = ErrorView err} where drawDoc doc = EncodingFallback.withHandle IO.stdout (pEncodingFallback aPresentation) $ PP.putDoc doc $> mempty drawImg size path = case aImages of Nothing -> drawDoc $ displayPresentationError size aPresentation "image backend not initialized" Just img -> do putStrLn "" IO.hFlush IO.stdout Images.drawImage img path drawMatrix size raster = hPutMatrix IO.stdout size raster mbTransition c size old new | c == Forward , oldSlide + 1 == newSlide , DisplayDoc oldDoc <- displayPresentation size old , DisplayDoc newDoc <- displayPresentation size new , Just (Just tgen) <- pTransitionGens new `Seq.safeIndex` newSlide = Just $ newTransition tgen size oldDoc newDoc | otherwise = Nothing where (oldSlide, _) = pActiveFragment old (newSlide, _) = pActiveFragment new scheduleTransitionTick tr = void $ forkIO $ do threadDelayDuration . snd . NonEmpty.head $ tiFrames tr Chan.writeChan aCommandChan $ TransitionTick $ tiId tr -------------------------------------------------------------------------------- -- | Utility for dealing with pecularities of stdin & interactive applications -- on the terminal. Tries to restore the original state of the terminal as much -- as possible. interactively :: (IO.Handle -> IO a) -- ^ Reads a command from stdin (or from some other IO). This will be -- interrupted by 'killThread' when the application finishes. -> (Chan a -> IO ()) -- ^ Application to run. -> IO () -- ^ Returns when application finishes. interactively reader app = bracket setup teardown $ \(_, _, chan) -> Async.withAsync (forever $ reader IO.stdin >>= Chan.writeChan chan) (\_ -> app chan) where setup = do chan <- Chan.newChan echo <- IO.hGetEcho IO.stdin buff <- IO.hGetBuffering IO.stdin IO.hSetEcho IO.stdin False IO.hSetBuffering IO.stdin IO.NoBuffering -- Suppress cursor hiding for WezTerm image compatibility termProgram <- lookupEnv "TERM_PROGRAM" unless (termProgram == Just "WezTerm") $ Ansi.hideCursor return (echo, buff, chan) teardown (echo, buff, _chan) = do Ansi.showCursor Ansi.clearScreen Ansi.setCursorPosition 0 0 IO.hSetEcho IO.stdin echo IO.hSetBuffering IO.stdin buff -------------------------------------------------------------------------------- withWatcher :: Bool -> Chan.Chan cmd -> FilePath -> cmd -> IO a -> IO a withWatcher False _ _ _ mx = mx withWatcher True chan filePath cmd mx = do mtime0 <- getModificationTime filePath Async.withAsync (watcher mtime0) (\_ -> mx) where watcher mtime0 = do -- The extra exists check helps because some editors temporarily make -- the file disappear while writing. exists <- doesFileExist filePath mtime1 <- if exists then getModificationTime filePath else return mtime0 when (mtime1 > mtime0) $ Chan.writeChan chan cmd threadDelay (200 * 1000) watcher mtime1 -------------------------------------------------------------------------------- -- | Wrapper for optional handles. withMaybeHandle :: (settings -> (handle -> IO a) -> IO a) -> Maybe settings -> (Maybe handle -> IO a) -> IO a withMaybeHandle _ Nothing f = f Nothing withMaybeHandle impl (Just settings) f = impl settings (f . Just) patat-0.14.1.0/lib/Patat/Presentation.hs000066400000000000000000000013061475634243500177070ustar00rootroot00000000000000module Patat.Presentation ( PresentationSettings (..) , defaultPresentationSettings , UniqueGen , Unique , zeroUniqueGen , Presentation (..) , readPresentation , activeSpeakerNotes , activeVars , Size , getPresentationSize , Display (..) , displayPresentation , displayPresentationError , dumpPresentation , PresentationCommand (..) , readPresentationCommand , UpdatedPresentation (..) , updatePresentation ) where import Patat.Presentation.Display import Patat.Presentation.Interactive import Patat.Presentation.Internal import Patat.Presentation.Read import Patat.Unique patat-0.14.1.0/lib/Patat/Presentation/000077500000000000000000000000001475634243500173535ustar00rootroot00000000000000patat-0.14.1.0/lib/Patat/Presentation/Display.hs000066400000000000000000000441711475634243500213230ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Patat.Presentation.Display ( Display (..) , displayPresentation , displayPresentationError , dumpPresentation ) where -------------------------------------------------------------------------------- import Control.Monad (guard) import Control.Monad.Identity (runIdentity) import Control.Monad.Writer (Writer, execWriter, tell) import qualified Data.Aeson.Extended as A import Data.Char.WCWidth.Extended (wcstrwidth) import Data.Foldable (for_) import qualified Data.HashMap.Strict as HMS import qualified Data.List as L import Data.Maybe (fromMaybe, maybeToList) import qualified Data.Sequence.Extended as Seq import qualified Data.Text as T import Patat.Presentation.Display.CodeBlock import Patat.Presentation.Display.Internal import Patat.Presentation.Display.Table import Patat.Presentation.Internal import Patat.Presentation.Settings import qualified Patat.Presentation.SpeakerNotes as SpeakerNotes import Patat.Presentation.Syntax import Patat.PrettyPrint ((<$$>), (<+>)) import qualified Patat.PrettyPrint as PP import Patat.Size import Patat.Theme (Theme (..)) import qualified Patat.Theme as Theme import Prelude import qualified Text.Pandoc.Extended as Pandoc -------------------------------------------------------------------------------- data Display = DisplayDoc PP.Doc | DisplayImage FilePath deriving (Show) -------------------------------------------------------------------------------- -- | Display something within the presentation borders that draw the title and -- the active slide number and so on. displayWithBorders :: Size -> Presentation -> (DisplaySettings -> PP.Doc) -> PP.Doc displayWithBorders (Size rows columns) pres@Presentation {..} f = (if null title then mempty else let titleRemainder = columns - titleWidth - titleOffset wrappedTitle = PP.spaces titleOffset <> PP.string title <> PP.spaces titleRemainder in borders wrappedTitle <> PP.hardline) <> f ds <> PP.hardline <> PP.goToLine (rows - 2) <> borders (PP.space <> PP.string author <> middleSpaces <> PP.string active <> PP.space) <> PP.hardline where -- Get terminal width/title settings = activeSettings pres (sidx, _) = pActiveFragment ds = DisplaySettings { dsSize = canvasSize , dsMargins = margins settings , dsWrap = fromMaybe NoWrap $ psWrap settings , dsTabStop = maybe 4 A.unFlexibleNum $ psTabStop settings , dsTheme = fromMaybe Theme.defaultTheme (psTheme settings) , dsSyntaxMap = pSyntaxMap , dsResolve = \var -> fromMaybe [] $ HMS.lookup var pVars , dsRevealState = revealState } revealState = case activeFragment pres of Just (ActiveContent _ _ c) -> c _ -> mempty -- Compute title. breadcrumbs = fromMaybe [] $ Seq.safeIndex pBreadcrumbs sidx plainTitle = PP.toString $ prettyInlines ds pTitle breadTitle = mappend plainTitle $ mconcat [ s | b <- map (prettyInlines ds . snd) breadcrumbs , s <- [" > ", PP.toString b] ] title | not . fromMaybe True $ psBreadcrumbs settings = plainTitle | wcstrwidth breadTitle > columns = plainTitle | otherwise = breadTitle -- Dimensions of title. titleWidth = wcstrwidth title titleOffset = (columns - titleWidth) `div` 2 borders = themed ds themeBorders -- Room left for content canvasSize = Size (rows - 3) columns -- Compute footer. active | fromMaybe True $ psSlideNumber settings = show (sidx + 1) ++ " / " ++ show (length pSlides) | otherwise = "" activeWidth = wcstrwidth active author = PP.toString (prettyInlines ds pAuthor) authorWidth = wcstrwidth author middleSpaces = PP.spaces $ columns - activeWidth - authorWidth - 2 -------------------------------------------------------------------------------- displayPresentation :: Size -> Presentation -> Display displayPresentation size pres@Presentation {..} = case activeFragment pres of Nothing -> DisplayDoc $ displayWithBorders size pres mempty Just (ActiveContent fragment _ _) | Just _ <- psImages pSettings , Just image <- onlyImage fragment -> DisplayImage $ T.unpack image Just (ActiveContent fragment _ _) -> DisplayDoc $ displayWithBorders size pres $ \theme -> prettyMargins theme fragment Just (ActiveTitle block) -> DisplayDoc $ displayWithBorders size pres $ \ds -> let auto = Margins {mTop = Auto, mRight = Auto, mLeft = Auto} in prettyMargins ds {dsMargins = auto} [block] where -- Check if the fragment consists of "just a single image". Discard -- headers. onlyImage (Header{} : bs) = onlyImage bs onlyImage bs = case bs of [Figure _ bs'] -> onlyImage bs' [Para [Image _ _ (target, _)]] -> Just target _ -> Nothing -------------------------------------------------------------------------------- -- | Displays an error in the place of the presentation. This is useful if we -- want to display an error but keep the presentation running. displayPresentationError :: Size -> Presentation -> String -> PP.Doc displayPresentationError size pres err = displayWithBorders size pres $ \ds -> themed ds themeStrong "Error occurred in the presentation:" <$$> "" <$$> (PP.string err) -------------------------------------------------------------------------------- dumpPresentation :: Presentation -> IO () dumpPresentation pres@Presentation {..} = PP.putDoc $ PP.removeControls $ PP.vcat $ L.intercalate ["{slide}"] $ map dumpSlide [0 .. length pSlides - 1] where dumpSlide :: Int -> [PP.Doc] dumpSlide i = do slide <- maybeToList $ getSlide i pres dumpSpeakerNotes slide <> L.intercalate ["{fragment}"] [ dumpFragment (i, j) | j <- [0 .. numFragments slide - 1] ] dumpSpeakerNotes :: Slide -> [PP.Doc] dumpSpeakerNotes slide = do guard (slideSpeakerNotes slide /= mempty) pure $ PP.text $ "{speakerNotes: " <> SpeakerNotes.toText (slideSpeakerNotes slide) <> "}" dumpFragment :: Index -> [PP.Doc] dumpFragment idx = case displayPresentation (getSize idx) pres {pActiveFragment = idx} of DisplayDoc doc -> [doc] DisplayImage filepath -> [PP.string $ "{image: " ++ filepath ++ "}"] getSize :: Index -> Size getSize idx = let settings = activeSettings pres {pActiveFragment = idx} sRows = fromMaybe 24 $ A.unFlexibleNum <$> psRows settings sCols = fromMaybe 72 $ A.unFlexibleNum <$> psColumns settings in Size {..} -------------------------------------------------------------------------------- -- | Renders the given blocks, adding margins based on the settings and wrapping -- based on width. prettyMargins :: DisplaySettings -> [Block] -> PP.Doc prettyMargins ds blocks = vertical $ map horizontal blocks ++ case prettyReferences ds blocks of [] -> [] refs -> let doc0 = PP.vcat refs size@(r, _) = PP.dimensions doc0 in [(horizontalIndent size $ horizontalWrap doc0, r)] where Size rows columns = dsSize ds Margins {..} = dsMargins ds -- For every block, calculate the size based on its last fragment. blockSize block = let revealState = blocksRevealLastStep [block] in PP.dimensions $ deindent $ horizontalWrap $ prettyBlock ds {dsRevealState = revealState} block -- Vertically align some blocks by adding spaces in front of it. -- We also take in the number of rows for every block so we don't -- need to recompute it. vertical :: [(PP.Doc, Int)] -> PP.Doc vertical docs0 = mconcat (replicate top PP.hardline) <> doc where top = case mTop of Auto -> (rows - actual) `div` 2 NotAuto x -> x docs1 = [verticalPad r d | (d, r) <- docs0] actual = sum $ L.intersperse 1 $ map snd docs1 doc = PP.vcat $ map fst docs1 -- Vertically pad a doc by adding lines below it. -- Return the actual size as well as the padded doc. verticalPad :: Int -> PP.Doc -> (PP.Doc, Int) verticalPad desired doc0 | actual >= rows = (doc0, actual) | otherwise = (doc0 <> padding, desired) where (actual, _) = PP.dimensions doc0 padding = mconcat $ replicate (desired - actual) PP.hardline -- Render and horizontally align a block. Also returns the desired rows. horizontal :: Block -> (PP.Doc, Int) horizontal b@(Reveal ConcatWrapper reveal) = -- Horizontally aligning a fragment with a ConcatWrapper is a special -- case, as we want to horizontal align all the things inside -- individually. let (fblocks, _) = unzip $ map horizontal $ revealToBlocks (dsRevealState ds) ConcatWrapper reveal in (PP.vcat fblocks, fst (blockSize b)) horizontal block = let size@(r, _) = blockSize block in (horizontalIndent size $ horizontalWrap $ prettyBlock ds block, r) horizontalIndent :: (Int, Int) -> PP.Doc -> PP.Doc horizontalIndent (_, dcols) doc0 = PP.indent indentation indentation doc1 where doc1 = deindent doc0 left = case mLeft of NotAuto x -> x Auto -> case mRight of NotAuto _ -> 0 Auto -> (columns - dcols) `div` 2 indentation = PP.Indentation left mempty -- Strip leading spaces to horizontally align code blocks etc. deindent doc0 = case (mLeft, mRight) of (Auto, Auto) -> PP.deindent doc0 _ -> doc0 -- Rearranges lines to fit into the wrap settings. horizontalWrap :: PP.Doc -> PP.Doc horizontalWrap doc0 = case dsWrap ds of NoWrap -> doc0 AutoWrap -> PP.wrapAt (Just $ columns - right - left) doc0 WrapAt col -> PP.wrapAt (Just col) doc0 where right = case mRight of Auto -> 0 NotAuto x -> x left = case mLeft of Auto -> 0 NotAuto x -> x -------------------------------------------------------------------------------- prettyBlock :: DisplaySettings -> Block -> PP.Doc prettyBlock ds (Plain inlines) = prettyInlines ds inlines prettyBlock ds (Para inlines) = prettyInlines ds inlines <> PP.hardline prettyBlock ds (Header i _ inlines) = themed ds themeHeader (PP.string (replicate i '#') <+> prettyInlines ds inlines) <> PP.hardline prettyBlock ds (CodeBlock (_, classes, _) txt) = prettyCodeBlock ds classes txt prettyBlock ds (BulletList bss) = PP.vcat [ PP.indent (PP.Indentation 2 $ themed ds themeBulletList prefix) (PP.Indentation 4 mempty) (prettyBlocks ds' bs) | bs <- bss ] <> PP.hardline where prefix = PP.string [marker] <> " " marker = case T.unpack <$> themeBulletListMarkers theme of Just (x : _) -> x _ -> '-' -- Cycle the markers. theme = dsTheme ds theme' = theme { themeBulletListMarkers = (\ls -> T.drop 1 ls <> T.take 1 ls) <$> themeBulletListMarkers theme } ds' = ds {dsTheme = theme'} prettyBlock ds (OrderedList _ bss) = PP.vcat [ PP.indent (PP.Indentation 0 $ themed ds themeOrderedList $ PP.string prefix) (PP.Indentation 4 mempty) (prettyBlocks ds bs) | (prefix, bs) <- zip padded bss ] <> PP.hardline where padded = [n ++ replicate (4 - length n) ' ' | n <- numbers] numbers = [ show i ++ "." | i <- [1 .. length bss] ] prettyBlock _ds (RawBlock _ t) = PP.text t <> PP.hardline prettyBlock _ds HorizontalRule = "---" prettyBlock ds (BlockQuote bs) = let quote = PP.Indentation 0 (themed ds themeBlockQuote "> ") in PP.indent quote quote (themed ds themeBlockQuote $ prettyBlocks ds bs) prettyBlock ds (DefinitionList terms) = PP.vcat $ map prettyDefinition terms where prettyDefinition (term, definitions) = themed ds themeDefinitionTerm (prettyInlines ds term) <$$> PP.hardline <> PP.vcat [ PP.indent (PP.Indentation 0 (themed ds themeDefinitionList ": ")) (PP.Indentation 4 mempty) $ prettyBlocks ds (plainToPara definition) | definition <- definitions ] plainToPara :: [Block] -> [Block] plainToPara = map $ \case Plain inlines -> Para inlines block -> block prettyBlock ds (Table caption aligns headers rows) = PP.wrapAt Nothing $ prettyTableDisplay ds TableDisplay { tdCaption = prettyInlines ds caption , tdAligns = map align aligns , tdHeaders = map (prettyBlocks ds) headers , tdRows = map (map (prettyBlocks ds)) rows } where align Pandoc.AlignLeft = PP.AlignLeft align Pandoc.AlignCenter = PP.AlignCenter align Pandoc.AlignDefault = PP.AlignLeft align Pandoc.AlignRight = PP.AlignRight prettyBlock ds (Div _attrs blocks) = prettyBlocks ds blocks prettyBlock ds (LineBlock inliness) = let ind = PP.Indentation 0 (themed ds themeLineBlock "| ") in PP.wrapAt Nothing $ PP.indent ind ind $ PP.vcat $ map (prettyInlines ds) inliness prettyBlock ds (Figure _attr blocks) = prettyBlocks ds blocks prettyBlock ds (Reveal w fragment) = prettyBlocks ds $ revealToBlocks (dsRevealState ds) w fragment prettyBlock ds (VarBlock var) = prettyBlocks ds $ dsResolve ds var prettyBlock _ (SpeakerNote _) = mempty prettyBlock _ (Config _) = mempty -------------------------------------------------------------------------------- prettyBlocks :: DisplaySettings -> [Block] -> PP.Doc prettyBlocks ds = PP.vcat . map (prettyBlock ds) -------------------------------------------------------------------------------- prettyInline :: DisplaySettings -> Inline -> PP.Doc prettyInline _ds Space = PP.space prettyInline _ds (Str str) = PP.text str prettyInline ds (Emph inlines) = themed ds themeEmph $ prettyInlines ds inlines prettyInline ds (Strong inlines) = themed ds themeStrong $ prettyInlines ds inlines prettyInline ds (Underline inlines) = themed ds themeUnderline $ prettyInlines ds inlines prettyInline ds (Code _ txt) = themed ds themeCode $ PP.text (" " <> txt <> " ") prettyInline ds link@(Link _attrs _text (target, _title)) | Just (text, _, _) <- toReferenceLink link = "[" <> themed ds themeLinkText (prettyInlines ds text) <> "]" | otherwise = "<" <> themed ds themeLinkTarget (PP.text target) <> ">" prettyInline _ds SoftBreak = PP.softline prettyInline _ds LineBreak = PP.hardline prettyInline ds (Strikeout t) = "~~" <> themed ds themeStrikeout (prettyInlines ds t) <> "~~" prettyInline ds (Quoted Pandoc.SingleQuote t) = "'" <> themed ds themeQuoted (prettyInlines ds t) <> "'" prettyInline ds (Quoted Pandoc.DoubleQuote t) = "'" <> themed ds themeQuoted (prettyInlines ds t) <> "'" prettyInline ds (Math _ t) = themed ds themeMath (PP.text t) prettyInline ds (Image _attrs text (target, _title)) = "![" <> themed ds themeImageText (prettyInlines ds text) <> "](" <> themed ds themeImageTarget (PP.text target) <> ")" prettyInline _ (RawInline _ t) = PP.text t -- These elements aren't really supported. prettyInline ds (Cite _ t) = prettyInlines ds t prettyInline ds (Span _ t) = prettyInlines ds t prettyInline _ (Note _) = mempty -- TODO: support notes? prettyInline ds (Superscript t) = prettyInlines ds t prettyInline ds (Subscript t) = prettyInlines ds t prettyInline ds (SmallCaps t) = prettyInlines ds t -- prettyInline unsupported = PP.ondullred $ PP.string $ show unsupported -------------------------------------------------------------------------------- prettyInlines :: DisplaySettings -> [Inline] -> PP.Doc prettyInlines ds = mconcat . map (prettyInline ds) -------------------------------------------------------------------------------- type Reference = ([Inline], T.Text, T.Text) -------------------------------------------------------------------------------- prettyReferences :: DisplaySettings -> [Block] -> [PP.Doc] prettyReferences ds = map prettyReference . execWriter . dftBlocks (pure . pure) tellReference where tellReference :: Inline -> Writer [Reference] [Inline] tellReference inline = do for_ (toReferenceLink inline) (tell . pure) pure [inline] prettyReference :: Reference -> PP.Doc prettyReference (text, target, title) = "[" <> themed ds themeLinkText (prettyInlines ds $ newlineToSpace text) <> "](" <> themed ds themeLinkTarget (PP.text target) <> (if T.null title then mempty else PP.space <> "\"" <> PP.text title <> "\"") <> ")" newlineToSpace :: [Inline] -> [Inline] newlineToSpace = runIdentity . dftInlines (pure . pure) work where work x = pure $ case x of SoftBreak -> [Space] LineBreak -> [Space] _ -> [x] -------------------------------------------------------------------------------- toReferenceLink :: Inline -> Maybe Reference toReferenceLink (Link _attrs text (target, title)) | [Str target] /= text = Just (text, target, title) toReferenceLink _ = Nothing patat-0.14.1.0/lib/Patat/Presentation/Display/000077500000000000000000000000001475634243500207605ustar00rootroot00000000000000patat-0.14.1.0/lib/Patat/Presentation/Display/CodeBlock.hs000066400000000000000000000100371475634243500231420ustar00rootroot00000000000000-------------------------------------------------------------------------------- -- | Displaying code blocks, optionally with syntax highlighting. {-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-} module Patat.Presentation.Display.CodeBlock ( prettyCodeBlock ) where -------------------------------------------------------------------------------- import Data.Char.WCWidth.Extended (wcstrwidth, wcwidth) import Data.Maybe (mapMaybe) import qualified Data.Text as T import Patat.Presentation.Display.Internal import qualified Patat.PrettyPrint as PP import Patat.Theme import Prelude import qualified Skylighting as Skylighting -------------------------------------------------------------------------------- highlight :: Skylighting.SyntaxMap -> [T.Text] -> T.Text -> [Skylighting.SourceLine] highlight extraSyntaxMap classes rawCodeBlock = case mapMaybe getSyntax classes of [] -> zeroHighlight rawCodeBlock (syn : _) -> case Skylighting.tokenize config syn rawCodeBlock of Left _ -> zeroHighlight rawCodeBlock Right sl -> sl where getSyntax :: T.Text -> Maybe Skylighting.Syntax getSyntax c = Skylighting.lookupSyntax c syntaxMap config :: Skylighting.TokenizerConfig config = Skylighting.TokenizerConfig { Skylighting.syntaxMap = syntaxMap , Skylighting.traceOutput = False } syntaxMap :: Skylighting.SyntaxMap syntaxMap = extraSyntaxMap <> Skylighting.defaultSyntaxMap -------------------------------------------------------------------------------- -- | This does fake highlighting, everything becomes a normal token. That makes -- things a bit easier, since we only need to deal with one cases in the -- renderer. zeroHighlight :: T.Text -> [Skylighting.SourceLine] zeroHighlight txt = [[(Skylighting.NormalTok, line)] | line <- T.lines txt] -------------------------------------------------------------------------------- -- | Expands tabs in code lines. expandTabs :: Int -> Skylighting.SourceLine -> Skylighting.SourceLine expandTabs tabStop = goTokens 0 where goTokens _ [] = [] goTokens col0 ((tokType, txt) : tokens) = goString col0 "" (T.unpack txt) $ \col1 str -> (tokType, T.pack str) : goTokens col1 tokens goString :: Int -> String -> String -> (Int -> String -> k) -> k goString !col acc str k = case str of [] -> k col (reverse acc) '\t' : t -> goString (col + spaces) (replicate spaces ' ' ++ acc) t k c : t -> goString (col + wcwidth c) (c : acc) t k where spaces = tabStop - col `mod` tabStop -------------------------------------------------------------------------------- prettyCodeBlock :: DisplaySettings -> [T.Text] -> T.Text -> PP.Doc prettyCodeBlock ds classes rawCodeBlock = PP.vcat (map blockified sourceLines) <> PP.hardline where sourceLines :: [Skylighting.SourceLine] sourceLines = map (expandTabs (dsTabStop ds)) $ [[]] ++ highlight (dsSyntaxMap ds) classes rawCodeBlock ++ [[]] prettySourceLine :: Skylighting.SourceLine -> PP.Doc prettySourceLine = mconcat . map prettyToken prettyToken :: Skylighting.Token -> PP.Doc prettyToken (tokenType, str) = themed ds (\theme -> syntaxHighlight theme tokenType) (PP.text str) sourceLineLength :: Skylighting.SourceLine -> Int sourceLineLength line = sum [wcstrwidth (T.unpack str) | (_, str) <- line] blockWidth :: Int blockWidth = foldr max 0 (map sourceLineLength sourceLines) blockified :: Skylighting.SourceLine -> PP.Doc blockified line = let len = sourceLineLength line indent = PP.Indentation 3 mempty in PP.indent indent indent $ themed ds themeCodeBlock $ " " <> prettySourceLine line <> PP.string (replicate (blockWidth - len) ' ') <> " " patat-0.14.1.0/lib/Patat/Presentation/Display/Internal.hs000066400000000000000000000025471475634243500231000ustar00rootroot00000000000000-------------------------------------------------------------------------------- module Patat.Presentation.Display.Internal ( DisplaySettings (..) , themed ) where -------------------------------------------------------------------------------- import Patat.Presentation.Internal (Margins) import Patat.Presentation.Settings (Wrap) import Patat.Presentation.Syntax (Block, RevealState, Var) import qualified Patat.PrettyPrint as PP import Patat.Size (Size) import qualified Patat.Theme as Theme import qualified Skylighting as Skylighting -------------------------------------------------------------------------------- data DisplaySettings = DisplaySettings { dsSize :: !Size , dsWrap :: !Wrap , dsTabStop :: !Int , dsMargins :: !Margins , dsTheme :: !Theme.Theme , dsSyntaxMap :: !Skylighting.SyntaxMap , dsResolve :: !(Var -> [Block]) , dsRevealState :: !RevealState } -------------------------------------------------------------------------------- themed :: DisplaySettings -> (Theme.Theme -> Maybe Theme.Style) -> PP.Doc -> PP.Doc themed ds f = case f (dsTheme ds) of Nothing -> id Just (Theme.Style []) -> id Just (Theme.Style codes) -> PP.ansi codes patat-0.14.1.0/lib/Patat/Presentation/Display/Table.hs000066400000000000000000000067041475634243500223520ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Patat.Presentation.Display.Table ( TableDisplay (..) , prettyTableDisplay , themed ) where -------------------------------------------------------------------------------- import Data.List (intersperse, transpose) import Patat.Presentation.Display.Internal import Patat.PrettyPrint ((<$$>)) import qualified Patat.PrettyPrint as PP import Patat.Theme (Theme (..)) import Prelude -------------------------------------------------------------------------------- data TableDisplay = TableDisplay { tdCaption :: PP.Doc , tdAligns :: [PP.Alignment] , tdHeaders :: [PP.Doc] , tdRows :: [[PP.Doc]] } -------------------------------------------------------------------------------- prettyTableDisplay :: DisplaySettings -> TableDisplay -> PP.Doc prettyTableDisplay ds TableDisplay {..} = PP.indent indentation indentation $ lineIf (not isHeaderLess) (hcat2 headerHeight [ themed ds themeTableHeader $ PP.align w a (vpad headerHeight header) | (w, a, header) <- zip3 columnWidths tdAligns tdHeaders ]) <> dashedHeaderSeparator ds columnWidths <$$> joinRows [ hcat2 rowHeight [ PP.align w a (vpad rowHeight cell) | (w, a, cell) <- zip3 columnWidths tdAligns row ] | (rowHeight, row) <- zip rowHeights tdRows ] <$$> lineIf isHeaderLess (dashedHeaderSeparator ds columnWidths) <> lineIf (not $ PP.null tdCaption) (PP.hardline <> "Table: " <> tdCaption) where indentation = PP.Indentation 2 mempty lineIf cond line = if cond then line <> PP.hardline else mempty joinRows | all (all isSimpleCell) tdRows = PP.vcat | otherwise = PP.vcat . intersperse "" isHeaderLess = all PP.null tdHeaders headerDimensions = map PP.dimensions tdHeaders :: [(Int, Int)] rowDimensions = map (map PP.dimensions) tdRows :: [[(Int, Int)]] columnWidths :: [Int] columnWidths = [ safeMax (map snd col) | col <- transpose (headerDimensions : rowDimensions) ] rowHeights = map (safeMax . map fst) rowDimensions :: [Int] headerHeight = safeMax (map fst headerDimensions) :: Int vpad :: Int -> PP.Doc -> PP.Doc vpad height doc = let (actual, _) = PP.dimensions doc in doc <> mconcat (replicate (height - actual) PP.hardline) safeMax = foldr max 0 hcat2 :: Int -> [PP.Doc] -> PP.Doc hcat2 rowHeight = PP.paste . intersperse (spaces2 rowHeight) spaces2 :: Int -> PP.Doc spaces2 rowHeight = mconcat $ intersperse PP.hardline $ replicate rowHeight (PP.string " ") -------------------------------------------------------------------------------- isSimpleCell :: PP.Doc -> Bool isSimpleCell = (<= 1) . fst . PP.dimensions -------------------------------------------------------------------------------- dashedHeaderSeparator :: DisplaySettings -> [Int] -> PP.Doc dashedHeaderSeparator ds columnWidths = mconcat $ intersperse (PP.string " ") [ themed ds themeTableSeparator (PP.string (replicate w '-')) | w <- columnWidths ] patat-0.14.1.0/lib/Patat/Presentation/Fragment.hs000066400000000000000000000144471475634243500214640ustar00rootroot00000000000000-- | For background info on the spec, see the "Incremental lists" section of the -- the pandoc manual. {-# LANGUAGE CPP #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE OverloadedStrings #-} module Patat.Presentation.Fragment ( FragmentSettings (..) , fragmentPresentation , fragmentBlocks , fragmentBlock ) where import Control.Monad.State (State, runState, state) import Data.List (intersperse) import Data.Maybe (fromMaybe) import qualified Data.Set as S import Patat.Presentation.Internal import Patat.Presentation.Syntax import Patat.Unique import Prelude fragmentPresentation :: Presentation -> Presentation fragmentPresentation presentation = let (pres, uniqueGen) = runState work (pUniqueGen presentation) in pres {pUniqueGen = uniqueGen} where work = do slides <- traverse fragmentSlide (pSlides presentation) pure presentation {pSlides = slides} fragmentSlide slide = case slideContent slide of TitleSlide _ _ -> pure slide ContentSlide blocks0 -> do blocks1 <- fragmentBlocks fragmentSettings blocks0 pure slide {slideContent = ContentSlide blocks1} settings = pSettings presentation fragmentSettings = FragmentSettings { fsIncrementalLists = fromMaybe False (psIncrementalLists settings) } data FragmentSettings = FragmentSettings { fsIncrementalLists :: !Bool } deriving (Show) type FragmentM = State UniqueGen splitOnThreeDots :: [Block] -> [[Block]] splitOnThreeDots blocks = case break (== threeDots) blocks of (pre, _ : post) -> [pre] ++ splitOnThreeDots post (pre, []) -> [pre] where threeDots = Para $ intersperse Space $ replicate 3 (Str ".") fragmentBlocks :: FragmentSettings -> [Block] -> FragmentM [Block] fragmentBlocks fs blocks = (>>= fragmentAgainAfterLists) $ case splitOnThreeDots blocks of [] -> pure [] [_] -> concat <$> traverse (fragmentBlock fs) blocks sections0@(_ : _) -> do revealID <- RevealID <$> state freshUnique sections1 <- traverse (fragmentBlocks fs) sections0 let pauses = length sections1 - 1 triggers = case sections1 of [] -> replicate pauses revealID (sh : st) -> blocksRevealOrder sh ++ [c | s <- st, c <- revealID : blocksRevealOrder s] pure $ pure $ Reveal ConcatWrapper $ RevealSequence revealID triggers [(S.fromList [i .. pauses], s) | (i, s) <- zip [0 ..] sections1] fragmentBlock :: FragmentSettings -> Block -> FragmentM [Block] fragmentBlock _fs (Para inlines) = pure [Para inlines] fragmentBlock fs (BulletList bs0) = fragmentList fs (fsIncrementalLists fs) BulletListWrapper bs0 fragmentBlock fs (OrderedList attr bs0) = fragmentList fs (fsIncrementalLists fs) (OrderedListWrapper attr) bs0 fragmentBlock fs (BlockQuote [BulletList bs0]) = fragmentList fs (not $ fsIncrementalLists fs) BulletListWrapper bs0 fragmentBlock fs (BlockQuote [OrderedList attr bs0]) = fragmentList fs (not $ fsIncrementalLists fs) (OrderedListWrapper attr) bs0 fragmentBlock _ block@(BlockQuote {}) = pure [block] fragmentBlock _ block@(Header {}) = pure [block] fragmentBlock _ block@(Plain {}) = pure [block] fragmentBlock _ block@(CodeBlock {}) = pure [block] fragmentBlock _ block@(RawBlock {}) = pure [block] fragmentBlock _ block@(DefinitionList {}) = pure [block] fragmentBlock _ block@(Table {}) = pure [block] fragmentBlock _ block@(Div {}) = pure [block] fragmentBlock _ block@HorizontalRule = pure [block] fragmentBlock _ block@(LineBlock {}) = pure [block] fragmentBlock _ block@(Figure {}) = pure [block] fragmentBlock _ block@(VarBlock {}) = pure [block] fragmentBlock _ block@(SpeakerNote {}) = pure [block] fragmentBlock _ block@(Config {}) = pure [block] fragmentBlock _ block@(Reveal {}) = pure [block] -- Should not happen fragmentList :: FragmentSettings -- ^ Global settings -> Bool -- ^ Fragment THIS list? -> RevealWrapper -- ^ List constructor -> [[Block]] -- ^ List items -> FragmentM [Block] -- ^ Resulting list fragmentList fs fragmentThisList rw items0 = do items1 <- traverse (fragmentBlocks fs) items0 case fragmentThisList of False -> pure $ revealWrapper rw items1 True -> do revealID <- RevealID <$> state freshUnique let triggers = [c | s <- items1, c <- revealID : blocksRevealOrder s] pauses = length items1 pure $ pure $ Reveal rw $ RevealSequence revealID triggers [ (S.fromList [i .. pauses], s) | (i, s) <- zip [1 ..] items1 ] -- Insert a final pause after any incremental lists. This needs to happen -- on the list containing these blocks. fragmentAgainAfterLists :: [Block] -> FragmentM [Block] fragmentAgainAfterLists blocks = case splitAfterLists [] blocks of [] -> pure [] [_] -> pure blocks sections@(_ : _) -> do revealID <- RevealID <$> state freshUnique let pauses = length sections - 1 triggers = init -- Use init to skip the final counter (we don't want to add -- a pause at the very end since everything is displayed at -- that point). [c | s <- sections, c <- blocksRevealOrder s ++ [revealID]] pure $ pure $ Reveal ConcatWrapper $ RevealSequence revealID triggers [(S.fromList [i .. pauses + 1], s) | (i, s) <- zip [0 ..] sections] where splitAfterLists :: [Block] -> [Block] -> [[Block]] splitAfterLists acc [] = [reverse acc] splitAfterLists acc (b@(Reveal w _) : bs) | isListWrapper w, not (null bs) = reverse (b : acc) : splitAfterLists [] bs splitAfterLists acc (b : bs) = splitAfterLists (b : acc) bs isListWrapper BulletListWrapper = True isListWrapper (OrderedListWrapper _) = True isListWrapper ConcatWrapper = False patat-0.14.1.0/lib/Patat/Presentation/Interactive.hs000066400000000000000000000125611475634243500221710ustar00rootroot00000000000000-------------------------------------------------------------------------------- -- | Module that allows the user to interact with the presentation {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Patat.Presentation.Interactive ( PresentationCommand (..) , readPresentationCommand , UpdatedPresentation (..) , updatePresentation ) where -------------------------------------------------------------------------------- import Data.Char (isDigit) import Patat.Presentation.Internal import Patat.Presentation.Read import Patat.Presentation.Syntax import qualified System.IO as IO import Text.Read (readMaybe) -------------------------------------------------------------------------------- data PresentationCommand = Exit | Forward | Backward | SkipForward | SkipBackward | First | Last | Reload | Seek Int | UpdateVar Var [Block] | UnknownCommand String deriving (Eq, Show) -------------------------------------------------------------------------------- readPresentationCommand :: IO.Handle -> IO PresentationCommand readPresentationCommand h = do k <- readKeys case k of "q" -> return Exit "\n" -> return Forward "\DEL" -> return Backward "h" -> return Backward "j" -> return SkipForward "k" -> return SkipBackward "l" -> return Forward -- Arrow keys "\ESC[C" -> return Forward "\ESC[D" -> return Backward "\ESC[B" -> return SkipForward "\ESC[A" -> return SkipBackward -- PageUp and PageDown "\ESC[6" -> return Forward "\ESC[5" -> return Backward "0" -> return First "G" -> return Last "r" -> return Reload -- Number followed by enter _ | Just n <- readMaybe k -> return (Seek n) _ -> return (UnknownCommand k) where readKeys :: IO String readKeys = do c0 <- IO.hGetChar h case c0 of '\ESC' -> do c1 <- IO.hGetChar h case c1 of '[' -> do c2 <- IO.hGetChar h return [c0, c1, c2] _ -> return [c0, c1] _ | isDigit c0 && c0 /= '0' -> (c0 :) <$> readDigits _ -> return [c0] readDigits :: IO String readDigits = do c <- IO.hGetChar h if isDigit c then (c :) <$> readDigits else return [c] -------------------------------------------------------------------------------- data UpdatedPresentation = UpdatedPresentation !Presentation | ExitedPresentation | ErroredPresentation String -------------------------------------------------------------------------------- updatePresentation :: PresentationCommand -> Presentation -> IO UpdatedPresentation updatePresentation cmd presentation = case cmd of Exit -> pure ExitedPresentation Forward -> pure $ goToSlide $ \(s, f) -> (s, f + 1) Backward -> pure $ goToSlide $ \(s, f) -> (s, f - 1) SkipForward -> pure $ goToSlide $ \(s, _) -> (s + 10, 0) SkipBackward -> pure $ goToSlide $ \(s, _) -> (s - 10, 0) First -> pure $ goToSlide $ \_ -> (0, 0) Last -> pure $ goToSlide $ \_ -> (numSlides presentation, 0) Seek n -> pure $ goToSlide $ \_ -> (n - 1, 0) Reload -> reloadPresentation UnknownCommand _ -> pure $ UpdatedPresentation presentation UpdateVar v b -> pure $ UpdatedPresentation $ updateVar v b presentation where numSlides :: Presentation -> Int numSlides pres = length (pSlides pres) clip :: Index -> Presentation -> Index clip (slide, fragment) pres | slide >= numSlides pres = (numSlides pres - 1, lastFragments - 1) | slide < 0 = (0, 0) | fragment >= numFragments' slide = if slide + 1 >= numSlides pres then (slide, lastFragments - 1) else (slide + 1, 0) | fragment < 0 = if slide - 1 >= 0 then (slide - 1, numFragments' (slide - 1) - 1) else (slide, 0) | otherwise = (slide, fragment) where numFragments' s = maybe 1 numFragments (getSlide s pres) lastFragments = numFragments' (numSlides pres - 1) goToSlide :: (Index -> Index) -> UpdatedPresentation goToSlide f = UpdatedPresentation $ presentation { pActiveFragment = clip (f $ pActiveFragment presentation) presentation } reloadPresentation = do errOrPres <- readPresentation (pUniqueGen presentation) (pFilePath presentation) return $ case errOrPres of Left err -> ErroredPresentation err Right pres -> UpdatedPresentation $ pres { pActiveFragment = clip (pActiveFragment presentation) pres } patat-0.14.1.0/lib/Patat/Presentation/Internal.hs000066400000000000000000000154551475634243500214750ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} module Patat.Presentation.Internal ( Breadcrumbs , Presentation (..) , PresentationSettings (..) , defaultPresentationSettings , MarginSettings (..) , Margins (..) , margins , ExtensionList (..) , defaultExtensionList , ImageSettings (..) , EvalSettingsMap , EvalSettings (..) , Slide (..) , SlideContent (..) , Index , getSlide , numFragments , ActiveFragment (..) , activeFragment , activeSpeakerNotes , activeVars , getSettings , activeSettings , Size , getPresentationSize , updateVar ) where -------------------------------------------------------------------------------- import qualified Data.Aeson.Extended as A import qualified Data.HashMap.Strict as HMS import qualified Data.HashSet as HS import Data.Maybe (fromMaybe) import Data.Sequence.Extended (Seq) import qualified Data.Sequence.Extended as Seq import Patat.EncodingFallback (EncodingFallback) import qualified Patat.Eval.Internal as Eval import Patat.Presentation.Settings import qualified Patat.Presentation.SpeakerNotes as SpeakerNotes import Patat.Presentation.Syntax import Patat.Size import Patat.Transition (TransitionGen) import Patat.Unique import Prelude import qualified Skylighting as Skylighting import qualified Text.Pandoc as Pandoc -------------------------------------------------------------------------------- type Breadcrumbs = [(Int, [Inline])] -------------------------------------------------------------------------------- data Presentation = Presentation { pFilePath :: !FilePath , pEncodingFallback :: !EncodingFallback , pTitle :: ![Inline] , pAuthor :: ![Inline] , pSettings :: !PresentationSettings , pSlides :: !(Seq Slide) , pBreadcrumbs :: !(Seq Breadcrumbs) -- One for each slide. , pSlideSettings :: !(Seq PresentationSettings) -- One for each slide. , pTransitionGens :: !(Seq (Maybe TransitionGen)) -- One for each slide. , pActiveFragment :: !Index , pSyntaxMap :: !Skylighting.SyntaxMap , pEvalBlocks :: !Eval.EvalBlocks , pUniqueGen :: !UniqueGen , pVars :: !(HMS.HashMap Var [Block]) } -------------------------------------------------------------------------------- data Margins = Margins { mTop :: AutoOr Int , mLeft :: AutoOr Int , mRight :: AutoOr Int } deriving (Show) -------------------------------------------------------------------------------- margins :: PresentationSettings -> Margins margins ps = Margins { mLeft = get 0 msLeft , mRight = get 0 msRight , mTop = get 1 msTop } where get def f = case psMargins ps >>= f of Just Auto -> Auto Nothing -> NotAuto def Just (NotAuto fn) -> NotAuto $ A.unFlexibleNum fn -------------------------------------------------------------------------------- data Slide = Slide { slideSpeakerNotes :: !SpeakerNotes.SpeakerNotes , slideSettings :: !(Either String PresentationSettings) , slideContent :: !SlideContent } deriving (Show) -------------------------------------------------------------------------------- data SlideContent = ContentSlide [Block] | TitleSlide Int [Inline] deriving (Show) -------------------------------------------------------------------------------- -- | Active slide, active fragment. type Index = (Int, Int) -------------------------------------------------------------------------------- getSlide :: Int -> Presentation -> Maybe Slide getSlide sidx = (`Seq.safeIndex` sidx) . pSlides -------------------------------------------------------------------------------- numFragments :: Slide -> Int numFragments slide = case slideContent slide of ContentSlide blocks -> blocksRevealSteps blocks TitleSlide _ _ -> 1 -------------------------------------------------------------------------------- data ActiveFragment = ActiveContent [Block] (HS.HashSet Var) RevealState | ActiveTitle Block deriving (Show) -------------------------------------------------------------------------------- activeFragment :: Presentation -> Maybe ActiveFragment activeFragment presentation = do let (sidx, fidx) = pActiveFragment presentation slide <- getSlide sidx presentation pure $ case slideContent slide of TitleSlide lvl is -> ActiveTitle $ Header lvl Pandoc.nullAttr is ContentSlide blocks -> let vars = variables $ blocksReveal revealState blocks revealState = blocksRevealStep fidx blocks in ActiveContent blocks vars revealState -------------------------------------------------------------------------------- activeSpeakerNotes :: Presentation -> SpeakerNotes.SpeakerNotes activeSpeakerNotes presentation = fromMaybe mempty $ do let (sidx, _) = pActiveFragment presentation slide <- getSlide sidx presentation pure $ slideSpeakerNotes slide -------------------------------------------------------------------------------- activeVars :: Presentation -> HS.HashSet Var activeVars presentation = case activeFragment presentation of Just (ActiveContent _ vars _) -> vars _ -> mempty -------------------------------------------------------------------------------- getSettings :: Int -> Presentation -> PresentationSettings getSettings sidx pres = fromMaybe mempty (Seq.safeIndex (pSlideSettings pres) sidx) <> pSettings pres -------------------------------------------------------------------------------- activeSettings :: Presentation -> PresentationSettings activeSettings pres = let (sidx, _) = pActiveFragment pres in getSettings sidx pres -------------------------------------------------------------------------------- getPresentationSize :: Presentation -> IO Size getPresentationSize pres = do term <- getTerminalSize let rows = fromMaybe (sRows term) $ A.unFlexibleNum <$> psRows settings cols = fromMaybe (sCols term) $ A.unFlexibleNum <$> psColumns settings pure $ Size {sRows = rows, sCols = cols} where settings = activeSettings pres -------------------------------------------------------------------------------- updateVar :: Var -> [Block] -> Presentation -> Presentation updateVar var blocks pres = pres {pVars = HMS.insert var blocks $ pVars pres} patat-0.14.1.0/lib/Patat/Presentation/Read.hs000066400000000000000000000306421475634243500205670ustar00rootroot00000000000000-- | Read a presentation from disk. {-# LANGUAGE BangPatterns #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Patat.Presentation.Read ( readPresentation -- Exposed for testing mostly. , detectSlideLevel , readMetaSettings ) where -------------------------------------------------------------------------------- import Control.Monad (guard) import Control.Monad.Except (ExceptT (..), runExceptT, throwError) import Control.Monad.Trans (liftIO) import qualified Data.Aeson.Extended as A import qualified Data.Aeson.KeyMap as AKM import Data.Bifunctor (first) import Data.Maybe (fromMaybe) import Data.Sequence.Extended (Seq) import qualified Data.Sequence.Extended as Seq import qualified Data.Text as T import qualified Data.Text.Encoding as T import Data.Traversable (for) import qualified Data.Yaml as Yaml import Patat.EncodingFallback (EncodingFallback) import qualified Patat.EncodingFallback as EncodingFallback import qualified Patat.Eval as Eval import Patat.Presentation.Fragment import Patat.Presentation.Internal import qualified Patat.Presentation.SpeakerNotes as SpeakerNotes import Patat.Presentation.Syntax import Patat.Transition (parseTransitionSettings) import Patat.Unique import Prelude import qualified Skylighting as Skylighting import System.Directory (XdgDirectory (XdgConfig), doesFileExist, getHomeDirectory, getXdgDirectory) import System.FilePath (splitFileName, takeExtension, ()) import qualified Text.Pandoc.Error as Pandoc import qualified Text.Pandoc.Extended as Pandoc -------------------------------------------------------------------------------- readPresentation :: UniqueGen -> FilePath -> IO (Either String Presentation) readPresentation uniqueGen filePath = runExceptT $ do -- We need to read the settings first. (enc, src) <- liftIO $ EncodingFallback.readFile filePath homeSettings <- ExceptT readHomeSettings xdgSettings <- ExceptT readXdgSettings metaSettings <- ExceptT $ return $ readMetaSettings src let settings = metaSettings <> xdgSettings <> homeSettings <> defaultPresentationSettings syntaxMap <- ExceptT $ readSyntaxMap $ fromMaybe [] $ psSyntaxDefinitions settings let pexts = fromMaybe defaultExtensionList (psPandocExtensions settings) reader <- case readExtension pexts ext of Nothing -> throwError $ "Unknown file extension: " ++ show ext Just x -> return x doc <- case reader src of Left e -> throwError $ "Could not parse document: " ++ show e Right x -> return x pres <- ExceptT $ pure $ pandocToPresentation uniqueGen filePath enc settings syntaxMap doc pure $ fragmentPresentation $ Eval.parseEvalBlocks pres where ext = takeExtension filePath -------------------------------------------------------------------------------- readSyntaxMap :: [FilePath] -> IO (Either String Skylighting.SyntaxMap) readSyntaxMap = runExceptT . fmap (foldr Skylighting.addSyntaxDefinition mempty) . traverse (ExceptT . Skylighting.loadSyntaxFromFile) -------------------------------------------------------------------------------- readExtension :: ExtensionList -> String -> Maybe (T.Text -> Either Pandoc.PandocError Pandoc.Pandoc) readExtension (ExtensionList extensions) fileExt = case fileExt of ".markdown" -> Just $ Pandoc.runPure . Pandoc.readMarkdown readerOpts ".md" -> Just $ Pandoc.runPure . Pandoc.readMarkdown readerOpts ".mdown" -> Just $ Pandoc.runPure . Pandoc.readMarkdown readerOpts ".mdtext" -> Just $ Pandoc.runPure . Pandoc.readMarkdown readerOpts ".mdtxt" -> Just $ Pandoc.runPure . Pandoc.readMarkdown readerOpts ".mdwn" -> Just $ Pandoc.runPure . Pandoc.readMarkdown readerOpts ".mkd" -> Just $ Pandoc.runPure . Pandoc.readMarkdown readerOpts ".mkdn" -> Just $ Pandoc.runPure . Pandoc.readMarkdown readerOpts ".lhs" -> Just $ Pandoc.runPure . Pandoc.readMarkdown lhsOpts "" -> Just $ Pandoc.runPure . Pandoc.readMarkdown readerOpts ".org" -> Just $ Pandoc.runPure . Pandoc.readOrg readerOpts ".txt" -> Just $ pure . Pandoc.readPlainText _ -> Nothing where readerOpts = Pandoc.def { Pandoc.readerExtensions = extensions <> absolutelyRequiredExtensions } lhsOpts = readerOpts { Pandoc.readerExtensions = Pandoc.readerExtensions readerOpts <> Pandoc.extensionsFromList [Pandoc.Ext_literate_haskell] } absolutelyRequiredExtensions = Pandoc.extensionsFromList [Pandoc.Ext_yaml_metadata_block] -------------------------------------------------------------------------------- pandocToPresentation :: UniqueGen -> FilePath -> EncodingFallback -> PresentationSettings -> Skylighting.SyntaxMap -> Pandoc.Pandoc -> Either String Presentation pandocToPresentation pUniqueGen pFilePath pEncodingFallback pSettings pSyntaxMap pandoc@(Pandoc.Pandoc meta _) = do let !pTitle = case Pandoc.docTitle meta of [] -> [Str . T.pack . snd $ splitFileName pFilePath] title -> fromPandocInlines title !pSlides = pandocToSlides pSettings pandoc !pBreadcrumbs = collectBreadcrumbs pSlides !pActiveFragment = (0, 0) !pAuthor = fromPandocInlines $ concat $ Pandoc.docAuthors meta !pEvalBlocks = mempty !pVars = mempty pSlideSettings <- Seq.traverseWithIndex (\i slide -> case slideSettings slide of Left err -> Left $ "on slide " ++ show (i + 1) ++ ": " ++ err Right cfg -> pure cfg) pSlides pTransitionGens <- for pSlideSettings $ \slideSettings -> case psTransition (slideSettings <> pSettings) of Nothing -> pure Nothing Just ts -> Just <$> parseTransitionSettings ts return $ Presentation {..} -------------------------------------------------------------------------------- -- | This re-parses the pandoc metadata block using the YAML library. This -- avoids the problems caused by pandoc involving rendering Markdown. This -- should only be used for settings though, not things like title / authors -- since those /can/ contain markdown. parseMetadataBlock :: T.Text -> Maybe (Either String A.Value) parseMetadataBlock src = case T.lines src of ("---" : ls) -> case break (`elem` ["---", "..."]) ls of (_, []) -> Nothing (block, (_ : _)) -> Just . first Yaml.prettyPrintParseException . Yaml.decodeEither' . T.encodeUtf8 . T.unlines $! block _ -> Nothing -------------------------------------------------------------------------------- -- | Read settings from the metadata block in the Pandoc document. readMetaSettings :: T.Text -> Either String PresentationSettings readMetaSettings src = case parseMetadataBlock src of Nothing -> Right mempty Just (Left err) -> Left err Just (Right (A.Object obj)) | Just val <- AKM.lookup "patat" obj -> first (\err -> "Error parsing patat settings from metadata: " ++ err) $! A.resultToEither $! A.fromJSON val Just (Right _) -> Right mempty -------------------------------------------------------------------------------- -- | Read settings from "$HOME/.patat.yaml". readHomeSettings :: IO (Either String PresentationSettings) readHomeSettings = do home <- getHomeDirectory readSettings $ home ".patat.yaml" -------------------------------------------------------------------------------- -- | Read settings from "$XDG_CONFIG_DIRECTORY/patat/config.yaml". readXdgSettings :: IO (Either String PresentationSettings) readXdgSettings = getXdgDirectory XdgConfig ("patat" "config.yaml") >>= readSettings -------------------------------------------------------------------------------- -- | Read settings from the specified path, if it exists. readSettings :: FilePath -> IO (Either String PresentationSettings) readSettings path = do exists <- doesFileExist path if not exists then return (Right mempty) else do errOrPs <- Yaml.decodeFileEither path return $! case errOrPs of Left err -> Left (show err) Right ps -> Right ps -------------------------------------------------------------------------------- pandocToSlides :: PresentationSettings -> Pandoc.Pandoc -> Seq.Seq Slide pandocToSlides settings (Pandoc.Pandoc _meta pblocks) = let blocks = fromPandocBlocks pblocks slideLevel = fromMaybe (detectSlideLevel blocks) (psSlideLevel settings) unfragmented = splitSlides slideLevel blocks in Seq.fromList unfragmented -------------------------------------------------------------------------------- -- | Find level of header that starts slides. This is defined as the least -- header that occurs before a non-header in the blocks. detectSlideLevel :: [Block] -> Int detectSlideLevel blocks0 = go 6 $ filter (not . isComment) blocks0 where go level (Header n _ _ : x : xs) | n < level && not (isHeader x) = go n xs | otherwise = go level (x:xs) go level (_ : xs) = go level xs go level [] = level isHeader (Header _ _ _) = True isHeader _ = False -------------------------------------------------------------------------------- -- | Split a pandoc document into slides. If the document contains horizonal -- rules, we use those as slide delimiters. If there are no horizontal rules, -- we split using headers, determined by the slide level (see -- 'detectSlideLevel'). splitSlides :: Int -> [Block] -> [Slide] splitSlides slideLevel blocks0 | any isHorizontalRule blocks0 = splitAtRules blocks0 | otherwise = splitAtHeaders [] blocks0 where mkContentSlide :: [Block] -> [Slide] mkContentSlide bs0 = do let bs1 = filter (not . isComment) bs0 sns = SpeakerNotes.SpeakerNotes [s | SpeakerNote s <- bs0] cfgs = concatCfgs [cfg | Config cfg <- bs0] guard $ not $ null bs1 -- Never create empty slides pure $ Slide sns cfgs $ ContentSlide bs1 splitAtRules blocks = case break isHorizontalRule blocks of (xs, []) -> mkContentSlide xs (xs, (_rule : ys)) -> mkContentSlide xs ++ splitAtRules ys splitAtHeaders acc [] = mkContentSlide (reverse acc) splitAtHeaders acc (b@(Header i _ txt) : bs0) | i > slideLevel = splitAtHeaders (b : acc) bs0 | i == slideLevel = mkContentSlide (reverse acc) ++ splitAtHeaders [b] bs0 | otherwise = let (cmnts, bs1) = break (not . isComment) bs0 sns = SpeakerNotes.SpeakerNotes [s | SpeakerNote s <- cmnts] cfgs = concatCfgs [cfg | Config cfg <- cmnts] in mkContentSlide (reverse acc) ++ [Slide sns cfgs $ TitleSlide i txt] ++ splitAtHeaders [] bs1 splitAtHeaders acc (b : bs) = splitAtHeaders (b : acc) bs concatCfgs :: [Either String PresentationSettings] -> Either String PresentationSettings concatCfgs = fmap mconcat . sequence -------------------------------------------------------------------------------- collectBreadcrumbs :: Seq Slide -> Seq Breadcrumbs collectBreadcrumbs = go [] . fmap slideContent where go breadcrumbs slides0 = case Seq.viewl slides0 of Seq.EmptyL -> Seq.empty ContentSlide _ Seq.:< slides -> breadcrumbs `Seq.cons` go breadcrumbs slides TitleSlide lvl inlines Seq.:< slides -> let parent = filter ((< lvl) . fst) breadcrumbs in parent `Seq.cons` go (parent ++ [(lvl, inlines)]) slides patat-0.14.1.0/lib/Patat/Presentation/Settings.hs000066400000000000000000000310131475634243500215050ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} module Patat.Presentation.Settings ( PresentationSettings (..) , defaultPresentationSettings , Wrap (..) , AutoOr (..) , MarginSettings (..) , ExtensionList (..) , defaultExtensionList , ImageSettings (..) , EvalSettingsMap , EvalSettingsContainer (..) , EvalSettings (..) , SpeakerNotesSettings (..) , TransitionSettings (..) , parseSlideSettings ) where -------------------------------------------------------------------------------- import Control.Applicative ((<|>)) import Control.Monad (mplus, unless) import qualified Data.Aeson.Extended as A import qualified Data.Aeson.TH.Extended as A import qualified Data.Foldable as Foldable import Data.Function (on) import qualified Data.HashMap.Strict as HMS import Data.List (intercalate) import qualified Data.Text as T import qualified Patat.Theme as Theme import Prelude import qualified Text.Pandoc as Pandoc import Text.Read (readMaybe) -------------------------------------------------------------------------------- -- | These are patat-specific settings. That is where they differ from more -- general metadata (author, title...) data PresentationSettings = PresentationSettings { psRows :: !(Maybe (A.FlexibleNum Int)) , psColumns :: !(Maybe (A.FlexibleNum Int)) , psMargins :: !(Maybe MarginSettings) , psWrap :: !(Maybe Wrap) , psTabStop :: !(Maybe (A.FlexibleNum Int)) , psTheme :: !(Maybe Theme.Theme) , psIncrementalLists :: !(Maybe Bool) , psAutoAdvanceDelay :: !(Maybe (A.FlexibleNum Int)) , psSlideLevel :: !(Maybe Int) , psPandocExtensions :: !(Maybe ExtensionList) , psImages :: !(Maybe ImageSettings) , psBreadcrumbs :: !(Maybe Bool) , psEval :: !(Maybe EvalSettingsMap) , psSlideNumber :: !(Maybe Bool) , psSyntaxDefinitions :: !(Maybe [FilePath]) , psSpeakerNotes :: !(Maybe SpeakerNotesSettings) , psTransition :: !(Maybe TransitionSettings) } deriving (Eq, Show) -------------------------------------------------------------------------------- instance Semigroup PresentationSettings where l <> r = PresentationSettings { psRows = on mplus psRows l r , psColumns = on mplus psColumns l r , psMargins = on (<>) psMargins l r , psWrap = on mplus psWrap l r , psTabStop = on mplus psTabStop l r , psTheme = on (<>) psTheme l r , psIncrementalLists = on mplus psIncrementalLists l r , psAutoAdvanceDelay = on mplus psAutoAdvanceDelay l r , psSlideLevel = on mplus psSlideLevel l r , psPandocExtensions = on mplus psPandocExtensions l r , psImages = on mplus psImages l r , psBreadcrumbs = on mplus psBreadcrumbs l r , psEval = on (<>) psEval l r , psSlideNumber = on mplus psSlideNumber l r , psSyntaxDefinitions = on (<>) psSyntaxDefinitions l r , psSpeakerNotes = on mplus psSpeakerNotes l r , psTransition = on mplus psTransition l r } -------------------------------------------------------------------------------- instance Monoid PresentationSettings where mappend = (<>) mempty = PresentationSettings Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing -------------------------------------------------------------------------------- defaultPresentationSettings :: PresentationSettings defaultPresentationSettings = mempty { psMargins = Nothing , psTheme = Just Theme.defaultTheme } -------------------------------------------------------------------------------- data Wrap = NoWrap | AutoWrap | WrapAt Int deriving (Eq, Show) -------------------------------------------------------------------------------- instance A.FromJSON Wrap where parseJSON val = ((\w -> if w then AutoWrap else NoWrap) <$> A.parseJSON val) <|> (WrapAt <$> A.parseJSON val) -------------------------------------------------------------------------------- data AutoOr a = Auto | NotAuto a deriving (Eq, Show) -------------------------------------------------------------------------------- instance A.FromJSON a => A.FromJSON (AutoOr a) where parseJSON (A.String "auto") = pure Auto parseJSON val = NotAuto <$> A.parseJSON val -------------------------------------------------------------------------------- data MarginSettings = MarginSettings { msTop :: !(Maybe (AutoOr (A.FlexibleNum Int))) , msLeft :: !(Maybe (AutoOr (A.FlexibleNum Int))) , msRight :: !(Maybe (AutoOr (A.FlexibleNum Int))) } deriving (Eq, Show) -------------------------------------------------------------------------------- instance Semigroup MarginSettings where l <> r = MarginSettings { msTop = on mplus msTop l r , msLeft = on mplus msLeft l r , msRight = on mplus msRight l r } -------------------------------------------------------------------------------- instance Monoid MarginSettings where mappend = (<>) mempty = MarginSettings Nothing Nothing Nothing -------------------------------------------------------------------------------- newtype ExtensionList = ExtensionList {unExtensionList :: Pandoc.Extensions} deriving (Eq, Show) -------------------------------------------------------------------------------- instance A.FromJSON ExtensionList where parseJSON = A.withArray "FromJSON ExtensionList" $ fmap (ExtensionList . mconcat) . mapM parseExt . Foldable.toList where parseExt = A.withText "FromJSON ExtensionList" $ \txt -> case txt of -- Our default extensions "patat_extensions" -> return (unExtensionList defaultExtensionList) -- Individuals _ -> case readMaybe ("Ext_" ++ T.unpack txt) of Just e -> return $ Pandoc.extensionsFromList [e] Nothing -> fail $ "Unknown extension: " ++ show txt ++ ", known extensions are: " ++ intercalate ", " (map (drop 4 . show) allExts) where -- This is an approximation since we can't enumerate extensions -- anymore in the latest pandoc... allExts = Pandoc.extensionsToList $ Pandoc.getAllExtensions "markdown" -------------------------------------------------------------------------------- defaultExtensionList :: ExtensionList defaultExtensionList = ExtensionList $ Pandoc.readerExtensions Pandoc.def `mappend` Pandoc.extensionsFromList [ Pandoc.Ext_yaml_metadata_block , Pandoc.Ext_table_captions , Pandoc.Ext_simple_tables , Pandoc.Ext_multiline_tables , Pandoc.Ext_grid_tables , Pandoc.Ext_pipe_tables , Pandoc.Ext_raw_html , Pandoc.Ext_tex_math_dollars , Pandoc.Ext_fenced_code_blocks , Pandoc.Ext_fenced_code_attributes , Pandoc.Ext_backtick_code_blocks , Pandoc.Ext_inline_code_attributes , Pandoc.Ext_fancy_lists , Pandoc.Ext_four_space_rule , Pandoc.Ext_definition_lists , Pandoc.Ext_compact_definition_lists , Pandoc.Ext_example_lists , Pandoc.Ext_strikeout , Pandoc.Ext_superscript , Pandoc.Ext_subscript ] -------------------------------------------------------------------------------- data ImageSettings = ImageSettings { isBackend :: !T.Text , isParams :: !A.Object } deriving (Eq, Show) -------------------------------------------------------------------------------- instance A.FromJSON ImageSettings where parseJSON = A.withObject "FromJSON ImageSettings" $ \o -> do t <- o A..: "backend" return ImageSettings {isBackend = t, isParams = o} -------------------------------------------------------------------------------- type EvalSettingsMap = HMS.HashMap T.Text EvalSettings -------------------------------------------------------------------------------- data EvalSettingsContainer = EvalContainerCode | EvalContainerNone | EvalContainerInline deriving (Eq, Show) -------------------------------------------------------------------------------- instance A.FromJSON EvalSettingsContainer where parseJSON = A.withText "FromJSON EvalSettingsContainer" $ \t -> case t of "code" -> pure EvalContainerCode "none" -> pure EvalContainerNone "inline" -> pure EvalContainerInline -- Deprecated names "raw" -> pure EvalContainerNone "rawInline" -> pure EvalContainerInline _ -> fail $ "unknown container: " <> show t -------------------------------------------------------------------------------- data EvalSettings = EvalSettings { evalCommand :: !T.Text , evalReplace :: !Bool , evalReveal :: !Bool , evalContainer :: !EvalSettingsContainer , evalStderr :: !Bool } deriving (Eq, Show) -------------------------------------------------------------------------------- instance A.FromJSON EvalSettings where parseJSON = A.withObject "FromJSON EvalSettings" $ \o -> EvalSettings <$> o A..: "command" <*> o A..:? "replace" A..!= False <*> deprecated "fragment" "reveal" True o <*> deprecated "wrap" "container" EvalContainerCode o <*> o A..:? "stderr" A..!= True where deprecated old new def obj = do mo <- obj A..:? old mn <- obj A..:? new case (mo, mn) of (Just _, Just _) -> fail $ show old ++ " (deprecated) and " ++ show new ++ " " ++ "are both specified, please remove " ++ show old (Just o, Nothing) -> pure o (Nothing, Just n) -> pure n (Nothing, Nothing) -> pure def -------------------------------------------------------------------------------- data SpeakerNotesSettings = SpeakerNotesSettings { snsFile :: !FilePath } deriving (Eq, Show) -------------------------------------------------------------------------------- data TransitionSettings = TransitionSettings { tsType :: !T.Text , tsParams :: !A.Object } deriving (Eq, Show) -------------------------------------------------------------------------------- instance A.FromJSON TransitionSettings where parseJSON = A.withObject "FromJSON TransitionSettings" $ \o -> TransitionSettings <$> o A..: "type" <*> pure o -------------------------------------------------------------------------------- $(A.deriveFromJSON A.dropPrefixOptions ''MarginSettings) $(A.deriveFromJSON A.dropPrefixOptions ''SpeakerNotesSettings) $(A.deriveFromJSON A.dropPrefixOptions ''PresentationSettings) -------------------------------------------------------------------------------- data Setting where Setting :: String -> (PresentationSettings -> Maybe a) -> Setting -------------------------------------------------------------------------------- unsupportedSlideSettings :: [Setting] unsupportedSlideSettings = [ Setting "incrementalLists" psIncrementalLists , Setting "autoAdvanceDelay" psAutoAdvanceDelay , Setting "slideLevel" psSlideLevel , Setting "pandocExtensions" psPandocExtensions , Setting "images" psImages , Setting "eval" psEval , Setting "speakerNotes" psSpeakerNotes ] -------------------------------------------------------------------------------- parseSlideSettings :: PresentationSettings -> Either String PresentationSettings parseSlideSettings settings = do unless (null unsupported) $ Left $ "the following settings are not supported in slide config blocks: " ++ intercalate ", " unsupported pure settings where unsupported = do setting <- unsupportedSlideSettings case setting of Setting name f | Just _ <- f settings -> [name] Setting _ _ -> [] patat-0.14.1.0/lib/Patat/Presentation/SpeakerNotes.hs000066400000000000000000000042441475634243500223160ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE GeneralizedNewtypeDeriving #-} module Patat.Presentation.SpeakerNotes ( SpeakerNotes (..) , toText , Handle , withHandle , write , parseSlideSettings ) where -------------------------------------------------------------------------------- import Control.Exception (bracket) import Control.Monad (when) import qualified Data.IORef as IORef import Data.List (intersperse) import qualified Data.Text as T import qualified Data.Text.IO as T import Patat.EncodingFallback (EncodingFallback) import qualified Patat.EncodingFallback as EncodingFallback import Patat.Presentation.Settings import System.Directory (removeFile) import qualified System.IO as IO -------------------------------------------------------------------------------- newtype SpeakerNotes = SpeakerNotes [T.Text] deriving (Eq, Monoid, Semigroup, Show) -------------------------------------------------------------------------------- toText :: SpeakerNotes -> T.Text toText (SpeakerNotes sn) = T.unlines $ intersperse mempty sn -------------------------------------------------------------------------------- data Handle = Handle { hSettings :: !SpeakerNotesSettings , hActive :: !(IORef.IORef SpeakerNotes) } -------------------------------------------------------------------------------- withHandle :: SpeakerNotesSettings -> (Handle -> IO a) -> IO a withHandle settings = bracket (Handle settings <$> IORef.newIORef mempty) (\_ -> removeFile (snsFile settings)) -------------------------------------------------------------------------------- write :: Handle -> EncodingFallback -> SpeakerNotes -> IO () write h encodingFallback sn = do change <- IORef.atomicModifyIORef' (hActive h) $ \old -> (sn, old /= sn) when change $ IO.withFile (snsFile $ hSettings h) IO.WriteMode $ \ioh -> EncodingFallback.withHandle ioh encodingFallback $ T.hPutStr ioh $ toText sn patat-0.14.1.0/lib/Patat/Presentation/Syntax.hs000066400000000000000000000353451475634243500212070ustar00rootroot00000000000000{-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} module Patat.Presentation.Syntax ( Block (..) , Inline (..) , dftBlocks , dftInlines , fromPandocBlocks , fromPandocInlines , isHorizontalRule , isComment , Var (..) , variables , RevealID (..) , blocksRevealSteps , blocksRevealStep , blocksRevealLastStep , blocksRevealOrder , blocksReveal , RevealState , revealToBlocks , RevealWrapper (..) , revealWrapper , RevealSequence (..) ) where import Control.Monad.Identity (runIdentity) import Control.Monad.State (State, execState, modify) import Control.Monad.Writer (Writer, execWriter, tell) import Data.Hashable (Hashable) import qualified Data.HashSet as HS import Data.List (foldl') import qualified Data.Map as M import Data.Maybe (fromMaybe) import qualified Data.Set as S import qualified Data.Text as T import qualified Data.Text.Encoding as T import Data.Traversable (for) import qualified Data.Yaml as Yaml import Patat.Presentation.Settings (PresentationSettings) import Patat.Unique import qualified Text.Pandoc as Pandoc import qualified Text.Pandoc.Writers.Shared as Pandoc -- | This is similar to 'Pandoc.Block'. Having our own datatype has some -- advantages: -- -- * We can extend it with slide-specific data (eval, reveals) -- * We can remove stuff we don't care about -- * We can parse attributes and move them to haskell datatypes -- * This conversion can happen in a single parsing phase -- * We can catch backwards-incompatible pandoc changes in this module -- -- We try to follow the naming conventions from Pandoc as much as possible. data Block = Plain ![Inline] | Para ![Inline] | LineBlock ![[Inline]] | CodeBlock !Pandoc.Attr !T.Text | RawBlock !Pandoc.Format !T.Text | BlockQuote ![Block] | OrderedList !Pandoc.ListAttributes ![[Block]] | BulletList ![[Block]] | DefinitionList ![([Inline], [[Block]])] | Header Int !Pandoc.Attr ![Inline] | HorizontalRule | Table ![Inline] ![Pandoc.Alignment] ![[Block]] ![[[Block]]] | Figure !Pandoc.Attr ![Block] | Div !Pandoc.Attr ![Block] -- Our own extensions: | Reveal !RevealWrapper !(RevealSequence [Block]) | VarBlock !Var | SpeakerNote !T.Text | Config !(Either String PresentationSettings) deriving (Eq, Show) -- | See comment on 'Block'. data Inline = Str !T.Text | Emph ![Inline] | Underline ![Inline] | Strong ![Inline] | Strikeout ![Inline] | Superscript ![Inline] | Subscript ![Inline] | SmallCaps ![Inline] | Quoted !Pandoc.QuoteType ![Inline] | Cite ![Pandoc.Citation] ![Inline] | Code !Pandoc.Attr !T.Text | Space | SoftBreak | LineBreak | Math !Pandoc.MathType !T.Text | RawInline !Pandoc.Format !T.Text | Link !Pandoc.Attr ![Inline] !Pandoc.Target | Image !Pandoc.Attr ![Inline] !Pandoc.Target | Note ![Block] | Span !Pandoc.Attr ![Inline] deriving (Eq, Show) -- | Depth-First Traversal of blocks (and inlines). dftBlocks :: forall m. Monad m => (Block -> m [Block]) -> (Inline -> m [Inline]) -> [Block] -> m [Block] dftBlocks fb fi = blocks where blocks :: [Block] -> m [Block] blocks = fmap concat . traverse block inlines :: [Inline] -> m [Inline] inlines = dftInlines fb fi block :: Block -> m [Block] block = (>>= fb) . \case Plain xs -> Plain <$> inlines xs Para xs -> Para <$> inlines xs LineBlock xss -> LineBlock <$> traverse inlines xss b@(CodeBlock _attr _txt) -> pure b b@(RawBlock _fmt _txt) -> pure b BlockQuote xs -> BlockQuote <$> blocks xs OrderedList attr xss -> OrderedList attr <$> traverse blocks xss BulletList xss ->BulletList <$> traverse blocks xss DefinitionList xss -> DefinitionList <$> for xss (\(term, definition) -> (,) <$> inlines term <*> traverse blocks definition) Header lvl attr xs -> Header lvl attr <$> inlines xs b@HorizontalRule -> pure b Table cptn aligns thead trows -> Table <$> inlines cptn <*> pure aligns <*> traverse blocks thead <*> traverse (traverse blocks) trows Figure attr xs -> Figure attr <$> blocks xs Div attr xs -> Div attr <$> blocks xs Reveal w revealer-> Reveal w <$> traverse blocks revealer b@(VarBlock _var) -> pure b b@(SpeakerNote _txt) -> pure b b@(Config _cfg) -> pure b -- | Depth-First Traversal of inlines (and blocks). dftInlines :: forall m. Monad m => (Block -> m [Block]) -> (Inline -> m [Inline]) -> [Inline] -> m [Inline] dftInlines fb fi = inlines where inlines :: [Inline] -> m [Inline] inlines = fmap concat . traverse inline inline :: Inline -> m [Inline] inline = (>>= fi) . \case i@(Str _txt) -> pure i Emph xs -> Emph <$> inlines xs Underline xs -> Underline <$> inlines xs Strong xs -> Strong <$> inlines xs Strikeout xs -> Strikeout <$> inlines xs Superscript xs -> Superscript <$> inlines xs Subscript xs -> Subscript <$> inlines xs SmallCaps xs -> SmallCaps <$> inlines xs Quoted ty xs -> Quoted ty <$> inlines xs Cite c xs -> Cite c <$> inlines xs i@(Code _attr _txt) -> pure i i@Space -> pure i i@SoftBreak -> pure i i@LineBreak -> pure i i@(Math _ty _txt) -> pure i i@(RawInline _fmt _txt) -> pure i Link attr xs tgt -> Link attr <$> inlines xs <*> pure tgt Image attr xs tgt -> Image attr <$> inlines xs <*> pure tgt Note blocks -> Note <$> dftBlocks fb fi blocks Span attr xs -> Span attr . concat <$> traverse inline xs fromPandocBlocks :: [Pandoc.Block] -> [Block] fromPandocBlocks = concatMap fromPandocBlock fromPandocBlock :: Pandoc.Block -> [Block] fromPandocBlock (Pandoc.Plain xs) = [Plain (fromPandocInlines xs)] fromPandocBlock (Pandoc.Para xs) = [Para (fromPandocInlines xs)] fromPandocBlock (Pandoc.LineBlock xs) = [LineBlock (map fromPandocInlines xs)] fromPandocBlock (Pandoc.CodeBlock attrs body) = [CodeBlock attrs body] fromPandocBlock (Pandoc.RawBlock fmt body) -- Parse config blocks. | fmt == "html" , Just t1 <- T.stripPrefix "" t1 = pure $ Config $ case Yaml.decodeEither' (T.encodeUtf8 t2) of Left err -> Left (show err) Right obj -> Right obj -- Parse other comments. | Just t1 <- T.stripPrefix "" t1 = pure $ SpeakerNote $ T.strip t2 -- Other raw blocks, leave as-is. | otherwise = [RawBlock fmt body] fromPandocBlock (Pandoc.BlockQuote blocks) = [BlockQuote $ fromPandocBlocks blocks] fromPandocBlock (Pandoc.OrderedList attrs items) = [OrderedList attrs $ map fromPandocBlocks items] fromPandocBlock (Pandoc.BulletList items) = [BulletList $ map fromPandocBlocks items] fromPandocBlock (Pandoc.DefinitionList items) = pure $ DefinitionList $ do (inlines, blockss) <- items pure (fromPandocInlines inlines, map (fromPandocBlocks) blockss) fromPandocBlock (Pandoc.Header lvl attrs inlines) = [Header lvl attrs (fromPandocInlines inlines)] fromPandocBlock Pandoc.HorizontalRule = [HorizontalRule] fromPandocBlock (Pandoc.Table _ cptn specs thead tbodies tfoot) = pure $ Table (fromPandocInlines cptn') aligns (map (fromPandocBlocks) headers) (map (map fromPandocBlocks) rows) where (cptn', aligns, _, headers, rows) = Pandoc.toLegacyTable cptn specs thead tbodies tfoot fromPandocBlock (Pandoc.Figure attrs _caption blocks) = [Figure attrs $ fromPandocBlocks blocks] fromPandocBlock (Pandoc.Div attrs blocks) = [Div attrs $ fromPandocBlocks blocks] fromPandocInlines :: [Pandoc.Inline] -> [Inline] fromPandocInlines = concatMap fromPandocInline fromPandocInline :: Pandoc.Inline -> [Inline] fromPandocInline inline = case inline of Pandoc.Str txt -> pure $ Str txt Pandoc.Emph xs -> pure $ Emph (fromPandocInlines xs) Pandoc.Underline xs -> pure $ Underline (fromPandocInlines xs) Pandoc.Strong xs -> pure $ Strong (fromPandocInlines xs) Pandoc.Strikeout xs -> pure $ Strikeout (fromPandocInlines xs) Pandoc.Superscript xs -> pure $ Superscript (fromPandocInlines xs) Pandoc.Subscript xs -> pure $ Subscript (fromPandocInlines xs) Pandoc.SmallCaps xs -> pure $ SmallCaps (fromPandocInlines xs) Pandoc.Quoted ty xs -> pure $ Quoted ty (fromPandocInlines xs) Pandoc.Cite c xs -> pure $ Cite c (fromPandocInlines xs) Pandoc.Code attr txt -> pure $ Code attr txt Pandoc.Space -> pure $ Space Pandoc.SoftBreak -> pure $ SoftBreak Pandoc.LineBreak -> pure $ LineBreak Pandoc.Math ty txt -> pure $ Math ty txt Pandoc.RawInline fmt txt -> pure $ RawInline fmt txt Pandoc.Link attr xs tgt -> pure $ Link attr (fromPandocInlines xs) tgt Pandoc.Image attr xs tgt -> pure $ Image attr (fromPandocInlines xs) tgt Pandoc.Note xs -> pure $ Note (fromPandocBlocks xs) Pandoc.Span attr xs -> pure $ Span attr (fromPandocInlines xs) isHorizontalRule :: Block -> Bool isHorizontalRule HorizontalRule = True isHorizontalRule _ = False isComment :: Block -> Bool isComment (SpeakerNote _) = True isComment (Config _) = True isComment _ = False -- | A variable is like a placeholder in the instructions, something we don't -- know yet, dynamic content. Currently this is only used for code evaluation. newtype Var = Var Unique deriving (Hashable, Eq, Ord, Show) -- | Finds all variables that appear in some content. variables :: [Block] -> HS.HashSet Var variables = execWriter . dftBlocks visit (pure . pure) where visit :: Block -> Writer (HS.HashSet Var) [Block] visit b = do case b of VarBlock var -> tell $ HS.singleton var _ -> pure () pure [b] -- | A counter is used to change state in a slide. As counters increment, -- content may deterministically show or hide. newtype RevealID = RevealID Unique deriving (Eq, Ord, Show) -- | A reveal sequence stores content which can be hidden or shown depending on -- a counter state. -- -- The easiest example to think about is a bullet list which appears -- incrmentally on a slide. Initially, the counter state is 0. As it is -- incremented (the user goes to the next fragment in the slide), more list -- items become visible. data RevealSequence a = RevealSequence { -- The ID used for this sequence. rsID :: RevealID , -- These reveals should be advanced in this order. -- Reveal IDs will be included multiple times if needed. -- -- This should (only) contain the ID of this counter, and IDs of counters -- nested inside the children fields. rsOrder :: [RevealID] , -- For each piece of content in this sequence, we store a set of ints. -- When the current counter state is included in this set, the item is -- visible. rsVisible :: [(S.Set Int, a)] } deriving (Foldable, Functor, Eq, Show, Traversable) -- | This determines how we construct content based on the visible items. -- This could also be represented as `[[Block]] -> [Block]` but then we lose -- the convenient Eq and Show instances. data RevealWrapper = ConcatWrapper | BulletListWrapper | OrderedListWrapper Pandoc.ListAttributes deriving (Eq, Show) revealWrapper :: RevealWrapper -> [[Block]] -> [Block] revealWrapper ConcatWrapper = concat revealWrapper BulletListWrapper = pure . BulletList revealWrapper (OrderedListWrapper attr) = pure . OrderedList attr -- | Number of reveal steps in some blocks. blocksRevealSteps :: [Block] -> Int blocksRevealSteps = succ . length . blocksRevealOrder -- | Construct the reveal state for a specific step. blocksRevealStep :: Int -> [Block] -> RevealState blocksRevealStep fidx = makeRevealState . take fidx . blocksRevealOrder -- | Construct the final reveal state. blocksRevealLastStep :: [Block] -> RevealState blocksRevealLastStep = makeRevealState . blocksRevealOrder -- | This does a deep traversal of some blocks, and returns all reveals that -- should be advanced in-order. blocksRevealOrder :: [Block] -> [RevealID] blocksRevealOrder blocks = concat $ execState (dftBlocks visit (pure . pure) blocks) [] where -- We store a [[RevealID]] state, where each list represents the triggers -- necessary for a single reveal block. visit :: Block -> State [[RevealID]] [Block] visit (Reveal w rs) = do modify $ merge rs pure [Reveal w rs] visit block = pure [block] -- When we encounter a new reveal, we want to merge this into our -- [[RevealID]] state. However, we need to ensure to remove any children -- of that reveal block that were already in this list. merge :: RevealSequence [Block] -> [[RevealID]] -> [[RevealID]] merge (RevealSequence fid triggers _) known | any (fid `elem`) known = known | otherwise = filter (not . any (`elem` triggers)) known ++ [triggers] -- | Stores the state of several counters. type RevealState = M.Map RevealID Int -- | Convert a list of counters that need to be triggered to the final state. makeRevealState :: [RevealID] -> RevealState makeRevealState = foldl' (\acc x -> M.insertWith (+) x 1 acc) M.empty -- | Render a reveal by applying its constructor to what is visible. revealToBlocks :: RevealState -> RevealWrapper -> RevealSequence [Block] -> [Block] revealToBlocks revealState rw (RevealSequence cid _ sections) = revealWrapper rw [s | (activation, s) <- sections, counter `S.member` activation] where counter = fromMaybe 0 $ M.lookup cid revealState -- | Apply `revealToBlocks` recursively at each position, removing reveals -- in favor of their currently visible content. blocksReveal :: RevealState -> [Block] -> [Block] blocksReveal revealState = runIdentity . dftBlocks visit (pure . pure) where visit (Reveal w rs) = pure $ revealToBlocks revealState w rs visit block = pure [block] patat-0.14.1.0/lib/Patat/PrettyPrint.hs000066400000000000000000000140271475634243500175440ustar00rootroot00000000000000-------------------------------------------------------------------------------- -- | This is a small pretty-printing library. {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE RecordWildCards #-} module Patat.PrettyPrint ( Doc , toString , dimensions , null , hPutDoc , putDoc , char , string , text , space , spaces , softline , hardline , wrapAt , Indentation (..) , indent , deindent , ansi , (<+>) , (<$$>) , vcat , intersperse -- * Exotic combinators , Alignment (..) , align , paste -- * Control codes , removeControls , clearScreen , goToLine ) where -------------------------------------------------------------------------------- import Data.Char.WCWidth.Extended (wcstrwidth) import qualified Data.List as L import qualified Data.Text as T import Patat.PrettyPrint.Internal import Prelude hiding (null) import qualified System.Console.ANSI as Ansi -------------------------------------------------------------------------------- char :: Char -> Doc char = string . pure -------------------------------------------------------------------------------- text :: T.Text -> Doc text = string . T.unpack -------------------------------------------------------------------------------- space :: Doc space = mkDoc Softspace -------------------------------------------------------------------------------- spaces :: Int -> Doc spaces n = mconcat $ replicate n space -------------------------------------------------------------------------------- softline :: Doc softline = mkDoc Softline -------------------------------------------------------------------------------- hardline :: Doc hardline = mkDoc Hardline -------------------------------------------------------------------------------- wrapAt :: Maybe Int -> Doc -> Doc wrapAt wrapAtCol wrapDoc = mkDoc WrapAt {..} -------------------------------------------------------------------------------- indent :: Indentation Doc -> Indentation Doc -> Doc -> Doc indent firstLineDoc otherLinesDoc doc = mkDoc $ Indent { indentFirstLine = fmap docToChunks firstLineDoc , indentOtherLines = fmap docToChunks otherLinesDoc , indentDoc = doc } -------------------------------------------------------------------------------- -- | Only strips leading spaces deindent :: Doc -> Doc deindent = Doc . concatMap go . unDoc where go :: DocE Doc -> [DocE Doc] go doc@(Indent {..}) | fs0 <= 0 && os0 <= 0 = [doc] | fs1 == 0 && os1 == 0 && L.null fc && L.null oc = concatMap go $ unDoc indentDoc | otherwise = pure $ Indent { indentFirstLine = Indentation fs1 fc , indentOtherLines = Indentation os1 oc , indentDoc = indentDoc } where Indentation fs0 fc = indentFirstLine Indentation os0 oc = indentOtherLines fs1 = fs0 - min fs0 os0 os1 = os0 - min fs0 os0 go doc = [doc] -------------------------------------------------------------------------------- ansi :: [Ansi.SGR] -> Doc -> Doc ansi codes = mkDoc . Ansi (codes ++) -------------------------------------------------------------------------------- (<+>) :: Doc -> Doc -> Doc x <+> y = x <> space <> y infixr 6 <+> -------------------------------------------------------------------------------- (<$$>) :: Doc -> Doc -> Doc x <$$> y = x <> hardline <> y infixr 5 <$$> -------------------------------------------------------------------------------- vcat :: [Doc] -> Doc vcat = intersperse hardline -------------------------------------------------------------------------------- intersperse :: Doc -> [Doc] -> Doc intersperse sep = mconcat . L.intersperse sep -------------------------------------------------------------------------------- data Alignment = AlignLeft | AlignCenter | AlignRight deriving (Eq, Ord, Show) -------------------------------------------------------------------------------- align :: Int -> Alignment -> Doc -> Doc align width alignment doc0 = let chunks0 = docToChunks $ removeControls doc0 lines_ = chunkLines chunks0 in vcat [ Doc (map chunkToDocE (alignLine line)) | line <- lines_ ] where lineWidth :: [Chunk] -> Int lineWidth = sum . map (wcstrwidth . chunkToString) alignLine :: [Chunk] -> [Chunk] alignLine line = let actual = lineWidth line chunkSpaces n = [StringChunk [] (replicate n ' ')] in case alignment of AlignLeft -> line <> chunkSpaces (width - actual) AlignRight -> chunkSpaces (width - actual) <> line AlignCenter -> let r = (width - actual) `div` 2 l = (width - actual) - r in chunkSpaces l <> line <> chunkSpaces r -------------------------------------------------------------------------------- -- | Like the unix program 'paste'. paste :: [Doc] -> Doc paste docs0 = let chunkss = map (docToChunks . removeControls) docs0 :: [Chunks] cols = map chunkLines chunkss :: [[Chunks]] rows0 = L.transpose cols :: [[Chunks]] rows1 = map (map (Doc . map chunkToDocE)) rows0 :: [[Doc]] in vcat $ map mconcat rows1 -------------------------------------------------------------------------------- removeControls :: Doc -> Doc removeControls = Doc . filter isNotControl . map (fmap removeControls) . unDoc where isNotControl (Control _) = False isNotControl _ = True -------------------------------------------------------------------------------- clearScreen :: Doc clearScreen = mkDoc $ Control ClearScreenControl -------------------------------------------------------------------------------- goToLine :: Int -> Doc goToLine = mkDoc . Control . GoToLineControl patat-0.14.1.0/lib/Patat/PrettyPrint/000077500000000000000000000000001475634243500172045ustar00rootroot00000000000000patat-0.14.1.0/lib/Patat/PrettyPrint/Internal.hs000066400000000000000000000262331475634243500213220ustar00rootroot00000000000000-------------------------------------------------------------------------------- -- | This is a small pretty-printing library. {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE RecordWildCards #-} module Patat.PrettyPrint.Internal ( Control (..) , Chunk (..) , Chunks , chunkToString , chunkLines , DocE (..) , chunkToDocE , Indentation (..) , Doc (..) , docToChunks , toString , dimensions , null , hPutDoc , putDoc , mkDoc , string ) where -------------------------------------------------------------------------------- import Control.Monad.Reader (asks, local) import Control.Monad.RWS (RWS, runRWS) import Control.Monad.State (get, modify) import Control.Monad.Writer (tell) import Data.Char.WCWidth.Extended (wcstrwidth) import qualified Data.List as L import Data.String (IsString (..)) import Prelude hiding (null) import qualified System.Console.ANSI as Ansi import qualified System.IO as IO -------------------------------------------------------------------------------- -- | Control actions for the terminal. data Control = ClearScreenControl | GoToLineControl Int deriving (Eq, Show) -------------------------------------------------------------------------------- -- | A simple chunk of text. All ANSI codes are "reset" after printing. data Chunk = StringChunk [Ansi.SGR] String | NewlineChunk | ControlChunk Control deriving (Eq, Show) -------------------------------------------------------------------------------- type Chunks = [Chunk] -------------------------------------------------------------------------------- hPutChunk :: IO.Handle -> Chunk -> IO () hPutChunk h NewlineChunk = IO.hPutStrLn h "" hPutChunk h (StringChunk codes str) = do Ansi.hSetSGR h (reverse codes) IO.hPutStr h str Ansi.hSetSGR h [Ansi.Reset] hPutChunk h (ControlChunk ctrl) = case ctrl of ClearScreenControl -> Ansi.hClearScreen h GoToLineControl l -> Ansi.hSetCursorPosition h l 0 -------------------------------------------------------------------------------- chunkToString :: Chunk -> String chunkToString NewlineChunk = "\n" chunkToString (StringChunk _ str) = str chunkToString (ControlChunk _) = "" -------------------------------------------------------------------------------- -- | If two neighboring chunks have the same set of ANSI codes, we can group -- them together. optimizeChunks :: Chunks -> Chunks optimizeChunks (StringChunk c1 s1 : StringChunk c2 s2 : chunks) | c1 == c2 = optimizeChunks (StringChunk c1 (s1 <> s2) : chunks) | otherwise = StringChunk c1 s1 : optimizeChunks (StringChunk c2 s2 : chunks) optimizeChunks (x : chunks) = x : optimizeChunks chunks optimizeChunks [] = [] -------------------------------------------------------------------------------- chunkLines :: Chunks -> [Chunks] chunkLines chunks = case break (== NewlineChunk) chunks of (xs, _newline : ys) -> xs : chunkLines ys (xs, []) -> [xs] -------------------------------------------------------------------------------- data DocE d = String String | Softspace | Hardspace | Softline | Hardline | WrapAt { wrapAtCol :: Maybe Int , wrapDoc :: d } | Ansi { ansiCode :: [Ansi.SGR] -> [Ansi.SGR] -- ^ Modifies current codes. , ansiDoc :: d } | Indent { indentFirstLine :: Indentation [Chunk] , indentOtherLines :: Indentation [Chunk] , indentDoc :: d } | Control Control deriving (Functor) -------------------------------------------------------------------------------- chunkToDocE :: Chunk -> DocE Doc chunkToDocE NewlineChunk = Hardline chunkToDocE (StringChunk c1 str) = Ansi (\c0 -> c1 ++ c0) (Doc [String str]) chunkToDocE (ControlChunk ctrl) = Control ctrl -------------------------------------------------------------------------------- newtype Doc = Doc {unDoc :: [DocE Doc]} deriving (Monoid, Semigroup) -------------------------------------------------------------------------------- instance Show Doc where show = toString -------------------------------------------------------------------------------- instance IsString Doc where fromString = string -------------------------------------------------------------------------------- data DocEnv = DocEnv { deCodes :: [Ansi.SGR] -- ^ Most recent ones first in the list , deIndent :: [Indentation [Chunk]] -- ^ No need to store first-line indent , deWrap :: Maybe Int -- ^ Wrap at columns } -------------------------------------------------------------------------------- type DocM = RWS DocEnv Chunks LineBuffer -------------------------------------------------------------------------------- -- | Note that the lists here are reversed so we have fast append. -- We also store the current length to avoid having to recompute it. data LineBuffer = LineBuffer Int [Indentation [Chunk]] [Chunk] -------------------------------------------------------------------------------- emptyLineBuffer :: LineBuffer emptyLineBuffer = LineBuffer 0 [] [] -------------------------------------------------------------------------------- data Indentation a = Indentation Int a deriving (Foldable, Functor, Traversable) -------------------------------------------------------------------------------- indentationToChunks :: Indentation [Chunk] -> [Chunk] indentationToChunks (Indentation 0 c) = c indentationToChunks (Indentation n c) = StringChunk [] (replicate n ' ') : c -------------------------------------------------------------------------------- indentationWidth :: Indentation [Chunk] -> Int indentationWidth (Indentation s c) = s + sum (map (wcstrwidth . chunkToString) c) -------------------------------------------------------------------------------- bufferToChunks :: LineBuffer -> Chunks bufferToChunks (LineBuffer _ ind chunks) = case chunks of [] -> concatMap indentationToChunks $ reverse $ dropWhile emptyIndentation ind _ -> concatMap indentationToChunks (reverse ind) ++ reverse chunks where emptyIndentation (Indentation _ []) = True emptyIndentation _ = False -------------------------------------------------------------------------------- docToChunks :: Doc -> Chunks docToChunks doc0 = let env0 = DocEnv [] [] Nothing ((), b, cs) = runRWS (go $ unDoc doc0) env0 emptyLineBuffer in optimizeChunks (cs <> bufferToChunks b) where go :: [DocE Doc] -> DocM () go [] = return () go (String str : docs) = do chunk <- makeChunk str appendChunk chunk go docs go (Softspace : docs) = do hard <- softConversion Softspace docs go (hard : docs) go (Hardspace : docs) = do chunk <- makeChunk " " appendChunk chunk go docs go (Softline : docs) = do hard <- softConversion Softline docs go (hard : docs) go (Hardline : docs) = do buffer <- get tell $ bufferToChunks buffer <> [NewlineChunk] ind <- asks deIndent modify $ \_ -> case docs of [] -> emptyLineBuffer _ : _ -> LineBuffer (sum $ map indentationWidth ind) ind [] go docs go (WrapAt {..} : docs) = do il <- asks $ sum . map indentationWidth . deIndent local (\env -> env {deWrap = fmap (+ il) wrapAtCol}) $ go (unDoc wrapDoc) go docs go (Ansi {..} : docs) = do local (\env -> env {deCodes = ansiCode (deCodes env)}) $ go (unDoc ansiDoc) go docs go (Indent {..} : docs) = do local (\e -> e {deIndent = indentOtherLines : deIndent e}) $ do modify $ \(LineBuffer w i c) -> LineBuffer (w + indentationWidth indentFirstLine) (indentFirstLine : i) c go (unDoc indentDoc) go docs go (Control ctrl : docs) = do tell [ControlChunk ctrl] go docs makeChunk :: String -> DocM Chunk makeChunk str = do codes <- asks deCodes return $ StringChunk codes str appendChunk :: Chunk -> DocM () appendChunk c = modify $ \(LineBuffer w i cs) -> LineBuffer (w + wcstrwidth (chunkToString c)) i (c : cs) -- Convert 'Softspace' or 'Softline' to 'Hardspace' or 'Hardline' softConversion :: DocE Doc -> [DocE Doc] -> DocM (DocE Doc) softConversion soft docs = do mbWrapCol <- asks deWrap case mbWrapCol of Nothing -> return hard Just maxCol -> do LineBuffer currentCol _ _ <- get case nextWordLength docs of Nothing -> return hard Just l | currentCol + 1 + l <= maxCol -> return Hardspace | otherwise -> return Hardline where hard = case soft of Softspace -> Hardspace Softline -> Hardline _ -> soft nextWordLength :: [DocE Doc] -> Maybe Int nextWordLength [] = Nothing nextWordLength (String x : xs) | L.null x = nextWordLength xs | otherwise = Just (wcstrwidth x) nextWordLength (Softspace : xs) = nextWordLength xs nextWordLength (Hardspace : xs) = nextWordLength xs nextWordLength (Softline : xs) = nextWordLength xs nextWordLength (Hardline : _) = Nothing nextWordLength (WrapAt {..} : xs) = nextWordLength (unDoc wrapDoc ++ xs) nextWordLength (Ansi {..} : xs) = nextWordLength (unDoc ansiDoc ++ xs) nextWordLength (Indent {..} : xs) = nextWordLength (unDoc indentDoc ++ xs) nextWordLength (Control _ : _) = Nothing -------------------------------------------------------------------------------- toString :: Doc -> String toString = concat . map chunkToString . docToChunks -------------------------------------------------------------------------------- -- | Returns the rows and columns necessary to render this document dimensions :: Doc -> (Int, Int) dimensions doc = let ls = lines (toString doc) in (length ls, foldr max 0 (map wcstrwidth ls)) -------------------------------------------------------------------------------- null :: Doc -> Bool null doc = case unDoc doc of [] -> True; _ -> False -------------------------------------------------------------------------------- hPutDoc :: IO.Handle -> Doc -> IO () hPutDoc h = mapM_ (hPutChunk h) . docToChunks -------------------------------------------------------------------------------- putDoc :: Doc -> IO () putDoc = hPutDoc IO.stdout -------------------------------------------------------------------------------- mkDoc :: DocE Doc -> Doc mkDoc e = Doc [e] -------------------------------------------------------------------------------- string :: String -> Doc string "" = Doc [] string str = mkDoc $ String str -- TODO (jaspervdj): Newline conversion? patat-0.14.1.0/lib/Patat/PrettyPrint/Matrix.hs000066400000000000000000000060751475634243500210140ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE BangPatterns #-} module Patat.PrettyPrint.Matrix ( Matrix , Cell (..) , emptyCell , docToMatrix , hPutMatrix ) where -------------------------------------------------------------------------------- import Control.Monad (unless, when) import Data.Char.WCWidth.Extended (wcwidth) import qualified Data.Vector as V import qualified Data.Vector.Mutable as VM import Patat.PrettyPrint.Internal hiding (null) import Patat.Size (Size (..)) import qualified System.Console.ANSI as Ansi import qualified System.IO as IO -------------------------------------------------------------------------------- data Cell = Cell [Ansi.SGR] Char deriving (Eq, Show) -------------------------------------------------------------------------------- type Matrix = V.Vector Cell -------------------------------------------------------------------------------- emptyCell :: Cell emptyCell = Cell [] ' ' -------------------------------------------------------------------------------- docToMatrix :: Size -> Doc -> Matrix docToMatrix (Size rows cols) doc = V.create $ do matrix <- VM.replicate (rows * cols) emptyCell go matrix 0 0 $ docToChunks doc pure matrix where go r y x (StringChunk _ [] : cs) = go r y x cs go _ _ _ [] = pure () go _ y _ _ | y >= rows = pure () go r y _ (NewlineChunk : cs) = go r (y + 1) 0 cs go r y x (ControlChunk ClearScreenControl : cs) = go r y x cs -- ? go r _ x (ControlChunk (GoToLineControl y) : cs) = go r y x cs go r y x chunks@(StringChunk codes (z : zs) : cs) | x + w > cols = go r (y + 1) 0 chunks | otherwise = do VM.write r (y * cols + x) (Cell codes z) go r y (x + wcwidth z) (StringChunk codes zs : cs) where w = wcwidth z -------------------------------------------------------------------------------- hPutMatrix :: IO.Handle -> Size -> Matrix -> IO () hPutMatrix h size matrix = go 0 0 0 [] where go !y !x !empties prevCodes | x >= sCols size = IO.hPutStrLn h "" >> go (y + 1) 0 0 prevCodes | y >= sRows size = Ansi.hSetSGR h [Ansi.Reset] -- Try to not print empty things (e.g. fill the screen with spaces) as -- an optimization. Instead, store the number of empties and print them -- when something actually follows. | cell == emptyCell = do unless (null prevCodes) $ Ansi.hSetSGR h [Ansi.Reset] go y (x + 1) (empties + 1) [] | otherwise = do unless (empties == 0) $ IO.hPutStr h (replicate empties ' ') when (prevCodes /= codes) $ Ansi.hSetSGR h (Ansi.Reset : reverse codes) IO.hPutStr h [c] go y (x + wcwidth c) 0 codes where cell@(Cell codes c) = matrix V.! (y * sCols size + x) patat-0.14.1.0/lib/Patat/Size.hs000066400000000000000000000014611475634243500161500ustar00rootroot00000000000000-------------------------------------------------------------------------------- module Patat.Size ( Size (..) , getTerminalSize ) where -------------------------------------------------------------------------------- import Data.Maybe (fromMaybe) import qualified System.Console.Terminal.Size as Terminal -------------------------------------------------------------------------------- data Size = Size {sRows :: Int, sCols :: Int} deriving (Show) -------------------------------------------------------------------------------- getTerminalSize :: IO Size getTerminalSize = do mbWindow <- Terminal.size let rows = fromMaybe 24 $ Terminal.height <$> mbWindow cols = fromMaybe 72 $ Terminal.width <$> mbWindow pure $ Size {sRows = rows, sCols = cols} patat-0.14.1.0/lib/Patat/Theme.hs000066400000000000000000000317011475634243500163000ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} module Patat.Theme ( Theme (..) , defaultTheme , Style (..) , SyntaxHighlighting (..) , defaultSyntaxHighlighting , syntaxHighlight ) where -------------------------------------------------------------------------------- import Control.Monad (forM_, mplus) import qualified Data.Aeson as A import qualified Data.Aeson.TH.Extended as A import Data.Char (toLower, toUpper) import Data.Colour.SRGB (RGB (..), sRGB24reads, toSRGB24) import Data.List (intercalate, isPrefixOf, isSuffixOf) import qualified Data.Map as M import Data.Maybe (mapMaybe, maybeToList) import qualified Data.Text as T import Numeric (showHex) import Prelude import qualified Skylighting as Skylighting import qualified System.Console.ANSI as Ansi import Text.Read (readMaybe) -------------------------------------------------------------------------------- data Theme = Theme { themeBorders :: !(Maybe Style) , themeHeader :: !(Maybe Style) , themeCodeBlock :: !(Maybe Style) , themeBulletList :: !(Maybe Style) , themeBulletListMarkers :: !(Maybe T.Text) , themeOrderedList :: !(Maybe Style) , themeBlockQuote :: !(Maybe Style) , themeDefinitionTerm :: !(Maybe Style) , themeDefinitionList :: !(Maybe Style) , themeTableHeader :: !(Maybe Style) , themeTableSeparator :: !(Maybe Style) , themeLineBlock :: !(Maybe Style) , themeEmph :: !(Maybe Style) , themeStrong :: !(Maybe Style) , themeUnderline :: !(Maybe Style) , themeCode :: !(Maybe Style) , themeLinkText :: !(Maybe Style) , themeLinkTarget :: !(Maybe Style) , themeStrikeout :: !(Maybe Style) , themeQuoted :: !(Maybe Style) , themeMath :: !(Maybe Style) , themeImageText :: !(Maybe Style) , themeImageTarget :: !(Maybe Style) , themeSyntaxHighlighting :: !(Maybe SyntaxHighlighting) } deriving (Eq, Show) -------------------------------------------------------------------------------- instance Semigroup Theme where l <> r = Theme { themeBorders = mplusOn themeBorders , themeHeader = mplusOn themeHeader , themeCodeBlock = mplusOn themeCodeBlock , themeBulletList = mplusOn themeBulletList , themeBulletListMarkers = mplusOn themeBulletListMarkers , themeOrderedList = mplusOn themeOrderedList , themeBlockQuote = mplusOn themeBlockQuote , themeDefinitionTerm = mplusOn themeDefinitionTerm , themeDefinitionList = mplusOn themeDefinitionList , themeTableHeader = mplusOn themeTableHeader , themeTableSeparator = mplusOn themeTableSeparator , themeLineBlock = mplusOn themeLineBlock , themeEmph = mplusOn themeEmph , themeStrong = mplusOn themeStrong , themeUnderline = mplusOn themeUnderline , themeCode = mplusOn themeCode , themeLinkText = mplusOn themeLinkText , themeLinkTarget = mplusOn themeLinkTarget , themeStrikeout = mplusOn themeStrikeout , themeQuoted = mplusOn themeQuoted , themeMath = mplusOn themeMath , themeImageText = mplusOn themeImageText , themeImageTarget = mplusOn themeImageTarget , themeSyntaxHighlighting = mappendOn themeSyntaxHighlighting } where mplusOn f = f l `mplus` f r mappendOn f = f l `mappend` f r -------------------------------------------------------------------------------- instance Monoid Theme where mappend = (<>) mempty = Theme Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing -------------------------------------------------------------------------------- defaultTheme :: Theme defaultTheme = Theme { themeBorders = dull Ansi.Yellow , themeHeader = dull Ansi.Blue , themeCodeBlock = dull Ansi.White `mappend` ondull Ansi.Black , themeBulletList = dull Ansi.Magenta , themeBulletListMarkers = Just "-*" , themeOrderedList = dull Ansi.Magenta , themeBlockQuote = dull Ansi.Green , themeDefinitionTerm = dull Ansi.Blue , themeDefinitionList = dull Ansi.Magenta , themeTableHeader = dull Ansi.Magenta `mappend` bold , themeTableSeparator = dull Ansi.Magenta , themeLineBlock = dull Ansi.Magenta , themeEmph = dull Ansi.Green , themeStrong = dull Ansi.Red `mappend` bold , themeUnderline = dull Ansi.Red `mappend` underline , themeCode = dull Ansi.White `mappend` ondull Ansi.Black , themeLinkText = dull Ansi.Green , themeLinkTarget = dull Ansi.Cyan `mappend` underline , themeStrikeout = ondull Ansi.Red , themeQuoted = dull Ansi.Green , themeMath = dull Ansi.Green , themeImageText = dull Ansi.Green , themeImageTarget = dull Ansi.Cyan `mappend` underline , themeSyntaxHighlighting = Just defaultSyntaxHighlighting } where dull c = Just $ Style [Ansi.SetColor Ansi.Foreground Ansi.Dull c] ondull c = Just $ Style [Ansi.SetColor Ansi.Background Ansi.Dull c] bold = Just $ Style [Ansi.SetConsoleIntensity Ansi.BoldIntensity] underline = Just $ Style [Ansi.SetUnderlining Ansi.SingleUnderline] -------------------------------------------------------------------------------- newtype Style = Style {unStyle :: [Ansi.SGR]} deriving (Eq, Monoid, Semigroup, Show) -------------------------------------------------------------------------------- instance A.ToJSON Style where toJSON = A.toJSON . mapMaybe sgrToString . unStyle -------------------------------------------------------------------------------- instance A.FromJSON Style where parseJSON val = do names <- A.parseJSON val sgrs <- mapM toSgr names return $! Style sgrs where toSgr name = case stringToSgr name of Just sgr -> return sgr Nothing -> fail $! "Unknown style: " ++ show name ++ ". Known styles are: " ++ intercalate ", " (map show $ M.keys namedSgrs) ++ ", or \"rgb#RrGgBb\" and \"onRgb#RrGgBb\" where 'Rr', " ++ "'Gg' and 'Bb' are hexadecimal bytes (e.g. \"rgb#f08000\")." -------------------------------------------------------------------------------- stringToSgr :: String -> Maybe Ansi.SGR stringToSgr s | "rgb#" `isPrefixOf` s = rgbToSgr Ansi.Foreground $ drop 4 s | "onRgb#" `isPrefixOf` s = rgbToSgr Ansi.Background $ drop 6 s | otherwise = M.lookup s namedSgrs -------------------------------------------------------------------------------- rgbToSgr :: Ansi.ConsoleLayer -> String -> Maybe Ansi.SGR rgbToSgr layer rgbHex = case sRGB24reads rgbHex of [(color, "")] -> Just $ Ansi.SetRGBColor layer color _ -> Nothing -------------------------------------------------------------------------------- sgrToString :: Ansi.SGR -> Maybe String sgrToString (Ansi.SetColor layer intensity color) = Just $ (\str -> case layer of Ansi.Foreground -> str Ansi.Background -> "on" ++ capitalize str) $ (case intensity of Ansi.Dull -> "dull" Ansi.Vivid -> "vivid") ++ (case color of Ansi.Black -> "Black" Ansi.Red -> "Red" Ansi.Green -> "Green" Ansi.Yellow -> "Yellow" Ansi.Blue -> "Blue" Ansi.Magenta -> "Magenta" Ansi.Cyan -> "Cyan" Ansi.White -> "White") sgrToString (Ansi.SetUnderlining Ansi.SingleUnderline) = Just "underline" sgrToString (Ansi.SetConsoleIntensity Ansi.BoldIntensity) = Just "bold" sgrToString (Ansi.SetItalicized True) = Just "italic" sgrToString (Ansi.SetRGBColor layer color) = Just $ (\str -> case layer of Ansi.Foreground -> str Ansi.Background -> "on" ++ capitalize str) $ "rgb#" ++ (toRGBHex $ toSRGB24 color) where toRGBHex (RGB r g b) = concat $ map toHexByte [r, g, b] toHexByte x = showHex2 x "" showHex2 x | x <= 0xf = ("0" ++) . showHex x | otherwise = showHex x sgrToString _ = Nothing -------------------------------------------------------------------------------- namedSgrs :: M.Map String Ansi.SGR namedSgrs = M.fromList [ (name, sgr) | sgr <- knownSgrs , name <- maybeToList (sgrToString sgr) ] where -- It doesn't really matter if we generate "too much" SGRs here since -- 'sgrToString' will only pick the ones we support. knownSgrs = [ Ansi.SetColor l i c | l <- [minBound .. maxBound] , i <- [minBound .. maxBound] , c <- [minBound .. maxBound] ] ++ [Ansi.SetUnderlining u | u <- [minBound .. maxBound]] ++ [Ansi.SetConsoleIntensity c | c <- [minBound .. maxBound]] ++ [Ansi.SetItalicized i | i <- [minBound .. maxBound]] -------------------------------------------------------------------------------- newtype SyntaxHighlighting = SyntaxHighlighting { unSyntaxHighlighting :: M.Map String Style } deriving (Eq, Monoid, Semigroup, Show, A.ToJSON) -------------------------------------------------------------------------------- instance A.FromJSON SyntaxHighlighting where parseJSON val = do styleMap <- A.parseJSON val forM_ (M.keys styleMap) $ \k -> case nameToTokenType k of Just _ -> return () Nothing -> fail $ "Unknown token type: " ++ show k return (SyntaxHighlighting styleMap) -------------------------------------------------------------------------------- defaultSyntaxHighlighting :: SyntaxHighlighting defaultSyntaxHighlighting = mkSyntaxHighlighting [ (Skylighting.KeywordTok, dull Ansi.Yellow) , (Skylighting.ControlFlowTok, dull Ansi.Yellow) , (Skylighting.DataTypeTok, dull Ansi.Green) , (Skylighting.DecValTok, dull Ansi.Red) , (Skylighting.BaseNTok, dull Ansi.Red) , (Skylighting.FloatTok, dull Ansi.Red) , (Skylighting.ConstantTok, dull Ansi.Red) , (Skylighting.CharTok, dull Ansi.Red) , (Skylighting.SpecialCharTok, dull Ansi.Red) , (Skylighting.StringTok, dull Ansi.Red) , (Skylighting.VerbatimStringTok, dull Ansi.Red) , (Skylighting.SpecialStringTok, dull Ansi.Red) , (Skylighting.CommentTok, dull Ansi.Blue) , (Skylighting.DocumentationTok, dull Ansi.Blue) , (Skylighting.AnnotationTok, dull Ansi.Blue) , (Skylighting.CommentVarTok, dull Ansi.Blue) , (Skylighting.ImportTok, dull Ansi.Cyan) , (Skylighting.OperatorTok, dull Ansi.Cyan) , (Skylighting.FunctionTok, dull Ansi.Cyan) , (Skylighting.PreprocessorTok, dull Ansi.Cyan) ] where dull c = Style [Ansi.SetColor Ansi.Foreground Ansi.Dull c] mkSyntaxHighlighting ls = SyntaxHighlighting $ M.fromList [(nameForTokenType tt, s) | (tt, s) <- ls] -------------------------------------------------------------------------------- nameForTokenType :: Skylighting.TokenType -> String nameForTokenType = unCapitalize . dropTok . show where unCapitalize (x : xs) = toLower x : xs unCapitalize xs = xs dropTok :: String -> String dropTok str | "Tok" `isSuffixOf` str = take (length str - 3) str | otherwise = str -------------------------------------------------------------------------------- nameToTokenType :: String -> Maybe Skylighting.TokenType nameToTokenType = readMaybe . capitalize . (++ "Tok") -------------------------------------------------------------------------------- capitalize :: String -> String capitalize "" = "" capitalize (x : xs) = toUpper x : xs -------------------------------------------------------------------------------- syntaxHighlight :: Theme -> Skylighting.TokenType -> Maybe Style syntaxHighlight theme tokenType = do sh <- themeSyntaxHighlighting theme M.lookup (nameForTokenType tokenType) (unSyntaxHighlighting sh) -------------------------------------------------------------------------------- $(A.deriveJSON A.dropPrefixOptions ''Theme) patat-0.14.1.0/lib/Patat/Transition.hs000066400000000000000000000070131475634243500173670ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} module Patat.Transition ( Duration (..) , threadDelayDuration , TransitionGen , TransitionId , TransitionInstance (..) , parseTransitionSettings , newTransition , stepTransition ) where -------------------------------------------------------------------------------- import qualified Data.Aeson.Extended as A import qualified Data.Aeson.TH.Extended as A import Data.Bifunctor (first) import qualified Data.HashMap.Strict as HMS import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.List.NonEmpty as NonEmpty import Data.Text (Text) import qualified Data.Text as T import Data.Traversable (for) import Patat.Presentation.Settings (TransitionSettings (..)) import qualified Patat.Transition.Dissolve as Dissolve import Patat.Transition.Internal import qualified Patat.Transition.Matrix as Matrix import qualified Patat.Transition.SlideLeft as SlideLeft import System.Random (uniformR) -------------------------------------------------------------------------------- data RandomTransitionSettings = RandomTransitionSettings { rtsItems :: Maybe (NonEmpty TransitionSettings) } -------------------------------------------------------------------------------- $(A.deriveFromJSON A.dropPrefixOptions ''RandomTransitionSettings) -------------------------------------------------------------------------------- random :: NonEmpty TransitionGen -> TransitionGen random items size matrix0 matrix1 rg0 = let (idx, rg1) = uniformR (0, length items - 1) rg0 in (items NonEmpty.!! idx) size matrix0 matrix1 rg1 -------------------------------------------------------------------------------- transitions :: NonEmpty (Text, Transition) transitions = ("dissolve", Transition Dissolve.transition) :| ("matrix", Transition Matrix.transition) : ("slideLeft", Transition SlideLeft.transition) : [] -------------------------------------------------------------------------------- transitionTable :: HMS.HashMap Text Transition transitionTable = foldMap (uncurry HMS.singleton) transitions -------------------------------------------------------------------------------- parseTransitionSettings :: TransitionSettings -> Either String TransitionGen parseTransitionSettings ts -- Random is treated specially here. | ty == "random" = fmap random $ do settings <- A.resultToEither . A.fromJSON . A.Object $ tsParams ts case rtsItems settings of -- Items specified: parse those Just items -> traverse parseTransitionSettings items -- No items specified: parse default transition settings. Nothing -> for transitions $ \(typ, _) -> parseTransitionSettings TransitionSettings {tsType = typ, tsParams = mempty} -- Found the transition type. | Just (Transition f) <- HMS.lookup ty transitionTable = fmap (f $) . first (\err -> "could not parse " ++ T.unpack ty ++ " transition: " ++ err) . A.resultToEither . A.fromJSON . A.Object $ tsParams ts -- Not found, error. | otherwise = Left $ "unknown transition type: " ++ show ty where ty = tsType ts patat-0.14.1.0/lib/Patat/Transition/000077500000000000000000000000001475634243500170325ustar00rootroot00000000000000patat-0.14.1.0/lib/Patat/Transition/Dissolve.hs000066400000000000000000000032551475634243500211630ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE TemplateHaskell #-} module Patat.Transition.Dissolve ( transition ) where -------------------------------------------------------------------------------- import qualified Data.Aeson.Extended as A import qualified Data.Aeson.TH.Extended as A import Data.Bifunctor (first) import qualified Data.Vector as V import Patat.PrettyPrint.Matrix import Patat.Size (Size (..)) import Patat.Transition.Internal import System.Random.Stateful -------------------------------------------------------------------------------- data Config = Config { cDuration :: Maybe (A.FlexibleNum Double) , cFrameRate :: Maybe (A.FlexibleNum Int) } -------------------------------------------------------------------------------- transition :: Config -> TransitionGen transition config (Size rows cols) initial final rgen = first frame <$> evenlySpacedFrames (A.unFlexibleNum <$> cDuration config) (A.unFlexibleNum <$> cFrameRate config) where -- Generate a random number between 0 and 1 for each position. noise :: V.Vector Double noise = runStateGen_ rgen $ \g -> V.replicateM (rows * cols) (uniformRM (0, 1) g) -- Select the initial or final value depending on the noise. frame :: Double -> Matrix frame t = V.zipWith3 (\threshold l r -> if t < threshold then l else r) noise initial final -------------------------------------------------------------------------------- $(A.deriveFromJSON A.dropPrefixOptions ''Config) patat-0.14.1.0/lib/Patat/Transition/Internal.hs000066400000000000000000000071701475634243500211470ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE GADTs #-} module Patat.Transition.Internal ( Duration (..) , threadDelayDuration , Transition (..) , TransitionGen , TransitionId , TransitionInstance (..) , newTransition , stepTransition , evenlySpacedFrames ) where -------------------------------------------------------------------------------- import Control.Concurrent (threadDelay) import qualified Data.Aeson as A import Data.List.NonEmpty (NonEmpty ((:|))) import Data.Maybe (fromMaybe) import Data.Unique (Unique, newUnique) import qualified Patat.PrettyPrint as PP import Patat.PrettyPrint.Matrix import Patat.Size (Size (..)) import System.Random (StdGen, newStdGen) -------------------------------------------------------------------------------- newtype Duration = Duration Double -- Duration in seconds deriving (Show) -------------------------------------------------------------------------------- threadDelayDuration :: Duration -> IO () threadDelayDuration (Duration seconds) = threadDelay . round $ seconds * 1000 * 1000 -------------------------------------------------------------------------------- data Transition where Transition :: A.FromJSON conf => (conf -> TransitionGen) -> Transition -------------------------------------------------------------------------------- type TransitionGen = Size -> Matrix -> Matrix -> StdGen -> NonEmpty (Matrix, Duration) -------------------------------------------------------------------------------- newtype TransitionId = TransitionId Unique deriving (Eq) -------------------------------------------------------------------------------- data TransitionInstance = TransitionInstance { tiId :: TransitionId , tiSize :: Size , tiFrames :: NonEmpty (Matrix, Duration) } -------------------------------------------------------------------------------- newTransition :: TransitionGen -> Size -> PP.Doc -> PP.Doc -> IO TransitionInstance newTransition tgen termSize frame0 frame1 = do unique <- newUnique rgen <- newStdGen let frames = tgen size matrix0 matrix1 rgen pure $ TransitionInstance (TransitionId unique) size frames where -- The actual part we want to animate does not cover the last row, which is -- always empty. size = termSize {sRows = sRows termSize - 1} matrix0 = docToMatrix size frame0 matrix1 = docToMatrix size frame1 -------------------------------------------------------------------------------- stepTransition :: TransitionId -> TransitionInstance -> Maybe TransitionInstance stepTransition transId trans | transId /= tiId trans = Just trans stepTransition _ trans = case tiFrames trans of _ :| [] -> Nothing _ :| f : fs -> Just trans {tiFrames = f :| fs} -------------------------------------------------------------------------------- -- | Given an optional duration and frame rate, generate a sequence of evenly -- spaced frames, represented by a number ranging from [0 .. 1]. evenlySpacedFrames :: Maybe Double -> Maybe Int -> NonEmpty (Double, Duration) evenlySpacedFrames mbDuration mbFrameRate = frame 0 :| map frame [1 .. frames - 1] where duration = fromMaybe 1 mbDuration frameRate = fromMaybe 24 mbFrameRate frames = round $ duration * fromIntegral frameRate :: Int delay = duration / fromIntegral (frames + 1) frame idx = (fromIntegral (idx + 1) / fromIntegral frames, Duration delay) patat-0.14.1.0/lib/Patat/Transition/Matrix.hs000066400000000000000000000102421475634243500206310ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE TemplateHaskell #-} module Patat.Transition.Matrix ( transition ) where -------------------------------------------------------------------------------- import Control.Monad (forM_, guard, when) import qualified Data.Aeson.Extended as A import qualified Data.Aeson.TH.Extended as A import Data.Bifunctor (first) import qualified Data.Vector as V import qualified Data.Vector.Mutable as VM import Patat.PrettyPrint.Matrix import Patat.Size (Size (..)) import Patat.Transition.Internal import System.Random.Stateful -------------------------------------------------------------------------------- data Config = Config { cDuration :: Maybe (A.FlexibleNum Double) , cFrameRate :: Maybe (A.FlexibleNum Int) } -------------------------------------------------------------------------------- data Particle = Particle { pX :: Double , pInitialY :: Double , pFinalY :: Double , pSpeed :: Double , pCell :: Cell } -------------------------------------------------------------------------------- particleY :: Particle -> Double -> Double particleY p t = pInitialY p * (1 - t') + pFinalY p * t' where t' = min 1 (pSpeed p * t) -------------------------------------------------------------------------------- -- | Maximum speed of a particle, expressed as a factor of the minimum speed of -- a particle. particleMaxSpeed :: Double particleMaxSpeed = 2 -------------------------------------------------------------------------------- -- | Number of ghosts a particle leaves behind. Currently hardcoded but could -- be moved to config. particleGhosts :: Int particleGhosts = 3 -------------------------------------------------------------------------------- transition :: Config -> TransitionGen transition config (Size rows cols) initial final rgen = first frame <$> evenlySpacedFrames (A.unFlexibleNum <$> cDuration config) (A.unFlexibleNum <$> cFrameRate config) where speeds :: V.Vector Double speeds = runStateGen_ rgen $ \g -> V.replicateM (rows * cols) (uniformRM (1, particleMaxSpeed) g) up :: V.Vector Bool up = runStateGen_ rgen $ \g -> V.replicateM (rows * cols) (uniformM g) ghosts :: Double -> [Double] ghosts baseSpeed = [ baseSpeed * (1 + fromIntegral i / fromIntegral particleGhosts) | i <- [0 .. particleGhosts] ] initialParticles :: [Particle] initialParticles = do (x, y, cell) <- posCells initial let idx = y * cols + x speed <- ghosts $ speeds V.! idx pure Particle { pX = fromIntegral x , pInitialY = fromIntegral y , pFinalY = if up V.! idx then 0 else fromIntegral rows , pSpeed = speed , pCell = cell } finalParticles :: [Particle] finalParticles = do (x, y, cell) <- posCells final let idx = y * cols + x speed <- ghosts $ speeds V.! idx pure Particle { pX = fromIntegral x , pInitialY = if up V.! idx then -1 else fromIntegral rows , pFinalY = fromIntegral y , pSpeed = speed , pCell = cell } posCells :: Matrix -> [(Int, Int, Cell)] posCells mat = do y <- [0 .. rows - 1] x <- [0 .. cols - 1] let cell = mat V.! (y * cols + x) guard . not $ cell == emptyCell pure (x, y, cell) frame :: Double -> Matrix frame t = V.create $ do mat <- VM.replicate (rows * cols) emptyCell forM_ (initialParticles ++ finalParticles) $ \particle -> let y = round $ particleY particle t x = round $ pX particle idx = y * cols + x in when (x >= 0 && x < cols && y >= 0 && y < rows) $ VM.write mat idx $ pCell particle pure mat -------------------------------------------------------------------------------- $(A.deriveFromJSON A.dropPrefixOptions ''Config) patat-0.14.1.0/lib/Patat/Transition/SlideLeft.hs000066400000000000000000000036751475634243500212540ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE TemplateHaskell #-} module Patat.Transition.SlideLeft ( transition ) where -------------------------------------------------------------------------------- import qualified Data.Aeson.Extended as A import qualified Data.Aeson.TH.Extended as A import Data.Bifunctor (first) import Data.Foldable (for_) import qualified Data.Vector as V import qualified Data.Vector.Mutable as VM import Patat.PrettyPrint.Matrix import Patat.Size (Size (..)) import Patat.Transition.Internal -------------------------------------------------------------------------------- data Config = Config { cDuration :: Maybe (A.FlexibleNum Double) , cFrameRate :: Maybe (A.FlexibleNum Int) } -------------------------------------------------------------------------------- transition :: Config -> TransitionGen transition config (Size rows cols) initial final _rgen = first frame <$> evenlySpacedFrames (A.unFlexibleNum <$> cDuration config) (A.unFlexibleNum <$> cFrameRate config) where frame :: Double -> Matrix frame t = V.create $ do ini <- V.unsafeThaw initial fin <- V.unsafeThaw final mat <- VM.replicate (rows * cols) emptyCell for_ [0 .. rows - 1] $ \y -> do VM.copy (VM.slice (y * cols) (cols - offset) mat) (VM.slice (y * cols + offset) (cols - offset) ini) VM.copy (VM.slice (y * cols + cols - offset) offset mat) (VM.slice (y * cols) offset fin) pure mat where offset = max 0 . min cols . (round :: Double -> Int) $ t * fromIntegral cols -------------------------------------------------------------------------------- $(A.deriveFromJSON A.dropPrefixOptions ''Config) patat-0.14.1.0/lib/Patat/Unique.hs000066400000000000000000000010401475634243500164750ustar00rootroot00000000000000{-# LANGUAGE GeneralizedNewtypeDeriving #-} module Patat.Unique ( Unique , UniqueGen , zeroUniqueGen , freshUnique ) where import Data.Hashable (Hashable) -- | Can be used as a unique identifier. newtype Unique = Unique Int deriving (Hashable, Eq, Ord, Show) -- | Used to generate fresh variables. newtype UniqueGen = UniqueGen Int deriving (Show) zeroUniqueGen :: UniqueGen zeroUniqueGen = UniqueGen 0 freshUnique :: UniqueGen -> (Unique, UniqueGen) freshUnique (UniqueGen x) = (Unique x, UniqueGen (x + 1)) patat-0.14.1.0/lib/Text/000077500000000000000000000000001475634243500145535ustar00rootroot00000000000000patat-0.14.1.0/lib/Text/Pandoc/000077500000000000000000000000001475634243500157575ustar00rootroot00000000000000patat-0.14.1.0/lib/Text/Pandoc/Extended.hs000066400000000000000000000017001475634243500200510ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE LambdaCase #-} module Text.Pandoc.Extended ( module Text.Pandoc , readPlainText ) where -------------------------------------------------------------------------------- import Data.Char (isSpace) import qualified Data.Text as T import Prelude import Text.Pandoc -------------------------------------------------------------------------------- -- | A plain-text reader. Always returns empty metadata. readPlainText :: T.Text -> Pandoc readPlainText = Pandoc mempty . pure . Plain . go where go txt0 = case T.uncons txt0 of Nothing -> [] Just (' ', txt1) -> Space : go txt1 Just ('\r', txt1) -> go txt1 Just ('\n', txt1) -> SoftBreak : go txt1 Just (c, txt1) -> let (pre, post) = T.break isSpace txt1 in Str (T.cons c pre) : go post patat-0.14.1.0/patat.cabal000066400000000000000000000120671475634243500151640ustar00rootroot00000000000000Name: patat Version: 0.14.1.0 Synopsis: Terminal-based presentations using Pandoc Description: Terminal-based presentations using Pandoc. License: GPL-2 License-file: LICENSE Author: Jasper Van der Jeugt Maintainer: Jasper Van der Jeugt Homepage: http://github.com/jaspervdj/patat Copyright: 2016 Jasper Van der Jeugt Category: Text Build-type: Simple Cabal-version: >=1.10 Tested-with: GHC ==9.2.8 || ==9.4.8 || ==9.6.3 || ==9.8.1 Extra-source-files: CHANGELOG.md README.md Source-repository head Type: git Location: https://github.com/jaspervdj/patat.git Flag patat-make-man Description: Build the executable to generate the man page Default: False Manual: True Library Ghc-options: -Wall Hs-source-dirs: lib Default-language: Haskell2010 Build-depends: aeson >= 2.0 && < 2.3, ansi-terminal >= 0.6 && < 1.1, ansi-wl-pprint >= 0.6 && < 1.1, async >= 2.2 && < 2.3, base >= 4.9 && < 5, base64-bytestring >= 1.0 && < 1.3, bytestring >= 0.10 && < 0.13, colour >= 2.3 && < 2.4, containers >= 0.5 && < 0.7, directory >= 1.2 && < 1.4, filepath >= 1.4 && < 1.6, hashable >= 1.4 && < 1.5, JuicyPixels >= 3.3.3 && < 3.4, mtl >= 2.2 && < 2.4, optparse-applicative >= 0.16 && < 0.19, pandoc >= 3.1 && < 3.6, pandoc-types >= 1.23 && < 1.24, process >= 1.6 && < 1.7, random >= 1.2 && < 1.3, skylighting >= 0.10 && < 0.15, terminal-size >= 0.3 && < 0.4, text >= 1.2 && < 2.2, time >= 1.4 && < 1.13, unordered-containers >= 0.2 && < 0.3, yaml >= 0.8 && < 0.12, vector >= 0.13 && < 0.14, wcwidth >= 0.0 && < 0.1, -- We don't even depend on these packages but they can break cabal install -- because of the conflicting 'Network.URI' module. network-uri >= 2.6, network >= 2.6 If impl(ghc < 8.0) Build-depends: semigroups >= 0.16 && < 0.19 Exposed-modules: Patat.AutoAdvance Patat.Cleanup Patat.EncodingFallback Patat.Eval Patat.Eval.Internal Patat.Images Patat.Images.Internal Patat.Images.ITerm2 Patat.Images.Kitty Patat.Images.W3m Patat.Images.WezTerm Patat.Main Patat.Presentation Patat.Presentation.Display Patat.Presentation.Display.CodeBlock Patat.Presentation.Display.Internal Patat.Presentation.Display.Table Patat.Presentation.Fragment Patat.Presentation.Interactive Patat.Presentation.Internal Patat.Presentation.Read Patat.Presentation.Settings Patat.Presentation.SpeakerNotes Patat.Presentation.Syntax Patat.PrettyPrint Patat.PrettyPrint.Internal Patat.PrettyPrint.Matrix Patat.Size Patat.Theme Patat.Transition Patat.Transition.Internal Patat.Transition.Dissolve Patat.Transition.Matrix Patat.Transition.SlideLeft Patat.Unique Other-modules: Control.Concurrent.Chan.Extended Data.Aeson.Extended Data.Aeson.TH.Extended Data.Char.WCWidth.Extended Data.Sequence.Extended Paths_patat Text.Pandoc.Extended Executable patat Main-is: Main.hs Ghc-options: -Wall -threaded -rtsopts "-with-rtsopts=-N" Hs-source-dirs: src Default-language: Haskell2010 Build-depends: base, patat Executable patat-make-man Main-is: make-man.hs Ghc-options: -Wall Hs-source-dirs: extra Default-language: Haskell2010 If flag(patat-make-man) Buildable: True Else Buildable: False Build-depends: base >= 4.9 && < 5, containers >= 0.6 && < 0.8, doctemplates >= 0.8 && < 0.12, mtl >= 2.2 && < 2.4, pandoc >= 3.1 && < 3.6, text >= 1.2 && < 2.2, time >= 1.6 && < 1.13 Test-suite patat-tests Main-is: Main.hs Ghc-options: -Wall Hs-source-dirs: tests/haskell Type: exitcode-stdio-1.0 Default-language: Haskell2010 Other-modules: Patat.Presentation.Interactive.Tests Patat.Presentation.Read.Tests Patat.PrettyPrint.Matrix.Tests Build-depends: patat, ansi-terminal >= 0.6 && < 1.1, base >= 4.8 && < 5, directory >= 1.2 && < 1.4, pandoc >= 3.1 && < 3.6, tasty >= 1.2 && < 1.6, tasty-hunit >= 0.10 && < 0.11, tasty-quickcheck >= 0.10 && < 0.11, text >= 1.2 && < 2.2, QuickCheck >= 2.8 && < 2.15 Test-suite patat-goldplate Main-is: Main.hs Ghc-options: -Wall Hs-source-dirs: tests/golden Type: exitcode-stdio-1.0 Default-language: Haskell2010 Build-tool-depends: patat:patat Build-depends: base >= 4.8 && < 5, goldplate >= 0.2.2.1 && < 0.3 patat-0.14.1.0/src/000077500000000000000000000000001475634243500136505ustar00rootroot00000000000000patat-0.14.1.0/src/Main.hs000066400000000000000000000001021475634243500150610ustar00rootroot00000000000000import qualified Patat.Main main :: IO () main = Patat.Main.main patat-0.14.1.0/tests/000077500000000000000000000000001475634243500142235ustar00rootroot00000000000000patat-0.14.1.0/tests/golden/000077500000000000000000000000001475634243500154735ustar00rootroot00000000000000patat-0.14.1.0/tests/golden/Main.hs000066400000000000000000000003551475634243500167160ustar00rootroot00000000000000import Goldplate (Options (..), defaultOptions, mainWith) import System.Exit (exitWith) main :: IO () main = mainWith options >>= exitWith where options = defaultOptions { oPrettyDiff = True, oPaths = ["tests/golden"] } patat-0.14.1.0/tests/golden/inputs/000077500000000000000000000000001475634243500170155ustar00rootroot00000000000000patat-0.14.1.0/tests/golden/inputs/01.md000066400000000000000000000002131475634243500175530ustar00rootroot00000000000000--- title: This is my presentation author: Jasper Van der Jeugt ... # This is a test Hello world --- # This is a second slide lololol patat-0.14.1.0/tests/golden/inputs/02.lhs000066400000000000000000000001461475634243500177470ustar00rootroot00000000000000This is how you define a `String` in Haskell: > test :: String > test = "Hello World!" Cool, right? patat-0.14.1.0/tests/golden/inputs/03.md000066400000000000000000000007141475634243500175630ustar00rootroot00000000000000Inline markups: - ~~striked out~~ - --- > Some quote > Quote with embedded list: > > - Hello > - World --- - List with an embedded quote: > Tu quoque Wow rad stuff. - Second item in that list. --- Code with empty line: puts "wow" puts "amaze" --- Code in ordered list: 1. Do you know the coolest codes? It's this: fire_missiles() cancel() Great 2. Also `fib` is pretty cool yeah patat-0.14.1.0/tests/golden/inputs/bolditalic.md000066400000000000000000000001261475634243500214440ustar00rootroot00000000000000--- patat: theme: emph: [italic] strong: [bold] ... **Strong** and _emph_. patat-0.14.1.0/tests/golden/inputs/cjk01.md000066400000000000000000000001611475634243500202450ustar00rootroot00000000000000--- author: 双曲番 title: '双曲番: Hyperbolic Sokoban' ... Check out my game at patat-0.14.1.0/tests/golden/inputs/cjk02.md000066400000000000000000000023071475634243500202520ustar00rootroot00000000000000# A table with CJK characters Alignment should be: right, left, center, right. | Number | Sino-Japanese reading | Kanji | Native Japanese reading | |-------:|:-----------------------------------|:-----:|------------------------:| | 1 | いち (ichi) | 一 | ひとつ (hitotsu) | | 2 | に (ni) | 二 | ふたつ (futatsu) | | 3 | さん (san) | 三 | みっつ (mittsu) | | 4 | し、よん (shi, yon) | 四 | よっつ (yottsu) | | 5 | ご (go) | 五 | いつつ (itsutsu) | | 6 | ろく (roku) | 六 | むっつ (muttsu) | | 7 | しち、なな (shichi, nana) | 七 | ななつ (nanatsu) | | 8 | はち (hachi) | 八 | やっつ (yattsu) | | 9 | く、きゅう (ku, kyuu) | 九 | ここのつ (kokonotsu) | | 10 | じゅう (juu) | 十 | とう (tou) | | 0 | れい、ゼロ、マル (rei, zero, maru) | 零 | | patat-0.14.1.0/tests/golden/inputs/comments.lhs000066400000000000000000000001521475634243500213500ustar00rootroot00000000000000# This is a test > putStrLn "Hello, world" Yep. patat-0.14.1.0/tests/golden/inputs/comments.md000066400000000000000000000003351475634243500211650ustar00rootroot00000000000000# This is a test Hello world # This is a second slide Where are my raw blocks at patat-0.14.1.0/tests/golden/inputs/deflist.md000066400000000000000000000003451475634243500207730ustar00rootroot00000000000000Term 1 : Definition 1 Term 2 with *inline markup* : Definition 2 { some code, part of Definition 2 } Third paragraph of definition 2. --- Term 1 ~ Definition 1 Term 2 ~ Definition 2a ~ Definition 2b patat-0.14.1.0/tests/golden/inputs/eval01.md000066400000000000000000000004071475634243500204300ustar00rootroot00000000000000--- patat: eval: eval: command: bash replace: true reveal: true ... # Slide 1 - This is some code that is not evaluated: ```bash echo foo ``` - And here is some code that is evaluated: ```eval echo foo ``` patat-0.14.1.0/tests/golden/inputs/eval02.md000066400000000000000000000004121475634243500204250ustar00rootroot00000000000000--- patat: eval: eval: command: bash replace: false fragment: true ... # Slide 1 - This is some code that is not evaluated: ```bash echo foo ``` - And here is some code that is evaluated: ```eval echo foo ``` patat-0.14.1.0/tests/golden/inputs/eval03.md000066400000000000000000000004121475634243500204260ustar00rootroot00000000000000--- patat: eval: eval: command: bash replace: true fragment: false ... # Slide 1 - This is some code that is not evaluated: ```bash echo foo ``` - And here is some code that is evaluated: ```eval echo foo ``` patat-0.14.1.0/tests/golden/inputs/eval04.md000066400000000000000000000004131475634243500204300ustar00rootroot00000000000000--- patat: eval: eval: command: bash replace: false fragment: false ... # Slide 1 - This is some code that is not evaluated: ```bash echo foo ``` - And here is some code that is evaluated: ```eval echo foo ``` patat-0.14.1.0/tests/golden/inputs/eval05.md000066400000000000000000000002071475634243500204320ustar00rootroot00000000000000--- patat: eval: bash: command: bash rev: command: rev ... # Slide 1 ```bash echo foo ``` ```rev echo foo ``` patat-0.14.1.0/tests/golden/inputs/eval06.md000066400000000000000000000013361475634243500204370ustar00rootroot00000000000000--- patat: eval: shImplicit: command: sh replace: true fragment: false shCode: command: sh wrap: code replace: true fragment: false shRaw: command: sh wrap: raw replace: true fragment: false shInline: command: sh wrap: rawInline replace: true fragment: false ... # Implicit eval slide ~~~{.shImplicit} printf '\e[1;34m%-6s\e[m' "This is text" ~~~ # Code eval slide ~~~{.shCode} printf '\e[1;34m%-6s\e[m' "This is text" ~~~ # Raw eval slide ~~~{.shRaw} printf '\e[1;34m%-6s\e[m' "This is text" ~~~ Newline here... # Raw Inline eval slide ~~~{.shInline} printf '\e[1;34m%-6s\e[m' "This is text" ~~~ No newline here... patat-0.14.1.0/tests/golden/inputs/eval07.md000066400000000000000000000013521475634243500204360ustar00rootroot00000000000000--- patat: eval: shImplicit: command: sh replace: true fragment: false shCode: command: sh container: code replace: true fragment: false shNone: command: sh container: none replace: true fragment: false shInline: command: sh container: inline replace: true fragment: false ... # Implicit eval slide ~~~{.shImplicit} printf '\e[1;34m%-6s\e[m' "This is text" ~~~ # Code eval slide ~~~{.shCode} printf '\e[1;34m%-6s\e[m' "This is text" ~~~ # None eval slide ~~~{.shNone} printf '\e[1;34m%-6s\e[m' "This is text" ~~~ Newline here... # Inline eval slide ~~~{.shInline} printf '\e[1;34m%-6s\e[m' "This is text" ~~~ No newline here... patat-0.14.1.0/tests/golden/inputs/eval08.md000066400000000000000000000010351475634243500204350ustar00rootroot00000000000000--- patat: eval: implicitStderr: command: sh replace: true fragment: false withStderr: command: sh replace: true fragment: false stderr: true withoutStderr: command: sh replace: true fragment: false stderr: false ... # Slide ~~~{.implicitStderr} echo "Hello stdout" sleep 0.1 echo "Hello stderr" >&2 ~~~ ~~~{.withStderr} echo "Hello stdout" sleep 0.1 echo "Hello stderr" >&2 ~~~ ~~~{.withoutStderr} echo "Hello stdout" sleep 0.1 echo "Hello stderr" >&2 ~~~ patat-0.14.1.0/tests/golden/inputs/eval09.md000066400000000000000000000001631475634243500204370ustar00rootroot00000000000000--- patat: eval: bash: command: bash ... # Slide 1 ```bash echo foo ``` . . . ```bash echo foo ``` patat-0.14.1.0/tests/golden/inputs/extentions0.md000066400000000000000000000002241475634243500216150ustar00rootroot00000000000000--- patat: pandocExtensions: - patat_extensions - autolink_bare_uris - emoji ... Check out this example: http://example.com/ :smile: patat-0.14.1.0/tests/golden/inputs/extentions1.md000066400000000000000000000001671475634243500216240ustar00rootroot00000000000000--- patat: pandocExtensions: - emoji ... The patat default ~~strikeout~~ is not enabled, but emojis are :smile: patat-0.14.1.0/tests/golden/inputs/fragments.md000066400000000000000000000002721475634243500213260ustar00rootroot00000000000000--- patat: incrementalLists: true ... - This list - is displayed * item * by item - Or sometimes > * all at > * once --- Legen . . . wait for it . . . Dary! patat-0.14.1.0/tests/golden/inputs/fragments02.md000066400000000000000000000005531475634243500214720ustar00rootroot00000000000000--- patat: incrementalLists: true ... # Slide 1 Hello . . . World . . . What is up!!!! # Slide 2 1. This is fragmented . . . Inside a list 2. Lol ok # Slide 3 - A simple list - And two - And three 1. Why? - Some reason - IDK 2. Who? - Some dude - Or another 3. What - Did something - Or nothing patat-0.14.1.0/tests/golden/inputs/headers.md000066400000000000000000000002661475634243500207560ustar00rootroot00000000000000# This could be a title ## This is nested Here is some content ## This is also nested Here is more content # Another topic ## What is going on? I think we can display slides? patat-0.14.1.0/tests/golden/inputs/image.md000066400000000000000000000001261475634243500204200ustar00rootroot00000000000000--- patat: images: backend: iterm2 ... # An image ![](./extra/screenshot.png) patat-0.14.1.0/tests/golden/inputs/issue-111.md000066400000000000000000000005151475634243500207700ustar00rootroot00000000000000--- patat: theme: blockQuote: [italic, vividRed, onDullGreen] ... # Test quote This is not a quote > This is a block quote # Test > This is block quote > > a long quote > > putStrLn "Hello world" > > with some `code` as well # Test > This is a block quote with a fairly long paragraph > > - With > - a nice > - list patat-0.14.1.0/tests/golden/inputs/issue-171.md000066400000000000000000000001331475634243500207720ustar00rootroot00000000000000# what am i running? add to `$PS1`: ``` 象 $(php -v | grep "^PHP" | cut -d " " -f2) ``` patat-0.14.1.0/tests/golden/inputs/links.md000066400000000000000000000003401475634243500204540ustar00rootroot00000000000000This is an "automatic link": . This is an [inline link](/url), and here's [one with a title](http://fsf.org "click here for a good time!"). Let's talk about [foo][foosite] [foosite]: http://foo.com/ patat-0.14.1.0/tests/golden/inputs/lists.md000066400000000000000000000004001475634243500204670ustar00rootroot00000000000000- This is a nested list. * The nested items should have different list markers. * I mean, they can be the same, but it doesn't look nice. printf("Nested code block!\n") * Cool right? Definitely super cool - One final item patat-0.14.1.0/tests/golden/inputs/margins-auto-wrap.md000077500000000000000000000005211475634243500227150ustar00rootroot00000000000000--- title: Centered presentation author: John Doe patat: wrap: true margins: left: auto right: auto top: auto ... Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world --- Hello world This is a test patat-0.14.1.0/tests/golden/inputs/margins-auto.md000066400000000000000000000006331475634243500217470ustar00rootroot00000000000000--- author: 'Jasper' patat: margins: left: auto right: auto ... # A header Some text ## Subheader with more info 1. One 2. Two 3. Three # Hello - Some really fancy lists - With another list embedded: * Yes * No - And a codeblock: ``` 1 + 2 ``` And a paragraph ```haskell Yeah | Nope ``` # Vertical centered slide Yo patat-0.14.1.0/tests/golden/inputs/margins-top.md000066400000000000000000000002121475634243500215720ustar00rootroot00000000000000--- patat: wrap: true margins: top: 3 right: 5 ... # Title slides are not affected ## Some header Some content patat-0.14.1.0/tests/golden/inputs/margins.md000066400000000000000000000004431475634243500210000ustar00rootroot00000000000000--- author: 'Jasper' patat: wrap: true columns: 57 # 10 + 42 + 5 margins: left: 10 right: 5 ... This text will have 10 spaces on the left. - So * will * these * bullets This line will have 10 spaces on the left, but will also break after "left". patat-0.14.1.0/tests/golden/inputs/margins01.md000066400000000000000000000010261475634243500211370ustar00rootroot00000000000000--- patat: incrementalLists: true margins: left: auto right: auto top: auto ... # Introduction Make sure there is no difference in between using a fragment at the end of a list, or not. This test case also tests margins very well. # Entirely fragmented 1. This is 2. An ordered list . . . Hahaha! - Here is - A second list # Not fragmented 1. This is 2. An ordered list Hahaha! - Here is - A second list # Extra stuff at end 1. This is 2. An ordered list Hahaha! - Here is - A second list Extra stuff patat-0.14.1.0/tests/golden/inputs/meta.md000066400000000000000000000001651475634243500202670ustar00rootroot00000000000000--- patat: theme: bulletListMarkers: '<>' ... - Hello - World * How * Are * You * Doing patat-0.14.1.0/tests/golden/inputs/plain.txt000066400000000000000000000002001475634243500206510ustar00rootroot00000000000000This is some _plain text_. It should not have any formatting applied whatsoever. --- Slide splitting should not work either. patat-0.14.1.0/tests/golden/inputs/slide-config.md000066400000000000000000000011321475634243500216770ustar00rootroot00000000000000--- patat: theme: header: [vividRed] strong: [vividCyan] ... # This is a test This slide has **higher margin** at the top. # This is a test This slide has **normal margin** at the top. # Different color header This slide has a **different theme** set. It also has no slide number. The two config blocks should be merged. patat-0.14.1.0/tests/golden/inputs/slidelevel0.md000066400000000000000000000001651475634243500215510ustar00rootroot00000000000000--- patat: slideLevel: 0 --- # We should not split slides Never # At all Because we have `slideLevel` set to 0 patat-0.14.1.0/tests/golden/inputs/slidelevel1.md000066400000000000000000000004671475634243500215570ustar00rootroot00000000000000--- patat: slideLevel: 1 --- # This starts a new slide ## But this does not Here is some content ## And another header And more content (yep) # This should start a new slide ## With some content ### Very deeply nested #### Is a hidden message ##### A dark secret... jet fuel can't melt steel beams patat-0.14.1.0/tests/golden/inputs/slidelevel2.md000066400000000000000000000002361475634243500215520ustar00rootroot00000000000000# This is a title ## This is a slide Here is some content ## And another slide And more content (yep) # This is another title ## With some content Yay patat-0.14.1.0/tests/golden/inputs/slidelevel3.md000066400000000000000000000003001475634243500215430ustar00rootroot00000000000000# This is a title ## This is a subtitle ### This is a slide Here is some content # This is another title ## This is another subtitle ### This is another slide Here is some more content patat-0.14.1.0/tests/golden/inputs/slidenumber.md000066400000000000000000000001631475634243500216500ustar00rootroot00000000000000--- patat: slideNumber: false ... This slide will have no number --- This slide will have no number as well patat-0.14.1.0/tests/golden/inputs/syntax-definitions.md000066400000000000000000000002441475634243500231760ustar00rootroot00000000000000--- patat: syntaxDefinitions: - 'syntax-definitions/impurescript.xml' ... Hello ```impurescript iterate f 0 x = x iterate f n x = iterate f (n - 1) (f x) ``` patat-0.14.1.0/tests/golden/inputs/syntax.md000066400000000000000000000002371475634243500206670ustar00rootroot00000000000000--- patat: theme: syntaxHighlighting: decVal: [bold, onDullRed] ... Some simple code: ```c int main(int argc, char **argv) { return 0; } ``` patat-0.14.1.0/tests/golden/inputs/tables.md000066400000000000000000000030611475634243500206110ustar00rootroot00000000000000# Normal simple table Right Left Center Default ------- ------ ---------- ------- 12 12 12 12 123 123 123 123 1 1 1 1 Table: Demonstration of simple table syntax. # Headerless table ------- ------ ---------- ------- 12 12 12 12 123 123 123 123 1 1 1 1 ------- ------ ---------- ------- # Multiline ------------------------------------------------------------- Centered Default Right Left Header Aligned Aligned Aligned ----------- ------- --------------- ------------------------- First row 12.0 Example of a row that spans multiple lines. Second row 5.0 Here's another one. Note the blank line between rows. ------------------------------------------------------------- Table: Here's the caption. It, too, may span multiple lines. # Headerless multiline ----------- ------- --------------- ------------------------- First row 12.0 Example of a row that spans multiple lines. Second row 5.0 Here's another one. Note the blank line between rows. ----------- ------- --------------- ------------------------- : Here's a multiline table without headers. patat-0.14.1.0/tests/golden/inputs/tabstop.md000066400000000000000000000005361475634243500210170ustar00rootroot00000000000000--- patat: tabStop: 4 ... ``` Essential Name Expenses x Food $200 Data $150 Rent $800 x Candles $3,600 Utilities $150 ``` --- ``` Essential Name Expenses x Food $200 Data $150 Rent $800 x Candles $3,600 Utilities $150 ``` --- someone who is good at the economy please help me budget this. my family is dying patat-0.14.1.0/tests/golden/inputs/themes.md000066400000000000000000000003651475634243500206300ustar00rootroot00000000000000--- patat: theme: bulletListMarkers: '-+' emph: [onVividRed, underline] strong: [rgb#f08000, onRgb#101060] ... - This is a simple list. * With _nested_ items. * One or two **bold**. - The list theming is customized a bit. patat-0.14.1.0/tests/golden/inputs/utf8.md000066400000000000000000000021131475634243500202220ustar00rootroot00000000000000Some code: hello-exe-hello> src/hello.hs:23:35: error: hello-exe-hello> • Couldn't match type ‘Password’ with ‘BuiltinData’ hello-exe-hello> Expected type: template-haskell-2.16.0.0:Language.Haskell.TH.Syntax.Q hello-exe-hello> (template-haskell-2.16.0.0:Language.Haskell.TH.Syntax.TExp hello-exe-hello> (PlutusTx.Code.CompiledCode hello-exe-hello> (BuiltinData -> BuiltinData -> BuiltinData -> ()))) hello-exe-hello> Actual type: th-compat-0.1.4:Language.Haskell.TH.Syntax.Compat.SpliceQ hello-exe-hello> (PlutusTx.Code.CompiledCode hello-exe-hello> (Password -> Password -> BuiltinData -> ())) hello-exe-hello> • In the expression: compile [|| validator ||] hello-exe-hello> In the Template Haskell splice $$(compile [|| validator ||]) hello-exe-hello> In the first argument of ‘mkValidatorScript’, namely hello-exe-hello> ‘$$(compile [|| validator ||])’ patat-0.14.1.0/tests/golden/inputs/wrap-at.md000066400000000000000000000011031475634243500207050ustar00rootroot00000000000000 This is a really long sentence which could be wrapped. - The above sentence should be wrapped. - This bullet list should also be wrapped. --- This is a really long sentence which could be wrapped. - The above sentence should be wrapped. - This bullet list should also be wrapped. --- This is a really long sentence which could be wrapped. - The above sentence should be wrapped. - This bullet list should also be wrapped. patat-0.14.1.0/tests/golden/inputs/wrapping.md000066400000000000000000000011061475634243500211640ustar00rootroot00000000000000--- patat: wrap: true columns: 40 ... This is a long sentence over multiple lines which can be re-wrapped. This is a super long sentence over a single line which should also be re-wrapped. This is a table and tables should not be wrapped ------- ------- ---------- ---------- ---------- 1 2 3 4 5 6 7 8 9 10 - This is a list - This list has a really long sentence in it which should also be wrapped with proper indentation - Another item This line is long, and then ends with `code` patat-0.14.1.0/tests/golden/outputs/000077500000000000000000000000001475634243500172165ustar00rootroot00000000000000patat-0.14.1.0/tests/golden/outputs/01.md.dump000066400000000000000000000006741475634243500207330ustar00rootroot00000000000000 This is my presentation  # This is a test Hello world  Jasper Van der Jeugt 1 / 2  {slide}  This is my presentation  # This is a second slide lololol  Jasper Van der Jeugt 2 / 2  patat-0.14.1.0/tests/golden/outputs/02.lhs.dump000066400000000000000000000007621475634243500211200ustar00rootroot00000000000000 02.lhs  This is how you define a  String  in Haskell:      test :: String    test = "Hello World!"     Cool, right?  1 / 1  patat-0.14.1.0/tests/golden/outputs/03.md.dump000066400000000000000000000036541475634243500207360ustar00rootroot00000000000000 03.md  Inline markups:  - ~~striked out~~  - <http://example.com>  1 / 5  {slide}  03.md  > Some quote > Quote with embedded list: >  >  - Hello >  - World  2 / 5  {slide}  03.md   - List with an embedded quote:  > Tu quoque  Wow rad stuff.  - Second item in that list.  3 / 5  {slide}  03.md  Code with empty line:      puts "wow"       puts "amaze"      4 / 5  {slide}  03.md  Code in ordered list: 1. Do you know the coolest codes?  It's this:      fire_missiles()    cancel()      Great 2. Also  fib  is pretty cool yeah  5 / 5  patat-0.14.1.0/tests/golden/outputs/bolditalic.md.dump000066400000000000000000000003251475634243500226120ustar00rootroot00000000000000 bolditalic.md  Strong and emph.  1 / 1  patat-0.14.1.0/tests/golden/outputs/cjk01.md.dump000066400000000000000000000003641475634243500214170ustar00rootroot00000000000000 双曲番: Hyperbolic Sokoban  Check out my game at <https://sokyokuban.com/>  双曲番 1 / 1  patat-0.14.1.0/tests/golden/outputs/cjk02.md.dump000066400000000000000000000030621475634243500214160ustar00rootroot00000000000000 cjk02.md  # A table with CJK characters Alignment should be: right, left, center, right.  Number Sino-Japanese reading  Kanji Native Japanese reading  ------ ---------------------------------- ----- -----------------------  1 いち (ichi) 一 ひとつ (hitotsu)  2 に (ni) 二 ふたつ (futatsu)  3 さん (san) 三 みっつ (mittsu)  4 し、よん (shi, yon) 四 よっつ (yottsu)  5 ご (go) 五 いつつ (itsutsu)  6 ろく (roku) 六 むっつ (muttsu)  7 しち、なな (shichi, nana) 七 ななつ (nanatsu)  8 はち (hachi) 八 やっつ (yattsu)  9 く、きゅう (ku, kyuu) 九 ここのつ (kokonotsu)  10 じゅう (juu) 十 とう (tou)  0 れい、ゼロ、マル (rei, zero, maru) 零     1 / 1  patat-0.14.1.0/tests/golden/outputs/comments.lhs.dump000066400000000000000000000007301475634243500225170ustar00rootroot00000000000000{speakerNotes: This is a comment so please don't include it. }  comments.lhs  # This is a test      putStrLn "Hello, world"     Yep.  1 / 1  patat-0.14.1.0/tests/golden/outputs/comments.md.dump000066400000000000000000000011541475634243500223320ustar00rootroot00000000000000{speakerNotes: This is a comment so please don't include it. }  comments.md  # This is a test Hello world  1 / 2  {slide} {speakerNotes: - Differently-formatted comment Differently-formatted comment }  comments.md  # This is a second slide Where are my raw blocks at  2 / 2  patat-0.14.1.0/tests/golden/outputs/deflist.md.dump000066400000000000000000000015271475634243500221430ustar00rootroot00000000000000 deflist.md  Term 1 : Definition 1 Term 2 with inline markup : Definition 2      { some code, part of Definition 2 }      Third paragraph of definition 2.  1 / 2  {slide}  deflist.md  Term 1 : Definition 1 Term 2 : Definition 2a : Definition 2b  2 / 2  patat-0.14.1.0/tests/golden/outputs/eval01.md.dump000066400000000000000000000021141475634243500215720ustar00rootroot00000000000000 eval01.md  # Slide 1  - This is some code that is not evaluated:      echo foo      - And here is some code that is evaluated:      echo foo      1 / 1  {fragment}  eval01.md  # Slide 1  - This is some code that is not evaluated:      echo foo      - And here is some code that is evaluated:      foo      1 / 1  patat-0.14.1.0/tests/golden/outputs/eval02.md.dump000066400000000000000000000023001475634243500215700ustar00rootroot00000000000000 eval02.md  # Slide 1  - This is some code that is not evaluated:      echo foo      - And here is some code that is evaluated:      echo foo      1 / 1  {fragment}  eval02.md  # Slide 1  - This is some code that is not evaluated:      echo foo      - And here is some code that is evaluated:      echo foo          foo      1 / 1  patat-0.14.1.0/tests/golden/outputs/eval03.md.dump000066400000000000000000000010251475634243500215740ustar00rootroot00000000000000 eval03.md  # Slide 1  - This is some code that is not evaluated:      echo foo      - And here is some code that is evaluated:      foo      1 / 1  patat-0.14.1.0/tests/golden/outputs/eval04.md.dump000066400000000000000000000012051475634243500215750ustar00rootroot00000000000000 eval04.md  # Slide 1  - This is some code that is not evaluated:      echo foo      - And here is some code that is evaluated:      echo foo          foo      1 / 1  patat-0.14.1.0/tests/golden/outputs/eval05.md.dump000066400000000000000000000027031475634243500216020ustar00rootroot00000000000000 eval05.md  # Slide 1      echo foo          echo foo      1 / 1  {fragment}  eval05.md  # Slide 1      echo foo          foo          echo foo      1 / 1  {fragment}  eval05.md  # Slide 1      echo foo          foo          echo foo          oof ohce      1 / 1  patat-0.14.1.0/tests/golden/outputs/eval06.md.dump000066400000000000000000000022711475634243500216030ustar00rootroot00000000000000 eval06.md  # Implicit eval slide      This is text      1 / 4  {slide}  eval06.md  # Code eval slide      This is text      2 / 4  {slide}  eval06.md  # Raw eval slide This is text Newline here...  3 / 4  {slide}  eval06.md  # Raw Inline eval slide This is text No newline here...  4 / 4  patat-0.14.1.0/tests/golden/outputs/eval07.md.dump000066400000000000000000000022661475634243500216100ustar00rootroot00000000000000 eval07.md  # Implicit eval slide      This is text      1 / 4  {slide}  eval07.md  # Code eval slide      This is text      2 / 4  {slide}  eval07.md  # None eval slide This is text Newline here...  3 / 4  {slide}  eval07.md  # Inline eval slide This is text No newline here...  4 / 4  patat-0.14.1.0/tests/golden/outputs/eval08.md.dump000066400000000000000000000011211475634243500215760ustar00rootroot00000000000000 eval08.md  # Slide      Hello stdout    Hello stderr          Hello stdout    Hello stderr          Hello stdout      1 / 1  patat-0.14.1.0/tests/golden/outputs/eval09.md.dump000066400000000000000000000033411475634243500216050ustar00rootroot00000000000000 eval09.md  # Slide 1      echo foo      1 / 1  {fragment}  eval09.md  # Slide 1      echo foo          foo      1 / 1  {fragment}  eval09.md  # Slide 1      echo foo          foo          echo foo      1 / 1  {fragment}  eval09.md  # Slide 1      echo foo          foo          echo foo          foo      1 / 1  patat-0.14.1.0/tests/golden/outputs/extentions0.md.dump000066400000000000000000000003621475634243500227650ustar00rootroot00000000000000 extentions0.md  Check out this example: <http://example.com/> 😄  1 / 1  patat-0.14.1.0/tests/golden/outputs/extentions1.md.dump000066400000000000000000000003611475634243500227650ustar00rootroot00000000000000 extentions1.md  The patat default ~~strikeout~~ is not enabled, but emojis are 😄  1 / 1  patat-0.14.1.0/tests/golden/outputs/fragments.md.dump000066400000000000000000000046121475634243500224750ustar00rootroot00000000000000 fragments.md   1 / 2  {fragment}  fragments.md   - This list  1 / 2  {fragment}  fragments.md   - This list  - is displayed  1 / 2  {fragment}  fragments.md   - This list  - is displayed  * item  1 / 2  {fragment}  fragments.md   - This list  - is displayed  * item  * by item  1 / 2  {fragment}  fragments.md   - This list  - is displayed  * item  * by item  - Or sometimes  * all at  * once  1 / 2  {slide}  fragments.md  Legen  2 / 2  {fragment}  fragments.md  Legen wait for it  2 / 2  {fragment}  fragments.md  Legen wait for it Dary!  2 / 2  patat-0.14.1.0/tests/golden/outputs/fragments02.md.dump000066400000000000000000000167751475634243500226540ustar00rootroot00000000000000 fragments02.md  # Slide 1 Hello  1 / 3  {fragment}  fragments02.md  # Slide 1 Hello World  1 / 3  {fragment}  fragments02.md  # Slide 1 Hello World What is up!!!!  1 / 3  {slide}  fragments02.md  # Slide 2  2 / 3  {fragment}  fragments02.md  # Slide 2 1. This is fragmented  2 / 3  {fragment}  fragments02.md  # Slide 2 1. This is fragmented  Inside a list  2 / 3  {fragment}  fragments02.md  # Slide 2 1. This is fragmented  Inside a list 2. Lol ok  2 / 3  {slide}  fragments02.md  # Slide 3  3 / 3  {fragment}  fragments02.md  # Slide 3  - A simple list  3 / 3  {fragment}  fragments02.md  # Slide 3  - A simple list  - And two  3 / 3  {fragment}  fragments02.md  # Slide 3  - A simple list  - And two  - And three  3 / 3  {fragment}  fragments02.md  # Slide 3  - A simple list  - And two  - And three  3 / 3  {fragment}  fragments02.md  # Slide 3  - A simple list  - And two  - And three 1. Why?  3 / 3  {fragment}  fragments02.md  # Slide 3  - A simple list  - And two  - And three 1. Why?  - Some reason  3 / 3  {fragment}  fragments02.md  # Slide 3  - A simple list  - And two  - And three 1. Why?  - Some reason  - IDK  3 / 3  {fragment}  fragments02.md  # Slide 3  - A simple list  - And two  - And three 1. Why?  - Some reason  - IDK 2. Who?  3 / 3  {fragment}  fragments02.md  # Slide 3  - A simple list  - And two  - And three 1. Why?  - Some reason  - IDK 2. Who?  - Some dude  3 / 3  {fragment}  fragments02.md  # Slide 3  - A simple list  - And two  - And three 1. Why?  - Some reason  - IDK 2. Who?  - Some dude  - Or another  3 / 3  {fragment}  fragments02.md  # Slide 3  - A simple list  - And two  - And three 1. Why?  - Some reason  - IDK 2. Who?  - Some dude  - Or another 3. What  3 / 3  {fragment}  fragments02.md  # Slide 3  - A simple list  - And two  - And three 1. Why?  - Some reason  - IDK 2. Who?  - Some dude  - Or another 3. What  - Did something  3 / 3  {fragment}  fragments02.md  # Slide 3  - A simple list  - And two  - And three 1. Why?  - Some reason  - IDK 2. Who?  - Some dude  - Or another 3. What  - Did something  - Or nothing  3 / 3  patat-0.14.1.0/tests/golden/outputs/headers.md.dump000066400000000000000000000023061475634243500221200ustar00rootroot00000000000000 headers.md   # This could be a title  1 / 5  {slide}  headers.md > This could be a title  ## This is nested Here is some content  2 / 5  {slide}  headers.md > This could be a title  ## This is also nested Here is more content  3 / 5  {slide}  headers.md   # Another topic  4 / 5  {slide}  headers.md > Another topic  ## What is going on? I think we can display slides?  5 / 5  patat-0.14.1.0/tests/golden/outputs/image.md.dump000066400000000000000000000000461475634243500215660ustar00rootroot00000000000000{image: ./extra/screenshot.png}patat-0.14.1.0/tests/golden/outputs/issue-111.md.dump000066400000000000000000000024341475634243500221370ustar00rootroot00000000000000 issue-111.md  # Test quote This is not a quote > This is a block quote  1 / 3  {slide}  issue-111.md  # Test > This is block quote >  > a long quote >  >    >   putStrLn "Hello world"  >    >  > with some  code  as well  2 / 3  {slide}  issue-111.md  # Test > This is a block quote with a fairly long paragraph >  >  - With >  - a nice >  - list  3 / 3  patat-0.14.1.0/tests/golden/outputs/issue-171.md.dump000066400000000000000000000006741475634243500221510ustar00rootroot00000000000000 issue-171.md  # what am i running? add to  $PS1 :      象 $(php -v | grep "^PHP" | cut -d " " -f2)      1 / 1  patat-0.14.1.0/tests/golden/outputs/links.md.dump000066400000000000000000000011641475634243500216260ustar00rootroot00000000000000 links.md  This is an "automatic link": <https://jaspervdj.be>. This is an [inline link], and here's [one with a title]. Let's talk about [foo] [inline link](/url) [one with a title](http://fsf.org "click here for a good time!") [foo](http://foo.com/)  1 / 1  patat-0.14.1.0/tests/golden/outputs/lists.md.dump000066400000000000000000000010631475634243500216420ustar00rootroot00000000000000 lists.md   - This is a nested list.  * The nested items should have different list markers.  * I mean, they can be the same, but it doesn't look nice.  printf("Nested code block!\n")  * Cool right?  Definitely super cool  - One final item  1 / 1  patat-0.14.1.0/tests/golden/outputs/margins-auto-wrap.md.dump000066400000000000000000000011651475634243500240640ustar00rootroot00000000000000 Centered presentation   Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world  John Doe 1 / 2  {slide}  Centered presentation   Hello world This is a test  John Doe 2 / 2  patat-0.14.1.0/tests/golden/outputs/margins-auto.md.dump000066400000000000000000000035701475634243500231170ustar00rootroot00000000000000 margins-auto.md   # A header  Some text  ## Subheader  with more info  1. One  2. Two  3. Three  Jasper 1 / 3  {slide}  margins-auto.md   # Hello  - Some really fancy lists  - With another list embedded:  * Yes  * No  - And a codeblock:      1 + 2      And a paragraph      Yeah | Nope      Jasper 2 / 3  {slide}  margins-auto.md   # Vertical centered slide  Yo  Jasper 3 / 3  patat-0.14.1.0/tests/golden/outputs/margins-top.md.dump000066400000000000000000000007301475634243500227440ustar00rootroot00000000000000 margins-top.md   # Title slides are not affected  1 / 2  {slide}  margins-top.md > Title slides are not affected  ## Some header Some content  2 / 2  patat-0.14.1.0/tests/golden/outputs/margins.md.dump000066400000000000000000000007541475634243500221520ustar00rootroot00000000000000 margins.md   This text will have 10 spaces on the left.  - So  * will  * these  * bullets  This line will have 10 spaces on the left,  but will also break after "left".  Jasper 1 / 1  patat-0.14.1.0/tests/golden/outputs/margins01.md.dump000066400000000000000000000202551475634243500223110ustar00rootroot00000000000000 margins01.md   # Introduction Make sure there is no difference in between using a fragment at the end of a list, or not. This test case also tests margins very well.  1 / 4  {slide}  margins01.md   # Entirely fragmented  2 / 4  {fragment}  margins01.md   # Entirely fragmented  1. This is  2 / 4  {fragment}  margins01.md   # Entirely fragmented  1. This is  2. An ordered list  2 / 4  {fragment}  margins01.md   # Entirely fragmented  1. This is  2. An ordered list  Hahaha!  2 / 4  {fragment}  margins01.md   # Entirely fragmented  1. This is  2. An ordered list  Hahaha!  - Here is  2 / 4  {fragment}  margins01.md   # Entirely fragmented  1. This is  2. An ordered list  Hahaha!  - Here is  - A second list  2 / 4  {slide}  margins01.md   # Not fragmented  3 / 4  {fragment}  margins01.md   # Not fragmented  1. This is  3 / 4  {fragment}  margins01.md   # Not fragmented  1. This is  2. An ordered list  3 / 4  {fragment}  margins01.md   # Not fragmented  1. This is  2. An ordered list  Hahaha!  3 / 4  {fragment}  margins01.md   # Not fragmented  1. This is  2. An ordered list  Hahaha!  - Here is  3 / 4  {fragment}  margins01.md   # Not fragmented  1. This is  2. An ordered list  Hahaha!  - Here is  - A second list  3 / 4  {slide}  margins01.md   # Extra stuff at end  4 / 4  {fragment}  margins01.md   # Extra stuff at end  1. This is  4 / 4  {fragment}  margins01.md   # Extra stuff at end  1. This is  2. An ordered list  4 / 4  {fragment}  margins01.md   # Extra stuff at end  1. This is  2. An ordered list  Hahaha!  4 / 4  {fragment}  margins01.md   # Extra stuff at end  1. This is  2. An ordered list  Hahaha!  - Here is  4 / 4  {fragment}  margins01.md   # Extra stuff at end  1. This is  2. An ordered list  Hahaha!  - Here is  - A second list  4 / 4  {fragment}  margins01.md   # Extra stuff at end  1. This is  2. An ordered list  Hahaha!  - Here is  - A second list  Extra stuff  4 / 4  patat-0.14.1.0/tests/golden/outputs/meta.md.dump000066400000000000000000000005671475634243500214420ustar00rootroot00000000000000 meta.md   < Hello  < World  > How  > Are  > You  > Doing  1 / 1  patat-0.14.1.0/tests/golden/outputs/plain.txt.dump000066400000000000000000000005021475634243500220230ustar00rootroot00000000000000 plain.txt  This is some _plain text_. It should not have any formatting applied whatsoever. --- Slide splitting should not work either.  1 / 1  patat-0.14.1.0/tests/golden/outputs/slide-config.md.dump000066400000000000000000000017521475634243500230540ustar00rootroot00000000000000 slide-config.md  # This is a test This slide has higher margin at the top.  1 / 3  {slide} {speakerNotes: config: This is not a config, but a speaker comment. The whitespace matters... }  slide-config.md  # This is a test This slide has normal margin at the top.  2 / 3  {slide}  slide-config.md  # Different color header This slide has a different theme set. It also has no slide number. The two config blocks should be merged.   patat-0.14.1.0/tests/golden/outputs/slidelevel0.md.dump000066400000000000000000000004561475634243500227210ustar00rootroot00000000000000 slidelevel0.md  # We should not split slides Never # At all Because we have  slideLevel  set to 0  1 / 1  patat-0.14.1.0/tests/golden/outputs/slidelevel1.md.dump000066400000000000000000000013171475634243500227170ustar00rootroot00000000000000 slidelevel1.md  # This starts a new slide ## But this does not Here is some content ## And another header And more content (yep)  1 / 2  {slide}  slidelevel1.md  # This should start a new slide ## With some content ### Very deeply nested #### Is a hidden message ##### A dark secret... jet fuel can't melt steel beams  2 / 2  patat-0.14.1.0/tests/golden/outputs/slidelevel2.md.dump000066400000000000000000000022551475634243500227220ustar00rootroot00000000000000 slidelevel2.md   # This is a title  1 / 5  {slide}  slidelevel2.md > This is a title  ## This is a slide Here is some content  2 / 5  {slide}  slidelevel2.md > This is a title  ## And another slide And more content (yep)  3 / 5  {slide}  slidelevel2.md   # This is another title  4 / 5  {slide}  slidelevel2.md > This is another title  ## With some content Yay  5 / 5  patat-0.14.1.0/tests/golden/outputs/slidelevel3.md.dump000066400000000000000000000027251475634243500227250ustar00rootroot00000000000000 slidelevel3.md   # This is a title  1 / 6  {slide}  slidelevel3.md > This is a title   ## This is a subtitle  2 / 6  {slide}  slidelevel3.md > This is a title > This is a subtitle  ### This is a slide Here is some content  3 / 6  {slide}  slidelevel3.md   # This is another title  4 / 6  {slide}  slidelevel3.md > This is another title   ## This is another subtitle  5 / 6  {slide}  slidelevel3.md > This is another title > This is another subtitle  ### This is another slide Here is some more content  6 / 6  patat-0.14.1.0/tests/golden/outputs/slidenumber.md.dump000066400000000000000000000006601475634243500230170ustar00rootroot00000000000000 slidenumber.md  This slide will have no number   {slide}  slidenumber.md  This slide will have no number as well   patat-0.14.1.0/tests/golden/outputs/syntax-definitions.md.dump000066400000000000000000000011261475634243500243430ustar00rootroot00000000000000 syntax-definitions.md  Hello      iterate f 0 x = x    iterate f n x = iterate f (n - 1) (f x)      1 / 1  patat-0.14.1.0/tests/golden/outputs/syntax.md.dump000066400000000000000000000014201475634243500220270ustar00rootroot00000000000000 syntax.md  Some simple code:      int main(int argc, char **argv) {    return 0;    }      1 / 1  patat-0.14.1.0/tests/golden/outputs/tables.md.dump000066400000000000000000000053061475634243500217620ustar00rootroot00000000000000 tables.md  # Normal simple table  Right Left Center Default  ----- ---- ------ -------  12 12 12 12   123 123 123 123   1 1 1 1   Table: Demonstration of simple table syntax.  1 / 4  {slide}  tables.md  # Headerless table  --- --- --- ---  12 12 12 12  123 123 123 123  1 1 1 1   --- --- --- ---  2 / 4  {slide}  tables.md  # Multiline  Centered Default  Right Left    Header  Aligned Aligned Aligned   -------- ------- ------- ------------------------  First row 12.0 Example of a row that   spans multiple lines.   Second row 5.0 Here's another one. Note  the blank line between   rows.   Table: Here's the caption. It, too, may span  multiple lines.  3 / 4  {slide}  tables.md  # Headerless multiline  ------ --- ---- ------------------------  First row 12.0 Example of a row that   spans multiple lines.   Second row 5.0 Here's another one. Note  the blank line between   rows.   ------ --- ---- ------------------------  Table: Here's a multiline table without headers.  4 / 4  patat-0.14.1.0/tests/golden/outputs/tabstop.md.dump000066400000000000000000000027331475634243500221650ustar00rootroot00000000000000 tabstop.md       Essential Name Expenses    x Food $200    Data $150    Rent $800    x Candles $3,600    Utilities $150      1 / 3  {slide}  tabstop.md       Essential Name Expenses    x Food $200    Data $150    Rent $800    x Candles $3,600    Utilities $150      2 / 3  {slide}  tabstop.md  someone who is good at the economy please help me budget this. my family is dying  3 / 3  patat-0.14.1.0/tests/golden/outputs/themes.md.dump000066400000000000000000000006711475634243500217750ustar00rootroot00000000000000 themes.md   - This is a simple list.  + With nested items.  + One or two bold.  - The list theming is customized a bit.  1 / 1  patat-0.14.1.0/tests/golden/outputs/utf8.md.dump000066400000000000000000000040021475634243500213660ustar00rootroot00000000000000 utf8.md  Some code:      hello-exe-hello> src/hello.hs:23:35: error:    hello-exe-hello> • Couldn't match type ‘Password’ with ‘BuiltinData’    hello-exe-hello> Expected type: template-haskell-2.16.0.0:Language.Haskell.TH.Syntax.Q    hello-exe-hello> (template-haskell-2.16.0.0:Language.Haskell.TH.Syntax.TExp    hello-exe-hello> (PlutusTx.Code.CompiledCode    hello-exe-hello> (BuiltinData -> BuiltinData -> BuiltinData -> ())))    hello-exe-hello> Actual type: th-compat-0.1.4:Language.Haskell.TH.Syntax.Compat.SpliceQ    hello-exe-hello> (PlutusTx.Code.CompiledCode    hello-exe-hello> (Password -> Password -> BuiltinData -> ()))    hello-exe-hello> • In the expression: compile [|| validator ||]    hello-exe-hello> In the Template Haskell splice $$(compile [|| validator ||])    hello-exe-hello> In the first argument of ‘mkValidatorScript’, namely    hello-exe-hello> ‘$$(compile [|| validator ||])’      1 / 1  patat-0.14.1.0/tests/golden/outputs/wrap-at.md.dump000066400000000000000000000025351475634243500220640ustar00rootroot00000000000000 wrap-at.md  This is a really long sentence which could be wrapped.  - The above sentence should be  wrapped.  - This bullet list should also be  wrapped.  1 / 3  {slide}  wrap-at.md   This is a really long sentence which  could be wrapped.  - The above sentence should be  wrapped.  - This bullet list should also be  wrapped.  2 / 3  {slide}  wrap-at.md   This is a really long sentence which  could be wrapped.  - The above sentence should be  wrapped.  - This bullet list should also be  wrapped.  3 / 3  patat-0.14.1.0/tests/golden/outputs/wrapping.md.dump000066400000000000000000000017331475634243500223370ustar00rootroot00000000000000 wrapping.md  This is a long sentence over multiple lines which can be re-wrapped. This is a super long sentence over a single line which should also be re-wrapped.  This is a table and tables should not be wrapped  ------- ------- ---------- ---------- ----------  1 2 3 4 5   6 7 8 9 10   - This is a list  - This list has a really long sentence  in it which should also be wrapped  with proper indentation  - Another item This line is long, and then ends with  code   1 / 1  patat-0.14.1.0/tests/golden/spec.goldplate000066400000000000000000000004461475634243500203260ustar00rootroot00000000000000{ "command": "patat", "input_files": "inputs/*", "arguments": ["--dump", "--force", "${GOLDPLATE_INPUT_FILE}"], "environment": { "HOME": "/dev/null" }, "asserts": [ {"exit_code": 0}, {"stdout": "outputs/${GOLDPLATE_INPUT_BASENAME}.dump"} ] } patat-0.14.1.0/tests/golden/syntax-definitions/000077500000000000000000000000001475634243500213325ustar00rootroot00000000000000patat-0.14.1.0/tests/golden/syntax-definitions/impurescript.xml000066400000000000000000000014441475634243500246050ustar00rootroot00000000000000 patat-0.14.1.0/tests/haskell/000077500000000000000000000000001475634243500156465ustar00rootroot00000000000000patat-0.14.1.0/tests/haskell/Main.hs000066400000000000000000000006651475634243500170750ustar00rootroot00000000000000module Main where import qualified Patat.Presentation.Interactive.Tests import qualified Patat.Presentation.Read.Tests import qualified Patat.PrettyPrint.Matrix.Tests import qualified Test.Tasty as Tasty main :: IO () main = Tasty.defaultMain $ Tasty.testGroup "patat" [ Patat.Presentation.Interactive.Tests.tests , Patat.Presentation.Read.Tests.tests , Patat.PrettyPrint.Matrix.Tests.tests ] patat-0.14.1.0/tests/haskell/Patat/000077500000000000000000000000001475634243500167175ustar00rootroot00000000000000patat-0.14.1.0/tests/haskell/Patat/Presentation/000077500000000000000000000000001475634243500213725ustar00rootroot00000000000000patat-0.14.1.0/tests/haskell/Patat/Presentation/Interactive/000077500000000000000000000000001475634243500236475ustar00rootroot00000000000000patat-0.14.1.0/tests/haskell/Patat/Presentation/Interactive/Tests.hs000066400000000000000000000047701475634243500253150ustar00rootroot00000000000000module Patat.Presentation.Interactive.Tests ( tests ) where import Control.Monad (forM_, replicateM) import Patat.Presentation.Interactive import System.Directory (getTemporaryDirectory, removeFile) import qualified System.IO as IO import qualified Test.QuickCheck as QC import qualified Test.QuickCheck.Monadic as QC import qualified Test.Tasty as Tasty import qualified Test.Tasty.QuickCheck as Tasty tests :: Tasty.TestTree tests = Tasty.testGroup "Patat.Presentation.Interactive.Tests" [ Tasty.testProperty "testReadPresentationCommands" $ QC.monadicIO . QC.run . testReadPresentationCommands ] -- | A raw input string followed by the expected command. data ArbitraryCommand = ArbitraryCommand String PresentationCommand deriving (Show) instance QC.Arbitrary ArbitraryCommand where arbitrary = QC.oneof $ [ return $ ArbitraryCommand "q" Exit , return $ ArbitraryCommand "\n" Forward , return $ ArbitraryCommand "\DEL" Backward , return $ ArbitraryCommand "h" Backward , return $ ArbitraryCommand "j" SkipForward , return $ ArbitraryCommand "k" SkipBackward , return $ ArbitraryCommand "l" Forward , return $ ArbitraryCommand "\ESC[C" Forward , return $ ArbitraryCommand "\ESC[D" Backward , return $ ArbitraryCommand "\ESC[B" SkipForward , return $ ArbitraryCommand "\ESC[A" SkipBackward , return $ ArbitraryCommand "\ESC[6" Forward , return $ ArbitraryCommand "\ESC[5" Backward , return $ ArbitraryCommand "0" First , return $ ArbitraryCommand "G" Last , return $ ArbitraryCommand "r" Reload , do n <- QC.choose (1, 1000) return $ ArbitraryCommand (show n <> "\n") (Seek n) ] testReadPresentationCommands :: [ArbitraryCommand] -> IO Bool testReadPresentationCommands commands = do tmpdir <- getTemporaryDirectory (tmppath, h) <- IO.openBinaryTempFile tmpdir "patat.input" IO.hSetBuffering h IO.NoBuffering forM_ commands $ \(ArbitraryCommand s _) -> IO.hPutStr h s IO.hSeek h IO.AbsoluteSeek 0 parsed <- replicateM (length commands) (readPresentationCommand h) IO.hClose h removeFile tmppath return $ [expect | ArbitraryCommand _ expect <- commands] == parsed patat-0.14.1.0/tests/haskell/Patat/Presentation/Read/000077500000000000000000000000001475634243500222455ustar00rootroot00000000000000patat-0.14.1.0/tests/haskell/Patat/Presentation/Read/Tests.hs000066400000000000000000000041301475634243500237010ustar00rootroot00000000000000{-# LANGUAGE OverloadedStrings #-} module Patat.Presentation.Read.Tests ( tests ) where -------------------------------------------------------------------------------- import Patat.Presentation.Read import Patat.Presentation.Syntax import qualified Test.Tasty as Tasty import qualified Test.Tasty.HUnit as Tasty -------------------------------------------------------------------------------- tests :: Tasty.TestTree tests = Tasty.testGroup "Patat.Presentation.Read.Tests" [ testReadMetaSettings , testDetectSlideLevel ] -------------------------------------------------------------------------------- testReadMetaSettings :: Tasty.TestTree testReadMetaSettings = Tasty.testCase "readMetaSettings" $ case readMetaSettings invalidMetadata of Left _ -> pure () Right _ -> Tasty.assertFailure "expecting invalid metadata" where invalidMetadata = "---\n\ \title: mixing tabs and spaces bad\n\ \author: thoastbrot\n\ \patat:\n\ \ images:\n\ \ backend: 'w3m'\n\ \ path: '/usr/lib/w3m/w3mimgdisplay'\n\ \ theme:\n\ \\theader: [vividBlue,onDullBlack]\n\ \ emph: [dullBlue,italic]\n\ \...\n\ \\n\ \Hi!" -------------------------------------------------------------------------------- testDetectSlideLevel :: Tasty.TestTree testDetectSlideLevel = Tasty.testGroup "detectSlideLevel" [ Tasty.testCase "01" $ (Tasty.@=?) 1 $ detectSlideLevel [ Header 1 mempty [Str "Intro"] , Para [Str "Hi"] ] , Tasty.testCase "02" $ (Tasty.@=?) 2 $ detectSlideLevel [ Header 1 mempty [Str "Intro"] , Header 2 mempty [Str "Detail"] , Para [Str "Hi"] ] , Tasty.testCase "03" $ (Tasty.@=?) 2 $ detectSlideLevel [ Header 1 mempty [Str "Intro"] , SpeakerNote "Some speaker note" , Header 2 mempty [Str "Detail"] , Para [Str "Hi"] ] ] patat-0.14.1.0/tests/haskell/Patat/PrettyPrint/000077500000000000000000000000001475634243500212235ustar00rootroot00000000000000patat-0.14.1.0/tests/haskell/Patat/PrettyPrint/Matrix/000077500000000000000000000000001475634243500224675ustar00rootroot00000000000000patat-0.14.1.0/tests/haskell/Patat/PrettyPrint/Matrix/Tests.hs000066400000000000000000000040731475634243500241310ustar00rootroot00000000000000-------------------------------------------------------------------------------- {-# LANGUAGE OverloadedLists #-} module Patat.PrettyPrint.Matrix.Tests ( tests ) where -------------------------------------------------------------------------------- import Patat.PrettyPrint import Patat.PrettyPrint.Matrix import Patat.Size import qualified System.Console.ANSI as Ansi import qualified Test.Tasty as Tasty import qualified Test.Tasty.HUnit as Tasty import Test.Tasty.HUnit ((@?=)) -------------------------------------------------------------------------------- tests :: Tasty.TestTree tests = Tasty.testGroup "Patat.PrettyPrint.Matrix.Tests" [ testDocToMatrix ] -------------------------------------------------------------------------------- testDocToMatrix :: Tasty.TestTree testDocToMatrix = Tasty.testGroup "docToMatrix" [ Tasty.testCase "wcwidth" $ docToMatrix (Size 2 10) (string "wcwidth:" <$$> ansi [green] (string "コンニチハ")) @?= [ c 'w', c 'c', c 'w', c 'i', c 'd', c 't', c 'h', c ':', e, e , cg 'コ', e, cg 'ン', e, cg 'ニ', e, cg 'チ', e, cg 'ハ', e ] , Tasty.testCase "wrap" $ docToMatrix (Size 4 4) (string "fits" <$$> string "wrapped" <$$> string "fits") @?= [ c 'f', c 'i', c 't', c 's' , c 'w', c 'r', c 'a', c 'p' , c 'p', c 'e', c 'd', e , c 'f', c 'i', c 't', c 's' ] , Tasty.testCase "overflow" $ docToMatrix (Size 3 3) (string "overflowed") @?= [ c 'o', c 'v', c 'e' , c 'r', c 'f', c 'l' , c 'o', c 'w', c 'e' ] , Tasty.testCase "wrap in middle of wide character" $ docToMatrix (Size 2 5) (string "溢れる") @?= [ c '溢', e, c 'れ', e, e , c 'る', e, e , e, e ] ] where green = Ansi.SetColor Ansi.Foreground Ansi.Vivid Ansi.Green c = Cell [] cg = Cell [green] e = emptyCell