Cozmin Velciu
unread,Apr 9, 2026, 6:35:39 AMApr 9Sign in to reply to author
Sign in to forward
You do not have permission to delete messages in this group
Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message
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.