Recent

Author Topic: From UTF8 to font rendering  (Read 2489 times)

circular

  • Hero Member
  • *****
  • Posts: 4471
    • Personal webpage
From UTF8 to font rendering
« on: May 10, 2020, 08:54:38 pm »
I have been playing for some time with text rendering, at first just calling TextOut and retrieving the pixels, and then implementing bidirectional layout, etc. and recently I am adding Unicode support to BGRAFreeType. It may interest some of you what I have learnt in this journey so I would like to share it here.

There are many steps that take place to render some text. Here is a list from UTF8 to rendering. Note that to implement this you need to retrieve the Unicode database to know the properties of each codepoint. It can be found here: https://www.unicode.org/reports/tr44/

Determining character ordering (done by BGRAUnicode)
- first analyze UTF8 to get Unicode characters, basically LongWord values (from 0 to $10FFFF)
- apply bidirectional ordering to know the display order of the characters. Some scripts go from right to left, like Hebrew, but digits go from left to right, so the direction can change in the same text. Also it is possible to embed subtexts with different directions or to enforce a direction so that for example latin letters would be written from right to left. Algorithm is explained here: https://unicode.org/reports/tr9/
- determine ligatures. In some scripts like Arabic, when letters join, they change shape. So there are various presentation forms. The ligatures are listed in the database so you can determine whether a letter joins to the left, to the right or on both sides. Later, when rendering, you will need to replace the codepoint with the presentation code point to get the correct glyph.

Determining groups of characters (done by BGRATypeWriter)
- determine the groups of characters that are rendered together. Unicode characters are not necessary individual letters or symbols. One letter can be followed by any number of marks that are merged with it. There are basically two types. First, combining marks which have a width (like letters), making the resulting glyph wider. This is the case of Devanagari script for example. Secondly, the non spacing marks, that are painted on top of the glyph. This includes for example all sort of accents on latin letters.
- when determining groups of characters, one need to determine multicharacters ligatures. For example the letters "ffi" can be merged and in Arabic, lam "ل" followed on the left by aleph "ا" is to be replaced by a particular glyph "لا". Thus you obtain the main glyph of the group of characters.
- Determing the kerning of the glyphs. This information is provided by the font. For example A and V are closer because they fit together. This correspond to a negative kerning value.

Measuring text and applying layout (done by BGRATextBidi)
- Measure the length of the text by adding glyph widths and kerning. So a layout can be applied. For example, if you write text inside a rectangle, you can determine how many glyphs you can fit horizontally. In case of ligatures, you need to check when you stop the text at the given point whether the shape of the last glyph will be wider without ligature.
- Apply wordbreak. Look for the first space before the split. The way wordbreak is applied is different for ideographic punctation, as ideogramms basically don't have space (#32) in between them. Unicode proposes a refined algorithm (that I haven't implemented as such): http://www.unicode.org/reports/tr29/
- You get a text split into different portions that you can place inside a rectangle for example.

Dermining glyph positions (done by BGRAFreeType)
- to render the text, you need to use the order provided by the bidirectional algorithm. From the left, draw the first group of characters, then move to the right by the width of the group of characters, an draw the next group of characters.
- put together characters using the Unicode decomposition, when the composed glyph is available. For example an "e" followed by "`" is replaced by the character "è".
- to render a group of characters, first render combining marks. If the mark combines to the left, draw it and continue. If the mark combines to the right, draw it from the right. I did not find the information about the combining side, but luckily there are not too many of these, and you can just look in a text editor what happens with them. Note that some combining marks are both on the left and right, so you need to split them using the canonical decomposition.
- after the combining marks are drawn, the remaining space is for the main glyph, that you can render. If there are no marks, all the space is used by the main glyph. Some characters are to be mirrored horizontally if the context is right-to-left. This is the case for brackets.
- after that, the non spacing marks are sorted for rendering. The way they are placed relative to the main glyph is specified in the database as the "combining class". In the case of Arabic there are some things to consider. Some Arabic symbols do not have an adequate class defined, because they are considered to be part of the letter. Still it corresponds to the classes "above" or "below". Also before rendering you need to sort the marks, in particular for Arabic, using the AMTRA algorithm: http://www.unicode.org/reports/tr53/
- find replacement for the missing glyphs. For example, there is a codepoint for an acute accent as a non spacing mark, but it may be absent of the font, so you would need to use a replacement.
- then you can render the non spacing marks. When the class is "above", the bounding boxes of the glyph and of the mark are used to determine where to draw it vertically. The horizontal position in most cases is just centered. The same is done for the class "below". Multiple marks can be stacked this way. There are other classes, which are generally drawn using the coordinate of the cursor just after the letter. When it is a right-to-left text, then this is on the right, otherwise it is on the left. The glyph itself normally contains the adequate relative offset.

Drawing the pixels (done by LazFreeType)
- read the font files, determining their friendly name and styles
- determine the matching font file
- scale the font using the DPI and the font size. Note that with ClearType, the width is multiplied by 3.
- determine for each character the corresponding glyph
- retrieve the outline of the glyph to draw
- run the hint program embedded in the font for each glyph
- compute the Bézier curves
- render the density scanlines and send them to the pixel renderer

Rendering the pixels (done by BGRABlend)
- if it is not ClearType, draw using transparency depending on the density with the chosen color or texture.
- with ClearType and that the target pixel is not transparent, modify the RGB channels depending on the density
Conscience is the debugger of the mind

440bx

  • Hero Member
  • *****
  • Posts: 6556
Re: From UTF8 to font rendering
« Reply #1 on: May 10, 2020, 09:19:21 pm »
It may interest some of you what I have learnt in this journey so I would like to share it here.
Thank you for sharing all that information and, all the hard work that went into obtaining it.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

lainz

  • Hero Member
  • *****
  • Posts: 4743
  • Web, Desktop & Android developer
    • https://lainz.github.io/
Re: From UTF8 to font rendering
« Reply #2 on: May 10, 2020, 09:30:18 pm »
Wow  :)

circular

  • Hero Member
  • *****
  • Posts: 4471
    • Personal webpage
Re: From UTF8 to font rendering
« Reply #3 on: May 10, 2020, 09:41:50 pm »
Thank you for sharing all that information and, all the hard work that went into obtaining it.
I am happy to be able to share some quality summary of it all.  :)

The time I have spent on this is indeed significant.

Wow  :)
That's quite a big thing indeed.  8-)
Conscience is the debugger of the mind

winni

  • Hero Member
  • *****
  • Posts: 3197
Re: From UTF8 to font rendering
« Reply #4 on: May 10, 2020, 09:43:12 pm »
Thanx that you pulled all that info together!

Winni

ps

  • Full Member
  • ***
  • Posts: 136
    • CSS
Re: From UTF8 to font rendering
« Reply #5 on: May 11, 2020, 09:08:51 am »
@circular Nice journey report! It is possible to get find baseline for font? https://en.wikipedia.org/wiki/Baseline_(typography)
Small simple CSS/box model implementation: https://github.com/pst2d/csscontrols/tree/dev

circular

  • Hero Member
  • *****
  • Posts: 4471
    • Personal webpage
Re: From UTF8 to font rendering
« Reply #6 on: May 11, 2020, 09:39:47 am »
Yes the baseline is in fact the default vertical anchor of TrueType font. In other word, y=0 corresponds to the baseline.

In the information provided by the font, there is the Ascent and Descent which corresponds to the space above and below the baseline.

So if you want to draw text like with TextOut with the top-left corner of the text, you need to add the Ascent value to the y position.

Conscience is the debugger of the mind

 

TinyPortal © 2005-2018