nana/source/paint/text_renderer.cpp
2017-05-31 22:36:56 +08:00

685 lines
18 KiB
C++

#include "../detail/platform_spec_selector.hpp"
#include <nana/paint/text_renderer.hpp>
#include <nana/unicode_bidi.hpp>
#include <nana/paint/detail/native_paint_interface.hpp>
namespace nana
{
namespace paint
{
namespace helper
{
template<typename F>
void for_each_line(const wchar_t * str, std::size_t len, int top, F & f)
{
auto const end = str + len;
for(auto i = str; i != end; ++i)
{
if(*i == '\n')
{
top += static_cast<int>(f(top, str, i - str));
str = i + 1;
}
}
if(str != end)
f(top, str, end - str);
}
struct draw_string
{
drawable_type dw;
const int x, endpos;
align text_align;
draw_string(drawable_type dw, int x, int endpos, align ta)
: dw(dw), x(x), endpos(endpos), text_align(ta)
{}
unsigned operator()(const int top, const wchar_t * buf, std::size_t bufsize)
{
auto const reordered = unicode_reorder(buf, bufsize);
if (reordered.empty())
return 0;
nana::point pos{ x, top };
unsigned pixels = 0;
switch(text_align)
{
case align::left:
for(auto & ent : reordered)
{
std::size_t len = ent.end - ent.begin;
nana::size ts = detail::text_extent_size(dw, ent.begin, len);
if(ts.height > pixels) pixels = ts.height;
if(pos.x + static_cast<int>(ts.width) > 0)
detail::draw_string(dw, pos, ent.begin, len);
pos.x += static_cast<int>(ts.width);
if(pos.x >= endpos)
break;
}
break;
case align::center:
{
unsigned lenpx = 0;
std::unique_ptr<unsigned[]> entity_pxs(new unsigned[reordered.size()]);
auto ent_px = entity_pxs.get();
for(auto & ent : reordered)
{
auto ts = detail::text_extent_size(dw, ent.begin, ent.end - ent.begin);
if(ts.height > pixels) pixels = ts.height;
lenpx += ts.width;
*ent_px++ = ts.width;
}
pos.x += (endpos - pos.x - static_cast<int>(lenpx))/2;
ent_px = entity_pxs.get();
for(auto & ent : reordered)
{
if (pos.x + static_cast<int>(*ent_px) > 0)
detail::draw_string(dw, pos, ent.begin, ent.end - ent.begin);
pos.x += static_cast<int>(*ent_px++);
if(pos.x >= endpos)
break;
}
}
break;
case align::right:
{
int xend = endpos;
std::swap(pos.x, xend);
for(auto i = reordered.crbegin(); i != reordered.crend(); ++i)
{
auto & ent = *i;
std::size_t len = ent.end - ent.begin;
nana::size ts = detail::text_extent_size(dw, ent.begin, len);
if(ts.height > pixels) pixels = ts.height;
if(pos.x > xend)
{
pos.x -= static_cast<int>(ts.width);
detail::draw_string(dw, pos, i->begin, len);
}
if(pos.x <= xend || pos.x <= 0)
break;
}
}
break;
}
return pixels;
}
};
struct draw_string_omitted
{
graphics & graph;
int x, endpos;
unsigned omitted_pixels;
draw_string_omitted(graphics& graph, int x, int endpos, bool omitted)
: graph(graph), x(x), endpos(endpos)
{
omitted_pixels = (omitted ? graph.text_extent_size("...", 3).width : 0);
if (endpos - x > static_cast<int>(omitted_pixels))
this->endpos -= omitted_pixels;
else
this->endpos = x;
}
unsigned operator()(const int top, const wchar_t * buf, std::size_t bufsize)
{
drawable_type dw = graph.handle();
::nana::point pos{ x, top };
unsigned pixels = 0;
auto const reordered = unicode_reorder(buf, bufsize);
for(auto & i : reordered)
{
std::size_t len = i.end - i.begin;
nana::size ts = detail::text_extent_size(dw, i.begin, len);
if(ts.height > pixels) pixels = ts.height;
if(pos.x + static_cast<int>(ts.width) <= endpos)
{
detail::draw_string(dw, pos, i.begin, len);
pos.x += static_cast<int>(ts.width);
}
else
{
nana::rectangle r;
r.width = endpos - pos.x;
r.height = ts.height;
nana::paint::graphics dum_graph({ r.width, r.height });
dum_graph.bitblt(r, graph, pos);
dum_graph.palette(true, graph.palette(true));
dum_graph.string({}, i.begin, len);
r.x = pos.x;
r.y = top;
graph.bitblt(r, dum_graph);
if(omitted_pixels)
detail::draw_string(dw, point{ endpos, top }, L"...", 3);
break;
}
}
return pixels;
}
};
struct draw_string_auto_changing_lines
{
graphics & graph;
int x, endpos;
align text_align;
draw_string_auto_changing_lines(graphics& graph, int x, int endpos, align ta)
: graph(graph), x(x), endpos(endpos), text_align(ta)
{}
unsigned operator()(const int top, const wchar_t * buf, std::size_t bufsize)
{
unsigned pixels = 0;
auto const dw = graph.handle();
unsigned str_w = 0;
std::vector<nana::size> ts_keeper;
auto const reordered = unicode_reorder(buf, bufsize);
for(auto & i : reordered)
{
nana::size ts = detail::text_extent_size(dw, i.begin, i.end - i.begin);
if(ts.height > pixels) pixels = ts.height;
ts_keeper.emplace_back(ts);
str_w += ts.width;
}
//Test whether the text needs the new line.
if(x + static_cast<int>(str_w) > endpos)
{
pixels = 0;
unsigned line_pixels = 0;
nana::point pos{ x, top };
int orig_top = top;
auto i_ts_keeper = ts_keeper.cbegin();
for(auto & i : reordered)
{
if(line_pixels < i_ts_keeper->height)
line_pixels = i_ts_keeper->height;
bool beyond_edge = (pos.x + static_cast<int>(i_ts_keeper->width) > endpos);
if(beyond_edge)
{
const std::size_t len = i.end - i.begin;
if(len > 1)
{
std::unique_ptr<unsigned[]> pixel_buf(new unsigned[len]);
//Find the char that should be splitted
graph.glyph_pixels(i.begin, len, pixel_buf.get());
std::size_t idx_head = 0, idx_splitted;
do
{
auto pxbuf = pixel_buf.get();
idx_splitted = find_splitted(idx_head, len, pos.x, endpos, pxbuf);
if(idx_splitted == len)
{
detail::draw_string(dw, pos, i.begin + idx_head, idx_splitted - idx_head);
for(std::size_t i = idx_head; i < len; ++i)
pos.x += static_cast<int>(pxbuf[i]);
break;
}
//Check the word whether it is splittable.
if(splittable(i.begin, idx_splitted))
{
detail::draw_string(dw, pos, i.begin + idx_head, idx_splitted - idx_head);
idx_head = idx_splitted;
pos.x = x;
pos.y += static_cast<int>(line_pixels);
line_pixels = i_ts_keeper->height;
}
else
{
//Search the splittable character from idx_head to idx_splitted
const wchar_t * u = i.begin + idx_splitted;
const wchar_t * head = i.begin + idx_head;
for(; head < u; --u)
{
if(splittable(head, u - head))
break;
}
if(u != head)
{
detail::draw_string(dw, pos, head, u - head);
idx_head += u - head;
pos.x = x;
pos.y += static_cast<int>(line_pixels);
line_pixels = i_ts_keeper->height;
}
else
{
u = i.begin + idx_splitted;
const wchar_t * end = i.begin + len;
for(; u < end; ++u)
{
if(splittable(head, u - head))
break;
}
std::size_t splen = u - head;
pos.y += static_cast<int>(line_pixels);
pos.x = x;
detail::draw_string(dw, pos, head, splen);
line_pixels = i_ts_keeper->height;
for(std::size_t k = idx_head; k < idx_head + splen; ++k)
pos.x += static_cast<int>(pxbuf[k]);
if (pos.x >= endpos)
{
pos.x = x;
pos.y += static_cast<int>(line_pixels);
}
idx_head += splen;
}
}
}while(idx_head < len);
}
else
{
pos.x = x;
pos.y += static_cast<int>(line_pixels);
detail::draw_string(dw, pos, i.begin, 1);
pos.x += static_cast<int>(i_ts_keeper->width);
}
line_pixels = 0;
}
else
{
detail::draw_string(dw, pos, i.begin, i.end - i.begin);
pos.x += static_cast<int>(i_ts_keeper->width);
}
++i_ts_keeper;
}
pixels = (top - orig_top) + line_pixels;
}
else
{
//The text could be drawn in a line.
if((align::left == text_align) || (align::center == text_align))
{
point pos{ x, top };
if(align::center == text_align)
pos.x += (endpos - x - static_cast<int>(str_w)) / 2;
auto i_ts_keeper = ts_keeper.cbegin();
for(auto & ent : reordered)
{
const nana::size & ts = *i_ts_keeper;
if (pos.x + static_cast<int>(ts.width) > 0)
detail::draw_string(dw, pos, ent.begin, ent.end - ent.begin);
pos.x += static_cast<int>(ts.width);
++i_ts_keeper;
}
}
else if(align::right == text_align)
{
point pos{ endpos, top };
auto i_ts_keeper = ts_keeper.crbegin();
for(auto i = reordered.crbegin(); i != reordered.crend(); ++i)
{
auto & ent = *i;
std::size_t len = ent.end - ent.begin;
const nana::size & ts = *i_ts_keeper;
pos.x -= static_cast<int>(ts.width);
if (pos.x >= 0)
detail::draw_string(dw, pos, ent.begin, len);
++i_ts_keeper;
}
}
}
return pixels;
}
static std::size_t find_splitted(std::size_t begin, std::size_t end, int x, int endpos, unsigned * pxbuf)
{
unsigned acc_width = 0;
for(std::size_t i = begin; i < end; ++i)
{
if(x + static_cast<int>(acc_width + pxbuf[i]) > endpos)
{
if(i == begin)
++i;
return i;
}
acc_width += pxbuf[i];
}
return end;
}
static bool splittable(const wchar_t * str, std::size_t index)
{
wchar_t ch = str[index];
if(('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z'))
{
wchar_t prch;
if(index)
{
prch = str[index - 1];
if('0' <= ch && ch <= '9')
return !(('0' <= prch && prch <= '9') || (str[index - 1] == '-'));
return (('z' < prch || prch < 'a') && ('Z' < prch || prch < 'A'));
}
else
return false;
}
return true;
}
};
struct extent_auto_changing_lines
{
graphics & graph;
int x, endpos;
unsigned extents;
extent_auto_changing_lines(graphics& graph, int x, int endpos)
: graph(graph), x(x), endpos(endpos), extents(0)
{}
unsigned operator()(int top, const wchar_t * buf, std::size_t bufsize)
{
unsigned pixels = 0;
drawable_type dw = graph.handle();
unsigned str_w = 0;
std::vector<nana::size> ts_keeper;
auto const reordered = unicode_reorder(buf, bufsize);
for(auto & i : reordered)
{
nana::size ts = detail::text_extent_size(dw, i.begin, i.end - i.begin);
ts_keeper.emplace_back(ts);
str_w += ts.width;
}
auto i_ts_keeper = ts_keeper.cbegin();
//Test whether the text needs the new line.
if(x + static_cast<int>(str_w) > endpos)
{
unsigned line_pixels = 0;
int xpos = x;
int orig_top = top;
for(auto & i : reordered)
{
if(line_pixels < i_ts_keeper->height)
line_pixels = i_ts_keeper->height;
bool beyond_edge = (xpos + static_cast<int>(i_ts_keeper->width) > endpos);
if(beyond_edge)
{
std::size_t len = i.end - i.begin;
if(len > 1)
{
std::unique_ptr<unsigned[]> scope_res(new unsigned[len]);
auto pxbuf = scope_res.get();
//Find the char that should be splitted
graph.glyph_pixels(i.begin, len, pxbuf);
std::size_t idx_head = 0, idx_splitted;
do
{
idx_splitted = draw_string_auto_changing_lines::find_splitted(idx_head, len, xpos, endpos, pxbuf);
if(idx_splitted == len)
{
for(std::size_t i = idx_head; i < len; ++i)
xpos += static_cast<int>(pxbuf[i]);
break;
}
//Check the word whether it is splittable.
if(draw_string_auto_changing_lines::splittable(i.begin, idx_splitted))
{
idx_head = idx_splitted;
xpos = x;
top += line_pixels;
line_pixels = i_ts_keeper->height;
}
else
{
//Search the splittable character from idx_head to idx_splitted
const wchar_t * u = i.begin + idx_splitted;
const wchar_t * head = i.begin + idx_head;
for(; head < u; --u)
{
if(draw_string_auto_changing_lines::splittable(head, u - head))
break;
}
if(u != head)
{
idx_head += u - head;
xpos = x;
top += line_pixels;
line_pixels = i_ts_keeper->height;
}
else
{
u = i.begin + idx_splitted;
const wchar_t * end = i.begin + len;
for(; u < end; ++u)
{
if(draw_string_auto_changing_lines::splittable(head, u - head))
break;
}
std::size_t splen = u - head;
top += line_pixels;
xpos = x;
line_pixels = i_ts_keeper->height;
for(std::size_t k = idx_head; k < idx_head + splen; ++k)
xpos += static_cast<int>(pxbuf[k]);
if(xpos >= endpos)
{
xpos = x;
top += line_pixels;
}
idx_head += splen;
}
}
}while(idx_head < len);
}
else
xpos = x + static_cast<int>(i_ts_keeper->width);
line_pixels = 0;
}
else
xpos += static_cast<int>(i_ts_keeper->width);
++i_ts_keeper;
}
pixels = (top - orig_top) + line_pixels;
}
else
{
while(i_ts_keeper != ts_keeper.cend())
{
const nana::size & ts = *(i_ts_keeper++);
if(ts.height > pixels) pixels = ts.height;
}
}
extents += pixels;
return pixels;
}
};
}//end namespace helper
//class text_renderer
text_renderer::text_renderer(graph_reference graph, align ta)
: graph_(graph), text_align_(ta)
{}
nana::size text_renderer::extent_size(int x, int y, const wchar_t* str, std::size_t len, unsigned restricted_pixels) const
{
nana::size extents;
if(graph_)
{
helper::extent_auto_changing_lines eacl(graph_, x, x + static_cast<int>(restricted_pixels));
helper::for_each_line(str, len, y, eacl);
extents.width = restricted_pixels;
extents.height = eacl.extents;
}
return extents;
}
void text_renderer::render(const point& pos, const wchar_t * str, std::size_t len)
{
if (graph_)
{
helper::draw_string ds(graph_.handle(), pos.x, static_cast<int>(graph_.width()), text_align_);
helper::for_each_line(str, len, pos.y, ds);
}
}
void text_renderer::render(const point& pos, const wchar_t* str, std::size_t len, unsigned restricted_pixels, bool omitted)
{
if (graph_)
{
helper::draw_string_omitted dso(graph_, pos.x, pos.x + static_cast<int>(restricted_pixels), omitted);
helper::for_each_line(str, len, pos.y, dso);
}
}
void text_renderer::render(const point& pos, const wchar_t * str, std::size_t len, unsigned restricted_pixels)
{
if (graph_)
{
helper::draw_string_auto_changing_lines dsacl(graph_, pos.x, pos.x + static_cast<int>(restricted_pixels), text_align_);
helper::for_each_line(str, len, pos.y, dsacl);
}
}
//end class text_renderer
//class aligner
//Constructor
aligner::aligner(graph_reference graph, align text_align)
: aligner{ graph, text_align, text_align }
{}
aligner::aligner(graph_reference graph, align text_align, align text_align_ex) :
graph_(graph),
text_align_(text_align),
text_align_ex_(text_align_ex)
{}
// Draws a text with specified text alignment.
void aligner::draw(const std::string& text, point pos, unsigned width)
{
draw(to_wstring(text), pos, width);
}
void aligner::draw(const std::wstring& text, point pos, unsigned width)
{
auto text_px = graph_.text_extent_size(text).width;
if (text_px <= width)
{
switch (text_align_)
{
case align::center:
pos.x += static_cast<int>(width - text_px) / 2;
break;
case align::right:
pos.x += static_cast<int>(width - text_px);
default:
break;
}
graph_.bidi_string(pos, text.c_str(), text.size());
return;
}
const auto ellipsis = graph_.text_extent_size("...", 3).width;
std::unique_ptr<unsigned[]> pixels(new unsigned[text.size()]);
graph_.glyph_pixels(text.c_str(), text.size(), pixels.get());
std::size_t substr_len = 0;
unsigned substr_px = 0;
if (align::right == text_align_ex_)
{
auto end = pixels.get();
auto p = end + text.size();
do
{
--p;
if (substr_px + *p + ellipsis > width)
{
substr_len = p - pixels.get() + 1;
break;
}
substr_px += *p;
} while (p != end);
pos.x += static_cast<int>(width - ellipsis - substr_px) + ellipsis;
graph_.bidi_string(pos, text.c_str() + substr_len, text.size() - substr_len);
pos.x -= ellipsis;
}
else
{
for (auto p = pixels.get(), end = pixels.get() + text.size(); p != end; ++p)
{
if (substr_px + *p + ellipsis > width)
{
substr_len = p - pixels.get();
break;
}
substr_px += *p;
}
if (align::center == text_align_ex_)
pos.x += (width - substr_px - ellipsis) / 2;
graph_.bidi_string(pos, text.c_str(), substr_len);
pos.x += substr_px;
}
graph_.string(pos, "...");
}
//end class string
}
}