diff --git a/src/Text/Pandoc/Writers/Typst.hs b/src/Text/Pandoc/Writers/Typst.hs
index 42ca465340a6..2bf394b12e7d 100644
--- a/src/Text/Pandoc/Writers/Typst.hs
+++ b/src/Text/Pandoc/Writers/Typst.hs
@@ -33,7 +33,7 @@ import Control.Monad.State ( StateT, evalStateT, gets, modify )
import Text.Pandoc.Writers.Shared ( lookupMetaInlines, lookupMetaString,
metaToContext, defField, resetField,
setupTranslations )
-import Text.Pandoc.Shared (isTightList, orderedListMarkers, tshow)
+import Text.Pandoc.Shared (isTightList, orderedListMarkers, stringify, tshow)
import Text.Pandoc.Highlighting (highlight, formatTypstBlock, formatTypstInline,
styleToTypst)
import Text.Pandoc.Translations (Term(Abstract), translateTerm)
@@ -365,10 +365,16 @@ blockToTypst block =
opts <- gets stOptions
contents <- case blocks of
-- don't need #box around block-level image
- [Para [Image attr _ (src, _)]]
- -> pure $ mkImage opts False src attr
- [Plain [Image attr _ (src, _)]]
- -> pure $ mkImage opts False src attr
+ [Para [Image attr imgInlines (src, _)]]
+ -> let mbAlt = if null imgInlines
+ then Nothing
+ else Just (stringify imgInlines)
+ in pure $ mkImage opts False src attr mbAlt
+ [Plain [Image attr imgInlines (src, _)]]
+ -> let mbAlt = if null imgInlines
+ then Nothing
+ else Just (stringify imgInlines)
+ in pure $ mkImage opts False src attr mbAlt
_ -> brackets <$> blocksToTypst blocks
let lab = toLabel FreestandingLabel ident
return $ "#figure(" <> nest 2 ((contents <> ",")
@@ -529,34 +535,45 @@ inlineToTypst inline =
(if inlines == [Str src]
then mempty
else nowrap $ brackets contents)
- Image attr _inlines (src,_tit) -> do
- opts <- gets stOptions
- pure $ mkImage opts True src attr
+ Image attr imgInlines (src,_tit) -> do
+ opts <- gets stOptions
+ let mbAlt = if null imgInlines
+ then Nothing
+ else Just (stringify imgInlines)
+ pure $ mkImage opts True src attr mbAlt
Note blocks -> do
contents <- blocksToTypst blocks
return $ "#footnote" <> brackets (chomp contents)
-- see #9104; need box or image is treated as block-level
-mkImage :: WriterOptions -> Bool -> Text -> Attr -> Doc Text
-mkImage opts useBox src attr
+mkImage :: WriterOptions -> Bool -> Text -> Attr -> Maybe Text -> Doc Text
+mkImage opts useBox src (ident, cls, kvs) mbAlt
| useBox = "#box" <> parens coreImage
| otherwise = coreImage
where
src' = T.pack $ unEscapeString $ T.unpack src -- #9389
showDim (Pixel a) = literal (showInInch opts (Pixel a) <> "in")
showDim dim = text (show dim)
+ -- Priority: explicit alt attribute > inlines from markdown
+ -- Empty alt="" is preserved (indicates decorative image)
+ altText = case lookup "alt" kvs of
+ Just alt -> Just alt
+ Nothing -> mbAlt
+ altAttr = case altText of
+ Just alt | not (T.null alt) -> ", alt: " <> doubleQuoted alt
+ _ -> mempty
dimAttrs =
- (case dimension Height attr of
+ (case dimension Height (ident, cls, kvs) of
Nothing -> mempty
Just dim -> ", height: " <> showDim dim) <>
- (case dimension Width attr of
+ (case dimension Width (ident, cls, kvs) of
Nothing -> mempty
Just dim -> ", width: " <> showDim dim)
isData = "data:" `T.isPrefixOf` src'
dataSvg = ""
coreImage
- | isData = "image.decode" <> parens(doubleQuoted dataSvg <> dimAttrs)
- | otherwise = "image" <> parens (doubleQuoted src' <> dimAttrs)
+ | isData = "image.decode" <> parens(doubleQuoted dataSvg <> altAttr <> dimAttrs)
+ | otherwise = "image" <> parens (doubleQuoted src' <> altAttr <> dimAttrs)
textstyle :: PandocMonad m => Doc Text -> [Inline] -> TW m (Doc Text)
textstyle s inlines = do
diff --git a/test/command/9236.md b/test/command/9236.md
index f8eca5fb553b..c903c845ac1a 100644
--- a/test/command/9236.md
+++ b/test/command/9236.md
@@ -9,25 +9,26 @@
And inline: {height=2in} and
.
^D
-#figure(image("command/minimal.svg", width: 3in),
+#figure(image("command/minimal.svg", alt: "minimal", width: 3in),
caption: [
minimal
]
)
-#figure(image("command/minimal.svg", height: 2in, width: 3in),
+#figure(image("command/minimal.svg", alt: "minimal", height: 2in, width: 3in),
caption: [
minimal
]
)
-#figure(image("command/minimal.svg"),
+#figure(image("command/minimal.svg", alt: "minimal"),
caption: [
minimal
]
)
-And inline: #box(image("command/minimal.svg", height: 2in)) and
-#box(image("command/minimal.svg")).
+And inline:
+#box(image("command/minimal.svg", alt: "minimal", height: 2in)) and
+#box(image("command/minimal.svg", alt: "minimal")).
```
diff --git a/test/command/typst-image-alt.md b/test/command/typst-image-alt.md
new file mode 100644
index 000000000000..12284182c89c
--- /dev/null
+++ b/test/command/typst-image-alt.md
@@ -0,0 +1,78 @@
+```
+% pandoc -f markdown-implicit_figures -t typst
+
+^D
+#box(image("image.png", alt: "Alt text from inlines"))
+```
+
+```
+% pandoc -f markdown-implicit_figures -t typst
+{alt="Explicit alt attribute"}
+^D
+#box(image("image.png", alt: "Explicit alt attribute"))
+```
+
+```
+% pandoc -f markdown-implicit_figures -t typst
+{alt="Explicit wins"}
+^D
+#box(image("image.png", alt: "Explicit wins"))
+```
+
+```
+% pandoc -f markdown-implicit_figures -t typst
+
+^D
+#box(image("image.png"))
+```
+
+```
+% pandoc -f markdown-implicit_figures -t typst
+{alt=""}
+^D
+#box(image("image.png"))
+```
+
+```
+% pandoc -t typst
+{alt="Alt text describing the image"}
+
+^D
+#figure(image("image.png", alt: "Alt text describing the image"),
+ caption: [
+ Caption text
+ ]
+)
+```
+
+```
+% pandoc -t typst
+
+
+^D
+#figure(image("image.png", alt: "Caption only"),
+ caption: [
+ Caption only
+ ]
+)
+```
+
+```
+% pandoc -f markdown -t typst
+{alt="A \"quoted\" phrase and C:\\path\\file"}
+^D
+#figure(image("test.png", alt: "A \"quoted\" phrase and C:\\path\\file"),
+ caption: [
+ Caption
+ ]
+)
+
+```
+
+```
+% pandoc -f html -t typst
+
+^D
+#box(image.decode("", alt: "A small red dot"))
+
+```
diff --git a/test/writer.typst b/test/writer.typst
index 7ce5d05f1520..873628977e26 100644
--- a/test/writer.typst
+++ b/test/writer.typst
@@ -837,13 +837,13 @@ or here:
From "Voyage dans la Lune" by Georges Melies (1902):
-#figure(image("lalune.jpg"),
+#figure(image("lalune.jpg", alt: "lalune"),
caption: [
lalune
]
)
-Here is a movie #box(image("movie.jpg")) icon.
+Here is a movie #box(image("movie.jpg", alt: "movie")) icon.
#horizontalrule