Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Horizontal line output in nroff/troff #78

Closed
rnkn opened this issue Nov 15, 2021 · 5 comments · Fixed by #79
Closed

Horizontal line output in nroff/troff #78

rnkn opened this issue Nov 15, 2021 · 5 comments · Fixed by #79

Comments

@rnkn
Copy link
Contributor

rnkn commented Nov 15, 2021

Hello!

I have both a small problem with horizontal rules in Heirloom nroff/troff, and some ideas about it...

If we begin with this minimal markdown:

Fusce suscipit, wisi nec facilisis facilisis, est dui fermentum leo,
quis tempor ligula erat quis odio. Nunc porta vulputate tellus. Nunc
rutrum turpis sed pede. Sed bibendum.

***

Nunc aliquet, augue nec adipiscing interdum, lacus tellus malesuada
massa, quis varius mi purus non odio. Pellentesque condimentum, magna ut
suscipit hendrerit, ipsum augue ornare nulla, non luctus diam neque sit
amet urna.

The following creates a horizontal rule between the paragraphs using GNU groff:

$ < test.md lowdown -Tms | groff -Tutf8 | less   # okay
$ < test.md lowdown -Tms | groff -ms > out.ps    # okay

However the same with Heirloom nroff/troff we get a horizontal line of 'n' characters:

nroff

troff pdf

I'm pretty sure this is an easy fix:

diff --git a/nroff.c b/nroff.c
index 92b9627..bb50a30 100644
--- a/nroff.c
+++ b/nroff.c
@@ -1002,10 +1002,10 @@ rndr_hrule(const struct nroff *st, struct bnodeq *obq)
 	 * The LP is to reset the margins.
 	 */
 
-	if (bqueue_block(obq, ".LP") == NULL)
+	if (bqueue_block(obq, ".RT") == NULL)
 		return 0;
 	if (!st->man && 
-	    bqueue_block(obq, "\\l\'\\n(.lu-\\n(\\n[.in]u\'") == NULL)
+	    bqueue_block(obq, "\\l\'\\n(.lu\'") == NULL)
 		return 0;
 	return 1;
 }

Here we're using the .RT macro to reset the paragraph, which I think removes any indentation, then just drawing a line without the need to subtract indentation.

Now to test:

$ < test.md lowdown -Tms | groff -Tutf8 | less         # okay
$ < test.md lowdown -Tms | groff -ms > out.ps          # okay
$ < test.md lowdown -Tms | nroff | less                # okay
$ < test.md lowdown -Tms | troff -ms | dpost > out.ps  # okay

But ideally a horizontal rule would have some space around it:

diff --git a/nroff.c b/nroff.c
index 92b9627..0827737 100644
--- a/nroff.c
+++ b/nroff.c
@@ -1002,10 +1002,10 @@ rndr_hrule(const struct nroff *st, struct bnodeq *obq)
 	 * The LP is to reset the margins.
 	 */
 
-	if (bqueue_block(obq, ".LP") == NULL)
+	if (bqueue_block(obq, ".RT") == NULL)
 		return 0;
 	if (!st->man && 
-	    bqueue_block(obq, "\\l\'\\n(.lu-\\n(\\n[.in]u\'") == NULL)
+	    bqueue_block(obq, ".sp 1\n\\l\'\\n(.lu\'\n.sp 1") == NULL)
 		return 0;
 	return 1;
 }

This adds a blank line before and after the horizontal rule (which I think looks better):

spaced hr

However! How often do we see horizontal rules like this in typeset documents? Usually we have a 3-line vertical space (i.e. four newlines) or a vertical space with "***" like this:

scene break

When I'm writing fiction in markdown, I use the *** for a traditional "scene break" so ideally I'd like to be able to typeset these.

But hardcoding this kind of literary style on everyone would not be very considerate...

While the ms macro file mostly has macros to correlate for markdown things like headings, it has nothing for horizontal rules. So instead, what if we allowed the user to define their own HR macro to insert as the "horizontal rule" in roff output?

In this case, lowdown could output a conditional — if HR is defined, insert that, otherwise, insert the horizontal rule as above:

.LP
Fusce suscipit, wisi nec facilisis facilisis, est dui fermentum leo,
quis tempor ligula erat quis odio. Nunc porta vulputate tellus. Nunc
rutrum turpis sed pede. Sed bibendum.
.RT
.ie d HR \{\
.HR
\}
.el \{\
.sp 1
\l'\n(.lu'
.sp 1
.\}
.LP
Nunc aliquet, augue nec adipiscing interdum, lacus tellus malesuada
massa, quis varius mi purus non odio. Pellentesque condimentum, magna ut
suscipit hendrerit, ipsum augue ornare nulla, non luctus diam neque sit
amet urna.

The above will still output the same horizontal rule (with blank lines above & below), but if the user were to define their own HR like so:

.de HR
.sp 1
.ce 1
***
.sp 1
..

...and then pass that as a configuration:

$ < test.md lowdown -Tms | cat config.ms - | groff -ms > out.ps

Then they would get the literary-style scene break (or whatever else they defined).

Thoughts?

p.s. I'm using the LP macro for paragraphs occurring after a horizontal rule, which I think it appropriate, although it appears lowdown uses the PP macro in this case.

@kristapsdz
Copy link
Owner

Ok, first, this is the best Issue I've ever seen. Second, I love it. I'll roll the C code for it---let me know if you'd like to do that yourself and get the glory. If you were... you... where would you want to see this documented? I'm thinking of converting the existing "metadata" subsection in lowdown.1 into a new "standalone" that describes how each output handles its standalone document envelope.

@rnkn
Copy link
Contributor Author

rnkn commented Nov 16, 2021

Haha thanks!

I seek no glory. It's probably best if you implement, because this is all I could come up with:

diff --git a/nroff.c b/nroff.c
index 92b9627..1107264 100644
--- a/nroff.c
+++ b/nroff.c
@@ -1002,10 +1002,10 @@ rndr_hrule(const struct nroff *st, struct bnodeq *obq)
 	 * The LP is to reset the margins.
 	 */
 
-	if (bqueue_block(obq, ".LP") == NULL)
+	if (bqueue_block(obq, ".RT") == NULL)
 		return 0;
 	if (!st->man && 
-	    bqueue_block(obq, "\\l\'\\n(.lu-\\n(\\n[.in]u\'") == NULL)
+	    bqueue_block(obq, ".ie d HR \\{\\\n.HR\n\\}\n.el \\{\\\n.sp 1v\n\\l'\\n(.lu'\n.sp 1v\n.\\}") == NULL)
 		return 0;
 	return 1;
 }

This works as advertised, but it's ugly and unreadable code. As an indication of my current C abilities, I couldn't figure out how to split this into multiple line statements without getting a bunch of compiler errors.

As for documentation, I would look for something like this under Output Modes, but yeah I see everything for -Tms is just in that one paragraph, which might feel a bit cramped. It probably needs a second paragraph and maybe an addition to the EXAMPLES section.

Overriding the horizontal rule with your own .de HR doesn't necessarily mean you're using the -s standalone output though (e.g. I'm not for this hypothetical novel), but yeah I can see that whole section given some breathing room.


Btw, just noticed:

diff --git a/man/lowdown.1 b/man/lowdown.1
index fa377bd..adc6e0c 100644
--- a/man/lowdown.1
+++ b/man/lowdown.1
@@ -509,7 +509,7 @@ is used as a variable.
 .Ss Output modes
 A detailed description of the output modes follows.
 .Bl -tag -width Ds
-.It Fl T Ns Ar latex
+.It Fl T Ns Ar fodt
 .Dq Flat
 OpenDocument output.
 This output mode is still in development.

@rnkn
Copy link
Contributor Author

rnkn commented Nov 16, 2021

Okay so here's an updated proposed diff:

diff --git a/nroff.c b/nroff.c
index 92b9627..e32d71e 100644
--- a/nroff.c
+++ b/nroff.c
@@ -995,18 +995,19 @@ rndr_raw_block(const struct nroff *st,
 }
 
 static int
-rndr_hrule(const struct nroff *st, struct bnodeq *obq)
+rndr_hrule(struct nroff *st, struct bnodeq *obq)
 {
 	/*
-	 * I'm not sure how else to do horizontal lines.
-	 * The LP is to reset the margins.
+	 * Use RT to reset the paragraph.
+	 * Set post_para so we get LP rather than PP.
 	 */
 
-	if (bqueue_block(obq, ".LP") == NULL)
+	if (bqueue_block(obq, ".RT") == NULL)
 		return 0;
 	if (!st->man && 
-	    bqueue_block(obq, "\\l\'\\n(.lu-\\n(\\n[.in]u\'") == NULL)
+	    bqueue_block(obq, ".ie d HR \\{\\\n.HR\n\\}\n.el \\{\\\n.sp 1v\n\\l'\\n(.lu'\n.sp 1v\n.\\}") == NULL)
 		return 0;
+	st->post_para = 1;
 	return 1;
 }

Then with me setting the HR macro to this:

.\" HR - scene break
.de HR
.sp 1v
.CD
***
..

This is the kind of typeset PDF I get:

literary

And now this is just showing off:

showing off

kristapsdz added a commit that referenced this issue Nov 16, 2021
Adds a sub-section "Standalone" to lowdown.1 that specifies what happens
when -s is provided.

Also splits out recognised metadata from those within the document, and
those only in -s content.

Future versions will have additional content in "Output modes" that
stipulates any required invocations that a caller may need (this is now
done in e.g. -Tlatex in specifying which includes are necessary).

References #78
@kristapsdz
Copy link
Owner

kristapsdz commented Nov 16, 2021

You can break apart string literals like so:

HBUF_PUTSL(ob, "this is "
    "all going to be on one "
    "line\n");

If you put in a pull request, tho, don't worry about style---I can take care of it. No worries if you don't feel confident doing so, as you've provided all the information needed. Thank you again for that!

However, according to groff's tmac files, RT is -ms only. Is there an alternative that works for -man, or is there an invocation that would work for both? (Or is my analysis incorrect? I couldn't find RT in the manpages and dug around the sources.)

@rnkn
Copy link
Contributor Author

rnkn commented Nov 17, 2021

Oh you're right, RT is -ms only. I will use LP, which calls RT anyway in -ms and calls PP in -man. PR incoming!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants