Blame src/Text/Pandoc/ImageSize.hs

Packit Service d2f85f
{-# LANGUAGE OverloadedStrings, ScopedTypeVariables, CPP #-}
Packit Service d2f85f
{-# OPTIONS_GHC -fno-warn-type-defaults #-}
Packit Service d2f85f
{-
Packit Service d2f85f
  Copyright (C) 2011-2017 John MacFarlane <jgm@berkeley.edu>
Packit Service d2f85f
Packit Service d2f85f
    This program is free software; you can redistribute it and/or modify
Packit Service d2f85f
    it under the terms of the GNU General Public License as published by
Packit Service d2f85f
    the Free Software Foundation; either version 2 of the License, or
Packit Service d2f85f
    (at your option) any later version.
Packit Service d2f85f
Packit Service d2f85f
    This program is distributed in the hope that it will be useful, but WITHOUT
Packit Service d2f85f
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
Packit Service d2f85f
    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
Packit Service d2f85f
    more details.
Packit Service d2f85f
Packit Service d2f85f
    You should have received a copy of the GNU General Public License along
Packit Service d2f85f
    with this program; if not, write to the Free Software Foundation, Inc., 59
Packit Service d2f85f
    Temple Place, Suite 330, Boston, MA  02111-1307  USA
Packit Service d2f85f
-}
Packit Service d2f85f
Packit Service d2f85f
{- |
Packit Service d2f85f
Module      : Text.Pandoc.ImageSize
Packit Service d2f85f
Copyright   : Copyright (C) 2011-2017 John MacFarlane
Packit Service d2f85f
License     : GNU GPL, version 2 or above
Packit Service d2f85f
Packit Service d2f85f
Maintainer  : John MacFarlane <jgm@berkeley.edu>
Packit Service d2f85f
Stability   : alpha
Packit Service d2f85f
Portability : portable
Packit Service d2f85f
Packit Service d2f85f
Functions for determining the size of a PNG, JPEG, or GIF image.
Packit Service d2f85f
-}
Packit Service d2f85f
module Text.Pandoc.ImageSize ( ImageType(..)
Packit Service d2f85f
                             , imageType
Packit Service d2f85f
                             , imageSize
Packit Service d2f85f
                             , sizeInPixels
Packit Service d2f85f
                             , sizeInPoints
Packit Service d2f85f
                             , desiredSizeInPoints
Packit Service d2f85f
                             , Dimension(..)
Packit Service d2f85f
                             , Direction(..)
Packit Service d2f85f
                             , dimension
Packit Service d2f85f
                             , lengthToDim
Packit Service d2f85f
                             , scaleDimension
Packit Service d2f85f
                             , inInch
Packit Service d2f85f
                             , inPixel
Packit Service d2f85f
                             , inPoints
Packit Service d2f85f
                             , inEm
Packit Service d2f85f
                             , numUnit
Packit Service d2f85f
                             , showInInch
Packit Service d2f85f
                             , showInPixel
Packit Service d2f85f
                             , showFl
Packit Service d2f85f
                             ) where
Packit Service d2f85f
import Data.ByteString (ByteString, unpack)
Packit Service d2f85f
import qualified Data.ByteString.Char8 as B
Packit Service d2f85f
import qualified Data.ByteString.Lazy as BL
Packit Service d2f85f
import Data.Char (isDigit)
Packit Service d2f85f
import Control.Monad
Packit Service d2f85f
import Data.Bits
Packit Service d2f85f
import Data.Binary
Packit Service d2f85f
import Data.Binary.Get
Packit Service d2f85f
import Text.Pandoc.Shared (safeRead)
Packit Service d2f85f
import Data.Default (Default)
Packit Service d2f85f
import Numeric (showFFloat)
Packit Service d2f85f
import Text.Pandoc.Definition
Packit Service d2f85f
import Text.Pandoc.Options
Packit Service d2f85f
import qualified Text.Pandoc.UTF8 as UTF8
Packit Service d2f85f
import qualified Text.XML.Light as Xml
Packit Service d2f85f
import qualified Data.Map as M
Packit Service d2f85f
import Control.Monad.Except
Packit Service d2f85f
import Data.Maybe (fromMaybe)
Packit Service d2f85f
Packit Service d2f85f
-- quick and dirty functions to get image sizes
Packit Service d2f85f
-- algorithms borrowed from wwwis.pl
Packit Service d2f85f
Packit Service d2f85f
data ImageType = Png | Gif | Jpeg | Svg | Pdf | Eps deriving Show
Packit Service d2f85f
data Direction = Width | Height
Packit Service d2f85f
instance Show Direction where
Packit Service d2f85f
  show Width  = "width"
Packit Service d2f85f
  show Height = "height"
Packit Service d2f85f
Packit Service d2f85f
data Dimension = Pixel Integer
Packit Service d2f85f
               | Centimeter Double
Packit Service d2f85f
               | Millimeter Double
Packit Service d2f85f
               | Inch Double
Packit Service d2f85f
               | Percent Double
Packit Service d2f85f
               | Em Double
Packit Service d2f85f
Packit Service d2f85f
instance Show Dimension where
Packit Service d2f85f
  show (Pixel a)      = show   a ++ "px"
Packit Service d2f85f
  show (Centimeter a) = showFl a ++ "cm"
Packit Service d2f85f
  show (Millimeter a) = showFl a ++ "mm"
Packit Service d2f85f
  show (Inch a)       = showFl a ++ "in"
Packit Service d2f85f
  show (Percent a)    = show   a ++ "%"
Packit Service d2f85f
  show (Em a)         = showFl a ++ "em"
Packit Service d2f85f
Packit Service d2f85f
data ImageSize = ImageSize{
Packit Service d2f85f
                     pxX   :: Integer
Packit Service d2f85f
                   , pxY   :: Integer
Packit Service d2f85f
                   , dpiX  :: Integer
Packit Service d2f85f
                   , dpiY  :: Integer
Packit Service d2f85f
                   } deriving (Read, Show, Eq)
Packit Service d2f85f
instance Default ImageSize where
Packit Service d2f85f
  def = ImageSize 300 200 72 72
Packit Service d2f85f
Packit Service d2f85f
showFl :: (RealFloat a) => a -> String
Packit Service d2f85f
showFl a = removeExtra0s $ showFFloat (Just 5) a ""
Packit Service d2f85f
Packit Service d2f85f
removeExtra0s :: String -> String
Packit Service d2f85f
removeExtra0s s =
Packit Service d2f85f
  case dropWhile (=='0') $ reverse s of
Packit Service d2f85f
       '.':xs -> reverse xs
Packit Service d2f85f
       xs     -> reverse xs
Packit Service d2f85f
Packit Service d2f85f
imageType :: ByteString -> Maybe ImageType
Packit Service d2f85f
imageType img = case B.take 4 img of
Packit Service d2f85f
                     "\x89\x50\x4e\x47" -> return Png
Packit Service d2f85f
                     "\x47\x49\x46\x38" -> return Gif
Packit Service d2f85f
                     "\xff\xd8\xff\xe0" -> return Jpeg  -- JFIF
Packit Service d2f85f
                     "\xff\xd8\xff\xe1" -> return Jpeg  -- Exif
Packit Service d2f85f
                     "%PDF"             -> return Pdf
Packit Service d2f85f
                     "<svg"             -> return Svg
Packit Service d2f85f
                     "
Packit Service d2f85f
                       | findSvgTag img
Packit Service d2f85f
                                        -> return Svg
Packit Service d2f85f
                     "%!PS"
Packit Service d2f85f
                       |  B.take 4 (B.drop 1 $ B.dropWhile (/=' ') img) == "EPSF"
Packit Service d2f85f
                                        -> return Eps
Packit Service d2f85f
                     _                  -> mzero
Packit Service d2f85f
Packit Service d2f85f
findSvgTag :: ByteString -> Bool
Packit Service d2f85f
findSvgTag img = "
Packit Service d2f85f
Packit Service d2f85f
imageSize :: WriterOptions -> ByteString -> Either String ImageSize
Packit Service d2f85f
imageSize opts img =
Packit Service d2f85f
  case imageType img of
Packit Service d2f85f
       Just Png  -> mbToEither "could not determine PNG size" $ pngSize img
Packit Service d2f85f
       Just Gif  -> mbToEither "could not determine GIF size" $ gifSize img
Packit Service d2f85f
       Just Jpeg -> jpegSize img
Packit Service d2f85f
       Just Svg  -> mbToEither "could not determine SVG size" $ svgSize opts img
Packit Service d2f85f
       Just Eps  -> mbToEither "could not determine EPS size" $ epsSize img
Packit Service d2f85f
       Just Pdf  -> Left "could not determine PDF size" -- TODO
Packit Service d2f85f
       Nothing   -> Left "could not determine image type"
Packit Service d2f85f
  where mbToEither msg Nothing  = Left msg
Packit Service d2f85f
        mbToEither _   (Just x) = Right x
Packit Service d2f85f
Packit Service d2f85f
defaultSize :: (Integer, Integer)
Packit Service d2f85f
defaultSize = (72, 72)
Packit Service d2f85f
Packit Service d2f85f
sizeInPixels :: ImageSize -> (Integer, Integer)
Packit Service d2f85f
sizeInPixels s = (pxX s, pxY s)
Packit Service d2f85f
Packit Service d2f85f
-- | Calculate (height, width) in points using the image file's dpi metadata,
Packit Service d2f85f
-- using 72 Points == 1 Inch.
Packit Service d2f85f
sizeInPoints :: ImageSize -> (Double, Double)
Packit Service d2f85f
sizeInPoints s = (pxXf * 72 / dpiXf, pxYf * 72 / dpiYf)
Packit Service d2f85f
  where
Packit Service d2f85f
    pxXf  = fromIntegral $ pxX s
Packit Service d2f85f
    pxYf  = fromIntegral $ pxY s
Packit Service d2f85f
    dpiXf = fromIntegral $ dpiX s
Packit Service d2f85f
    dpiYf = fromIntegral $ dpiY s
Packit Service d2f85f
Packit Service d2f85f
-- | Calculate (height, width) in points, considering the desired dimensions in the
Packit Service d2f85f
-- attribute, while falling back on the image file's dpi metadata if no dimensions
Packit Service d2f85f
-- are specified in the attribute (or only dimensions in percentages).
Packit Service d2f85f
desiredSizeInPoints :: WriterOptions -> Attr -> ImageSize -> (Double, Double)
Packit Service d2f85f
desiredSizeInPoints opts attr s =
Packit Service d2f85f
  case (getDim Width, getDim Height) of
Packit Service d2f85f
    (Just w, Just h)   -> (w, h)
Packit Service d2f85f
    (Just w, Nothing)  -> (w, w / ratio)
Packit Service d2f85f
    (Nothing, Just h)  -> (h * ratio, h)
Packit Service d2f85f
    (Nothing, Nothing) -> sizeInPoints s
Packit Service d2f85f
  where
Packit Service d2f85f
    ratio = fromIntegral (pxX s) / fromIntegral (pxY s)
Packit Service d2f85f
    getDim dir = case dimension dir attr of
Packit Service d2f85f
                   Just (Percent _) -> Nothing
Packit Service d2f85f
                   Just dim         -> Just $ inPoints opts dim
Packit Service d2f85f
                   Nothing          -> Nothing
Packit Service d2f85f
Packit Service d2f85f
inPoints :: WriterOptions -> Dimension -> Double
Packit Service d2f85f
inPoints opts dim = 72 * inInch opts dim
Packit Service d2f85f
Packit Service d2f85f
inEm :: WriterOptions -> Dimension -> Double
Packit Service d2f85f
inEm opts dim = (64/11) * inInch opts dim
Packit Service d2f85f
Packit Service d2f85f
inInch :: WriterOptions -> Dimension -> Double
Packit Service d2f85f
inInch opts dim =
Packit Service d2f85f
  case dim of
Packit Service d2f85f
    (Pixel a)      -> fromIntegral a / fromIntegral (writerDpi opts)
Packit Service d2f85f
    (Centimeter a) -> a * 0.3937007874
Packit Service d2f85f
    (Millimeter a) -> a * 0.03937007874
Packit Service d2f85f
    (Inch a)       -> a
Packit Service d2f85f
    (Percent _)    -> 0
Packit Service d2f85f
    (Em a)         -> a * (11/64)
Packit Service d2f85f
Packit Service d2f85f
inPixel :: WriterOptions -> Dimension -> Integer
Packit Service d2f85f
inPixel opts dim =
Packit Service d2f85f
  case dim of
Packit Service d2f85f
    (Pixel a)      -> a
Packit Service d2f85f
    (Centimeter a) -> floor $ dpi * a * 0.3937007874 :: Integer
Packit Service d2f85f
    (Millimeter a) -> floor $ dpi * a * 0.03937007874 :: Integer
Packit Service d2f85f
    (Inch a)       -> floor $ dpi * a :: Integer
Packit Service d2f85f
    (Percent _)    -> 0
Packit Service d2f85f
    (Em a)         -> floor $ dpi * a * (11/64) :: Integer
Packit Service d2f85f
  where
Packit Service d2f85f
    dpi = fromIntegral $ writerDpi opts
Packit Service d2f85f
Packit Service d2f85f
-- | Convert a Dimension to a String denoting its equivalent in inches, for example "2.00000".
Packit Service d2f85f
-- Note: Dimensions in percentages are converted to the empty string.
Packit Service d2f85f
showInInch :: WriterOptions -> Dimension -> String
Packit Service d2f85f
showInInch _ (Percent _) = ""
Packit Service d2f85f
showInInch opts dim = showFl $ inInch opts dim
Packit Service d2f85f
Packit Service d2f85f
-- | Convert a Dimension to a String denoting its equivalent in pixels, for example "600".
Packit Service d2f85f
-- Note: Dimensions in percentages are converted to the empty string.
Packit Service d2f85f
showInPixel :: WriterOptions -> Dimension -> String
Packit Service d2f85f
showInPixel _ (Percent _) = ""
Packit Service d2f85f
showInPixel opts dim = show $ inPixel opts dim
Packit Service d2f85f
Packit Service d2f85f
-- | Maybe split a string into a leading number and trailing unit, e.g. "3cm" to Just (3.0, "cm")
Packit Service d2f85f
numUnit :: String -> Maybe (Double, String)
Packit Service d2f85f
numUnit s =
Packit Service d2f85f
  let (nums, unit) = span (\c -> isDigit c || ('.'==c)) s
Packit Service d2f85f
  in  case safeRead nums of
Packit Service d2f85f
        Just n  -> Just (n, unit)
Packit Service d2f85f
        Nothing -> Nothing
Packit Service d2f85f
Packit Service d2f85f
-- | Scale a dimension by a factor.
Packit Service d2f85f
scaleDimension :: Double -> Dimension -> Dimension
Packit Service d2f85f
scaleDimension factor dim =
Packit Service d2f85f
  case dim of
Packit Service d2f85f
        Pixel x      -> Pixel (round $ factor * fromIntegral x)
Packit Service d2f85f
        Centimeter x -> Centimeter (factor * x)
Packit Service d2f85f
        Millimeter x -> Millimeter (factor * x)
Packit Service d2f85f
        Inch x       -> Inch (factor * x)
Packit Service d2f85f
        Percent x    -> Percent (factor * x)
Packit Service d2f85f
        Em x         -> Em (factor * x)
Packit Service d2f85f
Packit Service d2f85f
-- | Read a Dimension from an Attr attribute.
Packit Service d2f85f
-- `dimension Width attr` might return `Just (Pixel 3)` or for example `Just (Centimeter 2.0)`, etc.
Packit Service d2f85f
dimension :: Direction -> Attr -> Maybe Dimension
Packit Service d2f85f
dimension dir (_, _, kvs) =
Packit Service d2f85f
  case dir of
Packit Service d2f85f
    Width  -> extractDim "width"
Packit Service d2f85f
    Height -> extractDim "height"
Packit Service d2f85f
  where
Packit Service d2f85f
    extractDim key = lookup key kvs >>= lengthToDim
Packit Service d2f85f
Packit Service d2f85f
lengthToDim :: String -> Maybe Dimension
Packit Service d2f85f
lengthToDim s = numUnit s >>= uncurry toDim
Packit Service d2f85f
  where
Packit Service d2f85f
    toDim a "cm"   = Just $ Centimeter a
Packit Service d2f85f
    toDim a "mm"   = Just $ Millimeter a
Packit Service d2f85f
    toDim a "in"   = Just $ Inch a
Packit Service d2f85f
    toDim a "inch" = Just $ Inch a
Packit Service d2f85f
    toDim a "%"    = Just $ Percent a
Packit Service d2f85f
    toDim a "px"   = Just $ Pixel (floor a::Integer)
Packit Service d2f85f
    toDim a ""     = Just $ Pixel (floor a::Integer)
Packit Service d2f85f
    toDim a "pt"   = Just $ Inch (a / 72)
Packit Service d2f85f
    toDim a "pc"   = Just $ Inch (a / 6)
Packit Service d2f85f
    toDim a "em"   = Just $ Em a
Packit Service d2f85f
    toDim _ _      = Nothing
Packit Service d2f85f
Packit Service d2f85f
epsSize :: ByteString -> Maybe ImageSize
Packit Service d2f85f
epsSize img = do
Packit Service d2f85f
  let ls = takeWhile ("%" `B.isPrefixOf`) $ B.lines img
Packit Service d2f85f
  let ls' = dropWhile (not . ("%%BoundingBox:" `B.isPrefixOf`)) ls
Packit Service d2f85f
  case ls' of
Packit Service d2f85f
       []    -> mzero
Packit Service d2f85f
       (x:_) -> case B.words x of
Packit Service d2f85f
                     [_, _, _, ux, uy] -> do
Packit Service d2f85f
                        ux' <- safeRead $ B.unpack ux
Packit Service d2f85f
                        uy' <- safeRead $ B.unpack uy
Packit Service d2f85f
                        return ImageSize{
Packit Service d2f85f
                            pxX  = ux'
Packit Service d2f85f
                          , pxY  = uy'
Packit Service d2f85f
                          , dpiX = 72
Packit Service d2f85f
                          , dpiY = 72 }
Packit Service d2f85f
                     _ -> mzero
Packit Service d2f85f
Packit Service d2f85f
pngSize :: ByteString -> Maybe ImageSize
Packit Service d2f85f
pngSize img = do
Packit Service d2f85f
  let (h, rest) = B.splitAt 8 img
Packit Service d2f85f
  guard $ h == "\x8a\x4d\x4e\x47\x0d\x0a\x1a\x0a" ||
Packit Service d2f85f
          h == "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"
Packit Service d2f85f
  let (i, rest') = B.splitAt 4 $ B.drop 4 rest
Packit Service d2f85f
  guard $ i == "MHDR" || i == "IHDR"
Packit Service d2f85f
  let (sizes, rest'') = B.splitAt 8 rest'
Packit Service d2f85f
  (x,y) <- case map fromIntegral $unpack sizes of
Packit Service d2f85f
                ([w1,w2,w3,w4,h1,h2,h3,h4] :: [Integer]) -> return
Packit Service d2f85f
                    (shift w1 24 + shift w2 16 + shift w3 8 + w4,
Packit Service d2f85f
                     shift h1 24 + shift h2 16 + shift h3 8 + h4)
Packit Service d2f85f
                _ -> Nothing -- "PNG parse error"
Packit Service d2f85f
  let (dpix, dpiy) = findpHYs rest''
Packit Service d2f85f
  return ImageSize { pxX  = x, pxY = y, dpiX = dpix, dpiY = dpiy }
Packit Service d2f85f
Packit Service d2f85f
findpHYs :: ByteString -> (Integer, Integer)
Packit Service d2f85f
findpHYs x
Packit Service d2f85f
  | B.null x || "IDAT" `B.isPrefixOf` x = (72,72)
Packit Service d2f85f
  | "pHYs" `B.isPrefixOf` x =
Packit Service d2f85f
    let [x1,x2,x3,x4,y1,y2,y3,y4,u] =
Packit Service d2f85f
          map fromIntegral $ unpack $ B.take 9 $ B.drop 4 x
Packit Service d2f85f
        factor = if u == 1 -- dots per meter
Packit Service d2f85f
                    then \z -> z * 254 `div` 10000
Packit Service d2f85f
                    else const 72
Packit Service d2f85f
    in  ( factor $ shift x1 24 + shift x2 16 + shift x3 8 + x4,
Packit Service d2f85f
          factor $ shift y1 24 + shift y2 16 + shift y3 8 + y4 )
Packit Service d2f85f
  | otherwise = findpHYs $ B.drop 1 x  -- read another byte
Packit Service d2f85f
Packit Service d2f85f
gifSize :: ByteString -> Maybe ImageSize
Packit Service d2f85f
gifSize img = do
Packit Service d2f85f
  let (h, rest) = B.splitAt 6 img
Packit Service d2f85f
  guard $ h == "GIF87a" || h == "GIF89a"
Packit Service d2f85f
  case map fromIntegral $ unpack $ B.take 4 rest of
Packit Service d2f85f
       [w2,w1,h2,h1] -> return ImageSize {
Packit Service d2f85f
                          pxX  = shift w1 8 + w2,
Packit Service d2f85f
                          pxY  = shift h1 8 + h2,
Packit Service d2f85f
                          dpiX = 72,
Packit Service d2f85f
                          dpiY = 72
Packit Service d2f85f
                          }
Packit Service d2f85f
       _             -> Nothing -- "GIF parse error"
Packit Service d2f85f
Packit Service d2f85f
svgSize :: WriterOptions -> ByteString -> Maybe ImageSize
Packit Service d2f85f
svgSize opts img = do
Packit Service d2f85f
  doc <- Xml.parseXMLDoc $ UTF8.toString img
Packit Service d2f85f
  let dpi = fromIntegral $ writerDpi opts
Packit Service d2f85f
  let dirToInt dir = do
Packit Service d2f85f
        dim <- Xml.findAttrBy (== Xml.QName dir Nothing Nothing) doc >>= lengthToDim
Packit Service d2f85f
        return $ inPixel opts dim
Packit Service d2f85f
  w <- dirToInt "width"
Packit Service d2f85f
  h <- dirToInt "height"
Packit Service d2f85f
  return ImageSize {
Packit Service d2f85f
    pxX  = w
Packit Service d2f85f
  , pxY  = h
Packit Service d2f85f
  , dpiX = dpi
Packit Service d2f85f
  , dpiY = dpi
Packit Service d2f85f
  }
Packit Service d2f85f
Packit Service d2f85f
jpegSize :: ByteString -> Either String ImageSize
Packit Service d2f85f
jpegSize img =
Packit Service d2f85f
  let (hdr, rest) = B.splitAt 4 img
Packit Service d2f85f
  in if B.length rest < 14
Packit Service d2f85f
        then Left "unable to determine JPEG size"
Packit Service d2f85f
        else case hdr of
Packit Service d2f85f
               "\xff\xd8\xff\xe0" -> jfifSize rest
Packit Service d2f85f
               "\xff\xd8\xff\xe1" -> exifSize rest
Packit Service d2f85f
               _                  -> Left "unable to determine JPEG size"
Packit Service d2f85f
Packit Service d2f85f
jfifSize :: ByteString -> Either String ImageSize
Packit Service d2f85f
jfifSize rest =
Packit Service d2f85f
  let [dpiDensity,dpix1,dpix2,dpiy1,dpiy2] = map fromIntegral
Packit Service d2f85f
                                           $ unpack $ B.take 5 $B.drop 9 rest
Packit Service d2f85f
      factor = case dpiDensity of
Packit Service d2f85f
                    1 -> id
Packit Service d2f85f
                    2 -> \x -> x * 254 `div` 10
Packit Service d2f85f
                    _ -> const 72
Packit Service d2f85f
      dpix = factor (shift dpix1 8 + dpix2)
Packit Service d2f85f
      dpiy = factor (shift dpiy1 8 + dpiy2)
Packit Service d2f85f
  in case findJfifSize rest of
Packit Service d2f85f
       Left msg    -> Left msg
Packit Service d2f85f
       Right (w,h) ->Right ImageSize { pxX = w
Packit Service d2f85f
                                        , pxY = h
Packit Service d2f85f
                                        , dpiX = dpix
Packit Service d2f85f
                                        , dpiY = dpiy }
Packit Service d2f85f
Packit Service d2f85f
findJfifSize :: ByteString -> Either String (Integer,Integer)
Packit Service d2f85f
findJfifSize bs =
Packit Service d2f85f
  let bs' = B.dropWhile (=='\xff') $ B.dropWhile (/='\xff') bs
Packit Service d2f85f
  in case B.uncons bs' of
Packit Service d2f85f
       Just (c,bs'') | c >= '\xc0' && c <= '\xc3' ->
Packit Service d2f85f
         case map fromIntegral $ unpack $ B.take 4 $ B.drop 3 bs'' of
Packit Service d2f85f
              [h1,h2,w1,w2] -> Right (shift w1 8 + w2, shift h1 8 + h2)
Packit Service d2f85f
              _             -> Left "JFIF parse error"
Packit Service d2f85f
       Just (_,bs'') ->
Packit Service d2f85f
         case map fromIntegral $ unpack $ B.take 2 bs'' of
Packit Service d2f85f
              [c1,c2] ->
Packit Service d2f85f
                let len = shift c1 8 + c2
Packit Service d2f85f
                -- skip variables
Packit Service d2f85f
                in  findJfifSize $ B.drop len bs''
Packit Service d2f85f
              _       -> Left "JFIF parse error"
Packit Service d2f85f
       Nothing -> Left "Did not find JFIF length record"
Packit Service d2f85f
Packit Service d2f85f
runGet' :: Get (Either String a) -> BL.ByteString -> Either String a
Packit Service d2f85f
runGet' p bl =
Packit Service d2f85f
#if MIN_VERSION_binary(0,7,0)
Packit Service d2f85f
  case runGetOrFail p bl of
Packit Service d2f85f
       Left (_,_,msg) -> Left msg
Packit Service d2f85f
       Right (_,_,x)  -> x
Packit Service d2f85f
#else
Packit Service d2f85f
  runGet p bl
Packit Service d2f85f
#endif
Packit Service d2f85f
Packit Service d2f85f
Packit Service d2f85f
exifSize :: ByteString -> Either String ImageSize
Packit Service d2f85f
exifSize bs =runGet' header bl
Packit Service d2f85f
  where bl = BL.fromChunks [bs]
Packit Service d2f85f
        header = runExceptT $ exifHeader bl
Packit Service d2f85f
-- NOTE:  It would be nicer to do
Packit Service d2f85f
-- runGet ((Just <$> exifHeader) <|> return Nothing)
Packit Service d2f85f
-- which would prevent pandoc from raising an error when an exif header can't
Packit Service d2f85f
-- be parsed.  But we only get an Alternative instance for Get in binary 0.6,
Packit Service d2f85f
-- and binary 0.5 ships with ghc 7.6.
Packit Service d2f85f
Packit Service d2f85f
exifHeader :: BL.ByteString -> ExceptT String Get ImageSize
Packit Service d2f85f
exifHeader hdr = do
Packit Service d2f85f
  _app1DataSize <- lift getWord16be
Packit Service d2f85f
  exifHdr <- lift getWord32be
Packit Service d2f85f
  unless (exifHdr == 0x45786966) $ throwError "Did not find exif header"
Packit Service d2f85f
  zeros <- lift getWord16be
Packit Service d2f85f
  unless (zeros == 0) $ throwError "Expected zeros after exif header"
Packit Service d2f85f
  -- beginning of tiff header -- we read whole thing to use
Packit Service d2f85f
  -- in getting data from offsets:
Packit Service d2f85f
  let tiffHeader = BL.drop 8 hdr
Packit Service d2f85f
  byteAlign <- lift getWord16be
Packit Service d2f85f
  let bigEndian = byteAlign == 0x4d4d
Packit Service d2f85f
  let (getWord16, getWord32, getWord64) =
Packit Service d2f85f
        if bigEndian
Packit Service d2f85f
           then (getWord16be, getWord32be, getWord64be)
Packit Service d2f85f
           else (getWord16le, getWord32le, getWord64le)
Packit Service d2f85f
  let getRational = do
Packit Service d2f85f
        num <- getWord32
Packit Service d2f85f
        den <- getWord32
Packit Service d2f85f
        return $ fromIntegral num / fromIntegral den
Packit Service d2f85f
  tagmark <- lift getWord16
Packit Service d2f85f
  unless (tagmark == 0x002a) $ throwError "Failed alignment sanity check"
Packit Service d2f85f
  ifdOffset <- lift getWord32
Packit Service d2f85f
  lift $ skip (fromIntegral ifdOffset - 8) -- skip to IDF
Packit Service d2f85f
  numentries <- lift  getWord16
Packit Service d2f85f
  let ifdEntry :: ExceptT String Get (TagType, DataFormat)
Packit Service d2f85f
      ifdEntry = do
Packit Service d2f85f
       tag <- fromMaybe UnknownTagType . flip M.lookup tagTypeTable
Packit Service d2f85f
                <$> lift getWord16
Packit Service d2f85f
       dataFormat <- lift getWord16
Packit Service d2f85f
       numComponents <- lift getWord32
Packit Service d2f85f
       (fmt, bytesPerComponent) <-
Packit Service d2f85f
             case dataFormat of
Packit Service d2f85f
                  1  -> return (UnsignedByte <$> getWord8, 1)
Packit Service d2f85f
                  2  -> return (AsciiString <$>
Packit Service d2f85f
                                getLazyByteString
Packit Service d2f85f
                                (fromIntegral numComponents), 1)
Packit Service d2f85f
                  3  -> return (UnsignedShort <$> getWord16, 2)
Packit Service d2f85f
                  4  -> return (UnsignedLong <$> getWord32, 4)
Packit Service d2f85f
                  5  -> return (UnsignedRational <$> getRational, 8)
Packit Service d2f85f
                  6  -> return (SignedByte <$> getWord8, 1)
Packit Service d2f85f
                  7  -> return (Undefined <$> getLazyByteString
Packit Service d2f85f
                                (fromIntegral numComponents), 1)
Packit Service d2f85f
                  8  -> return (SignedShort <$> getWord16, 2)
Packit Service d2f85f
                  9  -> return (SignedLong <$> getWord32, 4)
Packit Service d2f85f
                  10 -> return (SignedRational <$> getRational, 8)
Packit Service d2f85f
                  11 -> return (SingleFloat <$> getWord32 {- TODO -}, 4)
Packit Service d2f85f
                  12 -> return (DoubleFloat <$> getWord64 {- TODO -}, 8)
Packit Service d2f85f
                  _  -> throwError $ "Unknown data format " ++ show dataFormat
Packit Service d2f85f
       let totalBytes = fromIntegral $ numComponents * bytesPerComponent
Packit Service d2f85f
       payload <- if totalBytes <= 4 -- data is right here
Packit Service d2f85f
                     then lift $ fmt <* skip (4 - totalBytes)
Packit Service d2f85f
                     else do  -- get data from offset
Packit Service d2f85f
                          offs <- lift getWord32
Packit Service d2f85f
                          let bytesAtOffset =
Packit Service d2f85f
                                 BL.take (fromIntegral totalBytes)
Packit Service d2f85f
                                 $ BL.drop (fromIntegral offs) tiffHeader
Packit Service d2f85f
                          case runGet' (Right <$> fmt) bytesAtOffset of
Packit Service d2f85f
                               Left msg -> throwError msg
Packit Service d2f85f
                               Right x  -> return x
Packit Service d2f85f
       return (tag, payload)
Packit Service d2f85f
  entries <- replicateM (fromIntegral numentries) ifdEntry
Packit Service d2f85f
  subentries <- case lookup ExifOffset entries of
Packit Service d2f85f
                      Just (UnsignedLong offset') -> do
Packit Service d2f85f
                        pos <- lift bytesRead
Packit Service d2f85f
                        lift $ skip (fromIntegral offset' - (fromIntegral pos - 8))
Packit Service d2f85f
                        numsubentries <- lift getWord16
Packit Service d2f85f
                        replicateM (fromIntegral numsubentries) ifdEntry
Packit Service d2f85f
                      _ -> return []
Packit Service d2f85f
  let allentries = entries ++ subentries
Packit Service d2f85f
  (wdth, hght) <- case (lookup ExifImageWidth allentries,
Packit Service d2f85f
                        lookup ExifImageHeight allentries) of
Packit Service d2f85f
                       (Just (UnsignedLong w), Just (UnsignedLong h)) ->
Packit Service d2f85f
                         return (fromIntegral w, fromIntegral h)
Packit Service d2f85f
                       _ -> return defaultSize
Packit Service d2f85f
                            -- we return a default width and height when
Packit Service d2f85f
                            -- the exif header doesn't contain these
Packit Service d2f85f
  let resfactor = case lookup ResolutionUnit allentries of
Packit Service d2f85f
                        Just (UnsignedShort 1) -> 100 / 254
Packit Service d2f85f
                        _ -> 1
Packit Service d2f85f
  let xres = maybe 72 (\(UnsignedRational x) -> floor $ x * resfactor)
Packit Service d2f85f
             $ lookup XResolution allentries
Packit Service d2f85f
  let yres = maybe 72 (\(UnsignedRational x) -> floor $ x * resfactor)
Packit Service d2f85f
             $ lookup YResolution allentries
Packit Service d2f85f
  return ImageSize{
Packit Service d2f85f
                    pxX  = wdth
Packit Service d2f85f
                  , pxY  = hght
Packit Service d2f85f
                  , dpiX = xres
Packit Service d2f85f
                  , dpiY = yres }
Packit Service d2f85f
Packit Service d2f85f
data DataFormat = UnsignedByte Word8
Packit Service d2f85f
                | AsciiString BL.ByteString
Packit Service d2f85f
                | UnsignedShort Word16
Packit Service d2f85f
                | UnsignedLong Word32
Packit Service d2f85f
                | UnsignedRational Rational
Packit Service d2f85f
                | SignedByte Word8
Packit Service d2f85f
                | Undefined BL.ByteString
Packit Service d2f85f
                | SignedShort Word16
Packit Service d2f85f
                | SignedLong Word32
Packit Service d2f85f
                | SignedRational Rational
Packit Service d2f85f
                | SingleFloat Word32
Packit Service d2f85f
                | DoubleFloat Word64
Packit Service d2f85f
                deriving (Show)
Packit Service d2f85f
Packit Service d2f85f
data TagType = ImageDescription
Packit Service d2f85f
             | Make
Packit Service d2f85f
             | Model
Packit Service d2f85f
             | Orientation
Packit Service d2f85f
             | XResolution
Packit Service d2f85f
             | YResolution
Packit Service d2f85f
             | ResolutionUnit
Packit Service d2f85f
             | Software
Packit Service d2f85f
             | DateTime
Packit Service d2f85f
             | WhitePoint
Packit Service d2f85f
             | PrimaryChromaticities
Packit Service d2f85f
             | YCbCrCoefficients
Packit Service d2f85f
             | YCbCrPositioning
Packit Service d2f85f
             | ReferenceBlackWhite
Packit Service d2f85f
             | Copyright
Packit Service d2f85f
             | ExifOffset
Packit Service d2f85f
             | ExposureTime
Packit Service d2f85f
             | FNumber
Packit Service d2f85f
             | ExposureProgram
Packit Service d2f85f
             | ISOSpeedRatings
Packit Service d2f85f
             | ExifVersion
Packit Service d2f85f
             | DateTimeOriginal
Packit Service d2f85f
             | DateTimeDigitized
Packit Service d2f85f
             | ComponentConfiguration
Packit Service d2f85f
             | CompressedBitsPerPixel
Packit Service d2f85f
             | ShutterSpeedValue
Packit Service d2f85f
             | ApertureValue
Packit Service d2f85f
             | BrightnessValue
Packit Service d2f85f
             | ExposureBiasValue
Packit Service d2f85f
             | MaxApertureValue
Packit Service d2f85f
             | SubjectDistance
Packit Service d2f85f
             | MeteringMode
Packit Service d2f85f
             | LightSource
Packit Service d2f85f
             | Flash
Packit Service d2f85f
             | FocalLength
Packit Service d2f85f
             | MakerNote
Packit Service d2f85f
             | UserComment
Packit Service d2f85f
             | FlashPixVersion
Packit Service d2f85f
             | ColorSpace
Packit Service d2f85f
             | ExifImageWidth
Packit Service d2f85f
             | ExifImageHeight
Packit Service d2f85f
             | RelatedSoundFile
Packit Service d2f85f
             | ExifInteroperabilityOffset
Packit Service d2f85f
             | FocalPlaneXResolution
Packit Service d2f85f
             | FocalPlaneYResolution
Packit Service d2f85f
             | FocalPlaneResolutionUnit
Packit Service d2f85f
             | SensingMethod
Packit Service d2f85f
             | FileSource
Packit Service d2f85f
             | SceneType
Packit Service d2f85f
             | UnknownTagType
Packit Service d2f85f
             deriving (Show, Eq, Ord)
Packit Service d2f85f
Packit Service d2f85f
tagTypeTable :: M.Map Word16 TagType
Packit Service d2f85f
tagTypeTable = M.fromList
Packit Service d2f85f
  [ (0x010e, ImageDescription)
Packit Service d2f85f
  , (0x010f, Make)
Packit Service d2f85f
  , (0x0110, Model)
Packit Service d2f85f
  , (0x0112, Orientation)
Packit Service d2f85f
  , (0x011a, XResolution)
Packit Service d2f85f
  , (0x011b, YResolution)
Packit Service d2f85f
  , (0x0128, ResolutionUnit)
Packit Service d2f85f
  , (0x0131, Software)
Packit Service d2f85f
  , (0x0132, DateTime)
Packit Service d2f85f
  , (0x013e, WhitePoint)
Packit Service d2f85f
  , (0x013f, PrimaryChromaticities)
Packit Service d2f85f
  , (0x0211, YCbCrCoefficients)
Packit Service d2f85f
  , (0x0213, YCbCrPositioning)
Packit Service d2f85f
  , (0x0214, ReferenceBlackWhite)
Packit Service d2f85f
  , (0x8298, Copyright)
Packit Service d2f85f
  , (0x8769, ExifOffset)
Packit Service d2f85f
  , (0x829a, ExposureTime)
Packit Service d2f85f
  , (0x829d, FNumber)
Packit Service d2f85f
  , (0x8822, ExposureProgram)
Packit Service d2f85f
  , (0x8827, ISOSpeedRatings)
Packit Service d2f85f
  , (0x9000, ExifVersion)
Packit Service d2f85f
  , (0x9003, DateTimeOriginal)
Packit Service d2f85f
  , (0x9004, DateTimeDigitized)
Packit Service d2f85f
  , (0x9101, ComponentConfiguration)
Packit Service d2f85f
  , (0x9102, CompressedBitsPerPixel)
Packit Service d2f85f
  , (0x9201, ShutterSpeedValue)
Packit Service d2f85f
  , (0x9202, ApertureValue)
Packit Service d2f85f
  , (0x9203, BrightnessValue)
Packit Service d2f85f
  , (0x9204, ExposureBiasValue)
Packit Service d2f85f
  , (0x9205, MaxApertureValue)
Packit Service d2f85f
  , (0x9206, SubjectDistance)
Packit Service d2f85f
  , (0x9207, MeteringMode)
Packit Service d2f85f
  , (0x9208, LightSource)
Packit Service d2f85f
  , (0x9209, Flash)
Packit Service d2f85f
  , (0x920a, FocalLength)
Packit Service d2f85f
  , (0x927c, MakerNote)
Packit Service d2f85f
  , (0x9286, UserComment)
Packit Service d2f85f
  , (0xa000, FlashPixVersion)
Packit Service d2f85f
  , (0xa001, ColorSpace)
Packit Service d2f85f
  , (0xa002, ExifImageWidth)
Packit Service d2f85f
  , (0xa003, ExifImageHeight)
Packit Service d2f85f
  , (0xa004, RelatedSoundFile)
Packit Service d2f85f
  , (0xa005, ExifInteroperabilityOffset)
Packit Service d2f85f
  , (0xa20e, FocalPlaneXResolution)
Packit Service d2f85f
  , (0xa20f, FocalPlaneYResolution)
Packit Service d2f85f
  , (0xa210, FocalPlaneResolutionUnit)
Packit Service d2f85f
  , (0xa217, SensingMethod)
Packit Service d2f85f
  , (0xa300, FileSource)
Packit Service d2f85f
  , (0xa301, SceneType)
Packit Service d2f85f
  ]