Our DNA is written in Swift

DTCoreText: Custom List Prefixes

A while ago Austrian development company Antiloop approached me. They needed to have custom list prefixes in DTCoreText as the default bullets didn’t cut it for their purpose. They had sponsored new features for DTCoreText in the past, a shining example of a company that is willing to invest in the Open Source technologies they use.

There is no provision in CSS to achieve this directly, but a well known workaround exists using the li:before pseudo-selector together with a content attribute and list-style:none. This omits the list prefix, but substitutes the contents of content.

To get the unicode double chevrons right (U+00BB) you would specify this style:

ul {list-style-type:none;}
li:before {content:"\00bb ";}

Note the use of the non-standard -webkit-padding-start which specifies the left padding for lists in Safari. The content attribute apparently is containing unicode sequences with a single back slash.


Of course I could have implemented a smarter way, e.g. via adding a custom character style to DTCSSListStyle. Down the road this might still be the better solution. Though we decided against this as the primary solution because we wanted to have the same effect on HTML code with DTCoreText as you would get when viewing it in the browser.

First I needed to replace the previous hard coded list indent with one that comes from the default.css style sheet which is compiled into a binary array for easy portability with the static library. For this I used the same attribute attached to UL and OL as Safari: -webkit-padding-start. This value now lets you specify the left indent per list level.

The second change was related to the tab stops. “What tab stops?” you might ask, “There are no tab stops in HTML!”.

Well, there are tab stops in CTParagraphStyle which is used to describe paragraph-level style information in CoreText. We are using these tab stops when creating lists. For normal list styles two tab stops are needed. One to right-align the bullet, the second to left-align the first line of a list item.

For the above mentioned method you set the list-style to none and therefore one one tab stop is needed, for the left-aligning. So I changed it to only have two tab stops where needed.

The largest part of the puzzle was to decode the back-slash-encoded unicode characters. I added the stringByDecodingCSSContentAttribute method to NSString+CSS for this purpose. I’m not exactly 100% certain that this is the proper method, but it worked for the test cases that I could come up with. As usual I welcome fixes.

Several hours later – or rather several days later because I was busily hacking on the next major release of the iCatalog.framework – I had fixed the remaining problems with the approach while – hopefully – leaving the existing list functionality intact. This is how it looks like:

This is with “Debug Frames” turned on so that you can see how the red glyph run of the first line of each list item aligns perfectly with the green shaded glyph run from the following lines. There’s a small trick at work here: instead of a space I used the \0009 sequence for a tab character following the double chevron. This works because the first tab stop in this paragraph style is equal to the “head indent”.

Oh and this method is actually superior to HTML for a very simple reason: In HTML if you do this, then you lose the proper “hanging” of wrapped list item lines. Haha! Take that HTML!

Here’s yet another reason why you would want to prefer DTCoreText over UIWebView …

These changes are kindly sponsored by AntiLoop!

Categories: Updates

Leave a Comment

%d bloggers like this: