Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add C2D_LayoutText and C2D_DuplicateText #38

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions include/c2d/text.h
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ typedef struct
u32 lines; ///< Number of lines in the text.
u32 words; ///< Number of words in the text.
C2D_Font font; ///< Font used to draw the text, or NULL for system font
float yScale; ///< Reserved for internal use.
} C2D_Text;

enum
Expand Down Expand Up @@ -131,7 +132,9 @@ void C2D_TextOptimize(const C2D_Text* text);
/** @brief Retrieves the total dimensions of a text object.
* @param[in] text Pointer to text object.
* @param[in] scaleX Horizontal size of the font. 1.0f corresponds to the native size of the font.
* Ignored if text has been run through C2D_LayoutText before this call.
* @param[in] scaleY Vertical size of the font. 1.0f corresponds to the native size of the font.
* Ignored if text has been run through C2D_LayoutText before this call.
* @param[out] outWidth (optional) Variable in which to store the width of the text.
* @param[out] outHeight (optional) Variable in which to store the height of the text.
*/
Expand All @@ -146,9 +149,32 @@ void C2D_TextGetDimensions(const C2D_Text* text, float scaleX, float scaleY, flo
* of the first line of text.
* @param[in] z Depth value of the text. If unsure, pass 0.0f.
* @param[in] scaleX Horizontal size of the font. 1.0f corresponds to the native size of the font.
* Ignored if text has been run through C2D_LayoutText before this call.
* @param[in] scaleY Vertical size of the font. 1.0f corresponds to the native size of the font.
* Ignored if text has been run through C2D_LayoutText before this call.
* @remarks The default 3DS system font has a glyph height of 30px, and the baseline is at 25px.
*/
void C2D_DrawText(const C2D_Text* text, u32 flags, float x, float y, float z, float scaleX, float scaleY, ...);

/** @brief Lays out text for drawing as C2D_DrawText would.
* @param[inout] text Pointer to text object to modify.
* @param[in] flags Text drawing flags.
* @param[in] scaleX Horizontal size of the font. 1.0f corresponds to the native size of the font.
* @param[in] scaleY Vertical size of the font. 1.0f corresponds to the native size of the font.
* @remarks The default 3DS system font has a glyph height of 30px, and the baseline is at 25px.
* If this function is used, only C2D_AtBaseline and C2D_WithColor need to be passed to C2D_DrawText.
* Running a C2D_Text through C2D_LayoutText a second time will not set its layout to the new layout.
* It will instead do nothing.
*/
void C2D_LayoutText(C2D_Text* text, u32 flags, float scaleX, float scaleY, ...);

/** @brief Duplicates a text into the given C2D_Text and C2D_TextBuf.
* @param[in] text Pointer to text object to copy.
* @param[out] outText Pointer to text object to copy into.
* @param[inout] buffer Text buffer handle to use.
* @returns Number of characters copied. This may be less than the number of characters in the original text if the
* buffer becomes full.
* @remarks This duplication includes layout data from C2D_LayoutText (or lack thereof)
*/
size_t C2D_DuplicateText(const C2D_Text* text, C2D_Text* outText, C2D_TextBuf buffer);
/** @} */
284 changes: 284 additions & 0 deletions source/text.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ const char* C2D_TextFontParseLine(C2D_Text* text, C2D_Font font, C2D_TextBuf buf
text->buf = buf;
text->begin = buf->glyphCount;
text->width = 0.0f;
text->yScale = 0.0f;
u32 wordNum = 0;
bool lastWasWhitespace = true;
while (buf->glyphCount < buf->glyphBufSize)
Expand Down Expand Up @@ -209,6 +210,7 @@ const char* C2D_TextFontParse(C2D_Text* text, C2D_Font font, C2D_TextBuf buf, co
text->width = 0.0f;
text->words = 0;
text->lines = 0;
text->yScale = 0.0f;

for (;;)
{
Expand All @@ -234,6 +236,11 @@ void C2D_TextOptimize(const C2D_Text* text)

void C2D_TextGetDimensions(const C2D_Text* text, float scaleX, float scaleY, float* outWidth, float* outHeight)
{
if (text->yScale != 0.0f)
{
scaleY = text->yScale;
scaleX = 1;
}
if (outWidth)
*outWidth = scaleX*text->width;
if (outHeight)
Expand Down Expand Up @@ -324,6 +331,9 @@ void C2D_DrawText(const C2D_Text* text, u32 flags, float x, float y, float z, fl
float glyphZ = z;
float glyphH;
float dispY;
// Reset Y scale if C2D_LayoutText has been called on this text
if (text->yScale != 0.0f)
scaleY = text->yScale;
if (text->font)
{
scaleX *= text->font->textScale;
Expand Down Expand Up @@ -362,6 +372,26 @@ void C2D_DrawText(const C2D_Text* text, u32 flags, float x, float y, float z, fl
C2Di_LineInfo* lines = NULL;
C2Di_WordInfo* words = NULL;

// Only set to non-zero by C2D_LayoutText
if (text->yScale != 0.0f)
{
for (cur = begin; cur != end; ++cur)
{
float glyphW = cur->width;
float glyphX = x+cur->xPos;
float glyphY = y+dispY*cur->lineNo;

C2Di_SetTex(cur->sheet);
C2Di_Update();
C2Di_AppendQuad();
C2Di_AppendVtx(glyphX, glyphY, glyphZ, cur->texcoord.left, cur->texcoord.top, 0.0f, 1.0f, color);
C2Di_AppendVtx(glyphX+glyphW, glyphY, glyphZ, cur->texcoord.right, cur->texcoord.top, 0.0f, 1.0f, color);
C2Di_AppendVtx(glyphX, glyphY+glyphH, glyphZ, cur->texcoord.left, cur->texcoord.bottom, 0.0f, 1.0f, color);
C2Di_AppendVtx(glyphX+glyphW, glyphY+glyphH, glyphZ, cur->texcoord.right, cur->texcoord.bottom, 0.0f, 1.0f, color);
}
return;
}

if (flags & C2D_WordWrap)
{
lines = alloca(sizeof(*lines)*text->lines);
Expand Down Expand Up @@ -560,3 +590,257 @@ void C2D_DrawText(const C2D_Text* text, u32 flags, float x, float y, float z, fl
break;
}
}

void C2D_LayoutText(C2D_Text* text, u32 flags, float scaleX, float scaleY, ...)
{
// If there are no words, we can't do the math calculations necessary with them. Just return; nothing would be drawn anyway.
if (text->words == 0)
return;
// If the text has already been laid out, don't re-do it
if (text->yScale != 0.0f)
return;

C2Di_Glyph* begin = &text->buf->glyphs[text->begin];
C2Di_Glyph* end = &text->buf->glyphs[text->end];
C2Di_Glyph* cur;

text->yScale = scaleY;

if (text->font)
{
scaleX *= text->font->textScale;
scaleY *= text->font->textScale;
} else
{
scaleX *= s_textScale;
scaleY *= s_textScale;
}
float maxWidth = scaleX*text->width;

float newWidth = 0;
u32 newLines = text->lines;

va_list va;
va_start(va, scaleY);

// C2D_AtBaseline does nothing here

// Color does nothing, but we need to get past it
if (flags & C2D_WithColor)
va_arg(va, u32);

if (flags & C2D_WordWrap)
maxWidth = va_arg(va, double); // Passed as float, but varargs promotes to double.

va_end(va);

C2Di_LineInfo* lines = NULL;
C2Di_WordInfo* words = NULL;
float* finalLineWidths = NULL;

if (flags & C2D_WordWrap)
{
lines = alloca(sizeof(*lines)*text->lines);
words = alloca(sizeof(*words)*text->words);
C2Di_CalcLineInfo(text, lines, words);
// The first word will never have a wrap offset in X or Y
for (u32 i = 1; i < text->words; i++)
{
// If the current word was originally on a different line than the last one, only the difference between new line number and original line number should be the same
if (words[i-1].start->lineNo != words[i].start->lineNo)
{
words[i].wrapXOffset = 0;
words[i].newLineNumber = words[i].start->lineNo + (words[i-1].newLineNumber - words[i-1].start->lineNo);
}
// Otherwise, if the current word goes over the width, with the previous word's offset taken into account...
else if (scaleX*(words[i-1].wrapXOffset + words[i].end->xPos + words[i].end->width) > maxWidth)
{
// Then set the X offset to the negative of the original position
words[i].wrapXOffset = -words[i].start->xPos;
// And set the new line number based off the last word's
words[i].newLineNumber = words[i-1].newLineNumber + 1;
}
// Otherwise both X offset and new line number should be the same as the last word's
else
{
words[i].wrapXOffset = words[i-1].wrapXOffset;
words[i].newLineNumber = words[i-1].newLineNumber;
}
}
newLines = words[text->words-1].newLineNumber + 1;
// Get the largest line width
u32 currentWord = 0;
while (currentWord != text->words)
{
u32 nextLineWord = currentWord + 1;
// Advance nextLineWord to the next word that's on a different line, or the end
while (nextLineWord != text->words && words[nextLineWord].newLineNumber == words[currentWord].newLineNumber) nextLineWord++;
// Finally, set the new line width
float candidateWidth = words[nextLineWord-1].end->xPos + words[nextLineWord-1].end->width - words[currentWord].start->xPos;
if (candidateWidth > newWidth)
newWidth = candidateWidth;

currentWord = nextLineWord;
}
}
else
{
newWidth = text->width;
}

switch (flags & C2D_AlignMask)
{
case C2D_AlignLeft:
for (cur = begin; cur != end; ++cur)
{
if (flags & C2D_WordWrap)
{
u32 consecutiveWordNum = cur->wordNo + lines[cur->lineNo].wordStart;
cur->xPos = scaleX*(cur->xPos + words[consecutiveWordNum].wrapXOffset);
cur->width *= scaleX;
cur->lineNo = words[consecutiveWordNum].newLineNumber;
}
else
{
cur->xPos = scaleX*cur->xPos;
cur->width *= scaleX;
}
}
break;
case C2D_AlignRight:
{
float finalLineWidths[flags & C2D_WordWrap ? words[text->words-1].newLineNumber + 1 : text->lines];
C2Di_CalcLineWidths(finalLineWidths, text, words, flags & C2D_WordWrap);

for (cur = begin; cur != end; cur++)
{
if (flags & C2D_WordWrap)
{
u32 consecutiveWordNum = cur->wordNo + lines[cur->lineNo].wordStart;
cur->xPos = scaleX*(cur->xPos + words[consecutiveWordNum].wrapXOffset - finalLineWidths[words[consecutiveWordNum].newLineNumber]);
cur->width *= scaleX;
cur->lineNo = words[consecutiveWordNum].newLineNumber;
}
else
{
cur->xPos = scaleX*(cur->xPos - finalLineWidths[cur->lineNo]);
cur->width *= scaleX;
}
}
}
break;
case C2D_AlignCenter:
{
float finalLineWidths[flags & C2D_WordWrap ? words[text->words-1].newLineNumber + 1 : text->lines];
C2Di_CalcLineWidths(finalLineWidths, text, words, flags & C2D_WordWrap);

for (cur = begin; cur != end; cur++)
{
if (flags & C2D_WordWrap)
{
u32 consecutiveWordNum = cur->wordNo + lines[cur->lineNo].wordStart;
cur->xPos = scaleX*(cur->xPos + words[consecutiveWordNum].wrapXOffset - finalLineWidths[words[consecutiveWordNum].newLineNumber]/2);
cur->width *= scaleX;
cur->lineNo = words[consecutiveWordNum].newLineNumber;
}
else
{
cur->xPos = scaleX*(cur->xPos - finalLineWidths[cur->lineNo]/2);
cur->width *= scaleX;
}
}
}
break;
case C2D_AlignJustified:
{
if (!(flags & C2D_WordWrap))
{
lines = alloca(sizeof(*lines)*text->lines);
words = alloca(sizeof(*words)*text->words);
C2Di_CalcLineInfo(text, lines, words);
}
// Get total width available for whitespace for all lines after wrapping
struct
{
float whitespaceWidth;
u32 wordStart;
u32 words;
} justifiedLineInfo[words[text->words - 1].newLineNumber + 1];
for (u32 i = 0; i < words[text->words - 1].newLineNumber + 1; i++)
{
justifiedLineInfo[i].whitespaceWidth = 0;
justifiedLineInfo[i].words = 0;
justifiedLineInfo[i].wordStart = 0;
}
for (u32 i = 0; i < text->words; i++)
{
// Calculate the total text width
justifiedLineInfo[words[i].newLineNumber].whitespaceWidth += words[i].end->xPos + words[i].end->width - words[i].start->xPos;
// Increment amount of words
justifiedLineInfo[words[i].newLineNumber].words++;
// And set the word starts
if (i > 0 && words[i-1].newLineNumber != words[i].newLineNumber)
justifiedLineInfo[words[i].newLineNumber].wordStart = i;
}
for (u32 i = 0; i < words[text->words - 1].newLineNumber + 1; i++)
{
// Transform it from total text width to total whitespace width
justifiedLineInfo[i].whitespaceWidth = maxWidth - justifiedLineInfo[i].whitespaceWidth;
// And then get the width of a single whitespace
if (justifiedLineInfo[i].words > 1)
justifiedLineInfo[i].whitespaceWidth /= justifiedLineInfo[i].words - 1;
}

// Set up final word beginnings and ends
struct
{
float xBegin;
float xEnd;
} wordPositions[text->words];
wordPositions[0].xBegin = 0;
wordPositions[0].xEnd = wordPositions[0].xBegin + words[0].end->xPos + words[0].end->width - words[0].start->xPos;
for (u32 i = 1; i < text->words; i++)
{
wordPositions[i].xBegin = words[i-1].newLineNumber != words[i].newLineNumber ? 0 : wordPositions[i-1].xEnd;
wordPositions[i].xEnd = wordPositions[i].xBegin + words[i].end->xPos + words[i].end->width - words[i].start->xPos;
}

for (cur = begin; cur != end; cur++)
{
u32 consecutiveWordNum = cur->wordNo + lines[cur->lineNo].wordStart;
// The beginning position for this word, plus the offset of this glyph within the word, plus the whitespace width for this line times the word number within the line
cur->xPos = scaleX*wordPositions[consecutiveWordNum].xBegin + scaleX*(cur->xPos - words[consecutiveWordNum].start->xPos) + justifiedLineInfo[words[consecutiveWordNum].newLineNumber].whitespaceWidth*(consecutiveWordNum - justifiedLineInfo[words[consecutiveWordNum].newLineNumber].wordStart);
cur->width *= scaleX;
cur->lineNo = words[consecutiveWordNum].newLineNumber;
}
}
break;
}

text->width = newWidth * scaleX;
text->lines = newLines;
}

size_t C2D_DuplicateText(const C2D_Text* text, C2D_Text* outText, C2D_TextBuf buffer)
{
outText->buf = buffer;
outText->width = text->width;
outText->lines = text->lines;
outText->words = text->words;
outText->font = text->font;
outText->yScale = text->yScale;

size_t copied = text->end - text->begin;
if (copied + buffer->glyphCount > buffer->glyphBufSize)
{
copied = buffer->glyphBufSize - buffer->glyphCount;
}

outText->begin = buffer->glyphCount;
memcpy(&buffer->glyphs[buffer->glyphCount], &text->buf->glyphs[text->begin], copied * sizeof(C2Di_Glyph));
buffer->glyphCount += copied;

outText->end = buffer->glyphCount;

return copied;
}