Problem with text justication in EPUB

1,564 views
Skip to first unread message

Ron Hale-Evans

unread,
Jun 20, 2010, 2:37:09 PM6/20/10
to fbre...@googlegroups.com
I'm finding that FBReader 0.12.10 incorrectly centers all text in some
EPUB files, even when I attempt to force it to left-justify it in
Options. The EPUB version of this book is just one example.

http://www.webscription.net/p-347-retief.aspx

I've found that FBReader also incorrectly centers all text in every
file I convert to EPUB with calibre.

Thank you.

Ron Hale-Evans

--
Ron Hale-Evans ... rw...@ludism.org ... http://ron.ludism.org ... (206) 201-1768
Mind Performance Hacks book: http://oreilly.com/catalog/9780596101534/
The proteiform graph itself is a polyhedron of scripture. (Finnegans
Wake 107:08)

AlanW

unread,
Jun 25, 2010, 3:18:15 PM6/25/10
to FBReader
On Jun 20, 1:37 pm, Ron Hale-Evans <r...@ludism.org> wrote:
> I've found that FBReader also incorrectly centers all text in every
> file I convert to EPUB with calibre.

I have seen this some times when converting from ePub to ePub using
Calibre (e.g. to add left justification). It does seem to be a bug in
FBReader, since the resulting ePub works in other readers, but I found
that a work around is to do a double convert: a) ePub to MOBI, then b)
MOBI to ePub.

Ron Hale-Evans

unread,
Jun 29, 2010, 7:25:10 AM6/29/10
to fbre...@googlegroups.com

This does seem to work! I tried it on the ePub version of the book in
my original post <http://www.webscription.net/p-347-retief.aspx>, and
after converting to MOBI and back, the body text in the ePub was no
longer centered in FBReader.

The solution is not completely satisfactory because the process lost
some other formatting, such as boldface in section titles, but overall
the book is much more readable now.

Thanks!

Ron H-E

dannym

unread,
Aug 12, 2010, 5:00:17 PM8/12/10
to FBReader
Hi,

I tried your book and it indeed does have the problem.

So I checked the source code of FBReader and found why.

