Thursday, March 17, 2016

Finding longest lines in XSLT

I needed to scale some code listings so that they fit the width of the page. DocBook doesn't have a particular automation for that, so I needed to find the length of the longest line in a listing and do scaling according to that.
So here it is:
<!-- Finds the length of the longest line in the given text -->
  <xsl:function name="vaadin:longestLineLength">
    <xsl:param name="listing">

    <xsl:variable name="firstLineLength">
      <xsl:choose>
        <xsl:when test="contains($listing, '&#xa;')">
          <xsl:value-of select="string-length(substring-before($listing, '&#xa;'))">
        </xsl:value-of></xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="string-length($listing)">
        </xsl:value-of></xsl:otherwise>
      </xsl:choose>
    </xsl:variable>

    <xsl:variable name="longestInRest">
      <xsl:choose>
        <xsl:when test="contains($listing, '&#xa;')">
          <xsl:value-of select="vaadin:longestLineLength(substring-after($listing, '&#xa;'))">
        </xsl:value-of></xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="number(0)">
        </xsl:value-of></xsl:otherwise>
      </xsl:choose>
    </xsl:variable>

    <xsl:choose>
      <!-- Must use number() here or otherwise it'll use string comparison. -->
      <xsl:when test="number($longestInRest) &gt; number($firstLineLength)">
        <xsl:value-of select="$longestInRest">
      </xsl:value-of></xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$firstLineLength">
      </xsl:value-of></xsl:otherwise>
    </xsl:choose>
  </xsl:param></xsl:function>

It can be called for example as follows:
<xsl:template match="programlisting | screen">
    <!-- Extract text content by eliminating formatting tags -->
    <xsl:variable name="content">
      <xsl:apply-templates select="* | text()" mode="text-listing"/>
    </xsl:variable>

    <xsl:variable name="longestLineLength">
      <xsl:value-of select="vaadin:longestLineLength($content)"/>
    </xsl:variable>

    <xsl:copy>
      <xsl:if test="$longestLineLength > 50">
        <xsl:attribute name="dbscaling">
          <!-- This should give 65% scaling for lines 63 chars long -->
          <xsl:value-of select="(50.0 div $longestLineLength) * 95"/>
        </xsl:attribute>
      </xsl:if>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

Note how you need to give the listing as pure text to the method, and node as an input node structure.

Monday, February 29, 2016

Editing PDF with a Text Editor (Emacs)

I'm again making a new edition of Book of Vaadin. The PDF was made some time last year and the place in which it will be printed has changed.

In earlier times, this kind of changes to editions would be a matter of the printing house and little interest to the authors. One interesting example is the printer's key, which was a list of numbers to indicate the print run. The printer (a person) simply had to file off the letters from the Linotype plates. Changing the print location was not possible in Linotype or offset, but easy in even earlier letterpress printing. But here we are, in the age of technology, that the people doing printing do not want to open a PDF editor to change such details.

There are some PDF editors for Linux, but I want to be careful about not changing the content in any other way. Thereby, editing PDF with a text editor is handy for such tasks. The problem is that PDF is compressed, so you can't edit it directly.

The very simple solution that I used was to use the pdftk tool to uncompress the file, edit it with XEmacs, and then compress it again.

$ pdftk original.pdf output uncompressed.pdf uncompress

Then edit it in Emacs (which is suitable for editing even huge binary files) and compress again:

$ pdftk uncompressed.pdf output edited.pdf compress

Start heating the printing machines!