diff --git a/pandoc-lua-engine.cabal b/pandoc-lua-engine.cabal
--- a/pandoc-lua-engine.cabal
+++ b/pandoc-lua-engine.cabal
@@ -1,6 +1,6 @@
 cabal-version:       2.4
 name:                pandoc-lua-engine
-version:             0.2.1.4
+version:             0.2.1.5
 build-type:          Simple
 license:             GPL-2.0-or-later
 license-file:        COPYING.md
@@ -74,6 +74,8 @@
                      , Text.Pandoc.Lua.Marshal.CommonState
                      , Text.Pandoc.Lua.Marshal.Context
                      , Text.Pandoc.Lua.Marshal.Format
+                     , Text.Pandoc.Lua.Marshal.ImageSize
+                     , Text.Pandoc.Lua.Marshal.LogMessage
                      , Text.Pandoc.Lua.Marshal.PandocError
                      , Text.Pandoc.Lua.Marshal.ReaderOptions
                      , Text.Pandoc.Lua.Marshal.Reference
@@ -82,7 +84,9 @@
                      , Text.Pandoc.Lua.Marshal.WriterOptions
                      , Text.Pandoc.Lua.Module.CLI
                      , Text.Pandoc.Lua.Module.Format
+                     , Text.Pandoc.Lua.Module.Image
                      , Text.Pandoc.Lua.Module.JSON
+                     , Text.Pandoc.Lua.Module.Log
                      , Text.Pandoc.Lua.Module.MediaBag
                      , Text.Pandoc.Lua.Module.Pandoc
                      , Text.Pandoc.Lua.Module.Scaffolding
@@ -94,6 +98,7 @@
                      , Text.Pandoc.Lua.Module.Utils
                      , Text.Pandoc.Lua.Orphans
                      , Text.Pandoc.Lua.PandocLua
+                     , Text.Pandoc.Lua.SourcePos
                      , Text.Pandoc.Lua.Writer.Classic
                      , Text.Pandoc.Lua.Writer.Scaffolding
 
@@ -112,12 +117,12 @@
                      , hslua-module-system   >= 1.1     && < 1.2
                      , hslua-module-text     >= 1.1     && < 1.2
                      , hslua-module-version  >= 1.1     && < 1.2
-                     , hslua-module-zip      >= 1.1     && < 1.2
+                     , hslua-module-zip      >= 1.1.3   && < 1.2
                      , hslua-repl            >= 0.1.1   && < 0.2
                      , lpeg                  >= 1.1     && < 1.2
                      , mtl                   >= 2.2     && < 2.4
-                     , pandoc                >= 3.1.4   && < 3.2
-                     , pandoc-lua-marshal    >= 0.2.6   && < 0.3
+                     , pandoc                >= 3.1.4   && < 3.3
+                     , pandoc-lua-marshal    >= 0.2.7   && < 0.3
                      , pandoc-types          >= 1.22    && < 1.24
                      , parsec                >= 3.1     && < 3.2
                      , text                  >= 1.1.1   && < 2.2
