/*--------------------------------------------------------------------------
  Project:  Manual 2D Library - m2d
  File:     m2d_textBox.cpp

  Copyright (C)2007-2009 Nintendo  All Rights Reserved.

  These coded instructions, statements, and computer programs contain
  proprietary information of Nintendo of America Inc. and/or Nintendo
  Company Ltd., and are protected by Federal copyright law. They may
  not be disclosed to third parties or copied or duplicated in any form,
  in whole or in part, without the prior written consent of Nintendo.

  $Revision: 1.50 $
 *-------------------------------------------------------------------------*/

#include <nitro.h>

// Use wchar_t (utf-16) as the string type handled by string rendering functions
#define NNS_G2D_UNICODE
#include <nnsys/g2d/g2d_TextCanvas.h>

#include <nnsys/misc.h>
#include <ntmv/m2d/debug.h>
#include <ntmv/m2d/drawInfo.h>
#include <ntmv/m2d/page.h>
#include <ntmv/m2d/textBox.h>
#include <ntmv/m2d/common.h>
#include <ntmv/m2d/texMap.h>
#include <ntmv/m2d/resourceAccessor.h>
#include <cstring>

namespace ntmv
{
namespace m2d
{

namespace
{

/*
    Structure that represents colors using their RGB components
*/
struct Rgb5
{
    static const int ELEMENT_MAX         = 31;

                Rgb5()
                :   r(0),
                    g(0),
                    b(0)
                {}

                Rgb5(
                    u8  Ar,
                    u8  Ag,
                    u8  Ab
                )
                :   r(Ar),
                    g(Ag),
                    b(Ab)
                {}

                Rgb5(u16 col);

                operator u16 () const       { return GX_RGB(r, g, b); }

    Rgb5&       operator += (Rgb5 other)
    {
        r = static_cast<u8>(Min(ELEMENT_MAX, r + other.r));
        g = static_cast<u8>(Min(ELEMENT_MAX, g + other.g));
        b = static_cast<u8>(Min(ELEMENT_MAX, b + other.b));
        return *this;
    }

