diff --git a/src/Text/Pandoc/Writers/Typst.hs b/src/Text/Pandoc/Writers/Typst.hs
index 42ca465340a6..34c22c4ec3f7 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,12 @@ blockToTypst block =
opts <- gets stOptions
contents <- case blocks of
-- don't need #box around block-level image
- [Para [Image attr _ (src, _)]]
+ [Para [Image attr altInlines (src, _)]]
-> pure $ mkImage opts False src attr
- [Plain [Image attr _ (src, _)]]
+ (imageAltText attr altInlines)
+ [Plain [Image attr altInlines (src, _)]]
-> pure $ mkImage opts False src attr
+ (imageAltText attr altInlines)
_ -> brackets <$> blocksToTypst blocks
let lab = toLabel FreestandingLabel ident
return $ "#figure(" <> nest 2 ((contents <> ",")
@@ -529,16 +531,21 @@ inlineToTypst inline =
(if inlines == [Str src]
then mempty
else nowrap $ brackets contents)
- Image attr _inlines (src,_tit) -> do
+ Image attr inlines (src,_tit) -> do
opts <- gets stOptions
- pure $ mkImage opts True src attr
+ pure $ mkImage opts True src attr (imageAltText attr inlines)
Note blocks -> do
contents <- blocksToTypst blocks
return $ "#footnote" <> brackets (chomp contents)
+-- Extract alt text: prefer explicit alt attribute, fall back to caption
+imageAltText :: Attr -> [Inline] -> Text
+imageAltText (_, _, kvs) caption =
+ fromMaybe (stringify caption) (lookup "alt" kvs)
+
-- 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 -> Text -> Doc Text
+mkImage opts useBox src attr altText
| useBox = "#box" <> parens coreImage
| otherwise = coreImage
where
@@ -552,11 +559,14 @@ mkImage opts useBox src attr
(case dimension Width attr of
Nothing -> mempty
Just dim -> ", width: " <> showDim dim)
+ altAttr
+ | T.null altText = mempty
+ | otherwise = ", alt: " <> doubleQuoted altText
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 <> dimAttrs <> altAttr)
+ | otherwise = "image" <> parens (doubleQuoted src' <> dimAttrs <> altAttr)
textstyle :: PandocMonad m => Doc Text -> [Inline] -> TW m (Doc Text)
textstyle s inlines = do
diff --git a/test/command/typst-image-alt.md b/test/command/typst-image-alt.md
new file mode 100644
index 000000000000..eeb48259f6b1
--- /dev/null
+++ b/test/command/typst-image-alt.md
@@ -0,0 +1,83 @@
+Typst writer: image alt text support
+
+```
+% pandoc -f markdown -t typst
+
+^D
+#figure(image("cat.png", alt: "A cat sleeping on a couch"),
+ caption: [
+ A cat sleeping on a couch
+ ]
+)
+
+```
+
+Inline image with alt text:
+
+```
+% pandoc -f markdown -t typst
+Here is an icon  in the text.
+^D
+Here is an icon #box(image("icon.png", alt: "small logo")) in the text.
+
+```
+
+Image with explicit alt attribute (different from caption):
+
+```
+% pandoc -f markdown -t typst
+{alt="Detailed description for accessibility"}
+^D
+#figure(image("diagram.png", alt: "Detailed description for accessibility"),
+ caption: [
+ Figure caption
+ ]
+)
+
+```
+
+Image with no alt text (should omit alt parameter):
+
+```
+% pandoc -f markdown -t typst
+
+^D
+#box(image("empty.png"))
+
+```
+
+Inline image with dimensions should preserve alt:
+
+```
+% pandoc -f markdown -t typst
+Here is {width=20px height=20px} inline.
+^D
+Here is
+#box(image("icon.png", height: 0.20833in, width: 0.20833in, alt: "small icon"))
+inline.
+
+```
+
+Alt text with special characters (quotes and backslashes):
+
+```
+% 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
+ ]
+)
+
+```
+
+Data URI image with alt text:
+
+```
+% 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