The CSS parser will emit ZLTextStyleEntry instances which will be
added to the model via addControl. However, ZLTextStyleEntry has a
flag which says whether or not a specific attribute has indeed be set
(I'm not sure why). So if your CSS entry has no "text-align" (which it
usually doesn't), it _will not touch the text alignment at all_. This
becomes a problem if there was an element before us (which ended
already) which did affect text alignment but our element doesn't,
hence it isn't restoring the style to its original setting.

An easy fix would be to change the CSS parser to always set text-align
to left whether it has been specified or not (1 liner workaround).

I've just checked zlibrary/text/src/model/ZLTextParagraph.cpp and
zlibrary/text/src/area/ZLTextAreaStyle.cpp.
zlibrary/text/src/style/ZLTextDecoratedStyle.cpp seems to have some
base() reference for some kind of ZLTextStyleEntry-with-holes
stacking, but for some reason it does not always work (?).

(Dirty) workaround attached.

diff -upr orig/fbreader-0.12.10/fbreader/src/formats/xhtml/
XHTMLReader.cpp fbreader-0.12.10/fbreader/src/formats/xhtml/
XHTMLReader.cpp
--- orig/fbreader-0.12.10/fbreader/src/formats/xhtml/XHTMLReader.cpp
2010-04-01 15:14:24.000000000 +0200
+++ fbreader-0.12.10/fbreader/src/formats/xhtml/XHTMLReader.cpp
2010-08-12 22:11:19.000000000 +0200
@@ -486,6 +486,20 @@ void XHTMLReader::fillTagTable() {
XHTMLReader::XHTMLReader(BookReader &modelReader) :
myModelReader(modelReader) {
}

+static shared_ptr<ZLTextStyleEntry> createBodyStyleEntry() {
+ shared_ptr<ZLTextStyleEntry> entry = new ZLTextStyleEntry();
+ //setLength
+ // TODO don't hard-code these, just move that to some central place
where it does the right thing (settings etc).
+ //entry->setFontModifier(FONT_MODIFIER_DEFAULT);
+ entry->setFontModifier(FONT_MODIFIER_BOLD, false);
+ entry->setFontModifier(FONT_MODIFIER_ITALIC, false);
+ entry->setFontModifier(FONT_MODIFIER_SMALLCAPS, false);
+ entry->setFontSizeMag(1);
+ //void setFontFamily(const std::string &fontFamily);
+ entry->setAlignmentType(ALIGN_LEFT);
+ return entry;
+}
+
bool XHTMLReader::readFile(const std::string &filePath, const
std::string &referenceName) {
myModelReader.addHyperlinkLabel(referenceName);

@@ -502,6 +516,7 @@ bool XHTMLReader::readFile(const std::st

myCSSStack.clear();
myStyleEntryStack.clear();
+ myStyleEntryStack.push_back(createBodyStyleEntry());
myStylesToRemove = 0;

return readDocument(filePath);
@@ -538,14 +553,21 @@ void XHTMLReader::startElementHandler(co
}

const int sizeBefore = myStyleEntryStack.size();
- addStyleEntry(sTag, "");
- addStyleEntry("", sClass);
- addStyleEntry(sTag, sClass);
- const char *style = attributeValue(attributes, "style");
- if (style != 0) {
- shared_ptr<ZLTextStyleEntry> entry =
myStyleParser.parseString(style);
+
+ if(strcmp(tag, "body") == 0) {
+ shared_ptr<ZLTextStyleEntry> entry = createBodyStyleEntry();
myModelReader.addControl(*entry);
myStyleEntryStack.push_back(entry);
+ } else {
+ addStyleEntry(sTag, "");
+ addStyleEntry("", sClass);
+ addStyleEntry(sTag, sClass);
+ const char *style = attributeValue(attributes, "style");
+ if (style != 0) {
+ shared_ptr<ZLTextStyleEntry> entry =
myStyleParser.parseString(style);
+ myModelReader.addControl(*entry);
+ myStyleEntryStack.push_back(entry);
+ }
}
myCSSStack.push_back(myStyleEntryStack.size() - sizeBefore);
}
@@ -559,13 +581,15 @@ void XHTMLReader::endElementHandler(cons

XHTMLTagAction *action = ourTagActions[ZLUnicodeUtil::toLower(tag)];
if (action != 0) {
- action->doAtEnd(*this);
+ //action->doAtEnd(*this);
myNewParagraphInProgress = false;
}

for (; myStylesToRemove > 0; --myStylesToRemove) {
myStyleEntryStack.pop_back();
}
+
+ replayStyleEntryStack();

if (myDoPageBreakAfterStack.back()) {
myModelReader.insertEndOfSectionParagraph();
@@ -573,6 +597,13 @@ void XHTMLReader::endElementHandler(cons
myDoPageBreakAfterStack.pop_back();
}

+void XHTMLReader::replayStyleEntryStack() {
+ std::vector<shared_ptr<ZLTextStyleEntry> >::const_iterator iter_end
= myStyleEntryStack.end();
+ for(std::vector<shared_ptr<ZLTextStyleEntry> >::const_iterator iter
= myStyleEntryStack.begin(); iter != iter_end; ++iter) {
+ myModelReader.addControl(**iter);
+ }
+}
+
void XHTMLReader::beginParagraph() {
myCurrentParagraphIsEmpty = true;
myModelReader.beginParagraph();
@@ -612,10 +643,9 @@ void XHTMLReader::endParagraph() {
myModelReader.addControl(blockingEntry);
}
for (; myStylesToRemove > 0; --myStylesToRemove) {
- myModelReader.addControl(*myStyleEntryStack.back());
myStyleEntryStack.pop_back();
}
- myModelReader.endParagraph();
+ replayStyleEntryStack();
}

void XHTMLReader::characterDataHandler(const char *text, size_t len)
{
Nur in fbreader-0.12.10/fbreader/src/formats/xhtml/: XHTMLReader.d.
diff -upr orig/fbreader-0.12.10/fbreader/src/formats/xhtml/
XHTMLReader.h fbreader-0.12.10/fbreader/src/formats/xhtml/
XHTMLReader.h
--- orig/fbreader-0.12.10/fbreader/src/formats/xhtml/XHTMLReader.h
2010-04-01 15:14:24.000000000 +0200
+++ fbreader-0.12.10/fbreader/src/formats/xhtml/XHTMLReader.h
2010-08-12 22:10:41.000000000 +0200
@@ -72,6 +72,7 @@ private:
void beginParagraph();
void endParagraph();
void addStyleEntry(const std::string tag, const std::string aClass);
+ void replayStyleEntryStack();

private:
BookReader &myModelReader;
Nur in fbreader-0.12.10/fbreader/src/formats/xhtml/: XHTMLReader.o.

Andrew Ramage

unread,
Aug 18, 2010, 6:08:32 PM8/18/10
to fbre...@googlegroups.com
Is text-align passed to the function as a parameter ? If so then C++
allows default values. Could this be used in this case ? Have a
default value (left aligned/justified) and override it for chapter
headings, etc.

dannym

unread,
Sep 4, 2010, 10:44:24 AM9/4/10
to FBReader
Hi,

ZLTextStyleEntry is a data class which contains all kinds of
attributes, like boldness, font, font size, text alignment etc.

However, when creating you don't have to specify all these attributes,
just the ones you actually want to change. This means it also
remembers which have actually been set. This object will be then
serialized into the editor buffer.

Obviously, the actual display code then has to find out which text
size is the actual text size, which font size is the actual font size
etc. How it does that is that the display code (warning:
oversimplified explanation follows) remembers the ZLTextStyleEntry
that was in effect before inside the newly set ZLTextStyleEntry. In
this way it can actually traverse the entire chain back to find out if
need be (i.e. if the current ZLTextStyleEntry did not set text
alignment, check the previous one, and so on).
Also, when "removing" the attribute again (that is, adding an
ZLTextStyleEntry which says to remove that style), it can (and
somewhat does) traverse this chain back to find out what was in effect
before.
And there seems to be something fishy going on.
I tried to find the actual bug for some hours, but to no avail, sorry.
Maybe geometer can take a look once he's done moving.

In the mean time, the workaround I posted works but is the wrong thing
(replayStyleEntryStack shouldn't be necessary because the display code
already does this on its own - or it seems that way).

The most important thing in the workaround is to add a
ZLTextStyleEntry for the HTML "body" tag.

cheers,
Danny
Reply all
Reply to author
Forward
0 new messages