    u8          r;
    u8          g;
    u8          b;
};

/*
    Structure for text processing.

    This derives from and extends the NNSG2dTagCallbackInfo structure that is accepted by the callback for the NNS_G2dTextCanvasDrawTaggedText function.
    
*/
struct TagCallbackInfoEx : NNSG2dTagCallbackInfo
{
    bool                bCharWrited;    // Whether text processing was the last to adjust x values
                                        // This value is used to determine whether character spacing is included.
                                        // 
};


/*---------------------------------------------------------------------------*
  Name:         GetRgb8

  Description:  Constructor.
                Constructs an object from a u16 RGB value.

  Arguments:    col:    The u16 RGB value to convert.

  Returns:      None.
 *---------------------------------------------------------------------------*/
inline
Rgb5::Rgb5(u16 col)
{
    r = static_cast<u8>((col >>  0) & 0x1F);
    g = static_cast<u8>((col >>  5) & 0x1F);
    b = static_cast<u8>((col >> 10) & 0x1F);
}

/*---------------------------------------------------------------------------*
  Name:         DivRgb5

  Description:  Divides color components.
 
  Arguments:    src:  Color component to divide.
                c:    Numerator value.
                m:    Denominator value.

  Returns:      Returns the value of the divided Rgb5 structure.
 *---------------------------------------------------------------------------*/
inline
const Rgb5
DivRgb5(
    Rgb5    src,
    s32     c,
    s32     m
)
{
    return Rgb5(
        static_cast<u8>(src.r * c / m),
        static_cast<u8>(src.g * c / m),
        static_cast<u8>(src.b * c / m));
}

inline
u32
GetLineOffset(
    Size    frameBufSize,
    s16     y
)
{
    return static_cast<u32>((y & (frameBufSize.height - 1)) * frameBufSize.width);
}

/*---------------------------------------------------------------------------*
  Name:         DrawGlyphLine

  Description:  Renders the specified glyph to CharCanvas.
                This is for a CharCanvas where a fixed number of characters are empty in each line.

  Arguments:    pCC:    Pointer to CharCanvas.
                pFont:  Pointer to the font used for rendering.
                x:      Upper-left character coordinate.
                y:      Upper-left character coordinate.
                cl:     Color number of character color.
                pGlyph: Pointer to the glyph to render.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
DrawGlyphLine(
    const NNSG2dCharCanvas* pCC,
    const NNSG2dFont*       pFont,
    int                     dx,
    int                     dy,
    int                     cl,
    const NNSG2dGlyph*      pGlyph
)
{
    const Size fontSz(pGlyph->pWidths->glyphWidth, NNS_G2dFontGetCellHeight(pFont));
    const Rect& invRect = *reinterpret_cast<const Rect*>(pCC->param);
    const Rect drawRect = detail::GetDrawRect(Point(static_cast<s16>(dx), static_cast<s16>(dy)), fontSz, invRect);
    if (drawRect.GetWidth() == 0) // Quit when there is no rendering area
    {
        return;
    }

    const u16 texCol = static_cast<u16>(cl);    // Text color
    const int fontBpp = NNS_G2dFontGetBpp(pFont);
    NNS_ASSERT(fontBpp == 2);

    const int bitMask = (1 << fontBpp) - 1;
    const int fontMaxVal = bitMask;

    Rgb5 textBaseRgb(texCol);

    Rgb5 textCols[4];
    textCols[0] = Rgb5(0, 0, 0);
    textCols[1] = DivRgb5(textBaseRgb, 1, 3);
    textCols[2] = DivRgb5(textBaseRgb, 2, 3);
    textCols[3] = textBaseRgb;

    u16 *const frameBuf = reinterpret_cast<u16*>(pCC->charBase);
    Size frameBufSize(static_cast<s16>(pCC->areaWidth), static_cast<s16>(pCC->areaHeight));
    // The width is fixed at 256.
    NNS_ASSERT(frameBufSize.width == 0x100);

    // The height should be a multiple of 256.
    NNS_ASSERT(frameBufSize.height != 0 && (frameBufSize.height & (0x100 - 1)) == 0);

    for (int sy = drawRect.top; sy < drawRect.bottom; sy++)
    {
        const u32 lineOffset = GetLineOffset(frameBufSize, static_cast<s16>(dy + sy));

        for (int sx = drawRect.left; sx < drawRect.right; sx++)
        {
            const int glpIdx = sy * NNS_G2dFontGetCellWidth(pFont) + sx;
            const u8 bits = pGlyph->image[glpIdx >> fontBpp];  // = glpIdx / 8
            const int bitShift = (bitMask - (glpIdx & bitMask)) * fontBpp;
            const int fontIn = (bits >> bitShift) & bitMask;

            if (fontIn == fontMaxVal)  // Definitely a text color
            {
                frameBuf[lineOffset + (dx + sx)] = static_cast<u16>(texCol | GX_RGBA_A_MASK);
            }
            else if (fontIn > 0)    // Blend the frame buffer with the text color
            {
                const Rgb5 frmBaseRgb(frameBuf[lineOffset + (dx + sx)]);
                Rgb5 frmRgb = DivRgb5(frmBaseRgb, fontMaxVal - fontIn, fontMaxVal);
                frmRgb += textCols[fontIn];
                const u16 frmCol = frmRgb;
                frameBuf[lineOffset + (dx + sx)] = static_cast<u16>(frmCol | GX_RGBA_A_MASK);
            }
            else
            {
                // Frame buffer color (do nothing)
            }
        }
    }
}

/*---------------------------------------------------------------------------*
  Name:         ClearContinuous

  Description:  Fills all the characters belonging to CharCanvas with a designated color.
                
                This is for a CharCanvas where all characters are continuous.

  Arguments:    pCC:    Pointer to CharCanvas.
                cl:     Color number of the fill color.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ClearContinuous(const NNSG2dCharCanvas* /* pCC */, int /* cl */)
{
}

/*---------------------------------------------------------------------------*
  Name:         ClearAreaLine

  Description:  Uses a designated color to fill a specified area of the characters associated with CharCanvas.
                
                This is for a CharCanvas where a fixed number of characters are empty in each line.

  Arguments:    pCC:    Pointer to CharCanvas.
                cl:     Color number used in filling
                x:      x-coordinate of the upper-left corner of the rectangle
                y:      y-coordinate of the upper-left corner of the rectangle
                w:      rectangle width
                h:      rectangle height

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ClearAreaLine(
    const NNSG2dCharCanvas* /* pCC */,
    int /* cl */,
    int /* x */,
    int /* y */,
    int /* w */,
    int /* h */
)
{
}

const NNSiG2dCharCanvasVTable VTABLE_BG =
    { DrawGlyphLine, ClearContinuous, ClearAreaLine };

/*---------------------------------------------------------------------------*
  Name:         InitCharCanvas

  Description:  Initializes CharCanvas by directly designating its members.

  Arguments:    pCC:        Pointer to the CharCanvas to initialize.
                areaWidth:  CharCanvas width in character units.
                areaHeight: CharCanvas height in character units.
                colorMode:  Color mode of the render target character.
                charBase:   Pointer to the top of the character that is the render target
                pDrawGlyph: Pointer to the function used in glyph rendering
                pDrawGlyph: Pointer to the function used in clearing
                pDrawGlyph: Pointer to the function used in sectional clearing
                param:      Parameters used by the character rendering function

  Returns:      None.
 *---------------------------------------------------------------------------*/
void InitCharCanvas(
    NNSG2dCharCanvas* pCC,
    void* charBase,
    int areaWidth,
    int areaHeight,
    NNSG2dCharaColorMode colorMode,
    const NNSiG2dCharCanvasVTable* vtable,
    u32 param
)
{
    NNS_G2D_POINTER_ASSERT(pCC);
    NNS_G2D_ASSERT( 0 < areaWidth );
    NNS_G2D_ASSERT( 0 < areaHeight );
    
	// Disable Bpp ASSERT
    // NNS_G2D_COLORMODE_ASSERT( colorMode );

    NNS_G2D_POINTER_ASSERT( charBase );
    NNS_G2D_ALIGN_ASSERT(charBase, 4);
    NNS_G2D_POINTER_ASSERT( vtable );
    NNS_G2D_POINTER_ASSERT( vtable->pDrawGlyph );
    NNS_G2D_POINTER_ASSERT( vtable->pClear );
    NNS_G2D_POINTER_ASSERT( vtable->pClearArea );

    pCC->areaWidth  = areaWidth;
    pCC->areaHeight = areaHeight;
    pCC->dstBpp     = colorMode;
    pCC->charBase   = (u8*)charBase;
    pCC->vtable     = vtable;
    pCC->param      = param;
}

/*---------------------------------------------------------------------------*
  Name:         NNS_G2dCharCanvasInitForBG

  Description:  Initializes the CharCanvas for use by the background.

  Arguments:    pCC:        Pointer to the CharCanvas to initialize.
                charBase:   Pointer to the top of the character that is the render target
                areaWidth:  CharCanvas width in character units.
                areaHeight: CharCanvas height in character units.
                colorMode:  Color mode of the render target character.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
NNS_G2dCharCanvasInitForBGDirect(
    NNSG2dCharCanvas* pCC,
    void* charBase,
    int areaWidth,
    int areaHeight,
    NNSG2dCharaColorMode colorMode,
    const Rect*     pInvRect
)
{
    NNS_G2D_POINTER_ASSERT( pCC );
    NNS_G2D_POINTER_ASSERT( charBase );
    NNS_G2D_ALIGN_ASSERT(charBase, 4);
    NNS_G2D_ASSERT( 0 < areaWidth );
    NNS_G2D_ASSERT( 0 < areaHeight );
    
	// Disable Bpp ASSERT
    // NNS_G2D_COLORMODE_ASSERT( colorMode );

    InitCharCanvas(
        pCC,
        charBase, areaWidth, areaHeight, colorMode,
        &VTABLE_BG, reinterpret_cast<u32>(pInvRect)
    );
}

/*---------------------------------------------------------------------------*
  Name:         G2dCharCanvasDrawGlyph

  Description:  Renders the specified glyph to CharCanvas.

  Arguments:    pCC:    Pointer to CharCanvas.
                pFont:  Pointer to the font used for rendering.
                x:      Upper-left character coordinate.
                y:      Upper-left character coordinate.
                cl:     Color number of character color.
                pGlyph: Pointer to the glyph to render.

  Returns:      None.
 *---------------------------------------------------------------------------*/
inline
void
G2dCharCanvasDrawGlyph(
    const NNSG2dCharCanvas* pCC,
    const NNSG2dFont* pFont,
    int x,
    int y,
    int cl,
    const NNSG2dGlyph* pGlyph
)
{
	// Disable Bpp ASSERT
    // NNS_G2D_CHARCANVAS_ASSERT( pCC );
    
    pCC->vtable->pDrawGlyph(pCC, pFont, x, y, cl, pGlyph);
}

/*---------------------------------------------------------------------------*
  Name:         G2dCharCanvasDrawChar

  Description:  Renders characters.

  Arguments:    pCC:    Pointer to CharCanvas.
                x:      Character's upper-left x-coordinate (text screen coordinate system)
                y:      Character's upper-left y-coordinate (text screen coordinate system)
                cl:     Color number of the character color
                ccode:  Character code

  Returns:      Returns the width in pixels of the rendered character.
 *---------------------------------------------------------------------------*/
int
G2dCharCanvasDrawChar(const NNSG2dCharCanvas* pCC, const NNSG2dFont* pFont, int x, int y, int cl, u16 ccode)
{
    NNSG2dGlyph glyph;

	// Disable Bpp ASSERT
    // NNS_G2D_CHARCANVAS_ASSERT( pCC );
    
    NNS_G2D_FONT_ASSERT( pFont );
    NNS_G2D_ASSERT( NNS_G2dFontGetBpp(pFont) <= pCC->dstBpp );
#if 0
    NNS_G2D_MINMAX_ASSERT( cl, 0,
        (1 << pCC->dstBpp)
        - (1 << NNS_G2dFontGetBpp(pFont)) + 1 );
#endif

    NNS_G2dFontGetGlyph(&glyph, pFont, ccode);

#ifdef NNS_G2D_FONT_ENABLE_DIRECTION_SUPPORT
    switch( NNS_G2dFontGetFlags(pFont) )
    {
    case (NNS_G2D_FONT_FLAG_ROT_0):
    case (NNS_G2D_FONT_FLAG_ROT_270|NNS_G2D_FONT_FLAG_TBRL):
#endif
        x += glyph.pWidths->left;
#ifdef NNS_G2D_FONT_ENABLE_DIRECTION_SUPPORT
        break;
    case (NNS_G2D_FONT_FLAG_ROT_90):
    case (NNS_G2D_FONT_FLAG_ROT_0|NNS_G2D_FONT_FLAG_TBRL):
        x -= NNS_G2dFontGetCellWidth(pFont);
        y += glyph.pWidths->left;
        break;
    case (NNS_G2D_FONT_FLAG_ROT_180):
    case (NNS_G2D_FONT_FLAG_ROT_90|NNS_G2D_FONT_FLAG_TBRL):
        x -= glyph.pWidths->left + glyph.pWidths->glyphWidth;
        y -= NNS_G2dFontGetCellHeight(pFont);
        break;
    case (NNS_G2D_FONT_FLAG_ROT_270):
    case (NNS_G2D_FONT_FLAG_ROT_180|NNS_G2D_FONT_FLAG_TBRL):
        y -= glyph.pWidths->left + NNS_G2dFontGetCellHeight(pFont);
        break;
    }
#endif

    G2dCharCanvasDrawGlyph(pCC, pFont, x, y, cl, &glyph);

    return glyph.pWidths->charWidth;
}

inline u8
ClampColor(s16 colVal)
{
    return u8(colVal < 0 ? 0: (colVal > 255 ? 255: colVal));
}

/*---------------------------------------------------------------------------*
  Name:         G2dTextCanvasInit

  Description:  Initializes the TextCanvas.

  Arguments:    pTxn:   Pointer to the TextCanvas to initialize.
                pCC:    Pointer to the CharCanvas to be rendered on.
                pFont:  Pointer to the font used for rendering.
                hSpace: Space between characters.
                vSpace: Correction difference for the font leading.
                encode: Pointer to the character cutout callback function.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
G2dTextCanvasInit(
    NNSG2dTextCanvas* pTxn,
    const NNSG2dCharCanvas* pCC,
    const NNSG2dFont* pFont,
    int hSpace,
    int vSpace
)
{
    NNS_G2D_POINTER_ASSERT( pTxn );
    
	// Disable Bpp ASSERT
    // NNS_G2D_CHARCANVAS_ASSERT( pCC );
    
    NNS_G2D_FONT_ASSERT( pFont );

    pTxn->pCanvas       = pCC;
    pTxn->pFont         = pFont;
    pTxn->hSpace        = hSpace;
    pTxn->vSpace        = vSpace;
}

/*---------------------------------------------------------------------------*
  Name:         AddCharSpace

  Description:  Adds character spacing, as necessary, to the current (x) position.

  Arguments:    pTextWk:   Pointer to a structure used for text processing.

  Returns:      None.
 *---------------------------------------------------------------------------*/
inline
void
AddCharSpace(TagCallbackInfoEx* pTextWk)
{
    // If text processing last adjusted x values, add character spacing.
    // 
    if (pTextWk->bCharWrited)
    {
        pTextWk->x += pTextWk->txn.hSpace;
        pTextWk->bCharWrited = false;
    }
}

/*---------------------------------------------------------------------------*
  Name:         ProcessTagChar

  Description:  Processes tag characters (and information that accompanies tag characters) and performs rendering.

  Arguments:    pDrawInfo:  Pointer to a DrawInfo object.
                c:          Tag character
                pTextWk:    Pointer to a structure used for text processing.
                            The str member variable specifies the tag character's next position. When information is associated with the tag character, this will change to specify the character position after the processed byte string.
                            
                            
                            

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ProcessTagChar(
    const DrawInfo*         pDrawInfo,
    NNSG2dChar              c,
    TagCallbackInfoEx*      pTextWk
)
{
    switch (c)
    {
    case detail::TAGCHAR_IMG:   // img tag
        {
            // const int imgBaseY = NNS_G2dFontGetBaselinePos(pTextWk->txn.pFont);    // Standard position to place images
            const int imgBaseY = NNS_G2dFontGetCellHeight(pTextWk->txn.pFont);     // Standard position to place images
            const TextureList *const  pTexList = pDrawInfo->GetPage()->GetTextureList();

            const ImageTagData& data = *reinterpret_cast<const ImageTagData*>(pTextWk->str);
            pTextWk->str = reinterpret_cast<const wchar_t*>(
                reinterpret_cast<const u8*>(pTextWk->str) + data.size   // Set the next character position
            );
            
            if (data.size == sizeof(data))      // The byte length and texture index value
            {
                const int texIdx = data.texIdx - 1;
                TexMap texMap;
                texMap.Set(&pTexList->texList[texIdx]);

                AddCharSpace(pTextWk);
                detail::DrawImage(*pDrawInfo, Point(static_cast<s16>(pTextWk->x), static_cast<s16>(pTextWk->y + imgBaseY - texMap.GetSize().height)), texMap);
                pTextWk->x += texMap.GetSize().width;
                pTextWk->bCharWrited = true;    // Process the image as text.
            }
            else
            {
                NNS_WARNING(false, "img tag data illegal length. [%d]\n", data.size);
            }
        }
        break;

    case detail::TAGCHAR_COLOR: // color tag
        {
            const ColorTagData& data = *reinterpret_cast<const ColorTagData*>(pTextWk->str);
            pTextWk->str = reinterpret_cast<const wchar_t*>(
                reinterpret_cast<const u8*>(pTextWk->str) + data.size   // Set the next character position
            );
            
            if (data.size == sizeof(data))      // The byte length and texture index value
            {
                pTextWk->clr = data.color;
            }
            else
            {
                NNS_WARNING(false, "color tag data illegal length. [%d]\n", data.size);
            }

            // Leave pTextWk->bCharWrited unchanged because the color tag does not have a width.
        }
        break;

    case detail::TAGCHAR_HSPACER: // Space in the horizontal direction
        {
            const SpacerTagData& data = *reinterpret_cast<const SpacerTagData*>(pTextWk->str);
            pTextWk->str = reinterpret_cast<const wchar_t*>(
                reinterpret_cast<const u8*>(pTextWk->str) + data.size   // Set the next character position
            );
            pTextWk->x += data.value;
            pTextWk->bCharWrited = false;   // Spaces are not handled as characters.
        }
        break;
    }
}

/*---------------------------------------------------------------------------*
  Name:         DrawTaggedTextLine

  Description:  Renders a (one-line) string that has embedded tags.

  Arguments:    pTextWk:   Pointer to a structure used for text processing.
                edStrPos:  Ending position of the string to render.
                drawInfo:  A reference to a DrawInfo object.

  Returns:      None.
 *---------------------------------------------------------------------------*/
void
DrawTaggedTextLine(
    TagCallbackInfoEx*  pTextWk,
    const void*         edStrPos,
    const DrawInfo&     drawInfo
)
{
    NNS_G2D_FONT_ASSERT(pTextWk->txn.pFont);
    NNS_G2D_POINTER_ASSERT(pTextWk->str);
    NNS_G2D_ASSERT( NNS_G2dFontGetBpp(NNS_G2dTextCanvasGetFont(&pTextWk->txn))
                    <= NNS_G2dTextCanvasGetCharCanvas(&pTextWk->txn)->dstBpp );
#if 0
    NNS_G2D_MINMAX_ASSERT( pTextWk->clr, 0,
        (1 << NNS_G2dTextCanvasGetCharCanvas(pTextWk->txn)->dstBpp)
        - (1 << NNS_G2dFontGetBpp(NNS_G2dTextCanvasGetFont(pTextWk->txn))) + 1 );
#endif

    NNSiG2dSplitCharCallback getNextChar = NNSi_G2dFontGetSpliter(pTextWk->txn.pFont);

    const wchar_t* lineHeadPos = static_cast<const wchar_t*>(pTextWk->str);

    while (reinterpret_cast<const void*>(pTextWk->str) < edStrPos)
    {
        const wchar_t* crPos = pTextWk->str;  // Current character position
        const u16 c = getNextChar((const void**)&pTextWk->str);
        if (c == 0 || c == '\n')    // Quit if this is a terminating character or a newline character
        {
            break;
        }

        if (c < ' ')    // Tag processing
        {
            ProcessTagChar(&drawInfo, c, pTextWk);
        }
        else    // render one character
        {
            AddCharSpace(pTextWk);
            pTextWk->x += G2dCharCanvasDrawChar(pTextWk->txn.pCanvas, pTextWk->txn.pFont, pTextWk->x, pTextWk->y, pTextWk->clr, c);
            pTextWk->bCharWrited = true;
        }
    }
}

}   // namespace

