mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-12 13:37:00 -04:00
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. git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@624 0fc631ac-6414-0410-93d0-97cfa31319b6
This commit is contained in:
@@ -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) {
|
|||||||
(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 {
|
|||||||
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 {
|
|||||||
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.);
|
||||||
}
|
}
|
||||||
|
|||||||
+129
-53
@@ -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
|
|||||||
// ----------------------------------------------------------------------------- : 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
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
|||||||
} 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,
|
|||||||
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 {
|
|||||||
/** 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 {
|
|||||||
|
|
||||||
/// 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 {
|
|||||||
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) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
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;
|
||||||
|
|||||||
Reference in New Issue
Block a user