From 50cbeba446231e7d4e75a9950fe78e3d3d9f9126 Mon Sep 17 00:00:00 2001 From: Aner Lucero <4rgento@gmail.com> Date: Thu, 29 Jul 2021 19:02:55 -0300 Subject: [PATCH] Add markdown figure filters. --- markdown-figures/Makefile | 3 + markdown-figures/README.md | 107 ++++++++++++++++++++++++ markdown-figures/md-figure-explicit.lua | 54 ++++++++++++ markdown-figures/md-figure-implicit.lua | 62 ++++++++++++++ markdown-figures/render.png | Bin 0 -> 10328 bytes 5 files changed, 226 insertions(+) create mode 100644 markdown-figures/Makefile create mode 100644 markdown-figures/README.md create mode 100644 markdown-figures/md-figure-explicit.lua create mode 100644 markdown-figures/md-figure-implicit.lua create mode 100644 markdown-figures/render.png diff --git a/markdown-figures/Makefile b/markdown-figures/Makefile new file mode 100644 index 00000000..d12e2106 --- /dev/null +++ b/markdown-figures/Makefile @@ -0,0 +1,3 @@ +.PHONY: test + +test: diff --git a/markdown-figures/README.md b/markdown-figures/README.md new file mode 100644 index 00000000..d624b32e --- /dev/null +++ b/markdown-figures/README.md @@ -0,0 +1,107 @@ +# Support for figures in Markdown + +This filter provides two syntaxs to represent figures in markdown. + +## Explicit syntax + +The explicit syntax is constructed using a `div` with "figure" class. The +caption is also specified using a `div` but with a "caption" class. + +Here is an example. + +``` +::: { .figure } + +content. + +:::: {.caption } +caption +:::: + +::: +``` + +All elements inside the figure that are an image without a caption in its own +paragraph become html's `img` tags. + +Here is an example of figure containing two images and a caption. + +```{markdown} +::: { .figure } + +![](test/media/rId25.jpg "") + +![](test/media/rId25.jpg "") + +:::: {.caption } +caption +:::: + +::: +``` + +This will result in a single figure containing multiple images. + +``` +$ pandoc -f markdown -t native --lua-filter=md-figure-explicit.lua fig-explicit.md + +[Figure ("",[],[]) (Caption (Just []) [Para [Str "caption"]]) + [ Plain [Image ("",[],[]) [] ("test/media/rId25.jpg","")] + , Plain [Image ("",[],[]) [] ("test/media/rId25.jpg","")]]] +``` + + +```{html} +
+ + +

caption

+
+``` + +This will result in a single figure containing multiple images. + +## Implicit syntax + +The second syntax uses the last paragraph inside the figure as the caption. + +```{markdown} +::: { .figure } + + +![](test/media/rId25.jpg "") + +![](test/media/rId25.jpg "") + +This is a caption with +multiple lines + +::: + +``` + +This results in the following output: + + +``` +$ pandoc -f markdown -t native --lua-filter=md-figure-implicit.lua fig-implict.md +[Figure ("",[],[]) + (Caption + (Just []) + [ Para [Str "This",Space,Str "is",Space,Str "a",Space,Str "caption",Space,Str "with",SoftBreak,Str "multiple",Space,Str "lines"]]) + [Plain [Image ("",[],[]) [] ("test/media/rId25.jpg","")],Plain [Image ("",[],[]) [] ("test/media/rId25.jpg","")]]] +``` + +```{html} +
+ + +

This is a caption with multiple lines

+
+``` + +## Sample Firefox's HTML rendering + +For the implicit syntax example, this is firefox's render. + +![Example](render.png) diff --git a/markdown-figures/md-figure-explicit.lua b/markdown-figures/md-figure-explicit.lua new file mode 100644 index 00000000..444e67a4 --- /dev/null +++ b/markdown-figures/md-figure-explicit.lua @@ -0,0 +1,54 @@ +--- Translate Divs with the "figure" class into Figure elements. +-- The contents of last child Div with the "caption" class will be used as the +-- figure caption. +function Div(div) + + local content = div.content + local attr = div.attr + local null_caption = { + short = {}, + long = {} + } + + if attr.classes:includes "figure" then + + local new_content = pandoc.List({}) + + -- A div with the caption class is captured as the figure's + -- caption. + for _,elem in pairs(div.content) do + if elem.t == 'Div' then + null_caption.long = elem.content + else + new_content:insert(elem) + end + end + + + -- Remove the figure for `SimpleFigure` with no caption inside a Figure. + local final_content = pandoc.List({}) + + for _,elem in pairs(new_content) do + -- Check that it is a simple figure with no caption + if elem.t == 'Para' and + #elem.content == 1 and + elem.content[1].t == 'Image' and + #elem.content[1].caption == 0 then + + local image = elem.content[1] + final_content:insert(pandoc.Plain({image})) + else + final_content:insert(elem) + end + end + + -- Remove the figure class in the output + attr.classes = attr.classes:filter( + function(c) return c ~= "figure" end) + + return pandoc.Figure(final_content, null_caption, attr) + end + + -- Return an identical div when it lacks the "figure" class. + return pandoc.Div(content, attr) +end diff --git a/markdown-figures/md-figure-implicit.lua b/markdown-figures/md-figure-implicit.lua new file mode 100644 index 00000000..d92308b9 --- /dev/null +++ b/markdown-figures/md-figure-implicit.lua @@ -0,0 +1,62 @@ +--- Translate Divs with the "figure" class into Figure elements. +-- The contents of last paragraph became the figure caption will be used as the +-- figure caption. +function Div(div) + + local content = div.content + local attr = div.attr + local null_caption = { + short = {}, + long = {} + } + + if attr.classes:includes "figure" then + + local new_content = pandoc.List({}) + + -- Capture the last element + local last_elem = nil + + for _,elem in pairs(div.content) do + if last_elem then + new_content:insert(last_elem) + end + last_elem = elem + end + + -- If the last element is a paragraph, use it as caption. + if last_elem then + if last_elem.t == 'Para' then + null_caption.long = { last_elem } + else + new_content:insert(last_elem) + end + end + + -- Remove the figure for `SimpleFigure` with no caption inside a Figure. + local final_content = pandoc.List({}) + + for _,elem in pairs(new_content) do + -- Check that it is a simple figure with no caption + if elem.t == 'Para' and + #elem.content == 1 and + elem.content[1].t == 'Image' and + #elem.content[1].caption == 0 then + + local image = elem.content[1] + final_content:insert(pandoc.Plain({image})) + else + final_content:insert(elem) + end + end + + -- Remove the figure class in the output + attr.classes = attr.classes:filter( + function(c) return c ~= "figure" end) + + return pandoc.Figure(final_content, null_caption, attr) + end + + -- Return an identical div when it lacks the "figure" class. + return pandoc.Div(content, attr) +end diff --git a/markdown-figures/render.png b/markdown-figures/render.png new file mode 100644 index 0000000000000000000000000000000000000000..17f878ea9c35fa25fb4dccb73afe043badcfbaed GIT binary patch literal 10328 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 IMGb@g563(3@Bjb+ literal 0 HcmV?d00001