Major refactoring of CSS support

Also, improved support for line breaks.
This commit is contained in:
Slava Monich 2015-07-07 18:30:51 +03:00
parent a7e40b8d34
commit 248b307696
21 changed files with 649 additions and 368 deletions

View file

@ -109,6 +109,12 @@ void BookReader::addFixedHSpace(unsigned char length) {
}
}
void BookReader::addLineBreak() {
if (myTextParagraphExists) {
myCurrentTextModel->addLineBreak();
}
}
void BookReader::addControl(const ZLTextStyleEntry &entry) {
if (myTextParagraphExists) {
flushTextBufferToParagraph();

View file

@ -58,6 +58,7 @@ public:
void addHyperlinkLabel(const std::string &label);
void addHyperlinkLabel(const std::string &label, int paragraphNumber);
void addFixedHSpace(unsigned char length);
void addLineBreak();
void addImageReference(const std::string &id, short vOffset = 0);
void addImage(const std::string &id, shared_ptr<const ZLImage> image);

View file

@ -28,26 +28,22 @@
StyleSheetTableParser::StyleSheetTableParser(StyleSheetTable &table) : myTable(table) {
}
void StyleSheetTableParser::storeData(const std::string &tagName, const std::string &className, const StyleSheetTable::AttributeMap &map) {
myTable.addMap(tagName, className, map);
void StyleSheetTableParser::storeData(const std::string &selector, const StyleSheetTable::AttributeMap &map) {
myTable.addMap(ZLStringUtil::splitString(selector, " \t+"), map);
}
shared_ptr<ZLTextStyleEntry> StyleSheetSingleStyleParser::parseString(const char *text, ZLBoolean3 *pageBreakBefore, ZLBoolean3 *pageBreakAfter) {
myReadState = ATTRIBUTE_NAME;
parse(text, strlen(text), true);
shared_ptr<ZLTextStyleEntry> control = StyleSheetTable::createControl(NULL, myMap);
bool value;
if (pageBreakBefore && StyleSheetTable::getPageBreakBefore(myMap, value)) {
*pageBreakBefore = value ? B3_TRUE : B3_FALSE;
StyleSheetTable::Style StyleSheetSingleStyleParser::parseString(const char *text) {
StyleSheetTable::Style style;
if (text) {
myReadState = ATTRIBUTE_NAME;
parse(text, strlen(text), true);
StyleSheetTable::updateStyle(style, myMap);
reset();
}
if (pageBreakAfter && StyleSheetTable::getPageBreakAfter(myMap, value)) {
*pageBreakAfter = value ? B3_TRUE : B3_FALSE;
}
reset();
return control;
return style;
}
StyleSheetParser::StyleSheetParser() : myReadState(TAG_NAME), myInsideComment(false) {
StyleSheetParser::StyleSheetParser() : myReadState(TAG_NAME), myInsideComment(false), myAtBlockDepth(0) {
}
StyleSheetParser::~StyleSheetParser() {
@ -58,6 +54,7 @@ void StyleSheetParser::reset() {
myAttributeName.erase();
myReadState = TAG_NAME;
myInsideComment = false;
myAtBlockDepth = 0;
mySelectors.clear();
myMap.clear();
}
@ -81,7 +78,8 @@ void StyleSheetParser::parse(const char *text, int len, bool final) {
const char *start = text;
const char *end = text + len;
for (const char *ptr = start; ptr != end; ++ptr) {
if (isspace(*ptr)) {
if ((myReadState != TAG_NAME && isspace(*ptr)) ||
(myReadState == TAG_NAME && *ptr == ',')) {
if (start != ptr) {
myWord.append(start, ptr - start);
}
@ -121,40 +119,78 @@ bool StyleSheetParser::isControlSymbol(const char symbol) {
}
}
void StyleSheetParser::storeData(const std::string&, const std::string&, const StyleSheetTable::AttributeMap&) {
void StyleSheetParser::storeData(const std::string&, const StyleSheetTable::AttributeMap&) {
}
void StyleSheetParser::processControl(const char control) {
switch (control) {
case '{':
myReadState = (myReadState == TAG_NAME) ? ATTRIBUTE_NAME : BROKEN;
switch (myReadState) {
case AT_RULE:
myReadState = AT_BLOCK;
myAtBlockDepth = 1;
break;
case AT_BLOCK:
myAtBlockDepth++;
break;
case TAG_NAME:
myReadState = ATTRIBUTE_NAME;
myMap.clear();
break;
default:
myReadState = BROKEN;
break;
}
break;
case '}':
if (myReadState != BROKEN) {
for (unsigned int i=0; i<mySelectors.size(); i++) {
std::string selector(mySelectors[i]);
std::string tag, klass;
const int index = selector.find('.');
if (index == -1) {
tag = selector;
} else {
tag = selector.substr(0, index);
klass = selector.substr(index + 1);
switch (myReadState) {
case AT_BLOCK:
if (--myAtBlockDepth > 0) {
return;
}
storeData(tag, klass, myMap);
}
break;
case AT_RULE:
case BROKEN:
break;
default:
for (unsigned int i=0; i<mySelectors.size(); i++) {
storeData(mySelectors[i], myMap);
}
break;
}
myReadState = TAG_NAME;
mySelectors.clear();
myMap.clear();
break;
case ';':
myReadState =
((myReadState == ATTRIBUTE_VALUE) ||
(myReadState == ATTRIBUTE_NAME)) ? ATTRIBUTE_NAME : BROKEN;
switch (myReadState) {
case AT_RULE:
myReadState = TAG_NAME;
mySelectors.clear();
myMap.clear();
break;
case AT_BLOCK:
break;
case ATTRIBUTE_VALUE:
case ATTRIBUTE_NAME:
myReadState = ATTRIBUTE_NAME;
break;
default:
myReadState = BROKEN;
break;
}
break;
case ':':
myReadState = (myReadState == ATTRIBUTE_NAME) ? ATTRIBUTE_VALUE : BROKEN;
switch (myReadState) {
case AT_BLOCK:
break;
case ATTRIBUTE_NAME:
myReadState = ATTRIBUTE_VALUE;
break;
default:
myReadState = BROKEN;
break;
}
break;
}
}
@ -177,18 +213,21 @@ void StyleSheetParser::processWord(std::string &word) {
}
}
void StyleSheetParser::processWordWithoutComments(const std::string &word) {
void StyleSheetParser::processWordWithoutComments(std::string word) {
switch (myReadState) {
case AT_RULE:
case AT_BLOCK:
break;
case TAG_NAME:
ZLStringUtil::stripWhiteSpaces(word);
if (!word.empty()) {
const unsigned int len = word.length();
if (word.at(len-1) == ',') {
mySelectors.push_back(word.substr(0, len-1));
if (word[0] == '@') {
myReadState = AT_RULE;
} else {
mySelectors.push_back(word);
}
}
myMap.clear();
break;
case ATTRIBUTE_NAME:
myAttributeName = word;

View file

@ -21,7 +21,6 @@
#define __STYLESHEETPARSER_H__
#include "StyleSheetTable.h"
#include "ZLBoolean3.h"
class ZLInputStream;
@ -37,24 +36,27 @@ public:
void parse(const char *text, int len, bool final = false);
protected:
virtual void storeData(const std::string &tagName, const std::string &className, const StyleSheetTable::AttributeMap &map);
virtual void storeData(const std::string &selector, const StyleSheetTable::AttributeMap &map);
private:
bool isControlSymbol(const char symbol);
void processWord(std::string &word);
void processWordWithoutComments(const std::string &word);
void processWordWithoutComments(std::string word);
void processControl(const char control);
private:
std::string myWord;
std::string myAttributeName;
enum {
AT_RULE,
AT_BLOCK,
TAG_NAME,
ATTRIBUTE_NAME,
ATTRIBUTE_VALUE,
BROKEN,
} myReadState;
bool myInsideComment;
int myAtBlockDepth;
std::vector<std::string> mySelectors;
StyleSheetTable::AttributeMap myMap;
@ -67,7 +69,7 @@ public:
StyleSheetTableParser(StyleSheetTable &table);
private:
void storeData(const std::string &tagName, const std::string &className, const StyleSheetTable::AttributeMap &map);
void storeData(const std::string &selector, const StyleSheetTable::AttributeMap &map);
private:
StyleSheetTable &myTable;
@ -76,7 +78,7 @@ private:
class StyleSheetSingleStyleParser : public StyleSheetParser {
public:
shared_ptr<ZLTextStyleEntry> parseString(const char *text, ZLBoolean3* pageBreakBefore, ZLBoolean3* pageBreakAfter);
StyleSheetTable::Style parseString(const char *text);
};
#endif /* __STYLESHEETPARSER_H__ */

View file

@ -18,32 +18,99 @@
*/
#include <cstdlib>
#include <algorithm>
#include <ZLStringUtil.h>
#include "StyleSheetTable.h"
bool StyleSheetTable::isEmpty() const {
return myControlMap.empty() && myPageBreakBeforeMap.empty() && myPageBreakAfterMap.empty();
static const std::string WILDCARD("*");
StyleSheetTable::Element::Element(const std::string &tag, const char *klass, const char* id) :
Name(tag), Classes(ZLStringUtil::splitString(klass, ", \n")) {
if (id) Id = id;
}
void StyleSheetTable::addMap(const std::string &tag, const std::string &aClass, const AttributeMap &map) {
if ((!tag.empty() || !aClass.empty()) && !map.empty()) {
Key key(tag, aClass);
// This will update the existing element if it already exists
// or create a new one if it wasn't there yet
myControlMap[key] = createControl(myControlMap[key], map);
bool value;
if (getPageBreakBefore(map, value)) {
myPageBreakBeforeMap[key] = value;
}
if (getPageBreakAfter(map, value)) {
myPageBreakAfterMap[key] = value;
}
StyleSheetTable::Selector::Selector(const std::string &selector) {
std::string buf(selector);
const int dot = buf.find('.');
if (dot >= 0) {
myClass = buf.substr(dot + 1);
buf = buf.substr(0, dot);
}
const int hash = buf.find('#');
if (hash < 0) {
myType = buf;
} else {
myType = buf.substr(0, hash);
myId = buf.substr(hash + 1);
}
if (myType == "*") myType.clear();
}
bool StyleSheetTable::Selector::match(const std::string &pattern, const std::string &str) {
return pattern.empty() || pattern == WILDCARD || pattern == str;
}
bool StyleSheetTable::Selector::match(const std::string &pattern, const std::vector<std::string> &strings) {
return pattern.empty() || pattern == WILDCARD || std::find(strings.begin(), strings.end(), pattern) != strings.end();
}
void StyleSheetTable::Style::apply(const Style &other) {
TextStyle.apply(other.TextStyle);
if (other.PageBreakBefore != B3_UNDEFINED) {
PageBreakBefore = other.PageBreakBefore;
}
if (other.PageBreakAfter != B3_UNDEFINED) {
PageBreakAfter = other.PageBreakAfter;
}
}
static bool parseLength(const std::string &toParse, short &size, ZLTextStyleEntry::SizeUnit &unit) {
bool StyleSheetTable::Entry::match(const ElementList &stack) const {
if (!stack.empty() && !Selectors.empty()) {
SelectorList::const_reverse_iterator it(Selectors.rbegin());
ElementList::const_reverse_iterator match_it(stack.rbegin());
if ((*it).match(*match_it)) {
++it;
++match_it;
while (it != Selectors.rend()) {
const Selector &selector = (*it);
while (match_it != stack.rend() && !selector.match(*match_it)) {
++match_it;
}
if (match_it == stack.rend()) {
break;
}
++it;
}
if (it == Selectors.rend()) {
return true;
}
}
}
return false;
}
void StyleSheetTable::addMap(const std::vector<std::string> &selectors, const AttributeMap &map) {
if ((!selectors.empty()) && !map.empty()) {
// http://www.w3.org/TR/selectors/#specificity
int a = 0, b = 0, c = 0;
SelectorList stack;
for (unsigned int i=0; i<selectors.size(); i++) {
const Selector &selector = selectors[i];
a += selector.a();
b += selector.b();
c += selector.c();
stack.push_back(selector);
}
if (a > 255) a = 255;
if (b > 255) b = 255;
if (c > 255) c = 255;
myEntries.push_back(Entry(stack, (a << 16) + (b << 8) + c, map));
}
}
bool StyleSheetTable::parseLength(const std::string &toParse, short &size, ZLTextStyleEntry::SizeUnit &unit) {
if (!toParse.empty()) {
if (ZLStringUtil::stringEndsWith(toParse, "%")) {
unit = ZLTextStyleEntry::SIZE_UNIT_PERCENT;
@ -72,14 +139,28 @@ static bool parseLength(const std::string &toParse, short &size, ZLTextStyleEntr
return false;
}
void StyleSheetTable::setLength(ZLTextStyleEntry &entry, ZLTextStyleEntry::Length name, const AttributeMap &map, const std::string &attributeName) {
StyleSheetTable::AttributeMap::const_iterator it = map.find(attributeName);
if (it == map.end()) {
return;
bool StyleSheetTable::parseMargin(const std::string &toParse, short &size, ZLTextStyleEntry::SizeUnit &unit) {
if (parseLength(toParse, size, unit)) {
// Negative margins do make sense but we don't really support them
if (size < 0) size = 0;
return true;
} else if (toParse == "auto") {
size = 0;
unit = ZLTextStyleEntry::SIZE_UNIT_PIXEL;
return true;
} else {
return false;
}
}
void StyleSheetTable::setLength(ZLTextStyleEntry &entry, ZLTextStyleEntry::Length name, const AttributeMap &map, const std::string &attributeName) {
AttributeMap::const_iterator it = map.find(attributeName);
if (it != map.end()) {
const std::vector<std::string> &values = it->second;
if (!values.empty()) {
setLength(entry, name, values[0]);
}
}
const std::vector<std::string> &values = it->second;
if (!values.empty())
setLength(entry, name, values[0]);
}
void StyleSheetTable::setLength(ZLTextStyleEntry &entry, ZLTextStyleEntry::Length name, const std::string &value) {
@ -90,48 +171,39 @@ void StyleSheetTable::setLength(ZLTextStyleEntry &entry, ZLTextStyleEntry::Lengt
}
}
bool StyleSheetTable::doBreakBefore(const std::string &tag, const std::string &aClass) const {
std::map<Key,bool>::const_iterator it = myPageBreakBeforeMap.find(Key(tag, aClass));
if (it != myPageBreakBeforeMap.end()) {
return it->second;
void StyleSheetTable::setMargin(ZLTextStyleEntry &entry, ZLTextStyleEntry::Length name, const AttributeMap &map, const std::string &attributeName) {
AttributeMap::const_iterator it = map.find(attributeName);
if (it != map.end()) {
const std::vector<std::string> &values = it->second;
if (!values.empty()) {
setMargin(entry, name, values[0]);
}
}
it = myPageBreakBeforeMap.find(Key("", aClass));
if (it != myPageBreakBeforeMap.end()) {
return it->second;
}
it = myPageBreakBeforeMap.find(Key(tag, ""));
if (it != myPageBreakBeforeMap.end()) {
return it->second;
}
return false;
}
bool StyleSheetTable::doBreakAfter(const std::string &tag, const std::string &aClass) const {
std::map<Key,bool>::const_iterator it = myPageBreakAfterMap.find(Key(tag, aClass));
if (it != myPageBreakAfterMap.end()) {
return it->second;
void StyleSheetTable::setMargin(ZLTextStyleEntry &entry, ZLTextStyleEntry::Length name, const std::string &value) {
short size;
ZLTextStyleEntry::SizeUnit unit;
if (parseMargin(value, size, unit)) {
entry.setLength(name, size, unit);
}
it = myPageBreakAfterMap.find(Key("", aClass));
if (it != myPageBreakAfterMap.end()) {
return it->second;
}
it = myPageBreakAfterMap.find(Key(tag, ""));
if (it != myPageBreakAfterMap.end()) {
return it->second;
}
return false;
}
shared_ptr<ZLTextStyleEntry> StyleSheetTable::control(const std::string &tag, const std::string &aClass) const {
std::map<Key,shared_ptr<ZLTextStyleEntry> >::const_iterator it =
myControlMap.find(Key(tag, aClass));
return (it != myControlMap.end()) ? it->second : 0;
bool StyleSheetTable::sortBySpecificity(const Entry *e1, const Entry *e2) {
return e1->Specificity < e2->Specificity;
}
void StyleSheetTable::applyStyles(const ElementList &stack, Style &style) const {
std::vector<const Entry*> entries;
for (std::vector<Entry>::const_iterator i = myEntries.begin(); i != myEntries.end(); ++i) {
if ((*i).match(stack)) {
entries.push_back(&(*i));
}
}
std::sort(entries.begin(), entries.end(), sortBySpecificity);
for (std::vector<const Entry*>::const_iterator e = entries.begin(); e != entries.end(); ++e) {
style.apply((*e)->Style);
}
}
const std::vector<std::string> &StyleSheetTable::values(const AttributeMap &map, const std::string &name) {
@ -143,21 +215,17 @@ const std::vector<std::string> &StyleSheetTable::values(const AttributeMap &map,
return emptyVector;
}
shared_ptr<ZLTextStyleEntry> StyleSheetTable::createControl(shared_ptr<ZLTextStyleEntry> entry, const AttributeMap &styles) {
if (entry.isNull()) {
entry = new ZLTextStyleEntry();
}
void StyleSheetTable::updateTextStyle(ZLTextStyleEntry &entry, const AttributeMap &styles) {
const std::vector<std::string> &alignment = values(styles, "text-align");
if (!alignment.empty()) {
if (alignment[0] == "justify") {
entry->setAlignmentType(ALIGN_JUSTIFY);
entry.setAlignmentType(ALIGN_JUSTIFY);
} else if (alignment[0] == "left") {
entry->setAlignmentType(ALIGN_LEFT);
entry.setAlignmentType(ALIGN_LEFT);
} else if (alignment[0] == "right") {
entry->setAlignmentType(ALIGN_RIGHT);
entry.setAlignmentType(ALIGN_RIGHT);
} else if (alignment[0] == "center") {
entry->setAlignmentType(ALIGN_CENTER);
entry.setAlignmentType(ALIGN_CENTER);
}
}
@ -178,23 +246,23 @@ shared_ptr<ZLTextStyleEntry> StyleSheetTable::createControl(shared_ptr<ZLTextSty
} else if (bold[0] == "lighter") {
}
if (num != -1) {
entry->setFontModifier(FONT_MODIFIER_BOLD, num >= 600);
entry.setFontModifier(FONT_MODIFIER_BOLD, num >= 600);
}
}
const std::vector<std::string> &italic = values(styles, "font-style");
if (!italic.empty()) {
entry->setFontModifier(FONT_MODIFIER_ITALIC, italic[0] == "italic");
entry.setFontModifier(FONT_MODIFIER_ITALIC, italic[0] == "italic");
}
const std::vector<std::string> &variant = values(styles, "font-variant");
if (!variant.empty()) {
entry->setFontModifier(FONT_MODIFIER_SMALLCAPS, variant[0] == "small-caps");
entry.setFontModifier(FONT_MODIFIER_SMALLCAPS, variant[0] == "small-caps");
}
const std::vector<std::string> &fontFamily = values(styles, "font-family");
if (!fontFamily.empty() && !fontFamily[0].empty()) {
entry->setFontFamily(fontFamily[0]);
entry.setFontFamily(fontFamily[0]);
}
short size;
@ -203,19 +271,19 @@ shared_ptr<ZLTextStyleEntry> StyleSheetTable::createControl(shared_ptr<ZLTextSty
if (!fontSize.empty()) {
std::string value = fontSize[0];
if (value == "xx-small") {
entry->setFontSizeMag(-3);
entry.setFontSizeMag(-3);
} else if (value == "x-small") {
entry->setFontSizeMag(-2);
entry.setFontSizeMag(-2);
} else if (value == "small" || value == "smaller") {
entry->setFontSizeMag(-1);
entry.setFontSizeMag(-1);
} else if (value == "medium") {
entry->setFontSizeMag(0);
entry.setFontSizeMag(0);
} else if (value == "large" || value == "larger") {
entry->setFontSizeMag(1);
entry.setFontSizeMag(1);
} else if (value == "x-large") {
entry->setFontSizeMag(2);
entry.setFontSizeMag(2);
} else if (value == "xx-large") {
entry->setFontSizeMag(3);
entry.setFontSizeMag(3);
} else {
if (parseLength(value, size, unit)) {
switch (unit) {
@ -225,16 +293,21 @@ shared_ptr<ZLTextStyleEntry> StyleSheetTable::createControl(shared_ptr<ZLTextSty
case ZLTextStyleEntry::SIZE_UNIT_EM_100:
case ZLTextStyleEntry::SIZE_UNIT_EX_100:
case ZLTextStyleEntry::SIZE_UNIT_PERCENT:
entry->setFontSizeMag(
(size < 100 && size > 80) ? -1 :
(size > 100 && size < 120) ? 1 :
(size - 100)/20);
entry.setFontSizeMag((size < 100 && size > 80) ? -1 :
(size > 100 && size < 120) ? 1 :
(size - 100)/20);
break;
}
}
}
}
const std::vector<std::string> &opacity = values(styles, "opacity");
if (!opacity.empty()) {
const int value = (int)(255 * ZLStringUtil::stringToDouble(opacity[0], 1));
entry.setOpacity((unsigned char)((value < 0) ? 0 : (value > 255) ? 255 : value));
}
std::vector<std::string> margins = values(styles, "margin");
if (!margins.empty() && margins.back() == "!important") {
// Ignore the "!important" modifier for now
@ -242,78 +315,82 @@ shared_ptr<ZLTextStyleEntry> StyleSheetTable::createControl(shared_ptr<ZLTextSty
}
switch (margins.size()) {
case 1:
if (parseLength(margins[0], size, unit)) {
entry->setLength(ZLTextStyleEntry::LENGTH_SPACE_BEFORE, size, unit);
entry->setLength(ZLTextStyleEntry::LENGTH_RIGHT_INDENT, size, unit);
entry->setLength(ZLTextStyleEntry::LENGTH_SPACE_AFTER, size, unit);
entry->setLength(ZLTextStyleEntry::LENGTH_LEFT_INDENT, size, unit);
if (parseMargin(margins[0], size, unit)) {
entry.setLength(ZLTextStyleEntry::LENGTH_SPACE_BEFORE, size, unit);
entry.setLength(ZLTextStyleEntry::LENGTH_RIGHT_INDENT, size, unit);
entry.setLength(ZLTextStyleEntry::LENGTH_SPACE_AFTER, size, unit);
entry.setLength(ZLTextStyleEntry::LENGTH_LEFT_INDENT, size, unit);
}
break;
case 2:
if (parseLength(margins[0], size, unit)) {
entry->setLength(ZLTextStyleEntry::LENGTH_SPACE_BEFORE, size, unit);
entry->setLength(ZLTextStyleEntry::LENGTH_SPACE_AFTER, size, unit);
if (parseMargin(margins[0], size, unit)) {
entry.setLength(ZLTextStyleEntry::LENGTH_SPACE_BEFORE, size, unit);
entry.setLength(ZLTextStyleEntry::LENGTH_SPACE_AFTER, size, unit);
}
if (parseLength(margins[1], size, unit)) {
entry->setLength(ZLTextStyleEntry::LENGTH_RIGHT_INDENT, size, unit);
entry->setLength(ZLTextStyleEntry::LENGTH_LEFT_INDENT, size, unit);
if (parseMargin(margins[1], size, unit)) {
entry.setLength(ZLTextStyleEntry::LENGTH_RIGHT_INDENT, size, unit);
entry.setLength(ZLTextStyleEntry::LENGTH_LEFT_INDENT, size, unit);
}
break;
case 3:
setLength(*entry, ZLTextStyleEntry::LENGTH_SPACE_BEFORE, margins[0]);
if (parseLength(margins[1], size, unit)) {
entry->setLength(ZLTextStyleEntry::LENGTH_RIGHT_INDENT, size, unit);
entry->setLength(ZLTextStyleEntry::LENGTH_LEFT_INDENT, size, unit);
setMargin(entry, ZLTextStyleEntry::LENGTH_SPACE_BEFORE, margins[0]);
if (parseMargin(margins[1], size, unit)) {
entry.setLength(ZLTextStyleEntry::LENGTH_RIGHT_INDENT, size, unit);
entry.setLength(ZLTextStyleEntry::LENGTH_LEFT_INDENT, size, unit);
}
setLength(*entry, ZLTextStyleEntry::LENGTH_SPACE_AFTER, margins[2]);
setMargin(entry, ZLTextStyleEntry::LENGTH_SPACE_AFTER, margins[2]);
break;
case 4:
setLength(*entry, ZLTextStyleEntry::LENGTH_SPACE_BEFORE, margins[0]);
setLength(*entry, ZLTextStyleEntry::LENGTH_RIGHT_INDENT, margins[1]);
setLength(*entry, ZLTextStyleEntry::LENGTH_SPACE_AFTER, margins[2]);
setLength(*entry, ZLTextStyleEntry::LENGTH_LEFT_INDENT, margins[3]);
setMargin(entry, ZLTextStyleEntry::LENGTH_SPACE_BEFORE, margins[0]);
setMargin(entry, ZLTextStyleEntry::LENGTH_RIGHT_INDENT, margins[1]);
setMargin(entry, ZLTextStyleEntry::LENGTH_SPACE_AFTER, margins[2]);
setMargin(entry, ZLTextStyleEntry::LENGTH_LEFT_INDENT, margins[3]);
break;
}
setLength(*entry, ZLTextStyleEntry::LENGTH_LEFT_INDENT, styles, "margin-left");
setLength(*entry, ZLTextStyleEntry::LENGTH_RIGHT_INDENT, styles, "margin-right");
setLength(*entry, ZLTextStyleEntry::LENGTH_FIRST_LINE_INDENT_DELTA, styles, "text-indent");
setLength(*entry, ZLTextStyleEntry::LENGTH_SPACE_BEFORE, styles, "margin-top");
setLength(*entry, ZLTextStyleEntry::LENGTH_SPACE_BEFORE, styles, "padding-top");
setLength(*entry, ZLTextStyleEntry::LENGTH_SPACE_AFTER, styles, "margin-bottom");
setLength(*entry, ZLTextStyleEntry::LENGTH_SPACE_AFTER, styles, "padding-bottom");
return entry;
setMargin(entry, ZLTextStyleEntry::LENGTH_LEFT_INDENT, styles, "margin-left");
setMargin(entry, ZLTextStyleEntry::LENGTH_RIGHT_INDENT, styles, "margin-right");
setLength(entry, ZLTextStyleEntry::LENGTH_FIRST_LINE_INDENT_DELTA, styles, "text-indent");
setMargin(entry, ZLTextStyleEntry::LENGTH_SPACE_BEFORE, styles, "margin-top");
setLength(entry, ZLTextStyleEntry::LENGTH_SPACE_BEFORE, styles, "padding-top");
setMargin(entry, ZLTextStyleEntry::LENGTH_SPACE_AFTER, styles, "margin-bottom");
setLength(entry, ZLTextStyleEntry::LENGTH_SPACE_AFTER, styles, "padding-bottom");
}
bool StyleSheetTable::getPageBreakBefore(const AttributeMap &map, bool &value) {
bool StyleSheetTable::getPageBreakBefore(const AttributeMap &map, ZLBoolean3 &value) {
const std::vector<std::string> &pbb = values(map, "page-break-before");
if (!pbb.empty()) {
if ((pbb[0] == "always") ||
(pbb[0] == "left") ||
(pbb[0] == "right")) {
value = true;
value = B3_TRUE;
return true;
} else if (pbb[0] == "avoid") {
value = false;
value = B3_FALSE;
return true;
}
}
return false;
}
bool StyleSheetTable::getPageBreakAfter(const AttributeMap &map, bool &value) {
bool StyleSheetTable::getPageBreakAfter(const AttributeMap &map, ZLBoolean3 &value) {
const std::vector<std::string> &pba = values(map, "page-break-after");
if (!pba.empty()) {
if ((pba[0] == "always") ||
(pba[0] == "left") ||
(pba[0] == "right")) {
value = true;
value = B3_TRUE;
return true;
} else if (pba[0] == "avoid") {
value = false;
value = B3_FALSE;
return true;
}
}
return false;
}
void StyleSheetTable::updateStyle(Style &style, const AttributeMap &map) {
updateTextStyle(style.TextStyle, map);
getPageBreakBefore(map, style.PageBreakBefore);
getPageBreakAfter(map, style.PageBreakAfter);
}

View file

@ -27,50 +27,151 @@
#include <shared_ptr.h>
#include <ZLTextParagraph.h>
#include <ZLBoolean3.h>
class StyleSheetTable {
public:
struct Element {
Element(const std::string &tag, const char *klass, const char* id);
std::string Name;
std::vector<std::string> Classes;
std::string Id;
};
class Selector {
public:
Selector(const std::string &type, const std::string &klass, const std::string &id);
Selector(const std::string &type, const std::string &klass);
Selector(const std::string &selector);
Selector(const Selector &other);
Selector();
Selector &operator = (const Selector &other);
bool operator == (const Selector &other) const;
bool operator != (const Selector &other) const;
bool operator < (const Selector &other) const;
bool match(const Element &element) const;
static bool match(const std::string &pattern, const std::string &str);
static bool match(const std::string &pattern, const std::vector<std::string> &strings);
const std::string &type() const;
const std::string &klass() const;
const std::string &id() const;
int a() const;
int b() const;
int c() const;
private:
std::string myType;
std::string myClass;
std::string myId;
};
typedef std::vector<Element> ElementList;
typedef std::vector<Selector> SelectorList;
typedef std::map<std::string,std::vector<std::string> > AttributeMap;
static shared_ptr<ZLTextStyleEntry> createControl(shared_ptr<ZLTextStyleEntry> entry, const AttributeMap &map);
static bool getPageBreakBefore(const AttributeMap &map, bool &value);
static bool getPageBreakAfter(const AttributeMap &map, bool &value);
struct Style {
Style();
Style(const Style &other);
Style(const AttributeMap &map);
Style &operator = (const Style &other);
void apply(const Style &other);
ZLTextStyleEntry TextStyle;
ZLBoolean3 PageBreakBefore;
ZLBoolean3 PageBreakAfter;
};
typedef std::vector<Style> StyleList;
static void updateStyle(Style &style, const AttributeMap &map);
static void updateTextStyle(ZLTextStyleEntry &entry, const AttributeMap &map);
static bool getPageBreakBefore(const AttributeMap &map, ZLBoolean3 &value);
static bool getPageBreakAfter(const AttributeMap &map, ZLBoolean3 &value);
private:
void addMap(const std::string &tag, const std::string &aClass, const AttributeMap &map);
struct Entry {
Entry();
Entry(const SelectorList &selectors, int specificity, const AttributeMap &map);
Entry &operator = (const Entry &other);
bool match(const ElementList &stack) const;
SelectorList Selectors;
int Specificity;
struct Style Style;
};
void addMap(const std::vector<std::string> &selectors, const AttributeMap &map);
static bool parseLength(const std::string &toParse, short &size, ZLTextStyleEntry::SizeUnit &unit);
static bool parseMargin(const std::string &toParse, short &size, ZLTextStyleEntry::SizeUnit &unit);
static void setLength(ZLTextStyleEntry &entry, ZLTextStyleEntry::Length name, const AttributeMap &map, const std::string &attributeName);
static void setLength(ZLTextStyleEntry &entry, ZLTextStyleEntry::Length name, const std::string &value);
static void setMargin(ZLTextStyleEntry &entry, ZLTextStyleEntry::Length name, const AttributeMap &map, const std::string &attributeName);
static void setMargin(ZLTextStyleEntry &entry, ZLTextStyleEntry::Length name, const std::string &value);
static const std::vector<std::string> &values(const AttributeMap &map, const std::string &name);
static bool sortBySpecificity(const Entry *e1, const Entry *e2);
public:
bool isEmpty() const;
bool doBreakBefore(const std::string &tag, const std::string &aClass) const;
bool doBreakAfter(const std::string &tag, const std::string &aClass) const;
shared_ptr<ZLTextStyleEntry> control(const std::string &tag, const std::string &aClass) const;
void applyStyles(const ElementList &stack, Style &style) const;
private:
struct Key {
Key(const std::string &tag, const std::string &aClass);
const std::string TagName;
const std::string ClassName;
bool operator < (const Key &key) const;
};
std::map<Key,shared_ptr<ZLTextStyleEntry> > myControlMap;
std::map<Key,bool> myPageBreakBeforeMap;
std::map<Key,bool> myPageBreakAfterMap;
std::vector<Entry> myEntries;
friend class StyleSheetTableParser;
};
inline StyleSheetTable::Key::Key(const std::string &tag, const std::string &aClass) : TagName(tag), ClassName(aClass) {
inline StyleSheetTable::Selector::Selector(const std::string &type, const std::string &klass, const std::string &id) : myType(type), myClass(klass), myId(id) {}
inline StyleSheetTable::Selector::Selector(const std::string &type, const std::string &klass) : myType(type), myClass(klass) {}
inline StyleSheetTable::Selector::Selector(const StyleSheetTable::Selector &other) : myType(other.myType), myClass(other.myClass), myId(other.myId) {}
inline StyleSheetTable::Selector::Selector() {}
inline StyleSheetTable::Selector &StyleSheetTable::Selector::operator = (const StyleSheetTable::Selector &other) {
myType = other.myType; myClass = other.myClass; myId = other.myId; return *this;
}
inline bool StyleSheetTable::Selector::operator == (const Selector &other) const {
return (&other == this) || (myType == other.myType && myClass == other.myClass && myId == other.myId);
}
inline bool StyleSheetTable::Selector::operator != (const Selector &other) const {
return (&other != this) && (myType != other.myType || myClass != other.myClass || myId != other.myId);
}
inline bool StyleSheetTable::Selector::operator < (const StyleSheetTable::Selector &other) const {
return myType < other.myType || (myType == other.myType && (myClass < other.myClass || (myClass == other.myClass && myId < other.myId)));
}
inline bool StyleSheetTable::Selector::match(const Element &element) const {
return match(myType, element.Name) && match(myId, element.Id) && match(myClass, element.Classes);
}
inline int StyleSheetTable::Selector::a() const { return myId.empty() ? 0 : 1; }
inline int StyleSheetTable::Selector::b() const { return myClass.empty() ? 0 : 1; }
inline int StyleSheetTable::Selector::c() const { return myType.empty() ? 0 : 1; }
inline const std::string &StyleSheetTable::Selector::type() const { return myType; }
inline const std::string &StyleSheetTable::Selector::klass() const { return myClass; }
inline const std::string &StyleSheetTable::Selector::id() const { return myId; }
inline StyleSheetTable::Style::Style() : PageBreakBefore(B3_UNDEFINED), PageBreakAfter(B3_UNDEFINED) {}
inline StyleSheetTable::Style::Style(const StyleSheetTable::Style &other) : TextStyle(other.TextStyle), PageBreakBefore(other.PageBreakBefore), PageBreakAfter(other.PageBreakAfter) {}
inline StyleSheetTable::Style::Style(const StyleSheetTable::AttributeMap &map) : PageBreakBefore(B3_UNDEFINED), PageBreakAfter(B3_UNDEFINED) {
updateStyle(*this, map);
}
inline StyleSheetTable::Style &StyleSheetTable::Style::operator = (const StyleSheetTable::Style &other) {
TextStyle = other.TextStyle;
PageBreakBefore = other.PageBreakBefore;
PageBreakAfter = other.PageBreakAfter;
return *this;
}
inline bool StyleSheetTable::Key::operator < (const StyleSheetTable::Key &key) const {
return (TagName < key.TagName) || ((TagName == key.TagName) && (ClassName < key.ClassName));
inline StyleSheetTable::Entry::Entry() : Specificity(0) {}
inline StyleSheetTable::Entry::Entry(const SelectorList &selectors, int specificity, const AttributeMap &map) : Selectors(selectors), Specificity(specificity), Style(map) {}
inline StyleSheetTable::Entry &StyleSheetTable::Entry::operator = (const StyleSheetTable::Entry &other) {
Selectors = other.Selectors;
Style = other.Style;
Specificity = other.Specificity;
return *this;
}
inline bool StyleSheetTable::isEmpty() const { return myEntries.empty(); }
#endif /* __STYLESHEETTABLE_H__ */

View file

@ -41,6 +41,12 @@ std::map<std::string,XHTMLTagAction*> XHTMLReader::ourTagActions;
XHTMLTagAction::~XHTMLTagAction() {
}
void XHTMLTagAction::doAtStart(XHTMLReader &reader, const char **xmlattributes) {
}
void XHTMLTagAction::doAtEnd(XHTMLReader &reader) {
}
BookReader &XHTMLTagAction::bookReader(XHTMLReader &reader) {
return reader.myModelReader;
}
@ -68,7 +74,6 @@ class XHTMLTagLinkAction : public XHTMLTagAction {
public:
void doAtStart(XHTMLReader &reader, const char **xmlattributes);
void doAtEnd(XHTMLReader &reader);
};
class XHTMLTagParagraphAction : public XHTMLTagAction {
@ -89,6 +94,11 @@ class XHTMLTagRestartParagraphAction : public XHTMLTagAction {
public:
void doAtStart(XHTMLReader &reader, const char **xmlattributes);
};
class XHTMLTagLineBreakAction : public XHTMLTagAction {
public:
void doAtEnd(XHTMLReader &reader);
};
@ -99,7 +109,6 @@ public:
XHTMLTagImageAction(const std::string &attributeName);
void doAtStart(XHTMLReader &reader, const char **xmlattributes);
void doAtEnd(XHTMLReader &reader);
private:
shared_ptr<ZLXMLReader::AttributeNamePredicate> myPredicate;
@ -224,9 +233,6 @@ void XHTMLTagLinkAction::doAtStart(XHTMLReader &reader, const char **xmlattribut
//reader.myStyleSheetTable.dump();
}
void XHTMLTagLinkAction::doAtEnd(XHTMLReader&) {
}
void XHTMLTagParagraphAction::doAtStart(XHTMLReader &reader, const char**) {
if (!reader.myNewParagraphInProgress) {
beginParagraph(reader);
@ -255,7 +261,8 @@ void XHTMLTagRestartParagraphAction::doAtStart(XHTMLReader &reader, const char**
beginParagraph(reader);
}
void XHTMLTagRestartParagraphAction::doAtEnd(XHTMLReader&) {
void XHTMLTagLineBreakAction::doAtEnd(XHTMLReader& reader) {
bookReader(reader).addLineBreak();
}
void XHTMLTagItemAction::doAtStart(XHTMLReader &reader, const char**) {
@ -280,6 +287,11 @@ XHTMLTagImageAction::XHTMLTagImageAction(const std::string &attributeName) {
}
void XHTMLTagImageAction::doAtStart(XHTMLReader &reader, const char **xmlattributes) {
// Ignore transparent images
if (!reader.myOpacityStack.back()) {
return;
}
const char *fileName = reader.attributeValue(xmlattributes, *myPredicate);
if (fileName == 0) {
return;
@ -302,6 +314,7 @@ void XHTMLTagImageAction::doAtStart(XHTMLReader &reader, const char **xmlattribu
if (flag) {
beginParagraph(reader);
}
reader.myElementHasContents.back() = true;
}
XHTMLTagSvgAction::XHTMLTagSvgAction(XHTMLSvgImageAttributeNamePredicate &predicate) : myPredicate(predicate) {
@ -322,9 +335,6 @@ bool XHTMLSvgImageAttributeNamePredicate::accepts(const ZLXMLReader &reader, con
return myIsEnabled && NamespaceAttributeNamePredicate::accepts(reader, name);
}
void XHTMLTagImageAction::doAtEnd(XHTMLReader&) {
}
XHTMLTagControlAction::XHTMLTagControlAction(FBTextKind control) : myControl(control) {
}
@ -387,7 +397,7 @@ void XHTMLTagParagraphWithControlAction::doAtEnd(XHTMLReader &reader) {
}
void XHTMLTagPreAction::doAtStart(XHTMLReader &reader, const char**) {
reader.myPreformatted = true;
reader.myPreformatted++;
beginParagraph(reader);
bookReader(reader).addControl(CODE, true);
}
@ -395,7 +405,7 @@ void XHTMLTagPreAction::doAtStart(XHTMLReader &reader, const char**) {
void XHTMLTagPreAction::doAtEnd(XHTMLReader &reader) {
bookReader(reader).addControl(CODE, false);
endParagraph(reader);
reader.myPreformatted = false;
reader.myPreformatted--;
}
XHTMLTagAction *XHTMLReader::addAction(const std::string &tag, XHTMLTagAction *action) {
@ -440,8 +450,9 @@ void XHTMLReader::fillTagTable() {
addAction("cite", new XHTMLTagControlAction(CITE));
addAction("sub", new XHTMLTagControlAction(SUB));
addAction("sup", new XHTMLTagControlAction(SUP));
addAction("dd", new XHTMLTagControlAction(DEFINITION_DESCRIPTION));
addAction("dfn", new XHTMLTagControlAction(DEFINITION));
addAction("dd", new XHTMLTagParagraphWithControlAction(DEFINITION_DESCRIPTION));
addAction("dt", new XHTMLTagParagraphWithControlAction(DEFINITION));
addAction("dfn", new XHTMLTagParagraphWithControlAction(DEFINITION));
addAction("strike", new XHTMLTagControlAction(STRIKETHROUGH));
addAction("a", new XHTMLTagHyperlinkAction());
@ -457,7 +468,7 @@ void XHTMLReader::fillTagTable() {
//addAction("base", new XHTMLTagAction());
//addAction("blockquote", new XHTMLTagAction());
addAction("br", new XHTMLTagRestartParagraphAction());
addAction("br", new XHTMLTagLineBreakAction());
//addAction("center", new XHTMLTagAction());
addAction("div", new XHTMLTagParagraphAction());
addAction("dt", new XHTMLTagParagraphAction());
@ -495,13 +506,14 @@ bool XHTMLReader::readFile(const ZLFile &file, const std::string &referenceName)
const int index = referenceName.rfind('/', referenceName.length() - 1);
myReferenceDirName = referenceName.substr(0, index + 1);
myPreformatted = false;
myPreformatted = 0;
myNewParagraphInProgress = false;
myReadState = READ_NOTHING;
myCSSStack.clear();
myStyleEntryStack.clear();
myStylesToRemove = 0;
myElementStack.clear();
myStyleStack.clear();
myElementHasContents.clear();
myOpacityStack.clear();
return readDocument(file);
}
@ -516,80 +528,45 @@ shared_ptr<ZLTextStyleEntry> XHTMLReader::addStyleEntry(shared_ptr<ZLTextStyleEn
void XHTMLReader::startElementHandler(const char *tag, const char **attributes) {
static const std::string HASH = "#";
static const std::string EMPTY;
const char *id = attributeValue(attributes, "id");
const char *style = attributeValue(attributes, "style");
const char *klass = attributeValue(attributes, "class");
if (id != 0) {
myModelReader.addHyperlinkLabel(myReferenceName + HASH + id);
}
const std::string sTag = ZLUnicodeUtil::toLower(tag);
myElementStack.push_back(StyleSheetTable::Element(sTag, klass, id));
const char *aClass = attributeValue(attributes, "class");
std::vector<std::string> classes;
if (aClass != 0) {
classes = ZLStringUtil::splitString(aClass, ", \n");
}
if (classes.empty()) {
classes.push_back(EMPTY);
}
shared_ptr<ZLTextStyleEntry> inlineStyle;
const char *style = attributeValue(attributes, "style");
ZLBoolean3 pageBreakBefore(B3_UNDEFINED), pageBreakAfter(B3_UNDEFINED);
StyleSheetTable::Style cssStyle;
myStyleSheetTable.applyStyles(myElementStack, cssStyle);
if (style != 0) {
inlineStyle = myStyleParser.parseString(style, &pageBreakBefore, &pageBreakAfter);
cssStyle.apply(myStyleParser.parseString(style));
}
if (pageBreakBefore == B3_UNDEFINED) {
for (unsigned int i=0; i<classes.size(); i++) {
if (myStyleSheetTable.doBreakBefore(sTag, classes[i])) {
pageBreakBefore = B3_TRUE;
break;
}
}
}
if (pageBreakAfter == B3_UNDEFINED) {
for (unsigned int i=0; i<classes.size(); i++) {
if (myStyleSheetTable.doBreakAfter(sTag, classes[i])) {
pageBreakAfter = B3_TRUE;
break;
}
}
}
if (pageBreakBefore == B3_TRUE) {
if (cssStyle.PageBreakBefore == B3_TRUE) {
myModelReader.insertEndOfSectionParagraph();
}
myDoPageBreakAfterStack.push_back(pageBreakAfter == B3_TRUE);
int opacity = myOpacityStack.empty() ? 255 : myOpacityStack.back();
if (cssStyle.TextStyle.opacitySupported()) {
opacity *= cssStyle.TextStyle.opacity();
opacity /= 255;
}
myStyleStack.push_back(cssStyle);
myElementHasContents.push_back(false);
myOpacityStack.push_back((unsigned char)opacity);
XHTMLTagAction *action = ourTagActions[sTag];
if (action != 0) {
action->doAtStart(*this, attributes);
}
shared_ptr<ZLTextStyleEntry> entry;
entry = addStyleEntry(entry, myStyleSheetTable.control(sTag, EMPTY));
for (unsigned int i=0; i<classes.size(); i++) {
std::string klass = classes[i];
entry = addStyleEntry(entry, myStyleSheetTable.control(EMPTY, klass));
if (!klass.empty()) {
entry = addStyleEntry(entry, myStyleSheetTable.control(sTag, klass));
}
}
entry = addStyleEntry(entry, inlineStyle);
if (!entry.isNull()) {
myModelReader.addControl(*entry);
myStyleEntryStack.push_back(entry);
myCSSStack.push_back(1);
} else {
myCSSStack.push_back(0);
}
myModelReader.addControl(cssStyle.TextStyle);
}
void XHTMLReader::endElementHandler(const char *tag) {
for (int i = myCSSStack.back(); i > 0; --i) {
myModelReader.addControl(REGULAR, false);
}
myStylesToRemove = myCSSStack.back();
myCSSStack.pop_back();
myModelReader.addControl(REGULAR, false);
XHTMLTagAction *action = ourTagActions[ZLUnicodeUtil::toLower(tag)];
if (action != 0) {
@ -597,58 +574,32 @@ void XHTMLReader::endElementHandler(const char *tag) {
myNewParagraphInProgress = false;
}
for (; myStylesToRemove > 0; --myStylesToRemove) {
myStyleEntryStack.pop_back();
}
if (myDoPageBreakAfterStack.back()) {
const bool haveContents = myElementHasContents.back();
if (myStyleStack.back().PageBreakAfter == B3_TRUE && haveContents) {
myModelReader.insertEndOfSectionParagraph();
}
myDoPageBreakAfterStack.pop_back();
myElementStack.pop_back();
myStyleStack.pop_back();
myElementHasContents.pop_back();
myOpacityStack.pop_back();
if (!myElementHasContents.empty() && haveContents) {
myElementHasContents.back() = true;
}
}
void XHTMLReader::beginParagraph() {
myCurrentParagraphIsEmpty = true;
myModelReader.beginParagraph();
bool doBlockSpaceBefore = false;
for (std::vector<shared_ptr<ZLTextStyleEntry> >::const_iterator it = myStyleEntryStack.begin(); it != myStyleEntryStack.end(); ++it) {
myModelReader.addControl(**it);
doBlockSpaceBefore =
doBlockSpaceBefore ||
(*it)->lengthSupported(ZLTextStyleEntry::LENGTH_SPACE_BEFORE);
}
if (doBlockSpaceBefore) {
ZLTextStyleEntry blockingEntry;
blockingEntry.setLength(
ZLTextStyleEntry::LENGTH_SPACE_BEFORE,
0,
ZLTextStyleEntry::SIZE_UNIT_PIXEL
);
myModelReader.addControl(blockingEntry);
if (!myStyleStack.empty()) {
for (StyleSheetTable::StyleList::const_iterator it = myStyleStack.begin(); it != myStyleStack.end(); ++it) {
myModelReader.addControl(((*it).TextStyle));
}
}
}
void XHTMLReader::endParagraph() {
bool doBlockSpaceAfter = false;
for (std::vector<shared_ptr<ZLTextStyleEntry> >::const_iterator it = myStyleEntryStack.begin(); it != myStyleEntryStack.end() - myStylesToRemove; ++it) {
doBlockSpaceAfter =
doBlockSpaceAfter ||
(*it)->lengthSupported(ZLTextStyleEntry::LENGTH_SPACE_AFTER);
}
if (doBlockSpaceAfter) {
ZLTextStyleEntry blockingEntry;
blockingEntry.setLength(
ZLTextStyleEntry::LENGTH_SPACE_AFTER,
0,
ZLTextStyleEntry::SIZE_UNIT_PIXEL
);
myModelReader.addControl(blockingEntry);
}
for (; myStylesToRemove > 0; --myStylesToRemove) {
myModelReader.addControl(*myStyleEntryStack.back());
myStyleEntryStack.pop_back();
}
myModelReader.endParagraph();
}
@ -662,35 +613,35 @@ void XHTMLReader::characterDataHandler(const char *text, size_t len) {
}
break;
case READ_BODY:
if (myPreformatted) {
if ((*text == '\r') || (*text == '\n')) {
myModelReader.addControl(CODE, false);
endParagraph();
beginParagraph();
myModelReader.addControl(CODE, true);
}
size_t spaceCounter = 0;
while ((spaceCounter < len) && isspace((unsigned char)*(text + spaceCounter))) {
++spaceCounter;
}
myModelReader.addFixedHSpace(spaceCounter);
text += spaceCounter;
len -= spaceCounter;
} else if ((myNewParagraphInProgress) || !myModelReader.paragraphIsOpen()) {
while (isspace((unsigned char)*text)) {
++text;
if (--len == 0) {
break;
if (myOpacityStack.back()) {
if (myPreformatted) {
if ((*text == '\r') || (*text == '\n')) {
myModelReader.addLineBreak();
}
size_t spaceCounter = 0;
while ((spaceCounter < len) && isspace((unsigned char)*(text + spaceCounter))) {
++spaceCounter;
}
myModelReader.addFixedHSpace(spaceCounter);
text += spaceCounter;
len -= spaceCounter;
} else if ((myNewParagraphInProgress) || !myModelReader.paragraphIsOpen()) {
while (isspace((unsigned char)*text)) {
++text;
if (--len == 0) {
break;
}
}
}
}
if (len > 0) {
myCurrentParagraphIsEmpty = false;
if (!myModelReader.paragraphIsOpen()) {
myModelReader.beginParagraph();
if (len > 0) {
myCurrentParagraphIsEmpty = false;
myElementHasContents.back() = true;
if (!myModelReader.paragraphIsOpen()) {
myModelReader.beginParagraph();
}
myModelReader.addData(std::string(text, len));
myNewParagraphInProgress = false;
}
myModelReader.addData(std::string(text, len));
myNewParagraphInProgress = false;
}
break;
}

View file

@ -39,8 +39,8 @@ class XHTMLTagAction {
public:
virtual ~XHTMLTagAction();
virtual void doAtStart(XHTMLReader &reader, const char **xmlattributes) = 0;
virtual void doAtEnd(XHTMLReader &reader) = 0;
virtual void doAtStart(XHTMLReader &reader, const char **xmlattributes);
virtual void doAtEnd(XHTMLReader &reader);
protected:
static BookReader &bookReader(XHTMLReader &reader);
@ -80,13 +80,13 @@ private:
std::string myPathPrefix;
std::string myReferenceName;
std::string myReferenceDirName;
bool myPreformatted;
int myPreformatted;
bool myNewParagraphInProgress;
StyleSheetTable myStyleSheetTable;
std::vector<int> myCSSStack;
std::vector<shared_ptr<ZLTextStyleEntry> > myStyleEntryStack;
int myStylesToRemove;
std::vector<bool> myDoPageBreakAfterStack;
StyleSheetTable::ElementList myElementStack;
StyleSheetTable::StyleList myStyleStack;
std::vector<unsigned char> myOpacityStack;
std::vector<bool> myElementHasContents;
bool myCurrentParagraphIsEmpty;
StyleSheetSingleStyleParser myStyleParser;
shared_ptr<StyleSheetTableParser> myTableParser;
@ -104,6 +104,7 @@ private:
friend class XHTMLTagParagraphAction;
friend class XHTMLTagBodyAction;
friend class XHTMLTagRestartParagraphAction;
friend class XHTMLTagImageAction;
};
#endif /* __XHTMLREADER_H__ */

View file

@ -91,17 +91,18 @@ void ZLStringUtil::stripWhiteSpaces(std::string &str) {
str.erase(r_counter, length - r_counter);
}
std::vector<std::string> ZLStringUtil::splitString(const std::string &str, const char* delim)
{
std::vector<std::string> ZLStringUtil::splitString(const char *str, const char* delim) {
std::vector<std::string> tokens;
char *buf = strdup(str.c_str());
char *saveptr;
char *token = strtok_r(buf, delim, &saveptr);
while (token) {
tokens.push_back(std::string(token));
token = strtok_r(NULL, delim, &saveptr);
if (str != 0) {
char *buf = strdup(str);
char *saveptr;
char *token = strtok_r(buf, delim, &saveptr);
while (token) {
tokens.push_back(std::string(token));
token = strtok_r(NULL, delim, &saveptr);
}
free(buf);
}
free(buf);
return tokens;
}

View file

@ -34,6 +34,7 @@ public:
static void appendNumber(std::string &str, unsigned int n);
static void append(std::string &str, const std::vector<std::string> &buffer);
static void stripWhiteSpaces(std::string &str);
static std::vector<std::string> splitString(const char *str, const char* delim);
static std::vector<std::string> splitString(const std::string &str, const char* delim);
static std::string printf(const std::string &format, const std::string &arg0);
@ -42,4 +43,8 @@ public:
static double stringToDouble(const std::string &value, double defaultValue);
};
inline std::vector<std::string> ZLStringUtil::splitString(const std::string &str, const char* delim) {
return ZLStringUtil::splitString(str.c_str(), delim);
}
#endif /* __ZLSTRINGUTIL_H__ */

View file

@ -103,6 +103,7 @@ int ZLTextArea::Style::elementWidth(const ZLTextElement &element, unsigned int c
case ZLTextElement::AFTER_PARAGRAPH_ELEMENT:
case ZLTextElement::EMPTY_LINE_ELEMENT:
return metrics.FullWidth + abs(textStyle()->lineStartIndent(metrics, false)) + abs(textStyle()->lineEndIndent(metrics, false)) + abs(textStyle()->firstLineIndentDelta(metrics)) + 1;
case ZLTextElement::LINE_BREAK_ELEMENT:
case ZLTextElement::FORCED_CONTROL_ELEMENT:
case ZLTextElement::CONTROL_ELEMENT:
case ZLTextElement::START_REVERSED_SEQUENCE_ELEMENT:
@ -131,6 +132,7 @@ int ZLTextArea::Style::elementHeight(const ZLTextElement &element, const ZLTextS
case ZLTextElement::AFTER_PARAGRAPH_ELEMENT:
return - textStyle()->spaceBefore(metrics);
case ZLTextElement::EMPTY_LINE_ELEMENT:
case ZLTextElement::LINE_BREAK_ELEMENT:
return myArea.context().stringHeight();
case ZLTextElement::INDENT_ELEMENT:
case ZLTextElement::HSPACE_ELEMENT:

View file

@ -66,6 +66,15 @@ void ZLTextArea::prepareTextLine(Style &style, const ZLTextLineInfo &info, int y
const bool endOfParagraph = info.End.isEndOfParagraph();
bool wordOccured = false;
bool endsWithLineBreak = false;
if (info.RealStart < info.End) {
ZLTextWordCursor last(info.End);
last.previousWord();
if (last.element().kind() == ZLTextElement::LINE_BREAK_ELEMENT) {
endsWithLineBreak = true;
}
}
int x = info.StartIndent;
const int fontSize = style.textStyle()->fontSize();
@ -89,7 +98,7 @@ void ZLTextArea::prepareTextLine(Style &style, const ZLTextLineInfo &info, int y
x += (metrics.FullWidth - style.textStyle()->lineEndIndent(metrics, isRtl()) - info.Width) / 2;
break;
case ALIGN_JUSTIFY:
if (!endOfParagraph && (info.End.element().kind() != ZLTextElement::AFTER_PARAGRAPH_ELEMENT)) {
if (!endsWithLineBreak && !endOfParagraph && info.End.element().kind() != ZLTextElement::AFTER_PARAGRAPH_ELEMENT) {
fullCorrection = metrics.FullWidth - style.textStyle()->lineEndIndent(metrics, isRtl()) - info.Width;
}
break;
@ -140,6 +149,7 @@ void ZLTextArea::prepareTextLine(Style &style, const ZLTextLineInfo &info, int y
case ZLTextElement::BEFORE_PARAGRAPH_ELEMENT:
case ZLTextElement::AFTER_PARAGRAPH_ELEMENT:
case ZLTextElement::EMPTY_LINE_ELEMENT:
case ZLTextElement::LINE_BREAK_ELEMENT:
case ZLTextElement::FIXED_HSPACE_ELEMENT:
break;
case ZLTextElement::START_REVERSED_SEQUENCE_ELEMENT:

View file

@ -177,7 +177,11 @@ ZLTextLineInfoPtr ZLTextArea::processTextLine(Style &style, const ZLTextWordCurs
break;
}
if (newInfo.Width > maxWidth && info.Start != info.End) {
if (elementKind == ZLTextElement::LINE_BREAK_ELEMENT) {
newInfo.End.nextWord();
newInfo.setTo(info);
break;
} else if (newInfo.Width > maxWidth && info.Start != info.End) {
if (!info.End.equalElementIndex(start)) {
break;
}
@ -222,8 +226,7 @@ ZLTextLineInfoPtr ZLTextArea::processTextLine(Style &style, const ZLTextWordCurs
}
} while (!newInfo.End.equalElementIndex(end));
if (!newInfo.End.equalElementIndex(end) && useHyphenator &&
style.textStyle()->allowHyphenations()) {
if (elementKind != ZLTextElement::LINE_BREAK_ELEMENT && !newInfo.End.equalElementIndex(end) && useHyphenator && style.textStyle()->allowHyphenations()) {
const ZLTextElement &element = paragraphCursor[newInfo.End.elementIndex()];
if (element.kind() == ZLTextElement::WORD_ELEMENT) {
const int startCharIndex = newInfo.End.charIndex();

View file

@ -45,6 +45,7 @@ public:
BEFORE_PARAGRAPH_ELEMENT,
AFTER_PARAGRAPH_ELEMENT,
EMPTY_LINE_ELEMENT,
LINE_BREAK_ELEMENT,
START_REVERSED_SEQUENCE_ELEMENT,
END_REVERSED_SEQUENCE_ELEMENT,
};

View file

@ -108,6 +108,9 @@ void ZLTextParagraphCursor::Builder::fill() {
updateBidiLevel(myBaseBidiLevel);
myLatestBidiLevel = myBaseBidiLevel;
break;
case ZLTextParagraphEntry::LINE_BREAK_ENTRY:
myElements.push_back(myTextElementPool.LineBreakElement);
break;
}
}

View file

@ -46,6 +46,7 @@ ZLTextElementVector::~ZLTextElementVector() {
case ZLTextElement::BEFORE_PARAGRAPH_ELEMENT:
case ZLTextElement::AFTER_PARAGRAPH_ELEMENT:
case ZLTextElement::EMPTY_LINE_ELEMENT:
case ZLTextElement::LINE_BREAK_ELEMENT:
case ZLTextElement::START_REVERSED_SEQUENCE_ELEMENT:
case ZLTextElement::END_REVERSED_SEQUENCE_ELEMENT:
break;
@ -59,6 +60,7 @@ ZLTextElementPool::ZLTextElementPool() {
BeforeParagraphElement = new ZLTextSpecialElement(ZLTextElement::BEFORE_PARAGRAPH_ELEMENT);
AfterParagraphElement = new ZLTextSpecialElement(ZLTextElement::AFTER_PARAGRAPH_ELEMENT);
EmptyLineElement = new ZLTextSpecialElement(ZLTextElement::EMPTY_LINE_ELEMENT);
LineBreakElement = new ZLTextSpecialElement(ZLTextElement::LINE_BREAK_ELEMENT);
StartReversedSequenceElement = new ZLTextSpecialElement(ZLTextElement::START_REVERSED_SEQUENCE_ELEMENT);
EndReversedSequenceElement = new ZLTextSpecialElement(ZLTextElement::END_REVERSED_SEQUENCE_ELEMENT);
}
@ -69,6 +71,7 @@ ZLTextElementPool::~ZLTextElementPool() {
delete BeforeParagraphElement;
delete AfterParagraphElement;
delete EmptyLineElement;
delete LineBreakElement;
delete StartReversedSequenceElement;
delete EndReversedSequenceElement;
}

View file

@ -62,6 +62,7 @@ public:
ZLTextElement *BeforeParagraphElement;
ZLTextElement *AfterParagraphElement;
ZLTextElement *EmptyLineElement;
ZLTextElement *LineBreakElement;
ZLTextElement *StartReversedSequenceElement;
ZLTextElement *EndReversedSequenceElement;

View file

@ -214,24 +214,40 @@ void ZLTextModel::addControl(ZLTextKind textKind, bool isStart) {
}
void ZLTextModel::addControl(const ZLTextStyleEntry &entry) {
int len = sizeof(int) + 5 + ZLTextStyleEntry::NUMBER_OF_LENGTHS * (sizeof(short) + 1);
int len = sizeof(int) + 2;
if (entry.myMask & ((1 << ZLTextStyleEntry::NUMBER_OF_LENGTHS) - 1)) {
for (int i = 0; i < ZLTextStyleEntry::NUMBER_OF_LENGTHS; ++i) {
if (entry.myMask & (1 << i)) {
len += (sizeof(short) + 1);
}
}
}
if (entry.opacitySupported()) ++len;
if (entry.alignmentTypeSupported()) ++len;
if (entry.supportedFontModifier()) ++len;
if (entry.fontSizeSupported()) ++len;
if (entry.fontFamilySupported()) {
len += entry.fontFamily().length() + 1;
}
myLastEntryStart = myAllocator.allocate(len);
char *address = myLastEntryStart;
*address++ = ZLTextParagraphEntry::STYLE_ENTRY;
*address++ = entry.mySupportedFontModifier;
memcpy(address, &entry.myMask, sizeof(int));
address += sizeof(int);
for (int i = 0; i < ZLTextStyleEntry::NUMBER_OF_LENGTHS; ++i) {
*address++ = entry.myLengths[i].Unit;
memcpy(address, &entry.myLengths[i].Size, sizeof(short));
address += sizeof(short);
if (entry.myMask & ((1 << ZLTextStyleEntry::NUMBER_OF_LENGTHS) - 1)) {
for (int i = 0; i < ZLTextStyleEntry::NUMBER_OF_LENGTHS; ++i) {
if (entry.myMask & (1 << i)) {
*address++ = entry.myLengths[i].Unit;
memcpy(address, &entry.myLengths[i].Size, sizeof(short));
address += sizeof(short);
}
}
}
*address++ = entry.mySupportedFontModifier;
*address++ = entry.myFontModifier;
*address++ = entry.myAlignmentType;
*address++ = entry.myFontSizeMag;
if (entry.opacitySupported()) *address++ = entry.myOpacity;
if (entry.alignmentTypeSupported()) *address++ = entry.myAlignmentType;
if (entry.supportedFontModifier()) *address++ = entry.myFontModifier;
if (entry.fontSizeSupported()) *address++ = entry.myFontSizeMag;
if (entry.fontFamilySupported()) {
memcpy(address, entry.fontFamily().data(), entry.fontFamily().length());
address += entry.fontFamily().length();
@ -267,3 +283,9 @@ void ZLTextModel::addBidiReset() {
*myLastEntryStart = ZLTextParagraphEntry::RESET_BIDI_ENTRY;
myParagraphs.back()->addEntry(myLastEntryStart);
}
void ZLTextModel::addLineBreak() {
myLastEntryStart = myAllocator.allocate(1);
*myLastEntryStart = ZLTextParagraphEntry::LINE_BREAK_ENTRY;
myParagraphs.back()->addEntry(myLastEntryStart);
}

View file

@ -72,6 +72,7 @@ public:
void addImage(const std::string &id, const ZLImageMap &imageMap, short vOffset);
void addFixedHSpace(unsigned char length);
void addBidiReset();
void addLineBreak();
protected:
void addParagraphInternal(ZLTextParagraph *paragraph);

View file

@ -58,22 +58,39 @@ short ZLTextStyleEntry::length(Length name, const Metrics &metrics) const {
}
ZLTextStyleEntry::ZLTextStyleEntry(char *address) {
mySupportedFontModifier = *address++;
memcpy(&myMask, address, sizeof(int));
address += sizeof(int);
for (int i = 0; i < NUMBER_OF_LENGTHS; ++i) {
myLengths[i].Unit = (SizeUnit)*address++;
memcpy(&myLengths[i].Size, address, sizeof(short));
address += sizeof(short);
if (myMask & (1 << i)) {
myLengths[i].Unit = (SizeUnit)*address++;
memcpy(&myLengths[i].Size, address, sizeof(short));
address += sizeof(short);
}
}
if (opacitySupported()) {
myOpacity = *address++;
}
if (alignmentTypeSupported()) {
myAlignmentType = (ZLTextAlignmentType)*address++;
}
if (supportedFontModifier()) {
myFontModifier = *address++;
}
if (fontSizeSupported()) {
myFontSizeMag = (signed char)*address++;
}
mySupportedFontModifier = *address++;
myFontModifier = *address++;
myAlignmentType = (ZLTextAlignmentType)*address++;
myFontSizeMag = (signed char)*address++;
if (fontFamilySupported()) {
myFontFamily = address;
}
}
void ZLTextStyleEntry::reset() {
mySupportedFontModifier = 0;
myMask = 0;
myFontFamily.clear();
}
void ZLTextStyleEntry::apply(const ZLTextStyleEntry &entry) {
for (int i = 0; i < NUMBER_OF_LENGTHS; ++i) {
if (entry.lengthSupported((Length)i)) {
@ -81,6 +98,9 @@ void ZLTextStyleEntry::apply(const ZLTextStyleEntry &entry) {
myMask |= 1 << i;
}
}
if (entry.opacitySupported()) {
setOpacity(entry.opacity());
}
if (entry.alignmentTypeSupported()) {
setAlignmentType(entry.alignmentType());
}
@ -171,10 +191,21 @@ void ZLTextParagraph::Iterator::next() {
case ZLTextParagraphEntry::STYLE_ENTRY:
{
int mask;
memcpy(&mask, myPointer + 1, sizeof(int));
bool withFontFamily = (mask & ZLTextStyleEntry::SUPPORT_FONT_FAMILY) == ZLTextStyleEntry::SUPPORT_FONT_FAMILY;
myPointer += sizeof(int) + ZLTextStyleEntry::NUMBER_OF_LENGTHS * (sizeof(short) + 1) + 5;
if (withFontFamily) {
const unsigned char supportedFontModifier = myPointer[1];
memcpy(&mask, myPointer + 2, sizeof(int));
myPointer += sizeof(int) + 2;
if (mask & ((1 << ZLTextStyleEntry::NUMBER_OF_LENGTHS) - 1)) {
for (int i = 0; i < ZLTextStyleEntry::NUMBER_OF_LENGTHS; ++i) {
if (mask & (1 << i)) {
myPointer += (sizeof(short) + 1);
}
}
}
if (mask & ZLTextStyleEntry::SUPPORT_OPACITY) ++myPointer;
if (mask & ZLTextStyleEntry::SUPPORT_ALIGNMENT_TYPE) ++myPointer;
if (supportedFontModifier) ++myPointer;
if (mask & ZLTextStyleEntry::SUPPORT_FONT_SIZE) ++myPointer;
if (mask & ZLTextStyleEntry::SUPPORT_FONT_FAMILY) {
while (*myPointer != '\0') {
++myPointer;
}
@ -186,6 +217,7 @@ void ZLTextParagraph::Iterator::next() {
myPointer += 2;
break;
case ZLTextParagraphEntry::RESET_BIDI_ENTRY:
case ZLTextParagraphEntry::LINE_BREAK_ENTRY:
++myPointer;
break;
}

View file

@ -44,6 +44,7 @@ public:
STYLE_ENTRY = 5,
FIXED_HSPACE_ENTRY = 6,
RESET_BIDI_ENTRY = 7,
LINE_BREAK_ENTRY = 8
};
protected:
@ -93,10 +94,13 @@ private:
public:
ZLTextStyleEntry();
ZLTextStyleEntry(const ZLTextStyleEntry &other);
ZLTextStyleEntry(char *address);
~ZLTextStyleEntry();
void reset();
void apply(const ZLTextStyleEntry &entry);
ZLTextStyleEntry &operator = (const ZLTextStyleEntry &other);
bool isEmpty() const;
@ -104,6 +108,10 @@ public:
short length(Length name, const Metrics &metrics) const;
void setLength(Length name, short length, SizeUnit unit);
bool opacitySupported() const;
unsigned char opacity() const;
void setOpacity(unsigned char opacity);
bool alignmentTypeSupported() const;
ZLTextAlignmentType alignmentType() const;
void setAlignmentType(ZLTextAlignmentType alignmentType);
@ -120,9 +128,12 @@ public:
const std::string &fontFamily() const;
void setFontFamily(const std::string &fontFamily);
static const int SUPPORT_ALIGNMENT_TYPE = 1 << NUMBER_OF_LENGTHS;
static const int SUPPORT_FONT_SIZE = 1 << (NUMBER_OF_LENGTHS + 1);
static const int SUPPORT_FONT_FAMILY = 1 << (NUMBER_OF_LENGTHS + 2);
enum {
SUPPORT_ALIGNMENT_TYPE = 1 << NUMBER_OF_LENGTHS,
SUPPORT_FONT_SIZE = 1 << (NUMBER_OF_LENGTHS + 1),
SUPPORT_FONT_FAMILY = 1 << (NUMBER_OF_LENGTHS + 2),
SUPPORT_OPACITY = 1 << (NUMBER_OF_LENGTHS + 3)
};
private:
int myMask;
@ -130,6 +141,7 @@ private:
LengthType myLengths[NUMBER_OF_LENGTHS];
ZLTextAlignmentType myAlignmentType;
unsigned char myOpacity;
unsigned char mySupportedFontModifier;
unsigned char myFontModifier;
signed char myFontSizeMag;
@ -333,12 +345,15 @@ private:
inline ZLTextParagraphEntry::ZLTextParagraphEntry() {}
inline ZLTextParagraphEntry::~ZLTextParagraphEntry() {}
inline ZLTextStyleEntry::ZLTextStyleEntry() : myMask(0), mySupportedFontModifier(0), myFontModifier(0) {}
inline ZLTextStyleEntry::ZLTextStyleEntry() : myMask(0), mySupportedFontModifier(0) {}
inline ZLTextStyleEntry::ZLTextStyleEntry(const ZLTextStyleEntry &other) : ZLTextParagraphEntry(), myMask(0), mySupportedFontModifier(0) { apply(other); }
inline ZLTextStyleEntry::~ZLTextStyleEntry() {}
inline ZLTextStyleEntry::Metrics::Metrics(int fontSize, int fontXHeight, int fullWidth, int fullHeight) : FontSize(fontSize), FontXHeight(fontXHeight), FullWidth(fullWidth), FullHeight(fullHeight) {}
inline bool ZLTextStyleEntry::isEmpty() const { return myMask == 0 && mySupportedFontModifier == 0; }
inline ZLTextStyleEntry &ZLTextStyleEntry::operator = (const ZLTextStyleEntry &other) { reset(); apply(other); return *this; }
inline bool ZLTextStyleEntry::lengthSupported(Length name) const { return (myMask & (1 << name)) != 0; }
inline void ZLTextStyleEntry::setLength(Length name, short length, SizeUnit unit) {
@ -347,7 +362,11 @@ inline void ZLTextStyleEntry::setLength(Length name, short length, SizeUnit unit
myMask |= 1 << name;
}
inline bool ZLTextStyleEntry::alignmentTypeSupported() const { return (myMask & SUPPORT_ALIGNMENT_TYPE) == SUPPORT_ALIGNMENT_TYPE; }
inline bool ZLTextStyleEntry::opacitySupported() const { return (myMask & SUPPORT_OPACITY) != 0; }
inline unsigned char ZLTextStyleEntry::opacity() const { return myOpacity; }
inline void ZLTextStyleEntry::setOpacity(unsigned char opacity) { myOpacity = opacity; myMask |= SUPPORT_OPACITY; }
inline bool ZLTextStyleEntry::alignmentTypeSupported() const { return (myMask & SUPPORT_ALIGNMENT_TYPE) != 0; }
inline ZLTextAlignmentType ZLTextStyleEntry::alignmentType() const { return myAlignmentType; }
inline void ZLTextStyleEntry::setAlignmentType(ZLTextAlignmentType alignmentType) { myAlignmentType = alignmentType; myMask |= SUPPORT_ALIGNMENT_TYPE; }