Calligra/KoText/Styles

From KDE Community Wiki

KoText is the koffice library that provides text features required for an office suite but not present in Qt. This page will first show an overview of what features there are in KoText styles and then will continue with explaining how this is mapped onto the much less powerful QtTextDocument structure 'scribe'. At the end some not-so-obvious style features will be discussed.

KoText Styles

KoText supplies a text editing engine with advanced options for layout out text. This ranges from different fonts, bold italic and other more complex features for text layout. Examples of more advanced features are things like drop-caps, double overline, complicated list-numbering etc. The goal of KoText is to have complete coverage of the ODF-text feature set available via named properties, and add anything interesting for KOffice too. In order to do this KoText defines named properties that are stored in styles. You have a KoParagraphStyle::DropCaps enum value for example. There is a related KoParagraphStyle::setDropCaps(bool) method to store that property.

All known properties are segmented into different style classes. We have KoParagraphStyle properties, KoCharacterStyle properties, KoListStyle properties and I expect we'll get a KoTableStyle set of properties too.

Paragraph style

Conceptually a paragraph style describes the layout of a paragraph of text. So it separates content from the layout by storing the styling information in one place. You can indeed make any number of paragraphs obey the layout as defined in one paragraph style. Or, in other words, updating the paragraph style will change the layout of all paragraphs that have this style set.

A paragraph style is applied to a whole paragraph of text and can not be applied to a part of a paragraph only. Features;

  • This is a container for properties like text indent, spacing before / after.
  • A Paragraph style can have a parent paragraph style. If you alter any property in the paragraph style, and that property is not explicitly overwritten in the child (parag)style then all the paragraphs follow this change.
  • A Paragraph style always has a character style as a child which can be reused between different paragraph styles.
  • A paragraph style can optionally have a list style.

Character style

Conceptually a character style describes the look of a piece of text. Just like a paragraph style updating the style will change the look of all text that have this style set.

A Character style is applied to a set of characters, the smallest unit for applying this is the individual character (unicode code point). Features;

  • All properties that are to do with text size, markup and things like language etc.
  • Character styles are persistent just like paragraph styles. Change the style and all text that has this style set is changed too.

List Style

Conceptually a character style describes the look of a numbered list. Specifically the bullets, the indent etc. Just like a paragraph style updating the style will change the look of each of the numbered items that have this style set.

A List style specifies the paragraph to be part of a list. This can mean it will have a bullet or a numbering preceding it in the layout.

  • Stores the list style for different levels of listings.

QTextDocument

Formats

QTextDocument doesn't know anything about styles as described above. The concept of having one style describe more than one paragraph is just not present at all. We choose to model this behavior on Qts formats. So before I can continue I'll have to explain what formats are.

The text document as stored in QTextDocument is essentially a linear sequence of characters. Internally Qt holds a data structure that decorates that plain text with formatting information. Imagine for example one paragraph of text with some markup like bold and italic. Qt will have a QTextBlockFormat that it stores at the start of the paragraph. Additionally it will have some QTextCharFormat objects that it stores at the start and end at the relevant cursor positions.

Each of these formats inherits from the QTextFormat class which is essentially a hash table of enums (integers) mapping to a QVariant. For the above example you would find a format representing the italic text as a property with the QTextCharFormat::FontItalic to have a QVariant with 'true'.

Turns out this is a very powerful system as the formats are easy to extend and you can place them anywhere on the complete text document using the QTextCursor api. The idea then formed that we have style classes as described in the previous section; the KoCharacterStyle one, for example and whenever we want to make a piece of text follow that style we simply copy all the relevant properties to the format on the text document. If you manage to apply a QTextCharFormat::FontItalic = true on a format in the QTextDocument it will automatically become italic.

On top of that formats can hold any property that Qt doesn't know about. So what we did is add a special property to the formats to register that, he, this format is the result of style with name XYZ. This allows us to find out at saving time that the properties set are actually the result of a named style which means we don't loose any information.

Applying a style to a format

Imagine, for a moment, that you have a paragraph style with a text indent of 2 cm. The paragraph style should be applied to all odd paragraphs. To make this happen we call the KoParagraphStyle::applyStyle(QTextBlock &block); this method essentially just copies all the properties that are set on the style to the format and then stops.

Updating a style

Changing the style, we said in part 1, should be seen by the user as directly changing all the text that has that style applied since we want to give the impression there is a clear separation of style and content. Naturally we are copying the information from the style again and again to each of the paragraphs we applied the style to. So when a paragraph-style is changed we need to do something to make sure all those changes are applied to all the paragraphs that follow the style.

We store a unique integer to mark the style-id on the format of each of the paragraphs we apply the style to. We do that just like the QTextCharFormat::FontItalic property is set on the format, just using an enum called KoParagraphStyle::StyleId. So, when the style changes we can just iterate over all paragraphs and check the styleId to represent the style that changed. If it does, all we need to do is copy all the properties again from the style to the format. And voíla, the change is applied.

Changing the style set on a paragraph

A nice tricky-bit here is that if you switch a paragraph from style 1 to style 2. The intuitive thing to do is to just apply style2, which we learned before means copying all properties from that style to the paragraphs format. Unfortunately this turns out to not be fool proof since style1 might have some properties that are not present on style2 due to those properties being set to the default value on style2. The solution is to first unapply style 1 which means removing all properties of the first style before applying the properties of the second style.

Advanced styling features in KoText

Inheritance

Determining which effective properties are used in the case of styles can be a bit tricky. In Paragraph styles its reasonably simple as the paragraph style explicitly has a parent style. Consider this usecase;

Annie is working on a document that has a paragraph style "Headers". This style, as all paragraph styles, has a character style and it sets the font to be 12 points, bold+italic. The document has a second style called "Head 1". This style has as parent "Headers". Head 1 also has its own character style. The only property Head 1 character style has is the font size which is 20 point. A 3rd style on this document is a character style "FontColor" This style has the property that its fonts are "red" and it has the property that fonts are "light" which means its the opposite of bold.

Annie writes the first sentence "This is a header" and then applies the "Head 1" paragraph style on this whole sentence. The effect this has is that the paragraph gets the effective properties of Head 1 + Headers. Effectively setting it to 20 point, bold + italic.

Annie selects the word "header" in the paragraph and applies character style "FontColor". The effective settings in this case is the stacking of the FontColor style on top of the character style from the Head 1 on top of the character style of Headers. The effective style of the word 'header' is 20 point, light + italic and its red.