NTMV_RNT_RUNTIME_TYPEINFO_DEFINITION(TextBox, TextBox::Base);    // Defines an instance of runtime type information

TextBox::TextBox(
    const res::TextBox* pBlock,
    const ResBlockSet&  resBlockSet
)
:   Pane(pBlock, resBlockSet)
{
    std::memset(&mBits, 0, sizeof(mBits));

    mTextBuf = detail::GetTextPoolStr(resBlockSet.pTextBlock->offsets, pBlock->textIdx);
    mTextLines = pBlock->textLines;
    mLineNum = pBlock->lineNum;

    mTextColor = pBlock->textCol;

    mFontSize = pBlock->fontSize;
    mTextPosition = pBlock->textPosition;
    mBits.textAlignment = pBlock->textAlignment;
    mCharSpace = pBlock->charSpace;
    mLineSpace = pBlock->lineSpace;

    mMargin = pBlock->margin;
}

void
TextBox::CalcSelf(
    const DrawInfo& drawInfo,
    Point           parentPosition
)
{
    if (!mTextBuf)
    {
        return;
    }

    Base::CalcSelf(drawInfo, parentPosition);

    // Calculate the rectangular size of the entire text
    mTextSize.Set(0, 0);
    for (int i = 0; i < mLineNum; ++i)
    {
        mTextSize.width = Max(mTextSize.width, mTextLines[i].size.width);
        if (i > 0)
        {
            mTextSize.height += mLineSpace;
        }
        mTextSize.height += mTextLines[i].size.height;
    }
}

