Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Системное программирование и WinAPI > Перенос такста по символам


Автор: 586 1.5.2008, 00:59
Как в MessageBox реализован перенос текста? Мне нужен перенос не только по словам, но и по символам, для длинных строк.
Код
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    static const char text[] = "aaaaaa\r\n"
                               "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n"
                               "cccccc\r\n"
                               "dddd eeeeeee ffffffffff ggggggggg hhhh\r\n"
                               "iiiiiiii";
    RECT rc = {5, 5, 100, 150};
    DrawText(Canvas->Handle, text, strlen(text), &rc, DT_WORDBREAK);
}

Вторая строка не переносится.

Автор: GremlinProg 3.5.2008, 12:51
Если не хочется особенно разбираться, то в RichEdit есть возможность установить пользовательскую функцию переноса слов(EM_SETWORDBREAKPROC), а чтобы вывести этот текст в другой девайс, используй EM_FORMATRANGE.

Но такой способ требует наличия скрытого контрола в программе, это может быть не очень удобно.

Я сделал свой вариант печати, тоже потребовался вывод с нормальным переносом, но в моем случае нужна был еще и подсветка синтаксиса.  Я реализовал класс rtf_printer так:
Код

class rtf_printer{
protected:
    RECT        rc;
    SIZE        size;
    int            x,y;
    int            line;
    int            height;
    //
    BYTE        print_state    : 1;
    BYTE        calc_size    : 1;
    void inc_x(void){
        x        += size.cx;
        line    -= size.cx;
        if(max_width < x){
            max_width    = x;
        }
    }
    void inc_y(void){
        y    += height;
    }
    void start_line(void){
        x    = rc.left;
    }
    void next_line(void){
        start_line();
        inc_y();
        if(max_height < y){
            max_height    = y;
        }
    }
    bool last_line(void){
        return( (rc.bottom - y) < 2 * height );
    }
    // print_text вернет число распечатанных символов
    int print_text(HDC hdc,LPCWSTR lpszWord,int len){
        SIZE extra;
        if(!len)return len;
        // получение первоначальной длины текста в пикселах, используя текущий шрифт
        ::GetTextExtentPoint32W(hdc,lpszWord,len,&size);
        // если сегмент не последний, extra.cx = 0
        extra.cx    = 0;
        if(last_line()){// последняя линия
            if(x + line > rc.right){//остаток не вмещается на линию
                // получение длины символа "…" в пикселах, используя текущий шрифт
                ::GetTextExtentPoint32W(hdc,L"…",1,&extra);
            }
        }
        // поиск оптимальной длины текста, который вмещается на линию (с учетом многоточия, если нужно)
        while(len && (x + size.cx > rc.right - extra.cx)){
            ::GetTextExtentPoint32W(hdc,lpszWord,--len,&size);
        }
        // вывод расчитанного сегмента текста
        if(!calc_size){
            print_segment(hdc,lpszWord,len);
        }
        // продвижение координаты x
        inc_x();
        //
        if(!len){// текст не поместился в линию, т.е. ни один символ не может быть на ней распечатан
            if(last_line()){// последняя линия
                //если многоточие помещается на линию
                if(x + extra.cx <= rc.right){
                    // и поднят флаг печати
                    if(!calc_size){
                        // вывести многоточие
                        print_segment(hdc,L"…",1);
                    }
                    // это только для корректной обработки в inc_x
                    size    = extra;
                    // здесь inc_x - только для учета многоточия в max_width
                    inc_x();
                }
            }
            // начать новую линию (продвижение координат)
            next_line();
        }
        return len;
    }
protected:
    virtual int prepare_dc(HDC hdc,LPCWSTR lpszText,int offset,int length){
        return length;
    }
    virtual void restore_dc(HDC hdc){
    }
    virtual bool print_segment(HDC hdc,LPCWSTR lpszSegment,int length){
        return(::TextOutW(hdc,x,y,lpszSegment,length) != FALSE);
    }
public:
    int            max_width;
    int            max_height;
    // print вернет число не распечатанных символов
    int print(HDC hdc,LPCWSTR lpszText,int length,CONST RECT&rc,bool calc_size){
        TEXTMETRIC tm;
        max_width        = rc.left;
        y                = rc.top;
        height            = 0;
        line            = 0;
        print_state        = false;
        this->calc_size    = calc_size;
        if(!::CopyRect(&this->rc,&rc))return length;
        UINT align;
        int len,calc_length,offset;
        align        = ::SetTextAlign(hdc,TA_LEFT|TA_TOP);
        //вычисление высоты строки
        ::GetTextMetrics(hdc,&tm);
        height        = tm.tmHeight + tm.tmExternalLeading + (tm.tmUnderlined?1:0);
        //
        for(offset = 0,calc_length = length;calc_length;calc_length -= len,offset += len){
            //перевод устройства в пользовательское состояние
            len    = prepare_dc(hdc,lpszText,offset,calc_length);
            //проверка корректности пользовательского ввода
            assert((len > 0) && (len <= calc_length));
            //вычисление размера текста
            ::GetTextExtentPoint32W(hdc,lpszText + offset,len,&size);
            //вычисление общей длины текста
            line    += size.cx;
            //вычисление общей высоты текста
            ::GetTextMetrics(hdc,&tm);
            if(height < tm.tmHeight + tm.tmExternalLeading + (tm.tmUnderlined?1:0)){
                height    = tm.tmHeight + tm.tmExternalLeading + (tm.tmUnderlined?1:0);
            }
            //восстановление устройства в состояние "по-умолчанию"
            restore_dc(hdc);
        }
        print_state        = true;
        start_line();
        for(offset = 0;(length) && (y + height <= rc.bottom);length -= len,offset += len){
            //перевод устройства в пользовательское состояние
            len    = prepare_dc(hdc,lpszText,offset,length);
            //проверка корректности пользовательского ввода
            assert((len > 0) && (len <= length));
            //вывод текста
            len    = print_text(hdc,lpszText + offset,len);
            //восстановление устройства в состояние "по-умолчанию"
            restore_dc(hdc);
        }
        ::SetTextAlign(hdc,align);
        max_width    -= rc.left;
        max_height     = y - rc.top;
        if(y + height <= rc.bottom){
            max_height+=height;
        }
        return length;
    }
};

код для юникода, кому нужно, локализуют как надо.
пример:
Код

rtf_printer p;
p.print(hdc,lpszText,(int)wcslen(lpszText),rc,false);

здесь rc - прямоугольник в координатах hdc, в котором нужно вывести текст lpszText.
Алгоритм пытается распределить текст по ширине прямоугольника, если солово длинное - переносит его на новую строку, если текст не помещается в прямоугольнике - добавляет в конец многоточие.

prepare_dc и restore_dc - виртуальные методы для раскраски отдельных частей текста.
prepare_dc должен вернуть длину разобранного слова, если оно меньше length. По умолчанию, она возвращает length.

параметр calc_size позволяет расчитать точные ширину и высоту в прямоугольнике, т.е. не выводит текст, а только считает его. Результаты на выходе в параметрах max_width и max_height, т.е. можно перед выводом отцентрировать многострочный текст как надо.

Если текст простой, сплошной, не rtf, то можно убрать первый цикл в основном методе print, он считает ширину и высоту всего текста, как если бы он был распечатан без переносов.
Код

for(offset = 0,calc_length = length;calc_length;)...


Вообще тут можно дополнительно к раскраске обрабатывать hittest на отдельных элементах скрипта, поэтому вывод отдельной последовательности print, сделан виртуальным.(в нем можно использовать x,y - координаты слова, а size - его ширина и высота)

Один момент, который пришлось подгонять вручную:  при расчете шрифта с подчеркиванием ни как не учитывается само подчеркивание, поэтому я тут просто добавляю единицу к высоте текста.

Высота текста для любого форматирования - всегда доинакова на всех линиях, поэтому нежелательно использовать несколько разновысотных шрифтов, т.к. вывод не с базовой линии, а с левого-верхнего угла.

PS: упростил класс rtf_printer, т.к. пословный перенос особенно и не интересен, а вот посимвольный вывод ускорился в разы.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)