bugGNU roff - Bugs: bug #64592, [troff] registers .m and .M...


bug #64592: [troff] registers .m and .M contain no initial value

Submitter:  Deri James <deri>
Submitted:  Thu 24 Aug 2023 09:35:37 PM UTC
Category:  Core Severity:  3 - Normal
Item Group:  Warning/Suspicious behaviour Status:  Need Info
Privacy:  Public Assigned to:  gbranden
Open/Closed:  Open Planned Release:  None
* Mandatory Fields

Add a New Comment Rich Markup

Jump to the original submission

Sat 18 Nov 2023 01:54:40 PM UTC, comment #11: 

After the change I ran the code in comment #1 and now the second line works fine (the first line will never work properly because we don't stack colour changes).

Interestingly the call to \m[\*[curcol]] where curcol contains the word "default", causes groff to emit "mr 0 0 0". Tested on pdf, ps, dvi, html and utf8 devices, so I would be happy with this fix to the problem.

Deri James <deri>
Group Member
Sat 18 Nov 2023 06:39:03 AM UTC, comment #10: 

Except for updating documentation (and mom(7)), the change I mentioned in comment #9 looks this easy.

diff --git a/src/libs/libgroff/color.cpp b/src/libs/libgroff/color.cpp
index 32a1bc8e7..8417c2074 100644
--- a/src/libs/libgroff/color.cpp
+++ b/src/libs/libgroff/color.cpp
@@ -395,7 +395,7 @@ char *color::print_color()
   return s;

-color default_color("");
+color default_color("default");

 // Local Variables:
 // fill-column: 72

G. Branden Robinson <gbranden>
Group administrator
Sat 18 Nov 2023 06:38:01 AM UTC, comment #9: 

We could certainly change the `default_color` object to give it a non-empty name.  It doesn't seem like it should be a problem; the default color already can't be redefined, so it's okay if it means different things for stroke and fill.

There is also no way AFAIK to query the formatter with respect to how a color is defined in practice.  (One can of course inspect device-independent output from a document that draws using the stroke or fill colors.)

We might need to coordinate such a change with Peter Schaffter, since mom(7) got bitten by bug #64421 and had to work around it.

G. Branden Robinson <gbranden>
Group administrator
Fri 17 Nov 2023 03:13:11 PM UTC, comment #8: 

Sorry, my fault, I was not clear. My reference to the registers in comment #3 was intended to show that your statement "registers .m and .M don't behave like other registers (containing the default values)" was the problem. I'm not sure (2) can be successfully implemented for all devices, groff does not "know" for certain the default colour. Groff outputs the command "DFd" initially since it is up to the output device to decide the default colour and groff does not know. This is why the fix for this anomalous behaviour is to delegate the default colour decision to each output driver.

It is quite common for macros or string to change a text state and restore it afterwards. One way is to note the current value using the documented state variables and restore them afterwards, but because \n[.m] does not contain the actual colour, this is not possible for colours.

Deri James <deri>
Group Member
Fri 17 Nov 2023 02:48:40 AM UTC, comment #7: 

comment #6:

> See comment #3 which mentions two registers which reflect the default value.

I apologize for being dense, but I don't see in comment #3 (or any other comments) what registers contain the default colors.  In comment #1, Branden mentions the color named "default" but also says "I don't know that we have access via the roff language to what that default is."  Comment #3 mentions two other registers that are initially populated with default values (.fn for font and .s for type size), hence my trying to clarify whether the objection this bug is raising is (1) that .m and .M are "odd men out" in not being populated with default values, or (2) that no other mechanism exists to retrieve the default colors.

I make the distinction because (2) can be solved in a way that doesn't break compatibility.  (I don't know if this particular aspect of compatibility is important to anyone.  But some users might use the existing behavior, for example to test whether any colors changes have yet been made in a document.)  But your example in the original submission might be trying to show that the .m/.M behavior also complicates certain aspects of color handling, meaning (1) is the root problem.

Dave <barx>
Group Member
Thu 16 Nov 2023 11:14:11 PM UTC, comment #6: 

comment #5:

> Is the problem that registers .m and .M don't behave like other registers (containing the default values), or that the roff language offers no way to retrieve these defaults?

See comment #3 which mentions two registers which reflect the default value. The problem is that precise initial glyph and fill colours are probably only relavent for X, dvi, ps and pdf. For tty and html initial colours are chosen by the console/browser.

My proposed "fix" for this bug is to add:-

.gcolor black
.fcolor black

To the startup tmac for the 4 drivers mentioned above, e.g. pdf.tmac, and document in gropdf.1 that the default colours are black. Which means that if a user interogates \n[.m] they will find the value "black" rather than an unhelpful null value.

While we are on the subject of colour, a little rant:-

For pdfs and probably postscript there are two drawing modes, stroke and fill. For graphical objects, using \D commands \m is the stroke colour and \M is the fill colour, but, for text \m is the fill colour, since the paths described by font glyphs are always filled, not stroked. If glyphs were stroked rather than filled you get hollow outlines of the letters. Of course, with pdf, you can specify whether you want text glyphs to be filled or stroked or both, but the default is filled only. This means I have to swap the meaning of \m depending on whether gropdf is dealing with graphics or text.

This really has nothing to do with this bug, its really just a note to myself that if I introduce a pdf extension to control fill/stroking glyphs, I need to unswap the meaning of \m if the user asks  for stroking!!

Deri James <deri>
Group Member
Thu 16 Nov 2023 08:09:47 PM UTC, comment #5: 

