2006-12-29 23:54:48 +00:00
|
|
|
/**
|
2007-04-26 04:41:58 +00:00
|
|
|
* \file src/TextMetrics.cpp
|
2006-12-29 23:54:48 +00:00
|
|
|
* This file is part of LyX, the document processor.
|
|
|
|
* Licence details can be found in the file COPYING.
|
|
|
|
*
|
|
|
|
* \author Asger Alstrup
|
2008-11-14 15:58:50 +00:00
|
|
|
* \author Lars Gullik Bjønnes
|
2006-12-29 23:54:48 +00:00
|
|
|
* \author Jean-Marc Lasgouttes
|
|
|
|
* \author John Levon
|
2008-11-14 15:58:50 +00:00
|
|
|
* \author André Pönitz
|
2006-12-29 23:54:48 +00:00
|
|
|
* \author Dekel Tsur
|
2008-11-14 15:58:50 +00:00
|
|
|
* \author Jürgen Vigna
|
2006-12-29 23:54:48 +00:00
|
|
|
* \author Abdelrazak Younes
|
|
|
|
*
|
|
|
|
* Full author contact details are available in file CREDITS.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
|
|
|
#include "TextMetrics.h"
|
|
|
|
|
2007-04-26 04:41:58 +00:00
|
|
|
#include "Buffer.h"
|
|
|
|
#include "BufferParams.h"
|
2006-12-29 23:54:48 +00:00
|
|
|
#include "BufferView.h"
|
2007-11-02 20:41:04 +00:00
|
|
|
#include "CoordCache.h"
|
2007-11-05 22:54:53 +00:00
|
|
|
#include "Cursor.h"
|
2007-09-02 11:21:33 +00:00
|
|
|
#include "CutAndPaste.h"
|
2007-09-29 20:02:32 +00:00
|
|
|
#include "Layout.h"
|
2007-04-26 04:41:58 +00:00
|
|
|
#include "LyXRC.h"
|
|
|
|
#include "MetricsInfo.h"
|
2006-12-29 23:54:48 +00:00
|
|
|
#include "ParagraphParameters.h"
|
2014-07-25 19:55:08 +00:00
|
|
|
#include "RowPainter.h"
|
2021-02-26 18:05:35 +00:00
|
|
|
#include "Session.h"
|
2007-08-27 22:36:20 +00:00
|
|
|
#include "Text.h"
|
2007-11-07 23:25:08 +00:00
|
|
|
#include "TextClass.h"
|
2007-04-26 04:41:58 +00:00
|
|
|
#include "VSpace.h"
|
2006-12-29 23:54:48 +00:00
|
|
|
|
2009-08-09 16:19:43 +00:00
|
|
|
#include "insets/InsetText.h"
|
|
|
|
|
2020-10-20 08:36:59 +00:00
|
|
|
#include "mathed/MacroTable.h"
|
2007-11-01 11:13:07 +00:00
|
|
|
|
2006-12-29 23:54:48 +00:00
|
|
|
#include "frontends/FontMetrics.h"
|
2017-08-30 16:05:16 +00:00
|
|
|
#include "frontends/NullPainter.h"
|
2006-12-29 23:54:48 +00:00
|
|
|
|
2008-02-18 07:14:42 +00:00
|
|
|
#include "support/debug.h"
|
2008-04-30 08:26:40 +00:00
|
|
|
#include "support/lassert.h"
|
2020-11-12 12:09:36 +00:00
|
|
|
#include "support/Changer.h"
|
2008-03-15 00:22:54 +00:00
|
|
|
|
2016-07-04 18:36:16 +00:00
|
|
|
#include <stdlib.h>
|
2013-07-21 18:22:32 +00:00
|
|
|
#include <cmath>
|
2009-08-10 20:54:22 +00:00
|
|
|
|
2007-12-12 10:16:00 +00:00
|
|
|
using namespace std;
|
2006-12-29 23:54:48 +00:00
|
|
|
|
2008-03-15 00:22:54 +00:00
|
|
|
|
2006-12-29 23:54:48 +00:00
|
|
|
namespace lyx {
|
|
|
|
|
|
|
|
using frontend::FontMetrics;
|
|
|
|
|
2013-07-17 22:25:08 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
|
|
int numberOfLabelHfills(Paragraph const & par, Row const & row)
|
2006-12-29 23:54:48 +00:00
|
|
|
{
|
|
|
|
pos_type last = row.endpos() - 1;
|
|
|
|
pos_type first = row.pos();
|
|
|
|
|
|
|
|
// hfill *DO* count at the beginning of paragraphs!
|
|
|
|
if (first) {
|
|
|
|
while (first < last && par.isHfill(first))
|
|
|
|
++first;
|
|
|
|
}
|
|
|
|
|
|
|
|
last = min(last, par.beginOfBody());
|
|
|
|
int n = 0;
|
|
|
|
for (pos_type p = first; p < last; ++p) {
|
|
|
|
if (par.isHfill(p))
|
|
|
|
++n;
|
|
|
|
}
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
2015-11-27 10:50:26 +00:00
|
|
|
// FIXME: this needs to be rewritten, probably by merging it into some
|
|
|
|
// code that, besides counting, sets the active status of the space
|
|
|
|
// inset in the row element.
|
|
|
|
int numberOfHfills(Row const & row, ParagraphMetrics const & pm,
|
|
|
|
pos_type const body_pos)
|
2006-12-29 23:54:48 +00:00
|
|
|
{
|
|
|
|
int n = 0;
|
2013-07-17 22:25:08 +00:00
|
|
|
Row::const_iterator cit = row.begin();
|
|
|
|
Row::const_iterator const end = row.end();
|
|
|
|
for ( ; cit != end ; ++cit)
|
|
|
|
if (cit->pos >= body_pos
|
2015-11-27 10:50:26 +00:00
|
|
|
&& cit->inset && pm.hfillExpansion(row, cit->pos))
|
2006-12-29 23:54:48 +00:00
|
|
|
++n;
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
2008-03-15 00:22:54 +00:00
|
|
|
|
2017-07-23 11:11:54 +00:00
|
|
|
} // namespace
|
2013-07-17 22:25:08 +00:00
|
|
|
|
2008-03-15 00:22:54 +00:00
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// TextMetrics
|
|
|
|
//
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
|
2006-12-29 23:54:48 +00:00
|
|
|
|
2007-04-29 23:33:02 +00:00
|
|
|
TextMetrics::TextMetrics(BufferView * bv, Text * text)
|
2020-07-14 21:28:43 +00:00
|
|
|
: bv_(bv), text_(text), dim_(bv_->workWidth(), 10, 10),
|
|
|
|
max_width_(dim_.wid), tight_(false)
|
|
|
|
{}
|
2006-12-29 23:54:48 +00:00
|
|
|
|
|
|
|
|
2008-02-27 23:03:26 +00:00
|
|
|
bool TextMetrics::contains(pit_type pit) const
|
2007-09-11 16:04:10 +00:00
|
|
|
{
|
|
|
|
return par_metrics_.find(pit) != par_metrics_.end();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-07-12 22:00:36 +00:00
|
|
|
pair<pit_type, ParagraphMetrics const *> TextMetrics::first() const
|
2007-09-15 12:28:41 +00:00
|
|
|
{
|
2020-07-12 22:00:36 +00:00
|
|
|
ParMetricsCache::const_iterator it = par_metrics_.begin();
|
|
|
|
return make_pair(it->first, &it->second);
|
2007-09-15 12:28:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-07-12 22:00:36 +00:00
|
|
|
pair<pit_type, ParagraphMetrics const *> TextMetrics::last() const
|
2007-09-15 12:28:41 +00:00
|
|
|
{
|
2020-07-12 22:00:36 +00:00
|
|
|
LBUFERR(!par_metrics_.empty());
|
|
|
|
ParMetricsCache::const_reverse_iterator it = par_metrics_.rbegin();
|
|
|
|
return make_pair(it->first, &it->second);
|
2007-09-15 12:28:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-02-25 10:58:50 +00:00
|
|
|
bool TextMetrics::isLastRow(Row const & row) const
|
|
|
|
{
|
|
|
|
ParagraphList const & pars = text_->paragraphs();
|
|
|
|
return row.endpos() >= pars[row.pit()].size()
|
|
|
|
&& row.pit() + 1 == pit_type(pars.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool TextMetrics::isFirstRow(Row const & row) const
|
|
|
|
{
|
|
|
|
return row.pos() == 0 && row.pit() == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-02-21 16:52:36 +00:00
|
|
|
void TextMetrics::setRowChanged(pit_type pit, pos_type pos)
|
|
|
|
{
|
|
|
|
for (auto & pm_pair : par_metrics_)
|
|
|
|
if (pm_pair.first == pit)
|
|
|
|
for (Row & row : pm_pair.second.rows())
|
|
|
|
if (row.pos() == pos)
|
|
|
|
row.changed(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-11-02 20:41:04 +00:00
|
|
|
ParagraphMetrics & TextMetrics::parMetrics(pit_type pit, bool redo)
|
2006-12-29 23:54:48 +00:00
|
|
|
{
|
|
|
|
ParMetricsCache::iterator pmc_it = par_metrics_.find(pit);
|
|
|
|
if (pmc_it == par_metrics_.end()) {
|
|
|
|
pmc_it = par_metrics_.insert(
|
2007-09-11 16:04:10 +00:00
|
|
|
make_pair(pit, ParagraphMetrics(text_->getPar(pit)))).first;
|
2006-12-29 23:54:48 +00:00
|
|
|
}
|
2007-11-02 20:41:04 +00:00
|
|
|
if (pmc_it->second.rows().empty() && redo)
|
2006-12-29 23:54:48 +00:00
|
|
|
redoParagraph(pit);
|
|
|
|
return pmc_it->second;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-02-25 10:58:50 +00:00
|
|
|
ParagraphMetrics const & TextMetrics::parMetrics(pit_type pit) const
|
|
|
|
{
|
|
|
|
return const_cast<TextMetrics *>(this)->parMetrics(pit, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-07-13 22:08:07 +00:00
|
|
|
ParagraphMetrics & TextMetrics::parMetrics(pit_type pit)
|
|
|
|
{
|
|
|
|
return parMetrics(pit, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-02-25 10:58:50 +00:00
|
|
|
void TextMetrics::newParMetricsDown()
|
|
|
|
{
|
|
|
|
pair<pit_type, ParagraphMetrics> const & last = *par_metrics_.rbegin();
|
|
|
|
pit_type const pit = last.first + 1;
|
|
|
|
if (pit == int(text_->paragraphs().size()))
|
|
|
|
return;
|
|
|
|
|
|
|
|
// do it and update its position.
|
|
|
|
redoParagraph(pit);
|
|
|
|
par_metrics_[pit].setPosition(last.second.position()
|
|
|
|
+ last.second.descent() + par_metrics_[pit].ascent());
|
|
|
|
updatePosCache(pit);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void TextMetrics::newParMetricsUp()
|
|
|
|
{
|
|
|
|
pair<pit_type, ParagraphMetrics> const & first = *par_metrics_.begin();
|
|
|
|
if (first.first == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
pit_type const pit = first.first - 1;
|
|
|
|
// do it and update its position.
|
|
|
|
redoParagraph(pit);
|
|
|
|
par_metrics_[pit].setPosition(first.second.position()
|
|
|
|
- first.second.ascent() - par_metrics_[pit].descent());
|
|
|
|
updatePosCache(pit);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-07-14 21:28:43 +00:00
|
|
|
bool TextMetrics::metrics(MetricsInfo const & mi, Dimension & dim, int min_width)
|
2006-12-29 23:54:48 +00:00
|
|
|
{
|
2013-04-27 21:52:55 +00:00
|
|
|
LBUFERR(mi.base.textwidth > 0);
|
2006-12-29 23:54:48 +00:00
|
|
|
max_width_ = mi.base.textwidth;
|
2020-07-14 21:28:43 +00:00
|
|
|
tight_ = mi.tight_insets;
|
2007-09-01 14:49:08 +00:00
|
|
|
// backup old dimension.
|
|
|
|
Dimension const old_dim = dim_;
|
|
|
|
// reset dimension.
|
|
|
|
dim_ = Dimension();
|
2007-09-18 08:52:38 +00:00
|
|
|
dim_.wid = min_width;
|
2007-09-02 18:05:37 +00:00
|
|
|
pit_type const npar = text_->paragraphs().size();
|
2020-07-14 21:28:43 +00:00
|
|
|
if (npar > 1 && !tight_)
|
2008-03-21 11:26:20 +00:00
|
|
|
// If there is more than one row, expand the text to
|
2007-09-01 14:49:08 +00:00
|
|
|
// the full allowable width.
|
|
|
|
dim_.wid = max_width_;
|
|
|
|
|
2007-09-02 09:44:08 +00:00
|
|
|
//lyxerr << "TextMetrics::metrics: width: " << mi.base.textwidth
|
2007-09-01 14:49:08 +00:00
|
|
|
// << " maxWidth: " << max_width_ << "\nfont: " << mi.base.font << endl;
|
2007-05-28 22:27:45 +00:00
|
|
|
|
2006-12-29 23:54:48 +00:00
|
|
|
bool changed = false;
|
2020-05-12 15:42:50 +00:00
|
|
|
int h = 0;
|
2007-09-01 14:49:08 +00:00
|
|
|
for (pit_type pit = 0; pit != npar; ++pit) {
|
2019-01-11 14:55:17 +00:00
|
|
|
// create rows, but do not set alignment yet
|
|
|
|
changed |= redoParagraph(pit, false);
|
2007-08-18 21:10:45 +00:00
|
|
|
ParagraphMetrics const & pm = par_metrics_[pit];
|
2006-12-29 23:54:48 +00:00
|
|
|
h += pm.height();
|
2007-09-01 14:49:08 +00:00
|
|
|
if (dim_.wid < pm.width())
|
|
|
|
dim_.wid = pm.width();
|
2006-12-29 23:54:48 +00:00
|
|
|
}
|
|
|
|
|
2019-01-11 14:55:17 +00:00
|
|
|
// Now set alignment for all rows (the width might not have been known before).
|
|
|
|
for (pit_type pit = 0; pit != npar; ++pit) {
|
|
|
|
ParagraphMetrics & pm = par_metrics_[pit];
|
|
|
|
for (Row & row : pm.rows())
|
|
|
|
setRowAlignment(row, dim_.wid);
|
|
|
|
}
|
|
|
|
|
2007-09-01 14:49:08 +00:00
|
|
|
dim_.asc = par_metrics_[0].ascent();
|
|
|
|
dim_.des = h - dim_.asc;
|
|
|
|
//lyxerr << "dim_.wid " << dim_.wid << endl;
|
|
|
|
//lyxerr << "dim_.asc " << dim_.asc << endl;
|
|
|
|
//lyxerr << "dim_.des " << dim_.des << endl;
|
2006-12-29 23:54:48 +00:00
|
|
|
|
2007-09-01 14:49:08 +00:00
|
|
|
changed |= dim_ != old_dim;
|
|
|
|
dim = dim_;
|
2006-12-29 23:54:48 +00:00
|
|
|
return changed;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-08-30 16:05:16 +00:00
|
|
|
void TextMetrics::updatePosCache(pit_type pit) const
|
|
|
|
{
|
|
|
|
frontend::NullPainter np;
|
|
|
|
PainterInfo pi(bv_, np);
|
|
|
|
drawParagraph(pi, pit, origin_.x_, par_metrics_[pit].position());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-12-29 23:54:48 +00:00
|
|
|
int TextMetrics::rightMargin(ParagraphMetrics const & pm) const
|
|
|
|
{
|
2016-02-28 15:23:43 +00:00
|
|
|
return text_->isMainText() ? pm.rightMargin(*bv_) : 0;
|
2006-12-29 23:54:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int TextMetrics::rightMargin(pit_type const pit) const
|
|
|
|
{
|
2016-02-28 15:23:43 +00:00
|
|
|
return text_->isMainText() ? par_metrics_[pit].rightMargin(*bv_) : 0;
|
2006-12-29 23:54:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-09-02 21:48:49 +00:00
|
|
|
void TextMetrics::applyOuterFont(Font & font) const
|
|
|
|
{
|
2009-12-07 19:06:15 +00:00
|
|
|
FontInfo lf(font_.fontInfo());
|
|
|
|
lf.reduce(bv_->buffer().params().getFont().fontInfo());
|
2014-03-14 13:22:26 +00:00
|
|
|
font.fontInfo().realize(lf);
|
2007-09-02 21:48:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-02-27 23:03:26 +00:00
|
|
|
Font TextMetrics::displayFont(pit_type pit, pos_type pos) const
|
2007-09-02 21:48:49 +00:00
|
|
|
{
|
2013-04-25 21:27:10 +00:00
|
|
|
LASSERT(pos >= 0, { static Font f; return f; });
|
2007-09-02 21:48:49 +00:00
|
|
|
|
2007-09-02 22:28:49 +00:00
|
|
|
ParagraphList const & pars = text_->paragraphs();
|
|
|
|
Paragraph const & par = pars[pit];
|
2008-03-06 21:31:27 +00:00
|
|
|
Layout const & layout = par.layout();
|
2007-09-02 21:48:49 +00:00
|
|
|
Buffer const & buffer = bv_->buffer();
|
|
|
|
// FIXME: broken?
|
|
|
|
BufferParams const & params = buffer.params();
|
|
|
|
pos_type const body_pos = par.beginOfBody();
|
|
|
|
|
|
|
|
// We specialize the 95% common case:
|
|
|
|
if (!par.getDepth()) {
|
|
|
|
Font f = par.getFontSettings(params, pos);
|
2009-08-09 15:29:34 +00:00
|
|
|
if (!text_->isMainText())
|
2007-09-02 21:48:49 +00:00
|
|
|
applyOuterFont(f);
|
2008-03-06 21:31:27 +00:00
|
|
|
bool lab = layout.labeltype == LABEL_MANUAL && pos < body_pos;
|
2007-09-02 23:37:11 +00:00
|
|
|
|
2008-03-06 21:31:27 +00:00
|
|
|
FontInfo const & lf = lab ? layout.labelfont : layout.font;
|
|
|
|
FontInfo rlf = lab ? layout.reslabelfont : layout.resfont;
|
2008-03-21 11:26:20 +00:00
|
|
|
|
2007-09-02 21:48:49 +00:00
|
|
|
// In case the default family has been customized
|
2007-10-28 18:51:54 +00:00
|
|
|
if (lf.family() == INHERIT_FAMILY)
|
|
|
|
rlf.setFamily(params.getFont().fontInfo().family());
|
|
|
|
f.fontInfo().realize(rlf);
|
|
|
|
return f;
|
2007-09-02 21:48:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// The uncommon case need not be optimized as much
|
2008-03-21 11:26:20 +00:00
|
|
|
FontInfo const & layoutfont = pos < body_pos ?
|
2008-03-06 21:31:27 +00:00
|
|
|
layout.labelfont : layout.font;
|
2007-09-02 21:48:49 +00:00
|
|
|
|
|
|
|
Font font = par.getFontSettings(params, pos);
|
2007-10-28 18:51:54 +00:00
|
|
|
font.fontInfo().realize(layoutfont);
|
2007-09-02 21:48:49 +00:00
|
|
|
|
2009-08-09 15:29:34 +00:00
|
|
|
if (!text_->isMainText())
|
2007-09-02 21:48:49 +00:00
|
|
|
applyOuterFont(font);
|
|
|
|
|
|
|
|
// Realize against environment font information
|
|
|
|
// NOTE: the cast to pit_type should be removed when pit_type
|
|
|
|
// changes to a unsigned integer.
|
|
|
|
if (pit < pit_type(pars.size()))
|
2009-08-09 18:35:39 +00:00
|
|
|
font.fontInfo().realize(text_->outerFont(pit).fontInfo());
|
2007-09-02 21:48:49 +00:00
|
|
|
|
|
|
|
// Realize with the fonts of lesser depth.
|
2007-10-28 18:51:54 +00:00
|
|
|
font.fontInfo().realize(params.getFont().fontInfo());
|
2007-09-02 21:48:49 +00:00
|
|
|
|
|
|
|
return font;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool TextMetrics::isRTL(CursorSlice const & sl, bool boundary) const
|
|
|
|
{
|
2014-07-09 18:12:06 +00:00
|
|
|
if (!sl.text())
|
2007-09-02 21:48:49 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
int correction = 0;
|
|
|
|
if (boundary && sl.pos() > 0)
|
|
|
|
correction = -1;
|
2008-03-21 11:26:20 +00:00
|
|
|
|
2008-02-27 23:03:26 +00:00
|
|
|
return displayFont(sl.pit(), sl.pos() + correction).isVisibleRightToLeft();
|
2007-09-02 21:48:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-09-02 22:28:49 +00:00
|
|
|
bool TextMetrics::isRTLBoundary(pit_type pit, pos_type pos) const
|
2007-09-02 21:48:49 +00:00
|
|
|
{
|
2009-07-24 21:33:50 +00:00
|
|
|
// no RTL boundary at paragraph start
|
2014-07-09 18:12:06 +00:00
|
|
|
if (pos == 0)
|
2007-09-02 21:48:49 +00:00
|
|
|
return false;
|
|
|
|
|
2008-11-21 11:32:56 +00:00
|
|
|
Font const & left_font = displayFont(pit, pos - 1);
|
2007-09-02 22:28:49 +00:00
|
|
|
|
2008-11-21 11:32:56 +00:00
|
|
|
return isRTLBoundary(pit, pos, left_font);
|
2007-09-02 21:48:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-02-13 09:49:50 +00:00
|
|
|
// isRTLBoundary returns false on a real end-of-line boundary,
|
|
|
|
// because otherwise the two boundary types get mixed up.
|
|
|
|
// This is the whole purpose of this being in TextMetrics.
|
2007-09-02 22:28:49 +00:00
|
|
|
bool TextMetrics::isRTLBoundary(pit_type pit, pos_type pos,
|
|
|
|
Font const & font) const
|
2007-09-02 21:48:49 +00:00
|
|
|
{
|
2014-07-09 18:12:06 +00:00
|
|
|
if (// no RTL boundary at paragraph start
|
|
|
|
pos == 0
|
2010-03-19 01:46:13 +00:00
|
|
|
// if the metrics have not been calculated, then we are not
|
|
|
|
// on screen and can safely ignore issues about boundaries.
|
|
|
|
|| !contains(pit))
|
2010-03-19 01:43:53 +00:00
|
|
|
return false;
|
|
|
|
|
2020-10-01 12:58:18 +00:00
|
|
|
ParagraphMetrics const & pm = par_metrics_[pit];
|
2009-07-24 21:33:50 +00:00
|
|
|
// no RTL boundary in empty paragraph
|
|
|
|
if (pm.rows().empty())
|
|
|
|
return false;
|
|
|
|
|
2020-10-01 12:58:18 +00:00
|
|
|
pos_type const endpos = pm.getRow(pos - 1, false).endpos();
|
|
|
|
pos_type const startpos = pm.getRow(pos, false).pos();
|
2009-07-24 21:33:50 +00:00
|
|
|
// no RTL boundary at line start:
|
|
|
|
// abc\n -> toggle to RTL -> abc\n (and not: abc\n|
|
|
|
|
// | | )
|
|
|
|
if (pos == startpos && pos == endpos) // start of cur row, end of prev row
|
|
|
|
return false;
|
|
|
|
|
2007-09-02 22:28:49 +00:00
|
|
|
Paragraph const & par = text_->getPar(pit);
|
2009-07-24 21:33:50 +00:00
|
|
|
// no RTL boundary at line break:
|
|
|
|
// abc|\n -> move right -> abc\n (and not: abc\n|
|
|
|
|
// FED FED| FED )
|
2014-03-14 13:22:26 +00:00
|
|
|
if (startpos == pos && endpos == pos && endpos != par.size()
|
|
|
|
&& (par.isNewline(pos - 1)
|
2014-06-30 09:45:24 +00:00
|
|
|
|| par.isEnvSeparator(pos - 1)
|
2014-03-14 13:22:26 +00:00
|
|
|
|| par.isLineSeparator(pos - 1)
|
2009-07-24 21:33:50 +00:00
|
|
|
|| par.isSeparator(pos - 1)))
|
|
|
|
return false;
|
2014-03-14 13:22:26 +00:00
|
|
|
|
2020-10-01 12:58:18 +00:00
|
|
|
bool const left = font.isVisibleRightToLeft();
|
2010-03-19 15:07:52 +00:00
|
|
|
bool right;
|
|
|
|
if (pos == par.size())
|
|
|
|
right = par.isRTL(bv_->buffer().params());
|
|
|
|
else
|
|
|
|
right = displayFont(pit, pos).isVisibleRightToLeft();
|
2014-03-14 13:22:26 +00:00
|
|
|
|
2007-09-02 21:48:49 +00:00
|
|
|
return left != right;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-01-11 14:55:17 +00:00
|
|
|
bool TextMetrics::redoParagraph(pit_type const pit, bool const align_rows)
|
2006-12-29 23:54:48 +00:00
|
|
|
{
|
2007-08-27 14:04:46 +00:00
|
|
|
Paragraph & par = text_->getPar(pit);
|
2010-01-29 14:44:21 +00:00
|
|
|
// IMPORTANT NOTE: We pass 'false' explicitly in order to not call
|
2007-08-18 21:10:45 +00:00
|
|
|
// redoParagraph() recursively inside parMetrics.
|
|
|
|
Dimension old_dim = parMetrics(pit, false).dim();
|
|
|
|
ParagraphMetrics & pm = par_metrics_[pit];
|
2007-08-27 14:04:46 +00:00
|
|
|
pm.reset(par);
|
|
|
|
|
2007-08-21 13:03:55 +00:00
|
|
|
Buffer & buffer = bv_->buffer();
|
2006-12-29 23:54:48 +00:00
|
|
|
bool changed = false;
|
|
|
|
|
2012-07-17 21:59:04 +00:00
|
|
|
// Check whether there are InsetBibItems that need fixing
|
2011-05-11 12:44:39 +00:00
|
|
|
// FIXME: This check ought to be done somewhere else. It is the reason
|
2012-07-17 21:59:04 +00:00
|
|
|
// why text_ is not const. But then, where else to do it?
|
2011-05-11 12:44:39 +00:00
|
|
|
// Well, how can you end up with either (a) a biblio environment that
|
2019-04-21 09:17:44 +00:00
|
|
|
// has no InsetBibitem, (b) a biblio environment with more than one
|
|
|
|
// InsetBibitem or (c) a paragraph that has a bib item but is no biblio
|
|
|
|
// environment? I think the answer is: when paragraphs are merged;
|
2011-05-11 12:44:39 +00:00
|
|
|
// when layout is set; when material is pasted.
|
2012-07-17 21:59:04 +00:00
|
|
|
if (par.brokenBiblio()) {
|
2021-09-30 21:37:58 +00:00
|
|
|
Cursor & cur = bv_->cursor();
|
2012-07-17 21:59:04 +00:00
|
|
|
// In some cases, we do not know how to record undo
|
|
|
|
if (&cur.inset() == &text_->inset())
|
2015-03-12 14:57:29 +00:00
|
|
|
cur.recordUndo(pit, pit);
|
2012-07-17 21:59:04 +00:00
|
|
|
|
|
|
|
int const moveCursor = par.fixBiblio(buffer);
|
|
|
|
|
|
|
|
// Is it necessary to update the cursor?
|
|
|
|
if (&cur.inset() == &text_->inset() && cur.pit() == pit) {
|
|
|
|
if (moveCursor > 0)
|
|
|
|
cur.posForward();
|
|
|
|
else if (moveCursor < 0 && cur.pos() >= -moveCursor)
|
|
|
|
cur.posBackward();
|
|
|
|
}
|
2011-05-11 12:44:39 +00:00
|
|
|
}
|
|
|
|
|
2006-12-29 23:54:48 +00:00
|
|
|
// Optimisation: this is used in the next two loops
|
|
|
|
// so better to calculate that once here.
|
|
|
|
int const right_margin = rightMargin(pm);
|
|
|
|
|
2007-12-21 20:42:46 +00:00
|
|
|
// iterator pointing to paragraph to resolve macros
|
|
|
|
DocIterator parPos = text_->macrocontextPosition();
|
|
|
|
if (!parPos.empty())
|
|
|
|
parPos.pit() = pit;
|
2008-02-06 11:21:42 +00:00
|
|
|
else {
|
|
|
|
LYXERR(Debug::INFO, "MacroContext not initialised!"
|
|
|
|
<< " Going through the buffer again and hope"
|
|
|
|
<< " the context is better then.");
|
2010-07-09 14:37:00 +00:00
|
|
|
// FIXME audit updateBuffer calls
|
|
|
|
// This should not be here, but it is not clear yet where else it
|
|
|
|
// should be.
|
2010-03-03 22:13:45 +00:00
|
|
|
bv_->buffer().updateBuffer();
|
2008-02-06 11:21:42 +00:00
|
|
|
parPos = text_->macrocontextPosition();
|
2013-04-27 21:52:55 +00:00
|
|
|
LBUFERR(!parPos.empty());
|
2008-02-06 11:26:46 +00:00
|
|
|
parPos.pit() = pit;
|
2008-02-06 11:21:42 +00:00
|
|
|
}
|
2008-03-21 11:26:20 +00:00
|
|
|
|
2006-12-29 23:54:48 +00:00
|
|
|
// redo insets
|
2016-07-21 08:21:45 +00:00
|
|
|
par.setBeginOfBody();
|
2007-04-29 18:17:15 +00:00
|
|
|
Font const bufferfont = buffer.params().getFont();
|
2015-10-12 14:11:58 +00:00
|
|
|
CoordCache::Insets & insetCache = bv_->coordCache().insets();
|
2018-05-28 10:33:17 +00:00
|
|
|
for (auto const & e : par.insetList()) {
|
2011-04-25 01:46:37 +00:00
|
|
|
// FIXME Doesn't this HAVE to be non-empty?
|
2007-12-21 20:42:46 +00:00
|
|
|
// position already initialized?
|
|
|
|
if (!parPos.empty()) {
|
2018-05-28 10:33:17 +00:00
|
|
|
parPos.pos() = e.pos;
|
2008-03-21 11:26:20 +00:00
|
|
|
|
|
|
|
// A macro template would normally not be visible
|
|
|
|
// by itself. But the tex macro semantics allow
|
2007-12-21 20:42:46 +00:00
|
|
|
// recursion, so we artifically take the context
|
|
|
|
// after the macro template to simulate this.
|
2018-05-28 10:33:17 +00:00
|
|
|
if (e.inset->lyxCode() == MATHMACRO_CODE)
|
2007-12-21 20:42:46 +00:00
|
|
|
parPos.pos()++;
|
2007-11-01 11:13:07 +00:00
|
|
|
}
|
|
|
|
|
2017-05-24 12:05:06 +00:00
|
|
|
// If there is an end of paragraph marker, its size should be
|
|
|
|
// substracted to the available width. The logic here is
|
|
|
|
// almost the same as in breakRow, remember keep them in sync.
|
|
|
|
int eop = 0;
|
2021-07-21 09:24:45 +00:00
|
|
|
if (e.pos + 1 == par.size()
|
|
|
|
&& (lyxrc.paragraph_markers || par.lookupChange(par.size()).changed())
|
|
|
|
&& size_type(pit + 1) < text_->paragraphs().size()) {
|
2017-05-24 12:05:06 +00:00
|
|
|
Font f(text_->layoutFont(pit));
|
|
|
|
// ¶ U+00B6 PILCROW SIGN
|
|
|
|
eop = theFontMetrics(f).width(char_type(0x00B6));
|
|
|
|
}
|
|
|
|
|
2007-11-01 11:13:07 +00:00
|
|
|
// do the metric calculation
|
2006-12-29 23:54:48 +00:00
|
|
|
Dimension dim;
|
2018-05-28 10:33:17 +00:00
|
|
|
int const w = max_width_ - leftMargin(pit, e.pos)
|
2017-05-24 12:05:06 +00:00
|
|
|
- right_margin - eop;
|
2018-05-28 10:33:17 +00:00
|
|
|
Font const & font = e.inset->inheritFont() ?
|
|
|
|
displayFont(pit, e.pos) : bufferfont;
|
2009-11-08 11:45:46 +00:00
|
|
|
MacroContext mc(&buffer, parPos);
|
2020-07-14 21:28:43 +00:00
|
|
|
MetricsInfo mi(bv_, font.fontInfo(), w, mc, e.pos == 0, tight_);
|
2018-05-28 10:33:17 +00:00
|
|
|
e.inset->metrics(mi, dim);
|
|
|
|
if (!insetCache.has(e.inset) || insetCache.dim(e.inset) != dim) {
|
|
|
|
insetCache.add(e.inset, dim);
|
2008-09-21 01:39:00 +00:00
|
|
|
changed = true;
|
|
|
|
}
|
2006-12-29 23:54:48 +00:00
|
|
|
}
|
|
|
|
|
2021-07-13 22:48:03 +00:00
|
|
|
// Transform the paragraph into a single row containing all the elements.
|
|
|
|
Row const bigrow = tokenizeParagraph(pit);
|
|
|
|
// Split the row in several rows fitting in available width
|
|
|
|
pm.rows() = breakParagraph(bigrow);
|
|
|
|
|
|
|
|
/* If there is more than one row, expand the text to the full
|
|
|
|
* allowable width. This setting here is needed for the
|
|
|
|
* setRowAlignment() below. We do nothing when tight insets are
|
|
|
|
* requested.
|
|
|
|
*/
|
|
|
|
if (pm.rows().size() > 1 && !tight_ && dim_.wid < max_width_)
|
|
|
|
dim_.wid = max_width_;
|
|
|
|
|
|
|
|
// Compute height and alignment of the rows.
|
|
|
|
for (Row & row : pm.rows()) {
|
2016-05-18 07:39:47 +00:00
|
|
|
setRowHeight(row);
|
2019-01-11 14:55:17 +00:00
|
|
|
if (align_rows)
|
|
|
|
setRowAlignment(row, max(dim_.wid, row.width()));
|
2007-08-29 21:03:41 +00:00
|
|
|
|
2020-11-17 16:47:22 +00:00
|
|
|
pm.dim().wid = max(pm.dim().wid, row.width() + row.right_margin);
|
2013-06-14 17:24:29 +00:00
|
|
|
pm.dim().des += row.height();
|
2021-07-13 22:48:03 +00:00
|
|
|
}
|
2016-05-20 15:14:54 +00:00
|
|
|
|
2021-01-05 13:53:15 +00:00
|
|
|
// This type of margin can only be handled at the global paragraph level
|
|
|
|
if (par.layout().margintype == MARGIN_RIGHT_ADDRESS_BOX) {
|
|
|
|
int offset = 0;
|
|
|
|
if (par.isRTL(buffer.params())) {
|
|
|
|
// globally align the paragraph to the left.
|
|
|
|
int minleft = max_width_;
|
|
|
|
for (Row const & row : pm.rows())
|
|
|
|
minleft = min(minleft, row.left_margin);
|
|
|
|
offset = right_margin - minleft;
|
|
|
|
} else {
|
|
|
|
// globally align the paragraph to the right.
|
|
|
|
int maxwid = 0;
|
|
|
|
for (Row const & row : pm.rows())
|
|
|
|
maxwid = max(maxwid, row.width());
|
|
|
|
offset = max_width_ - right_margin - maxwid;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (Row & row : pm.rows()) {
|
|
|
|
row.left_margin += offset;
|
|
|
|
row.dim().wid += offset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-16 11:01:40 +00:00
|
|
|
// The space above and below the paragraph.
|
|
|
|
int top = parTopSpacing(pit);
|
|
|
|
int bottom = parBottomSpacing(pit);
|
|
|
|
|
|
|
|
// Top and bottom margin of the document (only at top-level)
|
2021-02-16 09:34:40 +00:00
|
|
|
// FIXME: It might be better to move this in another method
|
|
|
|
// specially tailored for the main text.
|
|
|
|
if (text_->isMainText()) {
|
2021-02-16 11:01:40 +00:00
|
|
|
if (pit == 0)
|
|
|
|
top += bv_->topMargin();
|
|
|
|
if (pit + 1 == pit_type(text_->paragraphs().size())) {
|
|
|
|
bottom += bv_->bottomMargin();
|
2021-02-16 09:34:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-16 11:01:40 +00:00
|
|
|
// Add the top/bottom space to rows and paragraph metrics
|
2019-02-18 14:46:42 +00:00
|
|
|
pm.rows().front().dim().asc += top;
|
|
|
|
pm.rows().back().dim().des += bottom;
|
2016-05-20 15:14:54 +00:00
|
|
|
pm.dim().des += top + bottom;
|
2007-08-29 21:33:11 +00:00
|
|
|
|
2021-02-16 11:01:40 +00:00
|
|
|
// Move the pm ascent to be the same as the first row ascent
|
|
|
|
pm.dim().asc += pm.rows().front().ascent();
|
|
|
|
pm.dim().des -= pm.rows().front().ascent();
|
2006-12-29 23:54:48 +00:00
|
|
|
|
|
|
|
changed |= old_dim.height() != pm.dim().height();
|
|
|
|
|
|
|
|
return changed;
|
|
|
|
}
|
|
|
|
|
2007-09-01 14:49:08 +00:00
|
|
|
|
2015-10-23 09:16:21 +00:00
|
|
|
LyXAlignment TextMetrics::getAlign(Paragraph const & par, Row const & row) const
|
2013-07-17 22:25:08 +00:00
|
|
|
{
|
2019-07-10 12:50:08 +00:00
|
|
|
LyXAlignment align = par.getAlign(bv_->buffer().params());
|
2013-07-17 22:25:08 +00:00
|
|
|
|
|
|
|
// handle alignment inside tabular cells
|
|
|
|
Inset const & owner = text_->inset();
|
2015-10-23 09:16:21 +00:00
|
|
|
bool forced_block = false;
|
2013-07-17 22:25:08 +00:00
|
|
|
switch (owner.contentAlignment()) {
|
2015-10-23 09:16:21 +00:00
|
|
|
case LYX_ALIGN_BLOCK:
|
|
|
|
// In general block align is the default state, but here it is
|
|
|
|
// an explicit choice. Therefore it should not be overridden
|
|
|
|
// later.
|
|
|
|
forced_block = true;
|
|
|
|
// fall through
|
2013-07-17 22:25:08 +00:00
|
|
|
case LYX_ALIGN_CENTER:
|
|
|
|
case LYX_ALIGN_LEFT:
|
|
|
|
case LYX_ALIGN_RIGHT:
|
|
|
|
if (align == LYX_ALIGN_NONE || align == LYX_ALIGN_BLOCK)
|
|
|
|
align = owner.contentAlignment();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// unchanged (use align)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Display-style insets should always be on a centered row
|
2015-10-23 09:16:21 +00:00
|
|
|
if (Inset const * inset = par.getInset(row.pos())) {
|
2021-07-11 22:07:59 +00:00
|
|
|
if (inset->rowFlags() & Display) {
|
|
|
|
if (inset->rowFlags() & AlignLeft)
|
2020-06-22 21:11:40 +00:00
|
|
|
align = LYX_ALIGN_BLOCK;
|
2021-07-11 22:07:59 +00:00
|
|
|
else if (inset->rowFlags() & AlignRight)
|
2020-06-22 21:11:40 +00:00
|
|
|
align = LYX_ALIGN_RIGHT;
|
|
|
|
else
|
|
|
|
align = LYX_ALIGN_CENTER;
|
2013-07-17 22:25:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-23 09:16:21 +00:00
|
|
|
if (align == LYX_ALIGN_BLOCK) {
|
|
|
|
// If this row has been broken abruptly by a display inset, or
|
|
|
|
// it is the end of the paragraph, or the user requested we
|
|
|
|
// not justify stuff, then don't stretch.
|
|
|
|
// A forced block alignment can only be overridden the 'no
|
|
|
|
// justification on screen' setting.
|
2017-01-27 15:09:03 +00:00
|
|
|
if ((row.flushed() && !forced_block)
|
2015-10-23 09:16:21 +00:00
|
|
|
|| !bv_->buffer().params().justification)
|
2016-12-07 11:16:41 +00:00
|
|
|
align = row.isRTL() ? LYX_ALIGN_RIGHT : LYX_ALIGN_LEFT;
|
2015-10-23 09:16:21 +00:00
|
|
|
}
|
2013-07-17 22:25:08 +00:00
|
|
|
|
|
|
|
return align;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-01-11 14:55:17 +00:00
|
|
|
void TextMetrics::setRowAlignment(Row & row, int width) const
|
2006-12-29 23:54:48 +00:00
|
|
|
{
|
2007-09-02 09:07:07 +00:00
|
|
|
row.label_hfill = 0;
|
|
|
|
row.separator = 0;
|
|
|
|
|
2016-05-18 07:39:47 +00:00
|
|
|
Paragraph const & par = text_->getPar(row.pit());
|
2006-12-29 23:54:48 +00:00
|
|
|
|
2015-01-07 21:38:48 +00:00
|
|
|
int const w = width - row.right_margin - row.width();
|
2007-09-01 22:01:34 +00:00
|
|
|
// FIXME: put back this assertion when the crash on new doc is solved.
|
2008-04-10 21:49:34 +00:00
|
|
|
//LASSERT(w >= 0, /**/);
|
2007-09-01 22:01:34 +00:00
|
|
|
|
2006-12-29 23:54:48 +00:00
|
|
|
// is there a manual margin with a manual label
|
2008-03-06 21:31:27 +00:00
|
|
|
Layout const & layout = par.layout();
|
2006-12-29 23:54:48 +00:00
|
|
|
|
2013-07-17 22:25:08 +00:00
|
|
|
int nlh = 0;
|
2008-03-06 21:31:27 +00:00
|
|
|
if (layout.margintype == MARGIN_MANUAL
|
|
|
|
&& layout.labeltype == LABEL_MANUAL) {
|
2006-12-29 23:54:48 +00:00
|
|
|
/// We might have real hfills in the label part
|
2013-07-17 22:25:08 +00:00
|
|
|
nlh = numberOfLabelHfills(par, row);
|
2006-12-29 23:54:48 +00:00
|
|
|
|
|
|
|
// A manual label par (e.g. List) has an auto-hfill
|
|
|
|
// between the label text and the body of the
|
|
|
|
// paragraph too.
|
|
|
|
// But we don't want to do this auto hfill if the par
|
|
|
|
// is empty.
|
|
|
|
if (!par.empty())
|
|
|
|
++nlh;
|
|
|
|
|
|
|
|
if (nlh && !par.getLabelWidthString().empty())
|
2016-05-18 07:39:47 +00:00
|
|
|
row.label_hfill = labelFill(row) / double(nlh);
|
2006-12-29 23:54:48 +00:00
|
|
|
}
|
|
|
|
|
2007-12-07 21:57:56 +00:00
|
|
|
// are there any hfills in the row?
|
2020-10-01 10:34:15 +00:00
|
|
|
ParagraphMetrics const & pm = par_metrics_[row.pit()];
|
2015-11-27 10:50:26 +00:00
|
|
|
int nh = numberOfHfills(row, pm, par.beginOfBody());
|
2015-11-13 09:57:26 +00:00
|
|
|
int hfill = 0;
|
|
|
|
int hfill_rem = 0;
|
|
|
|
|
2015-11-27 10:50:26 +00:00
|
|
|
// We don't have to look at the alignment if the row is already
|
|
|
|
// larger then the permitted width as then we force the
|
|
|
|
// LEFT_ALIGN'edness!
|
2021-09-30 21:37:58 +00:00
|
|
|
if (row.width() >= max_width_)
|
2015-11-27 10:50:26 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
if (nh == 0) {
|
|
|
|
// Common case : there is no hfill, and the alignment will be
|
|
|
|
// meaningful
|
2015-10-23 09:16:21 +00:00
|
|
|
switch (getAlign(par, row)) {
|
2016-08-13 18:03:02 +00:00
|
|
|
case LYX_ALIGN_BLOCK:
|
|
|
|
// Expand expanding characters by a total of w
|
2016-12-07 11:16:41 +00:00
|
|
|
if (!row.setExtraWidth(w) && row.isRTL()) {
|
2016-08-13 18:03:02 +00:00
|
|
|
// Justification failed and the text is RTL: align to the right
|
2015-01-07 21:38:48 +00:00
|
|
|
row.left_margin += w;
|
2019-02-18 14:46:42 +00:00
|
|
|
row.dim().wid += w;
|
2006-12-29 23:54:48 +00:00
|
|
|
}
|
|
|
|
break;
|
2017-04-11 11:03:34 +00:00
|
|
|
case LYX_ALIGN_LEFT:
|
|
|
|
// a displayed inset that is flushed
|
2017-09-11 10:40:40 +00:00
|
|
|
if (Inset const * inset = par.getInset(row.pos())) {
|
2017-04-11 11:03:34 +00:00
|
|
|
row.left_margin += inset->indent(*bv_);
|
2019-02-18 14:46:42 +00:00
|
|
|
row.dim().wid += inset->indent(*bv_);
|
2017-09-11 10:40:40 +00:00
|
|
|
}
|
2017-04-11 11:03:34 +00:00
|
|
|
break;
|
2006-12-29 23:54:48 +00:00
|
|
|
case LYX_ALIGN_RIGHT:
|
2017-04-11 11:03:34 +00:00
|
|
|
if (Inset const * inset = par.getInset(row.pos())) {
|
|
|
|
int const new_w = max(w - inset->indent(*bv_), 0);
|
|
|
|
row.left_margin += new_w;
|
2019-02-18 14:46:42 +00:00
|
|
|
row.dim().wid += new_w;
|
2017-04-11 11:03:34 +00:00
|
|
|
} else {
|
|
|
|
row.left_margin += w;
|
2019-02-18 14:46:42 +00:00
|
|
|
row.dim().wid += w;
|
2017-04-11 11:03:34 +00:00
|
|
|
}
|
2006-12-29 23:54:48 +00:00
|
|
|
break;
|
|
|
|
case LYX_ALIGN_CENTER:
|
2019-02-18 14:46:42 +00:00
|
|
|
row.dim().wid += w / 2;
|
2015-01-07 21:38:48 +00:00
|
|
|
row.left_margin += w / 2;
|
2006-12-29 23:54:48 +00:00
|
|
|
break;
|
2014-07-29 09:05:14 +00:00
|
|
|
case LYX_ALIGN_NONE:
|
|
|
|
case LYX_ALIGN_LAYOUT:
|
|
|
|
case LYX_ALIGN_SPECIAL:
|
|
|
|
case LYX_ALIGN_DECIMAL:
|
|
|
|
break;
|
2006-12-29 23:54:48 +00:00
|
|
|
}
|
2015-11-27 10:50:26 +00:00
|
|
|
return;
|
2006-12-29 23:54:48 +00:00
|
|
|
}
|
|
|
|
|
2016-08-13 18:03:02 +00:00
|
|
|
// Case nh > 0. There are hfill separators.
|
2015-11-27 10:50:26 +00:00
|
|
|
hfill = w / nh;
|
|
|
|
hfill_rem = w % nh;
|
2019-02-18 14:46:42 +00:00
|
|
|
row.dim().wid += w;
|
2015-11-27 10:50:26 +00:00
|
|
|
// Set size of hfill insets
|
2007-12-05 22:25:07 +00:00
|
|
|
pos_type const endpos = row.endpos();
|
|
|
|
pos_type body_pos = par.beginOfBody();
|
|
|
|
if (body_pos > 0
|
2013-07-17 22:25:08 +00:00
|
|
|
&& (body_pos > endpos || !par.isLineSeparator(body_pos - 1)))
|
2007-12-05 22:25:07 +00:00
|
|
|
body_pos = 0;
|
2015-11-27 10:50:26 +00:00
|
|
|
|
2015-10-12 14:11:58 +00:00
|
|
|
CoordCache::Insets & insetCache = bv_->coordCache().insets();
|
2018-05-28 10:33:17 +00:00
|
|
|
for (Row::Element & e : row) {
|
|
|
|
if (row.label_hfill && e.endpos == body_pos
|
|
|
|
&& e.type == Row::SPACE)
|
|
|
|
e.dim.wid -= int(row.label_hfill * (nlh - 1));
|
|
|
|
if (e.inset && pm.hfillExpansion(row, e.pos)) {
|
|
|
|
if (e.pos >= body_pos) {
|
|
|
|
e.dim.wid += hfill;
|
2015-11-13 09:57:26 +00:00
|
|
|
--nh;
|
|
|
|
if (nh == 0)
|
2018-05-28 10:33:17 +00:00
|
|
|
e.dim.wid += hfill_rem;
|
2015-11-13 09:57:26 +00:00
|
|
|
} else
|
2018-05-28 10:33:17 +00:00
|
|
|
e.dim.wid += int(row.label_hfill);
|
2015-11-27 10:50:26 +00:00
|
|
|
// Cache the inset dimension.
|
2018-05-28 10:33:17 +00:00
|
|
|
insetCache.add(e.inset, e.dim);
|
2015-11-13 09:57:26 +00:00
|
|
|
}
|
2007-12-05 22:25:07 +00:00
|
|
|
}
|
2006-12-29 23:54:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-05-18 07:39:47 +00:00
|
|
|
int TextMetrics::labelFill(Row const & row) const
|
2006-12-29 23:54:48 +00:00
|
|
|
{
|
2016-05-18 07:39:47 +00:00
|
|
|
Paragraph const & par = text_->getPar(row.pit());
|
2014-06-30 09:45:24 +00:00
|
|
|
LBUFERR(par.beginOfBody() > 0 || par.isEnvSeparator(0));
|
2006-12-29 23:54:48 +00:00
|
|
|
|
2014-12-22 09:36:53 +00:00
|
|
|
int w = 0;
|
2013-07-17 22:25:08 +00:00
|
|
|
// iterate over elements before main body (except the last one,
|
|
|
|
// which is extra space).
|
2018-05-28 10:33:17 +00:00
|
|
|
for (Row::Element const & e : row) {
|
|
|
|
if (e.endpos >= par.beginOfBody())
|
|
|
|
break;
|
|
|
|
w += e.dim.wid;
|
2013-07-17 22:25:08 +00:00
|
|
|
}
|
2006-12-29 23:54:48 +00:00
|
|
|
|
|
|
|
docstring const & label = par.params().labelWidthString();
|
|
|
|
if (label.empty())
|
|
|
|
return 0;
|
|
|
|
|
2007-05-28 22:27:45 +00:00
|
|
|
FontMetrics const & fm
|
2009-08-09 15:29:34 +00:00
|
|
|
= theFontMetrics(text_->labelFont(par));
|
2006-12-29 23:54:48 +00:00
|
|
|
|
2014-12-22 09:36:53 +00:00
|
|
|
return max(0, fm.width(label) - w);
|
2006-12-29 23:54:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-01-01 09:36:55 +00:00
|
|
|
int TextMetrics::labelEnd(pit_type const pit) const
|
|
|
|
{
|
|
|
|
// labelEnd is only needed if the layout fills a flushleft label.
|
2008-03-06 21:31:27 +00:00
|
|
|
if (text_->getPar(pit).layout().margintype != MARGIN_MANUAL)
|
2007-01-01 09:36:55 +00:00
|
|
|
return 0;
|
|
|
|
// return the beginning of the body
|
2017-07-12 08:25:54 +00:00
|
|
|
return leftMargin(pit);
|
2007-01-01 09:36:55 +00:00
|
|
|
}
|
|
|
|
|
2008-03-28 08:45:33 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calling Text::getFont is slow. While rebreaking we scan a
|
|
|
|
* paragraph from left to right calling getFont for every char. This
|
|
|
|
* simple class address this problem by hidding an optimization trick
|
|
|
|
* (not mine btw -AB): the font is reused in the whole font span. The
|
|
|
|
* class handles transparently the "hidden" (not part of the fontlist)
|
|
|
|
* label font (as getFont does).
|
|
|
|
**/
|
|
|
|
class FontIterator
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
///
|
|
|
|
FontIterator(TextMetrics const & tm,
|
|
|
|
Paragraph const & par, pit_type pit, pos_type pos)
|
|
|
|
: tm_(tm), par_(par), pit_(pit), pos_(pos),
|
|
|
|
font_(tm.displayFont(pit, pos)),
|
|
|
|
endspan_(par.fontSpan(pos).last),
|
|
|
|
bodypos_(par.beginOfBody())
|
|
|
|
{}
|
|
|
|
|
|
|
|
///
|
|
|
|
Font const & operator*() const { return font_; }
|
|
|
|
|
|
|
|
///
|
|
|
|
FontIterator & operator++()
|
|
|
|
{
|
|
|
|
++pos_;
|
2013-04-28 12:44:08 +00:00
|
|
|
if (pos_ < par_.size() && (pos_ > endspan_ || pos_ == bodypos_)) {
|
2008-03-28 08:45:33 +00:00
|
|
|
font_ = tm_.displayFont(pit_, pos_);
|
|
|
|
endspan_ = par_.fontSpan(pos_).last;
|
|
|
|
}
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
Font * operator->() { return &font_; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
///
|
|
|
|
TextMetrics const & tm_;
|
|
|
|
///
|
|
|
|
Paragraph const & par_;
|
|
|
|
///
|
|
|
|
pit_type pit_;
|
|
|
|
///
|
|
|
|
pos_type pos_;
|
|
|
|
///
|
|
|
|
Font font_;
|
|
|
|
///
|
|
|
|
pos_type endspan_;
|
|
|
|
///
|
|
|
|
pos_type bodypos_;
|
|
|
|
};
|
|
|
|
|
2017-07-23 11:11:54 +00:00
|
|
|
} // namespace
|
2007-01-01 09:36:55 +00:00
|
|
|
|
2021-07-11 13:19:37 +00:00
|
|
|
|
|
|
|
Row TextMetrics::tokenizeParagraph(pit_type const pit) const
|
|
|
|
{
|
|
|
|
Row row;
|
|
|
|
row.pit(pit);
|
|
|
|
Paragraph const & par = text_->getPar(pit);
|
|
|
|
Buffer const & buf = text_->inset().buffer();
|
|
|
|
BookmarksSection::BookmarkPosList bpl =
|
|
|
|
theSession().bookmarks().bookmarksInPar(buf.fileName(), par.id());
|
|
|
|
|
|
|
|
pos_type const end = par.size();
|
|
|
|
pos_type const body_pos = par.beginOfBody();
|
|
|
|
|
|
|
|
// check for possible inline completion
|
|
|
|
DocIterator const & ic_it = bv_->inlineCompletionPos();
|
|
|
|
pos_type ic_pos = -1;
|
|
|
|
if (ic_it.inTexted() && ic_it.text() == text_ && ic_it.pit() == pit)
|
|
|
|
ic_pos = ic_it.pos();
|
|
|
|
|
|
|
|
// Now we iterate through until we reach the right margin
|
|
|
|
// or the end of the par, then build a representation of the row.
|
|
|
|
pos_type i = 0;
|
|
|
|
FontIterator fi = FontIterator(*this, par, pit, 0);
|
|
|
|
// The real stopping condition is a few lines below.
|
|
|
|
while (true) {
|
|
|
|
// Firstly, check whether there is a bookmark here.
|
|
|
|
if (lyxrc.bookmarks_visibility == LyXRC::BMK_INLINE)
|
|
|
|
for (auto const & bp_p : bpl)
|
|
|
|
if (bp_p.second == i) {
|
|
|
|
Font f = *fi;
|
|
|
|
f.fontInfo().setColor(Color_bookmark);
|
|
|
|
// ❶ U+2776 DINGBAT NEGATIVE CIRCLED DIGIT ONE
|
|
|
|
char_type const ch = 0x2775 + bp_p.first;
|
|
|
|
row.addVirtual(i, docstring(1, ch), f, Change());
|
|
|
|
}
|
|
|
|
|
|
|
|
// The stopping condition is here so that the display of a
|
|
|
|
// bookmark can take place at paragraph start too.
|
|
|
|
if (i >= end)
|
|
|
|
break;
|
|
|
|
|
|
|
|
char_type c = par.getChar(i);
|
|
|
|
// The most special cases are handled first.
|
|
|
|
if (par.isInset(i)) {
|
|
|
|
Inset const * ins = par.getInset(i);
|
|
|
|
Dimension dim = bv_->coordCache().insets().dim(ins);
|
|
|
|
row.add(i, ins, dim, *fi, par.lookupChange(i));
|
|
|
|
} else if (c == ' ' && i + 1 == body_pos) {
|
|
|
|
// There is a space at i, but it should not be
|
|
|
|
// added as a separator, because it is just
|
|
|
|
// before body_pos. Instead, insert some spacing to
|
|
|
|
// align text
|
|
|
|
FontMetrics const & fm = theFontMetrics(text_->labelFont(par));
|
|
|
|
// this is needed to make sure that the row width is correct
|
|
|
|
row.finalizeLast();
|
|
|
|
int const add = max(fm.width(par.layout().labelsep),
|
|
|
|
labelEnd(pit) - row.width());
|
|
|
|
row.addSpace(i, add, *fi, par.lookupChange(i));
|
|
|
|
} else if (c == '\t')
|
|
|
|
row.addSpace(i, theFontMetrics(*fi).width(from_ascii(" ")),
|
2021-07-11 22:07:59 +00:00
|
|
|
*fi, par.lookupChange(i));
|
2021-07-11 13:19:37 +00:00
|
|
|
else if (c == 0x2028 || c == 0x2029) {
|
|
|
|
/**
|
|
|
|
* U+2028 LINE SEPARATOR
|
|
|
|
* U+2029 PARAGRAPH SEPARATOR
|
|
|
|
|
|
|
|
* These are special unicode characters that break
|
|
|
|
* lines/pragraphs. Not handling them lead to trouble wrt
|
|
|
|
* Qt QTextLayout formatting. We add a visible character
|
|
|
|
* on screen so that the user can see that something is
|
|
|
|
* happening.
|
|
|
|
*/
|
|
|
|
row.finalizeLast();
|
|
|
|
// ⤶ U+2936 ARROW POINTING DOWNWARDS THEN CURVING LEFTWARDS
|
|
|
|
// ¶ U+00B6 PILCROW SIGN
|
|
|
|
char_type const screen_char = (c == 0x2028) ? 0x2936 : 0x00B6;
|
2021-07-11 22:07:59 +00:00
|
|
|
row.add(i, screen_char, *fi, par.lookupChange(i), i >= body_pos);
|
2021-07-11 13:19:37 +00:00
|
|
|
} else
|
2021-07-11 22:07:59 +00:00
|
|
|
// row elements before body are unbreakable
|
|
|
|
row.add(i, c, *fi, par.lookupChange(i), i >= body_pos);
|
2021-07-11 13:19:37 +00:00
|
|
|
|
|
|
|
// add inline completion width
|
|
|
|
// draw logically behind the previous character
|
|
|
|
if (ic_pos == i + 1 && !bv_->inlineCompletion().empty()) {
|
|
|
|
docstring const comp = bv_->inlineCompletion();
|
|
|
|
size_t const uniqueTo =bv_->inlineCompletionUniqueChars();
|
|
|
|
Font f = *fi;
|
|
|
|
|
|
|
|
if (uniqueTo > 0) {
|
|
|
|
f.fontInfo().setColor(Color_inlinecompletion);
|
|
|
|
row.addVirtual(i + 1, comp.substr(0, uniqueTo), f, Change());
|
|
|
|
}
|
|
|
|
f.fontInfo().setColor(Color_nonunique_inlinecompletion);
|
|
|
|
row.addVirtual(i + 1, comp.substr(uniqueTo), f, Change());
|
|
|
|
}
|
|
|
|
|
|
|
|
++i;
|
|
|
|
++fi;
|
|
|
|
}
|
|
|
|
row.finalizeLast();
|
|
|
|
row.endpos(end);
|
|
|
|
|
2021-07-15 22:10:25 +00:00
|
|
|
// End of paragraph marker, either if LyXRc requires it, or there
|
|
|
|
// is an end of paragraph change. The logic here is almost the
|
2021-07-11 13:19:37 +00:00
|
|
|
// same as in redoParagraph, remember keep them in sync.
|
|
|
|
ParagraphList const & pars = text_->paragraphs();
|
2021-07-15 22:10:25 +00:00
|
|
|
Change const & endchange = par.lookupChange(end);
|
|
|
|
if (endchange.changed())
|
|
|
|
row.needsChangeBar(true);
|
|
|
|
if ((lyxrc.paragraph_markers || endchange.changed())
|
|
|
|
&& size_type(pit + 1) < pars.size()) {
|
2021-07-11 13:19:37 +00:00
|
|
|
// add a virtual element for the end-of-paragraph
|
|
|
|
// marker; it is shown on screen, but does not exist
|
|
|
|
// in the paragraph.
|
|
|
|
Font f(text_->layoutFont(pit));
|
|
|
|
f.fontInfo().setColor(Color_paragraphmarker);
|
|
|
|
f.setLanguage(par.getParLanguage(buf.params()));
|
|
|
|
// ¶ U+00B6 PILCROW SIGN
|
2021-07-15 22:10:25 +00:00
|
|
|
row.addVirtual(end, docstring(1, char_type(0x00B6)), f, endchange);
|
2021-07-11 13:19:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return row;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-07-13 22:47:42 +00:00
|
|
|
namespace {
|
|
|
|
|
2021-07-17 00:31:49 +00:00
|
|
|
/** Helper template flexible_const_iterator<T>
|
|
|
|
* A way to iterate over a const container, but insert fake elements in it.
|
|
|
|
* In the case of a row, we will have to break some elements, which
|
|
|
|
* create new ones. This class allows to abstract this.
|
|
|
|
* Only the required parts are implemented for now.
|
|
|
|
*/
|
|
|
|
template<class T>
|
|
|
|
class flexible_const_iterator {
|
|
|
|
typedef typename T::value_type value_type;
|
|
|
|
public:
|
|
|
|
|
|
|
|
//
|
|
|
|
flexible_const_iterator operator++() {
|
|
|
|
if (pile_.empty())
|
|
|
|
++cit_;
|
|
|
|
else
|
|
|
|
pile_.pop_back();
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
value_type operator*() const { return pile_.empty() ? *cit_ : pile_.back(); }
|
|
|
|
|
2021-07-17 23:09:33 +00:00
|
|
|
value_type const * operator->() const { return pile_.empty() ? &*cit_ : &pile_.back(); }
|
|
|
|
|
2021-07-17 00:31:49 +00:00
|
|
|
void put(value_type const & e) { pile_.push_back(e); }
|
|
|
|
|
|
|
|
// This should be private, but declaring the friend functions is too much work
|
|
|
|
//private:
|
|
|
|
typename T::const_iterator cit_;
|
|
|
|
// A vector that is used as like a pile to store the elements to
|
|
|
|
// consider before incrementing the underlying iterator.
|
|
|
|
vector<value_type> pile_;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
template<class T>
|
|
|
|
flexible_const_iterator<T> flexible_begin(T const & t)
|
|
|
|
{
|
|
|
|
return { t.begin(), vector<typename T::value_type>() };
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<class T>
|
|
|
|
flexible_const_iterator<T> flexible_end(T const & t)
|
|
|
|
{
|
|
|
|
return { t.end(), vector<typename T::value_type>() };
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Equality is only possible if respective piles are empty
|
|
|
|
template<class T>
|
|
|
|
bool operator==(flexible_const_iterator<T> const & t1,
|
|
|
|
flexible_const_iterator<T> const & t2)
|
|
|
|
{
|
|
|
|
return t1.cit_ == t2.cit_ && t1.pile_.empty() && t2.pile_.empty();
|
|
|
|
}
|
|
|
|
|
2021-07-17 23:09:33 +00:00
|
|
|
Row newRow(TextMetrics const & tm, pit_type pit, pos_type pos, bool is_rtl)
|
|
|
|
{
|
|
|
|
Row nrow;
|
|
|
|
nrow.pit(pit);
|
|
|
|
nrow.pos(pos);
|
|
|
|
nrow.left_margin = tm.leftMargin(pit, pos);
|
|
|
|
nrow.right_margin = tm.rightMargin(pit);
|
|
|
|
if (is_rtl)
|
|
|
|
swap(nrow.left_margin, nrow.right_margin);
|
|
|
|
// Remember that the row width takes into account the left_margin
|
|
|
|
// but not the right_margin.
|
|
|
|
nrow.dim().wid = nrow.left_margin;
|
|
|
|
return nrow;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cleanupRow(Row & row, pos_type pos, pos_type real_endpos, bool is_rtl)
|
|
|
|
{
|
|
|
|
row.endpos(pos);
|
|
|
|
row.right_boundary(!row.empty() && pos < real_endpos
|
|
|
|
&& row.back().endpos == pos);
|
|
|
|
// make sure that the RTL elements are in reverse ordering
|
|
|
|
row.reverseRTL(is_rtl);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implement the priorities described in RowFlags.h.
|
|
|
|
bool needsRowBreak(int f1, int f2)
|
|
|
|
{
|
|
|
|
if (f1 & AlwaysBreakAfter /*|| f2 & AlwaysBreakBefore*/)
|
|
|
|
return true;
|
|
|
|
if (f1 & NoBreakAfter || f2 & NoBreakBefore)
|
|
|
|
return false;
|
|
|
|
if (f1 & BreakAfter || f2 & BreakBefore)
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-07-13 22:47:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-07-15 22:10:25 +00:00
|
|
|
RowList TextMetrics::breakParagraph(Row const & bigrow) const
|
2021-07-13 22:47:42 +00:00
|
|
|
{
|
|
|
|
RowList rows;
|
2021-07-15 22:10:25 +00:00
|
|
|
bool const is_rtl = text_->isRTL(bigrow.pit());
|
2021-07-17 23:09:33 +00:00
|
|
|
bool const end_label = text_->getEndLabel(bigrow.pit()) != END_LABEL_NO_LABEL;
|
2021-07-13 22:47:42 +00:00
|
|
|
|
|
|
|
bool need_new_row = true;
|
|
|
|
pos_type pos = 0;
|
|
|
|
int width = 0;
|
2021-07-17 00:31:49 +00:00
|
|
|
flexible_const_iterator<Row> fcit = flexible_begin(bigrow);
|
|
|
|
flexible_const_iterator<Row> const end = flexible_end(bigrow);
|
2021-07-13 22:47:42 +00:00
|
|
|
while (true) {
|
2021-07-17 23:09:33 +00:00
|
|
|
bool const has_row = !rows.empty();
|
|
|
|
bool const row_empty = !has_row || rows.back().empty();
|
|
|
|
// The row flags of previous element, if there is one.
|
|
|
|
// Otherwise we use NoBreakAfter to avoid an empty row before
|
|
|
|
// e.g. a displayed equation.
|
|
|
|
int const f1 = row_empty ? NoBreakAfter : rows.back().back().row_flags;
|
|
|
|
// The row flags of next element, if there is one.
|
|
|
|
// Otherwise we use NoBreakBefore (see above), unless the
|
|
|
|
// paragraph has an end label (for which an empty row is OK).
|
|
|
|
int const f2 = (fcit == end) ? (end_label ? Inline : NoBreakBefore)
|
|
|
|
: fcit->row_flags;
|
|
|
|
need_new_row |= needsRowBreak(f1, f2);
|
2021-07-13 22:47:42 +00:00
|
|
|
if (need_new_row) {
|
2021-07-17 23:09:33 +00:00
|
|
|
if (!rows.empty())
|
|
|
|
cleanupRow(rows.back(), pos, bigrow.endpos(), is_rtl);
|
2021-07-15 22:10:25 +00:00
|
|
|
rows.push_back(newRow(*this, bigrow.pit(), pos, is_rtl));
|
2021-07-13 22:47:42 +00:00
|
|
|
// the width available for the row.
|
|
|
|
width = max_width_ - rows.back().right_margin;
|
|
|
|
need_new_row = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The stopping condition is here because we may need a new
|
|
|
|
// empty row at the end.
|
2021-07-17 00:31:49 +00:00
|
|
|
if (fcit == end)
|
2021-07-13 22:47:42 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
// Next element to consider is either the top of the temporary
|
|
|
|
// pile, or the place when we were in main row
|
2021-07-17 00:31:49 +00:00
|
|
|
Row::Element elt = *fcit;
|
2021-07-13 22:47:42 +00:00
|
|
|
Row::Element next_elt = elt.splitAt(width - rows.back().width(),
|
|
|
|
!elt.font.language()->wordWrap());
|
|
|
|
// a new element in the row
|
|
|
|
rows.back().push_back(elt);
|
2021-07-17 21:16:15 +00:00
|
|
|
rows.back().finalizeLast();
|
2021-07-13 22:47:42 +00:00
|
|
|
pos = elt.endpos;
|
2021-07-17 00:31:49 +00:00
|
|
|
|
2021-07-13 22:47:42 +00:00
|
|
|
// Go to next element
|
2021-07-17 00:31:49 +00:00
|
|
|
++fcit;
|
|
|
|
|
2021-07-13 22:47:42 +00:00
|
|
|
// Add a new next element on the pile
|
|
|
|
if (next_elt.isValid()) {
|
2021-07-17 00:31:49 +00:00
|
|
|
// do as if we inserted this element in the original row
|
|
|
|
fcit.put(next_elt);
|
2021-07-13 22:47:42 +00:00
|
|
|
need_new_row = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-15 22:10:25 +00:00
|
|
|
if (!rows.empty()) {
|
2021-07-17 23:09:33 +00:00
|
|
|
cleanupRow(rows.back(), pos, bigrow.endpos(), is_rtl);
|
2021-07-15 22:10:25 +00:00
|
|
|
// Last row in paragraph is flushed
|
2021-07-17 23:09:33 +00:00
|
|
|
rows.back().flushed(true);
|
2021-07-15 22:10:25 +00:00
|
|
|
}
|
|
|
|
|
2021-07-13 22:47:42 +00:00
|
|
|
return rows;
|
|
|
|
}
|
|
|
|
|
2013-07-23 14:24:01 +00:00
|
|
|
/** This is the function where the hard work is done. The code here is
|
|
|
|
* very sensitive to small changes :) Note that part of the
|
2015-04-16 09:39:33 +00:00
|
|
|
* intelligence is also in Row::shortenIfNeeded.
|
2013-07-23 14:24:01 +00:00
|
|
|
*/
|
2016-05-18 07:39:47 +00:00
|
|
|
bool TextMetrics::breakRow(Row & row, int const right_margin) const
|
2007-01-01 09:36:55 +00:00
|
|
|
{
|
2021-07-11 13:19:37 +00:00
|
|
|
LATTEST(row.empty());//
|
|
|
|
Paragraph const & par = text_->getPar(row.pit());//
|
|
|
|
Buffer const & buf = text_->inset().buffer();//
|
|
|
|
BookmarksSection::BookmarkPosList bpl =//
|
|
|
|
theSession().bookmarks().bookmarksInPar(buf.fileName(), par.id());//
|
2021-04-06 13:19:12 +00:00
|
|
|
|
2021-07-11 13:19:37 +00:00
|
|
|
pos_type const end = par.size();//
|
2021-07-13 22:47:42 +00:00
|
|
|
pos_type const pos = row.pos();//
|
2021-07-11 13:19:37 +00:00
|
|
|
pos_type const body_pos = par.beginOfBody();//
|
2021-07-13 22:47:42 +00:00
|
|
|
bool const is_rtl = text_->isRTL(row.pit());//
|
|
|
|
bool need_new_row = false;//
|
2015-07-17 22:07:30 +00:00
|
|
|
|
2021-07-13 22:47:42 +00:00
|
|
|
row.left_margin = leftMargin(row.pit(), pos);//
|
|
|
|
row.right_margin = right_margin;//
|
|
|
|
if (is_rtl)//
|
|
|
|
swap(row.left_margin, row.right_margin);//
|
2015-07-17 22:07:30 +00:00
|
|
|
// Remember that the row width takes into account the left_margin
|
|
|
|
// but not the right_margin.
|
2021-07-13 22:47:42 +00:00
|
|
|
row.dim().wid = row.left_margin;//
|
2015-07-17 22:07:30 +00:00
|
|
|
// the width available for the row.
|
2021-07-13 22:47:42 +00:00
|
|
|
int const width = max_width_ - row.right_margin;//
|
2013-06-25 12:57:09 +00:00
|
|
|
|
2008-02-21 19:42:34 +00:00
|
|
|
// check for possible inline completion
|
2021-07-11 13:19:37 +00:00
|
|
|
DocIterator const & ic_it = bv_->inlineCompletionPos();//
|
|
|
|
pos_type ic_pos = -1;//
|
|
|
|
if (ic_it.inTexted() && ic_it.text() == text_ && ic_it.pit() == row.pit())//
|
|
|
|
ic_pos = ic_it.pos();//
|
2007-01-01 09:36:55 +00:00
|
|
|
|
2013-06-13 22:01:49 +00:00
|
|
|
// Now we iterate through until we reach the right margin
|
2013-06-25 12:57:09 +00:00
|
|
|
// or the end of the par, then build a representation of the row.
|
2021-07-11 13:19:37 +00:00
|
|
|
pos_type i = pos;//---------------------------------------------------vvv
|
2016-05-18 07:39:47 +00:00
|
|
|
FontIterator fi = FontIterator(*this, par, row.pit(), pos);
|
2021-04-06 13:19:12 +00:00
|
|
|
// The real stopping condition is a few lines below.
|
|
|
|
while (true) {
|
|
|
|
// Firstly, check whether there is a bookmark here.
|
|
|
|
if (lyxrc.bookmarks_visibility == LyXRC::BMK_INLINE)
|
|
|
|
for (auto const & bp_p : bpl)
|
|
|
|
if (bp_p.second == i) {
|
|
|
|
Font f = *fi;
|
|
|
|
f.fontInfo().setColor(Color_bookmark);
|
|
|
|
// ❶ U+2776 DINGBAT NEGATIVE CIRCLED DIGIT ONE
|
|
|
|
char_type const ch = 0x2775 + bp_p.first;
|
|
|
|
row.addVirtual(i, docstring(1, ch), f, Change());
|
|
|
|
}
|
|
|
|
|
|
|
|
// The stopping condition is here so that the display of a
|
|
|
|
// bookmark can take place at paragraph start too.
|
2021-07-11 13:19:37 +00:00
|
|
|
if (i >= end || (i != pos && row.width() > width))//^width
|
2021-04-06 13:19:12 +00:00
|
|
|
break;
|
|
|
|
|
2013-06-13 22:01:49 +00:00
|
|
|
char_type c = par.getChar(i);
|
|
|
|
// The most special cases are handled first.
|
|
|
|
if (par.isInset(i)) {
|
2013-06-25 12:57:09 +00:00
|
|
|
Inset const * ins = par.getInset(i);
|
2015-10-12 14:11:58 +00:00
|
|
|
Dimension dim = bv_->coordCache().insets().dim(ins);
|
2013-07-16 22:59:34 +00:00
|
|
|
row.add(i, ins, dim, *fi, par.lookupChange(i));
|
2014-03-21 10:56:42 +00:00
|
|
|
} else if (c == ' ' && i + 1 == body_pos) {
|
|
|
|
// There is a space at i, but it should not be
|
|
|
|
// added as a separator, because it is just
|
|
|
|
// before body_pos. Instead, insert some spacing to
|
|
|
|
// align text
|
|
|
|
FontMetrics const & fm = theFontMetrics(text_->labelFont(par));
|
2014-10-09 20:09:33 +00:00
|
|
|
// this is needed to make sure that the row width is correct
|
|
|
|
row.finalizeLast();
|
2014-03-21 10:56:42 +00:00
|
|
|
int const add = max(fm.width(par.layout().labelsep),
|
2016-05-18 07:39:47 +00:00
|
|
|
labelEnd(row.pit()) - row.width());
|
2014-03-21 10:56:42 +00:00
|
|
|
row.addSpace(i, add, *fi, par.lookupChange(i));
|
2013-06-13 22:01:49 +00:00
|
|
|
} else if (c == '\t')
|
2013-07-17 22:25:08 +00:00
|
|
|
row.addSpace(i, theFontMetrics(*fi).width(from_ascii(" ")),
|
|
|
|
*fi, par.lookupChange(i));
|
2016-11-07 09:14:39 +00:00
|
|
|
else if (c == 0x2028 || c == 0x2029) {
|
|
|
|
/**
|
|
|
|
* U+2028 LINE SEPARATOR
|
|
|
|
* U+2029 PARAGRAPH SEPARATOR
|
|
|
|
|
|
|
|
* These are special unicode characters that break
|
|
|
|
* lines/pragraphs. Not handling them lead to trouble wrt
|
|
|
|
* Qt QTextLayout formatting. We add a visible character
|
|
|
|
* on screen so that the user can see that something is
|
|
|
|
* happening.
|
|
|
|
*/
|
|
|
|
row.finalizeLast();
|
|
|
|
// ⤶ U+2936 ARROW POINTING DOWNWARDS THEN CURVING LEFTWARDS
|
|
|
|
// ¶ U+00B6 PILCROW SIGN
|
|
|
|
char_type const screen_char = (c == 0x2028) ? 0x2936 : 0x00B6;
|
2021-07-11 22:07:59 +00:00
|
|
|
row.add(i, screen_char, *fi, par.lookupChange(i), i >= body_pos);
|
2018-10-30 11:33:35 +00:00
|
|
|
} else
|
2021-07-11 22:07:59 +00:00
|
|
|
row.add(i, c, *fi, par.lookupChange(i), i >= body_pos);
|
2014-03-14 13:22:26 +00:00
|
|
|
|
2008-02-21 19:42:34 +00:00
|
|
|
// add inline completion width
|
2015-07-15 19:38:55 +00:00
|
|
|
// draw logically behind the previous character
|
|
|
|
if (ic_pos == i + 1 && !bv_->inlineCompletion().empty()) {
|
|
|
|
docstring const comp = bv_->inlineCompletion();
|
|
|
|
size_t const uniqueTo =bv_->inlineCompletionUniqueChars();
|
2013-06-25 12:57:09 +00:00
|
|
|
Font f = *fi;
|
2015-07-15 19:38:55 +00:00
|
|
|
|
|
|
|
if (uniqueTo > 0) {
|
|
|
|
f.fontInfo().setColor(Color_inlinecompletion);
|
|
|
|
row.addVirtual(i + 1, comp.substr(0, uniqueTo), f, Change());
|
|
|
|
}
|
|
|
|
f.fontInfo().setColor(Color_nonunique_inlinecompletion);
|
|
|
|
row.addVirtual(i + 1, comp.substr(uniqueTo), f, Change());
|
2021-07-17 23:09:33 +00:00
|
|
|
}
|
2013-06-25 12:57:09 +00:00
|
|
|
|
|
|
|
// Handle some situations that abruptly terminate the row
|
2020-06-22 21:11:40 +00:00
|
|
|
// - Before an inset with BreakBefore
|
|
|
|
// - After an inset with BreakAfter
|
|
|
|
Inset const * prevInset = !row.empty() ? row.back().inset : 0;
|
|
|
|
Inset const * nextInset = (i + 1 < end) ? par.getInset(i + 1) : 0;
|
2021-07-11 22:07:59 +00:00
|
|
|
if ((nextInset && nextInset->rowFlags() & BreakBefore)
|
|
|
|
|| (prevInset && prevInset->rowFlags() & BreakAfter)) {
|
2017-01-27 15:09:03 +00:00
|
|
|
row.flushed(true);
|
2020-06-22 21:11:40 +00:00
|
|
|
// Force a row creation after this one if it is ended by
|
|
|
|
// an inset that either
|
|
|
|
// - has row flag RowAfter that enforces that;
|
|
|
|
// - or (1) did force the row breaking, (2) is at end of
|
|
|
|
// paragraph and (3) the said paragraph has an end label.
|
|
|
|
need_new_row = prevInset &&
|
2021-07-11 22:07:59 +00:00
|
|
|
(prevInset->rowFlags() & AlwaysBreakAfter
|
|
|
|
|| (prevInset->rowFlags() & BreakAfter && i + 1 == end
|
2020-06-22 21:11:40 +00:00
|
|
|
&& text_->getEndLabel(row.pit()) != END_LABEL_NO_LABEL));
|
2013-06-25 12:57:09 +00:00
|
|
|
++i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
++i;
|
|
|
|
++fi;
|
2018-01-03 17:28:55 +00:00
|
|
|
}
|
2014-05-19 09:35:15 +00:00
|
|
|
row.finalizeLast();
|
|
|
|
row.endpos(i);
|
2008-02-21 19:42:34 +00:00
|
|
|
|
2017-05-24 12:05:06 +00:00
|
|
|
// End of paragraph marker. The logic here is almost the
|
|
|
|
// same as in redoParagraph, remember keep them in sync.
|
2016-03-05 22:11:45 +00:00
|
|
|
ParagraphList const & pars = text_->paragraphs();
|
2019-03-21 10:27:51 +00:00
|
|
|
Change const & change = par.lookupChange(i);
|
|
|
|
if ((lyxrc.paragraph_markers || change.changed())
|
2021-07-11 13:19:37 +00:00
|
|
|
&& !need_new_row // not this
|
2016-05-18 07:39:47 +00:00
|
|
|
&& i == end && size_type(row.pit() + 1) < pars.size()) {
|
2014-03-21 10:56:42 +00:00
|
|
|
// add a virtual element for the end-of-paragraph
|
|
|
|
// marker; it is shown on screen, but does not exist
|
|
|
|
// in the paragraph.
|
2016-05-18 07:39:47 +00:00
|
|
|
Font f(text_->layoutFont(row.pit()));
|
2014-03-21 10:56:42 +00:00
|
|
|
f.fontInfo().setColor(Color_paragraphmarker);
|
2021-04-06 13:19:12 +00:00
|
|
|
f.setLanguage(par.getParLanguage(buf.params()));
|
2016-11-07 09:14:39 +00:00
|
|
|
// ¶ U+00B6 PILCROW SIGN
|
2019-03-21 10:27:51 +00:00
|
|
|
row.addVirtual(end, docstring(1, char_type(0x00B6)), f, change);
|
2007-01-01 09:36:55 +00:00
|
|
|
}
|
|
|
|
|
2017-11-11 10:57:39 +00:00
|
|
|
// Is there a end-of-paragaph change?
|
|
|
|
if (i == end && par.lookupChange(end).changed() && !need_new_row)
|
|
|
|
row.needsChangeBar(true);
|
2021-07-11 13:19:37 +00:00
|
|
|
//--------------------------------------------------------------------^^^
|
|
|
|
// FIXME : nothing below this
|
2017-11-11 10:57:39 +00:00
|
|
|
|
2016-03-07 12:46:13 +00:00
|
|
|
// if the row is too large, try to cut at last separator. In case
|
|
|
|
// of success, reset indication that the row was broken abruptly.
|
2017-07-12 08:25:54 +00:00
|
|
|
int const next_width = max_width_ - leftMargin(row.pit(), row.endpos())
|
2017-01-26 13:10:23 +00:00
|
|
|
- rightMargin(row.pit());
|
2017-01-27 15:09:03 +00:00
|
|
|
|
2021-07-11 22:07:59 +00:00
|
|
|
if (row.shortenIfNeeded(width, next_width))
|
2017-08-31 12:52:30 +00:00
|
|
|
row.flushed(false);
|
2021-07-15 22:10:25 +00:00
|
|
|
row.right_boundary(!row.empty() && row.endpos() < end//
|
|
|
|
&& row.back().endpos == row.endpos());//
|
2017-01-27 15:09:03 +00:00
|
|
|
// Last row in paragraph is flushed
|
2021-07-15 22:10:25 +00:00
|
|
|
if (row.endpos() == end)//
|
|
|
|
row.flushed(true);//
|
2013-06-25 12:57:09 +00:00
|
|
|
|
2013-07-21 18:22:32 +00:00
|
|
|
// make sure that the RTL elements are in reverse ordering
|
2021-07-15 22:10:25 +00:00
|
|
|
row.reverseRTL(is_rtl);//
|
2015-07-18 23:22:10 +00:00
|
|
|
//LYXERR0("breakrow: row is " << row);
|
2016-03-05 22:11:45 +00:00
|
|
|
|
|
|
|
return need_new_row;
|
2007-01-01 09:36:55 +00:00
|
|
|
}
|
|
|
|
|
2016-05-20 15:14:54 +00:00
|
|
|
int TextMetrics::parTopSpacing(pit_type const pit) const
|
|
|
|
{
|
|
|
|
Paragraph const & par = text_->getPar(pit);
|
|
|
|
Layout const & layout = par.layout();
|
|
|
|
|
|
|
|
int asc = 0;
|
|
|
|
ParagraphList const & pars = text_->paragraphs();
|
|
|
|
double const dh = defaultRowHeight();
|
|
|
|
|
|
|
|
BufferParams const & bparams = bv_->buffer().params();
|
|
|
|
Inset const & inset = text_->inset();
|
|
|
|
// some parskips VERY EASY IMPLEMENTATION
|
|
|
|
if (bparams.paragraph_separation == BufferParams::ParagraphSkipSeparation
|
|
|
|
&& !inset.getLayout().parbreakIsNewline()
|
|
|
|
&& !par.layout().parbreak_is_newline
|
|
|
|
&& pit > 0
|
|
|
|
&& ((layout.isParagraph() && par.getDepth() == 0)
|
|
|
|
|| (pars[pit - 1].layout().isParagraph()
|
|
|
|
&& pars[pit - 1].getDepth() == 0))) {
|
|
|
|
asc += bparams.getDefSkip().inPixels(*bv_);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (par.params().startOfAppendix())
|
|
|
|
asc += int(3 * dh);
|
|
|
|
|
|
|
|
// special code for the top label
|
|
|
|
if (layout.labelIsAbove()
|
|
|
|
&& (!layout.isParagraphGroup() || text_->isFirstInSequence(pit))
|
|
|
|
&& !par.labelString().empty()) {
|
|
|
|
FontInfo labelfont = text_->labelFont(par);
|
|
|
|
FontMetrics const & lfm = theFontMetrics(labelfont);
|
|
|
|
asc += int(lfm.maxHeight() * layout.spacing.getValue()
|
|
|
|
* text_->spacing(par)
|
|
|
|
+ (layout.topsep + layout.labelbottomsep) * dh);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the layout spaces, for example before and after
|
|
|
|
// a section, or between the items of a itemize or enumerate
|
|
|
|
// environment.
|
|
|
|
|
|
|
|
pit_type prev = text_->depthHook(pit, par.getDepth());
|
|
|
|
Paragraph const & prevpar = pars[prev];
|
|
|
|
double layoutasc = 0;
|
|
|
|
if (prev != pit
|
|
|
|
&& prevpar.layout() == layout
|
|
|
|
&& prevpar.getDepth() == par.getDepth()
|
|
|
|
&& prevpar.getLabelWidthString() == par.getLabelWidthString()) {
|
|
|
|
layoutasc = layout.itemsep * dh;
|
|
|
|
} else if (pit != 0 && layout.topsep > 0)
|
|
|
|
layoutasc = layout.topsep * dh;
|
|
|
|
|
|
|
|
asc += int(layoutasc * 2 / (2 + pars[pit].getDepth()));
|
|
|
|
|
|
|
|
prev = text_->outerHook(pit);
|
|
|
|
if (prev != pit_type(pars.size())) {
|
|
|
|
asc += int(pars[prev].layout().parsep * dh);
|
|
|
|
} else if (pit != 0) {
|
2018-02-24 05:25:56 +00:00
|
|
|
Paragraph const & prevpar2 = pars[pit - 1];
|
|
|
|
if (prevpar2.getDepth() != 0 || prevpar2.layout() == layout)
|
2016-05-20 15:14:54 +00:00
|
|
|
asc += int(layout.parsep * dh);
|
|
|
|
}
|
|
|
|
|
|
|
|
return asc;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int TextMetrics::parBottomSpacing(pit_type const pit) const
|
|
|
|
{
|
|
|
|
double layoutdesc = 0;
|
|
|
|
ParagraphList const & pars = text_->paragraphs();
|
|
|
|
double const dh = defaultRowHeight();
|
|
|
|
|
|
|
|
// add the layout spaces, for example before and after
|
|
|
|
// a section, or between the items of a itemize or enumerate
|
|
|
|
// environment
|
|
|
|
pit_type nextpit = pit + 1;
|
|
|
|
if (nextpit != pit_type(pars.size())) {
|
|
|
|
pit_type cpit = pit;
|
|
|
|
|
|
|
|
if (pars[cpit].getDepth() > pars[nextpit].getDepth()) {
|
|
|
|
double usual = pars[cpit].layout().bottomsep * dh;
|
|
|
|
double unusual = 0;
|
|
|
|
cpit = text_->depthHook(cpit, pars[nextpit].getDepth());
|
|
|
|
if (pars[cpit].layout() != pars[nextpit].layout()
|
|
|
|
|| pars[nextpit].getLabelWidthString() != pars[cpit].getLabelWidthString())
|
|
|
|
unusual = pars[cpit].layout().bottomsep * dh;
|
|
|
|
layoutdesc = max(unusual, usual);
|
|
|
|
} else if (pars[cpit].getDepth() == pars[nextpit].getDepth()) {
|
|
|
|
if (pars[cpit].layout() != pars[nextpit].layout()
|
|
|
|
|| pars[nextpit].getLabelWidthString() != pars[cpit].getLabelWidthString())
|
|
|
|
layoutdesc = int(pars[cpit].layout().bottomsep * dh);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return int(layoutdesc * 2 / (2 + pars[pit].getDepth()));
|
|
|
|
}
|
|
|
|
|
2007-01-01 09:36:55 +00:00
|
|
|
|
2016-05-20 15:14:54 +00:00
|
|
|
void TextMetrics::setRowHeight(Row & row) const
|
2007-01-01 09:36:55 +00:00
|
|
|
{
|
2016-05-18 07:39:47 +00:00
|
|
|
Paragraph const & par = text_->getPar(row.pit());
|
2008-03-06 21:31:27 +00:00
|
|
|
Layout const & layout = par.layout();
|
2016-05-20 15:14:54 +00:00
|
|
|
double const spacing_val = layout.spacing.getValue() * text_->spacing(par);
|
2007-01-01 09:36:55 +00:00
|
|
|
|
2016-03-06 15:22:53 +00:00
|
|
|
// Initial value for ascent (useful if row is empty).
|
2016-05-18 07:39:47 +00:00
|
|
|
Font const font = displayFont(row.pit(), row.pos());
|
2016-03-06 15:22:53 +00:00
|
|
|
FontMetrics const & fm = theFontMetrics(font);
|
2019-05-13 08:47:47 +00:00
|
|
|
int maxasc = int(fm.maxAscent() * spacing_val);
|
|
|
|
int maxdes = int(fm.maxDescent() * spacing_val);
|
2016-03-06 15:22:53 +00:00
|
|
|
|
2020-07-14 18:51:05 +00:00
|
|
|
// Take label string into account (useful if labelfont is large)
|
|
|
|
if (row.pos() == 0 && layout.labelIsInline()) {
|
|
|
|
FontInfo const labelfont = text_->labelFont(par);
|
|
|
|
FontMetrics const & lfm = theFontMetrics(labelfont);
|
|
|
|
maxasc = max(maxasc, int(lfm.maxAscent() * spacing_val));
|
|
|
|
maxdes = max(maxdes, int(lfm.maxDescent() * spacing_val));
|
|
|
|
}
|
|
|
|
|
2016-03-06 15:22:53 +00:00
|
|
|
// Find the ascent/descent of the row contents
|
2018-05-28 10:33:17 +00:00
|
|
|
for (Row::Element const & e : row) {
|
2019-05-13 08:47:47 +00:00
|
|
|
if (e.inset) {
|
|
|
|
maxasc = max(maxasc, e.dim.ascent());
|
|
|
|
maxdes = max(maxdes, e.dim.descent());
|
|
|
|
} else {
|
|
|
|
FontMetrics const & fm2 = theFontMetrics(e.font);
|
|
|
|
maxasc = max(maxasc, int(fm2.maxAscent() * spacing_val));
|
|
|
|
maxdes = max(maxdes, int(fm2.maxDescent() * spacing_val));
|
|
|
|
}
|
2007-01-01 09:36:55 +00:00
|
|
|
}
|
|
|
|
|
2019-05-13 08:47:47 +00:00
|
|
|
// This is nicer with box insets
|
|
|
|
++maxasc;
|
|
|
|
++maxdes;
|
|
|
|
|
|
|
|
row.dim().asc = maxasc;
|
|
|
|
row.dim().des = maxdes;
|
2021-02-16 11:21:12 +00:00
|
|
|
|
|
|
|
// This is useful for selections
|
|
|
|
row.contents_dim() = row.dim();
|
2007-01-01 09:36:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-01-01 10:33:37 +00:00
|
|
|
// x is an absolute screen coord
|
|
|
|
// returns the column near the specified x-coordinate of the row
|
|
|
|
// x is set to the real beginning of this column
|
2014-05-02 13:55:10 +00:00
|
|
|
pos_type TextMetrics::getPosNearX(Row const & row, int & x,
|
2017-04-01 11:09:23 +00:00
|
|
|
bool & boundary) const
|
2007-01-01 10:33:37 +00:00
|
|
|
{
|
2015-07-17 22:39:55 +00:00
|
|
|
//LYXERR0("getPosNearX(" << x << ") row=" << row);
|
2013-10-11 14:12:20 +00:00
|
|
|
/// For the main Text, it is possible that this pit is not
|
|
|
|
/// yet in the CoordCache when moving cursor up.
|
|
|
|
/// x Paragraph coordinate is always 0 for main text anyway.
|
|
|
|
int const xo = origin_.x_;
|
|
|
|
x -= xo;
|
2013-07-21 18:22:32 +00:00
|
|
|
|
2016-04-30 23:27:13 +00:00
|
|
|
// Adapt to cursor row scroll offset if applicable.
|
2016-02-29 15:05:06 +00:00
|
|
|
int const offset = bv_->horizScrollOffset(text_, row.pit(), row.pos());
|
2016-04-30 23:27:13 +00:00
|
|
|
x += offset;
|
|
|
|
|
2013-07-21 18:22:32 +00:00
|
|
|
pos_type pos = row.pos();
|
2013-10-11 14:12:20 +00:00
|
|
|
boundary = false;
|
2014-03-19 13:44:53 +00:00
|
|
|
if (row.empty())
|
2014-12-22 09:36:53 +00:00
|
|
|
x = row.left_margin;
|
|
|
|
else if (x <= row.left_margin) {
|
2014-03-21 10:56:42 +00:00
|
|
|
pos = row.front().left_pos();
|
2014-12-22 09:36:53 +00:00
|
|
|
x = row.left_margin;
|
2014-10-17 12:34:09 +00:00
|
|
|
} else if (x >= row.width()) {
|
2014-03-21 10:56:42 +00:00
|
|
|
pos = row.back().right_pos();
|
2014-10-17 12:34:09 +00:00
|
|
|
x = row.width();
|
2013-07-21 18:22:32 +00:00
|
|
|
} else {
|
2014-12-22 09:36:53 +00:00
|
|
|
double w = row.left_margin;
|
2013-07-21 18:22:32 +00:00
|
|
|
Row::const_iterator cit = row.begin();
|
|
|
|
Row::const_iterator cend = row.end();
|
|
|
|
for ( ; cit != cend; ++cit) {
|
2014-12-22 09:36:53 +00:00
|
|
|
if (w <= x && w + cit->full_width() > x) {
|
|
|
|
int x_offset = int(x - w);
|
2017-04-01 11:09:23 +00:00
|
|
|
pos = cit->x2pos(x_offset);
|
2014-07-28 07:46:13 +00:00
|
|
|
x = int(x_offset + w);
|
2013-07-21 18:22:32 +00:00
|
|
|
break;
|
|
|
|
}
|
2014-12-22 09:36:53 +00:00
|
|
|
w += cit->full_width();
|
2013-07-21 18:22:32 +00:00
|
|
|
}
|
2014-03-21 10:56:42 +00:00
|
|
|
if (cit == row.end()) {
|
|
|
|
pos = row.back().right_pos();
|
2014-10-17 12:34:09 +00:00
|
|
|
x = row.width();
|
2014-03-21 10:56:42 +00:00
|
|
|
}
|
2013-07-21 18:22:32 +00:00
|
|
|
/** This tests for the case where the cursor is placed
|
|
|
|
* just before a font direction change. See comment on
|
|
|
|
* the boundary_ member in DocIterator.h to understand
|
2014-03-21 10:56:42 +00:00
|
|
|
* how boundary helps here.
|
2013-07-21 18:22:32 +00:00
|
|
|
*/
|
|
|
|
else if (pos == cit->endpos
|
2017-04-06 13:05:19 +00:00
|
|
|
&& ((!cit->isRTL() && cit + 1 != row.end()
|
|
|
|
&& (cit + 1)->isRTL())
|
|
|
|
|| (cit->isRTL() && cit != row.begin()
|
|
|
|
&& !(cit - 1)->isRTL())))
|
2013-07-21 18:22:32 +00:00
|
|
|
boundary = true;
|
2013-07-21 18:22:32 +00:00
|
|
|
}
|
|
|
|
|
2013-07-21 18:22:32 +00:00
|
|
|
/** This tests for the case where the cursor is set at the end
|
2014-07-28 21:31:32 +00:00
|
|
|
* of a row which has been broken due something else than a
|
|
|
|
* separator (a display inset or a forced breaking of the
|
|
|
|
* row). We know that there is a separator when the end of the
|
|
|
|
* row is larger than the end of its last element.
|
2013-07-21 18:22:32 +00:00
|
|
|
*/
|
|
|
|
if (!row.empty() && pos == row.back().endpos
|
2016-03-06 14:29:25 +00:00
|
|
|
&& row.back().endpos == row.endpos()) {
|
|
|
|
Inset const * inset = row.back().inset;
|
|
|
|
if (inset && (inset->lyxCode() == NEWLINE_CODE
|
|
|
|
|| inset->lyxCode() == SEPARATOR_CODE))
|
|
|
|
pos = row.back().pos;
|
|
|
|
else
|
|
|
|
boundary = row.right_boundary();
|
|
|
|
}
|
2016-04-30 23:27:13 +00:00
|
|
|
|
|
|
|
x += xo - offset;
|
2015-07-17 22:39:55 +00:00
|
|
|
//LYXERR0("getPosNearX ==> pos=" << pos << ", boundary=" << boundary);
|
2016-04-30 23:27:13 +00:00
|
|
|
|
2013-12-20 13:02:31 +00:00
|
|
|
return pos;
|
2007-01-01 10:33:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-01-01 11:36:30 +00:00
|
|
|
pos_type TextMetrics::x2pos(pit_type pit, int row, int x) const
|
|
|
|
{
|
2007-09-05 13:04:05 +00:00
|
|
|
// We play safe and use parMetrics(pit) to make sure the
|
|
|
|
// ParagraphMetrics will be redone and OK to use if needed.
|
|
|
|
// Otherwise we would use an empty ParagraphMetrics in
|
|
|
|
// upDownInText() while in selection mode.
|
|
|
|
ParagraphMetrics const & pm = parMetrics(pit);
|
|
|
|
|
2013-04-27 21:52:55 +00:00
|
|
|
LBUFERR(row < int(pm.rows().size()));
|
2007-01-01 11:36:30 +00:00
|
|
|
bool bound = false;
|
|
|
|
Row const & r = pm.rows()[row];
|
2014-05-02 13:55:10 +00:00
|
|
|
return getPosNearX(r, x, bound);
|
2007-01-01 11:36:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-09-02 09:44:08 +00:00
|
|
|
// y is screen coordinate
|
|
|
|
pit_type TextMetrics::getPitNearY(int y)
|
|
|
|
{
|
2008-10-24 08:49:31 +00:00
|
|
|
LASSERT(!text_->paragraphs().empty(), return -1);
|
|
|
|
LASSERT(!par_metrics_.empty(), return -1);
|
2007-11-29 21:10:35 +00:00
|
|
|
LYXERR(Debug::DEBUG, "y: " << y << " cache size: " << par_metrics_.size());
|
2007-09-02 09:44:08 +00:00
|
|
|
|
|
|
|
// look for highest numbered paragraph with y coordinate less than given y
|
2007-11-26 15:29:54 +00:00
|
|
|
pit_type pit = -1;
|
2007-09-02 09:44:08 +00:00
|
|
|
int yy = -1;
|
2007-09-11 16:04:10 +00:00
|
|
|
ParMetricsCache::const_iterator it = par_metrics_.begin();
|
|
|
|
ParMetricsCache::const_iterator et = par_metrics_.end();
|
2012-05-28 20:41:32 +00:00
|
|
|
ParMetricsCache::const_iterator last = et;
|
|
|
|
--last;
|
2007-09-02 09:44:08 +00:00
|
|
|
|
2007-09-11 16:04:10 +00:00
|
|
|
ParagraphMetrics const & pm = it->second;
|
2007-09-02 09:44:08 +00:00
|
|
|
|
2021-09-30 21:37:58 +00:00
|
|
|
if (y < it->second.position() - pm.ascent()) {
|
2007-11-26 15:29:54 +00:00
|
|
|
// We are looking for a position that is before the first paragraph in
|
|
|
|
// the cache (which is in priciple off-screen, that is before the
|
|
|
|
// visible part.
|
2007-09-02 09:44:08 +00:00
|
|
|
if (it->first == 0)
|
2007-11-26 15:29:54 +00:00
|
|
|
// We are already at the first paragraph in the inset.
|
2007-09-02 09:44:08 +00:00
|
|
|
return 0;
|
2007-11-26 15:29:54 +00:00
|
|
|
// OK, this is the paragraph we are looking for.
|
2007-09-02 09:44:08 +00:00
|
|
|
pit = it->first - 1;
|
2007-11-26 15:29:54 +00:00
|
|
|
newParMetricsUp();
|
2007-09-02 09:44:08 +00:00
|
|
|
return pit;
|
|
|
|
}
|
|
|
|
|
|
|
|
ParagraphMetrics const & pm_last = par_metrics_[last->first];
|
|
|
|
|
2021-09-30 21:37:58 +00:00
|
|
|
if (y >= last->second.position() + pm_last.descent()) {
|
2007-11-26 15:29:54 +00:00
|
|
|
// We are looking for a position that is after the last paragraph in
|
2009-05-20 19:54:29 +00:00
|
|
|
// the cache (which is in priciple off-screen), that is before the
|
2007-11-26 15:29:54 +00:00
|
|
|
// visible part.
|
2007-09-02 09:44:08 +00:00
|
|
|
pit = last->first + 1;
|
|
|
|
if (pit == int(text_->paragraphs().size()))
|
2007-11-26 15:29:54 +00:00
|
|
|
// We are already at the last paragraph in the inset.
|
2007-09-02 09:44:08 +00:00
|
|
|
return last->first;
|
2007-11-26 15:29:54 +00:00
|
|
|
// OK, this is the paragraph we are looking for.
|
|
|
|
newParMetricsDown();
|
2007-09-02 09:44:08 +00:00
|
|
|
return pit;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (; it != et; ++it) {
|
2007-11-29 21:10:35 +00:00
|
|
|
LYXERR(Debug::DEBUG, "examining: pit: " << it->first
|
2007-11-15 20:04:51 +00:00
|
|
|
<< " y: " << it->second.position());
|
2007-09-02 09:44:08 +00:00
|
|
|
|
2018-02-24 05:25:56 +00:00
|
|
|
ParagraphMetrics const & pm2 = par_metrics_[it->first];
|
2007-09-02 09:44:08 +00:00
|
|
|
|
2021-09-30 21:37:58 +00:00
|
|
|
if (it->first >= pit && it->second.position() - pm2.ascent() <= y) {
|
2007-09-02 09:44:08 +00:00
|
|
|
pit = it->first;
|
2007-09-11 16:04:10 +00:00
|
|
|
yy = it->second.position();
|
2007-09-02 09:44:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-11-29 21:10:35 +00:00
|
|
|
LYXERR(Debug::DEBUG, "found best y: " << yy << " for pit: " << pit);
|
2007-09-02 09:44:08 +00:00
|
|
|
|
|
|
|
return pit;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-08-13 14:18:12 +00:00
|
|
|
Row const & TextMetrics::getPitAndRowNearY(int & y, pit_type & pit,
|
2009-05-20 20:28:24 +00:00
|
|
|
bool assert_in_view, bool up)
|
2007-09-02 09:44:08 +00:00
|
|
|
{
|
|
|
|
ParagraphMetrics const & pm = par_metrics_[pit];
|
|
|
|
|
2021-02-16 09:34:40 +00:00
|
|
|
int yy = pm.position() - pm.ascent();
|
2013-04-27 21:52:55 +00:00
|
|
|
LBUFERR(!pm.rows().empty());
|
2007-09-02 09:44:08 +00:00
|
|
|
RowList::const_iterator rit = pm.rows().begin();
|
2007-10-02 18:27:20 +00:00
|
|
|
RowList::const_iterator rlast = pm.rows().end();
|
|
|
|
--rlast;
|
2007-09-02 09:44:08 +00:00
|
|
|
for (; rit != rlast; yy += rit->height(), ++rit)
|
|
|
|
if (yy + rit->height() > y)
|
|
|
|
break;
|
2009-05-20 20:28:24 +00:00
|
|
|
|
2009-11-15 23:53:40 +00:00
|
|
|
if (assert_in_view) {
|
|
|
|
if (!up && yy + rit->height() > y) {
|
2009-08-13 14:18:12 +00:00
|
|
|
if (rit != pm.rows().begin()) {
|
|
|
|
y = yy;
|
2009-05-20 20:28:24 +00:00
|
|
|
--rit;
|
2009-08-13 14:18:12 +00:00
|
|
|
} else if (pit != 0) {
|
2009-05-20 20:28:24 +00:00
|
|
|
--pit;
|
|
|
|
newParMetricsUp();
|
|
|
|
ParagraphMetrics const & pm2 = par_metrics_[pit];
|
|
|
|
rit = pm2.rows().end();
|
|
|
|
--rit;
|
2009-08-13 14:18:12 +00:00
|
|
|
y = yy;
|
2009-05-20 20:28:24 +00:00
|
|
|
}
|
2021-02-16 08:27:28 +00:00
|
|
|
} else if (up && yy != y) {
|
2009-08-13 14:18:12 +00:00
|
|
|
if (rit != rlast) {
|
|
|
|
y = yy + rit->height();
|
2009-05-20 20:28:24 +00:00
|
|
|
++rit;
|
2009-11-15 23:45:39 +00:00
|
|
|
} else if (pit < int(text_->paragraphs().size()) - 1) {
|
2009-05-20 20:28:24 +00:00
|
|
|
++pit;
|
|
|
|
newParMetricsDown();
|
|
|
|
ParagraphMetrics const & pm2 = par_metrics_[pit];
|
|
|
|
rit = pm2.rows().begin();
|
2009-08-13 14:18:12 +00:00
|
|
|
y = pm2.position();
|
2009-05-20 20:28:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2007-09-02 09:44:08 +00:00
|
|
|
return *rit;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// x,y are absolute screen coordinates
|
|
|
|
// sets cursor recursively descending into nested editable insets
|
2009-05-20 20:28:24 +00:00
|
|
|
Inset * TextMetrics::editXY(Cursor & cur, int x, int y,
|
|
|
|
bool assert_in_view, bool up)
|
2007-09-02 09:44:08 +00:00
|
|
|
{
|
|
|
|
if (lyxerr.debugging(Debug::WORKAREA)) {
|
2007-11-28 22:12:03 +00:00
|
|
|
LYXERR0("TextMetrics::editXY(cur, " << x << ", " << y << ")");
|
2007-09-02 09:44:08 +00:00
|
|
|
cur.bv().coordCache().dump();
|
|
|
|
}
|
|
|
|
pit_type pit = getPitNearY(y);
|
2008-10-24 08:49:31 +00:00
|
|
|
LASSERT(pit != -1, return 0);
|
2017-04-06 13:08:50 +00:00
|
|
|
Row const & row = getPitAndRowNearY(y, pit, assert_in_view, up);
|
2007-09-02 09:44:08 +00:00
|
|
|
cur.pit() = pit;
|
|
|
|
|
2014-05-27 13:14:14 +00:00
|
|
|
// Do we cover an inset?
|
2018-05-28 10:33:17 +00:00
|
|
|
InsetList::Element * e = checkInsetHit(pit, x, y);
|
2014-05-27 13:14:14 +00:00
|
|
|
|
2018-05-28 10:33:17 +00:00
|
|
|
if (!e) {
|
2014-05-27 13:14:14 +00:00
|
|
|
// No inset, set position in the text
|
2013-12-20 13:02:31 +00:00
|
|
|
bool bound = false; // is modified by getPosNearX
|
2017-04-06 13:08:50 +00:00
|
|
|
cur.pos() = getPosNearX(row, x, bound);
|
2014-05-27 13:14:14 +00:00
|
|
|
cur.boundary(bound);
|
2007-09-02 13:35:48 +00:00
|
|
|
cur.setCurrentFont();
|
2017-04-06 13:08:50 +00:00
|
|
|
cur.setTargetX(x);
|
2007-09-02 09:44:08 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-05-28 10:33:17 +00:00
|
|
|
Inset * inset = e->inset;
|
2014-05-27 13:14:14 +00:00
|
|
|
//lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
|
|
|
|
|
|
|
|
// Set position in front of inset
|
2018-05-28 10:33:17 +00:00
|
|
|
cur.pos() = e->pos;
|
2014-05-27 13:14:14 +00:00
|
|
|
cur.boundary(false);
|
|
|
|
cur.setTargetX(x);
|
2007-09-02 09:44:08 +00:00
|
|
|
|
|
|
|
// Try to descend recursively inside the inset.
|
2017-04-06 13:08:50 +00:00
|
|
|
Inset * edited = inset->editXY(cur, x, y);
|
2017-06-08 09:35:05 +00:00
|
|
|
// FIXME: it is not clear that the test on position is needed
|
|
|
|
// Remove it if/when semantics of editXY is clarified
|
2018-05-28 10:33:17 +00:00
|
|
|
if (cur.text() == text_ && cur.pos() == e->pos) {
|
2015-10-11 12:21:45 +00:00
|
|
|
// non-editable inset, set cursor after the inset if x is
|
|
|
|
// nearer to that position (bug 9628)
|
2017-04-06 13:08:50 +00:00
|
|
|
bool bound = false; // is modified by getPosNearX
|
|
|
|
cur.pos() = getPosNearX(row, x, bound);
|
|
|
|
cur.boundary(bound);
|
|
|
|
cur.setCurrentFont();
|
|
|
|
cur.setTargetX(x);
|
2015-10-11 12:21:45 +00:00
|
|
|
}
|
2007-09-02 09:44:08 +00:00
|
|
|
|
|
|
|
if (cur.top().text() == text_)
|
2007-09-02 13:35:48 +00:00
|
|
|
cur.setCurrentFont();
|
2015-10-11 12:21:45 +00:00
|
|
|
return edited;
|
2007-09-02 09:44:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-04-01 11:09:23 +00:00
|
|
|
void TextMetrics::setCursorFromCoordinates(Cursor & cur, int const x, int const y)
|
2007-09-02 09:44:08 +00:00
|
|
|
{
|
2013-04-25 21:27:10 +00:00
|
|
|
LASSERT(text_ == cur.text(), return);
|
2013-04-08 22:32:02 +00:00
|
|
|
pit_type const pit = getPitNearY(y);
|
2008-10-24 08:49:31 +00:00
|
|
|
LASSERT(pit != -1, return);
|
2007-09-02 09:44:08 +00:00
|
|
|
|
|
|
|
ParagraphMetrics const & pm = par_metrics_[pit];
|
|
|
|
|
2020-08-24 15:10:06 +00:00
|
|
|
int yy = pm.position() - pm.rows().front().ascent();
|
2007-11-29 21:10:35 +00:00
|
|
|
LYXERR(Debug::DEBUG, "x: " << x << " y: " << y <<
|
|
|
|
" pit: " << pit << " yy: " << yy);
|
2007-09-02 09:44:08 +00:00
|
|
|
|
|
|
|
int r = 0;
|
2013-04-27 21:52:55 +00:00
|
|
|
LBUFERR(pm.rows().size());
|
2007-09-02 09:44:08 +00:00
|
|
|
for (; r < int(pm.rows().size()) - 1; ++r) {
|
|
|
|
Row const & row = pm.rows()[r];
|
2021-09-30 21:37:58 +00:00
|
|
|
if (yy + row.height() > y)
|
2007-09-02 09:44:08 +00:00
|
|
|
break;
|
|
|
|
yy += row.height();
|
|
|
|
}
|
|
|
|
|
|
|
|
Row const & row = pm.rows()[r];
|
|
|
|
|
2007-11-29 21:10:35 +00:00
|
|
|
LYXERR(Debug::DEBUG, "row " << r << " from pos: " << row.pos());
|
2007-09-02 09:44:08 +00:00
|
|
|
|
|
|
|
bool bound = false;
|
|
|
|
int xx = x;
|
2017-04-01 11:09:23 +00:00
|
|
|
pos_type const pos = getPosNearX(row, xx, bound);
|
2007-09-02 09:44:08 +00:00
|
|
|
|
2007-11-29 21:10:35 +00:00
|
|
|
LYXERR(Debug::DEBUG, "setting cursor pit: " << pit << " pos: " << pos);
|
2007-09-02 09:44:08 +00:00
|
|
|
|
|
|
|
text_->setCursor(cur, pit, pos, true, bound);
|
|
|
|
// remember new position.
|
|
|
|
cur.setTargetX();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//takes screen x,y coordinates
|
2018-05-28 10:33:17 +00:00
|
|
|
InsetList::Element * TextMetrics::checkInsetHit(pit_type pit, int x, int y)
|
2007-09-02 09:44:08 +00:00
|
|
|
{
|
|
|
|
Paragraph const & par = text_->paragraphs()[pit];
|
2015-10-12 14:11:58 +00:00
|
|
|
CoordCache::Insets const & insetCache = bv_->coordCache().getInsets();
|
2007-09-02 09:44:08 +00:00
|
|
|
|
2007-11-29 21:10:35 +00:00
|
|
|
LYXERR(Debug::DEBUG, "x: " << x << " y: " << y << " pit: " << pit);
|
2007-11-15 20:04:51 +00:00
|
|
|
|
2018-05-28 10:33:17 +00:00
|
|
|
for (InsetList::Element const & e : par.insetList()) {
|
|
|
|
LYXERR(Debug::DEBUG, "examining inset " << e.inset);
|
2007-09-21 20:39:47 +00:00
|
|
|
|
2018-05-28 10:33:17 +00:00
|
|
|
if (insetCache.covers(e.inset, x, y)) {
|
|
|
|
LYXERR(Debug::DEBUG, "Hit inset: " << e.inset);
|
|
|
|
return const_cast<InsetList::Element *>(&e);
|
2007-09-02 09:44:08 +00:00
|
|
|
}
|
|
|
|
}
|
2007-09-21 20:39:47 +00:00
|
|
|
|
2007-11-29 21:10:35 +00:00
|
|
|
LYXERR(Debug::DEBUG, "No inset hit. ");
|
2020-10-05 10:38:09 +00:00
|
|
|
return nullptr;
|
2007-09-02 09:44:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-05-27 13:14:14 +00:00
|
|
|
//takes screen x,y coordinates
|
|
|
|
Inset * TextMetrics::checkInsetHit(int x, int y)
|
|
|
|
{
|
|
|
|
pit_type const pit = getPitNearY(y);
|
|
|
|
LASSERT(pit != -1, return 0);
|
2018-05-28 10:33:17 +00:00
|
|
|
InsetList::Element * e = checkInsetHit(pit, x, y);
|
2014-05-27 13:14:14 +00:00
|
|
|
|
2018-05-28 10:33:17 +00:00
|
|
|
if (!e)
|
2014-05-27 13:14:14 +00:00
|
|
|
return 0;
|
|
|
|
|
2018-05-28 10:33:17 +00:00
|
|
|
return e->inset;
|
2014-05-27 13:14:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-09-14 20:13:39 +00:00
|
|
|
int TextMetrics::cursorX(CursorSlice const & sl,
|
|
|
|
bool boundary) const
|
|
|
|
{
|
|
|
|
LASSERT(sl.text() == text_, return 0);
|
|
|
|
|
|
|
|
ParagraphMetrics const & pm = par_metrics_[sl.pit()];
|
|
|
|
if (pm.rows().empty())
|
|
|
|
return 0;
|
|
|
|
Row const & row = pm.getRow(sl.pos(), boundary);
|
|
|
|
pos_type const pos = sl.pos();
|
|
|
|
|
|
|
|
double x = 0;
|
2017-02-02 14:23:20 +00:00
|
|
|
row.findElement(pos, boundary, x);
|
2007-09-02 09:44:08 +00:00
|
|
|
return int(x);
|
2015-09-14 20:13:39 +00:00
|
|
|
|
2007-09-02 09:44:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int TextMetrics::cursorY(CursorSlice const & sl, bool boundary) const
|
|
|
|
{
|
2007-12-12 19:28:07 +00:00
|
|
|
//lyxerr << "TextMetrics::cursorY: boundary: " << boundary << endl;
|
2018-02-12 16:11:09 +00:00
|
|
|
ParagraphMetrics const & pm = parMetrics(sl.pit());
|
2007-09-02 09:44:08 +00:00
|
|
|
if (pm.rows().empty())
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
int h = 0;
|
2018-02-12 16:11:09 +00:00
|
|
|
h -= parMetrics(0).rows()[0].ascent();
|
2007-09-02 09:44:08 +00:00
|
|
|
for (pit_type pit = 0; pit < sl.pit(); ++pit) {
|
2018-02-12 16:11:09 +00:00
|
|
|
h += parMetrics(pit).height();
|
2007-09-02 09:44:08 +00:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-09-02 11:21:33 +00:00
|
|
|
// 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)
|
|
|
|
{
|
2013-04-25 21:27:10 +00:00
|
|
|
LASSERT(text_ == cur.text(), return false);
|
2007-09-02 11:21:33 +00:00
|
|
|
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)
|
|
|
|
{
|
2013-04-25 21:27:10 +00:00
|
|
|
LASSERT(text_ == cur.text(), return false);
|
2007-09-02 11:21:33 +00:00
|
|
|
// 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)
|
2014-06-30 09:45:24 +00:00
|
|
|
&& !cur.paragraph().isNewline(end-1)
|
|
|
|
&& !cur.paragraph().isEnvSeparator(end-1))
|
2007-09-02 11:21:33 +00:00
|
|
|
boundary = true;
|
|
|
|
else
|
|
|
|
--end;
|
2016-04-21 21:44:14 +00:00
|
|
|
} else if (cur.paragraph().isEnvSeparator(end-1))
|
|
|
|
--end;
|
2007-09-02 11:21:33 +00:00
|
|
|
return text_->setCursor(cur, cur.pit(), end, true, boundary);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void TextMetrics::deleteLineForward(Cursor & cur)
|
|
|
|
{
|
2013-04-25 21:27:10 +00:00
|
|
|
LASSERT(text_ == cur.text(), return);
|
2007-09-02 11:21:33 +00:00
|
|
|
if (cur.lastpos() == 0) {
|
2007-10-22 22:18:52 +00:00
|
|
|
// Paragraph is empty, so we just go forward
|
|
|
|
text_->cursorForward(cur);
|
2007-09-02 11:21:33 +00:00
|
|
|
} else {
|
|
|
|
cur.resetAnchor();
|
2016-02-28 16:36:29 +00:00
|
|
|
cur.selection(true); // to avoid deletion
|
2007-09-02 11:21:33 +00:00
|
|
|
cursorEnd(cur);
|
|
|
|
cur.setSelection();
|
|
|
|
// What is this test for ??? (JMarc)
|
|
|
|
if (!cur.selection())
|
|
|
|
text_->deleteWordForward(cur);
|
|
|
|
else
|
2018-07-22 20:18:50 +00:00
|
|
|
cap::cutSelection(cur, false);
|
2008-01-12 21:38:51 +00:00
|
|
|
cur.checkBufferStructure();
|
2007-09-02 11:21:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-07-12 08:25:54 +00:00
|
|
|
int TextMetrics::leftMargin(pit_type pit) const
|
2007-09-02 09:44:08 +00:00
|
|
|
{
|
2021-02-16 19:22:46 +00:00
|
|
|
// FIXME: what is the semantics? It depends on whether the
|
|
|
|
// paragraph is empty!
|
|
|
|
return leftMargin(pit, text_->paragraphs()[pit].size());
|
2007-09-02 09:44:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-07-12 08:25:54 +00:00
|
|
|
int TextMetrics::leftMargin(pit_type const pit, pos_type const pos) const
|
2007-09-02 09:44:08 +00:00
|
|
|
{
|
|
|
|
ParagraphList const & pars = text_->paragraphs();
|
|
|
|
|
2013-04-25 21:27:10 +00:00
|
|
|
LASSERT(pit >= 0, return 0);
|
|
|
|
LASSERT(pit < int(pars.size()), return 0);
|
2007-09-02 09:44:08 +00:00
|
|
|
Paragraph const & par = pars[pit];
|
2013-04-25 21:27:10 +00:00
|
|
|
LASSERT(pos >= 0, return 0);
|
2021-01-05 13:53:15 +00:00
|
|
|
// We do not really care whether pos > par.size(), since we do not
|
2021-02-16 19:22:46 +00:00
|
|
|
// access the data. It can be actually useful, when querying the
|
2021-01-05 13:53:15 +00:00
|
|
|
// margin without indentation (see leftMargin(pit_type).
|
|
|
|
|
2007-09-02 09:44:08 +00:00
|
|
|
Buffer const & buffer = bv_->buffer();
|
|
|
|
//lyxerr << "TextMetrics::leftMargin: pit: " << pit << " pos: " << pos << endl;
|
2008-02-28 01:42:02 +00:00
|
|
|
DocumentClass const & tclass = buffer.params().documentClass();
|
2008-03-06 21:31:27 +00:00
|
|
|
Layout const & layout = par.layout();
|
2016-02-28 16:01:01 +00:00
|
|
|
FontMetrics const & bfm = theFontMetrics(buffer.params().getFont());
|
2007-09-02 09:44:08 +00:00
|
|
|
|
2008-03-06 21:31:27 +00:00
|
|
|
docstring parindent = layout.parindent;
|
2007-09-02 09:44:08 +00:00
|
|
|
|
|
|
|
int l_margin = 0;
|
|
|
|
|
2018-07-09 01:38:00 +00:00
|
|
|
if (text_->isMainText()) {
|
2008-02-09 17:20:23 +00:00
|
|
|
l_margin += bv_->leftMargin();
|
2018-07-09 01:38:00 +00:00
|
|
|
l_margin += bfm.signedWidth(tclass.leftmargin());
|
|
|
|
}
|
2007-09-02 09:44:08 +00:00
|
|
|
|
2015-05-12 13:47:38 +00:00
|
|
|
int depth = par.getDepth();
|
|
|
|
if (depth != 0) {
|
2007-09-02 09:44:08 +00:00
|
|
|
// find the next level paragraph
|
2009-08-09 18:35:39 +00:00
|
|
|
pit_type newpar = text_->outerHook(pit);
|
2007-09-02 09:44:08 +00:00
|
|
|
if (newpar != pit_type(pars.size())) {
|
2008-03-06 21:31:27 +00:00
|
|
|
if (pars[newpar].layout().isEnvironment()) {
|
2015-05-12 13:47:38 +00:00
|
|
|
int nestmargin = depth * nestMargin();
|
|
|
|
if (text_->isMainText())
|
|
|
|
nestmargin += changebarMargin();
|
2017-07-12 08:25:54 +00:00
|
|
|
l_margin = max(leftMargin(newpar), nestmargin);
|
2014-05-22 21:47:38 +00:00
|
|
|
// Remove the parindent that has been added
|
|
|
|
// if the paragraph was empty.
|
2014-06-30 09:45:24 +00:00
|
|
|
if (pars[newpar].empty() &&
|
|
|
|
buffer.params().paragraph_separation ==
|
|
|
|
BufferParams::ParagraphIndentSeparation) {
|
2014-05-22 21:47:38 +00:00
|
|
|
docstring pi = pars[newpar].layout().parindent;
|
2016-02-28 16:01:01 +00:00
|
|
|
l_margin -= bfm.signedWidth(pi);
|
2014-05-22 21:47:38 +00:00
|
|
|
}
|
2007-09-02 09:44:08 +00:00
|
|
|
}
|
2008-03-21 11:26:20 +00:00
|
|
|
if (tclass.isDefaultLayout(par.layout())
|
2008-07-10 17:41:52 +00:00
|
|
|
|| tclass.isPlainLayout(par.layout())) {
|
2007-09-02 09:44:08 +00:00
|
|
|
if (pars[newpar].params().noindent())
|
|
|
|
parindent.erase();
|
|
|
|
else
|
2008-03-06 21:31:27 +00:00
|
|
|
parindent = pars[newpar].layout().parindent;
|
2007-09-02 09:44:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-16 13:11:08 +00:00
|
|
|
// This happens after sections or environments in standard classes.
|
|
|
|
// We have to check the previous layout at same depth.
|
2014-06-30 09:45:24 +00:00
|
|
|
if (buffer.params().paragraph_separation ==
|
|
|
|
BufferParams::ParagraphSkipSeparation)
|
|
|
|
parindent.erase();
|
|
|
|
else if (pit > 0 && pars[pit - 1].getDepth() >= par.getDepth()) {
|
2014-05-21 19:47:01 +00:00
|
|
|
pit_type prev = text_->depthHook(pit, par.getDepth());
|
2014-06-30 09:45:24 +00:00
|
|
|
if (par.layout() == pars[prev].layout()) {
|
|
|
|
if (prev != pit - 1
|
|
|
|
&& pars[pit - 1].layout().nextnoindent)
|
|
|
|
parindent.erase();
|
|
|
|
} else if (pars[prev].layout().nextnoindent)
|
2014-05-21 19:47:01 +00:00
|
|
|
parindent.erase();
|
2014-05-16 13:11:08 +00:00
|
|
|
}
|
2007-09-02 09:44:08 +00:00
|
|
|
|
2009-08-09 15:29:34 +00:00
|
|
|
FontInfo const labelfont = text_->labelFont(par);
|
2016-02-28 16:01:01 +00:00
|
|
|
FontMetrics const & lfm = theFontMetrics(labelfont);
|
2007-09-02 09:44:08 +00:00
|
|
|
|
2008-03-06 21:31:27 +00:00
|
|
|
switch (layout.margintype) {
|
2007-09-02 09:44:08 +00:00
|
|
|
case MARGIN_DYNAMIC:
|
2008-03-06 21:31:27 +00:00
|
|
|
if (!layout.leftmargin.empty()) {
|
2016-02-28 16:01:01 +00:00
|
|
|
l_margin += bfm.signedWidth(layout.leftmargin);
|
2007-09-02 09:44:08 +00:00
|
|
|
}
|
2008-02-23 16:45:38 +00:00
|
|
|
if (!par.labelString().empty()) {
|
2016-02-28 16:01:01 +00:00
|
|
|
l_margin += lfm.signedWidth(layout.labelindent);
|
|
|
|
l_margin += lfm.width(par.labelString());
|
|
|
|
l_margin += lfm.width(layout.labelsep);
|
2007-09-02 09:44:08 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MARGIN_MANUAL: {
|
2016-02-28 16:01:01 +00:00
|
|
|
l_margin += lfm.signedWidth(layout.labelindent);
|
2007-09-02 09:44:08 +00:00
|
|
|
// 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();
|
2016-02-28 16:01:01 +00:00
|
|
|
l_margin += lfm.width(labstr);
|
|
|
|
l_margin += lfm.width(layout.labelsep);
|
2007-09-02 09:44:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MARGIN_STATIC: {
|
2016-02-28 16:01:01 +00:00
|
|
|
l_margin += bfm.signedWidth(layout.leftmargin) * 4
|
|
|
|
/ (par.getDepth() + 4);
|
2007-09-02 09:44:08 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MARGIN_FIRST_DYNAMIC:
|
2008-03-06 21:31:27 +00:00
|
|
|
if (layout.labeltype == LABEL_MANUAL) {
|
2009-04-02 21:36:30 +00:00
|
|
|
// if we are at position 0, we are never in the body
|
|
|
|
if (pos > 0 && pos >= par.beginOfBody())
|
2016-02-28 16:01:01 +00:00
|
|
|
l_margin += lfm.signedWidth(layout.leftmargin);
|
2009-04-02 21:36:30 +00:00
|
|
|
else
|
2016-02-28 16:01:01 +00:00
|
|
|
l_margin += lfm.signedWidth(layout.labelindent);
|
2007-09-02 09:44:08 +00:00
|
|
|
} else if (pos != 0
|
|
|
|
// Special case to fix problems with
|
|
|
|
// theorems (JMarc)
|
2008-03-06 21:31:27 +00:00
|
|
|
|| (layout.labeltype == LABEL_STATIC
|
|
|
|
&& layout.latextype == LATEX_ENVIRONMENT
|
2009-08-09 18:35:39 +00:00
|
|
|
&& !text_->isFirstInSequence(pit))) {
|
2016-02-28 16:01:01 +00:00
|
|
|
l_margin += lfm.signedWidth(layout.leftmargin);
|
2013-02-09 16:13:01 +00:00
|
|
|
} else if (!layout.labelIsAbove()) {
|
2016-02-28 16:01:01 +00:00
|
|
|
l_margin += lfm.signedWidth(layout.labelindent);
|
|
|
|
l_margin += lfm.width(layout.labelsep);
|
|
|
|
l_margin += lfm.width(par.labelString());
|
2007-09-02 09:44:08 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2021-01-05 13:53:15 +00:00
|
|
|
case MARGIN_RIGHT_ADDRESS_BOX:
|
|
|
|
// This is handled globally in redoParagraph().
|
2007-09-02 09:44:08 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!par.params().leftIndent().zero())
|
2017-07-12 08:25:54 +00:00
|
|
|
l_margin += par.params().leftIndent().inPixels(max_width_, lfm.em());
|
2007-09-02 09:44:08 +00:00
|
|
|
|
2019-07-10 12:50:08 +00:00
|
|
|
LyXAlignment align = par.getAlign(bv_->buffer().params());
|
2007-09-02 09:44:08 +00:00
|
|
|
|
|
|
|
// set the correct parindent
|
|
|
|
if (pos == 0
|
2008-03-06 21:31:27 +00:00
|
|
|
&& (layout.labeltype == LABEL_NO_LABEL
|
2017-04-13 20:51:48 +00:00
|
|
|
|| layout.labeltype == LABEL_ABOVE
|
|
|
|
|| layout.labeltype == LABEL_CENTERED
|
|
|
|
|| (layout.labeltype == LABEL_STATIC
|
|
|
|
&& layout.latextype == LATEX_ENVIRONMENT
|
|
|
|
&& !text_->isFirstInSequence(pit)))
|
2011-02-14 00:58:44 +00:00
|
|
|
&& (align == LYX_ALIGN_BLOCK || align == LYX_ALIGN_LEFT)
|
2007-09-02 09:44:08 +00:00
|
|
|
&& !par.params().noindent()
|
|
|
|
// in some insets, paragraphs are never indented
|
2009-08-09 16:19:43 +00:00
|
|
|
&& !text_->inset().neverIndent()
|
2020-06-22 21:11:40 +00:00
|
|
|
// display style insets do not need indentation
|
2007-09-02 09:44:08 +00:00
|
|
|
&& !(!par.empty()
|
2021-01-05 13:53:15 +00:00
|
|
|
&& par.isInset(0)
|
2021-07-11 22:07:59 +00:00
|
|
|
&& par.getInset(0)->rowFlags() & Display)
|
2013-07-17 22:25:08 +00:00
|
|
|
&& (!(tclass.isDefaultLayout(par.layout())
|
2017-04-13 20:51:48 +00:00
|
|
|
|| tclass.isPlainLayout(par.layout()))
|
2014-03-14 13:22:26 +00:00
|
|
|
|| buffer.params().paragraph_separation
|
2013-07-17 22:25:08 +00:00
|
|
|
== BufferParams::ParagraphIndentSeparation)) {
|
2017-04-13 20:51:48 +00:00
|
|
|
/* use the parindent of the layout when the default
|
|
|
|
* indentation is used otherwise use the indentation set in
|
|
|
|
* the document settings
|
|
|
|
*/
|
|
|
|
if (buffer.params().getParIndent().empty())
|
|
|
|
l_margin += bfm.signedWidth(parindent);
|
|
|
|
else
|
|
|
|
l_margin += buffer.params().getParIndent().inPixels(max_width_, bfm.em());
|
|
|
|
}
|
2014-03-14 13:22:26 +00:00
|
|
|
|
2007-09-02 09:44:08 +00:00
|
|
|
return l_margin;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-08-27 22:36:20 +00:00
|
|
|
void TextMetrics::draw(PainterInfo & pi, int x, int y) const
|
|
|
|
{
|
2007-08-28 18:05:55 +00:00
|
|
|
if (par_metrics_.empty())
|
|
|
|
return;
|
2007-08-31 10:05:12 +00:00
|
|
|
|
2007-09-11 16:04:10 +00:00
|
|
|
origin_.x_ = x;
|
|
|
|
origin_.y_ = y;
|
2007-09-05 13:04:05 +00:00
|
|
|
|
2018-05-28 10:33:17 +00:00
|
|
|
y -= par_metrics_.begin()->second.ascent();
|
|
|
|
for (auto & pm_pair : par_metrics_) {
|
|
|
|
pit_type const pit = pm_pair.first;
|
|
|
|
ParagraphMetrics & pm = pm_pair.second;
|
|
|
|
y += pm.ascent();
|
2007-09-05 13:04:05 +00:00
|
|
|
// Save the paragraph position in the cache.
|
2018-05-28 10:33:17 +00:00
|
|
|
pm.setPosition(y);
|
2007-09-05 13:04:05 +00:00
|
|
|
drawParagraph(pi, pit, x, y);
|
2018-05-28 10:33:17 +00:00
|
|
|
y += pm.descent();
|
2007-08-27 22:36:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-08-28 08:57:13 +00:00
|
|
|
|
2014-07-27 15:30:57 +00:00
|
|
|
void TextMetrics::drawParagraph(PainterInfo & pi, pit_type const pit, int const x, int y) const
|
2007-08-28 08:57:13 +00:00
|
|
|
{
|
|
|
|
ParagraphMetrics const & pm = par_metrics_[pit];
|
|
|
|
if (pm.rows().empty())
|
|
|
|
return;
|
2007-11-16 23:00:57 +00:00
|
|
|
size_t const nrows = pm.rows().size();
|
2019-04-02 09:05:19 +00:00
|
|
|
// Remember left and right margin for drawing math numbers
|
2020-11-12 12:09:36 +00:00
|
|
|
Changer changeleft = changeVar(pi.leftx, x + leftMargin(pit));
|
|
|
|
Changer changeright = changeVar(pi.rightx, x + width() - rightMargin(pit));
|
2007-11-06 14:07:49 +00:00
|
|
|
|
2017-07-15 23:25:03 +00:00
|
|
|
// Use fast lane in nodraw stage.
|
|
|
|
if (pi.pain.isNull()) {
|
2016-02-29 15:07:35 +00:00
|
|
|
for (size_t i = 0; i != nrows; ++i) {
|
|
|
|
|
|
|
|
Row const & row = pm.rows()[i];
|
|
|
|
// Adapt to cursor row scroll offset if applicable.
|
|
|
|
int row_x = x - bv_->horizScrollOffset(text_, pit, row.pos());
|
|
|
|
if (i)
|
|
|
|
y += row.ascent();
|
|
|
|
|
2016-05-18 07:39:47 +00:00
|
|
|
RowPainter rp(pi, *text_, row, row_x, y);
|
2016-02-29 15:07:35 +00:00
|
|
|
|
|
|
|
rp.paintOnlyInsets();
|
|
|
|
y += row.descent();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int const ww = bv_->workHeight();
|
2008-03-03 11:03:47 +00:00
|
|
|
Cursor const & cur = bv_->cursor();
|
|
|
|
DocIterator sel_beg = cur.selectionBegin();
|
|
|
|
DocIterator sel_end = cur.selectionEnd();
|
|
|
|
bool selection = cur.selection()
|
|
|
|
// This is our text.
|
|
|
|
&& cur.text() == text_
|
2008-03-21 11:26:20 +00:00
|
|
|
// if the anchor is outside, this is not our selection
|
2010-04-15 17:49:15 +00:00
|
|
|
&& cur.normalAnchor().text() == text_
|
2008-03-03 11:03:47 +00:00
|
|
|
&& pit >= sel_beg.pit() && pit <= sel_end.pit();
|
|
|
|
|
Patch by Vincent that solves a number of problems related to the painting of a selection:
1. When a listing is inserted in a bit of text, the line above the listing is not drawn over the full width like it is done for lines above other insets. This is because InsetListing has a AlignLeft alignment. Now, if you start selecting downwards with the mouse in this empty area, strange selection drawings appear (see attachment).
This is caused by the fact that starting your selection at such a place, causes beg.boundary() to be true in TextMetrics::drawRowSelection(..). This is correct, but this value is true for _all_ selected lines. Now, the selection acts as if it is RTL text. Therefore, just like for end.boundary, this value needs to be reset for every line.
2. Starting your selection in an end margin often causes the selection in this end margin to be painted later. This is because when starting your selection in an end margin, you may have set a (possible empty) selection before really selecting the end margin. The problem is that the checksum (computed later) is the same for this empty selection and for the end margin selection. Therfore, we need a call to cur.setSelection() before evaluating cur.selection().
3. In the following two lines, it is assumed that there is only an end margin to be painted if the selection extends to the next paragraph. This is not true for the above described case of an AlignLeft Inset. Then, the margin has also be drawn within a paragraph
4. The end and begin margins are only painted when the selection extends into the following or previous paragraph. This difference is not resembled in the checksum if you first select a row completely and then procede to the next or previous paragraph as the selection remains at the end of a row. This also holds for the AlignLeft case. Therefore I added a term to the checksum to monitor whether the end and begin margins need to be drawn.
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@26399 a592a061-630c-0410-9148-cb99ea01b6c8
2008-09-14 14:32:40 +00:00
|
|
|
// We store the begin and end pos of the selection relative to this par
|
|
|
|
DocIterator sel_beg_par = cur.selectionBegin();
|
|
|
|
DocIterator sel_end_par = cur.selectionEnd();
|
2014-03-14 13:22:26 +00:00
|
|
|
|
2008-03-03 11:03:47 +00:00
|
|
|
// We care only about visible selection.
|
|
|
|
if (selection) {
|
|
|
|
if (pit != sel_beg.pit()) {
|
Patch by Vincent that solves a number of problems related to the painting of a selection:
1. When a listing is inserted in a bit of text, the line above the listing is not drawn over the full width like it is done for lines above other insets. This is because InsetListing has a AlignLeft alignment. Now, if you start selecting downwards with the mouse in this empty area, strange selection drawings appear (see attachment).
This is caused by the fact that starting your selection at such a place, causes beg.boundary() to be true in TextMetrics::drawRowSelection(..). This is correct, but this value is true for _all_ selected lines. Now, the selection acts as if it is RTL text. Therefore, just like for end.boundary, this value needs to be reset for every line.
2. Starting your selection in an end margin often causes the selection in this end margin to be painted later. This is because when starting your selection in an end margin, you may have set a (possible empty) selection before really selecting the end margin. The problem is that the checksum (computed later) is the same for this empty selection and for the end margin selection. Therfore, we need a call to cur.setSelection() before evaluating cur.selection().
3. In the following two lines, it is assumed that there is only an end margin to be painted if the selection extends to the next paragraph. This is not true for the above described case of an AlignLeft Inset. Then, the margin has also be drawn within a paragraph
4. The end and begin margins are only painted when the selection extends into the following or previous paragraph. This difference is not resembled in the checksum if you first select a row completely and then procede to the next or previous paragraph as the selection remains at the end of a row. This also holds for the AlignLeft case. Therefore I added a term to the checksum to monitor whether the end and begin margins need to be drawn.
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@26399 a592a061-630c-0410-9148-cb99ea01b6c8
2008-09-14 14:32:40 +00:00
|
|
|
sel_beg_par.pit() = pit;
|
|
|
|
sel_beg_par.pos() = 0;
|
2008-03-03 11:03:47 +00:00
|
|
|
}
|
|
|
|
if (pit != sel_end.pit()) {
|
Patch by Vincent that solves a number of problems related to the painting of a selection:
1. When a listing is inserted in a bit of text, the line above the listing is not drawn over the full width like it is done for lines above other insets. This is because InsetListing has a AlignLeft alignment. Now, if you start selecting downwards with the mouse in this empty area, strange selection drawings appear (see attachment).
This is caused by the fact that starting your selection at such a place, causes beg.boundary() to be true in TextMetrics::drawRowSelection(..). This is correct, but this value is true for _all_ selected lines. Now, the selection acts as if it is RTL text. Therefore, just like for end.boundary, this value needs to be reset for every line.
2. Starting your selection in an end margin often causes the selection in this end margin to be painted later. This is because when starting your selection in an end margin, you may have set a (possible empty) selection before really selecting the end margin. The problem is that the checksum (computed later) is the same for this empty selection and for the end margin selection. Therfore, we need a call to cur.setSelection() before evaluating cur.selection().
3. In the following two lines, it is assumed that there is only an end margin to be painted if the selection extends to the next paragraph. This is not true for the above described case of an AlignLeft Inset. Then, the margin has also be drawn within a paragraph
4. The end and begin margins are only painted when the selection extends into the following or previous paragraph. This difference is not resembled in the checksum if you first select a row completely and then procede to the next or previous paragraph as the selection remains at the end of a row. This also holds for the AlignLeft case. Therefore I added a term to the checksum to monitor whether the end and begin margins need to be drawn.
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@26399 a592a061-630c-0410-9148-cb99ea01b6c8
2008-09-14 14:32:40 +00:00
|
|
|
sel_end_par.pit() = pit;
|
|
|
|
sel_end_par.pos() = sel_end_par.lastpos();
|
2008-03-03 11:03:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-02 09:05:19 +00:00
|
|
|
if (text_->isRTL(pit))
|
|
|
|
swap(pi.leftx, pi.rightx);
|
|
|
|
|
2021-02-26 18:05:35 +00:00
|
|
|
BookmarksSection::BookmarkPosList bpl =
|
|
|
|
theSession().bookmarks().bookmarksInPar(bv_->buffer().fileName(), pm.par().id());
|
|
|
|
|
2007-11-16 22:52:15 +00:00
|
|
|
for (size_t i = 0; i != nrows; ++i) {
|
2007-11-16 23:00:57 +00:00
|
|
|
|
2007-11-16 22:52:15 +00:00
|
|
|
Row const & row = pm.rows()[i];
|
2016-02-29 15:05:06 +00:00
|
|
|
// Adapt to cursor row scroll offset if applicable.
|
|
|
|
int row_x = x - bv_->horizScrollOffset(text_, pit, row.pos());
|
2007-11-16 22:52:15 +00:00
|
|
|
if (i)
|
|
|
|
y += row.ascent();
|
|
|
|
|
2016-02-29 15:07:35 +00:00
|
|
|
// It is not needed to draw on screen if we are not inside.
|
2007-11-16 22:52:15 +00:00
|
|
|
bool const inside = (y + row.descent() >= 0
|
|
|
|
&& y - row.ascent() < ww);
|
2016-02-29 15:07:35 +00:00
|
|
|
if (!inside) {
|
2017-07-15 23:25:03 +00:00
|
|
|
// Inset positions have already been set in nodraw stage.
|
2016-02-29 15:07:35 +00:00
|
|
|
y += row.descent();
|
|
|
|
continue;
|
|
|
|
}
|
Keyboard based horizontal scrolling for wide insets
[This commit is the output of the "horizontal scrolling" GSoC 2013
project, by Hashini Senaratne. The code has been cleaned up, some
variables have been renamed and moved from the Cursor class to
BufferView::Private. This is the base from which I (jmarc) will polish
the feature for landing on master.
Below is the original commit log of Hashini, updated to reflect the
changes that have been done.]
This feature also applicable for other insets; graphics and labels.
This implementation is capable of scrolling a single row when reaching
its content which is beyond the screen limits, using left and right
arrow keys.
The attribute 'horiz_scroll_offset_' introduced in the
BufferView::Private class plays a main role in horizontal scrolling of
the wide rows that grow beyond the screen limits. This attribute
represents by how much pixels the current row that the text cursor
lies in should be get scrolled.
The main logic that is responsible for drawing the scrolled rows is
within the BufferView class, BufferView::checkCursorScrollOffset.
* The main logic is called via BufferView::draw.
* What this does is set the horiz_scroll_offset_ attribute in in order to
show the position that the text cursor lies in.
* To make sure that BufferView::draw gets involved when Update flag is
FitCursor, necessary changes are made in BufferView::processUpdateFlags.
Basically what the logic that used to set the horiz_scroll_offset_
does is,
* The row which the text cursor lies in is identified by a
CursorSlice that points to the beginning of the row. This is the
'rowSlice' variable used in BufferView::checkCursorScrollOffset. Acessors
are added to obtain this variable. Here row objects were not used to
identify the current row, because it appears that row objects can
disappear when doing a decoration update for example. This means that
comparing row pointers is not a good idea, because they can change
without notice.
* Stop calculations of horiz_scroll_offset_ variable, if metrics have not been
computed yet. Otherwise the calls to TextMetrics::parMetrics, calls
redoParagraph and may change the row heigths. Therefore vertical scrolling
feature may get disturbed. This is avoided.
* Using BufferView::::setCurrentRowSlice resets horiz_scroll_offset_
when changing cursor row. This is done in order to prevent unwanted
scrolling that happens when changing the selected row using up and
down arrow keys.
* Recompute inset positions before checking scoll offset of the row, by
painting the row insets with drawing disabled. This is done because the
position of insets is computed within the drawing procedure.
* Current x position of the text cursor is compared with the
horiz_scroll_offset_ value and the other variables like row.width(),
bv.workWidth(). Compute the new horiz_scroll_offset_ value in order
to show where the text cursor lies in. The basics conditions that we
check before recomputing it are, if the text cursor lies rightward to
the current right screen boundary, if the text cursor lies leftward
to the current left screen boundary, if the text cursor lies within
screen boundaries but the length of the row is less than the left
boundary of the screen (this happens when we delete some content of
the row using delete key or backspace key).
* Change update strategy when scrooll offset has changed. This allows to
redraw the row when no drawing was scheduled. By doing so, it was
possible to redraw a wide row when moving to the leftmost position of the
wide row, from the leftmost position of the row below, using the left
arrow key.
In TextMetrics::drawParagraph it is checked whether the current row is
what is drawing now. If it is so, the value used to the x value of the row
for drawing is adapted according to BufferView::horizScrollOffset.
The method used to pass boundary() was fixed to get row when cursor was in
a nested inset. This matter is considered in Cursor::textRow and it is
modified accordingly.
GuiWorkArea::Private::showCursor() is modified to show the cursor position
in a scrolled row.
2014-07-26 11:17:28 +00:00
|
|
|
|
2008-03-03 11:03:47 +00:00
|
|
|
if (selection)
|
Patch by Vincent that solves a number of problems related to the painting of a selection:
1. When a listing is inserted in a bit of text, the line above the listing is not drawn over the full width like it is done for lines above other insets. This is because InsetListing has a AlignLeft alignment. Now, if you start selecting downwards with the mouse in this empty area, strange selection drawings appear (see attachment).
This is caused by the fact that starting your selection at such a place, causes beg.boundary() to be true in TextMetrics::drawRowSelection(..). This is correct, but this value is true for _all_ selected lines. Now, the selection acts as if it is RTL text. Therefore, just like for end.boundary, this value needs to be reset for every line.
2. Starting your selection in an end margin often causes the selection in this end margin to be painted later. This is because when starting your selection in an end margin, you may have set a (possible empty) selection before really selecting the end margin. The problem is that the checksum (computed later) is the same for this empty selection and for the end margin selection. Therfore, we need a call to cur.setSelection() before evaluating cur.selection().
3. In the following two lines, it is assumed that there is only an end margin to be painted if the selection extends to the next paragraph. This is not true for the above described case of an AlignLeft Inset. Then, the margin has also be drawn within a paragraph
4. The end and begin margins are only painted when the selection extends into the following or previous paragraph. This difference is not resembled in the checksum if you first select a row completely and then procede to the next or previous paragraph as the selection remains at the end of a row. This also holds for the AlignLeft case. Therefore I added a term to the checksum to monitor whether the end and begin margins need to be drawn.
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@26399 a592a061-630c-0410-9148-cb99ea01b6c8
2008-09-14 14:32:40 +00:00
|
|
|
row.setSelectionAndMargins(sel_beg_par, sel_end_par);
|
2008-03-03 11:03:47 +00:00
|
|
|
else
|
2018-01-15 15:14:21 +00:00
|
|
|
row.clearSelectionAndMargins();
|
2014-03-14 13:22:26 +00:00
|
|
|
|
Patch by Vincent that solves a number of problems related to the painting of a selection:
1. When a listing is inserted in a bit of text, the line above the listing is not drawn over the full width like it is done for lines above other insets. This is because InsetListing has a AlignLeft alignment. Now, if you start selecting downwards with the mouse in this empty area, strange selection drawings appear (see attachment).
This is caused by the fact that starting your selection at such a place, causes beg.boundary() to be true in TextMetrics::drawRowSelection(..). This is correct, but this value is true for _all_ selected lines. Now, the selection acts as if it is RTL text. Therefore, just like for end.boundary, this value needs to be reset for every line.
2. Starting your selection in an end margin often causes the selection in this end margin to be painted later. This is because when starting your selection in an end margin, you may have set a (possible empty) selection before really selecting the end margin. The problem is that the checksum (computed later) is the same for this empty selection and for the end margin selection. Therfore, we need a call to cur.setSelection() before evaluating cur.selection().
3. In the following two lines, it is assumed that there is only an end margin to be painted if the selection extends to the next paragraph. This is not true for the above described case of an AlignLeft Inset. Then, the margin has also be drawn within a paragraph
4. The end and begin margins are only painted when the selection extends into the following or previous paragraph. This difference is not resembled in the checksum if you first select a row completely and then procede to the next or previous paragraph as the selection remains at the end of a row. This also holds for the AlignLeft case. Therefore I added a term to the checksum to monitor whether the end and begin margins need to be drawn.
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@26399 a592a061-630c-0410-9148-cb99ea01b6c8
2008-09-14 14:32:40 +00:00
|
|
|
// The row knows nothing about the paragraph, so we have to check
|
|
|
|
// whether this row is the first or last and update the margins.
|
|
|
|
if (row.selection()) {
|
|
|
|
if (row.sel_beg == 0)
|
2017-11-29 10:16:09 +00:00
|
|
|
row.change(row.begin_margin_sel, sel_beg.pit() < pit);
|
Patch by Vincent that solves a number of problems related to the painting of a selection:
1. When a listing is inserted in a bit of text, the line above the listing is not drawn over the full width like it is done for lines above other insets. This is because InsetListing has a AlignLeft alignment. Now, if you start selecting downwards with the mouse in this empty area, strange selection drawings appear (see attachment).
This is caused by the fact that starting your selection at such a place, causes beg.boundary() to be true in TextMetrics::drawRowSelection(..). This is correct, but this value is true for _all_ selected lines. Now, the selection acts as if it is RTL text. Therefore, just like for end.boundary, this value needs to be reset for every line.
2. Starting your selection in an end margin often causes the selection in this end margin to be painted later. This is because when starting your selection in an end margin, you may have set a (possible empty) selection before really selecting the end margin. The problem is that the checksum (computed later) is the same for this empty selection and for the end margin selection. Therfore, we need a call to cur.setSelection() before evaluating cur.selection().
3. In the following two lines, it is assumed that there is only an end margin to be painted if the selection extends to the next paragraph. This is not true for the above described case of an AlignLeft Inset. Then, the margin has also be drawn within a paragraph
4. The end and begin margins are only painted when the selection extends into the following or previous paragraph. This difference is not resembled in the checksum if you first select a row completely and then procede to the next or previous paragraph as the selection remains at the end of a row. This also holds for the AlignLeft case. Therefore I added a term to the checksum to monitor whether the end and begin margins need to be drawn.
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@26399 a592a061-630c-0410-9148-cb99ea01b6c8
2008-09-14 14:32:40 +00:00
|
|
|
if (row.sel_end == sel_end_par.lastpos())
|
2017-11-29 10:16:09 +00:00
|
|
|
row.change(row.end_margin_sel, sel_end.pit() > pit);
|
Patch by Vincent that solves a number of problems related to the painting of a selection:
1. When a listing is inserted in a bit of text, the line above the listing is not drawn over the full width like it is done for lines above other insets. This is because InsetListing has a AlignLeft alignment. Now, if you start selecting downwards with the mouse in this empty area, strange selection drawings appear (see attachment).
This is caused by the fact that starting your selection at such a place, causes beg.boundary() to be true in TextMetrics::drawRowSelection(..). This is correct, but this value is true for _all_ selected lines. Now, the selection acts as if it is RTL text. Therefore, just like for end.boundary, this value needs to be reset for every line.
2. Starting your selection in an end margin often causes the selection in this end margin to be painted later. This is because when starting your selection in an end margin, you may have set a (possible empty) selection before really selecting the end margin. The problem is that the checksum (computed later) is the same for this empty selection and for the end margin selection. Therfore, we need a call to cur.setSelection() before evaluating cur.selection().
3. In the following two lines, it is assumed that there is only an end margin to be painted if the selection extends to the next paragraph. This is not true for the above described case of an AlignLeft Inset. Then, the margin has also be drawn within a paragraph
4. The end and begin margins are only painted when the selection extends into the following or previous paragraph. This difference is not resembled in the checksum if you first select a row completely and then procede to the next or previous paragraph as the selection remains at the end of a row. This also holds for the AlignLeft case. Therefore I added a term to the checksum to monitor whether the end and begin margins need to be drawn.
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@26399 a592a061-630c-0410-9148-cb99ea01b6c8
2008-09-14 14:32:40 +00:00
|
|
|
}
|
2008-03-03 11:03:47 +00:00
|
|
|
|
2009-08-10 20:54:22 +00:00
|
|
|
// Take this opportunity to spellcheck the row contents.
|
2019-02-21 16:52:36 +00:00
|
|
|
if (row.changed() && pi.do_spellcheck && lyxrc.spellcheck_continuously) {
|
2010-09-14 05:24:04 +00:00
|
|
|
text_->getPar(pit).spellCheck();
|
2009-08-10 20:54:22 +00:00
|
|
|
}
|
|
|
|
|
2017-07-15 23:25:03 +00:00
|
|
|
RowPainter rp(pi, *text_, row, row_x, y);
|
|
|
|
|
2007-11-17 11:27:03 +00:00
|
|
|
// Don't paint the row if a full repaint has not been requested
|
2007-11-19 14:13:06 +00:00
|
|
|
// and if it has not changed.
|
2019-02-21 16:52:36 +00:00
|
|
|
if (!pi.full_repaint && !row.changed()) {
|
2007-11-17 11:27:03 +00:00
|
|
|
// Paint only the insets if the text itself is
|
2007-08-30 13:19:24 +00:00
|
|
|
// unchanged.
|
|
|
|
rp.paintOnlyInsets();
|
2021-04-05 17:07:15 +00:00
|
|
|
rp.paintTooLargeMarks(
|
|
|
|
row_x + row.left_x() < bv_->leftMargin(),
|
|
|
|
row_x + row.right_x() > bv_->workWidth() - bv_->rightMargin());
|
2017-11-11 11:40:39 +00:00
|
|
|
row.changed(false);
|
2007-11-16 22:52:15 +00:00
|
|
|
y += row.descent();
|
2007-08-30 13:19:24 +00:00
|
|
|
continue;
|
|
|
|
}
|
2007-08-28 08:57:13 +00:00
|
|
|
|
2007-11-17 11:27:03 +00:00
|
|
|
// Clear background of this row if paragraph background was not
|
|
|
|
// already cleared because of a full repaint.
|
2019-02-21 16:52:36 +00:00
|
|
|
if (!pi.full_repaint && row.changed()) {
|
2014-07-27 15:30:57 +00:00
|
|
|
LYXERR(Debug::PAINTING, "Clear rect@("
|
2015-01-10 17:00:13 +00:00
|
|
|
<< max(row_x, 0) << ", " << y - row.ascent() << ")="
|
2014-07-26 20:25:48 +00:00
|
|
|
<< width() << " x " << row.height());
|
2020-11-21 19:00:26 +00:00
|
|
|
pi.pain.fillRectangle(row_x, y - row.ascent(),
|
|
|
|
width(), row.height(), pi.background_color);
|
2007-09-01 09:24:20 +00:00
|
|
|
}
|
2014-03-14 13:22:26 +00:00
|
|
|
|
2007-08-30 13:19:24 +00:00
|
|
|
// Instrumentation for testing row cache (see also
|
|
|
|
// 12 lines lower):
|
2016-02-29 15:07:35 +00:00
|
|
|
if (lyxerr.debugging(Debug::PAINTING)
|
2019-02-21 16:52:36 +00:00
|
|
|
&& (row.selection() || pi.full_repaint || row.changed())) {
|
2017-10-11 15:39:02 +00:00
|
|
|
string const foreword = text_->isMainText() ? "main text redraw "
|
|
|
|
: "inset text redraw: ";
|
|
|
|
LYXERR0(foreword << "pit=" << pit << " row=" << i
|
2017-10-11 16:00:48 +00:00
|
|
|
<< (row.selection() ? " row_selection": "")
|
|
|
|
<< (pi.full_repaint ? " full_repaint" : "")
|
2019-02-21 16:52:36 +00:00
|
|
|
<< (row.changed() ? " row.changed" : ""));
|
2007-08-28 08:57:13 +00:00
|
|
|
}
|
2007-09-01 09:24:20 +00:00
|
|
|
|
|
|
|
// Backup full_repaint status and force full repaint
|
|
|
|
// for inner insets as the Row has been cleared out.
|
|
|
|
bool tmp = pi.full_repaint;
|
|
|
|
pi.full_repaint = true;
|
2010-11-25 13:16:30 +00:00
|
|
|
|
|
|
|
rp.paintSelection();
|
2007-08-30 13:19:24 +00:00
|
|
|
rp.paintAppendix();
|
|
|
|
rp.paintDepthBar();
|
2017-11-11 10:57:39 +00:00
|
|
|
if (row.needsChangeBar())
|
|
|
|
rp.paintChangeBar();
|
2019-02-04 11:13:01 +00:00
|
|
|
if (i == 0)
|
2007-08-30 13:19:24 +00:00
|
|
|
rp.paintFirst();
|
2019-02-04 11:13:01 +00:00
|
|
|
if (i == nrows - 1)
|
2009-08-19 22:43:52 +00:00
|
|
|
rp.paintLast();
|
2007-08-30 13:19:24 +00:00
|
|
|
rp.paintText();
|
2021-04-05 17:07:15 +00:00
|
|
|
rp.paintTooLargeMarks(
|
|
|
|
row_x + row.left_x() < bv_->leftMargin(),
|
|
|
|
row_x + row.right_x() > bv_->workWidth() - bv_->rightMargin());
|
2021-02-26 18:05:35 +00:00
|
|
|
// indicate bookmarks presence in margin
|
2021-04-06 13:19:12 +00:00
|
|
|
if (lyxrc.bookmarks_visibility == LyXRC::BMK_MARGIN)
|
|
|
|
for (auto const & bp_p : bpl)
|
|
|
|
if (bp_p.second >= row.pos() && bp_p.second < row.endpos())
|
|
|
|
rp.paintBookmark(bp_p.first);
|
2021-02-26 18:05:35 +00:00
|
|
|
|
2007-11-16 22:52:15 +00:00
|
|
|
y += row.descent();
|
2010-11-25 13:16:30 +00:00
|
|
|
|
2017-11-23 14:38:17 +00:00
|
|
|
#if 0
|
|
|
|
// This debug code shows on screen which rows are repainted.
|
|
|
|
// FIXME: since the updates related to caret blinking restrict
|
|
|
|
// the painter to a small rectangle, the numbers are not
|
|
|
|
// updated when this happens. Change the code in
|
|
|
|
// GuiWorkArea::Private::show/hideCaret if this is important.
|
|
|
|
static int count = 0;
|
|
|
|
++count;
|
|
|
|
FontInfo fi(sane_font);
|
2019-06-14 14:42:02 +00:00
|
|
|
fi.setSize(TINY_SIZE);
|
2017-11-23 14:38:17 +00:00
|
|
|
fi.setColor(Color_red);
|
|
|
|
pi.pain.text(row_x, y, convert<docstring>(count), fi);
|
|
|
|
#endif
|
|
|
|
|
2007-09-01 09:24:20 +00:00
|
|
|
// Restore full_repaint status.
|
|
|
|
pi.full_repaint = tmp;
|
2017-11-11 11:40:39 +00:00
|
|
|
|
|
|
|
row.changed(false);
|
2007-08-28 08:57:13 +00:00
|
|
|
}
|
|
|
|
|
2007-11-28 22:12:03 +00:00
|
|
|
//LYXERR(Debug::PAINTING, ".");
|
2007-08-28 08:57:13 +00:00
|
|
|
}
|
2007-08-27 22:36:20 +00:00
|
|
|
|
2007-08-31 10:05:12 +00:00
|
|
|
|
2008-03-21 11:26:20 +00:00
|
|
|
void TextMetrics::completionPosAndDim(Cursor const & cur, int & x, int & y,
|
2008-03-15 12:22:28 +00:00
|
|
|
Dimension & dim) const
|
|
|
|
{
|
2020-09-22 13:06:46 +00:00
|
|
|
DocIterator from = cur.bv().cursor();
|
|
|
|
DocIterator to = from;
|
|
|
|
text_->getWord(from.top(), to.top(), PREVIOUS_WORD);
|
2008-03-21 11:26:20 +00:00
|
|
|
|
2020-09-22 13:06:46 +00:00
|
|
|
// The vertical dimension of the word
|
|
|
|
Font const font = displayFont(cur.pit(), from.pos());
|
|
|
|
FontMetrics const & fm = theFontMetrics(font);
|
|
|
|
// the +1's below are related to the extra pixels added in setRowHeight
|
|
|
|
dim.asc = fm.maxAscent() + 1;
|
|
|
|
dim.des = fm.maxDescent() + 1;
|
2016-05-20 15:14:54 +00:00
|
|
|
|
|
|
|
// get position on screen of the word start and end
|
|
|
|
//FIXME: Is it necessary to explicitly set this to false?
|
2020-09-22 13:06:46 +00:00
|
|
|
from.boundary(false);
|
|
|
|
Point lxy = cur.bv().getPos(from);
|
|
|
|
Point rxy = cur.bv().getPos(to);
|
2008-03-15 12:22:28 +00:00
|
|
|
dim.wid = abs(rxy.x_ - lxy.x_);
|
2008-03-21 11:26:20 +00:00
|
|
|
|
2008-03-15 12:22:28 +00:00
|
|
|
// calculate position of word
|
|
|
|
y = lxy.y_;
|
|
|
|
x = min(rxy.x_, lxy.x_);
|
2008-03-21 11:26:20 +00:00
|
|
|
|
2008-03-15 12:22:28 +00:00
|
|
|
//lyxerr << "wid=" << dim.width() << " x=" << x << " y=" << y << " lxy.x_=" << lxy.x_ << " rxy.x_=" << rxy.x_ << " word=" << word << std::endl;
|
|
|
|
//lyxerr << " wordstart=" << wordStart << " bvcur=" << bvcur << " cur=" << cur << std::endl;
|
|
|
|
}
|
|
|
|
|
2006-12-29 23:54:48 +00:00
|
|
|
int defaultRowHeight()
|
|
|
|
{
|
2019-05-13 08:47:47 +00:00
|
|
|
return int(theFontMetrics(sane_font).maxHeight() * 1.2);
|
2006-12-29 23:54:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace lyx
|