void
TextBox::DrawSelf(const DrawInfo& drawInfo)
{
    if (!mTextBuf)
    {
        return;
    }

    NNSG2dCharCanvas charCanvas;
    TagCallbackInfoEx textWork;

    SetupG2dTextCanvas(
        &textWork.txn,
        &charCanvas,
        drawInfo);

    // Size of the text rendering area with margins
    Size areaSize(
            static_cast<s16>(mSize.width  - (mMargin.l + mMargin.r)),
            static_cast<s16>(mSize.height - (mMargin.t + mMargin.b)));

    // Find the upper-left coordinate, taking into consideration the display alignment of the margin and entire text.
    Point ltPos(
            static_cast<s16>(
                  mPosition.x
                + mMargin.l
                + ((areaSize.width  - mTextSize.width ) * GetTextMagH()) / 2),
            static_cast<s16>(
                  mPosition.y
                + mMargin.t
                + ((areaSize.height - mTextSize.height) * GetTextMagV()) / 2)
    );

    const s16 hMag = GetTextAlignMag();

    textWork.str = mTextBuf;

    textWork.clr = mTextColor;  // Predetermined color for the text box
    for (int i = 0; i < mLineNum; ++i)
    {
        // Ending position of the string to render
        const wchar_t *const endStrPos = reinterpret_cast<const wchar_t*>(reinterpret_cast<const u8*>(textWork.str) + mTextLines[i].bytes);

        const Rect& drawRect = detail::GetDrawRect(ltPos, mTextLines[i].size, drawInfo.GetInvalidateRect());
        if (drawRect.GetHeight() > 0)   // When there is a rendering area
        {
            // adjusts the text in a single line so that it fits in the width of the text box
            const int textPosX = ((mTextSize.width - mTextLines[i].size.width) * hMag) >> 1;

            textWork.x = ltPos.x + textPosX;
            textWork.y = ltPos.y + mTextLines[i].charStartY;   // The top position for character rendering
            textWork.bCharWrited = false;
            DrawTaggedTextLine(&textWork, endStrPos, drawInfo);
        }
        textWork.str = endStrPos;

        ltPos.y += mTextLines[i].size.height + mLineSpace;
    }
}

