docbook / xslTNG

DocBook xslTNG Stylesheets
https://xsltng.docbook.org
MIT License
42 stars 22 forks source link

Option for padding with space when line number is enabled for programlisting? #186

Closed LdBeth closed 1 year ago

LdBeth commented 1 year ago

In current state (1.9.0 / 3e8fbc2d) the generated programlisting with line numbers enabled as in default setting.

For lines that are not numbers the content of <span class="ln"> </span> would be empty:

https://github.com/docbook/xslTNG/blob/985d770e9cd44d520044b435dfc9aa45b1473399/src/main/xslt/modules/verbatim.xsl#L434-L440

It often not a good practice to have empty span. The result without the CSS that specifies alignment would also look ugly, either by using text base browsers (w3m) or using Safari screen reader for example.

(defun make-bp (hm)
  (let* ((m (* 2 hm))
        (bp (make-noise m))
        last
5        void)
    (loop
       (setf last (find-location 1 bp))
       (setf (apply #'aref bp last) 0)
       (setf void (find-location 0 bp))
10       (setf (apply #'aref bp void) 1)
       (if (equal void last)
           (return bp)))))

verses

  (defun make-bp (hm)
    (let* ((m (* 2 hm))
          (bp (make-noise m))
          last
5         void)
      (loop
         (setf last (find-location 1 bp))
         (setf (apply #'aref bp last) 0)
         (setf void (find-location 0 bp))
10       (setf (apply #'aref bp void) 1)
         (if (equal void last)
             (return bp)))))

I did an ugly XSLT template to demonstrate the idea

<xsl:variable name="nlength"
                    select='string-length(
                            string(array:size($lines) + $starting-line-number - 1))'/>
...
<span class="ln">
  <xsl:sequence select="if ($numbered
                        and (array:size($lines) ge $minlines)
                        and (($index = 1 and $number-first)
                        or ($ln mod $every-nth = 0)))
                        then substring(string($ln) || '&#160;&#160;&#160;&#160;',0, $nlength + 1)
                        else substring('&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;',0, $nlength + 1)"/>
</span>
LdBeth commented 1 year ago

In fact I'm curious if there is a reason not using HTML table to display code listing like

<table>
<tbody>
<tr>
<td> <!-- vertical line numbers here --></td>
<td> <!-- code listing here --></td>
</tr>
</tbody>
</table>
1
2
3
line1
line2
line3

which have these advantages: does not require CSS to look nice, copy would not include the line numbers, works well with w3m.

and the current approach doesn't looks good when the browser/paged CSS media has wired interline spacing handling: With netsurf http://www.netsurf-browser.org and my own CSS

截圖 2022-10-24 下午6 17 17

With Prince https://www.princexml.com and using paged XSL and CSS setup:

截圖 2022-10-24 下午6 28 37
ndw commented 1 year ago

I'm sure I was motivated to "avoid using tables for layout" but your observations are spot on. In particular, I was unaware that some of the paged media browsers had fixed inter-line spacing.

I used to use tables as you suggest; I'll see about putting that code back in and perhaps making it the default.

ndw commented 1 year ago

I did a few experiments over lunch. The whole situation is fairly grim.

You're absolutely right that the current technique is a mess if you don't have the CSS. So tables look like they might be better.

What I'd really like to do is one tr per line with one td for the line number and one td for the line data. That makes sense and to some degree works best. It makes cutting and pasting the listing problematic, but the CSS user-select property can fix that. It won't fix it on text browsers, but there's probably nothing we can do there. It appears that NetSurf ignores the user-select property though. Alas.

Making a one-row, two cell table, where each cell contains a really long pre kind of works, though I worry about alignment across page breaks and with any sort of user CSS. (But that's true of the current design, I suppose). It works for most browsers, even NetSurf which has really wildly large line spacing. It does this with lynx:

                                Article wrapper

1

5

10

<?xml version="1.0" encoding="utf-8"?>
<article xmlns="http://docbook.org/ns/docbook" version="5.0">
   <title>Article wrapper</title>
   <programlisting language="xml"><inlinemediaobject>
         <imageobject>
           <imagedata fileref="programlisting.002.xml"
                      format="linespecific"/>
         </imageobject>
      </inlinemediaobject></programlisting>
</article>

That's kind of awful, but I'm not sure it's actually worse than the current behavior:

                                Article wrapper

1<?xml version="1.0" encoding="utf-8"?>
<article xmlns="http://docbook.org/ns/docbook" version="5.0">
   <title>Article wrapper</title>
   <programlisting language="xml"><inlinemediaobject>
5         <imageobject>
           <imagedata fileref="programlisting.002.xml"
                      format="linespecific"/>
         </imageobject>
      </inlinemediaobject></programlisting>
10</article>

To be perfectly honest, I'm strongly tempted to leave the listings just plain and apply the line numbers dynamically with JavaScript when the page is rendered. That won't work for print, of course, and I suppose cutting-and-pasting from PDF is a thing so the one-row-per-line approach is probably bad there too.

I think I'll add a table verbatim style using the one-row-two-cell tecnique and I might also add a javascript style.

LdBeth commented 1 year ago

Thanks for that!

For one-row two cell table with narrow screen, w3m would not wrap like lynx but instead able to scroll the page horizontally if in interactive mode, even in dump mode it would allow text to exceed the col width so I think it is not too bad.

A Common
Lisp
prototype
I've
written
before
the APL
one:

   (defun make-bp (hm)
     (let* ((m (* 2 hm))
           (bp (make-noise m))
           last
5          void)
       (loop
          (setf last (find-location 1 bp))
          (setf (apply #'aref bp last) 0)
          (setf void (find-location 0 bp))
10        (setf (apply #'aref bp void) 1)

The problem I could see with alignment of two cell table is when font-size of line numbers and the code are not same and when the browser does not recognize line-height (netsurf), but if they are just save monospace font there should be no alignment issue.

And I think for user-select Safari also does not support it unless with -webkit prefix. Something like below could be used but I totally understand if you don't like to add vendor specific CSS attribute.

-webkit-user-select: none;
user-select: none;
nikclayton commented 1 year ago

Is there a list of the approaches that have been tried so far, and their pros/cons? Or the specific constraints the solution needs to operate under?

My go to solution for this is normally something like https://sylvaindurand.org/using-css-to-add-line-numbering/ (writing from phone, so tricky to see what the code already does). As a technique it's worked for 8 years...

ndw commented 1 year ago

The pure CSS solution is an interesting one. DocBook's support predates that kind of thing so there's some historical legacy. There are also some options (only label every 5th line, don't label any lines if the listing is shorter than 9 lines, etc) that couldn't be supported.

I think I'm just going to make a table option and see what feedback I get.

LdBeth commented 1 year ago

Pure CSS actually supports modulo n line numbering. https://www.w3.org/Style/Examples/007/evenodd.en.html

Although one could do in XSLT more irregular ones like prime number only line numbering, but I doubt its usefulness.

However, the result that CSS method mentions by @nikclayton could achieve is essentially the same with the current approach, it is just moving the line number generation to the rendering time, so text browsers could just ignore that and display the result without numbering, instead of display the line numbers blended into the code.

But that still has the same problem I mentioned caused by line spacing, if the browser/printer supports the CSS. See the demo from https://stackoverflow.com/a/40842325/15387324 and press "Run code snippet"

LdBeth commented 1 year ago

This is what I could make with <table> with my XSLT customization: https://ldbeth.sdf.org/articles/blue-noise.html (I scrubbed the html file with tidy program). It would be nice it could help.

https://gist.github.com/LdBeth/93ec3b8cd6c12501d8a88547d2ee6fd0

ndw commented 1 year ago

Thanks. I think I've got the table-based verbatim elements working, but that's lead me to find a bug in how footnotes are handled in verbatim environments. Hopefully I can fix that tomorrow morning. I'm about done for the day.

ndw commented 1 year ago

Ironically, in the course of adding the table format, I reworked the way line numbers are presented and now they work just fine in the line style. I added leading spaces so that they would be right-aligned correctly and a following space to separate them from the line that follows. In a "normal" CSS presentation, this is irrelevant as the alignment properties do the right thing, but in a plain-text presentation, it improves the layout so much I wish I hadn't bothered with the table stuff :-)

On the plus side, I did fix a nasty bug in the way footnotes were handled in verbatim environments.

I also added a $verbatim-number-separator parameter, by default |, that is only visible in plain text presentations:

 8 |      hostname RTA
 9 |      router ospf
10 |          ospf router-id 192.134.7.241      ^1
11 |          network 192.134.7.0/24 area 0

Unclear if I should enable the separator by default.

LdBeth commented 1 year ago

This looks nice, I don’t insist on having table if the presentation without CSS/plaintext is repaired.

ndw commented 1 year ago

If I hadn't already implemented, I probably wouldn't. But I won't rip it out. Might turn out handy in the future.