AcroForm: stdColors forces explicit fill/border/text colors even when caller passes None, preventing style inheritance

61 views
Skip to first unread message

Cozmin Velciu

unread,
Apr 9, 2026, 6:35:39 AMApr 9
to reportlab-users
SUMMARY
-------
AcroForm.stdColors() unconditionally replaces None color arguments with concrete
color values before writing widget dictionaries. This makes it impossible to create
AcroForm fields that inherit their appearance from the document's AcroForm-level
/DA//DR or from PDF viewer defaults. Every field generated through textfield(),
checkbox(), radio(), or _textfield() (used for dropdowns) always receives explicit
/MK /BG, /MK /BC, and a widget-level /DA string — even when the caller passes no
style arguments at all.


ENVIRONMENT
-----------
ReportLab: 4.4.10
Python:    3.14 (Windows)


ROOT CAUSE
----------
AcroForm.stdColors() in reportlab/pdfbase/acroform.py:

    @staticmethod
    def stdColors(t, b, f):
        if isinstance(f, CMYKColor) or isinstance(t, CMYKColor) or isinstance(b, CMYKColor):
            return (t or CMYKColor(0,0,0,0.9), b or CMYKColor(0,0,0,0.9), f or CMYKColor(0.12,0.157,0,0))
        else:
            return (t or Color(0.1,0.1,0.1), b or Color(0.1,0.1,0.1), f or Color(0.8,0.843,1))

When fillColor=None, borderColor=None, textColor=None (the defaults for all field
creation methods), stdColors substitutes:

  - fillColor  -> Color(0.8, 0.843, 1)   (distinctive light blue)
  - borderColor -> Color(0.1, 0.1, 0.1)  (near-black)
  - textColor  -> Color(0.1, 0.1, 0.1)   (near-black)

These substituted colors are then written into:

  - /MK /BG  — background color array
  - /MK /BC  — border color array
  - /DA      — Default Appearance string, e.g. /Helv 12 Tf 0.1 0.1 0.1 rg

Additionally, fontSize defaults to 12 in _textfield() when unset:

    if fontSize is None:
        fontSize = 12

And the /DA string is always written explicitly on the widget:

    DA=PDFString('/%s %d Tf %s' % (iFontName, fontSize, self.streamFillColor(textColor))),


IMPACT
------
The PDF specification supports a /DA inheritance chain:

  1. Widget-level /DA
  2. AcroForm-level /DA (document default)
  3. PDF viewer built-in default

The AcroForm-level /DA and /DR are the intended mechanism for setting a consistent
appearance across all fields in a document. Because ReportLab always writes a
widget-level /DA (and always writes /MK /BG and /MK /BC), callers who want fields
to inherit appearance from an existing PDF template's /DA//DR have no way to achieve
this through the API. The only workaround is to post-process the generated PDF and
manually delete these entries after generation.


CONCRETE SCENARIO
-----------------
A PDF template already contains:

    /AcroForm << /DA (/Helv 0 Tf 0 g) /DR << ... >> /Fields [...] >>

A developer wants to add new fields that inherit font, size, and color from the
template's /DA. They call:

    canvas.acroForm.textfield(name="myField", x=50, y=700, width=200, height=20)

Expected: widget gets no widget-level /DA, inherits /Helv 0 Tf 0 g from AcroForm.

Actual: widget gets /DA (/Helv 12 Tf 0.1 0.1 0.1 rg) and
/MK << /BG [0.8 0.843 1] /BC [0.1 0.1 0.1] >>, overriding template inheritance
entirely.


PROPOSED FIX
------------
Change the default behavior so that when style arguments are None, no explicit style
entries are written to the widget dictionary:

  - When fillColor=None:  do not write /MK /BG.
  - When borderColor=None: do not write /MK /BC.
  - When textColor=None and fontName=None and fontSize=None: do not write /DA.
  - When borderWidth=None or 0: do not write /BS.

stdColors() should only be called when at least one color argument is explicitly
provided by the caller. When all three are None, skip stdColors() entirely and write
no color entries.

This aligns ReportLab's behavior with the PDF spec's intent: widget-level entries
should only be present when explicitly required, and inheritance from the AcroForm
/DA//DR chain should be the natural fallback when nothing is specified.


WORKAROUND
----------
Post-process the generated PDF with pypdf or pikepdf and manually delete /DA,
/MK /BG, /MK /BC, and /BS from each widget annotation that should inherit appearance,
then set /NeedAppearances true on the AcroForm dictionary so PDF viewers regenerate
field appearances from inherited defaults.

This workaround is functional but requires an additional post-processing pass over
the entire PDF object graph, is not obvious to discover, is contrary to the PDF
spec's intended inheritance mechanism, and is fragile across PDF reader
implementations.

Robin Becker

unread,
Apr 9, 2026, 11:19:15 AMApr 9
to reportl...@googlegroups.com
If you have a working patch please just email it.

--

---
You received this message because you are subscribed to the Google Groups "reportlab-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to reportlab-use...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/reportlab-users/73f032c5-5ec3-4657-9c48-74714c7a6cfbn%40googlegroups.com.


--
Robin Becker

Cozmin Velciu

unread,
Apr 11, 2026, 5:34:38 AMApr 11
to reportl...@googlegroups.com
I'll come back with a patch on Tuesday or Wednesday then. Happy Easter! 

You received this message because you are subscribed to a topic in the Google Groups "reportlab-users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/reportlab-users/mu5H2tchSOo/unsubscribe.
To unsubscribe from this group and all its topics, send an email to reportlab-use...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/reportlab-users/CAOBYczpFoOJH9Xd9q8c_zgakVz98SxHW3zGnn-T_zJOYb6N_6Q%40mail.gmail.com.

Cozmin Velciu

unread,
Apr 14, 2026, 2:46:21 AMApr 14
to reportlab-users
Do you have a repository somewhere I can fork and create a pull request against? Did not find anything in the documentation.

Robin Becker

unread,
Apr 14, 2026, 5:21:27 AMApr 14
to reportl...@googlegroups.com
We have a mercurial repository here https://hg.reportlab.com/hg-public/reportlab

You can just pull that repo make changes and then use hg diff to obtain a patch.

There is a github mirror here https://github.com/MrBitBucket/reportlab-mirror but we don't use it for commits or pulls etc. It's just a convenience. You can send a patch against a clone if that is easier, but we will not act on any pull requests.



--
Robin Becker

Cozmin Velciu

unread,
Apr 14, 2026, 6:19:51 AMApr 14
to reportl...@googlegroups.com
I went ahead and created a patch with hg diff. My apologies, I'm used to working with git, and did not know the exact workflow for mercurial.

It seems that attaching files is not allowed in the Google Groups Web Interface, but it worked from the Email client. Seems to be related to the "Who can attach files" permission under "Posting policies" in "Group settings".

As a summary of the patch, it changes pdfbase/acroform.py so omitted style arguments remain omitted instead of being converted into hardcoded defaults and then written back into widget dictionaries.

Main behavior changes:
  • removed stdColors()
  • updated varyColors() so it preserves None rather than trying to lighten/darken missing colors
  • added guards in checkboxAP() and txAP() so appearance stream generation remains valid when fill/border/text colors are omitted
  • changed widget dictionary emission so:
    • /MK /BG is only written when fillColor is explicitly provided
    • /MK /BC is only written when borderColor is explicitly provided
    • /DA is only written when at least one of textColor, fontName, or fontSize is explicitly provided
    • /BS is only written when borderWidth is explicitly provided and truthy
  • changed the default borderWidth from 1 to None in the relevant AcroForm entry points so “unset” no longer materializes a default border
  • fixed checkForceBorder() so forceBorder=True with fillColor=None no longer causes an unintended fill
  • fixed the txAP() path by pre-initializing the border-derived variables used later in the /Tx clipping-region stream when no border is drawn
I also added focused regression tests in tests/test_pdfbase_pdfform.py covering:
  • textfields with omitted style args not emitting /DA, /MK, or /BS
  • checkboxes with omitted colors not emitting /MK /BG or /MK /BC
  • forceBorder=True with no fill color not implying a background widget entry
I ran the focused pdfform tests and the full repository test suite locally.



acroform-inheritance.patch

Robin Becker

unread,
Apr 16, 2026, 4:05:22 AM (12 days ago) Apr 16
to reportl...@googlegroups.com
Hi Cozmin, thanks for the patch and the work.
1) It applied OK for me and seemed to do what you want.
2) There is a problem with the semantics in that by leaving the default values in the method definitions as None it changes the outcome for users that have not explicitly set text/fill/borderColor or borderWidth. Actually borderWidth already tried to use None in the way you want.
3) There are two approaches
    a) leave the None defaults as is and provide a magic value noDraw which would be used to turn off drawing of the relevant bits.
        The None value would continue to provide the same default widths and colours as before.
    b) change the None defaults to use a different magic value useDefault and make None the value that inhibits drawing; it makes None
        semantically sensible. I think that's the way we will go,
     if you have any arguments  for or against let me know.

4) I attach my latest patch for your inspection. I will go with this unless you or others have serious objections.




--
Robin Becker
cozmin-robin-patch.zip

Robin Becker

unread,
Apr 16, 2026, 4:08:59 AM (12 days ago) Apr 16
to reportlab-users
I forgot to say that my patch adds back the default values that we had before and also has a mechanism (untested) that would allow changing the defaults. That would mean you could use some calls to change the default values to None.

Cozmin Velciu

unread,
Apr 16, 2026, 6:50:58 AM (12 days ago) Apr 16
to reportlab-users
Thanks for the reply! I'll try to take a look today or tomorrow and come back at you.

Cozmin Velciu

unread,
Apr 17, 2026, 8:29:34 AM (11 days ago) Apr 17
to reportlab-users
Sorry, was a crazy day today, did not get the chance to try it out. If you're not in a rush, I'll prioritize this on Monday. Your approach sounds good.

Robin Becker

unread,
Apr 17, 2026, 12:20:25 PM (11 days ago) Apr 17
to reportl...@googlegroups.com
Reply all
Reply to author
Forward
0 new messages