diff --git a/src/Text/Pandoc/Lua.hs b/src/Text/Pandoc/Lua.hs
--- a/src/Text/Pandoc/Lua.hs
+++ b/src/Text/Pandoc/Lua.hs
@@ -2,7 +2,7 @@
 {-# LANGUAGE TypeApplications  #-}
 {- |
    Module      : Text.Pandoc.Lua
-   Copyright   : Copyright © 2017-2023 Albert Krewinkel
+   Copyright   : Copyright © 2017-2024 Albert Krewinkel
    License     : GNU GPL, version 2 or above
 
    Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
diff --git a/src/Text/Pandoc/Lua/Custom.hs b/src/Text/Pandoc/Lua/Custom.hs
--- a/src/Text/Pandoc/Lua/Custom.hs
+++ b/src/Text/Pandoc/Lua/Custom.hs
@@ -3,7 +3,7 @@
 {-# LANGUAGE TypeApplications    #-}
 {- |
    Module      : Text.Pandoc.Lua.Custom
-   Copyright   : © 2021-2023 Albert Krewinkel, John MacFarlane
+   Copyright   : © 2021-2024 Albert Krewinkel, John MacFarlane
    License     : GPL-2.0-or-later
    Maintainer  : Albert Krewinkel <albert+pandoc@tarleb.com>
 
diff --git a/src/Text/Pandoc/Lua/Filter.hs b/src/Text/Pandoc/Lua/Filter.hs
--- a/src/Text/Pandoc/Lua/Filter.hs
+++ b/src/Text/Pandoc/Lua/Filter.hs
@@ -4,8 +4,8 @@
 {-# LANGUAGE ScopedTypeVariables  #-}
 {- |
 Module      : Text.Pandoc.Lua.Filter
-Copyright   : © 2012-2023 John MacFarlane,
-              © 2017-2023 Albert Krewinkel
+Copyright   : © 2012-2024 John MacFarlane,
+              © 2017-2024 Albert Krewinkel
 License     : GNU GPL, version 2 or above
 Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
 Stability   : alpha
diff --git a/src/Text/Pandoc/Lua/Global.hs b/src/Text/Pandoc/Lua/Global.hs
--- a/src/Text/Pandoc/Lua/Global.hs
+++ b/src/Text/Pandoc/Lua/Global.hs
@@ -1,7 +1,7 @@
 {-# LANGUAGE OverloadedStrings  #-}
 {- |
    Module      : Text.Pandoc.Lua
-   Copyright   : Copyright © 2017-2023 Albert Krewinkel
+   Copyright   : Copyright © 2017-2024 Albert Krewinkel
    License     : GNU GPL, version 2 or above
 
    Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
diff --git a/src/Text/Pandoc/Lua/Init.hs b/src/Text/Pandoc/Lua/Init.hs
--- a/src/Text/Pandoc/Lua/Init.hs
+++ b/src/Text/Pandoc/Lua/Init.hs
@@ -3,7 +3,7 @@
 {-# LANGUAGE RankNTypes        #-}
 {- |
    Module      : Text.Pandoc.Lua
-   Copyright   : Copyright © 2017-2023 Albert Krewinkel
+   Copyright   : Copyright © 2017-2024 Albert Krewinkel
    License     : GNU GPL, version 2 or above
 
    Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
@@ -30,8 +30,7 @@
 import Text.Pandoc.Lua.Global (Global (..), setGlobals)
 import Text.Pandoc.Lua.Marshal.List (newListMetatable, pushListModule)
 import Text.Pandoc.Lua.PandocLua (PandocLua (..), liftPandocLua)
-import Text.Parsec.Pos (newPos)
-import Text.Read (readMaybe)
+import Text.Pandoc.Lua.SourcePos (luaSourcePos)
 import qualified Data.ByteString.Char8 as Char8
 import qualified Data.Text as T
 import qualified Lua.LPeg as LPeg
@@ -41,7 +40,9 @@
 import qualified HsLua.Module.Zip as Module.Zip
 import qualified Text.Pandoc.Lua.Module.CLI as Pandoc.CLI
 import qualified Text.Pandoc.Lua.Module.Format as Pandoc.Format
+import qualified Text.Pandoc.Lua.Module.Image as Pandoc.Image
 import qualified Text.Pandoc.Lua.Module.JSON as Pandoc.JSON
+import qualified Text.Pandoc.Lua.Module.Log as Pandoc.Log
 import qualified Text.Pandoc.Lua.Module.MediaBag as Pandoc.MediaBag
 import qualified Text.Pandoc.Lua.Module.Pandoc as Module.Pandoc
 import qualified Text.Pandoc.Lua.Module.Scaffolding as Pandoc.Scaffolding
@@ -91,7 +92,9 @@
 loadedModules =
   [ Pandoc.CLI.documentedModule
   , Pandoc.Format.documentedModule
+  , Pandoc.Image.documentedModule
   , Pandoc.JSON.documentedModule
+  , Pandoc.Log.documentedModule
   , Pandoc.MediaBag.documentedModule
   , Pandoc.Scaffolding.documentedModule
   , Pandoc.Structure.documentedModule
@@ -245,10 +248,5 @@
   -- 1: userdata wrapper function for the hook,
   -- 2: warn,
   -- 3: function calling warn.
-  where' 3
-  loc <- UTF8.toText <$> tostring' top
-  unPandocLua . report $ ScriptingWarning (UTF8.toText msg) (toSourcePos loc)
- where
-   toSourcePos loc = (T.breakOnEnd ":" <$> T.stripSuffix ": " loc)
-     >>= (\(prfx, sfx) -> (,) <$> T.unsnoc prfx <*> readMaybe (T.unpack sfx))
-     >>= \((source, _), line) -> Just $ newPos (T.unpack source) line 1
+  pos <- luaSourcePos 3
+  unPandocLua . report $ ScriptingWarning (UTF8.toText msg) pos
diff --git a/src/Text/Pandoc/Lua/Marshal/CommonState.hs b/src/Text/Pandoc/Lua/Marshal/CommonState.hs
--- a/src/Text/Pandoc/Lua/Marshal/CommonState.hs
+++ b/src/Text/Pandoc/Lua/Marshal/CommonState.hs
@@ -1,8 +1,8 @@
 {-# LANGUAGE OverloadedStrings    #-}
 {- |
    Module      : Text.Pandoc.Lua.Marshal.CommonState
-   Copyright   : © 2012-2023 John MacFarlane
-                 © 2017-2023 Albert Krewinkel
+   Copyright   : © 2012-2024 John MacFarlane
+                 © 2017-2024 Albert Krewinkel
    License     : GNU GPL, version 2 or above
    Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
    Stability   : alpha
@@ -17,9 +17,8 @@
 
 import HsLua
 import Text.Pandoc.Class (CommonState (..))
-import Text.Pandoc.Logging (LogMessage, showLogMessage)
 import Text.Pandoc.Lua.Marshal.List (pushPandocList)
-import qualified Data.Aeson as Aeson
+import Text.Pandoc.Lua.Marshal.LogMessage (pushLogMessage)
 
 -- | Lua type used for the @CommonState@ object.
 typeCommonState :: LuaError e => DocumentedType e CommonState
@@ -31,7 +30,7 @@
       (maybe pushnil pushString, stOutputFile)
 
   , readonly "log" "list of log messages"
-      (pushPandocList (pushUD typeLogMessage), stLog)
+      (pushPandocList pushLogMessage, stLog)
 
   , readonly "request_headers" "headers to add for HTTP requests"
       (pushPandocList (pushPair pushText pushText), stRequestHeaders)
@@ -58,16 +57,3 @@
 
 pushCommonState :: LuaError e => Pusher e CommonState
 pushCommonState = pushUD typeCommonState
-
-typeLogMessage :: LuaError e => DocumentedType e LogMessage
-typeLogMessage = deftype "pandoc LogMessage"
-  [ operation Index $ defun "__tostring"
-      ### liftPure showLogMessage
-      <#> udparam typeLogMessage "msg" "object"
-      =#> functionResult pushText "string" "stringified log message"
-  , operation (CustomOperation "__tojson") $ lambda
-      ### liftPure Aeson.encode
-      <#> udparam typeLogMessage "msg" "object"
-      =#> functionResult pushLazyByteString "string" "JSON encoded object"
-  ]
-  mempty -- no members
diff --git a/src/Text/Pandoc/Lua/Marshal/Context.hs b/src/Text/Pandoc/Lua/Marshal/Context.hs
--- a/src/Text/Pandoc/Lua/Marshal/Context.hs
+++ b/src/Text/Pandoc/Lua/Marshal/Context.hs
@@ -4,8 +4,8 @@
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 {- |
    Module      : Text.Pandoc.Lua.Marshaling.Context
-   Copyright   : © 2012-2023 John MacFarlane
-                 © 2017-2023 Albert Krewinkel
+   Copyright   : © 2012-2024 John MacFarlane
+                 © 2017-2024 Albert Krewinkel
    License     : GNU GPL, version 2 or above
 
    Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
diff --git a/src/Text/Pandoc/Lua/Marshal/Format.hs b/src/Text/Pandoc/Lua/Marshal/Format.hs
--- a/src/Text/Pandoc/Lua/Marshal/Format.hs
+++ b/src/Text/Pandoc/Lua/Marshal/Format.hs
@@ -4,7 +4,7 @@
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 {- |
    Module      : Text.Pandoc.Lua.Marshaling.Format
-   Copyright   : © 2022-2023 Albert Krewinkel
+   Copyright   : © 2022-2024 Albert Krewinkel
    License     : GPL-2.0-or-later
    Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
 
diff --git a/src/Text/Pandoc/Lua/Marshal/ImageSize.hs b/src/Text/Pandoc/Lua/Marshal/ImageSize.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Pandoc/Lua/Marshal/ImageSize.hs
@@ -0,0 +1,31 @@
+{-# LANGUAGE LambdaCase           #-}
+{-# LANGUAGE OverloadedStrings    #-}
+{- |
+   Module      : Text.Pandoc.Lua.Marshal.ImageSize
+   Copyright   : © 2024 Albert Krewinkel
+   License     : GPL-2.0-or-later
+   Maintainer  : Albert Krewinkel <albert+pandoc@tarleb.com>
+
+Marshaling image properties.
+-}
+module Text.Pandoc.Lua.Marshal.ImageSize
+  ( pushImageType
+  , pushImageSize
+  ) where
+
+import Data.Char (toLower)
+import HsLua
+import Text.Pandoc.ImageSize
+
+-- | Pushes an 'ImageType' as a string value.
+pushImageType :: LuaError e => Pusher e ImageType
+pushImageType = pushString . map toLower . show
+
+-- | Pushes a dimensional value.
+pushImageSize :: LuaError e => Pusher e ImageSize
+pushImageSize = pushAsTable
+  [ ("width", pushIntegral . pxX)
+  , ("height", pushIntegral . pxY)
+  , ("dpi_horz", pushIntegral . dpiX)
+  , ("dpi_vert", pushIntegral . dpiY)
+  ]
diff --git a/src/Text/Pandoc/Lua/Marshal/LogMessage.hs b/src/Text/Pandoc/Lua/Marshal/LogMessage.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Pandoc/Lua/Marshal/LogMessage.hs
@@ -0,0 +1,39 @@
+{-# LANGUAGE OverloadedStrings    #-}
+{- |
+   Module      : Text.Pandoc.Lua.Marshal.LogMessage
+   Copyright   : © 2017-2023 Albert Krewinkel
+   License     : GPL-2.0-or-later
+   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
+
+Pushing and retrieving of pandoc log messages.
+-}
+module Text.Pandoc.Lua.Marshal.LogMessage
+  ( peekLogMessage
+  , pushLogMessage
+  , typeLogMessage
+  ) where
+
+import HsLua
+import Text.Pandoc.Logging (LogMessage, showLogMessage)
+import qualified Data.Aeson as Aeson
+
+-- | Type definition for pandoc log messages.
+typeLogMessage :: LuaError e => DocumentedType e LogMessage
+typeLogMessage = deftype "pandoc LogMessage"
+  [ operation Index $ defun "__tostring"
+      ### liftPure showLogMessage
+      <#> udparam typeLogMessage "msg" "object"
+      =#> functionResult pushText "string" "stringified log message"
+  , operation (CustomOperation "__tojson") $ lambda
+      ### liftPure Aeson.encode
+      <#> udparam typeLogMessage "msg" "object"
+      =#> functionResult pushLazyByteString "string" "JSON encoded object"
+  ]
+  mempty -- no members
+
+-- | Pushes a LogMessage to the stack.
+pushLogMessage :: LuaError e => Pusher e LogMessage
+pushLogMessage = pushUD typeLogMessage
+
+peekLogMessage :: LuaError e => Peeker e LogMessage
+peekLogMessage  = peekUD typeLogMessage
diff --git a/src/Text/Pandoc/Lua/Marshal/PandocError.hs b/src/Text/Pandoc/Lua/Marshal/PandocError.hs
--- a/src/Text/Pandoc/Lua/Marshal/PandocError.hs
+++ b/src/Text/Pandoc/Lua/Marshal/PandocError.hs
@@ -3,7 +3,7 @@
 {-# LANGUAGE ScopedTypeVariables  #-}
 {- |
    Module      : Text.Pandoc.Lua.Marshal.PandocError
-   Copyright   : © 2020-2023 Albert Krewinkel
+   Copyright   : © 2020-2024 Albert Krewinkel
    License     : GNU GPL, version 2 or above
 
    Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
diff --git a/src/Text/Pandoc/Lua/Marshal/ReaderOptions.hs b/src/Text/Pandoc/Lua/Marshal/ReaderOptions.hs
--- a/src/Text/Pandoc/Lua/Marshal/ReaderOptions.hs
+++ b/src/Text/Pandoc/Lua/Marshal/ReaderOptions.hs
@@ -5,8 +5,8 @@
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 {- |
    Module      : Text.Pandoc.Lua.Marshaling.ReaderOptions
-   Copyright   : © 2012-2023 John MacFarlane
-                 © 2017-2023 Albert Krewinkel
+   Copyright   : © 2012-2024 John MacFarlane
+                 © 2017-2024 Albert Krewinkel
    License     : GNU GPL, version 2 or above
 
    Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
diff --git a/src/Text/Pandoc/Lua/Marshal/Reference.hs b/src/Text/Pandoc/Lua/Marshal/Reference.hs
--- a/src/Text/Pandoc/Lua/Marshal/Reference.hs
+++ b/src/Text/Pandoc/Lua/Marshal/Reference.hs
@@ -4,8 +4,8 @@
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 {- |
    Module      : Text.Pandoc.Lua.Marshaling.ReaderOptions
-   Copyright   : © 2012-2023 John MacFarlane
-                 © 2017-2023 Albert Krewinkel
+   Copyright   : © 2012-2024 John MacFarlane
+                 © 2017-2024 Albert Krewinkel
    License     : GNU GPL, version 2 or above
 
    Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
diff --git a/src/Text/Pandoc/Lua/Marshal/Sources.hs b/src/Text/Pandoc/Lua/Marshal/Sources.hs
--- a/src/Text/Pandoc/Lua/Marshal/Sources.hs
+++ b/src/Text/Pandoc/Lua/Marshal/Sources.hs
@@ -3,7 +3,7 @@
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 {- |
 Module      : Text.Pandoc.Lua.Marshaling.Sources
-Copyright   : © 2021-2023 Albert Krewinkel
+Copyright   : © 2021-2024 Albert Krewinkel
 License     : GNU GPL, version 2 or above
 Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
 
diff --git a/src/Text/Pandoc/Lua/Marshal/Template.hs b/src/Text/Pandoc/Lua/Marshal/Template.hs
--- a/src/Text/Pandoc/Lua/Marshal/Template.hs
+++ b/src/Text/Pandoc/Lua/Marshal/Template.hs
@@ -3,7 +3,7 @@
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 {- |
 Module      : Text.Pandoc.Lua.Marshal.Template
-Copyright   : © 2021-2023 Albert Krewinkel
+Copyright   : © 2021-2024 Albert Krewinkel
 License     : GNU GPL, version 2 or above
 Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
 
diff --git a/src/Text/Pandoc/Lua/Marshal/WriterOptions.hs b/src/Text/Pandoc/Lua/Marshal/WriterOptions.hs
--- a/src/Text/Pandoc/Lua/Marshal/WriterOptions.hs
+++ b/src/Text/Pandoc/Lua/Marshal/WriterOptions.hs
@@ -5,7 +5,7 @@
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 {- |
    Module      : Text.Pandoc.Lua.Marshaling.WriterOptions
-   Copyright   : © 2021-2023 Albert Krewinkel, John MacFarlane
+   Copyright   : © 2021-2024 Albert Krewinkel, John MacFarlane
    License     : GNU GPL, version 2 or above
 
    Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
diff --git a/src/Text/Pandoc/Lua/Module/CLI.hs b/src/Text/Pandoc/Lua/Module/CLI.hs
--- a/src/Text/Pandoc/Lua/Module/CLI.hs
+++ b/src/Text/Pandoc/Lua/Module/CLI.hs
@@ -2,7 +2,7 @@
 {-# LANGUAGE OverloadedStrings #-}
 {- |
    Module      : Text.Pandoc.Lua.Module.CLI
-   Copyright   : © 2022-2023 Albert Krewinkel
+   Copyright   : © 2022-2024 Albert Krewinkel
    License     : GPL-2.0-or-later
    Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
 
diff --git a/src/Text/Pandoc/Lua/Module/Format.hs b/src/Text/Pandoc/Lua/Module/Format.hs
--- a/src/Text/Pandoc/Lua/Module/Format.hs
+++ b/src/Text/Pandoc/Lua/Module/Format.hs
@@ -1,7 +1,7 @@
 {-# LANGUAGE OverloadedStrings #-}
 {- |
    Module      : Text.Pandoc.Lua.Module.Format
-   Copyright   : © 2022-2023 Albert Krewinkel
+   Copyright   : © 2022-2024 Albert Krewinkel
    License     : GPL-2.0-or-later
    Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
 
diff --git a/src/Text/Pandoc/Lua/Module/Image.hs b/src/Text/Pandoc/Lua/Module/Image.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Pandoc/Lua/Module/Image.hs
@@ -0,0 +1,98 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-|
+Module      : Text.Pandoc.Lua.Module.Image
+Copyright   : © 2024 Albert Krewinkel
+License     : MIT
+Maintainer  : Albert Krewinkel <albert+pandoc@tarleb.com>
+
+Lua module for basic image operations.
+-}
+module Text.Pandoc.Lua.Module.Image (
+  -- * Module
+    documentedModule
+
+  -- ** Functions
+  , size
+  , format
+  )
+where
+
+import Prelude hiding (null)
+import Data.Default (Default (def))
+import Data.Maybe (fromMaybe)
+import Data.Version (makeVersion)
+import HsLua.Core
+import HsLua.Marshalling
+import HsLua.Packaging
+import Text.Pandoc.Error (PandocError)
+import Text.Pandoc.ImageSize (imageType, imageSize)
+import Text.Pandoc.Lua.PandocLua ()
+import Text.Pandoc.Lua.Marshal.ImageSize (pushImageType, pushImageSize)
+import Text.Pandoc.Lua.Marshal.WriterOptions (peekWriterOptions)
+
+import qualified Data.Text as T
+
+-- | The @pandoc.image@ module specification.
+documentedModule :: Module PandocError
+documentedModule = Module
+  { moduleName = "pandoc.image"
+  , moduleDescription = "Basic image querying functions."
+  , moduleFields = fields
+  , moduleFunctions = functions
+  , moduleOperations = []
+  , moduleTypeInitializers = []
+  }
+
+--
+-- Fields
+--
+
+-- | Exported fields.
+fields :: LuaError e => [Field e]
+fields = []
+
+--
+-- Functions
+--
+
+functions :: [DocumentedFunction PandocError]
+functions =
+  [ size `since` makeVersion [3, 1, 13]
+  , format `since` makeVersion [3, 1, 13]
+  ]
+
+-- | Find the size of an image.
+size :: DocumentedFunction PandocError
+size = defun "size"
+  ### liftPure2 (\img mwriterOpts -> imageSize (fromMaybe def mwriterOpts) img)
+  <#> parameter peekByteString "string" "image" "image data"
+  <#> opt (parameter peekWriterOptions "WriterOptions|table" "opts"
+           "writer options")
+  =#> functionResult (either (failLua . T.unpack) pushImageSize) "table"
+        "image size information or error message"
+  #? T.unlines
+     [ "Returns a table containing the size and resolution of an image;"
+     , "throws an error if the given string is not an image, or if the size"
+     , "of the image cannot be determined."
+     , ""
+     , "The resulting table has four entires: *width*, *height*, *dpi\\_horz*,"
+     , "and *dpi\\_vert*."
+     , ""
+     , "The `opts` parameter, when given, should be either a WriterOptions"
+     , "object such as `PANDOC_WRITER_OPTIONS`, or a table with a `dpi` entry."
+     , "It affects the calculation for vector image formats such as SVG."
+     ]
+
+-- | Returns the format of an image.
+format :: LuaError e => DocumentedFunction e
+format = defun "format"
+  ### liftPure imageType
+  <#> parameter peekByteString "string" "image" "binary image data"
+  =#> functionResult (maybe pushnil pushImageType) "string|nil"
+        "image format, or nil if the format cannot be determined"
+  #? T.unlines
+     [ "Returns the format of an image as a lowercase string."
+     , ""
+     , "Formats recognized by pandoc include *png*, *gif*, *tiff*, *jpeg*,"
+     , "*pdf*, *svg*, *eps*, and *emf*."
+     ]
diff --git a/src/Text/Pandoc/Lua/Module/JSON.hs b/src/Text/Pandoc/Lua/Module/JSON.hs
--- a/src/Text/Pandoc/Lua/Module/JSON.hs
+++ b/src/Text/Pandoc/Lua/Module/JSON.hs
@@ -2,7 +2,7 @@
 {-# LANGUAGE OverloadedStrings #-}
 {-|
 Module      : Text.Pandoc.Lua.Module.JSON
-Copyright   : © 2022-2023 Albert Krewinkel
+Copyright   : © 2022-2024 Albert Krewinkel
 License     : MIT
 Maintainer  : Albert Krewinkel <albert@hslua.org>
 
@@ -93,11 +93,14 @@
            "whether to use pandoc types when possible.")
   =#> functionResult pure "any" "decoded object"
   #? T.unlines
-     [ "Creates a Lua object from a JSON string. The function returns an"
-     , "[[Inline]], [[Block]], [[Pandoc]], [[Inlines]], or [[Blocks]] element"
-     , "if the input can be decoded into represent any of those types."
-     , "Otherwise the default decoding is applied, using tables, booleans,"
-     , "numbers, and [null](#pandoc.json.null) to represent the JSON value."
+     [ "Creates a Lua object from a JSON string. If the input can be decoded"
+     , "as representing an [[Inline]], [[Block]], [[Pandoc]], [[Inlines]],"
+     , "or [[Blocks]] element the function will return an object of the"
+     , "appropriate type. Otherwise, if the input does not represent any"
+     , "of the AST types, the default decoding is applied: Objects and"
+     , "arrays are represented as tables, the JSON `null` value becomes"
+     , "[null](#pandoc.json.null), and JSON booleans, strings, and numbers"
+     , "are converted using the Lua types of the same name."
      , ""
      , "The special handling of AST elements can be disabled by setting"
      , "`pandoc_types` to `false`."
@@ -116,5 +119,5 @@
      , "If the object has a metamethod with name `__tojson`, then the"
      , "result is that of a call to that method with `object` passed as"
      , "the sole argument. The result of that call is expected to be a"
-     , "valid JSON string, but this not checked."
+     , "valid JSON string, but this is not checked."
      ]
diff --git a/src/Text/Pandoc/Lua/Module/Log.hs b/src/Text/Pandoc/Lua/Module/Log.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Pandoc/Lua/Module/Log.hs
@@ -0,0 +1,114 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeApplications  #-}
+{- |
+   Module      : Text.Pandoc.Lua.Module.Log
+   Copyright   : © 2024 Albert Krewinkel
+   License     : GPL-2.0-or-later
+   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
+
+Logging module.
+-}
+module Text.Pandoc.Lua.Module.Log
+  ( documentedModule
+  ) where
+
+import Data.Version (makeVersion)
+import HsLua
+import Text.Pandoc.Class
+  ( CommonState (stVerbosity, stLog)
+  , PandocMonad (putCommonState, getCommonState)
+  , report )
+import Text.Pandoc.Error (PandocError)
+import Text.Pandoc.Logging
+  ( Verbosity (ERROR)
+  , LogMessage (ScriptingInfo, ScriptingWarning) )
+import Text.Pandoc.Lua.Marshal.List (pushPandocList)
+import Text.Pandoc.Lua.Marshal.LogMessage (pushLogMessage)
+import Text.Pandoc.Lua.PandocLua (liftPandocLua, unPandocLua)
+import Text.Pandoc.Lua.SourcePos (luaSourcePos)
+import qualified Data.Text as T
+import qualified HsLua.Core.Utf8 as UTF8
+
+-- | Push the pandoc.log module on the Lua stack.
+documentedModule :: Module PandocError
+documentedModule = Module
+  { moduleName = "pandoc.log"
+  , moduleDescription =
+      "Access to pandoc's logging system."
+  , moduleFields = []
+  , moduleFunctions =
+      [ defun "info"
+        ### (\msg -> do
+                -- reporting levels:
+                -- 0: this function,
+                -- 1: userdata wrapper function for the function,
+                -- 2: function calling warn.
+                pos <- luaSourcePos 2
+                unPandocLua $ report $ ScriptingInfo (UTF8.toText msg) pos)
+        <#> parameter peekByteString "string" "message" "the info message"
+        =#> []
+        #? "Reports a ScriptingInfo message to pandoc's logging system."
+        `since` makeVersion [3, 2]
+
+      , defun "silence"
+        ### const silence
+        <#> parameter pure "function" "fn"
+              "function to be silenced"
+        =?> ("List of log messages triggered during the function call, " <>
+             "and any value returned by the function.")
+        #? T.unlines
+           [ "Applies the function to the given arguments while"
+           , "preventing log messages from being added to the log."
+           , "The warnings and info messages reported during the function"
+           , "call are returned as the first return value, with the"
+           , "results of the function call following thereafter."
+           ]
+        `since` makeVersion [3, 2]
+
+      , defun "warn"
+        ### (\msg -> do
+                -- reporting levels:
+                -- 0: this function,
+                -- 1: userdata wrapper function for the function,
+                -- 2: function calling warn.
+                pos <- luaSourcePos 2
+                unPandocLua $ report $ ScriptingWarning (UTF8.toText msg) pos)
+        <#> parameter peekByteString "string" "message"
+              "the warning message"
+        =#> []
+        #? T.unlines
+           [ "Reports a ScriptingWarning to pandoc's logging system."
+           , "The warning will be printed to stderr unless logging"
+           , "verbosity has been set to *ERROR*."
+           ]
+        `since` makeVersion [3, 2]
+      ]
+  , moduleOperations = []
+  , moduleTypeInitializers = []
+  }
+
+-- | Calls the function given as the first argument, but suppresses logging.
+-- Returns the list of generated log messages as the first result, and the other
+-- results of the function call after that.
+silence :: LuaE PandocError NumResults
+silence = unPandocLua $ do
+  -- get current log messages
+  origState <- getCommonState
+  let origLog = stLog origState
+  let origVerbosity = stVerbosity origState
+  putCommonState (origState { stLog = [], stVerbosity = ERROR })
+
+  -- call function given as the first argument
+  liftPandocLua $ do
+    nargs <- (NumArgs . subtract 1 . fromStackIndex) <$> gettop
+    call @PandocError nargs multret
+
+  -- restore original log messages
+  newState <- getCommonState
+  let newLog = stLog newState
+  putCommonState (newState { stLog = origLog, stVerbosity = origVerbosity })
+
+  liftPandocLua $ do
+    pushPandocList pushLogMessage newLog
+    insert 1
+    (NumResults . fromStackIndex) <$> gettop
diff --git a/src/Text/Pandoc/Lua/Module/MediaBag.hs b/src/Text/Pandoc/Lua/Module/MediaBag.hs
--- a/src/Text/Pandoc/Lua/Module/MediaBag.hs
+++ b/src/Text/Pandoc/Lua/Module/MediaBag.hs
@@ -2,7 +2,7 @@
 {-# LANGUAGE OverloadedStrings #-}
 {- |
    Module      : Text.Pandoc.Lua.Module.MediaBag
-   Copyright   : Copyright © 2017-2023 Albert Krewinkel
+   Copyright   : Copyright © 2017-2024 Albert Krewinkel
    License     : GNU GPL, version 2 or above
    Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
 
diff --git a/src/Text/Pandoc/Lua/Module/Pandoc.hs b/src/Text/Pandoc/Lua/Module/Pandoc.hs
--- a/src/Text/Pandoc/Lua/Module/Pandoc.hs
+++ b/src/Text/Pandoc/Lua/Module/Pandoc.hs
@@ -5,7 +5,7 @@
 {-# LANGUAGE TypeApplications  #-}
 {- |
    Module      : Text.Pandoc.Lua.Module.Pandoc
-   Copyright   : Copyright © 2017-2023 Albert Krewinkel
+   Copyright   : Copyright © 2017-2024 Albert Krewinkel
    License     : GNU GPL, version 2 or above
 
    Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
diff --git a/src/Text/Pandoc/Lua/Module/Scaffolding.hs b/src/Text/Pandoc/Lua/Module/Scaffolding.hs
--- a/src/Text/Pandoc/Lua/Module/Scaffolding.hs
+++ b/src/Text/Pandoc/Lua/Module/Scaffolding.hs
@@ -1,7 +1,7 @@
 {-# LANGUAGE OverloadedStrings #-}
 {- |
    Module      : Text.Pandoc.Lua.Module.Scaffolding
-   Copyright   : Copyright © 2022-2023 Albert Krewinkel, John MacFarlane
+   Copyright   : Copyright © 2022-2024 Albert Krewinkel, John MacFarlane
    License     : GNU GPL, version 2 or above
    Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
 
diff --git a/src/Text/Pandoc/Lua/Module/System.hs b/src/Text/Pandoc/Lua/Module/System.hs
--- a/src/Text/Pandoc/Lua/Module/System.hs
+++ b/src/Text/Pandoc/Lua/Module/System.hs
@@ -3,7 +3,7 @@
 {-# LANGUAGE TypeApplications    #-}
 {- |
    Module      : Text.Pandoc.Lua.Module.System
-   Copyright   : © 2019-2023 Albert Krewinkel
+   Copyright   : © 2019-2024 Albert Krewinkel
    License     : GNU GPL, version 2 or above
 
    Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
diff --git a/src/Text/Pandoc/Lua/Module/Template.hs b/src/Text/Pandoc/Lua/Module/Template.hs
--- a/src/Text/Pandoc/Lua/Module/Template.hs
+++ b/src/Text/Pandoc/Lua/Module/Template.hs
@@ -1,7 +1,7 @@
 {-# LANGUAGE OverloadedStrings #-}
 {- |
    Module      : Text.Pandoc.Lua.Module.Template
-   Copyright   : Copyright © 2022-2023 Albert Krewinkel, John MacFarlane
+   Copyright   : Copyright © 2022-2024 Albert Krewinkel, John MacFarlane
    License     : GNU GPL, version 2 or above
    Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
 
diff --git a/src/Text/Pandoc/Lua/Module/Types.hs b/src/Text/Pandoc/Lua/Module/Types.hs
--- a/src/Text/Pandoc/Lua/Module/Types.hs
+++ b/src/Text/Pandoc/Lua/Module/Types.hs
@@ -1,7 +1,7 @@
 {-# LANGUAGE OverloadedStrings #-}
 {- |
    Module      : Text.Pandoc.Lua.Module.Types
-   Copyright   : © 2019-2023 Albert Krewinkel
+   Copyright   : © 2019-2024 Albert Krewinkel
    License     : GNU GPL, version 2 or above
 
    Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
@@ -30,14 +30,14 @@
   , moduleFunctions =
       [ defun "Version"
         ### return
-        <#> parameter peekVersionFuzzy "string|integer|{integer,...}|Version"
+        <#> parameter peekVersionFuzzy "string|number|{integer,...}|Version"
               "version_specifier"
-              (mconcat [ "either a version string like `'2.7.3'`, "
-                       , "a single integer like `2`, "
-                       , "list of integers like `{2,7,3}`, "
-                       , "or a Version object"
+              (mconcat [ "A version string like `'2.7.3'`, "
+                       , "a Lua number like `2.0`, "
+                       , "a list of integers like `{2,7,3}`, "
+                       , "or a Version object."
                        ])
-        =#> functionResult pushVersion "Version" "A new Version object."
+        =#> functionResult pushVersion "Version" "New Version object."
         `since` makeVersion [2,7,3]
       ]
   , moduleOperations = []
diff --git a/src/Text/Pandoc/Lua/Module/Utils.hs b/src/Text/Pandoc/Lua/Module/Utils.hs
--- a/src/Text/Pandoc/Lua/Module/Utils.hs
+++ b/src/Text/Pandoc/Lua/Module/Utils.hs
@@ -4,7 +4,7 @@
 {-# LANGUAGE TypeApplications    #-}
 {- |
    Module      : Text.Pandoc.Lua.Module.Utils
-   Copyright   : Copyright © 2017-2023 Albert Krewinkel
+   Copyright   : Copyright © 2017-2024 Albert Krewinkel
    License     : GNU GPL, version 2 or above
 
    Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
@@ -369,10 +369,12 @@
   , "function."
   , ""
   , "Usage:"
+  , ""
   , "    -- Prints one of 'string', 'boolean', 'Inlines', 'Blocks',"
   , "    -- 'table', and 'nil', corresponding to the Haskell constructors"
   , "    -- MetaString, MetaBool, MetaInlines, MetaBlocks, MetaMap,"
   , "    -- and an unset value, respectively."
+  , ""
   , "    function Meta (meta)"
   , "      print('type of metavalue `author`:', pandoc.utils.type(meta.author))"
   , "    end"
diff --git a/src/Text/Pandoc/Lua/Orphans.hs b/src/Text/Pandoc/Lua/Orphans.hs
--- a/src/Text/Pandoc/Lua/Orphans.hs
+++ b/src/Text/Pandoc/Lua/Orphans.hs
@@ -2,8 +2,8 @@
 {-# LANGUAGE FlexibleInstances    #-}
 {- |
    Module      : Text.Pandoc.Lua.Orphans
-   Copyright   : © 2012-2023 John MacFarlane
-                 © 2017-2023 Albert Krewinkel
+   Copyright   : © 2012-2024 John MacFarlane
+                 © 2017-2024 Albert Krewinkel
    License     : GNU GPL, version 2 or above
 
    Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
diff --git a/src/Text/Pandoc/Lua/PandocLua.hs b/src/Text/Pandoc/Lua/PandocLua.hs
--- a/src/Text/Pandoc/Lua/PandocLua.hs
+++ b/src/Text/Pandoc/Lua/PandocLua.hs
@@ -7,7 +7,7 @@
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 {- |
    Module      : Text.Pandoc.Lua.PandocLua
-   Copyright   : © 2020-2023 Albert Krewinkel
+   Copyright   : © 2020-2024 Albert Krewinkel
    License     : GPL-2.0-or-later
    Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
 
diff --git a/src/Text/Pandoc/Lua/SourcePos.hs b/src/Text/Pandoc/Lua/SourcePos.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Pandoc/Lua/SourcePos.hs
@@ -0,0 +1,41 @@
+{-# LANGUAGE OverloadedStrings #-}
+{- |
+   Module      : Text.Pandoc.Lua.SourcePos
+   Copyright   : © 2024 Albert Krewinkel
+   License     : GPL-2.0-or-later
+   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
+
+Helper function to retrieve the 'SourcePos' in a Lua script.
+-}
+module Text.Pandoc.Lua.SourcePos
+  ( luaSourcePos
+  ) where
+
+import HsLua
+import Text.Parsec.Pos (SourcePos, newPos)
+import Text.Read (readMaybe)
+import qualified Data.Text as T
+import qualified HsLua.Core.Utf8 as UTF8
+
+-- | Returns the current position in a Lua script.
+--
+-- The reporting level is the level of the call stack, for which the
+-- position should be reported. There might not always be a position
+-- available, e.g., in C functions.
+luaSourcePos :: LuaError e
+             => Int                -- ^ reporting level
+             -> LuaE e (Maybe SourcePos)
+luaSourcePos lvl = do
+  -- reporting levels:
+  -- 0: this hook,
+  -- 1: userdata wrapper function for the hook,
+  -- 2: warn,
+  -- 3: function calling warn.
+  where' lvl
+  locStr <- UTF8.toText <$> tostring' top
+  return $ do
+    (prfx, sfx) <- T.breakOnEnd ":" <$> T.stripSuffix ": " locStr
+    (source, _) <- T.unsnoc prfx
+    line <- readMaybe (T.unpack sfx)
+    -- We have no column information, so always use column 1
+    Just $ newPos (T.unpack source) line 1
diff --git a/src/Text/Pandoc/Lua/Writer/Classic.hs b/src/Text/Pandoc/Lua/Writer/Classic.hs
--- a/src/Text/Pandoc/Lua/Writer/Classic.hs
+++ b/src/Text/Pandoc/Lua/Writer/Classic.hs
@@ -6,7 +6,7 @@
 {-# LANGUAGE TypeApplications    #-}
 {- |
    Module      : Text.Pandoc.Lua.Writer.Classic
-   Copyright   : Copyright (C) 2012-2023 John MacFarlane
+   Copyright   : Copyright (C) 2012-2024 John MacFarlane
    License     : GNU GPL, version 2 or above
 
    Maintainer  : John MacFarlane <jgm@berkeley.edu>
diff --git a/src/Text/Pandoc/Lua/Writer/Scaffolding.hs b/src/Text/Pandoc/Lua/Writer/Scaffolding.hs
--- a/src/Text/Pandoc/Lua/Writer/Scaffolding.hs
+++ b/src/Text/Pandoc/Lua/Writer/Scaffolding.hs
@@ -2,7 +2,7 @@
 {-# LANGUAGE OverloadedStrings   #-}
 {- |
    Module      : Text.Pandoc.Lua.Writer.Scaffolding
-   Copyright   : © 2022-2023 Albert Krewinkel
+   Copyright   : © 2022-2024 Albert Krewinkel
    License     : GPL-2.0-or-later
    Maintainer  : Albert Krewinkel <pandoc@tarleb.com>
 
diff --git a/test/Tests/Lua.hs b/test/Tests/Lua.hs
--- a/test/Tests/Lua.hs
+++ b/test/Tests/Lua.hs
@@ -3,7 +3,7 @@
 {-# LANGUAGE TypeApplications    #-}
 {- |
    Module      : Tests.Lua
-   Copyright   : © 2017-2023 Albert Krewinkel
+   Copyright   : © 2017-2024 Albert Krewinkel
    License     : GNU GPL, version 2 or above
 
    Maintainer  : Albert Krewinkel <albert@zeitkraut.de>
@@ -24,10 +24,12 @@
                             linebreak, math, orderedList, para, plain, rawBlock,
                             singleQuoted, space, str, strong,
                             HasMeta (setMeta))
-import Text.Pandoc.Class (runIOorExplode, setUserDataDir)
+import Text.Pandoc.Class ( CommonState (stVerbosity)
+                         , modifyCommonState, runIOorExplode, setUserDataDir)
 import Text.Pandoc.Definition (Attr, Block (BlockQuote, Div, Para), Pandoc,
                                Inline (Emph, Str), pandocTypesVersion)
 import Text.Pandoc.Error (PandocError (PandocLuaError))
+import Text.Pandoc.Logging (Verbosity (ERROR))
 import Text.Pandoc.Lua (Global (..), applyFilter, runLua, setGlobals)
 import Text.Pandoc.Options (def)
 import Text.Pandoc.Version (pandocVersionText)
@@ -238,6 +240,9 @@
 
 runLuaTest :: HasCallStack => Lua.LuaE PandocError a -> IO a
 runLuaTest op = runIOorExplode $ do
+  -- Disable printing of warnings on stderr: some tests will generate
+  -- warnings, we don't want to see those messages.
+  modifyCommonState $ \st -> st { stVerbosity = ERROR }
   res <- runLua $ do
     setGlobals [ PANDOC_WRITER_OPTIONS def ]
     op
diff --git a/test/Tests/Lua/Module.hs b/test/Tests/Lua/Module.hs
--- a/test/Tests/Lua/Module.hs
+++ b/test/Tests/Lua/Module.hs
@@ -1,6 +1,6 @@
 {- |
 Module      : Tests.Lua.Module
-Copyright   : © 2019-2023 Albert Krewinkel
+Copyright   : © 2019-2024 Albert Krewinkel
 License     : GNU GPL, version 2 or above
 
 Maintainer  : Albert Krewinkel <albert@zeitkraut.de>
@@ -25,8 +25,12 @@
                   ("lua" </> "module" </> "pandoc-list.lua")
   , testPandocLua "pandoc.format"
                   ("lua" </> "module" </> "pandoc-format.lua")
+  , testPandocLua "pandoc.image"
+                  ("lua" </> "module" </> "pandoc-image.lua")
   , testPandocLua "pandoc.json"
                   ("lua" </> "module" </> "pandoc-json.lua")
+  , testPandocLua "pandoc.log"
+                  ("lua" </> "module" </> "pandoc-log.lua")
   , testPandocLua "pandoc.mediabag"
                   ("lua" </> "module" </> "pandoc-mediabag.lua")
   , testPandocLua "pandoc.path"
diff --git a/test/Tests/Lua/Reader.hs b/test/Tests/Lua/Reader.hs
--- a/test/Tests/Lua/Reader.hs
+++ b/test/Tests/Lua/Reader.hs
@@ -1,7 +1,7 @@
 {-# LANGUAGE LambdaCase        #-}
 {- |
 Module      : Tests.Lua.Reader
-Copyright   : © 2022-2023 Albert Krewinkel
+Copyright   : © 2022-2024 Albert Krewinkel
 License     : GPL-2.0-or-later
 Maintainer  : Albert Krewinkel <pandoc@tarleb.com>
 
diff --git a/test/Tests/Lua/Writer.hs b/test/Tests/Lua/Writer.hs
--- a/test/Tests/Lua/Writer.hs
+++ b/test/Tests/Lua/Writer.hs
@@ -2,7 +2,7 @@
 {-# LANGUAGE OverloadedStrings #-}
 {- |
 Module      : Tests.Lua.Writer
-Copyright   : © 2019-2023 Albert Krewinkel
+Copyright   : © 2019-2024 Albert Krewinkel
 License     : GNU GPL, version 2 or above
 Maintainer  : Albert Krewinkel <albert@zeitkraut.de>
 
diff --git a/test/lua/module/pandoc-image.lua b/test/lua/module/pandoc-image.lua
new file mode 100644
--- /dev/null
+++ b/test/lua/module/pandoc-image.lua
@@ -0,0 +1,68 @@
+--
+-- Tests for the system module
+--
+local image = require 'pandoc.image'
+local tasty = require 'tasty'
+
+local group = tasty.test_group
+local test = tasty.test_case
+local assert = tasty.assert
+
+local svg_image = [==[<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     height="70" width="70"
+     viewBox="-35 -35 70 70">
+  <title>test</title>
+  <!-- document shape -->
+  <polygon points="-10,-31.53 -10,-3.25 0,0 10,-3.25 10,-23.53 2,-31.53" />
+</svg>
+]==]
+
+return {
+  -- Check existence of static fields
+  group 'static fields' {
+  },
+
+  group 'size' {
+    test('returns a table', function ()
+      local imgsize = {
+        width = 70,
+        height = 70,
+        dpi_horz = 96,
+        dpi_vert = 96,
+      }
+      assert.are_same(image.size(svg_image), imgsize)
+    end),
+    test('fails on faulty eps', function ()
+      assert.error_matches(
+        function () image.size('%!PS EPSF') end,
+        'could not determine EPS size'
+      )
+    end),
+    test('fails if input is not an image', function ()
+      assert.error_matches(
+        function () image.size('not an image') end,
+        'could not determine image type'
+      )
+    end),
+    test('respects the dpi setting', function ()
+      local imgsize = {
+        width = 70,
+        height = 70,
+        dpi_horz = 300,
+        dpi_vert = 300,
+      }
+      assert.are_same(image.size(svg_image, {dpi=300}), imgsize)
+    end),
+  },
+
+  group 'format' {
+    test('SVG', function ()
+      assert.are_equal(image.format(svg_image), 'svg')
+    end),
+    test('returns nil if input is not an image', function ()
+      assert.is_nil(image.format('not an image'))
+    end),
+  },
+}
diff --git a/test/lua/module/pandoc-log.lua b/test/lua/module/pandoc-log.lua
new file mode 100644
--- /dev/null
+++ b/test/lua/module/pandoc-log.lua
@@ -0,0 +1,75 @@
+--
+-- Tests for the pandoc.log module
+--
+-- =========================================
+-- PLEASE BE CAREFUL WHEN UPDATING THE TESTS
+-- =========================================
+--
+-- Some tests here are very, very fragile, as their correctness depends on the
+-- correct line number in this file.
+local log = require 'pandoc.log'
+local json = require 'pandoc.json'
+local tasty = require 'tasty'
+
+local group = tasty.test_group
+local test = tasty.test_case
+local assert = tasty.assert
+
+return {
+  group 'info' {
+    test('is a function', function ()
+           assert.are_equal(type(log.info), 'function')
+    end),
+    test('reports a warning', function ()
+      log.info('info test')
+      local msg = json.decode(json.encode(PANDOC_STATE.log[1]))
+      assert.are_equal(msg.message, 'info test')
+      assert.are_equal(msg.type, 'ScriptingInfo')
+    end),
+    test('info includes the correct number', function ()
+      log.info('line number test')
+      local msg = json.decode(json.encode(PANDOC_STATE.log[1]))
+      -- THIS NEEDS UPDATING if lines above are shifted.
+      assert.are_equal(msg.line, 30)
+    end),
+  },
+
+  group 'warn' {
+    test('is a function', function ()
+      assert.are_equal(type(log.warn), 'function')
+    end),
+    test('reports a warning', function ()
+      log.warn('testing')
+      local msg = json.decode(json.encode(PANDOC_STATE.log[1]))
+      assert.are_equal(msg.message, 'testing')
+      assert.are_equal(msg.type, 'ScriptingWarning')
+    end),
+  },
+
+  group 'silence' {
+    test('prevents info from being logged', function ()
+      local current_messages = PANDOC_STATE.log
+      log.silence(log.info, 'Just so you know')
+      assert.are_same(#current_messages, #PANDOC_STATE.log)
+      for i = 1, #current_messages do
+        assert.are_equal(
+          json.encode(current_messages[i]),
+          json.encode(PANDOC_STATE.log[i])
+        )
+      end
+    end),
+    test('returns the messages raised by the called function', function ()
+           local msgs = log.silence(log.info, 'Just so you know')
+           local msg = json.decode(json.encode(msgs[1]))
+           assert.are_equal(msg.message, 'Just so you know')
+    end),
+    test('returns function application results as additional return values',
+         function ()
+           local l, x, y = log.silence(function (a, b) return b, a + b end, 5, 8)
+           assert.are_same(l, {})
+           assert.are_equal(x, 8)
+        assert.are_equal(y, 13)
+      end
+    )
+  }
+}