void
TextBox::SetupG2dTextCanvas(
    NNSG2dTextCanvas*   pTextCanvas,
    NNSG2dCharCanvas*   pCharCanvas,
    const DrawInfo&     drawInfo
)
{
    NNS_G2dCharCanvasInitForBGDirect(
        pCharCanvas,
        drawInfo.GetFrameBuf(),
        drawInfo.GetFrameBufSize().width,
        drawInfo.GetFrameBufSize().height,
        NNSG2dCharaColorMode(16),			// Direct, 16-bit 
        &drawInfo.GetInvalidateRect());

    G2dTextCanvasInit(
        pTextCanvas,
        pCharCanvas,
        drawInfo.GetFont(static_cast<u16>(mFontSize.height)),
        mCharSpace,
        mLineSpace);
}

s16
TextBox::GetTextMagH() const
{
    switch (GetTextPositionH())
    {
    case HORIZONTALPOSITION_LEFT:
    default:                        return 0;
    case HORIZONTALPOSITION_CENTER: return 1;
    case HORIZONTALPOSITION_RIGHT:  return 2;
    }
}

s16
TextBox::GetTextMagV() const
{
    switch (GetTextPositionV())
    {
    case VERTICALPOSITION_TOP:
    default:                        return 0;
    case VERTICALPOSITION_CENTER:   return 1;
    case VERTICALPOSITION_BOTTOM:   return 2;
    }
}

s16
TextBox::GetTextAlignMag() const
{
    switch (GetTextAlignment())
    {
    case TEXTALIGNMENT_SYNCHRONOUS:
    default:                        return GetTextMagH();
    case TEXTALIGNMENT_LEFT:        return 0;
    case TEXTALIGNMENT_CENTER:      return 1;
    case TEXTALIGNMENT_RIGHT:       return 2;
    }
}

}   // namespace m2d
}   // namespace ntmv
