diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,73 @@
 # Changelog
 
+## 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
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -18,8 +18,8 @@
 - [Transition effects](#transitions).
 - Supports [smart slide splitting](#input-format).
 - [Auto advancing](#auto-advancing) with configurable delay.
-- Optionally [re-wrapping](#line-wrapping) text to terminal width with proper
-  indentation.
+- [Centering](#centering) and [re-wrapping](#line-wrapping) text to terminal
+  width with proper indentation.
 - [Theming](#theming) support including 24-bit RGB.
 - Hihgly portable as it only requires an ANSI terminal as opposed to
   something like `ncurses`.
@@ -34,8 +34,7 @@
 
 -   [Table of Contents](#table-of-contents)
 -   [Installation](#installation)
-    -   [Pre-built-packages](#pre-built-packages)
-    -   [Homebrew](#homebrew)
+    -   [Pre-built packages](#pre-built-packages)
     -   [From source](#from-source)
 -   [Running](#running)
 -   [Options](#options)
@@ -61,31 +60,23 @@
 Installation
 ------------
 
-### Pre-built-packages
-
-- Archlinux: <https://aur.archlinux.org/packages/patat-bin>
-- Debian: <https://packages.debian.org/unstable/patat>
-- Ubuntu: <https://packages.ubuntu.com/bionic/patat>
-- openSUSE: <https://build.opensuse.org/package/show/openSUSE:Factory:ARM/patat>
-- Fedora: <https://src.fedoraproject.org/rpms/patat>
-- NixOS: <https://search.nixos.org/packages?show=haskellPackages.patat>
+### Pre-built packages
 
-You can also find generic Linux and Mac OS binaries here:
-<https://github.com/jaspervdj/patat/releases>.
+Linux:
 
-### Homebrew
+- [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)
 
-[Homebrew] packages are offered by a
-[third-party repository](https://github.com/nicholasdille/homebrew-tap).
-**Unfortunately this repo has been archived, and the latest version that can
-be installed from there is v0.8.7.0**.  If you would like to contribute here,
-please contact us by opening an issue.
+Mac OS:
 
-1. Run `brew tap nicholasdille/tap`.
-2. Run `brew install patat-bin` to receive the official binaries or
-   `brew install patat` for pre-built ("bottled") binaries.
+- [Homebrew](https://formulae.brew.sh/formula/patat)
 
-[Homebrew]: https://brew.sh/
+You can also find generic Linux and Mac OS binaries here:
+<https://github.com/jaspervdj/patat/releases>.
 
 ### From source
 
@@ -310,7 +301,8 @@
 ### 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.
+will re-wrap all lines to fit the terminal width better.  You can also ask patat
+to wrap at a specific column using e.g. `wrap: 60`.
 
 ### Margins
 
@@ -342,6 +334,28 @@
 By default, the `left` and `right` margin are set to 0, and the `top` margin is
 set to 1.
 
+#### Centering
+
+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
@@ -595,6 +609,8 @@
 
 ### Images
 
+#### Native Images support
+
 `patat-0.8.0.0` and newer include images support for some terminal emulators.
 
 ```markdown
@@ -633,6 +649,33 @@
     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 `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
+    ```
+
 ### Breadcrumbs
 
 By default, `patat` will print a breadcrumbs-style header, e.g.:
@@ -669,6 +712,7 @@
           command: irb --noecho --noverbose
           fragment: true  # Optional
           replace: false  # Optional
+          wrap: code  # Optional
     ...
 
     Here is an example of a code block that is evaluated:
@@ -690,6 +734,12 @@
     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`.
+ -  `wrap`: 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.
 
 Setting `fragment: false` and `replace: true` offers a way to "filter" code
 blocks, which can be used to render ASCII graphics.
diff --git a/lib/Patat/Eval.hs b/lib/Patat/Eval.hs
--- a/lib/Patat/Eval.hs
+++ b/lib/Patat/Eval.hs
@@ -15,6 +15,7 @@
 import qualified Data.Text.IO                   as T
 import           Patat.Presentation.Instruction
 import           Patat.Presentation.Internal
+import           Patat.Presentation.Settings
 import           System.Exit                    (ExitCode (..))
 import qualified System.IO                      as IO
 import           System.IO.Unsafe               (unsafeInterleaveIO)
@@ -70,15 +71,20 @@
                 ExitFailure i ->
                     evalCommand <> ": exit code " <> T.pack (show i) <> "\n" <>
                     erStderr
+        let fmt = "eval"
+            blocks = case evalWrap of
+                EvalWrapCode      -> [Pandoc.CodeBlock attr out]
+                EvalWrapRaw       -> [Pandoc.RawBlock fmt out]
+                EvalWrapRawInline -> [Pandoc.Plain [Pandoc.RawInline fmt out]]
         pure $ case (evalFragment, evalReplace) of
-            (False, True) -> [Append [Pandoc.CodeBlock attr out]]
-            (False, False) -> [Append [orig, Pandoc.CodeBlock attr out]]
+            (False, True) -> [Append blocks]
+            (False, False) -> [Append (orig : blocks)]
             (True, True) ->
                 [ Append [orig], Pause
-                , Delete, Append [Pandoc.CodeBlock attr out]
+                , Delete, Append blocks
                 ]
             (True, False) ->
-                [Append [orig], Pause, Append [Pandoc.CodeBlock attr out]]
+                [Append [orig], Pause, Append blocks]
     | _ : _ : _ <- lookupSettings classes settings =
         let msg = "patat eval matched multiple settings for " <>
                 T.intercalate "," classes in
diff --git a/lib/Patat/Presentation/Display.hs b/lib/Patat/Presentation/Display.hs
--- a/lib/Patat/Presentation/Display.hs
+++ b/lib/Patat/Presentation/Display.hs
@@ -23,6 +23,7 @@
 import           Patat.Presentation.Display.Internal
 import           Patat.Presentation.Display.Table
 import           Patat.Presentation.Internal
+import           Patat.Presentation.Settings
 import           Patat.PrettyPrint                    ((<$$>), (<+>))
 import qualified Patat.PrettyPrint                    as PP
 import           Patat.Size
@@ -41,7 +42,7 @@
 -- | Display something within the presentation borders that draw the title and
 -- the active slide number and so on.
 displayWithBorders
-    :: Size -> Presentation -> (Size -> DisplaySettings -> PP.Doc) -> PP.Doc
+    :: Size -> Presentation -> (DisplaySettings -> PP.Doc) -> PP.Doc
 displayWithBorders (Size rows columns) pres@Presentation {..} f =
     (if null title
         then mempty
@@ -49,18 +50,20 @@
             let titleRemainder = columns - titleWidth - titleOffset
                 wrappedTitle = PP.spaces titleOffset <> PP.string title <> PP.spaces titleRemainder in
         borders wrappedTitle <> PP.hardline) <>
-    mconcat (replicate topMargin PP.hardline) <>
-    formatWith settings (f canvasSize ds) <> 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
-    (sidx, _)   = pActiveFragment
-    settings    = (activeSettings pres) {psColumns = Just $ A.FlexibleNum columns}
-    ds          = DisplaySettings
-        { dsTheme     = fromMaybe Theme.defaultTheme (psTheme settings)
-        , dsSyntaxMap = pSyntaxMap
+    (sidx, _) = pActiveFragment
+    settings  = activeSettings pres
+    ds        = DisplaySettings
+        { dsSize          = canvasSize
+        , dsWrap          = fromMaybe NoWrap $ psWrap settings
+        , dsMargins       = margins settings
+        , dsTheme         = fromMaybe Theme.defaultTheme (psTheme settings)
+        , dsSyntaxMap     = pSyntaxMap
         }
 
     -- Compute title.
@@ -82,8 +85,7 @@
     borders     = themed ds themeBorders
 
     -- Room left for content
-    topMargin  = mTop $ margins settings
-    canvasSize = Size (rows - 2 - topMargin) columns
+    canvasSize = Size (rows - 3) columns
 
     -- Compute footer.
     active
@@ -105,18 +107,12 @@
                 , Just image <- onlyImage fragment ->
             DisplayImage $ T.unpack image
         Just (ActiveContent fragment) -> DisplayDoc $
-            displayWithBorders size pres $ \_canvasSize theme ->
+            displayWithBorders size pres $ \theme ->
                 prettyFragment theme fragment
         Just (ActiveTitle block) -> DisplayDoc $
-            displayWithBorders size pres $ \canvasSize theme ->
-            let pblock         = prettyBlock theme block
-                (prows, pcols) = PP.dimensions pblock
-                Margins {..}   = margins (activeSettings pres)
-                offsetRow      = (sRows canvasSize `div` 2) - (prows `div` 2)
-                offsetCol      = ((sCols canvasSize - mLeft - mRight) `div` 2) - (pcols `div` 2)
-                spaces         = PP.NotTrimmable $ PP.spaces offsetCol in
-            mconcat (replicate (offsetRow - 3) PP.hardline) <$$>
-            PP.indent spaces spaces pblock
+            displayWithBorders size pres $ \ds ->
+                let auto = Margins {mTop = Auto, mRight = Auto, mLeft = Auto} in
+                prettyFragment ds {dsMargins = auto} $ Fragment [block]
 
   where
     -- Check if the fragment consists of "just a single image".  Discard
@@ -132,11 +128,10 @@
 -- | 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)
+displayPresentationError size pres err = displayWithBorders size pres $ \ds ->
+    themed ds themeStrong "Error occurred in the presentation:" <$$>
+    "" <$$>
+    (PP.string err)
 
 
 --------------------------------------------------------------------------------
@@ -149,11 +144,10 @@
     dumpSlide :: Int -> [PP.Doc]
     dumpSlide i = do
         slide <- maybeToList $ getSlide i pres
-        map (formatWith (getSettings i pres)) $
-            dumpComment slide <> L.intercalate ["{fragment}"]
-                [ dumpFragment (i, j)
-                | j <- [0 .. numFragments slide - 1]
-                ]
+        dumpComment slide <> L.intercalate ["{fragment}"]
+            [ dumpFragment (i, j)
+            | j <- [0 .. numFragments slide - 1]
+            ]
 
     dumpComment :: Slide -> [PP.Doc]
     dumpComment slide = do
@@ -178,26 +172,51 @@
 
 
 --------------------------------------------------------------------------------
-formatWith :: PresentationSettings -> PP.Doc -> PP.Doc
-formatWith ps = wrap . indent
-  where
-    Margins {..} = margins ps
-    wrap = case (psWrap ps, psColumns ps) of
-        (Just True,  Just (A.FlexibleNum col)) -> PP.wrapAt (Just $ col - mRight)
-        _                                      -> id
-    spaces = PP.NotTrimmable $ PP.spaces mLeft
-    indent = PP.indent spaces spaces
-
-
---------------------------------------------------------------------------------
 prettyFragment :: DisplaySettings -> Fragment -> PP.Doc
-prettyFragment ds (Fragment blocks) =
-    prettyBlocks ds blocks <>
+prettyFragment ds (Fragment blocks) = vertical $
+    PP.vcat (map (horizontal . prettyBlock ds) blocks) <>
     case prettyReferences ds blocks of
         []   -> mempty
-        refs -> PP.hardline <> PP.vcat refs
+        refs -> PP.hardline <> PP.vcat (map horizontal refs)
+  where
+    Size rows columns = dsSize ds
+    Margins {..} = dsMargins ds
 
+    vertical doc0 =
+        mconcat (replicate top PP.hardline) <> doc0
+      where
+        top = case mTop of
+            Auto -> let (r, _) = PP.dimensions doc0 in (rows - r) `div` 2
+            NotAuto x -> x
 
+    horizontal = horizontalIndent . horizontalWrap
+
+    horizontalIndent doc0 = PP.indent indentation indentation doc1
+      where
+        doc1 = case (mLeft, mRight) of
+            (Auto, Auto) -> PP.deindent doc0
+            _            -> doc0
+        (_, dcols) = PP.dimensions doc1
+        left = case mLeft of
+            NotAuto x -> x
+            Auto      -> case mRight of
+                NotAuto _ -> 0
+                Auto      -> (columns - dcols) `div` 2
+        indentation = PP.Indentation left mempty
+
+    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 -> Pandoc.Block -> PP.Doc
 
@@ -215,13 +234,13 @@
 
 prettyBlock ds (Pandoc.BulletList bss) = PP.vcat
     [ PP.indent
-        (PP.NotTrimmable $ themed ds themeBulletList prefix)
-        (PP.Trimmable "    ")
+        (PP.Indentation 2 $ themed ds themeBulletList prefix)
+        (PP.Indentation 4 mempty)
         (prettyBlocks ds' bs)
     | bs <- bss
     ] <> PP.hardline
   where
-    prefix = "  " <> PP.string [marker] <> " "
+    prefix = PP.string [marker] <> " "
     marker = case T.unpack <$> themeBulletListMarkers theme of
         Just (x : _) -> x
         _            -> '-'
@@ -236,8 +255,8 @@
 
 prettyBlock ds (Pandoc.OrderedList _ bss) = PP.vcat
     [ PP.indent
-        (PP.NotTrimmable $ themed ds themeOrderedList $ PP.string prefix)
-        (PP.Trimmable "    ")
+        (PP.Indentation 0 $ themed ds themeOrderedList $ PP.string prefix)
+        (PP.Indentation 4 mempty)
         (prettyBlocks ds bs)
     | (prefix, bs) <- zip padded bss
     ] <> PP.hardline
@@ -253,7 +272,7 @@
 prettyBlock _ds Pandoc.HorizontalRule = "---"
 
 prettyBlock ds (Pandoc.BlockQuote bs) =
-    let quote = PP.NotTrimmable (themed ds themeBlockQuote "> ") in
+    let quote = PP.Indentation 0 (themed ds themeBlockQuote "> ") in
     PP.indent quote quote (themed ds themeBlockQuote $ prettyBlocks ds bs)
 
 prettyBlock ds (Pandoc.DefinitionList terms) =
@@ -263,8 +282,8 @@
         themed ds themeDefinitionTerm (prettyInlines ds term) <$$>
         PP.hardline <> PP.vcat
         [ PP.indent
-            (PP.NotTrimmable (themed ds themeDefinitionList ":   "))
-            (PP.Trimmable "    ") $
+            (PP.Indentation 0 (themed ds themeDefinitionList ":   "))
+            (PP.Indentation 4 mempty) $
             prettyBlocks ds (Pandoc.plainToPara definition)
         | definition <- definitions
         ]
@@ -289,7 +308,7 @@
 prettyBlock ds (Pandoc.Div _attrs blocks) = prettyBlocks ds blocks
 
 prettyBlock ds (Pandoc.LineBlock inliness) =
-    let ind = PP.NotTrimmable (themed ds themeLineBlock "| ") in
+    let ind = PP.Indentation 0 (themed ds themeLineBlock "| ") in
     PP.wrapAt Nothing $
     PP.indent ind ind $
     PP.vcat $
@@ -352,10 +371,11 @@
     "![" <> themed ds themeImageText (prettyInlines ds text) <> "](" <>
     themed ds themeImageTarget (PP.text target) <> ")"
 
+prettyInline _ (Pandoc.RawInline _ t) = PP.text t
+
 -- These elements aren't really supported.
 prettyInline ds  (Pandoc.Cite      _ t) = prettyInlines ds t
 prettyInline ds  (Pandoc.Span      _ t) = prettyInlines ds t
-prettyInline _ds (Pandoc.RawInline _ t) = PP.text t
 prettyInline ds  (Pandoc.Note        t) = prettyBlocks  ds t
 prettyInline ds  (Pandoc.Superscript t) = prettyInlines ds t
 prettyInline ds  (Pandoc.Subscript   t) = prettyInlines ds t
diff --git a/lib/Patat/Presentation/Display/CodeBlock.hs b/lib/Patat/Presentation/Display/CodeBlock.hs
--- a/lib/Patat/Presentation/Display/CodeBlock.hs
+++ b/lib/Patat/Presentation/Display/CodeBlock.hs
@@ -76,7 +76,7 @@
     blockified :: Skylighting.SourceLine -> PP.Doc
     blockified line =
         let len    = sourceLineLength line
-            indent = PP.NotTrimmable "   " in
+            indent = PP.Indentation 3 mempty in
         PP.indent indent indent $
         themed ds themeCodeBlock $
             " " <>
diff --git a/lib/Patat/Presentation/Display/Internal.hs b/lib/Patat/Presentation/Display/Internal.hs
--- a/lib/Patat/Presentation/Display/Internal.hs
+++ b/lib/Patat/Presentation/Display/Internal.hs
@@ -6,14 +6,20 @@
 
 
 --------------------------------------------------------------------------------
-import qualified Patat.PrettyPrint as PP
-import qualified Patat.Theme       as Theme
-import qualified Skylighting       as Skylighting
+import           Patat.Presentation.Internal (Margins)
+import           Patat.Presentation.Settings (Wrap)
+import qualified Patat.PrettyPrint           as PP
+import           Patat.Size                  (Size)
+import qualified Patat.Theme                 as Theme
+import qualified Skylighting                 as Skylighting
 
 
 --------------------------------------------------------------------------------
 data DisplaySettings = DisplaySettings
-    { dsTheme     :: !Theme.Theme
+    { dsSize      :: !Size
+    , dsWrap      :: !Wrap
+    , dsMargins   :: !Margins
+    , dsTheme     :: !Theme.Theme
     , dsSyntaxMap :: !Skylighting.SyntaxMap
     }
 
diff --git a/lib/Patat/Presentation/Display/Table.hs b/lib/Patat/Presentation/Display/Table.hs
--- a/lib/Patat/Presentation/Display/Table.hs
+++ b/lib/Patat/Presentation/Display/Table.hs
@@ -30,7 +30,7 @@
 --------------------------------------------------------------------------------
 prettyTable :: DisplaySettings -> Table -> PP.Doc
 prettyTable ds Table {..} =
-    PP.indent (PP.Trimmable "  ") (PP.Trimmable "  ") $
+    PP.indent indentation indentation $
         lineIf (not isHeaderLess) (hcat2 headerHeight
             [ themed ds themeTableHeader $
                 PP.align w a (vpad headerHeight header)
@@ -48,6 +48,8 @@
         lineIf
             (not $ PP.null tCaption) (PP.hardline <> "Table: " <> tCaption)
   where
+    indentation = PP.Indentation 2 mempty
+
     lineIf cond line = if cond then line <> PP.hardline else mempty
 
     joinRows
diff --git a/lib/Patat/Presentation/Internal.hs b/lib/Patat/Presentation/Internal.hs
--- a/lib/Patat/Presentation/Internal.hs
+++ b/lib/Patat/Presentation/Internal.hs
@@ -78,9 +78,9 @@
 
 --------------------------------------------------------------------------------
 data Margins = Margins
-    { mTop   :: Int
-    , mLeft  :: Int
-    , mRight :: Int
+    { mTop   :: AutoOr Int
+    , mLeft  :: AutoOr Int
+    , mRight :: AutoOr Int
     } deriving (Show)
 
 
@@ -92,8 +92,10 @@
     , mTop   = get 1 msTop
     }
   where
-    get def f = fromMaybe def . fmap A.unFlexibleNum $ psMargins ps >>= f
-
+    get def f = case psMargins ps >>= f of
+        Just Auto         -> Auto
+        Nothing           -> NotAuto def
+        Just (NotAuto fn) -> NotAuto $ A.unFlexibleNum fn
 
 --------------------------------------------------------------------------------
 data Slide = Slide
diff --git a/lib/Patat/Presentation/Settings.hs b/lib/Patat/Presentation/Settings.hs
--- a/lib/Patat/Presentation/Settings.hs
+++ b/lib/Patat/Presentation/Settings.hs
@@ -6,6 +6,8 @@
     ( PresentationSettings (..)
     , defaultPresentationSettings
 
+    , Wrap (..)
+    , AutoOr (..)
     , MarginSettings (..)
 
     , ExtensionList (..)
@@ -14,6 +16,7 @@
     , ImageSettings (..)
 
     , EvalSettingsMap
+    , EvalSettingsWrap (..)
     , EvalSettings (..)
 
     , SpeakerNotesSettings (..)
@@ -23,18 +26,19 @@
 
 
 --------------------------------------------------------------------------------
-import           Control.Monad                  (mplus)
-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           Control.Applicative    ((<|>))
+import           Control.Monad          (mplus)
+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)
+import qualified Text.Pandoc            as Pandoc
+import           Text.Read              (readMaybe)
 
 
 --------------------------------------------------------------------------------
@@ -44,7 +48,7 @@
     { psRows              :: !(Maybe (A.FlexibleNum Int))
     , psColumns           :: !(Maybe (A.FlexibleNum Int))
     , psMargins           :: !(Maybe MarginSettings)
-    , psWrap              :: !(Maybe Bool)
+    , psWrap              :: !(Maybe Wrap)
     , psTheme             :: !(Maybe Theme.Theme)
     , psIncrementalLists  :: !(Maybe Bool)
     , psAutoAdvanceDelay  :: !(Maybe (A.FlexibleNum Int))
@@ -100,10 +104,31 @@
 
 
 --------------------------------------------------------------------------------
+data Wrap = NoWrap | AutoWrap | WrapAt Int deriving (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 (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 (A.FlexibleNum Int))
-    , msLeft  :: !(Maybe (A.FlexibleNum Int))
-    , msRight :: !(Maybe (A.FlexibleNum Int))
+    { msTop   :: !(Maybe (AutoOr (A.FlexibleNum Int)))
+    , msLeft  :: !(Maybe (AutoOr (A.FlexibleNum Int)))
+    , msRight :: !(Maybe (AutoOr (A.FlexibleNum Int)))
     } deriving (Show)
 
 
@@ -196,10 +221,28 @@
 
 
 --------------------------------------------------------------------------------
+data EvalSettingsWrap
+    = EvalWrapCode
+    | EvalWrapRaw
+    | EvalWrapRawInline
+    deriving (Show)
+
+
+--------------------------------------------------------------------------------
+instance A.FromJSON EvalSettingsWrap where
+    parseJSON = A.withText "FromJSON EvalSettingsWrap" $ \txt -> case txt of
+        "code"      -> pure EvalWrapCode
+        "raw"       -> pure EvalWrapRaw
+        "rawInline" -> pure EvalWrapRawInline
+        _           -> fail $ "unknown wrap: " <> show txt
+
+
+--------------------------------------------------------------------------------
 data EvalSettings = EvalSettings
     { evalCommand  :: !T.Text
     , evalReplace  :: !Bool
     , evalFragment :: !Bool
+    , evalWrap     :: !EvalSettingsWrap
     } deriving (Show)
 
 
@@ -207,8 +250,9 @@
 instance A.FromJSON EvalSettings where
     parseJSON = A.withObject "FromJSON EvalSettings" $ \o -> EvalSettings
         <$> o A..: "command"
-        <*> o A..:? "replace" A..!= False
+        <*> o A..:? "replace"  A..!= False
         <*> o A..:? "fragment" A..!= True
+        <*> o A..:? "wrap"     A..!= EvalWrapCode
 
 
 --------------------------------------------------------------------------------
diff --git a/lib/Patat/PrettyPrint.hs b/lib/Patat/PrettyPrint.hs
--- a/lib/Patat/PrettyPrint.hs
+++ b/lib/Patat/PrettyPrint.hs
@@ -24,8 +24,9 @@
 
     , wrapAt
 
-    , Trimmable (..)
+    , Indentation (..)
     , indent
+    , deindent
 
     , ansi
 
@@ -91,12 +92,35 @@
 
 
 --------------------------------------------------------------------------------
-indent :: Trimmable Doc -> Trimmable Doc -> Doc -> Doc
+indent :: Indentation Doc -> Indentation Doc -> Doc -> Doc
 indent firstLineDoc otherLinesDoc doc = mkDoc $ Indent
-    { indentFirstLine  = traverse docToChunks firstLineDoc
-    , indentOtherLines = traverse docToChunks otherLinesDoc
+    { 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]
 
 
 --------------------------------------------------------------------------------
diff --git a/lib/Patat/PrettyPrint/Internal.hs b/lib/Patat/PrettyPrint/Internal.hs
--- a/lib/Patat/PrettyPrint/Internal.hs
+++ b/lib/Patat/PrettyPrint/Internal.hs
@@ -15,11 +15,11 @@
     , DocE (..)
     , chunkToDocE
 
+    , Indentation (..)
+
     , Doc (..)
     , docToChunks
 
-    , Trimmable (..)
-
     , toString
     , dimensions
     , null
@@ -34,7 +34,7 @@
 --------------------------------------------------------------------------------
 import           Control.Monad.Reader       (asks, local)
 import           Control.Monad.RWS          (RWS, runRWS)
-import           Control.Monad.State        (get, gets, modify)
+import           Control.Monad.State        (get, modify)
 import           Control.Monad.Writer       (tell)
 import           Data.Char.WCWidth.Extended (wcstrwidth)
 import qualified Data.List                  as L
@@ -119,8 +119,8 @@
         , ansiDoc  :: d
         }
     | Indent
-        { indentFirstLine  :: LineBuffer
-        , indentOtherLines :: LineBuffer
+        { indentFirstLine  :: Indentation [Chunk]
+        , indentOtherLines :: Indentation [Chunk]
         , indentDoc        :: d
         }
     | Control Control
@@ -151,9 +151,9 @@
 
 --------------------------------------------------------------------------------
 data DocEnv = DocEnv
-    { deCodes  :: [Ansi.SGR]  -- ^ Most recent ones first in the list
-    , deIndent :: LineBuffer  -- ^ Don't need to store first-line indent
-    , deWrap   :: Maybe Int   -- ^ Wrap at columns
+    { 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
     }
 
 
@@ -162,33 +162,49 @@
 
 
 --------------------------------------------------------------------------------
-data Trimmable a
-    = NotTrimmable !a
-    | Trimmable    !a
+-- | 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)
 
 
 --------------------------------------------------------------------------------
--- | Note that this is reversed so we have fast append
-type LineBuffer = [Trimmable Chunk]
+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 = map trimmableToChunk . reverse . dropWhile isTrimmable
+bufferToChunks (LineBuffer _ ind chunks) = case chunks of
+    [] -> concatMap indentationToChunks $ reverse $
+        dropWhile emptyIndentation ind
+    _ -> concatMap indentationToChunks (reverse ind) ++ reverse chunks
   where
-    isTrimmable (NotTrimmable _) = False
-    isTrimmable (Trimmable    _) = True
-
-    trimmableToChunk (NotTrimmable c) = c
-    trimmableToChunk (Trimmable    c) = c
+    emptyIndentation (Indentation _ []) = True
+    emptyIndentation _                  = False
 
 
 --------------------------------------------------------------------------------
 docToChunks :: Doc -> Chunks
 docToChunks doc0 =
     let env0        = DocEnv [] [] Nothing
-        ((), b, cs) = runRWS (go $ unDoc doc0) env0 mempty in
+        ((), b, cs) = runRWS (go $ unDoc doc0) env0 emptyLineBuffer in
     optimizeChunks (cs <> bufferToChunks b)
   where
     go :: [DocE Doc] -> DocM ()
@@ -197,7 +213,7 @@
 
     go (String str : docs) = do
         chunk <- makeChunk str
-        modify (NotTrimmable chunk :)
+        appendChunk chunk
         go docs
 
     go (Softspace : docs) = do
@@ -206,7 +222,7 @@
 
     go (Hardspace : docs) = do
         chunk <- makeChunk " "
-        modify (NotTrimmable chunk :)
+        appendChunk chunk
         go docs
 
     go (Softline : docs) = do
@@ -216,12 +232,15 @@
     go (Hardline : docs) = do
         buffer <- get
         tell $ bufferToChunks buffer <> [NewlineChunk]
-        indentation <- asks deIndent
-        modify $ \_ -> if L.null docs then [] else indentation
+        ind <- asks deIndent
+        modify $ \_ -> case docs of
+            []    -> emptyLineBuffer
+            _ : _ -> LineBuffer (sum $ map indentationWidth ind) ind []
         go docs
 
     go (WrapAt {..} : docs) = do
-        local (\env -> env {deWrap = wrapAtCol}) $ go (unDoc wrapDoc)
+        il <- asks $ sum . map indentationWidth . deIndent
+        local (\env -> env {deWrap = fmap (+ il) wrapAtCol}) $ go (unDoc wrapDoc)
         go docs
 
     go (Ansi {..} : docs) = do
@@ -230,8 +249,9 @@
         go docs
 
     go (Indent {..} : docs) = do
-        local (\env -> env {deIndent = indentOtherLines ++ deIndent env}) $ do
-            modify (indentFirstLine ++)
+        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
 
@@ -245,6 +265,10 @@
         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
@@ -252,9 +276,7 @@
         case mbWrapCol of
             Nothing     -> return hard
             Just maxCol -> do
-                -- Slow.
-                currentLine <- gets (concatMap chunkToString . bufferToChunks)
-                let currentCol = wcstrwidth currentLine
+                LineBuffer currentCol _ _ <- get
                 case nextWordLength docs of
                     Nothing                            -> return hard
                     Just l
@@ -316,4 +338,5 @@
 
 --------------------------------------------------------------------------------
 string :: String -> Doc
-string = mkDoc . String  -- TODO (jaspervdj): Newline conversion?
+string ""  = Doc []
+string str = mkDoc $ String str  -- TODO (jaspervdj): Newline conversion?
diff --git a/patat.cabal b/patat.cabal
--- a/patat.cabal
+++ b/patat.cabal
@@ -1,16 +1,17 @@
-Name:                patat
-Version:             0.10.1.1
-Synopsis:            Terminal-based presentations using Pandoc
-Description:         Terminal-based presentations using Pandoc.
-License:             GPL-2
-License-file:        LICENSE
-Author:              Jasper Van der Jeugt <m@jaspervdj.be>
-Maintainer:          Jasper Van der Jeugt <m@jaspervdj.be>
-Homepage:            http://github.com/jaspervdj/patat
-Copyright:           2016 Jasper Van der Jeugt
-Category:            Text
-Build-type:          Simple
-Cabal-version:       >=1.10
+Name:          patat
+Version:       0.11.0.0
+Synopsis:      Terminal-based presentations using Pandoc
+Description:   Terminal-based presentations using Pandoc.
+License:       GPL-2
+License-file:  LICENSE
+Author:        Jasper Van der Jeugt <m@jaspervdj.be>
+Maintainer:    Jasper Van der Jeugt <m@jaspervdj.be>
+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
@@ -18,7 +19,7 @@
 
 Source-repository head
   Type:     git
-  Location: git://github.com/jaspervdj/patat.git
+  Location: https://github.com/jaspervdj/patat.git
 
 Flag patat-make-man
   Description: Build the executable to generate the man page
@@ -31,17 +32,17 @@
   Default-language:  Haskell2010
 
   Build-depends:
-    aeson                >= 2.0  && < 2.2,
+    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.12,
+    bytestring           >= 0.10 && < 0.13,
     colour               >= 2.3  && < 2.4,
     containers           >= 0.5  && < 0.7,
     directory            >= 1.2  && < 1.4,
-    filepath             >= 1.4  && < 1.5,
+    filepath             >= 1.4  && < 1.6,
     mtl                  >= 2.2  && < 2.4,
     optparse-applicative >= 0.16 && < 0.19,
     pandoc               >= 3.1  && < 3.2,
@@ -50,7 +51,7 @@
     random               >= 1.2  && < 1.3,
     skylighting          >= 0.10 && < 0.15,
     terminal-size        >= 0.3  && < 0.4,
-    text                 >= 1.2  && < 2.1,
+    text                 >= 1.2  && < 2.2,
     time                 >= 1.4  && < 1.13,
     unordered-containers >= 0.2  && < 0.3,
     yaml                 >= 0.8  && < 0.12,
@@ -128,11 +129,11 @@
 
   Build-depends:
     base         >= 4.9 && < 5,
-    containers   >= 0.6 && < 0.7,
+    containers   >= 0.6 && < 0.8,
     doctemplates >= 0.8 && < 0.12,
     mtl          >= 2.2 && < 2.4,
     pandoc       >= 3.1 && < 3.2,
-    text         >= 1.2 && < 2.1,
+    text         >= 1.2 && < 2.2,
     time         >= 1.6 && < 1.13
 
 Test-suite patat-tests
@@ -153,8 +154,8 @@
     base             >= 4.8  && < 5,
     directory        >= 1.2  && < 1.4,
     pandoc           >= 3.1  && < 3.2,
-    tasty            >= 1.2  && < 1.5,
+    tasty            >= 1.2  && < 1.6,
     tasty-hunit      >= 0.10 && < 0.11,
     tasty-quickcheck >= 0.10 && < 0.11,
-    text             >= 1.2  && < 2.1,
+    text             >= 1.2  && < 2.2,
     QuickCheck       >= 2.8  && < 2.15
