Since I started writing in the blog, talking about the steps I did, I wanted to write some technical stuff here. I think anyone wanting to hack a little evince would find this useful. I want to note that everything here is based on some notes I had used in the time I have been coding, so expect some not very clear or obscure explanations.
I want to notice, that poppler has been one of the hardest parts of my work, so I don’t want to write about it until I understand it correctly.
I noticed two fields on evince where I should focus my job.
libdocument
This one is related to how documents work. Evince does work with EvDocuments, this one works as an abstraction of the real documents as PDF, dvi, etc. The features related to those EvDocuments must be defined as interfaces that will implement the back ends that will use them.
As annotations are a new feature from the PDF documents, the first step is create EvAnnotation, a GObject that will carry the data related to the annotations to Evince, so I defined the fields from the data as properties of the object.
Defining it as a GObject and its class attributes.
ev-annotation.h
/* EvAnnotation */
typedef struct _EvAnnotation EvAnnotation;
typedef struct _EvAnnotationClass EvAnnotationClass;
typedef struct _EvAnnotationPrivate EvAnnotationPrivate;
struct _EvAnnotation {
GObject base_instance;
EvAnnotationPrivate *priv;
};
EvAnnotation *ev_annotation_new (const gchar *contents,
const gchar *annot_name,
const gchar *modified,
guint flags,
const gchar *appear_state,
GdkColor *color);
ev-annotation.c
struct _EvAnnotationPrivate {
gchar *contents;
gchar *annot_name;
gchar *modified;
guint flags;
gchar *appear_state;
GdkColor *color;
};
G_DEFINE_TYPE (EvAnnotation, ev_annotation, G_TYPE_OBJECT)
EvAnnotation *
ev_annotation_new (const gchar *contents,
const gchar *annot_name,
const gchar *modified,
guint flags,
const gchar *appear_state,
GdkColor *color)
{
EvAnnotation *annot;
annot = g_object_new (EV_TYPE_ANNOTATION,
“contents”, contents,
“annot_name”, annot_name,
“modified”, modified,
“flags”, flags,
“appear_state”, appear_state,
“color”, color,
NULL);
return annot;
}
Nothing special to anyone familiar with GObject, defining the object, object attributes as properties, class init and finalize, etc. After doing that, I then created an interface to be used on the back ends that will use annotations.
ev-document-annotations.h
struct _EvDocumentAnnotationsIface
{
GTypeInterface base_iface;
/* Methods */
GList *(* get_annotations) (EvDocumentAnnotations *document_annots,
gint page);
};
GType ev_document_annotations_get_type (void);
GList *ev_document_annotations_get_annotations (EvDocumentAnnotations *document_annots,
gint page);
ev-document-annotations.c
GList *
ev_document_annotations_get_annotations (EvDocumentAnnotations *document_annotations,
gint page)
{
EvDocumentAnnotationsIface *iface = EV_DOCUMENT_ANNOTATIONS_GET_IFACE (document_annotations);
GList *retval;
retval = iface->get_annotations (document_annotations, page);
return retval;
}
So we have the GObject defined and the interface to be implemented on the back ends so we can retrieve the data from any type of document that supports them. At the moment only PDF back end, poppler, does support annotations, but the idea is to extend this support to other back ends in the future.
static void pdf_document_document_annotations_iface_init (EvDocumentAnnotationsIface *iface);
G_DEFINE_TYPE_WITH_CODE (PdfDocument, pdf_document, G_TYPE_OBJECT,
{
...
G_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_ANNOTATIONS,
pdf_document_document_annotations_iface_init);
...
});
static void
pdf_document_document_annotations_iface_init (EvDocumentAnnotationsIface *iface)
{
iface->get_annotations = pdf_document_annotations_get_annotations;
}
As we can see the interface is implemented and pdf_document_annotations_get_annotations will be the function that will catch the data from poppler, and convert it properly, so a PopplerAnnotation will become EvAnnotation.
Converting a PopplerAnnotation into a EvAnnotation
static GList *
pdf_document_annotations_get_annotations (EvDocumentAnnotations *document_annotations,
gint page)
{
GList *retval = NULL;
PdfDocument *pdf_document;
PopplerPage *poppler_page;
GList *mapping_list;
GList *list;
gdouble height;
pdf_document = PDF_DOCUMENT (document_annotations);
poppler_page = poppler_document_get_page (pdf_document->document, page);
mapping_list = poppler_page_get_annot_mapping (poppler_page);
poppler_page_get_size (poppler_page, NULL, &height);
for (list = mapping_list; list; list = list->next) {
PopplerAnnotMapping *annot_mapping;
PopplerAnnot *poppler_annot;
EvAnnotationMapping *ev_annotation_mapping;
GdkColor *color;
annot_mapping = (PopplerAnnotMapping *) list->data;
poppler_annot = annot_mapping->annot;
ev_annotation_mapping = g_new (EvAnnotationMapping, 1);
switch (poppler_annot->type) {
case POPPLER_ANNOT_TEXT:
EvAnnotationText *annot_text;
EvAnnotationMarkupReply reply_to;
EvAnnotationExternalData ex_data;
EvAnnotationTextIcon icon;
EvAnnotationTextState state;
color = g_new0 (GdkColor, 1);
color->red = (guint16) poppler_annot->color.red;
color->green = (guint16) poppler_annot->color.green;
color->blue = (guint16) poppler_annot->color.blue;
reply_to = (EvAnnotationMarkupReply) poppler_annot->reply_to;
ex_data = (EvAnnotationExternalData) poppler_annot->ex_data;
icon = (EvAnnotationTextIcon) poppler_annot->annot_text.icon;
state = (EvAnnotationTextState) poppler_annot->annot_text.state;
annot_text = ev_annotation_text_new (poppler_annot->contents,
poppler_annot->name,
poppler_annot->modified,
poppler_annot->flags,
poppler_annot->appear_state,
color,
poppler_annot->label,
poppler_annot->opacity,
poppler_annot->date,
poppler_annot->subject,
reply_to,
ex_data,
poppler_annot->annot_text.open,
icon,
state);
g_free (color);
ev_annotation_mapping->annotation = EV_ANNOTATION (annot_text);
break;
default:
EvAnnotation *annot;
color = g_new0 (GdkColor, 1);
color->red = (guint16) poppler_annot->color.red;
color->green = (guint16) poppler_annot->color.green;
color->blue = (guint16) poppler_annot->color.blue;
annot = ev_annotation_new (poppler_annot->contents,
poppler_annot->name,
poppler_annot->modified,
poppler_annot->flags,
poppler_annot->appear_state,
color);
g_free (color);
ev_annotation_mapping->annotation = annot;
break;
}
ev_annotation_mapping->x1 = annot_mapping->area.x1;
ev_annotation_mapping->x2 = annot_mapping->area.x2;
/* Invert this for X-style coordinates */
ev_annotation_mapping->y1 = height - annot_mapping->area.y2;
ev_annotation_mapping->y2 = height - annot_mapping->area.y1;
retval = g_list_prepend (retval, ev_annotation_mapping);
}
poppler_page_free_annot_mapping (mapping_list);
g_object_unref (poppler_page);
return retval;
}
Two things I want to talk about. First the use of mappings. Surely you have noticed that annotations are surrounded by another structure called EvAnnotationMapping. Annotations have the coordinates of their area represented by the Rect field in the PDF file. As there are other objects in the documents that have an area representing their space, mappings of the coordinates are used to handle them better. So the coordinate data are taken out from the annotation and mapping data is filled with it.
The second one are these four lines (plus the five comment line):
ev_annotation_mapping->x1 = annot_mapping->area.x1;
ev_annotation_mapping->x2 = annot_mapping->area.x2;
/* Invert this for X-style coordinates */
ev_annotation_mapping->y1 = height - annot_mapping->area.y2;
ev_annotation_mapping->y2 = height - annot_mapping->area.y1;
Evince coordinate system isn’t the same as the one used on the PDF documents, and it has to be converted, and that’s why we are playing with the mapping coordinates.
What’s next ? We now must start with the evinces second field, the shell or evinces core, but that will the next time :).