<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Functional Lens &#187; Linux</title>
	<atom:link href="http://www.kennknowles.com/blog/category/linux/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.kennknowles.com/blog</link>
	<description>on Mathematics and Computation</description>
	<lastBuildDate>Mon, 26 Dec 2011 21:15:37 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.1.2</generator>
		<item>
		<title>Using HaXml to make a PDF slideshow from an Inkscape SVG</title>
		<link>http://www.kennknowles.com/blog/2008/04/20/using-haxml-to-make-a-pdf-slideshow-from-an-inkscape-svg/</link>
		<comments>http://www.kennknowles.com/blog/2008/04/20/using-haxml-to-make-a-pdf-slideshow-from-an-inkscape-svg/#comments</comments>
		<pubDate>Sun, 20 Apr 2008 09:01:21 +0000</pubDate>
		<dc:creator>Kenn</dc:creator>
				<category><![CDATA[Haskell]]></category>
		<category><![CDATA[LaTeX]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Mathematics]]></category>
		<category><![CDATA[gournal]]></category>
		<category><![CDATA[HaXml]]></category>
		<category><![CDATA[Inkscape]]></category>
		<category><![CDATA[jarnal]]></category>
		<category><![CDATA[note-taking]]></category>
		<category><![CDATA[presentation]]></category>
		<category><![CDATA[slide]]></category>
		<category><![CDATA[SVG]]></category>
		<category><![CDATA[XML]]></category>
		<category><![CDATA[xournal]]></category>

		<guid isPermaLink="false">http://www.kennknowles.com/blog/2008/04/20/using-haxml-to-make-a-pdf-slideshow-from-an-inkscape-svg/</guid>
		<description><![CDATA[I recently got a tablet to input handwritten math for slideshow presentations, but instead of using a note-taking program (Jarnal, Xournal, Gournal) I decided that I wanted the full power of image manipulation of a program like Gimp or Inkscape. Neither of these, though, has the level of support for multi-page documents that you find [...]]]></description>
			<content:encoded><![CDATA[<p>I recently got a tablet to input handwritten math for slideshow presentations, but instead of using a note-taking program (<a href="http://www.dklevine.com/general/software/tc1000/jarnal.htm">Jarnal</a>,
<a href="http://xournal.sourceforge.net/">Xournal</a>,
<a href="http://www.adebenham.com/gournal/">Gournal</a>) I decided that I wanted the full power of image manipulation of a program like <a href="http://www.gimp.org/">Gimp</a> or <a href="http://www.inkscape.org/">Inkscape</a>.  Neither of these, though, has the level of support for multi-page documents that you find in note-taking software.  But Inkscape uses SVG as its native file format, so I wrote this Haskell script to transform the layers of an Inkscape SVG file into the slides of a PDF presentation.  I use the
<a href="http://www.cs.york.ac.uk/fp/HaXml/">HaXml</a> library to manipulate the SVG, the Inkscape command-line interface to convert each page to PDF, and <a href="http://www.pdfhacks.com/pdftk/">pdftk</a> to glue the whole thing back together.</p>

<p><a href='http://www.kennknowles.com/blog/wp-content/uploads/2008/04/Slide001.svg' title='slide001.svg'><img width='200' src='http://www.kennknowles.com/blog/wp-content/uploads/2008/04/slide001.png' alt='slide001.png' /></a>
<a href='http://www.kennknowles.com/blog/wp-content/uploads/2008/04/Slide002.svg' title='slide002.png'><img width='200' src='http://www.kennknowles.com/blog/wp-content/uploads/2008/04/slide002.png' alt='slide002.png' /></a></p>

<p><a href='http://www.kennknowles.com/blog/wp-content/uploads/2008/04/Slide003.svg' title='slide003.png'><img width='200' src='http://www.kennknowles.com/blog/wp-content/uploads/2008/04/slide003.png' alt='slide003.png' /></a>
<a href='http://www.kennknowles.com/blog/wp-content/uploads/2008/04/Slide004.svg' title='slide004.png'><img width='200' src='http://www.kennknowles.com/blog/wp-content/uploads/2008/04/slide004.png' alt='slide004.png' /></a></p>

<p>As usual, this post is a literate Haskell file, so you can try it out by saving it to <code>Inkscape.lhs</code>, compiling with <code>ghc --make Inkscape</code>, grabbing the <a href="http://www.kennknowles.com/blog/wp-content/uploads/2008/04/demo.svg">source file for the images above</a>, and running <code>./Inkscape &lt; demo.svg</code>.  The output will appear in <code>Slides.pdf</code> (and your directory will be polluted with temp files, so be aware).
<span id="more-53"></span></p>

<p>For the record, multi-page documents have been on the Inkscape feature
request tracker for many versions, so I presume it is a significant
change.  I <em>do</em> grok C and C++, thanks to the legacy-oriented
education system, but take little enough pleasure from them that I
would rather hack around the issue in Haskell.</p>

<pre>
> import Text.XML.HaXml
> import Text.XML.HaXml.Pretty
> import Text.XML.HaXml.Posn
> import Text.PrettyPrint.HughesPJ
> import Text.Printf
> import Data.List
> import System.IO
> import System.Cmd
</pre>

<p>HaXml is based on a combinator library for <code>CFilter</code>s to filter, search, output, etc XML content.  It is a little crufty in some ways &#8212; many datatypes are transpararent, and you have to do a lot of your own set up and tear down.  The expected way to use it seems to be via <code>processXmlWith :: CFilter -&gt; IO ()</code> which is not sufficient for today&#8217;s task.  The Hackage documentation pointed to an old version of the API, so I used the current version of the source code for documentation.  I&#8217;d love any criticism like &#8220;you didn&#8217;t have to do X&#8221; or &#8220;here is an easier, safer way to do Y&#8221;.</p>

<p>I couldn&#8217;t think of a better way to narrate this code, so I&#8217;ll start with <code>main</code> for a high-level read, and then later fill in all the helper functions. Naturally we start with a call to <code>xmlParse</code>; the <code>"-"</code> is a required filename for error reporting.</p>

<pre>
> main = do input <- getContents
>           let xml = xmlParse "-" input
</pre>

<p>Then I grab the names of all the layer objects in the order they appear in the file, except for the special layer &#8220;Background&#8221; which I&#8217;ll include behind every slide.  The call to <code>verbatim</code> spits them out as <code>String</code>s instead of XML <code>Content</code>, and the <code>"-"</code> is yet another required filename for error reporting.</p>

<pre>
>           let names = delete "Background" 
>                       $ map verbatim 
>                       $ filterElem "-" getLayerNames
>                       $ xmlElem 
>                       $ xml
>           putStrLn $ "Making slides from layers:" 
>                        ++ concatMap ("\n\t"++) names ++ "\n"
</pre>

<p>Then for each layer, make a new version of the file with just that layer visible.</p>

<pre>
>           let outXmls = map (flip selectLayer xml) names
>               usedSlides = take (length names) slideNames
>           mapM_ (uncurry writeFile) 
>                 (zip (map (++".svg") slideNames) 
>                      (map (renderStyle xmlStyle . document) outXmls))
</pre>

<p>And some shell scripting done in Haskell.  I didn&#8217;t even try to find a scripting library or anything to e.g. prevent me from building a malformed command.</p>

<pre>
>           mapM_ (\slide -> do 
>                    system $ "inkscape --export-text-to-path --export-pdf='" 
>                             ++ slide ++ ".pdf' '" ++ slide ++ ".svg'")
>                 usedSlides
>           
>           system $ "pdftk " 
>                      ++ concat (intersperse " " (map (++".pdf") usedSlides)) 
>                      ++ " cat output Slides.pdf"
</pre>

<p>So now to the little details:</p>

<h2>Grabbing the layer names</h2>

<p>Here is the first helper I wrote, wrapping HaXml&#8217;s <code>attrval</code> for a common case.  This filter returns every tag whose <code>attr</code> attribute has the string value <code>val</code>.</p>

<pre>
> matchAttrString :: String -> String -> CFilter i
> matchAttrString attr val = attrval (attr, AttValue [Left val])
</pre>

<p>The next helper is one that maps a tag to its attribute value, otherwise discards anything else it sees.  The HaXml function <code>iffind</code> will pass the <code>attr</code> attribute value of a tag to <code>literal</code> which just returns it.  If the attribute isn&#8217;t found, or the XML data isn&#8217;t a tag, then <code>none</code> will discard it.</p>

<pre>
> showAttr :: String -> CFilter i
> showAttr attr = iffind attr literal none
</pre>

<p>The Inkscape layers are contained in <code>&lt;g inkscape:groupmode='layer' ...&gt;</code> tags.  The name of the layer is in the <code>inkscape:label</code> attribute.  I imagine this will change as Inkscape evolves.  The <code>o</code> is the composition operator for <code>CFilter</code>s.</p>

<pre>
> isLayer = matchAttrString "inkscape:groupmode" "layer"
> getLayerNames = showAttr "inkscape:label" `o` isLayer `o` children
</pre>

<h2>Isolating the layers</h2>

<p>Again proceeding from the outside of my program inwards, a layer is isolated with this helper, using <code>iffind</code> to match either the layer name or the layer &#8220;Background&#8221; which I&#8217;m going to leave in all the output files.  The final <code>keep</code> argument to <code>iffind</code> says to keep
parts of the XML that don&#8217;t have the <code>"inkscape:label"</code> attribute.</p>

<pre>
> selectLayer :: String -> Document Posn -> Document Posn
> selectLayer layer doc = onContent "-" (chip (visible `o` onlyLayer)) doc
>     where onlyLayer = iffind "inkscape:label" layerOrBG keep
>           layerOrBG l = if l == layer || l == "Background" then keep else none
</pre>

<p>In writing <code>visible</code> I was surprised that there was a combinator to set <em>all</em> attributes for a tag, but none to set a single attribute.</p>

<pre>
> visible = setAttr "style" "display:inline"
> setAttr key val (CElem (Elem tag attrs cs) i) = [CElem (Elem tag newattrs cs) i]
>     where newattrs = (key, AttValue [Left val]) : filter ((/= key) . fst) attrs
> setAttr key val other = [other] -- Hackish?
</pre>

<p>As I mentioned before, there is no way that I see to directly apply this filter to an XML file using HaXml.  The type <code>CFilter = Content -&gt; [Content]</code> needs wrapping to apply to an XML <code>Element</code> directly. Notice how I have to pass in a <code>file</code> for error reporting; it feels like I&#8217;m doing things I&#8217;m not supposed to.</p>

<pre>
> filterElem :: FilePath -> CFilter Posn -> Element Posn -> [Content Posn]
> filterElem file f e = f (CElem e (posInNewCxt file Nothing))

> xmlElem (Document _ _ e _) = e
</pre>

<p>And now the function to actually apply a filter to an XML document.  This is straight from the body of <code>processXmlWith</code> in the HaXml source, with <code>filterElem</code> pulled out.</p>

<pre>
> onContent :: FilePath -> (CFilter Posn) -> Document Posn -> Document Posn
> onContent file filter (Document p s e m) =
>     case filterElem file filter e of
>              [CElem e' _] -> Document p s e' m
>              []           -> error "produced no output"
>              _            -> error "produced more than one output"
</pre>

<h2>Bits and pieces</h2>

<p>I also used a modified style for the HughesPJ pretty printer</p>

<pre>
> xmlStyle = style { mode = LeftMode }
</pre>

<p>And a big list of slide names with three digits, for this one-off job.  Better would be to use an API for generating fresh temporary files.</p>

<pre>
> slideNumbers = map (printf "%03d") ([1..999] :: [Int])
> slideNames = map ("Slide"++) slideNumbers
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.kennknowles.com/blog/2008/04/20/using-haxml-to-make-a-pdf-slideshow-from-an-inkscape-svg/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>

