Setting Custom Checkbox Appearance

Product: PDFTron .NET x64

Product Version: 9.1.0

Setting Custom Checkbox Appearance

I am trying to customize the appearance of a checkbox field to use other ZapfDingbats characters (namely, a circle).

I am using the following version of the CreateCheckmarkAppearance() function:

        private Obj CreateCheckmarkAppearance(PDFDoc doc, Rect updateRectangle) {
            // Create a checkmark appearance stream
            var build = new ElementBuilder();
            var writer = new ElementWriter();
            writer.Begin(doc);

            // Draw background
            Element e = build.CreateRect(updateRectangle.x1, updateRectangle.y1, updateRectangle.Width(), updateRectangle.Height());
            e.SetPathFill(true); // this path is should be filled

            // Set the path color space and color
            GState gstate = e.GetGState();
            gstate.SetFillColorSpace(ColorSpace.CreateDeviceRGB());
            gstate.SetFillColor(new ColorPt(1, 0, 0)); // red
            writer.WritePlacedElement(e);

            // Draw checkmark
            writer.WriteElement(build.CreateTextBegin());

            // other options are circle ("l"), diamond ("H"), cross ("\x35")
            // See section D.4 "ZapfDingbats Set and Encoding" in PDF Reference
            // Manual for the complete graphical map for ZapfDingbats font.
            e = build.CreateTextRun("l", Font.Create(doc, Font.StandardType1Font.e_zapf_dingbats), 1);
            writer.WriteElement(e);
            writer.WriteElement(build.CreateTextEnd());

            Obj stm = writer.End();

            // Set the bounding box
            stm.PutRect("BBox", updateRectangle.x1, updateRectangle.y1, updateRectangle.x2, updateRectangle.y2);
            stm.PutName("Subtype", "Form");

            return stm;
        }

I’ve confirmed that I can successfully create a widget field and set the border and background colors, but, so far, I have been unable to modify the appearance in any way using the SetAppearance() function, like so:

var widget = CheckBoxWidget.Create(srcPdfDoc, valField.GetUpdateRect(), valField); 
                                    // Widget colors are set properly when the following lines are uncommented
                                    //widget.SetBackgroundColor(new ColorPt(1, 0, 0), 3);
                                    //widget.SetBorderColor(new ColorPt(1, 0, 0), 3);
                                    widget.SetAppearance(this.CreateCheckmarkAppearance(srcPdfDoc, valField.GetUpdateRect()));
                                    widget.SetChecked(true);
                                    widget.RefreshAppearance();
                                    valField.RefreshAppearance();
                                    srcPdfDoc.GetPage(1).AnnotPushFront(widget);

Why isn’t the background fill color getting set to red, let alone the ZapfDingbats character getting modified?

Hello, I’m Ron, an automated tech support bot :robot:

While you wait for one of our customer support representatives to get back to you, please check out some of these documentation pages:

Guides:APIs:Forums:

Hello,

Thank you for providing the code. There are a few things that need to be changed within it so that the appearance works as expected.

For the create appearance function, you must set the text matrix so that it is placed properly on the resulting document:

private static Obj CreateCheckmarkAppearance(PDFDoc doc, Rect updateRectangle, string text)
{
    // Create a checkmark appearance stream
    var build = new ElementBuilder();
    var writer = new ElementWriter();
    writer.Begin(doc);

    //// Draw background
    Element e = build.CreateRect(updateRectangle.x1, updateRectangle.y1, updateRectangle.x2, updateRectangle.y2);
    e.SetPathFill(true); // this path is should be filled

    // Set the path color space and color
    GState gstate = e.GetGState();
    gstate.SetFillColorSpace(ColorSpace.CreateDeviceRGB());
    gstate.SetFillColor(new ColorPt(1, 0, 0)); // red
    writer.WriteElement(e);

    // Draw checkmark
    writer.WriteElement(build.CreateTextBegin());

    // other options are circle ("l"), diamond ("H"), cross ("\x35")
    // See section D.4 "ZapfDingbats Set and Encoding" in PDF Reference
    // Manual for the complete graphical map for ZapfDingbats font.
    e = build.CreateUnicodeTextRun(text, Font.Create(doc, Font.StandardType1Font.e_zapf_dingbats), 18);
    e.SetTextMatrix(new Matrix2D(1, 0, 0, 1, updateRectangle.x1, updateRectangle.y1));
    e.GetGState().SetFillColor(new ColorPt(0, 0, 0));
    writer.WriteElement(e);
    writer.WriteElement(build.CreateTextEnd());

    Obj stm = writer.End();

    // Set the bounding box
    stm.PutRect("BBox", updateRectangle.x1, updateRectangle.y1, updateRectangle.x2, updateRectangle.y2);
    stm.PutName("Subtype", "Form");

    return stm;
}

For checkboxes, there is an extra step in setting different appearances for the checked and unchecked states. Something like the following function will work:

