The Liang-Knut algorithm in a real project, or how I did the reader for iOS
Hello! This time I want to tell how I implemented an alternative to iBooks. In my previous post, I wrote about the algorithm for arranging soft hyphens in the text. It was just useful when creating your reader, you can evaluate its work visually in the application . But besides this, during the implementation of the project I had to come across many other interesting things, such as parsing and rendering HTML with CSS, implementation of custom design controls, etc. Our designer rashapasta loves to throw me problems with a kind of non-standard interface that needs to be implemented with pens, but first things first.
In terms of UI in the project, it was not an easy task to make a grid table with horizontal paging. As usual, in search of ready-made solutions, I climbed onto stackoverflow.com , but alas, everything I went through was to one degree or another unsuitable.
There were high hopes for AQGridView , but as it turned out, from horizontal filling and paging there are only empty stubs. It was decided to give her a second chance and apply a familiar trick to many with a 90-degree rotation of the table. At first, this option even seemed to work and more or less acceptable, but even here there were stones.
Bugs in AQGridView itself and in standard UIScrollViewbeat off my desire to use this component. In some situations, the grid constantly broke: some cells fell out and the order constantly flied. To dispel doubts about my curvature, I tried to reproduce the problem on the demo from the kit - the bug was confirmed.
As for UIScrollView and its derivatives - here I also first sinned on AQGridView , but when I started using UITableView , the problem repeated. The essence of the bug is that when the UIScrollView is turned through the transformation, the bounce effect fell off, which was very ugly and unnatural for iOS.
Empirically, it turned out that resizing and moving UIScrollView is to blamewhen turning the device, which was done with your hands through the layoutSubviews handler . Taking my shamanic tambourine, I found out that everything breaks the positioning of the rotated UIScrollView through the center property . There were probably some other conditions, but so many options have been tried that you can no longer remember.
All this long story ended up having to be perverted with the good old UITableView . I fixed the Bounce, and the rotation problem was resolved. He made the table cell the size of a page and it consists of several sub-cells, each of which is implemented as an instance of a separate class. It turned out like this:

Parsing popular e-book formats and rendering is another story. HTML is basically not difficult, libxml did a great job. The HTML file is processed recursively, divided into blocks of text, the corresponding attributes are set for each block. It remains to drive all this into a framesetter from CoreText and you 're done. But it was not there! It is necessary to make hyphenation and alignment in width. I had to go down a level and use not a framesetter, but a typesetter. Using it, you can conveniently cut text into lines, for example, with the function
In the process of breaking into lines, you need to determine the place of the gap. If a gap arises in the middle of a word, then you need to correctly put the hyphen. This is where the implementation of the above Liang-Knut algorithm comes to the rescue .
All that's left is to cut the resulting mountain of lines of text into pages and you can render. Empirically, it turned out that all this bunch of text processing operations before rendering takes a whole bunch of time. From the profiler, I realized that the hyphenation was to blame. I drove the calculation of the book into the background and in a separate thread - it began to work faster.
The only negative is that while the rendering is in progress, you cannot use the rewind slider. If necessary, go to a chapter that has not yet been processed, put it first in the processing queue in order to display it on the screen as quickly as possible.
As a result, it turned out pretty good, and on the iPad books are processed quite quickly (given that this is a render on the fly).
Here's what the rendered pages look like in different screen orientations:

