Rank: Administration
Groups: Administrators
Joined: 1/5/2016(UTC) Posts: 1,138
Thanks: 10 times Was thanked: 133 time(s) in 130 post(s)
|
Seems we are lagging behind in providing the functionality you need for a couple of months. We are preparing a significant release, which will include work with annotations through the object model, without the need to use dictionaries. However, the low-level API (available through Pdfium class) is already released and is available in the latest version. Actually, you already can use it to easily create an appearance stream of any annotation, including widget annotation. There is only one point. You will have to use reflection to transition from a low-level API to an existing PdfPageObjectCollection class. Probably this explanation was incomprehensible, but please, look at the code below. It illustrates how you can easily create an appearance stream of any of annotation. The main algorithm Code: static void Main(string[] args)
{
PdfCommon.Initialize();
//create test document and page
var doc = PdfDocument.CreateNew();
doc.Pages.InsertPageAt(0, 500, 500);
var page = doc.Pages[0];
//create annotation array and insert it into page dictionary
var annotsArray = PdfTypeArray.Create();
page.Dictionary["Annots"] = annotsArray;
//Get list of indirect objects for future use.
var list = PdfIndirectList.FromPdfDocument(doc);
//create widget annotation
var widget = PdfTypeDictionary.Create();
widget["Type"] = PdfTypeName.Create("Annot");
widget["Subtype"] = PdfTypeName.Create("Widget");
widget["Name"] = PdfTypeString.Create(Guid.NewGuid().ToString(), false, false);
//Set up some other keys
//...
//add widget annotation to the annot array
list.Add(widget);
annotsArray.AddIndirect(list, widget);
//Create empty appearance stream
var stream = CreateEmptyAppearance(AppearanceStreamModes.Normal, widget, list);
//Convert Normal appearance stream to a collection of page objects
var pageObjectCollection = AppearanceStreamToPageObjectsCollection(page, stream);
//create test bitmap and set it to image object.
PdfBitmap bitmap = new PdfBitmap(10, 10, true);
bitmap.FillRect(0, 0, 10, 10, Color.Green);
PdfImageObject img = PdfImageObject.Create(doc, bitmap, 10, 10);
//Insert image object into page objects collection.
//Note: all other page objects may be added to the collection as well.
pageObjectCollection.Add(img); //<----Thus, to create an appearance stream, you simply manipulate the usual page objects.
//Generate content of page objects collection to the appearance stream
GenerateAppearance(AppearanceStreamModes.Normal, pageObjectCollection, stream, doc, widget);
doc.Save(...);
}
and some helper functions Code: /// <summary>
/// Creates empty appearance stream
/// </summary>
public static PdfTypeStream CreateEmptyAppearance(AppearanceStreamModes mode, PdfTypeDictionary widget, PdfIndirectList list)
{
if (mode != AppearanceStreamModes.Normal && mode != AppearanceStreamModes.Down && mode != AppearanceStreamModes.Rollover)
throw new ArgumentException();
if (!widget.ContainsKey("AP"))
widget["AP"] = PdfTypeDictionary.Create();
var ap = widget["AP"].As<PdfTypeDictionary>();
var stream = PdfTypeStream.Create();
stream.InitEmpty();
int num = list.Add(stream);
switch (mode)
{
case AppearanceStreamModes.Normal: ap.SetIndirectAt("N", list, num); break;
case AppearanceStreamModes.Down: ap.SetIndirectAt("D", list, num); break;
case AppearanceStreamModes.Rollover: ap.SetIndirectAt("R", list, num); break;
}
return stream;
}
/// <summary>
/// Convert appearance stream to page objects collection which can be used for drawing any annotation.
/// </summary>
public static PdfPageObjectsCollection AppearanceStreamToPageObjectsCollection(PdfPage page, PdfTypeStream stream)
{
IntPtr resDict = IntPtr.Zero;
if (page.Dictionary.ContainsKey("Resources"))
resDict = page.Dictionary["Resources"].Handle;
return CreatePdfPageObjectsCollection(page.Document, resDict, stream.Handle);
}
/// <summary>
/// Generate content of the specified collection to the specified appearance stream.
/// </summary>
public static void GenerateAppearance(AppearanceStreamModes mode, PdfPageObjectsCollection collection, PdfTypeStream stream, PdfDocument doc, PdfTypeDictionary widget)
{
Pdfium.FPDF_GenerateContentToStream(doc.Handle, collection.Handle, stream.Handle, IntPtr.Zero);
var bbox = CalcBBox(collection);
stream.Dictionary["BBox"] = RectToArray(bbox);
stream.Dictionary["Type"] = PdfTypeName.Create("XObject");
stream.Dictionary["Subtype"] = PdfTypeName.Create("Form");
stream.Dictionary["FormType"] = PdfTypeNumber.Create(1);
stream.Dictionary["Matrix"] = MatrixToArray(new FS_MATRIX(1, 0, 0, 1, 0, 0));
//Actualize annotation rectangle
widget["Rectangle"] = RectToArray(new FS_RECTF(bbox.left, bbox.top, bbox.right, bbox.bottom));
}
/// <summary>
/// Currently PdfPageObjectsCollection's constructors marked as internal, so you should use reflection to create an instance of that class
/// This behaviour will be fixed in the final release.
/// </summary>
private static PdfPageObjectsCollection CreatePdfPageObjectsCollection(PdfDocument document, IntPtr resDict, IntPtr stream)
{
BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance;
CultureInfo culture = null; // use InvariantCulture or other if you prefer
object[] parameters = { document, resDict, stream };
return (PdfPageObjectsCollection)Activator.CreateInstance(typeof(PdfPageObjectsCollection), flags, null, parameters, culture);
}
/// <summary>
/// Returns an array of 4 numbers specifying the coordinates of rectangle given in the order left edge, bottom edge, right edge, top edge.
/// </summary>
public static PdfTypeArray RectToArray(FS_RECTF rect)
{
var arr = PdfTypeArray.Create();
arr.Add(PdfTypeNumber.Create(rect.left));
arr.Add(PdfTypeNumber.Create(rect.bottom));
arr.Add(PdfTypeNumber.Create(rect.right));
arr.Add(PdfTypeNumber.Create(rect.top));
return arr;
}
/// <summary>
/// Returns an array of 6 numbers specifying the matrix coefficients given in the order a, b, c, d, e, f.
/// </summary>
public static PdfTypeArray MatrixToArray(FS_MATRIX matrix)
{
var arr = PdfTypeArray.Create();
arr.Add(PdfTypeNumber.Create(matrix.a));
arr.Add(PdfTypeNumber.Create(matrix.b));
arr.Add(PdfTypeNumber.Create(matrix.c));
arr.Add(PdfTypeNumber.Create(matrix.d));
arr.Add(PdfTypeNumber.Create(matrix.e));
arr.Add(PdfTypeNumber.Create(matrix.f));
return arr;
}
/// <summary>
/// Calculate the resulting bounding box for a collection of PdfPageObjects
/// </summary>
/// <param name="collection">Collection of <see cref="PdfPageObject"/></param>
/// <returns>Overal bounding box for entrie collection of objects</returns>
public static FS_RECTF CalcBBox(IEnumerable collection)
{
float left = float.MaxValue;
float right = float.MinValue;
float bottom = float.MaxValue;
float top = float.MinValue;
foreach (var obj in collection)
{
var bbox = BoundingBox((obj as PdfPageObject));
left = Math.Min(left, bbox.left);
right = Math.Max(right, bbox.right);
top = Math.Max(top, bbox.top);
bottom = Math.Min(bottom, bbox.bottom);
}
return new FS_RECTF(left, top, right, bottom);
}
/// <summary>
/// Gets page object bounding box.
/// </summary>
public static FS_RECTF BoundingBox(PdfPageObject pageObject)
{
float l, r, t, b;
Pdfium.FPDFPageObj_GetBBox(pageObject.Handle, null, out l, out t, out r, out b);
return new FS_RECTF(l, t, r, b);
}
Edited by user Thursday, September 13, 2018 11:13:38 PM(UTC)
| Reason: Not specified
|
 1 user thanked Paul Rayman for this useful post.
|
|