+ double x = row.x;
+ Bidi bidi;
+ bidi.computeTables(par, buffer, row);
+
+ pos_type const row_pos = row.pos();
+ pos_type const end = row.endpos();
+ // Spaces at logical line breaks in bidi text must be skipped during
+ // cursor positioning. However, they may appear visually in the middle
+ // of a row; they must be skipped, wherever they are...
+ // * logically "abc_[HEBREW_\nHEBREW]"
+ // * visually "abc_[_WERBEH\nWERBEH]"
+ pos_type skipped_sep_vpos = -1;
+
+ if (end <= row_pos)
+ cursor_vpos = row_pos;
+ else if (ppos >= end)
+ cursor_vpos = text_->isRTL(buffer, par) ? row_pos : end;
+ else if (ppos > row_pos && ppos >= end)
+ // Place cursor after char at (logical) position pos - 1
+ cursor_vpos = (bidi.level(ppos - 1) % 2 == 0)
+ ? bidi.log2vis(ppos - 1) + 1 : bidi.log2vis(ppos - 1);
+ else
+ // Place cursor before char at (logical) position ppos
+ cursor_vpos = (bidi.level(ppos) % 2 == 0)
+ ? bidi.log2vis(ppos) : bidi.log2vis(ppos) + 1;
+
+ pos_type body_pos = par.beginOfBody();
+ if (body_pos > 0 &&
+ (body_pos > end || !par.isLineSeparator(body_pos - 1)))
+ body_pos = 0;
+
+ // Use font span to speed things up, see below
+ FontSpan font_span;
+ Font font;
+
+ // If the last logical character is a separator, skip it, unless
+ // it's in the last row of a paragraph; see skipped_sep_vpos declaration
+ if (end > 0 && end < par.size() && par.isSeparator(end - 1))
+ skipped_sep_vpos = bidi.log2vis(end - 1);
+
+ for (pos_type vpos = row_pos; vpos < cursor_vpos; ++vpos) {
+ // Skip the separator which is at the logical end of the row
+ if (vpos == skipped_sep_vpos)
+ continue;
+ pos_type pos = bidi.vis2log(vpos);
+ if (body_pos > 0 && pos == body_pos - 1) {
+ FontMetrics const & labelfm = theFontMetrics(
+ text_->getLabelFont(buffer, par));
+ x += row.label_hfill + labelfm.width(par.layout()->labelsep);
+ if (par.isLineSeparator(body_pos - 1))
+ x -= singleWidth(pit, body_pos - 1);
+ }
+
+ // Use font span to speed things up, see above
+ if (pos < font_span.first || pos > font_span.last) {
+ font_span = par.fontSpan(pos);
+ font = getDisplayFont(pit, pos);
+ }
+
+ x += pm.singleWidth(pos, font);
+
+ if (par.isSeparator(pos) && pos >= body_pos)
+ x += row.separator;
+ }
+
+ // see correction above
+ if (boundary_correction) {
+ if (isRTL(sl, boundary))
+ x -= singleWidth(pit, ppos);
+ else
+ x += singleWidth(pit, ppos);
+ }
+
+ return int(x);
+}
+
+
+int TextMetrics::cursorY(CursorSlice const & sl, bool boundary) const
+{
+ //lyxerr << "TextMetrics::cursorY: boundary: " << boundary << endl;
+ ParagraphMetrics const & pm = par_metrics_[sl.pit()];
+ if (pm.rows().empty())
+ return 0;
+
+ int h = 0;
+ h -= par_metrics_[0].rows()[0].ascent();
+ for (pit_type pit = 0; pit < sl.pit(); ++pit) {
+ h += par_metrics_[pit].height();
+ }
+ int pos = sl.pos();
+ if (pos && boundary)
+ --pos;
+ size_t const rend = pm.pos2row(pos);
+ for (size_t rit = 0; rit != rend; ++rit)
+ h += pm.rows()[rit].height();
+ h += pm.rows()[rend].ascent();
+ return h;
+}
+
+
+void TextMetrics::cursorPrevious(Cursor & cur)
+{
+ pos_type cpos = cur.pos();
+ pit_type cpar = cur.pit();
+
+ int x = cur.x_target();
+ setCursorFromCoordinates(cur, x, 0);
+ cur.dispatch(FuncRequest(cur.selection()? LFUN_UP_SELECT: LFUN_UP));
+
+ if (cpar == cur.pit() && cpos == cur.pos())
+ // we have a row which is taller than the workarea. The
+ // simplest solution is to move to the previous row instead.
+ cur.dispatch(FuncRequest(cur.selection()? LFUN_UP_SELECT: LFUN_UP));
+
+ cur.finishUndo();
+ cur.updateFlags(Update::Force | Update::FitCursor);
+}
+
+
+void TextMetrics::cursorNext(Cursor & cur)
+{
+ pos_type cpos = cur.pos();
+ pit_type cpar = cur.pit();
+
+ int x = cur.x_target();
+ setCursorFromCoordinates(cur, x, cur.bv().workHeight() - 1);
+ cur.dispatch(FuncRequest(cur.selection()? LFUN_DOWN_SELECT: LFUN_DOWN));
+
+ if (cpar == cur.pit() && cpos == cur.pos())
+ // we have a row which is taller than the workarea. The
+ // simplest solution is to move to the next row instead.
+ cur.dispatch(
+ FuncRequest(cur.selection()? LFUN_DOWN_SELECT: LFUN_DOWN));
+
+ cur.finishUndo();
+ cur.updateFlags(Update::Force | Update::FitCursor);
+}
+
+
+// the cursor set functions have a special mechanism. When they
+// realize you left an empty paragraph, they will delete it.
+
+bool TextMetrics::cursorHome(Cursor & cur)
+{
+ BOOST_ASSERT(text_ == cur.text());
+ ParagraphMetrics const & pm = par_metrics_[cur.pit()];
+ Row const & row = pm.getRow(cur.pos(),cur.boundary());
+ return text_->setCursor(cur, cur.pit(), row.pos());
+}
+
+
+bool TextMetrics::cursorEnd(Cursor & cur)
+{
+ BOOST_ASSERT(text_ == cur.text());
+ // if not on the last row of the par, put the cursor before
+ // the final space exept if I have a spanning inset or one string
+ // is so long that we force a break.
+ pos_type end = cur.textRow().endpos();
+ if (end == 0)
+ // empty text, end-1 is no valid position
+ return false;
+ bool boundary = false;
+ if (end != cur.lastpos()) {
+ if (!cur.paragraph().isLineSeparator(end-1)
+ && !cur.paragraph().isNewline(end-1))
+ boundary = true;
+ else
+ --end;
+ }
+ return text_->setCursor(cur, cur.pit(), end, true, boundary);
+}
+
+
+void TextMetrics::deleteLineForward(Cursor & cur)
+{
+ BOOST_ASSERT(text_ == cur.text());
+ if (cur.lastpos() == 0) {
+ // Paragraph is empty, so we just go forward
+ text_->cursorForward(cur);
+ } else {
+ cur.resetAnchor();
+ cur.selection() = true; // to avoid deletion
+ cursorEnd(cur);
+ cur.setSelection();
+ // What is this test for ??? (JMarc)
+ if (!cur.selection())
+ text_->deleteWordForward(cur);
+ else
+ cap::cutSelection(cur, true, false);
+ cur.checkBufferStructure();
+ }
+}
+
+
+bool TextMetrics::isLastRow(pit_type pit, Row const & row) const
+{
+ ParagraphList const & pars = text_->paragraphs();
+ return row.endpos() >= pars[pit].size()
+ && pit + 1 == pit_type(pars.size());
+}
+
+
+bool TextMetrics::isFirstRow(pit_type pit, Row const & row) const
+{
+ return row.pos() == 0 && pit == 0;
+}
+
+
+int TextMetrics::leftMargin(int max_width, pit_type pit) const
+{
+ BOOST_ASSERT(pit >= 0);
+ BOOST_ASSERT(pit < int(text_->paragraphs().size()));
+ return leftMargin(max_width, pit, text_->paragraphs()[pit].size());
+}
+
+
+int TextMetrics::leftMargin(int max_width,
+ pit_type const pit, pos_type const pos) const
+{
+ ParagraphList const & pars = text_->paragraphs();
+
+ BOOST_ASSERT(pit >= 0);
+ BOOST_ASSERT(pit < int(pars.size()));
+ Paragraph const & par = pars[pit];
+ BOOST_ASSERT(pos >= 0);
+ BOOST_ASSERT(pos <= par.size());
+ Buffer const & buffer = bv_->buffer();
+ //lyxerr << "TextMetrics::leftMargin: pit: " << pit << " pos: " << pos << endl;
+ TextClass const & tclass = buffer.params().getTextClass();
+ LayoutPtr const & layout = par.layout();
+
+ docstring parindent = layout->parindent;
+
+ int l_margin = 0;
+
+ if (text_->isMainText(buffer))
+ l_margin += bv_->leftMargin();
+
+ l_margin += theFontMetrics(buffer.params().getFont()).signedWidth(
+ tclass.leftmargin());
+
+ if (par.getDepth() != 0) {
+ // find the next level paragraph
+ pit_type newpar = outerHook(pit, pars);
+ if (newpar != pit_type(pars.size())) {
+ if (pars[newpar].layout()->isEnvironment()) {
+ l_margin = leftMargin(max_width, newpar);
+ }
+ //FIXME Should this check for emptyLayout() as well?
+ if (par.layout() == tclass.defaultLayout()) {
+ if (pars[newpar].params().noindent())
+ parindent.erase();
+ else
+ parindent = pars[newpar].layout()->parindent;
+ }
+ }
+ }
+
+ // This happens after sections in standard classes. The 1.3.x
+ // code compared depths too, but it does not seem necessary
+ // (JMarc)
+ if (par.layout() == tclass.defaultLayout()
+ && pit > 0 && pars[pit - 1].layout()->nextnoindent)
+ parindent.erase();
+
+ FontInfo const labelfont = text_->getLabelFont(buffer, par);
+ FontMetrics const & labelfont_metrics = theFontMetrics(labelfont);
+
+ switch (layout->margintype) {
+ case MARGIN_DYNAMIC:
+ if (!layout->leftmargin.empty()) {
+ l_margin += theFontMetrics(buffer.params().getFont()).signedWidth(
+ layout->leftmargin);
+ }
+ if (!par.getLabelstring().empty()) {
+ l_margin += labelfont_metrics.signedWidth(layout->labelindent);
+ l_margin += labelfont_metrics.width(par.getLabelstring());
+ l_margin += labelfont_metrics.width(layout->labelsep);
+ }
+ break;
+
+ case MARGIN_MANUAL: {
+ l_margin += labelfont_metrics.signedWidth(layout->labelindent);
+ // The width of an empty par, even with manual label, should be 0
+ if (!par.empty() && pos >= par.beginOfBody()) {
+ if (!par.getLabelWidthString().empty()) {
+ docstring labstr = par.getLabelWidthString();
+ l_margin += labelfont_metrics.width(labstr);
+ l_margin += labelfont_metrics.width(layout->labelsep);
+ }
+ }
+ break;
+ }
+
+ case MARGIN_STATIC: {
+ l_margin += theFontMetrics(buffer.params().getFont()).
+ signedWidth(layout->leftmargin) * 4 / (par.getDepth() + 4);
+ break;
+ }
+
+ case MARGIN_FIRST_DYNAMIC:
+ if (layout->labeltype == LABEL_MANUAL) {
+ if (pos >= par.beginOfBody()) {
+ l_margin += labelfont_metrics.signedWidth(layout->leftmargin);
+ } else {
+ l_margin += labelfont_metrics.signedWidth(layout->labelindent);
+ }
+ } else if (pos != 0
+ // Special case to fix problems with
+ // theorems (JMarc)
+ || (layout->labeltype == LABEL_STATIC
+ && layout->latextype == LATEX_ENVIRONMENT
+ && !isFirstInSequence(pit, pars))) {
+ l_margin += labelfont_metrics.signedWidth(layout->leftmargin);
+ } else if (layout->labeltype != LABEL_TOP_ENVIRONMENT
+ && layout->labeltype != LABEL_BIBLIO
+ && layout->labeltype !=
+ LABEL_CENTERED_TOP_ENVIRONMENT) {
+ l_margin += labelfont_metrics.signedWidth(layout->labelindent);
+ l_margin += labelfont_metrics.width(layout->labelsep);
+ l_margin += labelfont_metrics.width(par.getLabelstring());
+ }
+ break;
+
+ case MARGIN_RIGHT_ADDRESS_BOX: {
+#if 0
+ // ok, a terrible hack. The left margin depends on the widest
+ // row in this paragraph.
+ RowList::iterator rit = par.rows().begin();
+ RowList::iterator end = par.rows().end();
+ // FIXME: This is wrong.
+ int minfill = max_width;
+ for ( ; rit != end; ++rit)
+ if (rit->fill() < minfill)
+ minfill = rit->fill();
+ l_margin += theFontMetrics(params.getFont()).signedWidth(layout->leftmargin);
+ l_margin += minfill;
+#endif
+ // also wrong, but much shorter.
+ l_margin += max_width / 2;
+ break;
+ }
+ }
+
+ if (!par.params().leftIndent().zero())
+ l_margin += par.params().leftIndent().inPixels(max_width);
+
+ LyXAlignment align;
+
+ if (par.params().align() == LYX_ALIGN_LAYOUT)
+ align = layout->align;
+ else
+ align = par.params().align();
+
+ // set the correct parindent
+ if (pos == 0
+ && (layout->labeltype == LABEL_NO_LABEL
+ || layout->labeltype == LABEL_TOP_ENVIRONMENT
+ || layout->labeltype == LABEL_CENTERED_TOP_ENVIRONMENT
+ || (layout->labeltype == LABEL_STATIC
+ && layout->latextype == LATEX_ENVIRONMENT
+ && !isFirstInSequence(pit, pars)))
+ && align == LYX_ALIGN_BLOCK
+ && !par.params().noindent()
+ // in some insets, paragraphs are never indented
+ && !(par.inInset() && par.inInset()->neverIndent(buffer))
+ // display style insets are always centered, omit indentation
+ && !(!par.empty()
+ && par.isInset(pos)
+ && par.getInset(pos)->display())
+ && (par.layout() != tclass.defaultLayout() //should this check emptyLayout()?
+ || buffer.params().paragraph_separation ==
+ BufferParams::PARSEP_INDENT))
+ {
+ l_margin += theFontMetrics(buffer.params().getFont()).signedWidth(
+ parindent);
+ }
+
+ return l_margin;
+}
+
+
+int TextMetrics::singleWidth(pit_type pit, pos_type pos) const
+{