OpenClonk
C4LogBuf.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2013-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 // a buffer holding a log history
17 
18 #include "C4Include.h"
19 #include "lib/C4LogBuf.h"
20 
21 #include "graphics/C4FontLoader.h"
22 
23 C4LogBuffer::C4LogBuffer(int iSize, int iMaxLines, int iLBWidth, const char *szIndentChars, bool fDynamicGrow, bool fMarkup)
24  : iBufSize(iSize), iFirstLinePos(0), iAfterLastLinePos(0), iLineDataPos(0),
25  iNextLineDataPos(0), iMaxLineCount(iMaxLines), iLineCount(0), iLineBreakWidth(iLBWidth), fDynamicGrow(fDynamicGrow), fMarkup(fMarkup)
26 {
27  // copy indent
28  if (szIndentChars && *szIndentChars)
29  {
30  szIndent = new char[strlen(szIndentChars)+1];
31  strcpy(szIndent, szIndentChars);
32  }
33  else szIndent = nullptr;
34  // create buffers, if buffer size is given. Otherwise, create/grow them dynamically
35  if (iBufSize) szBuf = new char[iBufSize]; else szBuf=nullptr;
36  if (iMaxLineCount) pLineDataBuf = new LineData[iMaxLineCount]; else pLineDataBuf=nullptr;
37  assert(fDynamicGrow || (iBufSize && iMaxLineCount));
38 }
39 
41 {
42  // free buffers
43  delete [] pLineDataBuf;
44  delete [] szBuf;
45  // free indent
46  if (szIndent) delete [] szIndent;
47 }
48 
49 void C4LogBuffer::GrowLineCountBuffer(size_t iGrowBy)
50 {
51  assert(fDynamicGrow);
52  if (!iGrowBy) return;
53  LineData *pNewBuf = new LineData[iMaxLineCount += iGrowBy];
54  if (iLineCount) memcpy(pNewBuf, pLineDataBuf, sizeof(LineData) * iLineCount);
55  delete [] pLineDataBuf;
56  pLineDataBuf = pNewBuf;
57 }
58 
59 void C4LogBuffer::GrowTextBuffer(size_t iGrowBy)
60 {
61  assert(fDynamicGrow);
62  if (!iGrowBy) return;
63  char *pNewBuf = new char[iBufSize += iGrowBy];
64  if (iAfterLastLinePos) memcpy(pNewBuf, szBuf, sizeof(char) * iAfterLastLinePos);
65  delete [] szBuf;
66  szBuf = pNewBuf;
67 }
68 
69 void C4LogBuffer::DiscardFirstLine()
70 {
71  // any line to discard? - this is guaranteed (private call)
72  assert(iLineCount && szBuf && !fDynamicGrow);
73  // dec line count
74  --iLineCount;
75  // advance first line pos until delimeter char is reached
76  while (szBuf[iFirstLinePos]) ++iFirstLinePos;
77  // skip delimeter
78  ++iFirstLinePos;
79  // check if end of used buffer is reached (by size or double delimeter)
80  if (iFirstLinePos == iBufSize || !szBuf[iFirstLinePos] || !iLineCount)
81  {
82  // end of buffer reached: wrap to front
83  iFirstLinePos = 0;
84  }
85  // discard line data
86  ++iLineDataPos;
87  if (!iLineCount) iLineDataPos = iNextLineDataPos = 0;
88 }
89 
90 void C4LogBuffer::AppendSingleLine(const char *szLine, int iLineLength, const char *szIndent, CStdFont *pFont, DWORD dwClr, bool fNewPar)
91 {
92  // security: do not append empty line
93  if (!szLine || !iLineLength || !*szLine) return;
94  // discard first line or grow buffer if data buffer is full
95  if (iLineCount == iMaxLineCount)
96  {
97  if (fDynamicGrow)
98  GrowLineCountBuffer(4 + iMaxLineCount/2);
99  else
100  DiscardFirstLine();
101  }
102  // include trailing zero-character
103  ++iLineLength;
104  // include indent
105  if (szIndent) iLineLength += strlen(szIndent);
106  // but do not add a message that is longer than the buffer (shouldn't happen anyway)
107  if (iLineLength > iBufSize && !fDynamicGrow)
108  {
109  // cut from beginning then
110  szLine += iLineLength - iBufSize;
111  iLineLength = iBufSize;
112  }
113  // check if the rest of the buffer is sufficient
114  if (iAfterLastLinePos + iLineLength > iBufSize)
115  {
116  if (fDynamicGrow)
117  {
118  // insufficient buffer in grow mode: grow text buffer
119  GrowTextBuffer(std::max(iLineLength, iBufSize/2));
120  }
121  else
122  {
123  // insufficient buffer in non-grow mode: wrap to beginning
124  // discard any messages in rest of buffer
125  // if there are no messages, iFirstLinePos is always zero
126  // and iAfterLastLinePos cannot be zero here
127  while (iFirstLinePos >= iAfterLastLinePos) DiscardFirstLine();
128  // add delimeter to mark end of used buffer
129  // if the buffer is exactly full by the last line, no delimeter is needed
130  if (iAfterLastLinePos < iBufSize) szBuf[iAfterLastLinePos] = 0;
131  // wrap insertion pos to beginning
132  iAfterLastLinePos = 0;
133  }
134  }
135  // discard any messages within insertion range of new message
136  if (!fDynamicGrow)
137  while (iLineCount && Inside(iFirstLinePos, iAfterLastLinePos, iAfterLastLinePos+iLineLength-1))
138  DiscardFirstLine();
139  // copy indent
140  int iIndentLen = 0;
141  if (szIndent)
142  {
143  iIndentLen = strlen(szIndent);
144  memcpy(szBuf + iAfterLastLinePos, szIndent, iIndentLen);
145  }
146  // copy message
147  if (iLineLength - iIndentLen > 1)
148  memcpy(szBuf + iAfterLastLinePos + iIndentLen, szLine, iLineLength-iIndentLen-1);
149  // add delimeter
150  iAfterLastLinePos += iLineLength;
151  szBuf[iAfterLastLinePos - 1] = 0;
152  // no need to add any double delimeters, because this is currently the end of the message list
153  // also no need to check for end of buffer here, because that will be done when the next message is inserted
154  // add line data
155  LineData &rData = pLineDataBuf[iNextLineDataPos];
156  rData.pFont = pFont;
157  rData.dwClr = dwClr;
158  rData.fNewParagraph = fNewPar;
159  // new message successfully added; count it
160  ++iLineCount;
161  if (++iNextLineDataPos == iMaxLineCount)
162  {
163  if (fDynamicGrow)
164  GrowLineCountBuffer(4 + iMaxLineCount/2);
165  else
166  iNextLineDataPos = 0;
167  }
168 }
169 
170 void C4LogBuffer::AppendLines(const char *szLine, CStdFont *pFont, DWORD dwClr, CStdFont *pFirstLineFont)
171 {
172  char LineBreakChars [] = { 0x0D, 0x0A, '|' };
173  int32_t iLineBreakCharCount = 2 + fMarkup;
174  // safety
175  if (!szLine) return;
176  // split '|'/CR/LF-separations first, if there are any
177  bool fAnyLineBreakChar = false;
178  for (int i = 0; i < iLineBreakCharCount; ++i)
179  if (strchr(szLine, LineBreakChars[i]))
180  {
181  fAnyLineBreakChar = true;
182  break;
183  }
184  if (fAnyLineBreakChar)
185  {
186  char *szBuf = new char[strlen(szLine)+1];
187  char *szBufPos, *szPos2 = szBuf, *szBufFind;
188  strcpy(szBuf, szLine);
189  while ((szBufPos = szPos2))
190  {
191  // find first occurance of any line break char
192  szPos2 = nullptr;
193  for (int i = 0; i < iLineBreakCharCount; ++i)
194  if ((szBufFind = strchr(szBufPos, LineBreakChars[i])))
195  if (!szPos2 || szBufFind < szPos2)
196  szPos2 = szBufFind;
197  // split string at linebreak char
198  if (szPos2) *szPos2++ = '\0';
199  // output current line if not empty
200  if (!*szBufPos) continue;
201  // first line in caption font
202  if (pFirstLineFont)
203  {
204  AppendLines(szBufPos, pFirstLineFont, dwClr);
205  pFirstLineFont = nullptr;
206  }
207  else
208  AppendLines(szBufPos, pFont, dwClr);
209  }
210  delete [] szBuf;
211  return;
212  }
213  // no line breaks desired: Output all in one line
214  if (!iLineBreakWidth || !pFont)
215  {
216  AppendSingleLine(szLine, strlen(szLine), nullptr, pFont, dwClr, true);
217  }
218  else
219  {
220  C4Markup markup(false);
221  const char *markupPos = szLine;
222  std::string rline;
223  // output broken lines until there are any
224  int iLineIndex = 0;
225  while (*szLine)
226  {
227  // get line width of this line
228  int iBreakWdt = iLineBreakWidth;
229  if (iLineIndex && szIndent)
230  {
231  int32_t iIndentWdt, Q;
232  pFont->GetTextExtent(szIndent, iIndentWdt, Q, true);
233  iBreakWdt -= iIndentWdt;
234  }
235  // get number of characters printable into this line
236  const char *szNextLine;
237  int iNumChars = pFont->GetMessageBreak(szLine, &szNextLine, iBreakWdt);
238  // make sure not to break markup
239  if (fMarkup)
240  {
241  std::string opening = markup.OpeningTags();
242  while (markupPos < szNextLine)
243  {
244  if (*markupPos == '<')
245  {
246  if (markup.Read(&markupPos))
247  {
248  if (markupPos > szNextLine)
249  {
250  // The message break is within a tag.
251  iNumChars += markupPos - szNextLine + 1;
252  szNextLine = markupPos;
253  }
254  // Read already moved us over a valid tag.
255  continue;
256  }
257  }
258  markupPos++;
259  }
260  std::string closing = markup.ClosingTags();
261  if (!opening.empty() || !closing.empty())
262  {
263  rline = std::move(opening);
264  rline.append(szLine, iNumChars);
265  rline.append(closing);
266  szLine = rline.c_str();
267  iNumChars = rline.size();
268  }
269  }
270  // add them
271  AppendSingleLine(szLine, iNumChars, iLineIndex ? szIndent : nullptr, pFont, dwClr, !iLineIndex);
272  // next line
273  szLine = szNextLine;
274  ++iLineIndex;
275  rline.clear();
276  }
277  }
278 }
279 
280 const char *C4LogBuffer::GetLine(int iLineIndex, CStdFont **ppFont, DWORD *pdwClr, bool *pfNewPar) const
281 {
282  // evaluate negative indices
283  if (iLineIndex < 0)
284  {
285  iLineIndex += iLineCount;
286  if (iLineIndex < 0) return nullptr;
287  }
288  // range check
289  if (iLineIndex >= iLineCount) return nullptr;
290  // assign data
291  LineData &rData = pLineDataBuf[(iLineDataPos + iLineIndex) % iMaxLineCount];
292  if (ppFont) *ppFont = rData.pFont;
293  if (pdwClr) *pdwClr = rData.dwClr;
294  if (pfNewPar) *pfNewPar = rData.fNewParagraph;
295  // advance in lines until desired line is found
296  char *szResult = szBuf + iFirstLinePos;
297  while (iLineIndex--)
298  {
299  // skip this line
300  while (*szResult++) ;
301  // double delimeter or end of buffer: reset searching to front of buffer
302  if (szResult == (szBuf+iBufSize) || !*szResult) szResult = szBuf;
303  }
304  // return found buffer pos
305  return szResult;
306 }
307 
309 {
310  // clear buffer usage
311  iFirstLinePos = iAfterLastLinePos = iLineCount = iNextLineDataPos = iLineDataPos = 0;
312 }
313 
314 void C4LogBuffer::SetLBWidth(int iToWidth)
315 {
316  iLineBreakWidth = iToWidth;
317 }
uint32_t DWORD
bool Inside(T ival, U lbound, V rbound)
Definition: Standard.h:43
int iSize
Definition: TstC4NetIO.cpp:32
void Clear()
Definition: C4LogBuf.cpp:308
C4LogBuffer(int iSize, int iMaxLines, int iLBWidth, const char *szIndentChars=" ", bool fDynamicGrow=false, bool fMarkup=true)
Definition: C4LogBuf.cpp:23
const char * GetLine(int iLineIndex, CStdFont **ppFont, DWORD *pdwClr, bool *pNewParagraph) const
Definition: C4LogBuf.cpp:280
void SetLBWidth(int iToWidth)
Definition: C4LogBuf.cpp:314
void AppendLines(const char *szLine, CStdFont *pFont, DWORD dwClr, CStdFont *pFirstLineFont=nullptr)
Definition: C4LogBuf.cpp:170
std::string OpeningTags() const
Definition: C4Markup.cpp:140
std::string ClosingTags() const
Definition: C4Markup.cpp:132
bool Read(const char **ppText, bool fSkip=false)
Definition: C4Markup.cpp:52
bool GetTextExtent(const char *szText, int32_t &rsx, int32_t &rsy, bool fCheckMarkup=true)
int GetMessageBreak(const char *szMsg, const char **ppNewPos, int iBreakWidth, float fZoom=1.0f)