From 99ba09cff4143ff5fa2ebca02e9dd28712ed17b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= Date: Tue, 26 Dec 2023 17:19:56 +0000 Subject: [PATCH 1/5] log(svgToPng): log calls to rsvg-convert with --trace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Edwin Török --- src/Text/Pandoc/Image.hs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Text/Pandoc/Image.hs b/src/Text/Pandoc/Image.hs index efb41c87c8ed..0c228250eff4 100644 --- a/src/Text/Pandoc/Image.hs +++ b/src/Text/Pandoc/Image.hs @@ -18,18 +18,22 @@ import Data.Text (Text) import Text.Pandoc.Shared (tshow) import qualified Control.Exception as E import Control.Monad.IO.Class (MonadIO(liftIO)) +import Text.Pandoc.Class.PandocMonad +import qualified Data.Text as T -- | Convert svg image to png. rsvg-convert -- is used and must be available on the path. -svgToPng :: MonadIO m +svgToPng :: (PandocMonad m, MonadIO m) => Int -- ^ DPI -> L.ByteString -- ^ Input image as bytestring -> m (Either Text L.ByteString) svgToPng dpi bs = do let dpi' = show dpi + let args = ["-f","png","-a","--dpi-x",dpi',"--dpi-y",dpi'] + trace (T.intercalate " " $ map T.pack $ "rsvg-convert" : args) liftIO $ E.catch (do (exit, out) <- pipeProcess Nothing "rsvg-convert" - ["-f","png","-a","--dpi-x",dpi',"--dpi-y",dpi'] + args bs return $ if exit == ExitSuccess then Right out From 6483e2f83c85788aed8100c8d453fcdbc463c372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= Date: Wed, 27 Dec 2023 10:29:23 +0000 Subject: [PATCH 2/5] refactor(svgToPng): introduce PandocMonad.svgToPng MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This will be needed to run the conversion inside the PandocMonad, where we know the desired image size. The arguments are: (dpi, width, height). The width and height is optional to more easily convert existing code. [API change] Signed-off-by: Edwin Török --- pandoc-lua-engine/src/Text/Pandoc/Lua/PandocLua.hs | 1 + src/Text/Pandoc/App.hs | 5 ++--- src/Text/Pandoc/Class/IO.hs | 7 +++++++ src/Text/Pandoc/Class/PandocIO.hs | 1 + src/Text/Pandoc/Class/PandocMonad.hs | 5 +++++ src/Text/Pandoc/Class/PandocPure.hs | 2 ++ src/Text/Pandoc/Image.hs | 11 ++++++++--- 7 files changed, 26 insertions(+), 6 deletions(-) diff --git a/pandoc-lua-engine/src/Text/Pandoc/Lua/PandocLua.hs b/pandoc-lua-engine/src/Text/Pandoc/Lua/PandocLua.hs index 242281b08cf7..972c6706adab 100644 --- a/pandoc-lua-engine/src/Text/Pandoc/Lua/PandocLua.hs +++ b/pandoc-lua-engine/src/Text/Pandoc/Lua/PandocLua.hs @@ -70,6 +70,7 @@ instance PandocMonad PandocLua where readFileLazy = IO.readFileLazy readFileStrict = IO.readFileStrict readStdinStrict = IO.readStdinStrict + svgToPng = IO.svgToPng glob = IO.glob fileExists = IO.fileExists diff --git a/src/Text/Pandoc/App.hs b/src/Text/Pandoc/App.hs index 0d76a6f95d17..b2bc5735f154 100644 --- a/src/Text/Pandoc/App.hs +++ b/src/Text/Pandoc/App.hs @@ -50,7 +50,6 @@ import qualified System.IO as IO (Newline (..)) import Text.Pandoc import Text.Pandoc.Builder (setMeta) import Text.Pandoc.MediaBag (mediaItems) -import Text.Pandoc.Image (svgToPng) import Text.Pandoc.App.Opt (Opt (..), LineEnding (..), defaultOpts, IpynbOutput (..), OptInfo(..)) import Text.Pandoc.App.CommandLineOptions (parseOptions, parseOptionsFromArgs, @@ -368,14 +367,14 @@ readAbbreviations mbfilepath = >>= fmap (Set.fromList . filter (not . T.null) . T.lines) . toTextM (fromMaybe mempty mbfilepath) -createPngFallbacks :: (PandocMonad m, MonadIO m) => Int -> m () +createPngFallbacks :: (PandocMonad m) => Int -> m () createPngFallbacks dpi = do -- create fallback pngs for svgs items <- mediaItems <$> getMediaBag forM_ items $ \(fp, mt, bs) -> case T.takeWhile (/=';') mt of "image/svg+xml" -> do - res <- svgToPng dpi bs + res <- svgToPng dpi Nothing Nothing bs case res of Right bs' -> do let fp' = fp <> ".png" diff --git a/src/Text/Pandoc/Class/IO.hs b/src/Text/Pandoc/Class/IO.hs index 2d337d942779..5656e4b23f98 100644 --- a/src/Text/Pandoc/Class/IO.hs +++ b/src/Text/Pandoc/Class/IO.hs @@ -31,6 +31,7 @@ module Text.Pandoc.Class.IO , readFileLazy , readFileStrict , readStdinStrict + , svgToPng , extractMedia , writeMedia ) where @@ -84,6 +85,7 @@ import qualified System.Random import qualified Text.Pandoc.UTF8 as UTF8 import Data.Default (def) import System.X509 (getSystemCertificateStore) +import Text.Pandoc.Image (svgToPngIO) #ifndef EMBED_DATA_FILES import qualified Paths_pandoc as Paths #endif @@ -184,6 +186,11 @@ readFileStrict s = liftIOError B.readFile s readStdinStrict :: (PandocMonad m, MonadIO m) => m B.ByteString readStdinStrict = liftIOError (const B.getContents) "stdin" +-- | Runs an image conversion step, returning an error on failure. +-- Not available when sandboxed. +svgToPng :: (PandocMonad m, MonadIO m) => Int -> Maybe Double -> Maybe Double -> BL.ByteString -> m (Either T.Text BL.ByteString) +svgToPng = svgToPngIO + -- | Return a list of paths that match a glob, relative to the working -- directory. See 'System.FilePath.Glob' for the glob syntax. glob :: (PandocMonad m, MonadIO m) => String -> m [FilePath] diff --git a/src/Text/Pandoc/Class/PandocIO.hs b/src/Text/Pandoc/Class/PandocIO.hs index 61ee1f1c629b..38c4e7709661 100644 --- a/src/Text/Pandoc/Class/PandocIO.hs +++ b/src/Text/Pandoc/Class/PandocIO.hs @@ -63,6 +63,7 @@ instance PandocMonad PandocIO where readFileLazy = IO.readFileLazy readFileStrict = IO.readFileStrict readStdinStrict = IO.readStdinStrict + svgToPng = IO.svgToPng glob = IO.glob fileExists = IO.fileExists diff --git a/src/Text/Pandoc/Class/PandocMonad.hs b/src/Text/Pandoc/Class/PandocMonad.hs index b7f2d5a9bb7c..5531b6d108ac 100644 --- a/src/Text/Pandoc/Class/PandocMonad.hs +++ b/src/Text/Pandoc/Class/PandocMonad.hs @@ -120,6 +120,9 @@ class (Functor m, Applicative m, Monad m, MonadError PandocError m) -- | Read the contents of stdin as a strict ByteString, raising -- an error on failure. readStdinStrict :: m B.ByteString + -- | Converts an SVG to a PNG (dpiX, widthPoints, heightPoints, svgBlob) + -- Not called when sandboxed. + svgToPng :: Int -> Maybe Double -> Maybe Double -> BL.ByteString -> m (Either T.Text BL.ByteString) -- | Return a list of paths that match a glob, relative to -- the working directory. See 'System.FilePath.Glob' for -- the glob syntax. @@ -529,6 +532,7 @@ instance (MonadTrans t, PandocMonad m, Functor (t m), readFileLazy = lift . readFileLazy readFileStrict = lift . readFileStrict readStdinStrict = lift readStdinStrict + svgToPng dpi width height bs = lift $ svgToPng dpi width height bs glob = lift . glob fileExists = lift . fileExists getDataFileName = lift . getDataFileName @@ -547,6 +551,7 @@ instance {-# OVERLAPS #-} PandocMonad m => PandocMonad (ParsecT s st m) where readFileLazy = lift . readFileLazy readFileStrict = lift . readFileStrict readStdinStrict = lift readStdinStrict + svgToPng dpi width height bs = lift $ svgToPng dpi width height bs glob = lift . glob fileExists = lift . fileExists getDataFileName = lift . getDataFileName diff --git a/src/Text/Pandoc/Class/PandocPure.hs b/src/Text/Pandoc/Class/PandocPure.hs index c86b20a05bcb..c578d6d058ef 100644 --- a/src/Text/Pandoc/Class/PandocPure.hs +++ b/src/Text/Pandoc/Class/PandocPure.hs @@ -205,6 +205,8 @@ instance PandocMonad PandocPure where Nothing -> throwError $ PandocResourceNotFound $ T.pack fp readStdinStrict = getsPureState stStdin + + svgToPng _ _ _ _ = return $ Left "SVG conversion not available in PandocPure" glob s = do FileTree ftmap <- getsPureState stFiles diff --git a/src/Text/Pandoc/Image.hs b/src/Text/Pandoc/Image.hs index 0c228250eff4..a81e26f9b7f2 100644 --- a/src/Text/Pandoc/Image.hs +++ b/src/Text/Pandoc/Image.hs @@ -10,7 +10,7 @@ Portability : portable Functions for converting images. -} -module Text.Pandoc.Image ( svgToPng ) where +module Text.Pandoc.Image ( svgToPngIO ) where import Text.Pandoc.Process (pipeProcess) import qualified Data.ByteString.Lazy as L import System.Exit @@ -20,16 +20,20 @@ import qualified Control.Exception as E import Control.Monad.IO.Class (MonadIO(liftIO)) import Text.Pandoc.Class.PandocMonad import qualified Data.Text as T +import Text.Printf (printf) -- | Convert svg image to png. rsvg-convert -- is used and must be available on the path. -svgToPng :: (PandocMonad m, MonadIO m) +svgToPngIO :: (PandocMonad m, MonadIO m) => Int -- ^ DPI + -> Maybe Double -- ^ width in Points + -> Maybe Double -- ^ height in Points -> L.ByteString -- ^ Input image as bytestring -> m (Either Text L.ByteString) -svgToPng dpi bs = do +svgToPngIO dpi widthPt heightPt bs = do let dpi' = show dpi let args = ["-f","png","-a","--dpi-x",dpi',"--dpi-y",dpi'] + ++ pt "width" widthPt ++ pt "height" heightPt trace (T.intercalate " " $ map T.pack $ "rsvg-convert" : args) liftIO $ E.catch (do (exit, out) <- pipeProcess Nothing "rsvg-convert" @@ -40,3 +44,4 @@ svgToPng dpi bs = do else Left "conversion from SVG failed") (\(e :: E.SomeException) -> return $ Left $ "check that rsvg-convert is in path.\n" <> tshow e) + where pt name = maybe [] $ \points -> ["--" <> name, printf "%.6fpt" points] \ No newline at end of file From d8d0c1999d28dd0f12171bff97adb380c6f55c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= Date: Wed, 27 Dec 2023 10:54:49 +0000 Subject: [PATCH 3/5] fix(docx): use proper DPI when creating fallback images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce getOrCreateFallback, and pass the desired size in points to rsvg-convert. Otherwise it'll guess the size based on the SVG's viewbox and completely ignore the DPI argument. Signed-off-by: Edwin Török --- src/Text/Pandoc/App.hs | 22 +--------------- src/Text/Pandoc/Image.hs | 19 ++++++++++++-- src/Text/Pandoc/Writers/Docx/OpenXML.hs | 35 ++++++++++++++++++------- 3 files changed, 43 insertions(+), 33 deletions(-) diff --git a/src/Text/Pandoc/App.hs b/src/Text/Pandoc/App.hs index b2bc5735f154..b5e6dd8357f0 100644 --- a/src/Text/Pandoc/App.hs +++ b/src/Text/Pandoc/App.hs @@ -28,7 +28,7 @@ module Text.Pandoc.App ( , applyFilters ) where import qualified Control.Exception as E -import Control.Monad ( (>=>), when, forM, forM_ ) +import Control.Monad ( (>=>), when, forM ) import Control.Monad.Trans ( MonadIO(..) ) import Control.Monad.Catch ( MonadMask ) import Control.Monad.Except (throwError) @@ -49,7 +49,6 @@ import System.IO (nativeNewline, stdout) import qualified System.IO as IO (Newline (..)) import Text.Pandoc import Text.Pandoc.Builder (setMeta) -import Text.Pandoc.MediaBag (mediaItems) import Text.Pandoc.App.Opt (Opt (..), LineEnding (..), defaultOpts, IpynbOutput (..), OptInfo(..)) import Text.Pandoc.App.CommandLineOptions (parseOptions, parseOptionsFromArgs, @@ -66,7 +65,6 @@ import qualified Text.Pandoc.Format as Format import Text.Pandoc.PDF (makePDF) import Text.Pandoc.Scripting (ScriptingEngine (..), CustomComponents(..)) import Text.Pandoc.SelfContained (makeSelfContained) -import Text.Pandoc.Shared (tshow) import Text.Pandoc.Writers.Shared (lookupMetaString) import Text.Pandoc.Readers.Markdown (yamlToMeta) import qualified Text.Pandoc.UTF8 as UTF8 @@ -300,9 +298,6 @@ convertWithOpts' scriptingEngine istty datadir opts = do >=> maybe return extractMedia (optExtractMedia opts) ) - when (format == "docx" && not (optSandbox opts)) $ do - createPngFallbacks (writerDpi writerOptions) - output <- case writer of ByteStringWriter f | format == "chunkedhtml" -> ZipOutput <$> f writerOptions doc @@ -367,21 +362,6 @@ readAbbreviations mbfilepath = >>= fmap (Set.fromList . filter (not . T.null) . T.lines) . toTextM (fromMaybe mempty mbfilepath) -createPngFallbacks :: (PandocMonad m) => Int -> m () -createPngFallbacks dpi = do - -- create fallback pngs for svgs - items <- mediaItems <$> getMediaBag - forM_ items $ \(fp, mt, bs) -> - case T.takeWhile (/=';') mt of - "image/svg+xml" -> do - res <- svgToPng dpi Nothing Nothing bs - case res of - Right bs' -> do - let fp' = fp <> ".png" - insertMedia fp' (Just "image/png") bs' - Left e -> report $ CouldNotConvertImage (T.pack fp) (tshow e) - _ -> return () - getMetadataFromFiles :: PandocMonad m => Text -> ReaderOptions -> [FilePath] -> m Meta getMetadataFromFiles readerFormat readerOpts = \case diff --git a/src/Text/Pandoc/Image.hs b/src/Text/Pandoc/Image.hs index a81e26f9b7f2..c93897ccda75 100644 --- a/src/Text/Pandoc/Image.hs +++ b/src/Text/Pandoc/Image.hs @@ -10,7 +10,7 @@ Portability : portable Functions for converting images. -} -module Text.Pandoc.Image ( svgToPngIO ) where +module Text.Pandoc.Image ( createPngFallback, svgToPngIO ) where import Text.Pandoc.Process (pipeProcess) import qualified Data.ByteString.Lazy as L import System.Exit @@ -20,6 +20,9 @@ import qualified Control.Exception as E import Control.Monad.IO.Class (MonadIO(liftIO)) import Text.Pandoc.Class.PandocMonad import qualified Data.Text as T +import Text.Pandoc.Logging (LogMessage(CouldNotConvertImage)) +import Data.ByteString.Lazy (ByteString) +import Text.Pandoc.MediaBag (MediaItem, lookupMedia) import Text.Printf (printf) -- | Convert svg image to png. rsvg-convert @@ -44,4 +47,16 @@ svgToPngIO dpi widthPt heightPt bs = do else Left "conversion from SVG failed") (\(e :: E.SomeException) -> return $ Left $ "check that rsvg-convert is in path.\n" <> tshow e) - where pt name = maybe [] $ \points -> ["--" <> name, printf "%.6fpt" points] \ No newline at end of file + where pt name = maybe [] $ \points -> ["--" <> name, printf "%.6fpt" points] + +createPngFallback :: (PandocMonad m) => Int -> (Double, Double) -> FilePath -> ByteString -> m (Maybe MediaItem) +createPngFallback dpi (xPt, yPt) fp bs = do + -- create fallback pngs for svgs + res <- svgToPng dpi (Just xPt) (Just yPt) bs + case res of + Right bs' -> do + insertMedia fp (Just "image/png") bs' + lookupMedia fp <$> getMediaBag + Left e -> do + report $ CouldNotConvertImage (T.pack fp) (tshow e) + return Nothing diff --git a/src/Text/Pandoc/Writers/Docx/OpenXML.hs b/src/Text/Pandoc/Writers/Docx/OpenXML.hs index cfe6b106e235..c6c75fabb21f 100644 --- a/src/Text/Pandoc/Writers/Docx/OpenXML.hs +++ b/src/Text/Pandoc/Writers/Docx/OpenXML.hs @@ -24,6 +24,7 @@ import Control.Monad (when, unless) import Control.Applicative ((<|>)) import Control.Monad.Except (catchError) import Crypto.Hash (hashWith, SHA1(SHA1)) +import Data.ByteString (ByteString) import qualified Data.ByteString.Lazy as BL import Data.Char (isLetter, isSpace) import Text.Pandoc.Char (isCJK) @@ -48,6 +49,7 @@ import Text.Pandoc.UTF8 (fromText) import Text.Pandoc.Definition import Text.Pandoc.Highlighting (highlight) import Text.Pandoc.Templates (compileDefaultTemplate, renderTemplate) +import Text.Pandoc.Image (createPngFallback) import Text.Pandoc.ImageSize import Text.Pandoc.Logging import Text.Pandoc.MIME (extensionFromMimeType, getMimeType) @@ -60,6 +62,7 @@ import Text.Pandoc.Walk import qualified Text.Pandoc.Writers.GridTable as Grid import Text.Pandoc.Writers.Math import Text.Pandoc.Writers.Shared +import Text.Printf (printf) import Text.TeXMath import Text.Pandoc.Writers.OOXML import Text.Pandoc.XML.Light as XML @@ -718,6 +721,16 @@ formattedRun els = do props <- getTextProps return $ mknode "w:r" [] $ props ++ els +getOrCreateFallback :: PandocMonad m => Int -> (Integer, Integer) -> FilePath -> ByteString -> m (Maybe MediaItem) +getOrCreateFallback dpi (xemu, yemu) src' img = do + mediabag <- getMediaBag + let src = printf "%s_%d_%d.png" src' xemu yemu + let xyPt = (fromIntegral xemu / 12700.0, fromIntegral yemu / 12700.0) + case lookupMedia src mediabag of + Just item -> return $ Just item + Nothing -> createPngFallback dpi xyPt src $ BL.fromStrict img + + -- | Convert an inline element to OpenXML. -- | Convert an inline element to OpenXML. inlineToOpenXML :: PandocMonad m => WriterOptions -> Inline -> WS m [Content] inlineToOpenXML opts il = withDirection $ inlineToOpenXML' opts il @@ -919,17 +932,26 @@ inlineToOpenXML' opts (Image attr@(imgident, _, _) alt (src, title)) = do imgs <- gets stImages let stImage = M.lookup (T.unpack src) imgs - generateImgElt (ident, _fp, mt, img) = do + generateImgElt (ident, fp, mt, img) = do docprid <- getUniqueId nvpicprid <- getUniqueId + let + (xpt,ypt) = desiredSizeInPoints opts attr + (either (const def) id (imageSize opts img)) + -- 12700 emu = 1 pt + pageWidthPt = case dimension Width attr of + Just (Percent a) -> pageWidth * floor (a * 127) + _ -> pageWidth * 12700 + (xemu,yemu) = fitToPage (xpt * 12700, ypt * 12700) pageWidthPt (blipAttrs, blipContents) <- case T.takeWhile (/=';') <$> mt of Just "image/svg+xml" -> do -- get fallback png - mediabag <- getMediaBag + fallback <- getOrCreateFallback (writerDpi opts) (xemu, yemu) fp img mbFallback <- - case lookupMedia (T.unpack (src <> ".png")) mediabag of + case fallback of Just item -> do + P.trace $ "Found fallback " <> tshow (mediaPath item) id' <- T.unpack . ("rId" <>) <$> getUniqueId let fp' = "media/" <> id' <> ".png" let imgdata = (id', @@ -956,13 +978,6 @@ inlineToOpenXML' opts (Image attr@(imgident, _, _) alt (src, title)) = do [extLst]) _ -> return ([("r:embed", T.pack ident)], []) let - (xpt,ypt) = desiredSizeInPoints opts attr - (either (const def) id (imageSize opts img)) - -- 12700 emu = 1 pt - pageWidthPt = case dimension Width attr of - Just (Percent a) -> pageWidth * floor (a * 127) - _ -> pageWidth * 12700 - (xemu,yemu) = fitToPage (xpt * 12700, ypt * 12700) pageWidthPt cNvPicPr = mknode "pic:cNvPicPr" [] $ mknode "a:picLocks" [("noChangeArrowheads","1") ,("noChangeAspect","1")] () From c3710cd20e24d49bfb12e186258b07a157ea7223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= Date: Wed, 27 Dec 2023 16:32:49 +0000 Subject: [PATCH 4/5] test(docx): add PNG fallback generation test for SVG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Just look at --trace output. Can't use a golden test because the actual .png will be different depending on rsvg-convert version. Signed-off-by: Edwin Török --- test/command/9288.md | 52 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 test/command/9288.md diff --git a/test/command/9288.md b/test/command/9288.md new file mode 100644 index 000000000000..6205c6f3e2db --- /dev/null +++ b/test/command/9288.md @@ -0,0 +1,52 @@ +``` +% pandoc -f native -t docx -o 9288.docx --trace +[ Figure + ( "" , [] , [] ) + (Caption Nothing [ Plain [ Str "5in" ] ]) + [ Plain + [ Image + ( "" , [] , [ ( "width" , "5in" ) ] ) + [ Str "5in" ] + ( "command/SVG_logo.svg" , "" ) + ] + ] +, Figure + ( "" , [] , [] ) + (Caption Nothing [ Plain [ Str "5in" ] ]) + [ Plain + [ Image + ( "" , [] , [ ( "width" , "5in" ) ] ) + [ Str "5in" ] + ( "command/SVG_logo.svg" , "" ) + ] + ] +, Figure + ( "" , [] , [] ) + (Caption Nothing [ Plain [ Str "80%" ] ]) + [ Plain + [ Image + ( "" , [] , [ ( "width" , "80%" ) ] ) + [ Str "5in" ] + ( "command/SVG_logo.svg" , "" ) + ] + ] +, Figure + ( "" , [] , [] ) + (Caption Nothing [ Plain [ Str "default" ] ]) + [ Plain + [ Image + ( "" , [] , [] ) + [ Str "5in" ] + ( "command/SVG_logo.svg" , "" ) + ] + ] +] +^D +2> [trace] rsvg-convert -f png -a --dpi-x 96 --dpi-y 96 --width 360.000000pt --height 360.000000pt +2> [trace] Found fallback "media/rId20.svg_4572000_4572000.png" +2> [trace] Found fallback "media/rId20.svg_4572000_4572000.png" +2> [trace] rsvg-convert -f png -a --dpi-x 96 --dpi-y 96 --width 75.000000pt --height 75.000000pt +2> [trace] Found fallback "media/rId20.svg_952500_952500.png" +2> [trace] Found fallback "media/rId20.svg_952500_952500.png" + +``` From a80fa1cee0397f44266a1f517f3a54a6ebe9b636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= Date: Wed, 27 Dec 2023 12:39:24 +0000 Subject: [PATCH 5/5] fix(docx): honour percentage widths for SVG images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Edwin Török --- src/Text/Pandoc/ImageSize.hs | 10 +++++++--- src/Text/Pandoc/Writers/Docx/OpenXML.hs | 4 ++-- src/Text/Pandoc/Writers/ICML.hs | 2 +- src/Text/Pandoc/Writers/RTF.hs | 2 +- test/command/9288.md | 3 ++- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Text/Pandoc/ImageSize.hs b/src/Text/Pandoc/ImageSize.hs index 6e0558f8fbc4..5ccdbe3d3e8e 100644 --- a/src/Text/Pandoc/ImageSize.hs +++ b/src/Text/Pandoc/ImageSize.hs @@ -174,8 +174,8 @@ sizeInPoints s = (pxXf * 72 / dpiXf, pxYf * 72 / dpiYf) -- | Calculate (height, width) in points, considering the desired dimensions in the -- attribute, while falling back on the image file's dpi metadata if no dimensions -- are specified in the attribute (or only dimensions in percentages). -desiredSizeInPoints :: WriterOptions -> Attr -> ImageSize -> (Double, Double) -desiredSizeInPoints opts attr s = +desiredSizeInPoints :: WriterOptions -> Attr -> Maybe Integer -> ImageSize -> (Double, Double) +desiredSizeInPoints opts attr pageWidthPoints' s = case (getDim Width, getDim Height) of (Just w, Just h) -> (w, h) (Just w, Nothing) -> (w, w / ratio) @@ -184,7 +184,11 @@ desiredSizeInPoints opts attr s = where ratio = fromIntegral (pxX s) / fromIntegral (pxY s) getDim dir = case dimension dir attr of - Just (Percent _) -> Nothing + Just (Percent a) -> + case (dir, pageWidthPoints') of + (Width, Just pageWidthPoints) -> + Just $ fromIntegral pageWidthPoints * a + _ -> Nothing Just dim -> Just $ inPoints opts dim Nothing -> Nothing diff --git a/src/Text/Pandoc/Writers/Docx/OpenXML.hs b/src/Text/Pandoc/Writers/Docx/OpenXML.hs index c6c75fabb21f..d0ded5b348dd 100644 --- a/src/Text/Pandoc/Writers/Docx/OpenXML.hs +++ b/src/Text/Pandoc/Writers/Docx/OpenXML.hs @@ -928,7 +928,7 @@ inlineToOpenXML' opts (Link _ txt (src,_)) = do return i return [ Elem $ mknode "w:hyperlink" [("r:id",id')] contents ] inlineToOpenXML' opts (Image attr@(imgident, _, _) alt (src, title)) = do - pageWidth <- asks envPrintWidth + pageWidth <- asks envPrintWidth -- in Points imgs <- gets stImages let stImage = M.lookup (T.unpack src) imgs @@ -936,7 +936,7 @@ inlineToOpenXML' opts (Image attr@(imgident, _, _) alt (src, title)) = do docprid <- getUniqueId nvpicprid <- getUniqueId let - (xpt,ypt) = desiredSizeInPoints opts attr + (xpt,ypt) = desiredSizeInPoints opts attr (Just pageWidth) (either (const def) id (imageSize opts img)) -- 12700 emu = 1 pt pageWidthPt = case dimension Width attr of diff --git a/src/Text/Pandoc/Writers/ICML.hs b/src/Text/Pandoc/Writers/ICML.hs index 1d766ab13072..7bb649a60a30 100644 --- a/src/Text/Pandoc/Writers/ICML.hs +++ b/src/Text/Pandoc/Writers/ICML.hs @@ -615,7 +615,7 @@ imageICML opts style attr (src, _) = do report $ CouldNotFetchResource src $ tshow e return def) let (ow, oh) = sizeInPoints imgS - (imgWidth, imgHeight) = desiredSizeInPoints opts attr imgS + (imgWidth, imgHeight) = desiredSizeInPoints opts attr Nothing imgS hw = showFl $ ow / 2 hh = showFl $ oh / 2 scale = showFl (imgWidth / ow) <> " 0 0 " <> showFl (imgHeight / oh) diff --git a/src/Text/Pandoc/Writers/RTF.hs b/src/Text/Pandoc/Writers/RTF.hs index 4a8f1d577f9e..c95a524ca06c 100644 --- a/src/Text/Pandoc/Writers/RTF.hs +++ b/src/Text/Pandoc/Writers/RTF.hs @@ -64,7 +64,7 @@ rtfEmbedImage opts x@(Image attr _ (src,_)) = catchError <> "\\pichgoal" <> tshow (floor (ypt * 20) :: Integer) -- twip = 1/1440in = 1/20pt where (xpx, ypx) = sizeInPixels sz - (xpt, ypt) = desiredSizeInPoints opts attr sz + (xpt, ypt) = desiredSizeInPoints opts attr Nothing sz let raw = "{\\pict" <> filetype <> sizeSpec <> " " <> T.concat bytes <> "}" if B.null imgdata diff --git a/test/command/9288.md b/test/command/9288.md index 6205c6f3e2db..1dc70481d0ed 100644 --- a/test/command/9288.md +++ b/test/command/9288.md @@ -45,8 +45,9 @@ 2> [trace] rsvg-convert -f png -a --dpi-x 96 --dpi-y 96 --width 360.000000pt --height 360.000000pt 2> [trace] Found fallback "media/rId20.svg_4572000_4572000.png" 2> [trace] Found fallback "media/rId20.svg_4572000_4572000.png" +2> [trace] rsvg-convert -f png -a --dpi-x 96 --dpi-y 96 --width 336.000000pt --height 336.000000pt +2> [trace] Found fallback "media/rId20.svg_4267200_4267200.png" 2> [trace] rsvg-convert -f png -a --dpi-x 96 --dpi-y 96 --width 75.000000pt --height 75.000000pt 2> [trace] Found fallback "media/rId20.svg_952500_952500.png" -2> [trace] Found fallback "media/rId20.svg_952500_952500.png" ```