To work over HTTP was as usual zayuzat AFNetworking , highly recommend. True, there was one “but”, when analyzing the application for memory leaks, there was a problem with displaying file download progress associated with circular links. The setDownloadProgressBlock method had a block like this:
The presence of self in the code of the block caused a cyclic dependence. This is solved by creating a separate local variable into which the pointer to the delegate is copied, and this variable is already used in the block. It became like this:
In the future, as I have free time, I will continue to describe my development experience for iOS, but for now I invite you to discuss the result of my work in the comments.
UI (or dancing with a tambourine)
In terms of UI in the project, it was not an easy task to make a grid table with horizontal paging. As usual, in search of ready-made solutions, I climbed onto stackoverflow.com , but alas, everything I went through was to one degree or another unsuitable.
There were high hopes for AQGridView , but as it turned out, from horizontal filling and paging there are only empty stubs. It was decided to give her a second chance and apply a familiar trick to many with a 90-degree rotation of the table. At first, this option even seemed to work and more or less acceptable, but even here there were stones.
Bugs in AQGridView itself and in standard UIScrollViewbeat off my desire to use this component. In some situations, the grid constantly broke: some cells fell out and the order constantly flied. To dispel doubts about my curvature, I tried to reproduce the problem on the demo from the kit - the bug was confirmed.
As for UIScrollView and its derivatives - here I also first sinned on AQGridView , but when I started using UITableView , the problem repeated. The essence of the bug is that when the UIScrollView is turned through the transformation, the bounce effect fell off, which was very ugly and unnatural for iOS.
Empirically, it turned out that resizing and moving UIScrollView is to blamewhen turning the device, which was done with your hands through the layoutSubviews handler . Taking my shamanic tambourine, I found out that everything breaks the positioning of the rotated UIScrollView through the center property . There were probably some other conditions, but so many options have been tried that you can no longer remember.
All this long story ended up having to be perverted with the good old UITableView . I fixed the Bounce, and the rotation problem was resolved. He made the table cell the size of a page and it consists of several sub-cells, each of which is implemented as an instance of a separate class. It turned out like this:

Work with HTML and the Liang-Knuth algorithm.
Parsing popular e-book formats and rendering is another story. HTML is basically not difficult, libxml did a great job. The HTML file is processed recursively, divided into blocks of text, the corresponding attributes are set for each block. It remains to drive all this into a framesetter from CoreText and you 're done. But it was not there! It is necessary to make hyphenation and alignment in width. I had to go down a level and use not a framesetter, but a typesetter. Using it, you can conveniently cut text into lines, for example, with the function
CFIndex CTTypesetterSuggestClusterBreak( CTTypesetterRef typesetter, CFIndex startIndex, double width);
In the process of breaking into lines, you need to determine the place of the gap. If a gap arises in the middle of a word, then you need to correctly put the hyphen. This is where the implementation of the above Liang-Knut algorithm comes to the rescue .
Render (or don't keep the user waiting!)
All that's left is to cut the resulting mountain of lines of text into pages and you can render. Empirically, it turned out that all this bunch of text processing operations before rendering takes a whole bunch of time. From the profiler, I realized that the hyphenation was to blame. I drove the calculation of the book into the background and in a separate thread - it began to work faster.
The only negative is that while the rendering is in progress, you cannot use the rewind slider. If necessary, go to a chapter that has not yet been processed, put it first in the processing queue in order to display it on the screen as quickly as possible.
As a result, it turned out pretty good, and on the iPad books are processed quite quickly (given that this is a render on the fly).
Here's what the rendered pages look like in different screen orientations:

To work over HTTP was as usual zayuzat AFNetworking , highly recommend. True, there was one “but”, when analyzing the application for memory leaks, there was a problem with displaying file download progress associated with circular links. The setDownloadProgressBlock method had a block like this:
if ([self.progressDelegate respondsToSelector:@selector(fileDownloadRequest:progressBytes:withTotalBytes:)])
{
[self.progressDelegate fileDownloadRequest:self progressBytes:alreadyDownloadedBytes+totalBytesRead withTotalBytes:alreadyDownloadedBytes+totalBytesExpectedToRead];
}
The presence of self in the code of the block caused a cyclic dependence. This is solved by creating a separate local variable into which the pointer to the delegate is copied, and this variable is already used in the block. It became like this:
id progress = self.progressDelegate;
[self.request setDownloadProgressBlock:^(NSInteger bytesRead, NSInteger totalBytesRead, NSInteger totalBytesExpectedToRead) {
if ([progress respondsToSelector:@selector(fileDownloadRequest:progressBytes:withTotalBytes:)])
{
[progress fileDownloadRequest:self progressBytes:alreadyDownloadedBytes+totalBytesRead withTotalBytes:alreadyDownloadedBytes+totalBytesExpectedToRead];
}
}];
In the future, as I have free time, I will continue to describe my development experience for iOS, but for now I invite you to discuss the result of my work in the comments.