Is the problem that registers .m and .M don't behave like other registers (containing the default values), or that the roff language offers no way to retrieve these defaults?

Dave <barx>
Group Member
Thu 02 Nov 2023 04:32:01 PM UTC, comment #4: 

comment #3:

> I agree the registers are behaving as currently documented,
> the question is whether the current behaviour makes sense.

The additional question concerns back compatibility.  \M and \m are groff innovations, so we don't have to worry about back to AT&T troff, but they've been in groff for many years now, and some users may rely on the current behavior.

We should also consider cross-compatibility.  Heirloom troff has not picked up these extensions (its manual says, "The escape sequences ... \mx, \m(xx, \m[xxx], \Mx, \M(xx, \M[xxx], are read but discarded in groff compatibility mode since the corresponding concepts of ... built-in color support are foreign to Heirloom troff"), but neatroff has.  To anyone who has a neatroff handy, does it follow groff's behavior here?

I don't oppose breaking compatibility if there's good reason, and Deri's points make sense to me, but it should be considered.

Dave <barx>
Group Member
Mon 25 Sep 2023 04:15:19 PM UTC, comment #3: 

Hi Branden,

Just a few thoughts really, I agree the registers are behaving as currently documented, the question is whether the current behaviour makes sense.

Groff currently has no concept of opacity, gropdf allows pdfs included by pdfpic to have objects which are not fully opaque, but that's as far as it goes currently, so no, or null, colour can't mean transparent. Also, groff uses the null string generally, to reset states to the previous value. So \m[] resets stroke colour to its previous value. For these colour registers the initial value is a null string but empirical testing shows this default colour is black, in most cases but see below.

This is in contrast to other state variables where we document the default value and the state variable reflects this, so the default pointsize is 10pt and \n[.s] contains 10, the default font is Times-Roman and the initial value of \n[.fn] is TR. Is there a reason why colour should be treated differently?

It could be argued that the default colour is in the purview of the output driver, and in this case each device's tmac file should set the actual default colour and document the choice in its man page. The odd man out is grotty since the initial stroke/fill colours are probably set by the terminal settings rather than groff, but you're more au-fait with terminal output. This dichotomy is probably the reason for the copout about default colours in the documentation without specifying what the actual colour would be. This may be a strong argument for delegating the initial startup default colour to each driver, rather than a one size fits all within groff.

Deri James <deri>
Group Member
Mon 25 Sep 2023 01:43:15 PM UTC, comment #2: 

Hi Deri,

Any thoughts on this issue, and my remarks in comment #1?

G. Branden Robinson <gbranden>
Group administrator
Fri 25 Aug 2023 01:35:14 PM UTC, comment #1: 

Hi Deri,

I was able to confirm the initial emptiness of these registers at formatter startup.

The registers seem to be behaving as documented.

    \n[.m]         Stroke color (string‐valued); see .gcolor and \m.
                   Empty if the stroke color is the default.
    \n[.M]         Fill color (string‐valued); see .fcolor and \M.
                   Empty if the fill color is the default.

Also, there is a color named "default".

   Each output device has a color named 'default', which cannot be
redefined.  A device's default stroke and fill colors are not
necessarily the same.  For the 'dvi', 'html', 'pdf', 'ps', and 'xhtml'
output devices, GNU 'troff' automatically loads a macro file defining
many color names at startup.  By the same mechanism, the devices
supported by 'grotty' recognize the eight standard ISO 6429/EMCA-48
color names.(1)  (*note Colors-Footnote-1::)

I don't know that we have access via the roff language to what that default is.

I also don't know if we can just assume that the default stroke and fill colors are black, though it seems likely to me that that's extremely common.

What is your use case?

G. Branden Robinson <gbranden>
Group administrator
Thu 24 Aug 2023 09:35:37 PM UTC, original submission:  

This code illustrates the issue:-

.ds curcol \n[.m]
.ds a \m[red]D\m[]eri
.ds b \m[blue]\*a James\m[]
\*b is black
.\" Fails - no colour stacking the 2nd \m[] does nothing
.ds b \m[blue]\*a James\m[\*[curcol]]
\*b is black
.\" Fails - using stored colour from \n[.m] fails too - why (answer below)
.gcolor black
.\" Answer - the value of \n[.m] is null before any colour is set
.\"          even though we document the default colour is black.
.\"          if .m is null \m[\*[curcol]] is equivalent to \m[] which we know fails.
.ds curcol \n[.m]
.ds b \m[blue]\*a James\m[\*[curcol]]
\*b is black
.\" Now it works because \n[.m] has been populated

Should \n[.m] (and \n[.M]) be populated with the default colour (black) initially.

Deri James <deri>
Group Member


(Note: upload size limit is set to 16384 kB, after insertion of the required escape characters.)

Attach Files:

No files currently attached


Depends on the following items: None found

Items that depend on this one: None found


Carbon-Copy List
  • -email is unavailable- added by barx (Posted a comment)
  • -email is unavailable- added by gbranden (Posted a comment)
  • -email is unavailable- added by deri (Submitted the item)

    There are 0 votes so far. Votes easily highlight which items people would like to see resolved in priority, independently of the priority of the item set by tracker managers.

    Only logged-in users can vote.


    Follow 4 latest changes.

    Date Changed by Updated Field Previous Value => Replaced by
    2023-08-25 gbranden Item GroupIncorrect behaviour Warning/Suspicious behaviour
        StatusNone Need Info
        Assigned toNone gbranden
        SummaryRegisters .m and .M contain no initial value [troff] registers .m and .M contain no initial value

    Back to the top

    Powered by Savane 3.12