static void SetCheckboxApp(Widget wg, Annot.AnnotationState state, bool on, Obj appearence)
{
    Obj wgObject = wg.GetSDFObj();
    if (wgObject == null) return;
    Obj ap = wgObject.FindObj("AP");
    if (ap == null)
    {
        ap = wgObject.PutDict("AP");
    }
    Obj anstate = null;
    switch (state)
    {
        case Annot.AnnotationState.e_normal:
            {
                anstate = ap.FindObj("N");
                if (anstate == null) anstate = ap.PutDict("N");
                break;
            }
        case Annot.AnnotationState.e_down:
            {
                anstate = ap.FindObj("D");
                if (anstate == null) anstate = ap.PutDict("D");
                break;
            }
        default:
            {
                return;
            }
    }

    if (on)
    {
        anstate.Put("On", appearence);
    } else
    {
        anstate.Put("Off", appearence);
    }
}

Finally, you can call the functions like so. Please note that you should not be calling RefreshAppearence as it will wipe the custom appearances:

var rect = new Rect(0, 0, 30, 30);
var widget = CheckBoxWidget.Create(doc, rect);

Obj down = CreateCheckmarkAppearance(doc, rect, "H");
Obj up = CreateCheckmarkAppearance(doc, rect, "1");

SetCheckboxApp(widget, Annot.AnnotationState.e_normal, true, down);
SetCheckboxApp(widget, Annot.AnnotationState.e_normal, false, up);

widget.SetChecked(true);
page.AnnotPushFront(widget);
doc.Save("output.pdf", SDFDoc.SaveOptions.e_linearized);

Note that Adobe Reader might not display the custom appearance if its form highlighting feature is set:

Please let me know if this works for you.

Thank you, @shakthi124 - y our feedback has gotten me halfway there!

I have modified the CreateCheckmarkAppearance() function so that it sets the widget’s background color based on whether or not the box should be checked:

        private static Obj CreateCheckmarkAppearance(PDFDoc doc, Rect updateRectangle, string text, bool isSelected = false) {
            // Create a checkmark appearance stream
            var build = new ElementBuilder();
            var writer = new ElementWriter();
            writer.Begin(doc);

            //// Draw background
            Element e = build.CreateRect(updateRectangle.x1, updateRectangle.y1, updateRectangle.x2, updateRectangle.y2);
            e.SetPathFill(true); // this path is should be filled

            GState gstate = e.GetGState();
            gstate.SetFillColorSpace(ColorSpace.CreateDeviceRGB());
            // Set color conditionally - red if selected, green if not
            gstate.SetFillColor(isSelected == true ? new ColorPt(1, 0, 0) : new ColorPt(0, 1, 0));
            writer.WriteElement(e);

            // Draw checkmark
            writer.WriteElement(build.CreateTextBegin());

            // other options are circle ("l"), diamond ("H"), cross ("\x35")
            // See section D.4 "ZapfDingbats Set and Encoding" in PDF Reference
            // Manual for the complete graphical map for ZapfDingbats font.
            e = build.CreateUnicodeTextRun(text, Font.Create(doc, Font.StandardType1Font.e_zapf_dingbats), 18);
            e.SetTextMatrix(new Matrix2D(1, 0, 0, 1, updateRectangle.x1, updateRectangle.y1));
            e.GetGState().SetFillColor(new ColorPt(1, 0, 0));
            writer.WriteElement(e);
            writer.WriteElement(build.CreateTextEnd());

            Obj stm = writer.End();

            // Set the bounding box
            stm.PutRect("BBox", updateRectangle.x1, updateRectangle.y1, updateRectangle.x2, updateRectangle.y2);
            stm.PutName("Subtype", "Form");

            return stm;
        }

I have confirmed that, when used as follows, the widget selected fill is red, and the unselected fill is green.

var widget = CheckBoxWidget.Create(srcPdfDoc, rect);
Obj down = CreateCheckmarkAppearance(srcPdfDoc, rect, "H", true); 
Obj up = CreateCheckmarkAppearance(srcPdfDoc, rect, "l");

SetCheckboxApp(widget, Annot.AnnotationState.e_normal, true, down);
SetCheckboxApp(widget, Annot.AnnotationState.e_normal, false, up);
// Comment or uncomment based on the state you want to see
widget.SetChecked(true);

However, I’m still not able to print any text to the checkbox field. I’ve commented out the background fill code to rule out some possibilities (e.g. that the text is covered up or the colors don’t have enough contrast), but, in every case, no text shows up.

Also, please note that the ZapfDingbats code for circle is lowercase “L” - “l” - instead of the number one -“1”. These look exactly the same in some fonts!

It looks like from the code below you are setting the text colour as red each time:

Could you try setting the fill color to black (or some other color)? In addition, was the code working on your end prior to making any modifications?

@shakthi124 ,

The code works in terms of setting the background color, but not in terms of writing a text item (e.g. check, circle, diamond) into the widget.

This seems to be the case no matter what colors I set the background and text. I’ve even removed the background fill entirely to make sure that the text by itself is not being printed (or, at least, is still not visible).