diagrams-pgf (empty) → 0.1.0.0
raw patch · 11 files changed
+2359/−0 lines, 11 filesdep +JuicyPixelsdep +basedep +bytestringsetup-changed
Dependencies added: JuicyPixels, base, bytestring, colour, containers, diagrams-core, diagrams-lib, directory, filepath, hashable, mtl, optparse-applicative, process, split, texrunner, time, vector, zlib
Files
- LICENSE +30/−0
- README.md +105/−0
- Setup.hs +3/−0
- diagrams-pgf.cabal +56/−0
- diagrams/hbox.svg +98/−0
- src/Diagrams/Backend/PGF.hs +223/−0
- src/Diagrams/Backend/PGF/CmdLine.hs +274/−0
- src/Diagrams/Backend/PGF/Hbox.hs +106/−0
- src/Diagrams/Backend/PGF/Render.hs +236/−0
- src/Diagrams/Backend/PGF/Surface.hs +301/−0
- src/Graphics/Rendering/PGF.hs +927/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2014, Christopher Chalmers++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Christopher Chalmers nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,105 @@+## PGF diagrams backend++`diagrams-pgf` is a [PGF] backend for [diagrams]. Diagrams is a powerful, flexible, declarative domain-specific language for creating vector graphics, using the [Haskell programming language][haskell].++[PGF]: http://sourceforge.net/projects/pgf/+[diagrams]: http://projects.haskell.org/diagrams/+[haskell]: http://www.haskell.org/haskellwiki/Haskell++PGF is a TeX macro package for generating graphics. It is platform- and format-independent and works together with the most important TeX backend drivers, including pdftex and dvips.++`diagrams-pgf` is a work in progress, it supports the basic features of diagrams with the following features:++- LaTeX, ConTeXt and plain TeX support+- direct PDF generation using a TeX distribution (e.g. [texlive](https://www.tug.org/texlive/)) via [texrunner].++### Usage++A simple example that uses diagrams-pgf to draw a square.++```haskell+import Diagrams.Prelude+import Diagrams.Backend.PGF.CmdLine++b1 = square 20 # lwG 0.05++main = defaultMain (pad 1.1 b1)+```++Save this to file named `square.hs` and compile this program:++```+ghc --make square.hs+```++This will generate an executable which, when run produces a TeX (or PDF) file. Run the+executable with the `--help` option to find out more about how to call it.++```+$ ./square --help+square++Usage: square [-?|--help] [-w|--width WIDTH] [-h|--height HEIGHT]+ [-o|--output OUTPUT] [-f|--format FORMAT] [-a|--standalone]+ [-r|--readable] [-l|--loop] [-s|--src ARG]+ [-i|--interval INTERVAL]+ Command-line diagram generation.++Available options:+ -?,--help Show this help text+ -w,--width WIDTH Desired WIDTH of the output image+ -h,--height HEIGHT Desired HEIGHT of the output image+ -o,--output OUTPUT OUTPUT file+ -f,--format FORMAT l for LaTeX, c for ConTeXt, p for plain+ TeX (default: LaTeX)+ -a,--standalone Produce standalone .tex output+ -r,--readable Indent lines+ -l,--loop Run in a self-recompiling loop+ -s,--src ARG Source file to watch+ -i,--interval INTERVAL When running in a loop, check for changes every+ INTERVAL seconds.+```++If no output file is given, output is send to `stdout`. Supported outputs are `.tex` and `.pdf`. PDF generation is done using [texrunner].++[texrunner]: http://www.github.com/cchalmers/texrunner++```+$ ./Square -o square.tex+$ cat ./square.tex+\begin{pgfpicture}+ \pgfpathrectangle{\pgfpointorigin}{\pgfqpoint{22.0000px}{22.0000px}}+ \pgfusepath{use as bounding box}+ \begin{pgfscope}+ \begin{pgfscope}+ \pgfpathmoveto{\pgfqpoint{21.0000px}{1.0000px}}+ \pgfpathlineto{\pgfqpoint{21.0000px}{21.0000px}}+ \pgfpathlineto{\pgfqpoint{1.0000px}{21.0000px}}+ \pgfpathlineto{\pgfqpoint{1.0000px}{1.0000px}}+ \pgfpathlineto{\pgfqpoint{21.0000px}{1.0000px}}+ \pgfpathclose+ \definecolor{sc}{rgb}{0.0000,0.0000,0.0000}+ \pgfsetstrokecolor{sc}+ \pgfsetlinewidth{0.0132mm}+ \pgfusepath{stroke}+ \end{pgfscope}+ \end{pgfscope}+\end{pgfpicture}+```++### Typesetting++`pgf-diagrams` allows typesetting TeX commands and can calculate the corresponding envelope. See the [hbox example] to see how get envelopes of text:++[hbox example]: examples/hbox.hs+++++### Missing features++The following features are not currently supported:++- selecting fonts (italic and bold work)+- gradients+
+ Setup.hs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+import Distribution.Simple+main = defaultMain
+ diagrams-pgf.cabal view
@@ -0,0 +1,56 @@+name: diagrams-pgf+version: 0.1.0.0+synopsis: PGF backend for diagrams drawing EDSL.+license: BSD3+license-file: LICENSE+author: Christopher Chalmers+maintainer: c.chalmers@me.com+homepage: http://github.com/cchalmers/diagrams-pgf+bug-reports: http://github.com/cchalmers/diagrams-pgf/issues+stability: Experimental+category: Graphics+build-type: Simple+cabal-version: >=1.10+extra-source-files: README.md, diagrams/*.svg+extra-doc-files: diagrams/*.svg+description:+ This package provides a modular backend for rendering diagrams created+ with the diagrams EDSL using the TeX library PGF.+ .+ Support for rendering LaTeX, ConTeXt and plain TeX files.+source-repository head+ type: git+ location: http://github.com/cchalmers/diagrams-pgf++library+ exposed-modules:+ Diagrams.Backend.PGF+ Diagrams.Backend.PGF.CmdLine+ Diagrams.Backend.PGF.Surface+ Diagrams.Backend.PGF.Render+ Diagrams.Backend.PGF.Hbox+ Graphics.Rendering.PGF+ hs-source-dirs: src+ build-depends:+ base >= 4.4 && < 4.9,+ bytestring >= 0.9 && < 1.1,+ containers >= 0.3 && < 0.6,+ colour,+ diagrams-core >= 1.3 && < 1.4,+ diagrams-lib >= 1.3 && < 1.4,+ directory >= 1.0 && < 1.4,+ filepath >= 1.2 && < 1.5,+ hashable >= 1.1 && < 1.3,+ mtl >= 2.1 && < 3.0,+ optparse-applicative >= 0.11 && < 0.12,+ process >= 1.0 && < 1.3,+ split >= 0.1.2 && < 0.3,+ texrunner <= 0.0.2,+ time >= 1.2 && < 1.6,+ JuicyPixels,+ vector,+ zlib++ ghc-options: -Wall+ default-language: Haskell2010+
+ diagrams/hbox.svg view
@@ -0,0 +1,98 @@+<?xml version="1.0" encoding="UTF-8"?>+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400pt" height="197.048pt" viewBox="0 0 400 197.048" version="1.1">+<defs>+<g>+<symbol overflow="visible" id="glyph0-0">+<path style="stroke:none;" d=""/>+</symbol>+<symbol overflow="visible" id="glyph0-1">+<path style="stroke:none;" d="M 10.796875 -5.859375 C 11.125 -6.734375 11.65625 -6.96875 12.265625 -6.984375 L 12.265625 -7.515625 C 11.890625 -7.484375 11.375 -7.46875 11 -7.46875 C 10.484375 -7.46875 9.703125 -7.5 9.375 -7.515625 L 9.375 -6.984375 C 10 -6.96875 10.390625 -6.65625 10.390625 -6.140625 C 10.390625 -6.03125 10.390625 -6 10.296875 -5.78125 L 8.703125 -1.3125 L 6.984375 -6.171875 C 6.90625 -6.390625 6.890625 -6.421875 6.890625 -6.515625 C 6.890625 -6.984375 7.578125 -6.984375 7.921875 -6.984375 L 7.921875 -7.515625 C 7.421875 -7.5 6.53125 -7.46875 6.109375 -7.46875 C 5.578125 -7.46875 5.078125 -7.484375 4.5625 -7.515625 L 4.5625 -6.984375 C 5.203125 -6.984375 5.484375 -6.953125 5.65625 -6.71875 C 5.734375 -6.609375 5.9375 -6.09375 6.0625 -5.765625 L 4.5625 -1.53125 L 2.890625 -6.203125 C 2.8125 -6.40625 2.8125 -6.4375 2.8125 -6.515625 C 2.8125 -6.984375 3.484375 -6.984375 3.84375 -6.984375 L 3.84375 -7.515625 C 3.3125 -7.5 2.34375 -7.46875 1.9375 -7.46875 C 1.875 -7.46875 0.9375 -7.484375 0.3125 -7.515625 L 0.3125 -6.984375 C 1.1875 -6.984375 1.390625 -6.921875 1.609375 -6.375 L 3.8125 -0.1875 C 3.890625 0.046875 3.9375 0.1875 4.171875 0.1875 C 4.390625 0.1875 4.4375 0.09375 4.515625 -0.15625 L 6.28125 -5.09375 L 8.0625 -0.140625 C 8.140625 0.046875 8.1875 0.1875 8.40625 0.1875 C 8.640625 0.1875 8.6875 0.03125 8.765625 -0.140625 Z "/>+</symbol>+<symbol overflow="visible" id="glyph0-2">+<path style="stroke:none;" d="M 3.09375 -7.71875 L 0.640625 -7.515625 L 0.640625 -6.984375 C 1.78125 -6.984375 1.9375 -6.875 1.9375 -6.015625 L 1.9375 -1.328125 C 1.9375 -0.546875 1.75 -0.546875 0.578125 -0.546875 L 0.578125 0 C 1.140625 -0.015625 2.078125 -0.046875 2.5 -0.046875 C 3.109375 -0.046875 3.71875 -0.015625 4.3125 0 L 4.3125 -0.546875 C 3.15625 -0.546875 3.09375 -0.625 3.09375 -1.3125 Z M 3.15625 -10.75 C 3.15625 -11.3125 2.71875 -11.671875 2.234375 -11.671875 C 1.6875 -11.671875 1.3125 -11.203125 1.3125 -10.75 C 1.3125 -10.28125 1.6875 -9.828125 2.234375 -9.828125 C 2.71875 -9.828125 3.15625 -10.1875 3.15625 -10.75 Z "/>+</symbol>+<symbol overflow="visible" id="glyph0-3">+<path style="stroke:none;" d="M 6.625 -0.953125 L 6.625 0.1875 L 9.203125 0 L 9.203125 -0.546875 C 7.96875 -0.546875 7.84375 -0.65625 7.84375 -1.515625 L 7.84375 -12.109375 L 5.328125 -11.921875 L 5.328125 -11.375 C 6.546875 -11.375 6.6875 -11.25 6.6875 -10.40625 L 6.6875 -6.625 C 6.171875 -7.265625 5.421875 -7.71875 4.484375 -7.71875 C 2.421875 -7.71875 0.59375 -6 0.59375 -3.75 C 0.59375 -1.53125 2.296875 0.1875 4.296875 0.1875 C 5.40625 0.1875 6.203125 -0.40625 6.625 -0.953125 Z M 6.625 -5.640625 L 6.625 -2.0625 C 6.625 -1.75 6.625 -1.703125 6.4375 -1.40625 C 5.921875 -0.578125 5.125 -0.1875 4.375 -0.1875 C 3.59375 -0.1875 2.96875 -0.640625 2.546875 -1.3125 C 2.09375 -2.03125 2.046875 -3.015625 2.046875 -3.734375 C 2.046875 -4.375 2.078125 -5.421875 2.578125 -6.21875 C 2.953125 -6.75 3.609375 -7.328125 4.5625 -7.328125 C 5.171875 -7.328125 5.90625 -7.0625 6.4375 -6.28125 C 6.625 -5.984375 6.625 -5.953125 6.625 -5.640625 Z "/>+</symbol>+<symbol overflow="visible" id="glyph0-4">+<path style="stroke:none;" d="M 3.015625 -6.984375 L 5.515625 -6.984375 L 5.515625 -7.515625 L 3.015625 -7.515625 L 3.015625 -10.734375 L 2.578125 -10.734375 C 2.5625 -9.296875 2.046875 -7.4375 0.328125 -7.359375 L 0.328125 -6.984375 L 1.8125 -6.984375 L 1.8125 -2.171875 C 1.8125 -0.015625 3.4375 0.1875 4.0625 0.1875 C 5.3125 0.1875 5.796875 -1.046875 5.796875 -2.171875 L 5.796875 -3.15625 L 5.359375 -3.15625 L 5.359375 -2.203125 C 5.359375 -0.90625 4.828125 -0.25 4.1875 -0.25 C 3.015625 -0.25 3.015625 -1.828125 3.015625 -2.125 Z "/>+</symbol>+<symbol overflow="visible" id="glyph0-5">+<path style="stroke:none;" d="M 1.921875 -1.328125 C 1.921875 -0.546875 1.734375 -0.546875 0.5625 -0.546875 L 0.5625 0 C 1.171875 -0.015625 2.0625 -0.046875 2.53125 -0.046875 C 2.984375 -0.046875 3.890625 -0.015625 4.484375 0 L 4.484375 -0.546875 C 3.3125 -0.546875 3.125 -0.546875 3.125 -1.328125 L 3.125 -4.53125 C 3.125 -6.359375 4.359375 -7.328125 5.484375 -7.328125 C 6.578125 -7.328125 6.765625 -6.390625 6.765625 -5.390625 L 6.765625 -1.328125 C 6.765625 -0.546875 6.578125 -0.546875 5.40625 -0.546875 L 5.40625 0 C 6.015625 -0.015625 6.90625 -0.046875 7.375 -0.046875 C 7.84375 -0.046875 8.75 -0.015625 9.34375 0 L 9.34375 -0.546875 C 8.4375 -0.546875 8 -0.546875 7.96875 -1.0625 L 7.96875 -4.390625 C 7.96875 -5.90625 7.96875 -6.4375 7.4375 -7.0625 C 7.1875 -7.359375 6.609375 -7.71875 5.609375 -7.71875 C 4.140625 -7.71875 3.375 -6.671875 3.078125 -6 L 3.078125 -12.109375 L 0.5625 -11.921875 L 0.5625 -11.375 C 1.78125 -11.375 1.921875 -11.25 1.921875 -10.40625 Z "/>+</symbol>+<symbol overflow="visible" id="glyph0-6">+<path style="stroke:none;" d="M 1.953125 -4.390625 C 2.0625 -7 3.53125 -7.4375 4.125 -7.4375 C 5.921875 -7.4375 6.09375 -5.078125 6.09375 -4.390625 Z M 1.9375 -4.03125 L 6.8125 -4.03125 C 7.1875 -4.03125 7.25 -4.03125 7.25 -4.390625 C 7.25 -6.125 6.296875 -7.8125 4.125 -7.8125 C 2.09375 -7.8125 0.484375 -6.015625 0.484375 -3.84375 C 0.484375 -1.5 2.328125 0.1875 4.328125 0.1875 C 6.453125 0.1875 7.25 -1.75 7.25 -2.078125 C 7.25 -2.25 7.109375 -2.28125 7.015625 -2.28125 C 6.859375 -2.28125 6.828125 -2.1875 6.796875 -2.046875 C 6.171875 -0.25 4.609375 -0.25 4.4375 -0.25 C 3.5625 -0.25 2.859375 -0.765625 2.453125 -1.40625 C 1.9375 -2.25 1.9375 -3.40625 1.9375 -4.03125 Z "/>+</symbol>+<symbol overflow="visible" id="glyph0-7">+<path style="stroke:none;" d="M 3 -6.5625 L 3 -7.71875 L 0.484375 -7.515625 L 0.484375 -6.984375 C 1.734375 -6.984375 1.84375 -6.875 1.84375 -6.109375 L 1.84375 2.0625 C 1.84375 2.84375 1.65625 2.84375 0.484375 2.84375 L 0.484375 3.390625 C 1.078125 3.375 1.984375 3.328125 2.4375 3.328125 C 2.921875 3.328125 3.8125 3.375 4.421875 3.390625 L 4.421875 2.84375 C 3.25 2.84375 3.046875 2.84375 3.046875 2.0625 L 3.046875 -1.03125 C 3.140625 -0.75 3.875 0.1875 5.203125 0.1875 C 7.28125 0.1875 9.09375 -1.515625 9.09375 -3.765625 C 9.09375 -5.984375 7.40625 -7.71875 5.453125 -7.71875 C 4.078125 -7.71875 3.34375 -6.953125 3 -6.5625 Z M 3.046875 -1.984375 L 3.046875 -5.875 C 3.5625 -6.765625 4.421875 -7.28125 5.3125 -7.28125 C 6.578125 -7.28125 7.640625 -5.734375 7.640625 -3.765625 C 7.640625 -1.65625 6.421875 -0.1875 5.125 -0.1875 C 4.4375 -0.1875 3.765625 -0.546875 3.296875 -1.25 C 3.046875 -1.625 3.046875 -1.640625 3.046875 -1.984375 Z "/>+</symbol>+<symbol overflow="visible" id="glyph0-8">+<path style="stroke:none;" d="M 3.875 -3 C 2.359375 -3 2.359375 -4.75 2.359375 -5.15625 C 2.359375 -5.625 2.375 -6.171875 2.640625 -6.609375 C 2.78125 -6.828125 3.171875 -7.3125 3.875 -7.3125 C 5.390625 -7.3125 5.390625 -5.5625 5.390625 -5.171875 C 5.390625 -4.6875 5.375 -4.140625 5.109375 -3.703125 C 4.96875 -3.484375 4.578125 -3 3.875 -3 Z M 1.84375 -2.328125 C 1.84375 -2.390625 1.84375 -2.796875 2.140625 -3.140625 C 2.828125 -2.65625 3.546875 -2.59375 3.875 -2.59375 C 5.5 -2.59375 6.703125 -3.8125 6.703125 -5.15625 C 6.703125 -5.796875 6.421875 -6.4375 5.984375 -6.84375 C 6.609375 -7.4375 7.25 -7.515625 7.5625 -7.515625 C 7.59375 -7.515625 7.671875 -7.515625 7.734375 -7.5 C 7.546875 -7.4375 7.453125 -7.25 7.453125 -7.03125 C 7.453125 -6.734375 7.671875 -6.53125 7.953125 -6.53125 C 8.140625 -6.53125 8.46875 -6.65625 8.46875 -7.046875 C 8.46875 -7.34375 8.25 -7.90625 7.578125 -7.90625 C 7.21875 -7.90625 6.453125 -7.796875 5.71875 -7.09375 C 4.984375 -7.65625 4.265625 -7.71875 3.875 -7.71875 C 2.25 -7.71875 1.046875 -6.515625 1.046875 -5.171875 C 1.046875 -4.390625 1.4375 -3.734375 1.875 -3.375 C 1.640625 -3.109375 1.328125 -2.53125 1.328125 -1.921875 C 1.328125 -1.375 1.546875 -0.71875 2.09375 -0.359375 C 1.046875 -0.0625 0.484375 0.6875 0.484375 1.375 C 0.484375 2.640625 2.21875 3.59375 4.34375 3.59375 C 6.40625 3.59375 8.21875 2.703125 8.21875 1.34375 C 8.21875 0.734375 7.96875 -0.15625 7.09375 -0.640625 C 6.15625 -1.140625 5.15625 -1.140625 4.078125 -1.140625 C 3.640625 -1.140625 2.890625 -1.140625 2.78125 -1.15625 C 2.21875 -1.21875 1.84375 -1.765625 1.84375 -2.328125 Z M 4.359375 3.1875 C 2.59375 3.1875 1.390625 2.296875 1.390625 1.375 C 1.390625 0.578125 2.0625 -0.0625 2.828125 -0.125 L 3.859375 -0.125 C 5.359375 -0.125 7.3125 -0.125 7.3125 1.375 C 7.3125 2.328125 6.078125 3.1875 4.359375 3.1875 Z "/>+</symbol>+<symbol overflow="visible" id="glyph1-0">+<path style="stroke:none;" d=""/>+</symbol>+<symbol overflow="visible" id="glyph1-1">+<path style="stroke:none;" d="M 116.265625 -118.1875 L 9.609375 -118.1875 L 6.28125 -78.90625 L 10.65625 -78.90625 C 13.09375 -107.015625 15.71875 -112.78125 42.078125 -112.78125 C 45.21875 -112.78125 49.75 -112.78125 51.5 -112.421875 C 55.171875 -111.734375 55.171875 -109.8125 55.171875 -105.796875 L 55.171875 -13.796875 C 55.171875 -7.859375 55.171875 -5.40625 36.84375 -5.40625 L 29.859375 -5.40625 L 29.859375 0 C 37.015625 -0.53125 54.8125 -0.53125 62.84375 -0.53125 C 70.875 -0.53125 88.859375 -0.53125 96.015625 0 L 96.015625 -5.40625 L 89.03125 -5.40625 C 70.703125 -5.40625 70.703125 -7.859375 70.703125 -13.796875 L 70.703125 -105.796875 C 70.703125 -109.28125 70.703125 -111.734375 73.84375 -112.421875 C 75.765625 -112.78125 80.484375 -112.78125 83.796875 -112.78125 C 110.15625 -112.78125 112.78125 -107.015625 115.21875 -78.90625 L 119.59375 -78.90625 Z "/>+</symbol>+<symbol overflow="visible" id="glyph1-2">+<path style="stroke:none;" d="M 23.75 -13.609375 C 23.75 -7.328125 23.390625 -5.40625 9.953125 -5.40625 L 5.765625 -5.40625 L 5.765625 0 L 106.5 0 L 113.828125 -45.046875 L 109.453125 -45.046875 C 105.09375 -18.15625 101.078125 -5.40625 71.046875 -5.40625 L 47.828125 -5.40625 C 39.625 -5.40625 39.28125 -6.640625 39.28125 -12.390625 L 39.28125 -59 L 54.984375 -59 C 71.921875 -59 73.84375 -53.421875 73.84375 -38.578125 L 78.21875 -38.578125 L 78.21875 -84.84375 L 73.84375 -84.84375 C 73.84375 -69.828125 71.921875 -64.421875 54.984375 -64.421875 L 39.28125 -64.421875 L 39.28125 -106.3125 C 39.28125 -112.078125 39.625 -113.296875 47.828125 -113.296875 L 70.359375 -113.296875 C 97.0625 -113.296875 101.78125 -103.703125 104.578125 -79.4375 L 108.9375 -79.4375 L 104.046875 -118.71875 L 5.765625 -118.71875 L 5.765625 -113.296875 L 9.953125 -113.296875 C 23.390625 -113.296875 23.75 -111.375 23.75 -105.09375 Z "/>+</symbol>+<symbol overflow="visible" id="glyph1-3">+<path style="stroke:none;" d="M 70.1875 -67.390625 L 94.09375 -102.484375 C 97.9375 -108.0625 103.875 -113.65625 119.40625 -113.828125 L 119.40625 -119.234375 C 112.609375 -119.0625 104.390625 -118.71875 100.03125 -118.71875 C 93.046875 -118.71875 84.671875 -118.71875 77.6875 -119.234375 L 77.6875 -113.828125 C 84.671875 -113.65625 88.515625 -109.8125 88.515625 -105.796875 C 88.515625 -104.046875 88.15625 -103.703125 86.9375 -101.78125 L 67.03125 -72.28125 L 44.515625 -105.96875 C 44.171875 -106.5 43.296875 -107.890625 43.296875 -108.59375 C 43.296875 -110.6875 47.140625 -113.65625 54.640625 -113.828125 L 54.640625 -119.234375 C 48.53125 -118.71875 35.78125 -118.71875 29.15625 -118.71875 C 23.75 -118.71875 12.921875 -118.890625 6.453125 -119.234375 L 6.453125 -113.828125 L 9.78125 -113.828125 C 19.375 -113.828125 22.6875 -112.609375 26.015625 -107.71875 L 58.140625 -59.1875 L 29.5 -16.9375 C 27.0625 -13.4375 21.828125 -5.40625 4.1875 -5.40625 L 4.1875 0 C 10.46875 -0.171875 17.8125 -0.53125 23.5625 -0.53125 C 30.03125 -0.53125 39.625 -0.53125 45.921875 0 L 45.921875 -5.40625 C 37.890625 -5.59375 34.921875 -10.296875 34.921875 -13.4375 C 34.921875 -15.015625 35.4375 -15.71875 36.65625 -17.625 L 61.453125 -54.296875 L 89.03125 -12.5625 C 89.390625 -11.875 89.90625 -11.171875 89.90625 -10.65625 C 89.90625 -8.546875 86.0625 -5.59375 78.5625 -5.40625 L 78.5625 0 C 84.671875 -0.53125 97.421875 -0.53125 104.046875 -0.53125 C 111.375 -0.53125 119.40625 -0.34375 126.75 0 L 126.75 -5.40625 L 123.421875 -5.40625 C 114.34375 -5.40625 110.6875 -6.28125 107.015625 -11.703125 Z "/>+</symbol>+</g>+</defs>+<g id="surface1">+<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">+ <use xlink:href="#glyph0-1" x="140.562" y="191.791"/>+ <use xlink:href="#glyph0-2" x="153.1705" y="191.791"/>+ <use xlink:href="#glyph0-3" x="158.02046" y="191.791"/>+ <use xlink:href="#glyph0-4" x="167.720381" y="191.791"/>+ <use xlink:href="#glyph0-5" x="174.509976" y="191.791"/>+</g>+<path style="fill:none;stroke-width:0.3985;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 4.914844 22.637844 L 320.0125 22.637844 " transform="matrix(1,0,0,-1,-0.075,197.048)"/>+<path style="fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.3985;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 4.914844 22.137844 C 4.864063 22.469875 4.864063 22.805813 4.914844 23.137844 Z " transform="matrix(1,0,0,-1,-0.075,197.048)"/>+<path style="fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.3985;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 0.00078125 22.637844 L 5.8875 25.075344 C 4.539844 23.731594 4.539844 21.548 5.8875 20.200344 Z " transform="matrix(1,0,0,-1,-0.075,197.048)"/>+<path style="fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.3985;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 324.922656 22.637844 L 319.035937 25.075344 C 320.383594 23.731594 320.383594 21.548 319.035937 20.200344 Z " transform="matrix(1,0,0,-1,-0.075,197.048)"/>+<path style="fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.3985;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 320.0125 22.137844 C 320.059375 22.469875 320.059375 22.805813 320.0125 23.137844 Z " transform="matrix(1,0,0,-1,-0.075,197.048)"/>+<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">+ <use xlink:href="#glyph0-3" x="347.627" y="142.458"/>+ <use xlink:href="#glyph0-6" x="357.326921" y="142.458"/>+ <use xlink:href="#glyph0-7" x="365.08546" y="142.458"/>+ <use xlink:href="#glyph0-4" x="374.785381" y="142.458"/>+ <use xlink:href="#glyph0-5" x="381.574976" y="142.458"/>+</g>+<path style="fill:none;stroke-width:0.3985;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 342.45 72.833156 L 342.45 45.075344 " transform="matrix(1,0,0,-1,-0.075,197.048)"/>+<path style="fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.3985;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 341.95 72.833156 C 342.278125 72.883938 342.617969 72.883938 342.95 72.833156 Z " transform="matrix(1,0,0,-1,-0.075,197.048)"/>+<path style="fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.3985;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 342.45 77.747219 L 344.8875 71.8605 C 343.539844 73.208156 341.35625 73.208156 340.008594 71.8605 Z " transform="matrix(1,0,0,-1,-0.075,197.048)"/>+<path style="fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.3985;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 342.45 40.161281 L 344.8875 46.048 C 343.539844 44.70425 341.35625 44.70425 340.008594 46.048 Z " transform="matrix(1,0,0,-1,-0.075,197.048)"/>+<path style="fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.3985;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 341.95 45.075344 C 342.278125 45.028469 342.617969 45.028469 342.95 45.075344 Z " transform="matrix(1,0,0,-1,-0.075,197.048)"/>+<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">+ <use xlink:href="#glyph0-5" x="347.627" y="64.015"/>+ <use xlink:href="#glyph0-6" x="357.326921" y="64.015"/>+ <use xlink:href="#glyph0-2" x="365.08546" y="64.015"/>+ <use xlink:href="#glyph0-8" x="369.93542" y="64.015"/>+ <use xlink:href="#glyph0-5" x="378.664651" y="64.015"/>+</g>+<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">+ <use xlink:href="#glyph0-4" x="387.875734" y="64.015"/>+</g>+<path style="fill:none;stroke-width:0.3985;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 342.45 82.661281 L 342.45 192.137844 " transform="matrix(1,0,0,-1,-0.075,197.048)"/>+<path style="fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.3985;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 342.95 82.661281 C 342.617969 82.614406 342.278125 82.614406 341.95 82.661281 Z " transform="matrix(1,0,0,-1,-0.075,197.048)"/>+<path style="fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.3985;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 342.45 77.747219 L 340.008594 83.633938 C 341.35625 82.286281 343.539844 82.286281 344.8875 83.633938 Z " transform="matrix(1,0,0,-1,-0.075,197.048)"/>+<path style="fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.3985;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 342.45 197.051906 L 340.008594 191.165188 C 341.35625 192.508938 343.539844 192.508938 344.8875 191.165188 Z " transform="matrix(1,0,0,-1,-0.075,197.048)"/>+<path style="fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.3985;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 342.95 192.137844 C 342.617969 192.184719 342.278125 192.184719 341.95 192.137844 Z " transform="matrix(1,0,0,-1,-0.075,197.048)"/>+<path style="fill:none;stroke-width:0.3985;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 0.00078125 77.747219 L 324.922656 77.747219 " transform="matrix(1,0,0,-1,-0.075,197.048)"/>+<path style="fill:none;stroke-width:0.3985;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 324.922656 40.161281 L 324.922656 197.051906 L 0.00078125 197.051906 L 0.00078125 40.161281 Z " transform="matrix(1,0,0,-1,-0.075,197.048)"/>+<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">+ <use xlink:href="#glyph1-1" x="-0.075" y="119.301"/>+</g>+<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">+ <use xlink:href="#glyph1-2" x="96.92034" y="156.88998"/>+</g>+<g style="fill:rgb(0%,0%,0%);fill-opacity:1;">+ <use xlink:href="#glyph1-3" x="193.91568" y="119.301"/>+</g>+</g>+</svg>
+ src/Diagrams/Backend/PGF.hs view
@@ -0,0 +1,223 @@+{-# LANGUAGE TypeFamilies #-}++-----------------------------------------------------------------------------+-- |+-- Module : Diagrams.Backend.PGF+-- Copyright : (c) 2015 Christopher Chalmers+-- License : BSD-style (see LICENSE)+-- Maintainer : diagrams-discuss@googlegroups.com+--+-- A full-featured PGF backend for diagrams producing PGF code+-- suitable for LaTeX, ConTeXt or plain TeX consumption.+--+-- To invoke the PGF backend, you have a number of options.+--+-- * You can use 'renderPGF' or 'renderPGF'' to render a 'Diagram' to a+-- ".tex" or ".pdf" file;+--+-- * You can use the most flexible 'renderDia' function to access the+-- resulting PGF code directly in memory as a bytestring 'Builder'.+--+-- * Use 'Diagrams.Backend.PGF.CmdLine.mainWith' or+-- 'Diagrams.Backend.PGF.CmdLine.defaultMain' to make a command line+-- application. See 'Diagrams.Backend.PGF.CmdLine' for more info.+--+-- The 'Surface' provides the necessary information for rendering PGF code and+-- building a PDF using "texrunner". See 'Diagrams.Backend.PGF.Surface'+-- for more info.+--++module Diagrams.Backend.PGF+ ( -- * Rendering token & options+ PGF (..)+ , B++ -- * Rendering functions+ , renderPGF+ , renderPGF'+ , renderPGFSurf++ -- * Options+ -- | Options for changing how the diagram is rendered. 'Options'+ -- 'PGF' is an instance of 'Default':+ --+ -- @+ -- def = PGFOptions {+ -- _surface = 'latexSurface'+ -- _sizeSpec = 'absolute'+ -- _readable = 'True'+ -- _standalone = 'False'+ -- }+ -- @+ --+ -- You can edit the default options using lenses.+ , readable+ , sizeSpec+ , surface+ , standalone++ -- * Surfaces+ -- | These surfaces should be suitable for basic diagrams. For more+ -- complicated options see 'Diagrams.Backend.PGF.Surface'.+ , Surface+ , surfOnlineTex++ -- ** Predefined surfaces+ , latexSurface+ , contextSurface+ , plaintexSurface++ -- ** Lenses+ , command+ , arguments+ , preamble++ -- * Online TeX+ -- | By using 'OnlineTex', diagrams is able to query tex for sizes+ -- of hboxs and give them the corresponding envelopes. These can+ -- then be used as any other diagram with the correct size.+ --+ -- Online diagrams use the 'Surface' to run tex in online mode and+ -- get feedback for hbox sizes. To run it you can use+ -- 'renderOnlinePGF', 'renderOnlinePGF'' or 'onlineMain' from+ -- 'Diagrams.Backend.PGF.CmdLine'.+ --+ -- See+ -- <https://github.com/diagrams/diagrams-pgf/tree/master/examples>+ -- for examples.+ , OnlineTex+ , renderOnlinePGF+ , renderOnlinePGF'++ -- ** Hbox+ , Hbox+ , hboxOnline+ , hboxPoint+ , hboxSurf+ ) where++import Data.ByteString.Builder+import System.Directory hiding (readable)+import System.FilePath+import System.IO+import System.Texrunner+import System.Texrunner.Online hiding (hbox)++import qualified Data.ByteString.Char8 as B+import qualified Data.ByteString.Lazy.Char8 as LB++import Diagrams.Backend.PGF.Hbox+import Diagrams.Backend.PGF.Render+import Diagrams.Backend.PGF.Surface+import Diagrams.Size+import Diagrams.Prelude hiding (r2)++type B = PGF++type instance V PGF = V2+type instance N PGF = Double++-- | Render a pgf diagram and write it to the given filepath. Same as+-- 'renderPGF'' but uses the default options.+renderPGF :: (TypeableFloat n, Monoid' m)+ => FilePath -- ^ path to output+ -> SizeSpec V2 n -- ^ size of output+ -> QDiagram PGF V2 n m -- ^ 'Diagram' to render+ -> IO ()+renderPGF outFile sizeSp = renderPGFSurf outFile sizeSp def++-- | Render a pgf diagram and write it to the given filepath. Same as+-- 'renderPGF' but takes a 'Surface'.+renderPGFSurf :: (TypeableFloat n, Monoid' m)+ => FilePath -- ^ path to output+ -> SizeSpec V2 n -- ^ size of output+ -> Surface -- ^ surface to render with+ -> QDiagram PGF V2 n m -- ^ diagram to render+ -> IO ()+renderPGFSurf outFile sizeSp surf =+ renderPGF' outFile $+ def & sizeSpec .~ sizeSp+ & surface .~ surf++-- | Render a pgf diagram and write it to the given filepath. If the file has+-- the extension @.pdf@, a PDF is generated in a temporary directory using+-- options from the given surface, otherwise, the tex output is saved+-- using the surface's 'TexFormat'.+renderPGF' :: (TypeableFloat n, Monoid' m)+ => FilePath -> Options PGF V2 n -> QDiagram PGF V2 n m -> IO ()+renderPGF' outFile opts d = case takeExtension outFile of+ ".pdf" -> do+ let rendered = renderDia PGF (opts & standalone .~ True & readable .~ False) d++ currentDir <- getCurrentDirectory+ targetDir <- canonicalizePath (takeDirectory outFile)++ let source = toLazyByteString rendered++ (_, texLog, mPDF) <- runTex (opts^.surface.command)+ (opts^.surface.arguments)+ [currentDir, targetDir]+ source++ case mPDF of+ Nothing -> putStrLn "Error, no PDF found:"+ >> B.putStrLn (prettyPrintLog texLog)+ Just pdf -> LB.writeFile outFile pdf++ -- tex output+ _ -> writeTexFile outFile opts d++-- | Render an online 'PGF' diagram and save it. Same as+-- 'renderOnlinePGF'' using default options.+renderOnlinePGF :: (TypeableFloat n, Monoid' m)+ => FilePath+ -> SizeSpec V2 n+ -> OnlineTex (QDiagram PGF V2 n m)+ -> IO ()+renderOnlinePGF outFile sizeSp = renderOnlinePGF' outFile (def & sizeSpec .~ sizeSp)++-- | Same as 'renderOnlinePDF' but takes 'Options' 'PGF'.+renderOnlinePGF' :: (TypeableFloat n, Monoid' m)+ => FilePath+ -> Options PGF V2 n+ -> OnlineTex (QDiagram PGF V2 n m)+ -> IO ()+renderOnlinePGF' outFile opts dOL = case takeExtension outFile of+ ".pdf" -> do++ ((), texLog, mPDF) <-+ runOnlineTex'+ (surf^.command)+ (surf^.arguments)+ (toByteString . stringUtf8 $ surf ^. (preamble <> beginDoc)) $ do++ d <- dOL++ -- we've already output the preamble so don't do it again+ let opts' = opts & surface %~ set beginDoc "" . set preamble ""+ & readable .~ False+ & standalone .~ True++ rendered = renderDia PGF opts' d++ texPutStrLn $ toByteString rendered++ case mPDF of+ Nothing -> putStrLn "Error, no PDF found:"+ >> print texLog+ Just pdf -> LB.writeFile outFile pdf++ -- tex output+ _ -> surfOnlineTexIO surf dOL >>= writeTexFile outFile opts+ where+ surf = opts ^. surface+ toByteString = LB.toStrict . toLazyByteString++-- | Write the rendered diagram to a text file, ignoring the extension.+writeTexFile :: (TypeableFloat n, Monoid' m)+ => FilePath -> Options PGF V2 n -> QDiagram PGF V2 n m -> IO ()+writeTexFile outFile opts d = do+ h <- openFile outFile WriteMode+ hPutBuilder h $ renderDia PGF opts d+ hClose h+
+ src/Diagrams/Backend/PGF/CmdLine.hs view
@@ -0,0 +1,274 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+-----------------------------------------------------------------------------+-- |+-- Module : Diagrams.Backend.PGF.CmdLine+-- Copyright : (c) 2015 Diagrams team (see LICENSE)+-- License : BSD-style (see LICENSE)+-- Maintainer : diagrams-discuss@googlegroups.com+--+-- Convenient creation of command-line-driven executables for+-- rendering diagrams using the PGF backend.+--+-----------------------------------------------------------------------------++module Diagrams.Backend.PGF.CmdLine+ ( -- * General form of @main@+ -- $mainwith++ mainWith++ -- * Supported forms of @main@++ , defaultMain+ , mainWithSurf+ , onlineMain+ , onlineMainWithSurf+ , multiMain++ , module Diagrams.Backend.PGF+ ) where++import Data.ByteString.Builder+import Options.Applicative as OP++import System.IO (stdout)++import Diagrams.Backend.CmdLine+import Diagrams.Prelude hiding (height, interval, output,+ width, (<>))++import Diagrams.Backend.PGF+import Diagrams.Backend.PGF.Surface++-- pgf specific stuff++data PGFCmdLineOpts = PGFCmdLineOpts+ { _cmdStandalone :: Bool+ , _cmdReadable :: Bool+ }++makeLenses ''PGFCmdLineOpts++instance Parseable PGFCmdLineOpts where+ parser = PGFCmdLineOpts+ <$> switch+ ( long "standalone"+ <> short 'a'+ <> help "Produce standalone .tex output"+ )+ <*> switch+ ( long "readable"+ <> short 'r'+ <> help "Indent lines"+ )++-- not sure if this is of any use+instance ToResult d => ToResult (OnlineTex d) where+ type Args (OnlineTex d) = (Surface, Args d)+ type ResultOf (OnlineTex d) = IO (ResultOf d)++ toResult d (surf, args) = flip toResult args <$> surfOnlineTexIO surf d++-- $mainwith+-- The 'mainWith' method unifies all of the other forms of @main@ and is+-- now the recommended way to build a command-line diagrams program. It+-- works as a direct replacement for 'defaultMain' or 'multiMain' as well+-- as allowing more general arguments. For example, given a function that+-- produces a diagram when given an @Int@ and a @'Colour' Double@, 'mainWith'+-- will produce a program that looks for additional number and color arguments.+--+-- > ... definitions ...+-- > f :: Int -> Colour Double -> Diagram PGF+-- > f i c = ...+-- >+-- > main = mainWith f+--+-- We can run this program as follows:+--+-- > $ ghc --make mydiagram+-- >+-- > # output image.tex built by `f 20 red`+-- > $ ./MyDiagram -o image.tex -w 200 20 red+++-- | This is the simplest way to render diagrams, and is intended to+-- be used like so:+--+-- > ... definitions ...+-- >+-- > main = defaultMain myDiagram+--+-- Compiling this file will result in an executable which takes+-- various command-line options for setting the size, output file,+-- and so on, and renders @myDiagram@ with the specified options.+--+-- Pass @--help@ to the generated executable to see all available+-- options. Currently it looks something like+--+-- @+-- mydiagram+--+-- Usage: mydiagram [-?|--help] [-w|--width WIDTH] [-h|--height HEIGHT]+-- [-o|--output OUTPUT] [-f|--format FORMAT] [-a|--standalone]+-- [-r|--readable] [-l|--loop] [-s|--src ARG]+-- [-i|--interval INTERVAL]+-- Command-line diagram generation.+--+-- Available options:+-- -?,--help Show this help text+-- -w,--width WIDTH Desired WIDTH of the output image+-- -h,--height HEIGHT Desired HEIGHT of the output image+-- -o,--output OUTPUT OUTPUT file+-- -f,--format FORMAT l for LaTeX, c for ConTeXt, p for plain+-- TeX (default: LaTeX)+-- -a,--standalone Produce standalone .tex output+-- -r,--readable Indent lines+-- -l,--loop Run in a self-recompiling loop+-- -s,--src ARG Source file to watch+-- -i,--interval INTERVAL When running in a loop, check for changes every+-- INTERVAL seconds.+-- @+--+-- For example, a common scenario is+--+-- @+-- $ ghc --make mydiagram+--+-- # output image.tex with a width of 400bp (and auto-determined height)+-- # (bp = big point = 1px at 72dpi)+-- $ ./mydiagram -o image.tex -w 400+-- @++defaultMain :: Diagram PGF -> IO ()+defaultMain = mainWith++-- | Allows you to pick a surface the diagram will be rendered with.+-- (This+mainWithSurf :: Surface -> Diagram PGF -> IO ()+mainWithSurf = curry mainWith++-- For online diagrams.++-- | Same as @defaultMain@ but takes an online pgf diagram.+onlineMain :: OnlineTex (Diagram PGF) -> IO ()+onlineMain = mainWith++-- | Same as @mainWithSurf@ but takes an online pgf diagram.+onlineMainWithSurf :: Surface -> OnlineTex (Diagram PGF) -> IO ()+onlineMainWithSurf = curry mainWith++-- Mainable instances++instance TypeableFloat n => Mainable (QDiagram PGF V2 n Any) where+ type MainOpts (QDiagram PGF V2 n Any) =+ (DiagramOpts, DiagramLoopOpts, PGFCmdLineOpts, TexFormat)+ mainRender (diaOpts, loopOpts, pgfOpts, format) d = do+ chooseRender diaOpts pgfOpts (formatToSurf format) d+ defaultLoopRender loopOpts++instance TypeableFloat n => Mainable (Surface, QDiagram PGF V2 n Any) where+ type MainOpts (Surface, QDiagram PGF V2 n Any) =+ (DiagramOpts, DiagramLoopOpts, PGFCmdLineOpts)+ mainRender (diaOpts, loopOpts, pgfOpts) (surf,d) = do+ chooseRender diaOpts pgfOpts surf d+ defaultLoopRender loopOpts++-- Online diagrams+instance TypeableFloat n => Mainable (OnlineTex (QDiagram PGF V2 n Any)) where+ type MainOpts (OnlineTex (QDiagram PGF V2 n Any))+ = (DiagramOpts, DiagramLoopOpts, PGFCmdLineOpts, TexFormat)+ mainRender (diaOpts, loopOpts, pgfOpts, format) d = do+ chooseOnlineRender diaOpts pgfOpts (formatToSurf format) d+ defaultLoopRender loopOpts++instance TypeableFloat n => Mainable (Surface, OnlineTex (QDiagram PGF V2 n Any)) where+ type MainOpts (Surface, OnlineTex (QDiagram PGF V2 n Any))+ = (DiagramOpts, DiagramLoopOpts, PGFCmdLineOpts)+ mainRender (diaOpts, loopOpts, pgfOpts) (surf, d) = do+ chooseOnlineRender diaOpts pgfOpts surf d+ defaultLoopRender loopOpts++formatToSurf :: TexFormat -> Surface+formatToSurf format = case format of+ LaTeX -> latexSurface+ ConTeXt -> contextSurface+ PlainTeX -> plaintexSurface++cmdLineOpts :: TypeableFloat n+ => DiagramOpts -> Surface -> PGFCmdLineOpts -> Options PGF V2 n+cmdLineOpts opts surf pgf+ = def & surface .~ surf+ & sizeSpec .~ sz+ & readable .~ pgf^.cmdReadable+ & standalone .~ pgf^.cmdStandalone+ where+ sz = fromIntegral <$> mkSizeSpec2D (opts^.width) (opts^.height)++chooseRender :: TypeableFloat n+ => DiagramOpts -> PGFCmdLineOpts -> Surface -> QDiagram PGF V2 n Any -> IO ()+chooseRender diaOpts pgfOpts surf d =+ case diaOpts^.output of+ "" -> hPutBuilder stdout $ renderDia PGF opts d+ out -> renderPGF' out opts d+ where+ opts = cmdLineOpts diaOpts surf pgfOpts++chooseOnlineRender :: TypeableFloat n+ => DiagramOpts -> PGFCmdLineOpts -> Surface -> OnlineTex (QDiagram PGF V2 n Any) -> IO ()+chooseOnlineRender diaOpts pgfOpts surf d =+ case diaOpts^.output of+ "" -> surfOnlineTexIO surf d >>= hPutBuilder stdout . renderDia PGF opts+ out -> renderOnlinePGF' out opts d+ where+ opts = cmdLineOpts diaOpts surf pgfOpts+++-- | @multiMain@ is like 'defaultMain', except instead of a single+-- diagram it takes a list of diagrams paired with names as input.+-- The generated executable then takes a @--selection@ option+-- specifying the name of the diagram that should be rendered. The+-- list of available diagrams may also be printed by passing the+-- option @--list@.+--+-- Example usage:+--+-- @+-- $ ghc --make MultiTest+-- [1 of 1] Compiling Main ( MultiTest.hs, MultiTest.o )+-- Linking MultiTest ...+-- $ ./MultiTest --list+-- Available diagrams:+-- foo bar+-- $ ./MultiTest --selection bar -o Bar.tex -w 200+-- @++multiMain :: [(String, Diagram PGF)] -> IO ()+multiMain = mainWith++instance TypeableFloat n => Mainable [(String,QDiagram PGF V2 n Any)] where+ type MainOpts [(String,QDiagram PGF V2 n Any)]+ = (MainOpts (QDiagram PGF V2 n Any), DiagramMultiOpts)++ mainRender = defaultMultiMainRender++instance Parseable TexFormat where+ parser = OP.option (eitherReader parseFormat)+ $ short 'f'+ <> long "format"+ <> help "l for LaTeX, c for ConTeXt, p for plain TeX"+ <> metavar "FORMAT"+ <> OP.value LaTeX+ <> showDefault++parseFormat :: String -> Either String TexFormat+parseFormat ('l':_) = Right LaTeX+parseFormat ('c':_) = Right ConTeXt+parseFormat ('p':_) = Right PlainTeX+parseFormat ('t':_) = Right PlainTeX+parseFormat x = Left $ "Unknown format" ++ x+
+ src/Diagrams/Backend/PGF/Hbox.hs view
@@ -0,0 +1,106 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-}+-----------------------------------------------------------------------------+-- |+-- Module : Diagrams.Backend.PGF+-- Copyright : (c) 2014 Christopher Chalmers+-- License : BSD-style (see LICENSE)+-- Maintainer : diagrams-discuss@googlegroups.com+--+-- A hbox a primitive Tex box, typically used for holding text and+-- formulas but can hold anything. This module provides functions for+-- retrieving the dimensions of these boxes to give diagrams the correct+-- envelopes.+-----------------------------------------------------------------------------+module Diagrams.Backend.PGF.Hbox+ ( Hbox (..)++ -- * Enveloped diagrams+ -- | The dimensions of a hbox can be recovered by calling Tex. The+ -- resulting envelope has its origin at the baseline of the text.+ --+ -- <<diagrams/hbox.svg#width=200 hbox>>+ , hboxOnline++ -- ** Non-Online version+ -- | These versions bypass 'OnlineTex' by just running a whole tex+ -- program just to get the size of a single hbox. This is not+ -- recommended but because it is slow, but can be convientient if+ -- you only need one or two hbox sizes.+ , hboxSurf+ , hboxSurfIO++ -- * Point envelope diagrams+ , hboxPoint++ ) where++import Data.ByteString.Char8 (pack)+import Data.Monoid+import Data.Typeable+import System.IO.Unsafe+import System.Texrunner.Online hiding (hbox)+import qualified System.Texrunner.Online as Online+import System.Texrunner.Parse++import Diagrams.Core.Envelope (pointEnvelope)+import Diagrams.Prelude hiding (Box, (<>))++import Diagrams.Backend.PGF.Surface++-- | Primitive for placing raw Tex commands in a hbox.+data Hbox n = Hbox (Transformation V2 n) String+ deriving Typeable++type instance V (Hbox n) = V2+type instance N (Hbox n) = n++instance Fractional n => Transformable (Hbox n) where+ transform t (Hbox tt str) = Hbox (t <> tt) str++instance Fractional n => Renderable (Hbox n) NullBackend where+ render _ _ = mempty++-- | Raw Tex commands in a hbox with no envelope. Transformations are+-- applied normally. This primitive ignores+-- 'Diagrams.TwoD.Text.FontSize'.+hboxPoint :: (OrderedField n, Typeable n, Renderable (Hbox n) b)+ => String -> QDiagram b V2 n Any+hboxPoint raw = mkQD (Prim (Hbox mempty raw))+ (pointEnvelope origin)+ mempty+ mempty+ mempty++-- | Hbox with bounding box envelope. Note that each box requires a call to+-- Tex. For multiple boxes consider using 'onlineHbox' to get multiple boxes+-- from a single call. (uses unsafePerformIO)+hboxSurf :: (TypeableFloat n, Renderable (Hbox n) b)+ => Surface -> String -> QDiagram b V2 n Any+hboxSurf surf txt = unsafePerformIO (hboxSurfIO surf txt)+{-# NOINLINE hboxSurf #-}++-- | Hbox with bounding box envelope. Note that each box requires a call to+-- Tex. For multiple boxes consider using 'onlineHbox' to get multiple boxes+-- from a single call.+hboxSurfIO :: (TypeableFloat n, Renderable (Hbox n) b)+ => Surface -> String -> IO (QDiagram b V2 n Any)+hboxSurfIO surf txt = surfOnlineTexIO surf (hboxOnline txt)++-- | Hbox with bounding box envelope.+hboxOnline :: (TypeableFloat n, Renderable (Hbox n) b)+ => String -> OnlineTex (QDiagram b V2 n Any)+hboxOnline txt = do+ Box h d w <- Online.hbox (pack txt)++ let bb = fromCorners (P $ V2 0 (-d))+ (P $ V2 w h)++ return $ mkQD (Prim (Hbox mempty txt))+ (getEnvelope bb)+ (getTrace bb)+ mempty+ (boundingBoxQuery bb)+
+ src/Diagrams/Backend/PGF/Render.hs view
@@ -0,0 +1,236 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ViewPatterns #-}+-----------------------------------------------------------------------------+-- |+-- Module : Diagrams.Backend.PGF.Render+-- Copyright : (c) 2015 Christopher Chalmers+-- License : BSD-style (see LICENSE)+-- Maintainer : diagrams-discuss@googlegroups.com+--+-- This is an internal module exposeing internals for rendering a+-- diagram. This is for advanced use only. 'Diagrams.Backend.PGF'+-- has enought for general use.+--+module Diagrams.Backend.PGF.Render+ ( PGF (..)+ , Options (..)+ , Render (..)++ -- * Lenses+ , surface+ , sizeSpec+ , readable+ , standalone++ -- * Utilities+ , escapeString+ ) where++import Control.Monad (when)+import Data.ByteString.Builder++import qualified Data.Foldable as F (foldMap)+import Data.Functor+import Data.Hashable (Hashable (..))+import Data.Tree (Tree (Node))++import Diagrams.Core.Types+import Diagrams.Prelude hiding ((<~))++import Diagrams.Backend.PGF.Hbox (Hbox (..))+import Diagrams.Backend.PGF.Surface (Surface)+import Diagrams.TwoD.Adjust (adjustDia2D)+import Diagrams.TwoD.Path+import Diagrams.TwoD.Text (Text (..), TextAlignment (..), getFontSize,+ getFontSlant, getFontWeight)+import Data.Typeable+import qualified Graphics.Rendering.PGF as P++import Prelude++-- | This data declaration is simply used as a token to distinguish+-- this rendering engine.+data PGF = PGF+ deriving (Show, Typeable)++instance TypeableFloat n => Backend PGF V2 n where+ newtype Render PGF V2 n = R (P.Render n)+ type Result PGF V2 n = Builder+ data Options PGF V2 n = PGFOptions+ { _surface :: Surface -- ^ Surface you want to use.+ , _sizeSpec :: SizeSpec V2 n -- ^ The requested size.+ , _readable :: Bool -- ^ Indented lines for @.tex@ output.+ , _standalone :: Bool -- ^ Should @.tex@ output be standalone.+ }++ renderRTree _ ops (toRender -> R r) =+ P.renderWith (ops^.surface) (ops^.readable) (ops^.standalone) bounds r+ where+ bounds = specToSize 100 (ops^.sizeSpec)++ adjustDia = adjustDia2D sizeSpec++toRender :: TypeableFloat n => RTree PGF V2 n Annotation -> Render PGF V2 n+toRender (Node n rs) = case n of+ RPrim p -> render PGF p+ RStyle sty' -> R $ do+ sty <- P.style <<<>= sty' -- mappend old style+ clips <- use (P.style . _clip)+ clip clips r <* (P.style .= sty) -- render then revert to old style+ RAnnot (OpacityGroup x) -> R $ P.opacityGroup x r+ _ -> R r+ where R r = F.foldMap toRender rs++instance Fractional n => Default (Options PGF V2 n) where+ def = PGFOptions+ { _surface = def+ , _sizeSpec = absolute+ , _readable = True+ , _standalone = False+ }++instance Monoid (Render PGF V2 n) where+ mempty = R $ return ()+ R ra `mappend` R rb = R $ ra >> rb++-- | Lens onto the surface used to render.+surface :: Lens' (Options PGF V2 n) Surface+surface = lens _surface (\o s -> o {_surface = s})++-- | Lens onto whether a standalone TeX document should be produced.+standalone :: Lens' (Options PGF V2 n) Bool+standalone = lens _standalone (\o s -> o {_standalone = s})++-- | Lens onto the 'SizeSpec2D'.+sizeSpec :: Lens' (Options PGF V2 n) (SizeSpec V2 n)+sizeSpec = lens _sizeSpec (\o s -> o {_sizeSpec = s})++-- | Lens onto whether the lines of the TeX output are indented.+readable :: Lens' (Options PGF V2 n) Bool+readable = lens _readable (\o b -> o {_readable = b})++-- helper function to easily get options and set them+(<~) :: AttributeClass a => (b -> P.Render n) -> (a -> b) -> P.Render n+renderF <~ getF = do+ s <- uses P.style (fmap getF . getAttr)+ maybe (return ()) renderF s++infixr 2 <~++-- | Fade a colour with the opacity from the style.+fade :: Color c => c -> P.RenderM n (AlphaColour Double)+fade c = flip dissolve (toAlphaColour c) <$> use (P.style . _opacity)++-- The Path is necessary so we can clip/workout gradients.+setFillTexture :: RealFloat n => Path V2 n -> Texture n -> P.Render n+setFillTexture p t = case t of+ SC (SomeColor c) -> fade c >>= P.setFillColor+ LG g -> P.linearGradient p g+ RG g -> P.radialGradient p g++setLineTexture :: RealFloat n => Texture n -> P.Render n+setLineTexture (SC (SomeColor c)) = fade c >>= P.setLineColor+setLineTexture _ = return ()++clip :: TypeableFloat n => [Path V2 n] -> P.Render n -> P.Render n+clip paths r = go paths+ where+ go [] = r+ go (p:ps) = P.scope $ P.path p >> P.clip >> go ps++-- | Escapes some common characters in a string. Note that this does not+-- mean the string can't create an error, it mearly escapes common+-- characters.+escapeString :: String -> String+escapeString = concatMap escapeChar+ where+ escapeChar ch = case ch of+ '$' -> "\\$"+ '%' -> "\\letterpercent{}"+ '&' -> "\\&"+ '#' -> "\\#"+ '_' -> "\\_"+ '{' -> "$\\{$"+ '}' -> "$\\}$"+ '\\'-> "$\\backslash{}$"+ '~' -> "\\~{}"+ '^' -> "\\^{}"+ '[' -> "{[}"+ ']' -> "{]}"+ x -> [x]++-- Renderable instances ------------------------------------------------++instance TypeableFloat n => Renderable (Path V2 n) PGF where+ render _ path = R . P.scope $ do+ -- lines and loops are separated when stroking so we only need to+ -- check the first one+ let canFill = noneOf (_head . located) isLine path+ -- solid colours need to be filled with usePath+ doFill <- if canFill+ then do+ mFillTexture <- preuse (P.style . _fillTexture)+ case mFillTexture of+ Nothing -> return False+ Just t -> do+ setFillTexture path t+ P.setFillRule <~ getFillRule+ return (has _SC t)+ else return False+ --+ w <- use (P.style . _lineWidthU . non 0)+ let doStroke = w > 0.0001+ when doStroke $ do+ P.setLineWidth w+ setLineTexture <~ getLineTexture+ P.setLineJoin <~ getLineJoin+ P.setLineCap <~ getLineCap+ P.setDash <~ getDashing+ --+ P.path path+ P.usePath doFill doStroke++-- | Does not support full alignment. Text is not escaped.+instance TypeableFloat n => Renderable (Text n) PGF where+ render _ (Text tt txtAlign str) = R . P.scope $ do+ setFillTexture mempty <~ getFillTexture+ --+ P.applyTransform tt+ (P.applyScale . (/8)) <~ getFontSize+ -- (/8) was obtained from trail and error+ --+ P.renderText (P.setTextAlign txtAlign) $ do+ P.setFontWeight <~ getFontWeight+ P.setFontSlant <~ getFontSlant+ P.rawString str++instance TypeableFloat n => Renderable (Hbox n) PGF where+ render _ (Hbox tt str) = R . P.scope $ do+ P.applyTransform tt+ P.renderText (P.setTextAlign BaselineText) (P.rawString str)++-- | Supported: @.pdf@, @.jpg@, @.png@.+instance RealFloat n => Renderable (DImage n External) PGF where+ render _ = R . P.image++-- | Supported: 'ImageRGB8'. (Other types from 'DynamicImage' will+-- error)+instance RealFloat n => Renderable (DImage n Embedded) PGF where+ render _ = R . P.embeddedImage++------------------------------------------------------------------------+-- Hashable instances++instance Hashable n => Hashable (Options PGF V2 n) where+ hashWithSalt s (PGFOptions sf sz rd st)+ = s `hashWithSalt`+ sf `hashWithSalt`+ sz `hashWithSalt`+ rd `hashWithSalt`+ st+
+ src/Diagrams/Backend/PGF/Surface.hs view
@@ -0,0 +1,301 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE TemplateHaskell #-}+-----------------------------------------------------------------------------+-- |+-- Module : Diagrams.Backend.PGF.Surface+-- Copyright : (c) 2015 Christopher Chalmers+-- License : BSD-style (see LICENSE)+-- Maintainer : diagrams-discuss@googlegroups.com+--+-- A 'Surface' defines how a pgfpicture should be placed and compiled. Surfaces+-- are used for rendering a @.tex@ or @.pdf@ using functions from+-- 'Diagrams.Backend.PGF'.+--+-- Surfaces are also used in 'Diagrams.Backend.PGF.Hbox' for querying+-- envelopes of text.+--+-- Surfaces for Latex, Context and plain Tex are provided and reexported by+-- Diagrams.Backend.PGF. Lenses here allow these to be adjusted as required.+-----------------------------------------------------------------------------++module Diagrams.Backend.PGF.Surface+ ( -- * Surface definition+ Surface(..)+ , TexFormat(..)++ -- * Online rendering with surfaces+ , surfOnlineTex+ , surfOnlineTexIO++ -- * Predefined surfaces+ , latexSurface+ , contextSurface+ , plaintexSurface+ , sampleSurfaceOutput++ -- * Lenses+ , texFormat+ , command+ , arguments+ , pageSize+ , preamble+ , beginDoc+ , endDoc+ ) where++import Data.ByteString.Builder+import Data.Hashable (Hashable (..))+import Data.Typeable (Typeable)+import System.IO.Unsafe+import System.Texrunner.Online+++import Diagrams.Prelude+import Prelude++-- | The 'TexFormat' is used to choose the different PGF commands nessesary for+-- that format.+data TexFormat = LaTeX | ConTeXt | PlainTeX+ deriving (Show, Read, Eq, Typeable)+ -- These names are only captialised so Context doesn't conflict with+ -- lens's Context.++data Surface = Surface+ { _texFormat :: TexFormat -- ^ Format for the PGF commands+ , _command :: String -- ^ System command to be called.+ , _arguments :: [String] -- ^ Auguments for command.+ , _pageSize :: Maybe (V2 Int -> String)+ -- ^ Command to change page size from dimensions of image.+ -- (in bp)+ , _preamble :: String -- ^ Preamble for document, should import pgfcore.+ , _beginDoc :: String -- ^ Begin document.+ , _endDoc :: String -- ^ End document.+ }++makeLensesWith (lensRules & generateSignatures .~ False) ''Surface++-- | Format for the PGF commands.+texFormat :: Lens' Surface TexFormat++-- | System command to call for rendering PDFs for 'OnlineTex'.+command :: Lens' Surface String++-- | List of arguments for the 'command'.+arguments :: Lens' Surface [String]++-- | Preamble for the tex document. This should at least import+-- @pgfcore@.+preamble :: Lens' Surface String++-- | Specify the page size for the tex file.+pageSize :: Lens' Surface (Maybe (V2 Int -> String))++-- | Command to begin the document. (This normally doesn't need to+-- change)+beginDoc :: Lens' Surface String++-- | Command to end the document. (This normally doesn't need to+-- change)+endDoc :: Lens' Surface String++-- Predefined surfaces -------------------------------------------------++-- | Default surface for latex files by calling @pdflatex@.+--+-- ==== __Sample output__+--+-- @+-- 'command': pdflatex+--+-- % 'preamble'+-- \documentclass{article}+-- \usepackage{pgfcore}+-- \pagenumbering{gobble}+--+-- % 'pageSize'+-- \pdfpagewidth=100bp+-- \pdfpageheight=80bp+-- \textheight=80bp+-- \pdfhorigin=-76.6bp+-- \pdfvorigin=-52.8bp+--+-- % 'beginDoc'+-- \begin{document}+--+-- \<Latex pgf code\>+--+-- % 'endDoc'+-- \end{document}+-- @+--+latexSurface :: Surface+latexSurface = Surface+ { _texFormat = LaTeX+ , _command = "pdflatex"+ , _arguments = []+ , _pageSize = Just $ \(V2 w h) ->+ "\\pdfpagewidth=" ++ show w ++ "bp\n"+ ++ "\\pdfpageheight=" ++ show h ++ "bp\n"+ ++ "\\textheight=" ++ show h ++ "bp\n"+ ++ "\\pdfhorigin=-76.6bp\n"+ ++ "\\pdfvorigin=-52.8bp"+ , _preamble = "\\documentclass{article}\n"+ ++ "\\usepackage{pgfcore}\n"+ ++ "\\pagenumbering{gobble}"+ , _beginDoc = "\\begin{document}"+ , _endDoc = "\\end{document}"+ }++-- | Default surface for latex files by calling @pdflatex@.+--+-- ==== __Sample output__+--+-- @+-- 'command': context --pipe --once++-- % 'preamble'+-- \usemodule[pgf]+-- \setuppagenumbering[location=]+--+-- % 'pageSize'+-- \definepapersize[diagram][width=100bp,height=80bp]+-- \setuppapersize[diagram][diagram]+-- \setuplayout+-- [ topspace=0bp+-- , backspace=0bp+-- , header=0bp+-- , footer=0bp+-- , width=100bp+-- , height=80bp+-- ]+--+-- % 'beginDoc'+-- \starttext+--+-- \<Context pgf code\>+--+-- % 'endDoc'+-- \stoptext+-- @+--+contextSurface :: Surface+contextSurface = Surface+ { _texFormat = ConTeXt+ , _command = "context"+ , _arguments = ["--pipe", "--once"]+ , _pageSize = Just $ \(V2 w h) ->+ "\\definepapersize[diagram][width="++ show w ++"bp,height="++ show h ++"bp]\n"+ ++ "\\setuppapersize[diagram][diagram]\n"+ ++ "\\setuplayout\n"+ ++ " [ topspace=0bp\n"+ ++ " , backspace=0bp\n"+ ++ " , header=0bp\n"+ ++ " , footer=0bp\n"+ ++ " , width=" ++ show w ++ "bp\n"+ ++ " , height=" ++ show h ++ "bp\n"+ ++ " ]"+ , _preamble = "\\usemodule[pgf]\n" -- pgfcore doesn't work+ ++ "\\setuppagenumbering[location=]"+ , _beginDoc = "\\starttext"+ , _endDoc = "\\stoptext"+ }++-- | Default surface for latex files by calling @pdflatex@.+--+-- ==== __Sample output__+--+-- @+-- 'command': pdftex+--+-- % 'preamble'+-- \input eplain+-- \beginpackages+-- \usepackage{color}+-- \endpackages+-- \input pgfcore+-- \def\frac#1#2{{\begingroup #1\endgroup\over #2}}\nopagenumbers+--+-- % 'pageSize'+-- \pdfpagewidth=100bp+-- \pdfpageheight=80bp+-- \pdfhorigin=-20bp+-- \pdfvorigin=0bp+--+-- % 'beginDoc'+--+--+-- <PlainTex pgf code>+--+-- % 'endDoc'+-- \bye+-- @+--+plaintexSurface :: Surface+plaintexSurface = Surface+ { _texFormat = PlainTeX+ , _command = "pdftex"+ , _arguments = []+ , _pageSize = Just $ \(V2 w h) ->+ "\\pdfpagewidth=" ++ show w ++ "bp\n"+ ++ "\\pdfpageheight=" ++ show h ++ "bp\n"+ ++ "\\pdfhorigin=-20bp\n"+ ++ "\\pdfvorigin=0bp"+ , _preamble = "\\input eplain\n"+ ++ "\\beginpackages\n\\usepackage{color}\n\\endpackages\n"+ ++ "\\input pgfcore\n"+ ++ "\\def\\frac#1#2{{\\begingroup #1\\endgroup\\over #2}}"+ ++ "\\nopagenumbers"+ , _beginDoc = ""+ , _endDoc = "\\bye"+ }++-- | Latex is the default surface.+instance Default Surface where+ def = latexSurface++sampleSurfaceOutput :: Surface -> String+sampleSurfaceOutput surf = unlines+ [ "command: " ++ surf ^. command ++ " " ++ unwords (surf ^. arguments)+ , "\n% preamble"+ , surf ^. preamble+ , "\n% pageSize"+ , view _Just $ surf ^. pageSize <*> Just (V2 100 80)+ , "\n% beginDoc"+ , surf ^. beginDoc+ , "\n<" ++ show (surf ^. texFormat) ++ " pgf code>"+ , "\n% endDoc"+ , surf ^. endDoc+ ]++-- OnlineTex functions -------------------------------------------------++-- | Get the result of an OnlineTex using the given surface.+surfOnlineTex :: Surface -> OnlineTex a -> a+surfOnlineTex surf a = unsafePerformIO (surfOnlineTexIO surf a)+{-# NOINLINE surfOnlineTex #-}++-- | Get the result of an OnlineTex using the given surface.+surfOnlineTexIO :: Surface -> OnlineTex a -> IO a+surfOnlineTexIO surf = runOnlineTex (surf^.command) (surf^.arguments) begin+ where+ begin = view strict . toLazyByteString . stringUtf8+ $ surf ^. (preamble <> beginDoc)++-- Hashable instances --------------------------------------------------++instance Hashable TexFormat where+ hashWithSalt s LaTeX = s `hashWithSalt` (1::Int)+ hashWithSalt s ConTeXt = s `hashWithSalt` (2::Int)+ hashWithSalt s PlainTeX = s `hashWithSalt` (3::Int)++instance Hashable Surface where+ hashWithSalt s (Surface tf cm ar ps p bd ed)+ = s `hashWithSalt`+ tf `hashWithSalt`+ cm `hashWithSalt`+ ar `hashWithSalt`+ ps <*> Just (V2 1 2) `hashWithSalt`+ p `hashWithSalt`+ bd `hashWithSalt`+ ed+
+ src/Graphics/Rendering/PGF.hs view
@@ -0,0 +1,927 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE ViewPatterns #-}+-----------------------------------------------------------------------------+-- |+-- Module : Graphics.Rendering.PGF+-- Copyright : (c) 2015 Christopher Chalmers+-- License : BSD-style (see LICENSE)+-- Maintainer : diagrams-discuss@googlegroups.com+--+-- Interface to PGF. See the manual http://www.ctan.org/pkg/pgf for details.+--+------------------------------------------------------------------------------+module Graphics.Rendering.PGF+ ( renderWith+ , RenderM+ , Render+ , initialState+ -- * Environments+ , scope+ -- , scopeHeader+ -- , resetState+ -- , scopeFooter+ , epsilon+ -- * Lenses+ -- , fillRule+ , style+ -- * units+ , bp+ , pt+ , mm+ , px+ -- * RenderM commands+ , ln+ , raw+ , rawString+ , pgf+ , bracers+ , brackets+ -- * Paths+ , path+ , trail+ , segment+ , usePath+ , lineTo+ , curveTo+ , moveTo+ , closePath+ , clip+ , stroke+ , fill+ , asBoundingBox+ -- , rectangleBoundingBox+ -- * Strokeing Options+ , setDash+ , setLineWidth+ , setLineCap+ , setLineJoin+ , setMiterLimit+ , setLineColor+ , setLineOpacity+ -- * Fill Options+ , setFillColor+ , setFillRule+ , setFillOpacity+ -- * Transformations+ , setTransform+ , applyTransform+ , baseTransform+ , applyScale+ , resetNonTranslations+ -- * Shading+ , linearGradient+ , radialGradient+ , colorSpec+ , shadePath+ , opacityGroup+ -- * images+ , image+ , embeddedImage+ , embeddedImage'+ -- * Text+ , renderText+ , setTextAlign+ , setTextRotation+ , setFontWeight+ , setFontSlant+ ) where++import Codec.Compression.Zlib+import Codec.Picture+import Control.Monad.RWS+import Data.ByteString.Builder+import Data.ByteString.Char8 (ByteString)+import qualified Data.ByteString.Char8 as B (replicate)+import Data.ByteString.Internal (fromForeignPtr)+import qualified Data.ByteString.Lazy as LB+import qualified Data.Foldable as F (foldMap)+import Data.List (intersperse)+import Data.Maybe (catMaybes)+import Data.Typeable+import qualified Data.Vector.Storable as S+import Numeric++import Diagrams.Core.Transform+import Diagrams.Prelude hiding (Render, image, moveTo,+ opacity, opacityGroup, stroke,+ (<>))+import Diagrams.TwoD.Text (FontSlant (..), FontWeight (..),+ TextAlignment (..))++import Diagrams.Backend.PGF.Surface+++-- * Types, lenses & runners++-- | Render state, mainly to be used for convenience when build, this module+-- only uses the indent properly.+data RenderState n = RenderState+ { _pos :: P2 n -- ^ Current position+ , _indent :: Int -- ^ Current indentation+ , _style :: Style V2 n+ }++makeLenses ''RenderState++data RenderInfo = RenderInfo+ { _format :: TexFormat+ , _pprint :: Bool+ }++makeLenses ''RenderInfo++-- | Type for render monad.+type RenderM n m = RWS RenderInfo Builder (RenderState n) m++-- | Convenient type for building.+type Render n = RenderM n ()++-- | Starting state for running the builder.+initialState :: (Typeable n, Floating n) => RenderState n+initialState = RenderState+ { _pos = origin+ , _indent = 0+ , _style = lc black mempty -- Until I think of something better:+ -- (square 1 # opacity 0.5) doesn't work otherwise+ }++renderWith :: (RealFloat n, Typeable n)+ => Surface -> Bool -> Bool -> V2 n -> Render n -> Builder+renderWith s readable standalone bounds r = builder+ where+ bounds' = fmap (fromInteger . floor) bounds+ (_,builder) = evalRWS r'+ (RenderInfo (s^.texFormat) readable)+ initialState+ r' = do+ when standalone $ do+ ln . rawString $ s^.preamble+ maybe (return ())+ (ln . rawString . ($ fmap ceiling bounds'))+ (s^.pageSize)+ ln . rawString $ s^.beginDoc+ picture $ rectangleBoundingBox bounds' >> r+ when standalone $ rawString $ s^.endDoc++-- low level utilities -------------------------------------------------++-- builder functions+raw :: Builder -> Render n+raw = tell+{-# INLINE raw #-}++rawByteString :: ByteString -> Render n+rawByteString = tell . byteString+{-# INLINE rawByteString #-}++rawString :: String -> Render n+rawString = tell . string8+{-# INLINE rawString #-}++pgf :: Builder -> Render n+pgf c = raw $ "\\pgf" <> c+{-# INLINE pgf #-}++rawChar :: Char -> Render n+rawChar = tell . char8+{-# INLINE rawChar #-}++-- | Emit the indentation when 'pprint' is True.+emit :: Render n+emit = do+ pp <- view pprint+ when pp $ do+ tab <- use indent+ rawByteString $ B.replicate tab ' '+{-# INLINE emit #-}++ln :: Render n -> Render n+ln r = do+ emit+ r+ rawChar '\n'+{-# INLINE ln #-}++-- | Wrap a `Render n` in { .. }.+bracers :: Render n -> Render n+bracers r = do+ rawChar '{'+ r+ rawChar '}'+{-# INLINE bracers #-}++bracersBlock :: Render n -> Render n+bracersBlock rs = do+ raw "{\n"+ inBlock rs+ emit+ rawChar '}'++-- | Wrap a `Render n` in [ .. ].+brackets :: Render n -> Render n+brackets r = do+ rawChar '['+ r+ rawChar ']'+{-# INLINE brackets #-}++parens :: Render n -> Render n+parens r = do+ rawChar '('+ r+ rawChar ')'+{-# INLINE parens #-}++-- | Intersperse list of Render ns with commas.+commaIntersperce :: [Render n] -> Render n+commaIntersperce = sequence_ . intersperse (rawChar ',')++-- | Place a Render n in an indented block+inBlock :: Render n -> Render n+inBlock r = do+ indent += 2+ r+ indent -= 2++-- numbers and points --------------------------------------------------++-- | Render a point.+point :: RealFloat n => P2 n -> Render a+point = tuplePoint . unp2++bracerPoint :: RealFloat n => P2 n -> Render a+bracerPoint (P (V2 x y)) = do+ bracers (bp x)+ bracers (bp y)++-- | Render n a tuple as a point.+tuplePoint :: RealFloat n => (n,n) -> Render a+tuplePoint (x,y) = do+ pgf "qpoint"+ bracers (bp x)+ bracers (bp y)++-- | Render n a n to four decimal places.+n :: RealFloat a => a -> Render n+n x = rawString $ showFFloat (Just 4) x ""++-- | Render n length with bp (big point = 1 px at 72 dpi) units.+bp :: RealFloat a => a -> Render n+bp = (>> raw "bp") . n++-- | Render n length with px units.+px :: RealFloat a => a -> Render n+px = (>> raw "px") . n++-- | Render n length with mm units.+mm :: RealFloat a => a -> Render n+mm = (>> raw "mm") . n -- . (*0.35278)++-- | Render n length with pt units.+pt :: RealFloat a => a -> Render n+pt = (>> raw "pt") . n -- . (*1.00375)++-- | ε = 0.0001 is the limit at which lines are no longer stroked.+epsilon :: Fractional n => n+epsilon = 0.0001++-- environments --------------------------------------------------------++picture :: Render n -> Render n+picture r = do+ f <- view format+ ln . raw $ case f of+ LaTeX -> "\\begin{pgfpicture}"+ ConTeXt -> "\\startpgfpicture"+ PlainTeX -> "\\pgfpicture"+ inBlock r+ ln . raw $ case f of+ LaTeX -> "\\end{pgfpicture}"+ ConTeXt -> "\\stoppgfpicture"+ PlainTeX -> "\\endpgfpicture"++rectangleBoundingBox :: RealFloat n => V2 n -> Render n+rectangleBoundingBox bounds = do+ ln $ do+ pgf "pathrectangle"+ bracers $ pgf "pointorigin"+ bracers $ tuplePoint (unr2 bounds)+ ln $ do+ pgf "usepath"+ bracers $ raw "use as bounding box"++-- | Wrap the rendering in a scope.+scope :: Render n -> Render n+scope r = do+ f <- view format+ ln . raw $ case f of+ LaTeX -> "\\begin{pgfscope}"+ ConTeXt -> "\\startpgfscope"+ PlainTeX -> "\\pgfscope"+ inBlock r+ ln . raw $ case f of+ LaTeX -> "\\end{pgfscope}"+ ConTeXt -> "\\stoppgfscope"+ PlainTeX -> "\\endpgfscope"++-- opacity groups ------------------------------------------------------++transparencyGroup :: Render n -> Render n+transparencyGroup r = do+ f <- view format+ ln . raw $ case f of+ LaTeX -> "\\begin{pgftransparencygroup}"+ ConTeXt -> "\\startpgftransparencygroup"+ PlainTeX -> "\\pgftransparencygroup"+ inBlock r+ ln . raw $ case f of+ LaTeX -> "\\end{pgftransparencygroup}"+ ConTeXt -> "\\stoppgftransparencygroup"+ PlainTeX -> "\\endpgftransparencygroup"++opacityGroup :: RealFloat a => a -> Render n -> Render n+opacityGroup x r = scope $ do+ setFillOpacity x+ transparencyGroup r++-- colours -------------------------------------------------------------++texColor :: RealFloat a => a -> a -> a -> Render n+texColor r g b = do+ n r+ rawChar ','+ n g+ rawChar ','+ n b++contextColor :: RealFloat a => a -> a -> a -> Render n+contextColor r g b = do+ raw "r=" >> n r+ rawChar ','+ raw "g=" >> n g+ rawChar ','+ raw "b=" >> n b++-- | Defines an RGB colour with the given name, using the Tex format.+defineColour :: RealFloat a => ByteString -> a -> a -> a -> Render n+defineColour name r g b = do+ f <- view format+ ln $ case f of+ ConTeXt -> do+ raw "\\definecolor"+ brackets $ rawByteString name+ brackets $ contextColor r g b+ _ -> do+ raw "\\definecolor"+ bracers $ rawByteString name+ bracers $ raw "rgb"+ bracers $ texColor r g b++parensColor :: Color c => c -> Render n+parensColor c = parens $ texColor r g b+ where (r,g,b,_) = colorToSRGBA c+++-- paths ---------------------------------------------------------------++-- | Close the current path.+closePath :: Render n+closePath = ln $ pgf "pathclose"++-- | Move path to point.+moveTo :: RealFloat n => P2 n -> Render n+moveTo v = ln $ do+ pos .= v+ pgf "pathqmoveto"+ bracerPoint v++-- | Move path by vector.+lineTo :: RealFloat n => V2 n -> Render n+lineTo v = ln $ do+ p <- use pos+ let v' = p .+^ v+ pos .= v'+ pgf "pathqlineto"+ bracerPoint v'++-- | Make curved path from vectors.+curveTo :: RealFloat n => V2 n -> V2 n -> V2 n -> Render n+curveTo v2 v3 v4 = ln $ do+ p <- use pos+ let [v2',v3',v4'] = map (p .+^) [v2,v3,v4]+ pos .= v4'+ pgf "pathqcurveto"+ mapM_ bracerPoint [v2', v3', v4']++-- | Stroke the defined path using parameters from current scope.+stroke :: Render n+stroke = ln $ pgf "usepathqstroke"++-- | Fill the defined path using parameters from current scope.+fill :: Render n+fill = ln $ pgf "usepathqfill"++-- | Use the defined path a clip for everything that follows in the current+-- scope. Stacks.+clip :: Render n+clip = ln $ pgf "usepathqclip"++path :: RealFloat n => Path V2 n -> Render n+path (Path trs) = do+ mapM_ renderTrail trs+ where+ renderTrail (viewLoc -> (p, tr)) = do+ moveTo p+ trail tr++trail :: RealFloat n => Trail V2 n -> Render n+trail t = withLine (render' . lineSegments) t+ where+ render' segs = do+ mapM_ segment segs+ when (isLoop t) closePath++segment :: RealFloat n => Segment Closed V2 n -> Render n+segment (Linear (OffsetClosed v)) = lineTo v+segment (Cubic v1 v2 (OffsetClosed v3)) = curveTo v1 v2 v3++-- | @usePath fill stroke@ combined in one function.+usePath :: Bool -> Bool -> Render n+usePath False False = return ()+usePath doFill doStroke = ln $ do+ pgf "usepathq"+ when doFill $ raw "fill"+ when doStroke $ raw "stroke"++-- | Uses the current path as the bounding box for whole picture.+asBoundingBox :: Render n+asBoundingBox = ln $ do+ pgf "usepath"+ bracers $ raw "use as bounding box"++-- rectangleBoundingBox :: (n,n) -> Render n+-- rectangleBoundingBox xy = do+-- ln $ do+-- pgf "pathrectangle"+-- bracers $ pgf "pointorigin"+-- bracers $ tuplePoint xy+-- asBoundingBox++-- stroke properties++-- | Sets the line width in current scope. Must be done before stroking.+setLineWidth :: RealFloat n => n -> Render n+setLineWidth w = ln $ do+ pgf "setlinewidth"+ bracers $ bp w++-- | Sets the line cap in current scope. Must be done before stroking.+setLineCap :: LineCap -> Render n+setLineCap cap = ln . pgf $ case cap of+ LineCapButt -> "setbuttcap"+ LineCapRound -> "setroundcap"+ LineCapSquare -> "setrectcap"++-- | Sets the line join in current scope. Must be done before stroking.+setLineJoin :: LineJoin -> Render n+setLineJoin lJoin = ln . pgf $ case lJoin of+ LineJoinBevel -> "setbeveljoin"+ LineJoinRound -> "setroundjoin"+ LineJoinMiter -> "setmiterjoin"++-- | Sets the miter limit in the current scope. Must be done before stroking.+setMiterLimit :: RealFloat n => n -> Render n+setMiterLimit l = do+ pgf "setmiterlimit"+ bracers $ bp l++-- stroke parameters ---------------------------------------------------++-- | Sets the dash for the current scope. Must be done before stroking.+setDash :: RealFloat n => Dashing n -> Render n+setDash (Dashing ds offs) = setDash' ds offs++-- \pgfsetdash{{0.5cm}{0.5cm}{0.1cm}{0.2cm}}{0cm}+-- | Takes the dash distances and offset, must be done before stroking.+setDash' :: RealFloat n => [n] -> n -> Render n+setDash' ds off = ln $ do+ pgf "setdash"+ bracers $ mapM_ (bracers . bp) ds+ bracers $ bp off++-- | Sets the stroke colour in current scope. If colour has opacity < 1, the+-- scope opacity is set accordingly. Must be done before stroking.+setLineColor :: (RealFloat a, Color c) => c -> Render a+setLineColor c = do+ defineColour "sc" r g b+ ln $ pgf "setstrokecolor{sc}"+ --+ when (a /= 1) $ setLineOpacity (realToFrac a)+ where+ (r,g,b,a) = colorToSRGBA c++-- | Sets the stroke opacity for the current scope. Should be a value between 0+-- and 1. Must be done before stroking.+setLineOpacity :: RealFloat n => n -> Render n+setLineOpacity a = ln $ do+ pgf "setstrokeopacity"+ bracers $ n a++-- filling -------------------------------------------------------------++-- | Set the fill rule to winding or even-odd for current scope. Must be done+-- before filling.+setFillRule :: FillRule -> Render n+setFillRule rule = ln $ case rule of+ Winding -> pgf "setnonzerorule"+ EvenOdd -> pgf "seteorule"++-- | Sets the fill colour for current scope. If an alpha colour is used, the+-- fill opacity is set accordingly. Must be done before filling.+setFillColor :: (RealFloat n, Color c) => c -> Render n+setFillColor (colorToSRGBA -> (r,g,b,a)) = do+ defineColour "fc" r g b+ ln $ pgf "setfillcolor{fc}"+ --+ when (a /= 1) $ setFillOpacity (realToFrac a :: Double)++-- | Sets the stroke opacity for the current scope. Should be a value between 0+-- and 1. Must be done before stroking.+setFillOpacity :: RealFloat a => a -> Render n+setFillOpacity a = ln $ do+ pgf "setfillopacity"+ bracers $ n a++-- transformations -----------------------------------------------------++getMatrix :: Num n => Transformation V2 n -> (n, n, n, n, n, n)+getMatrix t = (a1,a2,b1,b2,c1,c2)+ where+ [a1, a2, b1, b2, c1, c2] = concat $ matrixHomRep t++-- \pgftransformcm{⟨a⟩}{⟨b⟩}{⟨c⟩}{⟨d⟩}{⟨pointa}++-- | Applies a transformation to the current scope. This transformation only+-- effects coordinates and text, not line withs or dash spacing. (See+-- applyDeepTransform). Must be set before the path is used.+applyTransform :: RealFloat n => Transformation V2 n -> Render n+applyTransform t+ | isID = return ()+ | shiftOnly = ln $ do+ pgf "transformshift"+ bracers p+ | otherwise = ln $ do+ pgf "transformcm"+ mapM_ (bracers . n) [a, b, c, d] >> bracers p+ where+ (a,b,c,d,e,f) = getMatrix t+ p = tuplePoint (e,f)+ --+ shiftOnly = (a,b,c,d) == (1,0,0,1)+ isID = shiftOnly && (e,f) == (0,0)++-- | Resets the transform and sets it. Must be set before the path is used.+setTransform :: RealFloat n => Transformation V2 n -> Render n+setTransform t = do+ pgf "settransformentries"+ mapM_ (bracers . n) [a, b, c, d] >> mapM_ (bracers . bp) [e, f]+ where+ (a,b,c,d,e,f) = getMatrix t++applyScale :: RealFloat n => n -> Render n+applyScale s = ln $ do+ pgf "transformscale"+ bracers $ n s++resetNonTranslations :: Render n+resetNonTranslations = ln $ pgf "transformresetnontranslations"++-- | Base transforms are applied by the document reader.+baseTransform :: RealFloat n => Transformation V2 n -> Render n+baseTransform t = ln $ do+ pgf "lowlevel"+ bracers $ setTransform t++-- setShadetransform :: Transformation V2 -> Render n+-- setShadetransform (dropTransl -> t) = do+-- pgf "setadditionalshadetransform"+-- bracersBlock $ applyTransform t++-- shading -------------------------------------------------------------++linearGradient :: RealFloat n => Path V2 n -> LGradient n -> Render n+linearGradient p lg = scope $ do+ path p+ let (stops', t) = calcLinearStops p lg+ ln $ do+ pgf "declarehorizontalshading"+ bracers $ raw "ft" -- fill texture+ bracers $ raw "100bp" -- gradient is always 100 x 100 square+ bracersBlock $ colorSpec 1 stops'+ clip+ baseTransform t+ useShading $ raw "ft"++-- | Calculate the correct linear stops such that the path is completely+-- filled. PGF doesn't have spread methods so this has to be done+-- manually.+calcLinearStops :: RealFloat n+ => Path V2 n -> LGradient n -> ([GradientStop n], T2 n)+calcLinearStops (Path []) _ = ([], mempty)+calcLinearStops pth (LGradient stops p0 p1 gt sm)+ = (linearStops' x0 x1 stops sm, t <> ft)+ where+ -- Transform such that the transform t origin is start of the+ -- gradient, transform t unitX is the end.+ t = gt+ -- encorperate the start and end points+ <> translation (p0 ^. _Point)+ <> scaling (norm (p1 .-. p0))+ <> rotationTo (dirBetween p1 p0)++ -- Use the inverse transformed path and make the pre-transformed+ -- gradient fit to it. Then when we transform the gradient we know+ -- it'll fit the path.+ p' = transform (inv t) pth+ Just (x0,x1) = extentX p'+ Just (y0,y1) = extentY p'++ -- Final transform to fit the gradient to the path. The origin on+ -- the gradient is its centre so we translate by - V2 50 50 to get+ -- to the lower corner (because of this we set the size of the+ -- gradient to always be 100 x 100 for simplicity). Then scales up+ -- the gradient to cover the path and moves it into position.+ ft = translation (V2 x0 y0) <> scalingV ((*0.01) . abs <$> V2 (x0 - x1) (y0 - y1)) <> translation 50++scalingV :: (Additive v, Fractional n) => v n -> Transformation v n+scalingV v = fromSymmetric $ liftU2 (*) v <-> liftU2 (flip (/)) v++useShading :: Render n -> Render n+useShading nm = ln $ do+ pgf "useshading"+ bracers nm+++_translation :: Lens' (Transformation v n) (v n)+_translation f (Transformation a b v) = f v <&> \v' -> Transformation a b v'++linearStops' :: RealFloat n+ => n -> n -> [GradientStop n] -> SpreadMethod -> [GradientStop n]+linearStops' x0 x1 stops sm =+ GradientStop c1' 0 : filter (inRange . view stopFraction) stops' ++ [GradientStop c2' 100]+ where+ stops' = case sm of+ GradPad -> over (each . stopFraction) normalise stops+ GradRepeat -> flip F.foldMap [i0 .. i1] $ \i ->+ increaseFirst $+ over (each . stopFraction)+ (normalise . (+ fromIntegral i))+ stops+ GradReflect -> flip F.foldMap [i0 .. i1] $ \i ->+ over (each . stopFraction)+ (normalise . (+ fromIntegral i))+ (reverseOdd i stops)++ -- for repeat it sometimes complains if two are exactly the same so+ -- increase the first by a little+ increaseFirst = over (_head . stopFraction) (+0.001)+ reverseOdd i+ | odd i = reverse . over (each . stopFraction) (1 -)+ | otherwise = id+ i0 = floor x0 :: Int+ i1 = ceiling x1+ c1' = SomeColor $ colourInterp stops' 0+ c2' = SomeColor $ colourInterp stops' 100+ inRange x = x > 0 && x < 100+ normalise x = 100 * (x - x0) / (x1 - x0)++colourInterp :: RealFloat n => [GradientStop n] -> n -> AlphaColour Double+colourInterp cs0 x = go cs0+ where+ go (GradientStop c1 a : c@(GradientStop c2 b) : cs)+ | x <= a = toAlphaColour c1+ | x > a && x < b = blend y (toAlphaColour c2) (toAlphaColour c1)+ | otherwise = go (c : cs)+ where+ y = realToFrac $ (x - a) / (b - a)+ go [GradientStop c2 _] = toAlphaColour c2+ go _ = transparent++radialGradient :: RealFloat n => Path V2 n -> RGradient n -> Render n+radialGradient p rg = scope $ do+ path p+ let (stops', t, p0) = calcRadialStops p rg+ ln $ do+ pgf "declareradialshading"+ bracers $ raw "ft"+ bracers $ point p0+ bracersBlock $ colorSpec 1 stops'+ clip+ baseTransform t+ useShading $ raw "ft"++-- | Calculate the correct linear stops such that the path is completely+-- filled. PGF doesn't have spread methods so this has to be done+-- manually.+calcRadialStops :: RealFloat n+ => Path V2 n -> RGradient n -> ([GradientStop n], T2 n, P2 n)+calcRadialStops (Path []) _ = ([], mempty, origin)+calcRadialStops pth (RGradient stops p0 r0 p1 r1 gt _sm)+ = (stops', t <> ft, P cv)+ where+ cv = tp0 .-. tp1+ tp0 = papply gt p0+ tp1 = papply gt p1+ -- Transform such that the transform t origin is start of the+ -- gradient, transform t unitX is the end.+ t = gt+ <> translation (p1 ^. _Point)+ <> scaling r1++ -- Similar to linear gradients but not so precise, d is a (bad and+ -- probably incorrect) lower bound for the required radius of the+ -- circle to cover the path.+ p' = transform (inv t) pth+ Just (x0,x1) = extentX p'+ Just (y0,y1) = extentY p'+ d = 2 * max (max (abs $ x0 - x1) (abs $ y0 - y1)) (lstop ^. stopFraction)++ -- Adjust for gradient size having radius 100+ ft = scaling 0.01++ -- Stops are scaled to start at r0 and end at r1. The gradient is+ -- extended to d to try to cover the path.+ --+ -- The problem is extending the size of the gradient in this way+ -- affects how the gradient scales if it is off-centre. This needs+ -- to be fixed.+ --+ -- Only the GradPad spread method is supported for now.+ stops' = head stops : over (each . stopFraction) refrac stops ++ [lstop & stopFraction .~ 100*d]+ refrac x = 100 * ((r0 + x * (r1 - r0)) / r1) -- start at r0, end at r1+ lstop = last stops++-- Dirty adjustments for spread methods (PGF doesn't seem to have them).+-- adjustStops :: RealFloat n => [GradientStop n] -> SpreadMethod -> [GradientStop n]+-- adjustStops stops method =+-- case method of+-- GradPad -> (stopFraction .~ 0) (head stops) : map (stopFraction +~ 1) stops+-- ++ [(stopFraction +~ 2) (last stops)]+-- GradReflect -> correct . concat . replicate 10+-- $ [stops, zipWith (\a b -> a & (stopColor .§ b)) stops (reverse stops)]+-- GradRepeat -> correct . replicate 10 $ stops+ -- where+ -- correct = ifoldMap (\i -> map (stopFraction +~ (lastStop * fromIntegral i)) )+ -- lastStop = last stops ^. stopFraction++-- (.§) :: Lens s t b b -> s -> s -> t+-- (.§) l a b = b & l #~ (a ^# l)+-- {-# INLINE (.§) #-}++colorSpec :: RealFloat n => n -> [GradientStop n] -> Render n+colorSpec d = mapM_ ln+ . combinePairs+ . intersperse (rawChar ';')+ . map mkColor+ where+ mkColor (GradientStop c sf) = do+ raw "rgb"+ parens $ bp (d*sf)+ raw "="+ parensColor c++combinePairs :: Monad m => [m a] -> [m a]+combinePairs (x1:x2:xs) = (x1 >> x2) : combinePairs xs+combinePairs xs = xs++shadePath :: RealFloat n => Angle n -> Render n -> Render n+shadePath (view deg -> θ) name = ln $ do+ pgf "shadepath"+ bracers name+ bracers $ n θ++-- external images -----------------------------------------------------++-- \pgfimage[⟨options ⟩]{⟨filename ⟩}++-- | Images are wraped in a \pgftext.+image :: RealFloat n => DImage n External -> Render n+image (DImage (ImageRef ref) w h t2) = scope $ do+ applyTransform t2+ ln $ do+ pgf "text"+ bracers $ do+ pgf "image"+ brackets $ do+ raw "width=" >> bp (fromIntegral w :: Double)+ rawChar ','+ raw "height=" >> bp (fromIntegral h :: Double)+ bracers $ rawString ref++-- embedded images -----------------------------------------------------++embeddedImage :: RealFloat n => DImage n Embedded -> Render n+embeddedImage (DImage (ImageRaster (ImageRGB8 img)) w h t) =+ embeddedImage' (hexImage img) w h t+ -- TODO: Support more formats (like grey scale and alpha channels)+embeddedImage _ = error "Unsupported embedded image. Only ImageRGB8 is currently supported."++-- | Convert an 'Image' to a zlib compressed lazy 'ByteString' of the+-- raw image data. This is a suitable format for an embedded PDF image+-- stream.+hexImage :: Image PixelRGB8 -> LB.ByteString+hexImage (imageData -> v) = compress $ LB.fromStrict bs+ where+ bs = fromForeignPtr p i nn+ (p, i, nn) = S.unsafeToForeignPtr v++embeddedImage' :: RealFloat n => LB.ByteString -> Int -> Int -> T2 n -> Render n+embeddedImage' img w h t = scope $ do+ baseTransform t+ ln $ raw "\\immediate\\pdfliteral{"+ rawLn "q" -- save state++ -- Scale the image to it's actual size and translate so the origin is+ -- at the centre.+ rawLn $ s w <> " 0 0 " <> s h <> " -" <> half w <> " -" <> half h <> " cm"+ rawLn "BI" -- begin image+ rawLn $ "/W " <> s w -- width in pixels+ rawLn $ "/H " <> s h -- height in pixels+ rawLn "/CS /RGB" -- RGB colour space+ rawLn "/BPC 8" -- 8 bits per component++ -- Filters for the encoded image:+ -- ASCIIHexDecode -- decode from hexadecimal to binary+ -- FlateDecode -- decompress using zlib deflate compression+ rawLn "/F [/AHx /Fl]"++ -- We use hex format for the image data so tex can output it without+ -- any problems. Base85 might be possible and would be 2-3x smaller+ -- but there's some problem chars tex complains about. Base64 would+ -- be ideal but the pdf spec doesn't seem to support it.+ --+ -- This is an inline image which is only really suitable for small+ -- images. An XObject might be more appropriate. See+ -- http://partners.adobe.com/public/developer/en/pdf/PDFReference.pdf+ -- for more information.+ rawLn "ID" -- image data+ rawLn $ hexChunk img <> char8 '>'+ rawLn "EI" -- end image+ rawLn "Q" -- restore state+ rawLn "}"+ where+ rawLn r = raw r >> rawChar '\n'+ s = intDec+ half x = s (x `div` 2) <> if odd x then ".5" else mempty++-- | Insert hex encode and add a newline every 80 chars. This is useful for+-- readable output and stopping tex from choking when streaming. Note+-- that new-lines and spaces are ignored with the hex decode filter.+hexChunk :: LB.ByteString -> Builder+hexChunk (LB.splitAt 40 -> (a,b))+ | LB.null b = lazyByteStringHex a+ | otherwise = lazyByteStringHex a <> char8 '\n' <> hexChunk b++-- text ----------------------------------------------------------------++renderText :: [Render n] -> Render n -> Render n+renderText ops txt = ln $ do+ pgf "text"+ brackets . commaIntersperce $ ops+ bracers txt++-- | Returns a list of values to be put in square brackets like+-- @\pgftext[left,top]{txt}@.+setTextAlign :: RealFloat n => TextAlignment n -> [Render n]+setTextAlign a = case a of+ BaselineText -> [raw "base", raw "left"]+ BoxAlignedText xt yt -> catMaybes [xt', yt']+ where+ xt' | xt > 0.75 = Just $ raw "right"+ | xt < 0.25 = Just $ raw "left"+ | otherwise = Nothing+ yt' | yt > 0.75 = Just $ raw "top"+ | yt < 0.25 = Just $ raw "bottom"+ | otherwise = Nothing++setTextRotation :: RealFloat n => Angle n -> [Render n]+setTextRotation a = case a^.deg of+ 0 -> []+ θ -> [raw "rotate=" >> n θ]++-- | Set the font weight by rendering @\bf @. Nothing is done for normal+-- weight.+setFontWeight :: FontWeight -> Render n+setFontWeight FontWeightNormal = return ()+setFontWeight FontWeightBold = raw "\\bf "++-- | Set the font slant by rendering @\bf @. Nothing is done for normal weight.+setFontSlant :: FontSlant -> Render n+setFontSlant FontSlantNormal = return ()+setFontSlant FontSlantItalic = raw "\\it "+setFontSlant FontSlantOblique = raw "\\sl "