How do I make a FreeText annotation with solid text/border, but semi-transparent background?

Question:

I want to create a FreeText annotation, where the text and border are opaque (solid), but the background color is semi-transparent. There only seems to be a single Opacity setting for annotations, which gets applied to the entire annotation. How can I accomplish this?

Answer:

Yes, the PDF standard just supports a single opacity for annotations. To make your own adjustments to opacity, you would do the following.

  1. Set the opacity on the annotation normally.
  2. Call RefreshAppearance to generate the default appearance.
  3. Run the following code, which will make the adjustments for you.
// Generation default appearance, and apply custom opacity
static void SetAppearance(pdftron.PDF.Annots.Markup annot, PDFDoc doc)
{
annot.RefreshAppearance(); // create default
Obj app_stm = annot.GetAppearance(); // generate default appearance that will then get modified
ElementBuilder builder = new ElementBuilder();
ElementWriter writer = new ElementWriter();
ElementReader reader = new ElementReader();
reader.Begin(app_stm);
writer.Begin(doc);

//... read from reader, writing everything to the writer, and add your new content
System.Collections.Generic.List<int> visited = new System.Collections.Generic.List<int>();
ProcessElements(reader, writer, visited, annot);

Obj appearance_stream = writer.End();
appearance_stream.Put("BBox", app_stm.FindObj("BBox"));
annot.SetAppearance(appearance_stream);
}

static void ProcessElements(ElementReader reader, ElementWriter writer, System.Collections.Generic.List<int> visited, pdftron.PDF.Annots.Markup markup)
{
Element element;
while ((element = reader.Next()) != null) // Read page contents
{
switch (element.GetType())
{
case Element.Type.e_path: // Solid stroke, and transparent fill
{
GState gs = element.GetGState();
gs.SetStrokeOpacity(1.0);
gs.SetFillOpacity(markup.GetOpacity());
writer.WriteElement(element);
break;
}
case Element.Type.e_text: // solid stroke and fill (text is normally filled)
{
GState gs = element.GetGState();
gs.SetStrokeOpacity(1.0);
gs.SetFillOpacity(1.0);
writer.WriteElement(element);
break;
}
case Element.Type.e_form:
{
writer.WriteElement(element); // write Form XObject reference to current stream
Obj form_obj = element.GetXObject();
if (!visited.Contains(form_obj.GetObjNum())) // if this XObject has not been processed
{
// recursively process the Form XObject
visited.Add(form_obj.GetObjNum());
ElementWriter new_writer = new ElementWriter();

reader.FormBegin();
new_writer.Begin(form_obj, true);
ProcessElements(reader, new_writer, visited, markup);
new_writer.End();
reader.End();
}
break;
}
default:
writer.WriteElement(element);
break;
}
}
}