// Ported from https://github.com/kiwi515/ogws/blob/master/src/nw4r/ut/ut_TextWriterBase.cpp #include "nw4r/ut.h" #include namespace nw4r { namespace ut { template T *TextWriterBase::mFormatBuffer; template int TextWriterBase::mFormatBufferSize = 0x100; template TagProcessorBase TextWriterBase::mDefaultTagProcessor; template TextWriterBase::TextWriterBase() : mCharSpace(0.0f), mWidthLimit(NW4R_MATH_FLT_MAX), mLineSpace(0.0f), mTabWidth(4), mDrawFlag(0), mTagProcessor(&mDefaultTagProcessor) {} template TextWriterBase::~TextWriterBase() {} template void TextWriterBase::SetLineHeight(f32 height) { const Font *font = GetFont(); int lf = font != NULL ? font->GetLineFeed() : 0; mLineSpace = height - lf * GetScaleV(); } template f32 TextWriterBase::GetLineHeight() const { const Font *font = GetFont(); int lf = font != NULL ? font->GetLineFeed() : 0; return mLineSpace + GetScaleV() * lf; } template f32 TextWriterBase::CalcFormatStringWidth(const T *format, ...) const { Rect rect; _va_list_struct args; va_start(args, format); CalcVStringRect(&rect, format, &args); va_end(args); return rect.GetWidth(); } template f32 TextWriterBase::CalcFormatStringHeight(const T *format, ...) const { Rect rect; _va_list_struct args; va_start(args, format); CalcVStringRect(&rect, format, &args); va_end(args); return rect.GetHeight(); } template void TextWriterBase::CalcFormatStringRect(Rect *rect, const T *format, ...) const { _va_list_struct args; va_start(args, format); CalcVStringRect(rect, format, &args); va_end(args); } template void TextWriterBase::CalcVStringRect(Rect *rect, const T *format, va_list args) const { T *pBuffer; if (mFormatBuffer != NULL) { pBuffer = mFormatBuffer; } else { pBuffer = static_cast(__alloca(mFormatBufferSize)); } int len = VSNPrintf(pBuffer, mFormatBufferSize, format, args); len = Min(len, mFormatBufferSize - 1); CalcStringRect(rect, pBuffer, len); } template f32 TextWriterBase::CalcStringWidth(const T *str, int len) const { Rect rect; CalcStringRect(&rect, str, len); return rect.GetWidth(); } template f32 TextWriterBase::CalcStringHeight(const T *str, int len) const { Rect rect; CalcStringRect(&rect, str, len); return rect.GetHeight(); } template void TextWriterBase::CalcStringRect(Rect *rect, const T *str, int len) const { TextWriterBase clone(*this); clone.CalcStringRectImpl(rect, str, len); } template f32 TextWriterBase::Printf(const T *format, ...) { va_list args; va_start(args[0], format); f32 width = VPrintf(format, args); va_end(args[0]); return width; } template f32 TextWriterBase::VPrintf(const T *str, va_list args) { T *pBuffer; if (mFormatBuffer != NULL) { pBuffer = mFormatBuffer; } else { pBuffer = static_cast(__alloca(mFormatBufferSize)); } int len = VSNPrintf(pBuffer, mFormatBufferSize, str, args); len = Min(len, mFormatBufferSize - 1); return Print(pBuffer, len); } template f32 TextWriterBase::Print(const T *str, int len) { TextWriterBase clone(*this); f32 width = clone.PrintImpl(str, len, false); SetCursor(clone.GetCursorX(), clone.GetCursorY()); return width; } template f32 TextWriterBase::PrintfMutable(const T *format, ...) { va_list args; va_start(args[0], format); f32 width = VPrintfMutable(format, args); va_end(args[0]); return width; } template f32 TextWriterBase::VPrintfMutable(const T *format, va_list args) { T *pBuffer; if (mFormatBuffer != NULL) { pBuffer = mFormatBuffer; } else { pBuffer = static_cast(__alloca(mFormatBufferSize)); } int len = VSNPrintf(pBuffer, mFormatBufferSize, format, args); len = Min(len, mFormatBufferSize - 1); return PrintMutable(pBuffer, len); } template f32 TextWriterBase::PrintMutable(const T *str, int len) { return PrintImpl(str, len, true); } template f32 TextWriterBase::CalcLineWidth(const T *str, int len) { Rect rect; TextWriterBase clone(*this); clone.SetCursor(0.0f, 0.0f); clone.CalcLineRectImpl(&rect, &str, len); return rect.GetWidth(); } template int TextWriterBase::CalcLineRectImpl(Rect *pRect, const T **pStr, int length) { const T *strBegin = *pStr; const T *strEnd = strBegin + length; const bool useLimit = mWidthLimit < NW4R_MATH_FLT_MAX; PrintContext context = {this, strBegin}; f32 x = 0.0f; bool charSpace = false; bool overLimit = false; const T *prevStream = NULL; Rect prevRect; CharStrmReader reader = GetFont()->GetCharStrmReader(); pRect->left = 0.0f; pRect->right = 0.0f; pRect->top = Min(0.0f, GetLineHeight()); pRect->bottom = Max(0.0f, GetLineHeight()); prevRect = *pRect; reader.Set(strBegin); prevStream = NULL; u16 code = reader.Next(); while (static_cast(reader.GetCurrentPos()) <= strEnd) { if (code < ' ') { Rect r(x, 0.0f, 0.0f, 0.0f); context.str = static_cast(reader.GetCurrentPos()); context.flags = charSpace ? 0 : PRINTFLAGS_CHARSPACE; SetCursorX(x); if (useLimit && code != '\n' && prevStream != NULL) { PrintContext context2 = context; TextWriterBase clone(*this); Rect r; context2.writer = &clone; mTagProcessor->CalcRect(&r, code, &context2); if (r.GetWidth() > 0.0f) { if (clone.GetCursorX() - context.x > mWidthLimit) { overLimit = true; code = '\n'; reader.Set(prevStream); continue; } } } Operation oper = mTagProcessor->CalcRect(&r, code, &context); reader.Set(context.str); pRect->left = Min(pRect->left, r.left); pRect->top = Min(pRect->top, r.top); pRect->right = Max(pRect->right, r.right); pRect->bottom = Max(pRect->bottom, r.bottom); x = GetCursorX(); if (oper == OPERATION_END_DRAW) { *pStr += length; return false; } if (oper == OPERATION_NO_CHAR_SPACE) { charSpace = false; } else if (oper == OPERATION_CHAR_SPACE) { charSpace = true; } else if (oper == OPERATION_NEXT_LINE) { break; } } else { f32 dx = 0.0f; if (charSpace) { dx += GetCharSpace(); } if (IsWidthFixed()) { dx += GetFixedWidth(); } else { dx += GetFont()->GetCharWidth(code) * GetScaleH(); } if (useLimit && prevStream != NULL) { if (x + dx > mWidthLimit) { overLimit = true; code = '\n'; reader.Set(prevStream); continue; } } x += dx; pRect->left = Min(pRect->left, x); pRect->right = Max(pRect->right, x); charSpace = true; } if (useLimit) { prevStream = static_cast(reader.GetCurrentPos()); } code = reader.Next(); } *pStr = static_cast(reader.GetCurrentPos()); return overLimit; } template void TextWriterBase::CalcStringRectImpl(Rect *rect, const T *str, int len) { const T *end = str + len; int remain = len; rect->left = 0.0f; rect->right = 0.0f; rect->top = 0.0f; rect->bottom = 0.0f; SetCursor(0.0f, 0.0f); do { Rect r; CalcLineRectImpl(&r, &str, remain); remain = end - str; rect->left = Min(rect->left, r.left); rect->top = Min(rect->top, r.top); rect->right = Max(rect->right, r.right); rect->bottom = Max(rect->bottom, r.bottom); } while (remain > 0); } template f32 TextWriterBase::PrintImpl(const T *str, int len, bool m) { f32 cursorX = GetCursorX(); f32 cursorY = GetCursorY(); bool useLimit = mWidthLimit < NW4R_MATH_FLT_MAX; f32 orgCursorX = cursorX; f32 orgCursorY = cursorY; f32 cursorXAdj = 0.0f; f32 cursorYAdj = 0.0f; bool charSpace = false; const T *prevStream = str; const T *prevNewline = str; f32 textWidth = AdjustCursor(&cursorX, &cursorY, str, len); cursorXAdj = orgCursorX - GetCursorX(); cursorYAdj = orgCursorY - GetCursorY(); PrintContext context = {this, str, cursorX, cursorY}; CharStrmReader reader = GetFont()->GetCharStrmReader(); reader.Set(str); Operation oper; u16 ch = reader.Next(); while (static_cast(reader.GetCurrentPos()) - str <= len) { if (ch < ' ') { context.str = static_cast(reader.GetCurrentPos()); context.flags = charSpace ? 0 : PRINTFLAGS_CHARSPACE; if (useLimit && ch != '\n' && prevStream != prevNewline) { PrintContext context2 = context; TextWriterBase clone(*this); Rect rect; context2.writer = &clone; oper = mTagProcessor->CalcRect(&rect, ch, &context2); if (rect.GetWidth() > 0.0f && clone.GetCursorX() - context.x > mWidthLimit) { ch = '\n'; reader.Set(prevStream); continue; } } oper = mTagProcessor->Process(ch, &context); if (oper == OPERATION_NEXT_LINE) { if (IsDrawFlagSet(0x3, 0x1)) { int remain = len - (context.str - str); f32 width = CalcLineWidth(context.str, remain); f32 offset = (textWidth - width) / 2.0f; SetCursorX(context.x + offset); } else if (IsDrawFlagSet(0x3, 0x2)) { int remain = len - (context.str - str); f32 width = CalcLineWidth(context.str, remain); f32 offset = textWidth - width; SetCursorX(context.x + offset); } else { f32 width = GetCursorX() - context.x; textWidth = Max(textWidth, width); SetCursorX(context.x); } if (useLimit) { prevNewline = static_cast(reader.GetCurrentPos()); } charSpace = false; } else if (oper == OPERATION_NO_CHAR_SPACE) { charSpace = false; } else if (oper == OPERATION_CHAR_SPACE) { charSpace = true; } else if (oper == OPERATION_END_DRAW) { break; } reader.Set(context.str); } else { f32 baseY = GetCursorY(); if (useLimit && prevStream != prevNewline) { f32 baseX = GetCursorX(); f32 space = charSpace ? GetCharSpace() : 0.0f; f32 width = IsWidthFixed() ? GetFixedWidth() : GetFont()->GetCharWidth(ch) * GetScaleH(); if (baseX - cursorX + space + width > mWidthLimit) { ch = '\n'; reader.Set(prevStream); continue; } } if (charSpace) { MoveCursorX(GetCharSpace()); } charSpace = true; const Font *font = GetFont(); f32 adj = -font->GetBaselinePos() * GetScaleV(); MoveCursorY(adj); CharWriter::Print(ch); SetCursorY(baseY); } if (useLimit) { prevStream = static_cast(reader.GetCurrentPos()); } ch = reader.Next(); } f32 width = GetCursorX() - context.x; textWidth = Max(textWidth, width); if (IsDrawFlagSet(0x300, 0x100) || IsDrawFlagSet(0x300, 0x200)) { SetCursorY(orgCursorY); } else if (m) { if (IsDrawFlagSet(0x300, 0)) { SetCursorY(GetCursorY() - GetFontAscent()); } } else { MoveCursorY(cursorYAdj); } return textWidth; } template f32 TextWriterBase::AdjustCursor(f32 *x1, f32 *y1, const T *str, int len) { f32 textWidth = 0.0f; f32 textHeight = 0.0f; if (!IsDrawFlagSet(0x333, 0x300) && !IsDrawFlagSet(0x333, 0)) { Rect rect; CalcStringRect(&rect, str, len); textWidth = rect.left + rect.right; textHeight = rect.top + rect.bottom; } if (IsDrawFlagSet(0x30, 0x10)) { *x1 -= textWidth / 2; } else if (IsDrawFlagSet(0x30, 0x20)) { *x1 -= textWidth; } if (IsDrawFlagSet(0x300, 0x100)) { *y1 -= textHeight / 2; } else if (IsDrawFlagSet(0x300, 0x200)) { *y1 -= textHeight; } if (IsDrawFlagSet(0x3, 0x1)) { SetCursorX(*x1 + (textWidth - CalcLineWidth(str, len)) / 2); } else if (IsDrawFlagSet(0x3, 0x2)) { SetCursorX(*x1 + (textWidth - CalcLineWidth(str, len))); } else { SetCursorX(*x1); } if (IsDrawFlagSet(0x300, 0x300)) { SetCursorY(*y1); } else { SetCursorY(*y1 + GetFontAscent()); } return textWidth; } template struct TextWriterBase; template struct TextWriterBase; } // namespace ut } // namespace nw4r