Commit 2598936d authored by twanvl's avatar twanvl

Improved search algorithm for determining text size:

 - check using previous scale, often gives correct answer when typing
 - binary search + bound estimation otherwise
This allows the step size to be decreased, giving a better fit.
parent 22c94714
...@@ -568,7 +568,7 @@ void TextValueEditor::onValueChange() { ...@@ -568,7 +568,7 @@ void TextValueEditor::onValueChange() {
} }
void TextValueEditor::onAction(const Action& action, bool undone) { void TextValueEditor::onAction(const Action& action, bool undone) {
TextValueViewer::onValueChange(); TextValueViewer::onAction(action, undone);
findWordLists(); findWordLists();
TYPE_CASE(action, TextValueAction) { TYPE_CASE(action, TextValueAction) {
selection_start = action.selection_start; selection_start = action.selection_start;
...@@ -1001,7 +1001,7 @@ void TextValueEditor::determineSize(bool force_fit) { ...@@ -1001,7 +1001,7 @@ void TextValueEditor::determineSize(bool force_fit) {
(int)pos.y - 1, (int)pos.y - 1,
(int)sbw, (int)sbw,
(int)rot.trY(style().height) + 2); (int)rot.trY(style().height) + 2);
v.reset(); v.reset(true);
} else { } else {
// Height depends on font // Height depends on font
wxMemoryDC dc; wxMemoryDC dc;
......
...@@ -54,5 +54,5 @@ double FontTextElement::minScale() const { ...@@ -54,5 +54,5 @@ double FontTextElement::minScale() const {
return min(font->size(), font->scale_down_to) / max(0.01, font->size()); return min(font->size(), font->scale_down_to) / max(0.01, font->size());
} }
double FontTextElement::scaleStep() const { double FontTextElement::scaleStep() const {
return 1. / max(font->size(), 1.); return 1. / max(font->size() * 4, 1.);
} }
...@@ -28,5 +28,5 @@ double SymbolTextElement::minScale() const { ...@@ -28,5 +28,5 @@ double SymbolTextElement::minScale() const {
return min(font.size, font.scale_down_to) / max(0.01, font.size); return min(font.size, font.scale_down_to) / max(0.01, font.size);
} }
double SymbolTextElement::scaleStep() const { double SymbolTextElement::scaleStep() const {
return 1. / max(font.size, 1.); return 1. / max(font.size * 4, 1.);
} }
...@@ -156,9 +156,10 @@ bool TextViewer::prepare(RotatedDC& dc, const String& text, TextStyle& style, Co ...@@ -156,9 +156,10 @@ bool TextViewer::prepare(RotatedDC& dc, const String& text, TextStyle& style, Co
return false; return false;
} }
} }
void TextViewer::reset() { void TextViewer::reset(bool related) {
elements.clear(); elements.clear();
lines.clear(); lines.clear();
if (!related) scale = 1.0;
} }
bool TextViewer::prepared() const { bool TextViewer::prepared() const {
return !lines.empty(); return !lines.empty();
...@@ -335,55 +336,8 @@ void TextViewer::prepareElements(const String& text, const TextStyle& style, Con ...@@ -335,55 +336,8 @@ void TextViewer::prepareElements(const String& text, const TextStyle& style, Con
// ----------------------------------------------------------------------------- : Layout // ----------------------------------------------------------------------------- : Layout
void TextViewer::prepareLines(RotatedDC& dc, const String& text, TextStyle& style, Context& ctx) { void TextViewer::prepareLines(RotatedDC& dc, const String& text, TextStyle& style, Context& ctx) {
// try to layout, at different scales
vector<CharInfo> chars; vector<CharInfo> chars;
scale = 1; prepareLinesTryScales(dc, text, style, chars);
double min_scale = elements.minScale();
double scale_step = max(0.05,elements.scaleStep());
while (true) {
double next_scale = scale - scale_step;
bool last = next_scale < min_scale;
// fits?
chars.clear();
elements.getCharInfo(dc, scale, 0, text.size(), chars);
bool fits = prepareLinesScale(dc, chars, style, last);
if (fits && (lines.empty() || lines.back().bottom() <= dc.getInternalSize().height - style.padding_bottom)) {
break; // text fits in box
}
if (last) break;
// TODO: smarter iteration
scale = next_scale;
}
/*
double scale_1 = 1.
double fit_1 = fitLines(dc, text, style, scale_1);
if (fit_1 <= 0 || scale_1 >= scale_2) {
// ok
} else {
// find best text size, using the 'false position' root finding method
double scale_2 = elements.minScale();
double fit_2 = fitLines(dc, text, style, scale_2);
if (fit_2 > 0) {
// still doesn't fit at smallest size
} else {
// invariant: fit_1 > 0 && fit_2 <= 0
while (abs(scale_2 - scale_1) > 0.01) {
double scale_3 = scale_2 - fit_2 * (scale_2 - scale_1)/(fit_2 - fit_1);
double fit_3 = fitLines(dc, text, style, scale_3);
if (fit_3 > 0) {
scale_2 = scale_3;
fit_2 = fit_3;
} else {
scale_2 = scale_3;
fit_2 = fit_3;
}
}
}
}
// returns negative values if it fits, positive if it doesn't
*/
// store information about the content/layout, allow this to change alignment // store information about the content/layout, allow this to change alignment
style.content_width = 0; style.content_width = 0;
...@@ -420,7 +374,122 @@ void TextViewer::prepareLines(RotatedDC& dc, const String& text, TextStyle& styl ...@@ -420,7 +374,122 @@ void TextViewer::prepareLines(RotatedDC& dc, const String& text, TextStyle& styl
} }
} }
bool TextViewer::prepareLinesScale(RotatedDC& dc, const vector<CharInfo>& chars, const TextStyle& style, bool stop_if_too_long) { // bound on max_scale, given that scale fits and produces the given lines
inline double bound_on_max_scale(RotatedDC& dc, const TextStyle& style, const vector<TextViewer::Line>& lines, double scale) {
double tot_height = dc.getInternalSize().height + 1;
double height = min(tot_height, lines.back().bottom() + style.padding_bottom);
return scale * tot_height / height;
}
// bound on min_scale, given that scale doesn't fit and produces the given lines
inline double bound_on_min_scale(RotatedDC& dc, const TextStyle& style, const vector<TextViewer::Line>& lines, double scale) {
double tot_height = dc.getInternalSize().height;
double height = lines.back().bottom() + style.padding_bottom;
return scale * tot_height / height;
}
void TextViewer::prepareLinesTryScales(RotatedDC& dc, const String& text, const TextStyle& style, vector<CharInfo>& chars) {
// Bounds
double min_scale = elements.minScale();
double scale_step = max(0.01,elements.scaleStep());
// Is there any scaling (common case is: no)
if (min_scale >= 1.0) {
scale = 1.0;
elements.getCharInfo(dc, scale, 0, text.size(), chars);
prepareLinesScale(dc, chars, style, false, lines);
return;
}
// More complicated fitting
double max_scale = 1.0 + scale_step;
double best_scale;
// Assumption:
// It is likely that the text should have the same scale as the previous render attempt
// So:
// - try that scale first
// - if it fits
// - change min_scale
// - then try the scale just before it
// - if that doesn't fit, we are done
// - if it doesn't, we have just (almost) wasted 1 cycle, start binary search
// - if it doesn't
// - change max_scale
// Try the layout at the previous scale, this could give a quick upper bound
elements.getCharInfo(dc, scale, 0, text.size(), chars);
bool fits = prepareLinesScale(dc, chars, style, true, lines);
if (fits) {
min_scale = scale;
max_scale = min(max_scale, bound_on_max_scale(dc,style,lines,scale));
// is there a before?
if (scale + scale_step >= max_scale) return;
// try just before
scale += scale_step;
vector<Line> lines_before;
vector<CharInfo> chars_before;
elements.getCharInfo(dc, scale, 0, text.size(), chars_before);
fits = prepareLinesScale(dc, chars_before, style, true, lines_before);
if (fits) {
// too bad
swap(lines, lines_before);
swap(chars, chars_before);
best_scale = min_scale = scale;
max_scale = min(max_scale, bound_on_max_scale(dc,style,lines,scale));
} else {
// yay
scale = min_scale;
return;
}
} else {
max_scale = scale;
min_scale = max(min_scale, bound_on_min_scale(dc,style,lines,scale));
// ensure invariant d (below)
best_scale = scale = min_scale;
chars.clear();
elements.getCharInfo(dc, scale, 0, text.size(), chars);
prepareLinesScale(dc, chars, style, false, lines);
max_scale = min(max_scale, bound_on_max_scale(dc,style,lines,scale));
}
// The common case optimization fialed, try a binary search
// Invariant:
// a. The text fits at min_scale (or we force it anyway)
// b. but not at max_scale
// c. 0 < min_scale <= real_scale < max_scale <= 1.0+epsilon
// d. lines and chars give the best fitting positioning, at best_scale
// try: e. min_scale <= best_scale
// go binary search!
while(min_scale + scale_step < max_scale) {
scale = (min_scale + max_scale) / 2;
vector<Line> lines_try;
vector<CharInfo> chars_try;
elements.getCharInfo(dc, scale, 0, text.size(), chars_try);
fits = prepareLinesScale(dc, chars_try, style, false, lines_try);
if (fits) {
min_scale = scale;
max_scale = min(max_scale, bound_on_max_scale(dc,style,lines_try,scale));
best_scale = scale; // invariant d
swap(lines,lines_try);
swap(chars,chars_try);
} else {
max_scale = scale;
min_scale = max(min_scale, bound_on_min_scale(dc,style,lines_try,scale));
// the above can break pseudo invariant e
}
}
if (best_scale != min_scale) {
// we'd better update lines, e doesn't hold
scale = min_scale;
chars.clear();
elements.getCharInfo(dc, scale, 0, text.size(), chars);
fits = prepareLinesScale(dc, chars, style, false, lines);
}
scale = min_scale;
}
bool TextViewer::prepareLinesScale(RotatedDC& dc, const vector<CharInfo>& chars, const TextStyle& style, bool stop_if_too_long, vector<Line>& lines) const {
// Try to layout the text at the current scale // Try to layout the text at the current scale
// first line // first line
lines.clear(); lines.clear();
...@@ -519,6 +588,10 @@ bool TextViewer::prepareLinesScale(RotatedDC& dc, const vector<CharInfo>& chars, ...@@ -519,6 +588,10 @@ bool TextViewer::prepareLinesScale(RotatedDC& dc, const vector<CharInfo>& chars,
} else { } else {
line.line_height = line_size.height; line.line_height = line_size.height;
} }
// // too low?
// if (stop_if_too_long && line.bottom() > dc.getInternalSize().height - style.padding_bottom) {
// return false;
// }
// push // push
lines.push_back(line); lines.push_back(line);
// reset line object for next line // reset line object for next line
...@@ -549,16 +622,19 @@ bool TextViewer::prepareLinesScale(RotatedDC& dc, const vector<CharInfo>& chars, ...@@ -549,16 +622,19 @@ bool TextViewer::prepareLinesScale(RotatedDC& dc, const vector<CharInfo>& chars,
line.line_height = line_size.height; line.line_height = line_size.height;
} }
lines.push_back(line); lines.push_back(line);
return true; // does it fit vertically?
return lines.empty() ||
lines.back().bottom() <= dc.getInternalSize().height - style.padding_bottom;
} }
double TextViewer::lineLeft(RotatedDC& dc, const TextStyle& style, double y) { double TextViewer::lineLeft(RotatedDC& dc, const TextStyle& style, double y) const {
return style.mask.rowLeft(y, dc.getInternalSize()) + style.padding_left; return style.mask.rowLeft(y, dc.getInternalSize()) + style.padding_left;
} }
double TextViewer::lineRight(RotatedDC& dc, const TextStyle& style, double y) { double TextViewer::lineRight(RotatedDC& dc, const TextStyle& style, double y) const {
return style.mask.rowRight(y, dc.getInternalSize()) - style.padding_right; return style.mask.rowRight(y, dc.getInternalSize()) - style.padding_right;
} }
void TextViewer::alignLines(RotatedDC& dc, const vector<CharInfo>& chars, const TextStyle& style) { void TextViewer::alignLines(RotatedDC& dc, const vector<CharInfo>& chars, const TextStyle& style) {
if (style.alignment == ALIGN_TOP_LEFT) return; if (style.alignment == ALIGN_TOP_LEFT) return;
// Find height of the text, don't count the last lines if they are empty // Find height of the text, don't count the last lines if they are empty
......
...@@ -57,7 +57,9 @@ class TextViewer { ...@@ -57,7 +57,9 @@ class TextViewer {
/** Returns true if something has been done */ /** Returns true if something has been done */
bool prepare(RotatedDC& dc, const String& text, TextStyle& style, Context&); bool prepare(RotatedDC& dc, const String& text, TextStyle& style, Context&);
/// Reset the cached data, at a new call to draw it will be recalculated /// Reset the cached data, at a new call to draw it will be recalculated
void reset(); /** If related, the new value is related to the old one, and layout information should be reused where possible
*/
void reset(bool related);
/// Is the viewer prepare()d? /// Is the viewer prepare()d?
bool prepared() const; bool prepared() const;
...@@ -134,8 +136,11 @@ class TextViewer { ...@@ -134,8 +136,11 @@ class TextViewer {
/// Prepare the lines, layout the text /// Prepare the lines, layout the text
void prepareLines(RotatedDC& dc, const String& text, TextStyle& style, Context& ctx); void prepareLines(RotatedDC& dc, const String& text, TextStyle& style, Context& ctx);
/// Find the scale to use for the text
void prepareLinesTryScales(RotatedDC& dc, const String& text, const TextStyle& style, vector<CharInfo>& chars_out);
/// Prepare the lines, layout the text; at a specific scale /// Prepare the lines, layout the text; at a specific scale
bool prepareLinesScale(RotatedDC& dc, const vector<CharInfo>& chars, const TextStyle& style, bool stop_if_too_long); /** Stores output in lines_out */
bool prepareLinesScale(RotatedDC& dc, const vector<CharInfo>& chars, const TextStyle& style, bool stop_if_too_long, vector<Line>& lines_out) const;
/// Align the lines within the textbox /// Align the lines within the textbox
void alignLines(RotatedDC& dc, const vector<CharInfo>& chars, const TextStyle& style); void alignLines(RotatedDC& dc, const vector<CharInfo>& chars, const TextStyle& style);
...@@ -143,9 +148,9 @@ class TextViewer { ...@@ -143,9 +148,9 @@ class TextViewer {
const Line& findLine(size_t index) const; const Line& findLine(size_t index) const;
// helper : get the start coordinate of a line, this is 0 unless there is a contour mask // helper : get the start coordinate of a line, this is 0 unless there is a contour mask
double lineLeft (RotatedDC& dc, const TextStyle& style, double y); double lineLeft (RotatedDC& dc, const TextStyle& style, double y) const;
// helper : get the end coordinate of a line, this is width unless there is a contour mask // helper : get the end coordinate of a line, this is width unless there is a contour mask
double lineRight(RotatedDC& dc, const TextStyle& style, double y); double lineRight(RotatedDC& dc, const TextStyle& style, double y) const;
}; };
......
...@@ -39,10 +39,14 @@ void TextValueViewer::draw(RotatedDC& dc) { ...@@ -39,10 +39,14 @@ void TextValueViewer::draw(RotatedDC& dc) {
} }
void TextValueViewer::onValueChange() { void TextValueViewer::onValueChange() {
v.reset(); v.reset(false);
} }
void TextValueViewer::onStyleChange(bool already_prepared) { void TextValueViewer::onStyleChange(bool already_prepared) {
v.reset(); v.reset(true);
if (!already_prepared) viewer.redraw(*this); if (!already_prepared) viewer.redraw(*this);
} }
void TextValueViewer::onAction(const Action&, bool undone) {
v.reset(true);
}
...@@ -25,6 +25,7 @@ class TextValueViewer : public ValueViewer { ...@@ -25,6 +25,7 @@ class TextValueViewer : public ValueViewer {
virtual void draw(RotatedDC& dc); virtual void draw(RotatedDC& dc);
virtual void onValueChange(); virtual void onValueChange();
virtual void onStyleChange(bool); virtual void onStyleChange(bool);
virtual void onAction(const Action&, bool undone);
protected: protected:
TextViewer v; TextViewer v;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment