void Canvas::formatted_text(PixelRect *rc, const TCHAR *text, unsigned format) { if (font == NULL) return; UPixelScalar skip = font->GetLineSpacing(); unsigned max_lines = (format & DT_CALCRECT) ? -1 : (rc->bottom - rc->top + skip - 1) / skip; size_t len = _tcslen(text); TCHAR *duplicated = new TCHAR[len + 1], *p = duplicated; unsigned lines = 1; for (const TCHAR *i = text; *i != _T('\0'); ++i) { TCHAR ch = *i; if (ch == _T('\n')) { /* explicit line break */ if (++lines >= max_lines) break; ch = _T('\0'); } else if (ch == _T('\r')) /* skip */ continue; else if ((unsigned)ch < 0x20) /* replace non-printable characters */ ch = _T(' '); *p++ = ch; } *p = _T('\0'); len = p - duplicated; // simple wordbreak algorithm. looks for single spaces only, no tabs, // no grouping of multiple spaces if (format & DT_WORDBREAK) { for (size_t i = 0; i < len; i += _tcslen(duplicated + i) + 1) { PixelSize sz = CalcTextSize(duplicated + i); TCHAR *prev_p = NULL; // remove words from behind till line fits or no more space is found while (sz.cx > rc->right - rc->left && (p = _tcsrchr(duplicated + i, _T(' '))) != NULL) { if (prev_p) *prev_p = _T(' '); *p = _T('\0'); prev_p = p; sz = CalcTextSize(duplicated + i); } if (prev_p) { lines++; if (lines >= max_lines) break; } } } if (format & DT_CALCRECT) { rc->bottom = rc->top + lines * skip; delete[] duplicated; return; } PixelScalar y = (format & DT_VCENTER) && lines < max_lines ? (PixelScalar)(rc->top + rc->bottom - lines * skip) / 2 : rc->top; for (size_t i = 0; i < len; i += _tcslen(duplicated + i) + 1) { if (duplicated[i] != _T('\0')) { PixelScalar x; if (format & (DT_RIGHT | DT_CENTER)) { PixelSize sz = CalcTextSize(duplicated + i); x = (format & DT_CENTER) ? (rc->left + rc->right - sz.cx)/2 : rc->right - sz.cx; // DT_RIGHT } else { // default is DT_LEFT x = rc->left; } TextAutoClipped(x, y, duplicated + i); } y += skip; if (y >= rc->bottom) break; } delete[] duplicated; }
unsigned Canvas::DrawFormattedText(const PixelRect r, const TCHAR *text, unsigned format) { assert(text != nullptr); #ifndef UNICODE assert(ValidateUTF8(text)); #endif if (font == nullptr) return 0; unsigned skip = font->GetLineSpacing(); unsigned max_lines = (format & DT_CALCRECT) ? -1 : (r.GetHeight() + skip - 1) / skip; size_t len = _tcslen(text); TCHAR *duplicated = new TCHAR[len + 1], *p = duplicated; unsigned lines = 1; for (const TCHAR *i = text; *i != _T('\0'); ++i) { TCHAR ch = *i; if (ch == _T('\n')) { /* explicit line break */ if (++lines > max_lines) break; ch = _T('\0'); } else if (ch == _T('\r')) /* skip */ continue; else if ((unsigned)ch < 0x20) /* replace non-printable characters */ ch = _T(' '); *p++ = ch; } *p = _T('\0'); len = p - duplicated; // simple wordbreak algorithm. looks for single spaces only, no tabs, // no grouping of multiple spaces for (size_t i = 0; i < len; i += _tcslen(duplicated + i) + 1) { PixelSize sz = CalcTextSize(duplicated + i); TCHAR *prev_p = nullptr; // remove words from behind till line fits or no more space is found while (unsigned(sz.cx) > r.GetWidth() && (p = StringFindLast(duplicated + i, _T(' '))) != nullptr) { if (prev_p) *prev_p = _T(' '); *p = _T('\0'); prev_p = p; sz = CalcTextSize(duplicated + i); } if (prev_p) { lines++; if (lines >= max_lines) break; } } if (format & DT_CALCRECT) { delete[] duplicated; return lines * skip; } int y = (format & DT_VCENTER) && lines < max_lines ? (r.top + r.bottom - lines * skip) / 2 : r.top; for (size_t i = 0; i < len; i += _tcslen(duplicated + i) + 1) { if (duplicated[i] != _T('\0')) { int x; if (format & (DT_RIGHT | DT_CENTER)) { PixelSize sz = CalcTextSize(duplicated + i); x = (format & DT_CENTER) ? (r.left + r.right - sz.cx) / 2 : r.right - sz.cx; // DT_RIGHT } else { // default is DT_LEFT x = r.left; } TextAutoClipped(x, y, duplicated + i); if (format & DT_UNDERLINE) DrawHLine(x, x + CalcTextWidth(duplicated + i), y + font->GetAscentHeight() + 1, text_color); } y += skip; if (y >= r.bottom) break; } delete[] duplicated; return lines * skip; }