ZLTextWordCursor ZLTextAreaController::findStart(const ZLTextWordCursor &end, SizeUnit unit, int size) { ZLTextWordCursor start = end; size -= paragraphHeight(start, true, unit); bool positionChanged = !start.isStartOfParagraph(); start.moveToParagraphStart(); while (size > 0) { if (positionChanged && start.paragraphCursor().isEndOfSection()) { break; } if (!start.previousParagraph()) { break; } if (!start.paragraphCursor().isEndOfSection()) { positionChanged = true; } size -= paragraphHeight(start, false, unit); } skip(start, unit, -size); if (unit != LINE_UNIT) { bool sameStart = start == end; if (!sameStart && start.isEndOfParagraph() && end.isStartOfParagraph()) { ZLTextWordCursor startCopy = start; startCopy.nextParagraph(); sameStart = startCopy == end; } if (sameStart) { start = findStart(end, LINE_UNIT, 1); } } return start; }
size_t ZLTextView::PositionIndicator::sizeOfTextBeforeCursor(const ZLTextWordCursor &cursor) const { const size_t paragraphIndex = cursor.paragraphCursor().index(); const size_t paragraphLength = cursor.paragraphCursor().paragraphLength(); if (paragraphLength == 0) { return sizeOfTextBeforeParagraph(paragraphIndex); } else { return sizeOfTextBeforeParagraph(paragraphIndex) + muldiv(sizeOfParagraph(paragraphIndex), cursor.elementIndex(), paragraphLength); } }
bool ScrollToHomeAction::isEnabled() const { if (!isVisible()) { return false; } ZLTextWordCursor cursor = FBReader::Instance().bookTextView().textArea().startCursor(); return cursor.isNull() || !cursor.isStartOfParagraph() || !cursor.paragraphCursor().isFirst(); }
ZLTextWordCursor ZLTextAreaController::buildInfos(const ZLTextWordCursor &start) { myArea.myLineInfos.clear(); ZLTextWordCursor cursor = start; int textHeight = myArea.height(); int counter = 0; do { ZLTextWordCursor paragraphEnd = cursor; paragraphEnd.moveToParagraphEnd(); ZLTextWordCursor paragraphStart = cursor; paragraphStart.moveToParagraphStart(); ZLTextArea::Style style(myArea, myArea.myProperties.baseStyle()); style.applyControls(paragraphStart, cursor); ZLTextLineInfoPtr info = new ZLTextLineInfo(cursor, style.textStyle(), style.bidiLevel()); while (!info->End.isEndOfParagraph()) { info = myArea.processTextLine(style, info->End, paragraphEnd); textHeight -= info->Height + info->Descent; if ((textHeight < 0) && (counter > 0)) { break; } textHeight -= info->VSpaceAfter; cursor = info->End; myArea.myLineInfos.push_back(info); if (textHeight < 0) { break; } ++counter; } } while (cursor.isEndOfParagraph() && cursor.nextParagraph() && !cursor.paragraphCursor().isEndOfSection() && (textHeight >= 0)); return cursor; }
std::vector<size_t>::const_iterator ZLTextView::nextBreakIterator() const { ZLTextWordCursor cursor = endCursor(); if (cursor.isNull()) { cursor = startCursor(); } if (cursor.isNull()) { return myTextBreaks.begin(); } return std::lower_bound(myTextBreaks.begin(), myTextBreaks.end(), cursor.paragraphCursor().index()); }
bool strongContains(const ZLTextSelectionModel::Range &range, const ZLTextWordCursor &cursor) { const int pn = cursor.paragraphCursor().index(); const int wn = cursor.elementIndex(); return ((range.first.ParagraphIndex < pn) || ((range.first.ParagraphIndex == pn) && (range.first.ElementIndex < wn))) && ((range.second.ParagraphIndex > pn) || ((range.second.ParagraphIndex == pn) && (range.second.ElementIndex > wn))); }
void IfEmptyWordList() { if (acwlistcount == 0) { ZLTextWordCursor cr = is_footnote_mode() ? footview->textArea().startCursor() : bookview->textArea().startCursor(); int paragraph = !cr.isNull() ? cr.paragraphCursor().index() : 0; if (acwlistcount + 3 >= acwlistsize) { acwlistsize = acwlistsize + (acwlistsize >> 1) + 64; acwordlist = (SynWordList *)realloc(acwordlist, acwlistsize * sizeof(SynWordList)); }
int ZLTextSelectionModel::charIndex(const ZLTextElementArea &area, int x) { myView.myStyle.setTextStyle(area.Style, area.BidiLevel); ZLTextWordCursor cursor = myView.startCursor(); cursor.moveToParagraph(area.ParagraphIndex); const ZLTextWord &word = (const ZLTextWord&)cursor.paragraphCursor()[area.ElementIndex]; const bool mainDir = area.BidiLevel % 2 == myView.myStyle.baseBidiLevel() % 2; const int deltaX = mainDir ? x - area.XStart : area.XEnd - x; const int len = area.Length; const int start = area.StartCharIndex; int diff = deltaX; int previousDiff = diff; int index; for (index = 0; (index < len) && (diff > 0); ++index) { previousDiff = diff; diff = deltaX - myView.myStyle.wordWidth(word, start, index + 1); } if (previousDiff + diff < 0) { --index; } return start + index; }
int ZLTextSelectionModel::charIndex(const ZLTextElementRectangle &rectangle, int x) { int x1 = x - myArea.hOffset(); ZLTextArea::Style style(myArea, rectangle.Style); style.setTextStyle(rectangle.Style, rectangle.BidiLevel); ZLTextWordCursor cursor = myArea.startCursor(); cursor.moveToParagraph(rectangle.ParagraphIndex); const ZLTextWord &word = (const ZLTextWord&)cursor.paragraphCursor()[rectangle.ElementIndex]; const bool mainDir = rectangle.BidiLevel % 2 == (myArea.isRtl() ? 1 : 0); const int deltaX = mainDir ? x1 - rectangle.XStart : rectangle.XEnd - x1; const int len = rectangle.Length; const int start = rectangle.StartCharIndex; int diff = deltaX; int previousDiff = diff; int index; for (index = 0; (index < len) && (diff > 0); ++index) { previousDiff = diff; diff = deltaX - style.wordWidth(word, start, index + 1); } if (previousDiff + diff < 0) { --index; } return start + index; }
BookTextView::Position BookTextView::cursorPosition(const ZLTextWordCursor &cursor) const { return Position(cursor.paragraphCursor().index(), cursor.elementIndex(), cursor.charIndex()); }
BookTextView::Position BookTextView::cursorPosition(const ZLTextWordCursor &cursor) const { return Position(cursor.paragraphCursor().index(), cursor.wordNumber()); }
bool ZLTextSelectionModel::selectWord(int x, int y) { clear(); const ZLTextElementRectangle *rectangle = myArea.elementByCoordinates(x, y); if (rectangle == 0) { return false; } int startIndex = 0; int endIndex = 1; switch (rectangle->Kind) { default: return false; case ZLTextElement::IMAGE_ELEMENT: break; case ZLTextElement::WORD_ELEMENT: { ZLTextWordCursor cursor = myArea.startCursor(); cursor.moveToParagraph(rectangle->ParagraphIndex); const ZLTextWord &word = (const ZLTextWord&)cursor.paragraphCursor()[rectangle->ElementIndex]; ZLUnicodeUtil::Ucs4String ucs4string; ZLUnicodeUtil::utf8ToUcs4(ucs4string, word.Data, word.Size); startIndex = charIndex(*rectangle, x); if (startIndex == word.Length) { --startIndex; } endIndex = startIndex + 1; ZLUnicodeUtil::Ucs4Char ch = ucs4string[startIndex]; if (ZLUnicodeUtil::isLetter(ch) || (('0' <= ch) && (ch <= '9'))) { while (--startIndex >= 0) { ch = ucs4string[startIndex]; if (!ZLUnicodeUtil::isLetter(ch) && ((ch < '0') || (ch > '9'))) { break; } } ++startIndex; while (++endIndex <= word.Length) { ch = ucs4string[endIndex - 1]; if (!ZLUnicodeUtil::isLetter(ch) && ((ch < '0') || (ch > '9'))) { break; } } --endIndex; } } } myFirstBound.Before.Exists = true; myFirstBound.Before.ParagraphIndex = rectangle->ParagraphIndex; myFirstBound.Before.ElementIndex = rectangle->ElementIndex; myFirstBound.Before.CharIndex = startIndex; myFirstBound.After = myFirstBound.Before; mySecondBound.Before = myFirstBound.Before; mySecondBound.Before.CharIndex = endIndex; mySecondBound.After = mySecondBound.Before; myIsEmpty = false; myTextIsUpToDate = false; myRangeVectorIsUpToDate = false; copySelectionToClipboard(ZLDialogManager::CLIPBOARD_SELECTION); return true; }
ZLTextLineInfoPtr ZLTextView::processTextLine(const ZLTextWordCursor &start, const ZLTextWordCursor &end) { const bool useHyphenator = ZLTextStyleCollection::instance().baseStyle().AutoHyphenationOption.value(); ZLTextLineInfoPtr infoPtr = new ZLTextLineInfo(start, myStyle.textStyle()); std::set<ZLTextLineInfoPtr>::const_iterator it = myLineInfoCache.find(infoPtr); if (it != myLineInfoCache.end()) { const ZLTextLineInfoPtr &storedInfo = *it; myStyle.applyControls(storedInfo->Start, storedInfo->End); return storedInfo; } ZLTextLineInfo &info = *infoPtr; ZLTextWordCursor current = start; const ZLTextParagraphCursor ¶graphCursor = current.paragraphCursor(); const bool isFirstLine = current.isStartOfParagraph(); if (paragraphCursor.paragraph().kind() == ZLTextParagraph::TREE_PARAGRAPH) { info.NodeInfo = new ZLTextTreeNodeInfo(); ZLTextTreeNodeInfo &nodeInfo = *info.NodeInfo; const ZLTextTreeParagraph &treeParagraph = (const ZLTextTreeParagraph&)paragraphCursor.paragraph(); nodeInfo.IsLeaf = treeParagraph.children().empty(); nodeInfo.IsOpen = treeParagraph.isOpen(); nodeInfo.IsFirstLine = isFirstLine; nodeInfo.ParagraphNumber = paragraphCursor.index(); nodeInfo.VerticalLinesStack.reserve(treeParagraph.depth() - 1); if (treeParagraph.depth() > 1) { const ZLTextTreeParagraph *ctp = treeParagraph.parent(); nodeInfo.VerticalLinesStack.push_back(ctp->children().back() != &treeParagraph); for (int i = 1; i < treeParagraph.depth() - 1; ++i) { const ZLTextTreeParagraph *parent = ctp->parent(); nodeInfo.VerticalLinesStack.push_back(ctp != parent->children().back()); ctp = parent; } } } if (isFirstLine) { ZLTextElement::Kind elementKind = paragraphCursor[current.wordNumber()].kind(); while ((elementKind == ZLTextElement::CONTROL_ELEMENT) || (elementKind == ZLTextElement::FORCED_CONTROL_ELEMENT)) { const ZLTextElement &element = paragraphCursor[current.wordNumber()]; switch (elementKind) { case ZLTextElement::CONTROL_ELEMENT: myStyle.applyControl((const ZLTextControlElement&)element); break; case ZLTextElement::FORCED_CONTROL_ELEMENT: myStyle.applyControl((const ZLTextForcedControlElement&)element); break; default: break; } current.nextWord(); if (current.equalWordNumber(end)) { break; } elementKind = paragraphCursor[current.wordNumber()].kind(); } info.StartStyle = myStyle.textStyle(); info.RealStart = current; } ZLTextStylePtr storedStyle = myStyle.textStyle(); info.LeftIndent = myStyle.textStyle()->leftIndent(); if (isFirstLine) { info.LeftIndent += myStyle.textStyle()->firstLineIndentDelta(); } if (!info.NodeInfo.isNull()) { info.LeftIndent += (myStyle.context().stringHeight() + 2) / 3 * 4 * (info.NodeInfo->VerticalLinesStack.size() + 1); } info.Width = info.LeftIndent; if (info.RealStart.equalWordNumber(end)) { info.End = info.RealStart; return infoPtr; } ZLTextPartialInfo newInfo(info, current); bool allowBreakAtNBSpace = true; const int maxWidth = viewWidth() - myStyle.textStyle()->rightIndent(); bool wordOccured = false; int lastSpaceWidth = 0; int removeLastSpace = false; ZLTextElement::Kind elementKind = paragraphCursor[newInfo.End.wordNumber()].kind(); bool breakedAtFirstWord = false; do { const ZLTextElement &element = paragraphCursor[newInfo.End.wordNumber()]; newInfo.Width += myStyle.elementWidth(element, newInfo.End.charNumber()); newInfo.Height = std::max(newInfo.Height, myStyle.elementHeight(element)); newInfo.Descent = std::max(newInfo.Descent, myStyle.elementDescent(element)); switch (elementKind) { case ZLTextElement::CONTROL_ELEMENT: myStyle.applyControl((const ZLTextControlElement&)element); break; case ZLTextElement::FORCED_CONTROL_ELEMENT: myStyle.applyControl((const ZLTextForcedControlElement&)element); break; case ZLTextElement::WORD_ELEMENT: case ZLTextElement::IMAGE_ELEMENT: wordOccured = true; newInfo.IsVisible = true; break; case ZLTextElement::HSPACE_ELEMENT: case ZLTextElement::NB_HSPACE_ELEMENT: if (wordOccured) { wordOccured = false; ++newInfo.SpaceCounter; lastSpaceWidth = myStyle.context().spaceWidth(); newInfo.Width += lastSpaceWidth; } break; case ZLTextElement::EMPTY_LINE_ELEMENT: newInfo.IsVisible = true; default: break; } if (newInfo.Width > maxWidth) { if (!info.End.equalWordNumber(start)) { break; } if (useHyphenator && myStyle.textStyle()->allowHyphenations() && (elementKind == ZLTextElement::WORD_ELEMENT)) { breakedAtFirstWord = true; break; } } ZLTextElement::Kind previousKind = elementKind; newInfo.End.nextWord(); bool allowBreak = newInfo.End.equalWordNumber(end); bool nbspaceBreak = false; if (!allowBreak) { elementKind = paragraphCursor[newInfo.End.wordNumber()].kind(); if (elementKind == ZLTextElement::NB_HSPACE_ELEMENT) { if (allowBreakAtNBSpace) { allowBreak = true; nbspaceBreak = true; } } else { allowBreak = ((elementKind != ZLTextElement::WORD_ELEMENT) || (previousKind == ZLTextElement::WORD_ELEMENT)) && (elementKind != ZLTextElement::IMAGE_ELEMENT) && (elementKind != ZLTextElement::CONTROL_ELEMENT); } } if (allowBreak) { newInfo.setTo(info); allowBreakAtNBSpace = nbspaceBreak; storedStyle = myStyle.textStyle(); removeLastSpace = !wordOccured && (info.SpaceCounter > 0); } } while (!newInfo.End.equalWordNumber(end)); if (!newInfo.End.equalWordNumber(end) && useHyphenator && myStyle.textStyle()->allowHyphenations()) { const ZLTextElement &element = paragraphCursor[newInfo.End.wordNumber()]; if (element.kind() == ZLTextElement::WORD_ELEMENT) { const int startCharNumber = newInfo.End.charNumber(); newInfo.Width -= myStyle.elementWidth(element, startCharNumber); const ZLTextWord &word = (ZLTextWord&)element; int spaceLeft = maxWidth - newInfo.Width; if (breakedAtFirstWord || ((word.Length > 3) && (spaceLeft > 2 * myStyle.context().spaceWidth()))) { ZLUnicodeUtil::Ucs2String ucs2string; ZLUnicodeUtil::utf8ToUcs2(ucs2string, word.Data, word.Size); ZLTextHyphenationInfo hyphenationInfo = ZLTextHyphenator::instance().info(word); int hyphenationPosition = word.Length - 1; int subwordWidth = 0; for (; hyphenationPosition > startCharNumber; --hyphenationPosition) { if (hyphenationInfo.isHyphenationPossible(hyphenationPosition)) { subwordWidth = myStyle.wordWidth(word, startCharNumber, hyphenationPosition - startCharNumber, ucs2string[hyphenationPosition - 1] != '-'); if (subwordWidth <= spaceLeft) { break; } } } if ((hyphenationPosition == startCharNumber) && (info.End.wordNumber() <= info.RealStart.wordNumber())) { hyphenationPosition = word.Length; subwordWidth = myStyle.elementWidth(element, startCharNumber); } if (hyphenationPosition > startCharNumber) { newInfo.Width += subwordWidth; newInfo.setTo(info); storedStyle = myStyle.textStyle(); removeLastSpace = false; info.End.setCharNumber(hyphenationPosition); } } } } if (removeLastSpace) { info.Width -= lastSpaceWidth; --info.SpaceCounter; } myStyle.setTextStyle(storedStyle); if (isFirstLine) { info.Height += info.StartStyle->spaceBefore(); } if (info.End.isEndOfParagraph()) { info.VSpaceAfter = myStyle.textStyle()->spaceAfter(); } if (!info.End.equalWordNumber(end) || end.isEndOfParagraph()) { myLineInfoCache.insert(infoPtr); } return infoPtr; }
bool ZLTextSelectionModel::selectWord(const ZLTextElementArea & area, int x, int y) { if (ZLTextElementArea::RangeChecker(x, y)(area)) { int startIndex = 0; int endIndex = 1; switch (area.Kind) { default: return false; case ZLTextElement::IMAGE_ELEMENT: break; case ZLTextElement::WORD_ELEMENT: { ZLTextWordCursor cursor = myView.startCursor(); cursor.moveToParagraph(area.ParagraphIndex); const ZLTextWord &word = (const ZLTextWord&)cursor.paragraphCursor()[area.ElementIndex]; ZLUnicodeUtil::Ucs4String ucs4string; ZLUnicodeUtil::utf8ToUcs4(ucs4string, word.Data, word.Size); startIndex = charIndex(area, x); if (startIndex == word.Length) { --startIndex; } endIndex = startIndex + 1; ZLUnicodeUtil::Ucs4Char ch = ucs4string[startIndex]; // TODO: Get rid of non-letter character. while (!ZLUnicodeUtil::isLetter(ch) && ++startIndex < word.Length) { ch = ucs4string[startIndex]; } if (startIndex >= word.Length) { break; } if (ZLUnicodeUtil::isLetter(ch) || (('0' <= ch) && (ch <= '9'))) { while (--startIndex >= 0) { ch = ucs4string[startIndex]; if (!ZLUnicodeUtil::isLetter(ch) && ((ch < '0') || (ch > '9'))) { break; } } ++startIndex; while (++endIndex <= word.Length) { ch = ucs4string[endIndex - 1]; if (!ZLUnicodeUtil::isLetter(ch) && ((ch < '0') || (ch > '9'))) { break; } } --endIndex; } } } myFirstBound.Before.Exists = true; myFirstBound.Before.ParagraphIndex = area.ParagraphIndex; myFirstBound.Before.ElementIndex = area.ElementIndex; myFirstBound.Before.CharIndex = startIndex; myFirstBound.After = myFirstBound.Before; mySecondBound.Before = myFirstBound.Before; mySecondBound.Before.CharIndex = endIndex; mySecondBound.After = mySecondBound.Before; myIsEmpty = false; myTextIsUpToDate = false; myRangeVectorIsUpToDate = false; myDoUpdate = false; myArea = area; return true; } return false; }