// the formatter's core, wraps the line and justifies it if needed
int TextFormatter::WrapLine(CFDC& dc, Paragraph& pp,
    FilePos& pos, LineArray& la,
    int top, int maxh)
{
    if (maxh <= 0)
        return 0;
    // process images separately
    if (pp.flags&Paragraph::image)
        return WrapImage(dc, pp, pos, la, top, maxh);
    if (pp.len == 0 || (pp.len == 1 && pp.str[0] == L' '))
    {
        dc.SelectFont(0, 0);
        int fh, fa;
        dc.GetFontSize(fh, fa);
        if (fh > maxh)
            return -1;
        Line l;
        l.pos = pos;
        l.flags = Line::first | Line::last | Line::defstyle;
        l.height = fh;
        l.base = fa;
        la.Add(l);
        pos.off = pp.len;
        return l.height;
    }
    if (m_hyphenate)
        pp.Hyphenate();
    const wchar_t *str = pp.str;
    int len = pp.len;
    Buffer<int> dx(len + 1);
    int toth = 0;
    while (toth < maxh && pos.off < len)
    {
        // 1. get text size
        int nch = len;
        int curwidth = m_width;
        int ispace = 0;
        if (pos.off == 0 && (pp.flags&(Paragraph::center | Paragraph::right)) == 0)
            AdjustIndent(curwidth, ispace, pp.lindent, pp.rindent, pp.findent, dc.GetLPX());
        else
            AdjustIndent(curwidth, ispace, pp.lindent, pp.rindent, 0, dc.GetLPX());
        dx[0] = 0;
        int lh = 1, lbase = 1;
        GetTextExtent(dc, pp, pos.off, curwidth, nch, dx + 1, lh, lbase);
        if (toth + lh > maxh)
            return -1;
        if (nch == 0)
            nch = 1;
        // 2. do word wrap
        bool addhyp = false;
        if (nch + pos.off < pp.str.size())
        {
            int i;
            for (i = nch; i > 0 && str[pos.off + i] != L' '; --i)
            {
                // wrap at existing dashes
                if (i < nch && (str[pos.off + i] == L'-' || str[pos.off + i] == 0x2013 ||
                    str[pos.off + i] == 0x2014) && i < len - 1 && (str[pos.off + i + 1] == L' ' ||
                    iswalpha(str[pos.off + i + 1])))
                {
                    ++i;
                    break;
                }
                // or at possible hyphenation points
                if (m_hyphenate && pp.cflags[pos.off + i].hyphen &&
                    dx[i] + dc.GetHypWidth() <= curwidth)
                {
                    addhyp = true;
                    break;
                }
            }
            if (i > 0)
                nch = i;
            else
                addhyp = false;
        }
        // insert it into line list
        if (pos.off == 0 && nch == pp.str.size())
        {
            // got full line
            Line l(str, len, false);
            l.pos = pos;
            l.flags = Line::first | Line::last;
            l.ispace = ispace;
            l.height = lh;
            l.base = lbase;
            if (dx[nch] < curwidth)
            {
                if (pp.flags&Paragraph::center)
                    l.ispace += (curwidth - dx[nch]) / 2;
                else if (pp.flags&Paragraph::right)
                    l.ispace += curwidth - dx[nch];
            }
            CopyAttr(l.attr, pp.cflags, len);
            for (int j = 0; j < len; ++j)
                l.dx[j] = dx[j + 1] - dx[j];
            la.Add(l);
            pos.off = len;
        }
        else
        {
            Line l(str + pos.off, nch, addhyp);
            if (addhyp)
                l.str[nch] = L'-';
            l.pos = pos;
            l.ispace = ispace;
            l.height = lh;
            l.base = lbase;
            l.flags = 0;
            if (pos.off == 0)
                l.flags |= Line::first;
            if (pos.off + nch == pp.str.size())
                l.flags |= Line::last;
            for (int j = 0; j < nch; ++j)
                l.dx[j] = dx[j + 1] - dx[j];
            int extra_width = 0;
            if (addhyp)
                l.dx[nch] = extra_width = dc.GetHypWidth();
            // 3. justify/center text if needed
            if (dx[nch] < curwidth)
            {
                if (addhyp)
                    curwidth -= extra_width;
                if (pp.flags&Paragraph::center)
                {
                    l.ispace += (curwidth - dx[nch]) / 2;
                }
                else if (pp.flags&Paragraph::right)
                {
                    l.ispace += curwidth - dx[nch];
                }
                else if ((m_justified || pp.flags&Paragraph::justify) &&
                    !(l.flags&Line::last))
                {
                    // count spaces in string
                    int nspc = 0, i;
                    for (i = 0; i < nch; ++i)
                        if (L' ' == str[pos.off + i])
                            ++nspc;
                    // and distribute extra width to them
                    if (nspc > 0)
                    {
                        int addw = (curwidth - dx[nch]) / nspc;
                        int extraddw = curwidth - dx[nch] - addw*nspc;
                        for (i = 0; i < nch; ++i)
                        {
                            if (str[pos.off + i] == L' ')
                            {
                                l.dx[i] += addw;
                                if (extraddw)
                                {
                                    ++l.dx[i];
                                    --extraddw;
                                }
                            }
                        }
                    }
                }
            }
            CopyAttr(l.attr, pp.cflags + pos.off, nch);
            if (addhyp)
                l.attr[nch] = l.attr[nch - 1];
            la.Add(l);
            pos.off += nch;
            while (pos.off < len && str[pos.off] == L' ')
                ++pos.off;
        }
        toth += lh;
    }
    return toth;
}
// split image into strips
int TextFormatter::WrapImage(CFDC& dc, Paragraph& pp,
    FilePos& pos, LineArray& la,
    int top, int maxh)
{
    Image img;
#if 0
    int curwidth = m_width;
    int ispace = 0;
    AdjustIndent(curwidth, ispace, pp.lindent, pp.rindent, 0, dc.GetLPX());
#else
    int curwidth = m_total_width;
    int ispace = -m_margin;
#endif
    if (pp.links.size() <= 0 ||
        !m_tf->GetImage(pp.links[0].target, dc.DC(), curwidth, m_height, m_angle, img))
    {
        // image fetch failed, just skip the paragraph
        pos.off += pp.len;
        return 0;
    }
#if 0
    if (top && img.height > maxh)
        return -1;
#endif
    // calc strips, min strip height is 16
    int striph = (img.height + pp.str.size() - 1) / pp.str.size();
    if (striph < 16)
        striph = 16;
    if (striph > img.height)
        striph = img.height;
    // number of visible strips
    int vstrips = (img.height + striph - 1) / striph;
    int topstrip = vstrips;
    if (topstrip > pp.len)
        topstrip = pp.len;
    // all lines are the same
    Line line(L" ", 1, false);
    line.attr[0].wa = 0;
    line.dx[0] = 0;
    line.ispace = ispace + (curwidth - img.width) / 2;
    line.href = pp.links[0].target;
    line.pos = pos;
    line.flags = Line::image;
    line.height = striph;
    line.base = curwidth;
    line.imageheight = m_height;
    // take care of the strip offset
    int stripnum = pos.off;
    int yoffset = stripnum*striph;
    // add visible strips as lines
    int toth = 0;
    // add visible strips
    while (stripnum < topstrip)
    {
        line.yoffset = yoffset;
        line.pos = pos;
        if (stripnum == vstrips - 1)
        {
            // last line
            // assign all unsused spaces here
            int spcount = pp.len - pos.off;
            line.attr = Buffer<Attr>(spcount);
            line.str = Buffer<wchar_t>(spcount);
            line.dx = Buffer<int>(spcount);
            for (int i = 0; i < spcount; ++i)
            {
                line.attr[i].wa = 0;
                line.str[i] = L' ';
                line.dx[i] = 0;
            }
            line.real_len = spcount;
            // adjust line height
            line.height = img.height - striph*stripnum;
        }
        if (toth + line.height > maxh)
            break;
        la.Add(line);
        pos.off += line.real_len;
        ++stripnum;
        yoffset += striph;
        toth += line.height;
    }
    // ok, return processed height
    // if we didn't process anything, return a failure
    return toth ? toth : -1;
}