Calligra/Architecture/Styles/Internals

From KDE Community Wiki

Internals of Styles in Calligra

This page is about how styles are handled internally in Calligra. As you can read in the parent page, there are two ways that styles are handled in Calligra now:

  • The old way where everything is kept inside each shape and no styling information is saved in style classes. This applies also to named styles, which is the big problem. In other words, the named styles are not kept.
  • The new way where styling information is kept in style classes and can be saved back later. This is only applicable to named styles, of course, since the automatic styles are always generated while saving using the KoGenstyle class.

I also want to propose an even more improved system for styles that builds on and extends the new way. See below for details about this.

Relevant libraries

Most of the style handling are done in 3 libraries under libs/ :

  • /libs/odf
  • /libs/flake
  • /libs/kotext

/libs/odf contains classes that are relevant for handling ODF files. Fundamental classes for loading and saving are: KoXmlReader and KoXmlWriter, KoGenStyle and KoGenStyles, KoOdfReadStore and KoOdfWriteStore.

There are also a number of pure data store classes that store specific types of medium level data like KoBorder (border information for paragraphs, pages, cells, etc), KoStroke (information about pen strokes), KoElementReference (unique identifiers to elements in a document) and so on. All these classes know how to load from an odf style or style stack and how to save its content into a KoGenStyle.

There also seems to be a number of classes that duplicate classes under libs/kotext: KoCell, KoCellStyle, KoColumn, KoColumnStyle, etc, which seem to have to do with tables. I have not yet investigated where these are used and if they in fact do duplicate the classes in libs/kotext.

/libs/flake ...

/libs/kotext ...

The old way

In the old way there are no special classes that store the style information. Most of the style information is read into and kept inside the classes that use them. The most prominent example of this is the KoShape class in libs/flake which is the base class for all shape implementations.

KoShape has a method called loadOdfProperties() which load all or a subset of the shape's graphic properties from the style stack. This method examines the graphic-properties of the styles that are on the style stack including the one whose name is in the style-name property of the shape itself. It does this by calling a method loadStyle() which loads the style. If you want to load properties that are only relevant for a specific type of shape, then reimplement loadStyle() in that shape and call KoShape::loadStyle() at the beginning of it.

Loading of shape information is done using the loadOdf() method, which normally calls loadOdfProperties() somewhere near the top. A parameter to this method is the so called KoShapeLoadingContext which contains a number of relevant data that is used during the loading. One of them is the KoOdfStylesReader which originally comes from the KoOdfReadStore which represents the zip store of the ODF file.

The styles reader gives you the KoXmlElement which contains the XML tree for a certain style. It is then up to you to examine the properties of the style or push it on the style stack and examine that instead. This means that for every object that uses a style, the XML text of the style is reparsed. Also if the datatypes of the properties are complex, like vectors, matrixes, paths or other advanced data types, then they have to be reparsed from the string value of the property in question.

This is very inefficient but it is how most of the styling works right now.

The new way

The new way of handling styles is represented by the classes in libs/kotext/styles. These are classes that contain style information for a specific item, such as KoCharacterStyle, KoParagraphStyle, KoSectionStyle, KoTable*Style, etc. At this time (2.4) only styles that affect text are implemented in this way.

All of these classes use the StylePrivate class as their storage (Styles_p.h and .cpp). StylePrivate has a QMap<int, QVariant> as its central data structure, which maps a property ID to a QVariant. The ID corresponds to the property name and the QVariant stores a binary representation of the property value. All known property names for a specific property set of a style (like character-properties) are assigned an integer ID in the style class that represents the style in question. ID's for different types of properties overlap in values so for instance ID's for character properties are partly the same as those for paragraph properties.

There is also a KoStyleManager that stores and manages these styles during the runtime of the application. The style manager also affects style changes to the document contents when they are changed.

A drawback of the current implementation is that the style manager have hardcoded categories that cannot be extended by simply adding a number to an enum. For every new property set the manager should support, a number of new methods have to be implemented.

Proposed style system

As can be seen in the description above, the current way of doing things (both old and new) have a number of drawbacks:

  • No other than text and table styles are handled; all the other types are missing like graphic styles, page styles, etc. This means that no named styles are saved and no default styles are saved.
  • The system is very inefficient because it reparses the styles so much during loading, both on an XML level and on a data level.
  • The styling is spread out over 3 libraries and maybe also a little in the applications. It should be put in one place.

So here is my proposal in a short and concise way:

  • All styling should be moved to libs/odf/style (new folder). This means that we must separate pure style storage from e.g. KoShape where sometimes the KoShapeLoadingContext is a parameter to the loadOdf() methods and similarly for saveOdf() or fillStyle().
  • The new way of storing the styles is good: QMap<int, QVariant>. This should be kept. But the class StylePrivate in libs/kotext/style/Style_p.{h,cpp} should be renamed to StylePropertyStorage. The name StylePrivate is misleading because there are always other members in the private classes as well.
  • The StylePropertyStorage should hold all properties for a property set (e.g. paragraph-properties). Each property set would be a class for itself, e.g. ParagraphProperties (or ParagraphStyleProperties?). A style would be a list of property classes with getters each of them. If the X-properties element contains children in the XML then this should be parsed and stored by the appropriate property class as well.
  • The property classes should use QSharedDataPointer for their d pointers and QSharedData for their private classes so they can be implicitly shared and cheap to send around. The same could be done for the style classes.
  • The spec says that 'unknown properties should be loaded and saved back'. We should do this. It could be done by creating a special ID for unknown attributes and store them as QVariant<QPair<QString, QString>> where the first QString is the attribute name and the other is the value.
  • The KoStyleManager in libs/kotext/style should be moved to libs/odf/style and extended to handle other style types.
  • The KoStyleStack should be kept working as is so we don't have to rewrite styling in every shape and applicaiton at once. But it should be extended so that it can also handle the new type of styles. It should also be extended so that it can return the correct datatype immediately if the type is known. This should make it much faster. The same goes for KoGenStyle and KoGenStyles since comparison of integer ID's is faster than strings.
  • KoDocument should own the KoStyleManager and should be responsible to load all the styles before loading of the contents starts. The StyleManager should be part of the KoShapeLoadingContext along the KoStylesReader so that shapes can use that instead if it wants to. Then the shapes can be rewritten one by one to take advantage of the new faster method while the old method still works. These rewritings could be excellent Junior Jobs for people starting out in Calligra.

This should make for a generic and efficient style handling in Calligra.