mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-10 13:06:59 -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:
@@ -54,5 +54,5 @@ double FontTextElement::minScale() const {
|
||||
return min(font->size(), font->scale_down_to) / max(0.01, font->size());
|
||||
}
|
||||
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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
void TextViewer::reset() {
|
||||
void TextViewer::reset(bool related) {
|
||||
elements.clear();
|
||||
lines.clear();
|
||||
if (!related) scale = 1.0;
|
||||
}
|
||||
bool TextViewer::prepared() const {
|
||||
return !lines.empty();
|
||||
@@ -335,55 +336,8 @@ void TextViewer::prepareElements(const String& text, const TextStyle& style, Con
|
||||
// ----------------------------------------------------------------------------- : Layout
|
||||
|
||||
void TextViewer::prepareLines(RotatedDC& dc, const String& text, TextStyle& style, Context& ctx) {
|
||||
// try to layout, at different scales
|
||||
vector<CharInfo> chars;
|
||||
scale = 1;
|
||||
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
|
||||
*/
|
||||
prepareLinesTryScales(dc, text, style, chars);
|
||||
|
||||
// store information about the content/layout, allow this to change alignment
|
||||
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
|
||||
// first line
|
||||
lines.clear();
|
||||
@@ -519,6 +588,10 @@ bool TextViewer::prepareLinesScale(RotatedDC& dc, const vector<CharInfo>& chars,
|
||||
} else {
|
||||
line.line_height = line_size.height;
|
||||
}
|
||||
// // too low?
|
||||
// if (stop_if_too_long && line.bottom() > dc.getInternalSize().height - style.padding_bottom) {
|
||||
// return false;
|
||||
// }
|
||||
// push
|
||||
lines.push_back(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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
void TextViewer::alignLines(RotatedDC& dc, const vector<CharInfo>& chars, const TextStyle& style) {
|
||||
if (style.alignment == ALIGN_TOP_LEFT) return;
|
||||
// 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 */
|
||||
bool prepare(RotatedDC& dc, const String& text, TextStyle& style, Context&);
|
||||
/// 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?
|
||||
bool prepared() const;
|
||||
|
||||
@@ -134,8 +136,11 @@ class TextViewer {
|
||||
|
||||
/// Prepare the lines, layout the text
|
||||
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
|
||||
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
|
||||
void alignLines(RotatedDC& dc, const vector<CharInfo>& chars, const TextStyle& style);
|
||||
|
||||
@@ -143,9 +148,9 @@ class TextViewer {
|
||||
const Line& findLine(size_t index) const;
|
||||
|
||||
// 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
|
||||
double lineRight(RotatedDC& dc, const TextStyle& style, double y);
|
||||
double lineRight(RotatedDC& dc, const TextStyle& style, double y) const;
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user