renderer.inc 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743
  1. // We have reduced the renderer to just 2 phases: specifiers and visuals, where
  2. // the second phase does both colours and new lines in parallel. As a result,
  3. // the positions update code is vastly simplified because we only need to track
  4. // one phase not more - there is no need to update the first phase while it is
  5. // running.
  6. #if !defined Y_TEXT_MAX_COLOUR_EMBEDS
  7. #if defined Y_TEXT_MAX_COLOR_EMBEDS
  8. #define Y_TEXT_MAX_COLOUR_EMBEDS (Y_TEXT_MAX_COLOR_EMBEDS)
  9. #else
  10. #define Y_TEXT_MAX_COLOUR_EMBEDS (128)
  11. #endif
  12. #endif
  13. enum e_TEXT_RENDER_COLOUR
  14. {
  15. e_TEXT_RENDER_COLOUR_END = 0x01000000, // {/}
  16. e_TEXT_RENDER_COLOUR_FADE = 0x00010000, // {>XXXXXX}
  17. e_TEXT_RENDER_COLOUR_STAR = 0x00000100, // {*}
  18. e_TEXT_RENDER_COLOUR_LINE = 0x00000001, // \n, \r, \r\n
  19. e_TEXT_RENDER_COLOUR_MASK = 0xFEFEFEFE,
  20. e_TEXT_RENDER_COLOUR_FLAG = 0x01010101
  21. }
  22. // This function takes a list of positions in a string, and the length and
  23. // position of a new string inserted in to the original string, and returns a
  24. // list of positions adjusted for after that insertion. The input is actually a
  25. // list of relative jumps, and the output is a progressively updated list of
  26. // absolute locations. The input and output also includes additional data, but
  27. // this data is not in a 2D array but all concatenated.
  28. _Y_TEXT_STATIC stock TextR_UpdateRelToAbs(rel[], abs[], &idx, addition, insertPos)
  29. {
  30. // rel = 5 5 7 8 0
  31. // old = 0 5 10 17 25
  32. // idx = 0
  33. // add = 11
  34. // isp = 7
  35. // idx = 2
  36. // add = 3
  37. // isp = 30
  38. // idx = 4
  39. // add = 3
  40. // isp = 32
  41. // abs = 0 5 21 28 42
  42. new
  43. pos = abs[idx];
  44. while (pos <= insertPos)
  45. {
  46. if (!rel[idx]) return;
  47. pos += rel[idx++],
  48. abs[idx] = rel[idx], // First slot is data.
  49. abs[++idx] = pos; // Second slot is insertion point.
  50. }
  51. abs[idx] += addition;
  52. }
  53. _Y_TEXT_STATIC stock TextR_CompleteRelToAbs(rel[], abs[], &idx)
  54. {
  55. // Afterwards (to complete any stragglers). The function above only updates
  56. // the list and converts relative jumps to absolute ones as long as there is
  57. // processing left to do on specifiers. If we run out of specifiers before
  58. // the relative jumps have been converted, then we need to finish them off
  59. // explicitly.
  60. new
  61. pos = abs[idx];
  62. while (rel[idx])
  63. {
  64. pos += rel[idx++],
  65. abs[idx] = rel[idx],
  66. abs[++idx] = pos;
  67. }
  68. abs[0] = idx; // Save the length just in case.
  69. }
  70. _Y_TEXT_STATIC stock TextR_Render(string:str[], colourList[])
  71. {
  72. static
  73. colourLocations[Y_TEXT_MAX_COLOUR_EMBEDS];
  74. new
  75. colourIdx;
  76. ////////////////////////////////////////////////////////////////////////////
  77. ////////////////////////////////////////////////////////////////////////////
  78. //// ////
  79. //// PHASE 1 - SPECIFIERS ////
  80. //// ////
  81. ////////////////////////////////////////////////////////////////////////////
  82. ////////////////////////////////////////////////////////////////////////////
  83. // There is an embedded linked list of locations of specifiers.
  84. for (new off = str[0], pos, ins; off; )
  85. {
  86. // Jump to the next location.
  87. pos = pos + off + ins;
  88. // Get the next specifier relative offset.
  89. off = str[pos];
  90. // Do this specifier. Returns number of bytes the string GREW by, that
  91. // may not be the number of characters written, nor may it be positive.
  92. ins = TextR_DoSpecifier(str[pos]);
  93. // Update the other linked lists to account for this new data.
  94. TextR_UpdateRelToAbs(colourList, colourLocations, colourIdx, ins, pos);
  95. }
  96. // Copy the remaining relative locations over.
  97. TextR_CompleteRelToAbs(colourList, colourLocations, colourIdx);
  98. ////////////////////////////////////////////////////////////////////////////
  99. ////////////////////////////////////////////////////////////////////////////
  100. //// ////
  101. //// PHASE 2 - COLORS ////
  102. //// ////
  103. ////////////////////////////////////////////////////////////////////////////
  104. ////////////////////////////////////////////////////////////////////////////
  105. // Note that "colours" is spelt "colors" above PURELY for symmetry.
  106. new
  107. offset = 0;
  108. // Loop over the linked list of absolute colour locations.
  109. for (new i = 1; i < colourIdx; i += 2)
  110. {
  111. // Get the colour to be displayed, and where it is to be displayed.
  112. // new
  113. // col = colourLocations[i++],
  114. // pos = colourLocations[i++] + offset;
  115. // As we loop through the string, our offsets get bigger and bigger
  116. offset += TextR_DoColour(str, offset, colourLocations, i, colourIdx);
  117. }
  118. }
  119. /**
  120. Get all information about the extent of this fade.
  121. **/
  122. _Y_TEXT_STATIC stock TextR_GetFadeData(colourLocations[], start, &endColour, &endPos, &newLines)
  123. {
  124. newLines = 0;
  125. for ( ; ; )
  126. {
  127. // These lines are not mutually exclusive.
  128. if (colourLocations[start - 1] & _:e_TEXT_RENDER_COLOUR_LINE) ++newLines;
  129. // Don't check for an end condition, just don't fail!
  130. start += 2;
  131. // New lines at the END of a fade don't count as being IN the fade, so
  132. // we can do the increment in the middle of the loop.
  133. if (colourLocations[start - 1] & ~_:e_TEXT_RENDER_COLOUR_LINE)
  134. {
  135. endColour = colourLocations[start - 1],
  136. endPos = colourLocations[start];
  137. return;
  138. }
  139. }
  140. }
  141. _Y_TEXT_STATIC stock TextR_DoSpecifier(string:str[])
  142. {
  143. // Resolve what the specifier is, including all its parameters (which may be
  144. // very complex, indeed, variable length).
  145. new
  146. len = 0,
  147. headerLen = 2,
  148. header = str[1];
  149. // Look at the header and get more information...
  150. // Return the number of characters inserted, minus the length of the header.
  151. return len - headerLen;
  152. }
  153. // Relatives.
  154. // Colours are stored in a very unique manner (at least I've never seen it
  155. // before). Most people can't tell the difference visually between, say,
  156. // 0x286A44FF and 0x286A45FF (it's hard enough to read the difference). There
  157. // is just one bit different, and that is the LSB of the Blue component, meaning
  158. // that it makes a trivial contribution to the colour displayed. If we use
  159. // these LSBs for something else, then it doesn't matter if they are set or not
  160. // for the display of the colour, and we can instead get 4 bit flags embedded
  161. // within the colour.
  162. _Y_TEXT_STATIC stock TextR_InsertColour(str[], len, pos, &oldColour, newColour, &bool:pending) <y_text_colour : y_text_colour_gt>
  163. {
  164. new
  165. oldType = oldColour & 0xFF,
  166. oldLite = oldColour >>> 8,
  167. nc = Colours_SAMPToGT(newColour >>> 8, (oldType == 0 || oldType == 'x') ? (oldLite) : (3)),
  168. newType = nc & 0xFF,
  169. newLite = nc >>> 8;
  170. static const
  171. cs_hTags[] = "~x~~h~~h~~h~~h~~h~%s";
  172. if (newType == oldType || newType == 'x')
  173. {
  174. // We don't need to output the type when the colour is 'x'.
  175. oldLite = (newLite - oldLite) * 3;
  176. if (oldLite == 0) return 0; // The two are identical.
  177. else if (oldLite > 0)
  178. {
  179. // Only need add some more "~h~"s.
  180. return
  181. format(str[pos], len - pos, cs_hTags[sizeof (cs_hTags) - 3 - oldLite], str[pos]),
  182. oldColour = nc,
  183. oldLite;
  184. }
  185. }
  186. // Need a whole new colour outputting.
  187. return
  188. newLite *= 3,
  189. format(str[pos], len - pos, cs_hTags[sizeof (cs_hTags) - 6 - newLite], str[pos]),
  190. str[pos + 1] = newType,
  191. oldColour = nc,
  192. 3 + newLite;
  193. }
  194. /**
  195. <summary>TextR_InsertColour</summary>
  196. <param name="str">The string we are inserting a colour in to.</param>
  197. <param name="len">The total length of this string.</param>
  198. <param name="pos">The position in the string we want to insert at.</param>
  199. <param name="oldColour">The previous colour for comparison.</param>
  200. <param name="newColour">The new colour to insert.</param>
  201. <param name="pending">Don't write if this colour is followed by a space.</param>
  202. <remarks>
  203. The format "oldColour" is not constant between different state versions
  204. of this function, it is determined purely by what display mode is in use.
  205. On the other hand, "newColour" is always "RRGGBBAA" with component LSBs used
  206. for formatting flags.
  207. </remarks>
  208. **/
  209. _Y_TEXT_STATIC stock TextR_InsertColour(str[], len, pos, &oldColour, newColour, &bool:pending) <y_text_colour : y_text_colour_scm>
  210. {
  211. // Insert a colour of the form "{RRGGBB}".
  212. newColour &= (_:e_TEXT_RENDER_COLOUR_MASK & 0xFFFFFF00); // SCM ignores alpha.
  213. P:4("TextR_InsertColour: \"%s\", %i, %i, %04x%04x, %04x%04x, %d", str, len, pos, oldColour >>> 16, oldColour & 0xFFFF, newColour >> 16, newColour & 0xFFFF, pending);
  214. if (oldColour != newColour) // Don't insert at the end.
  215. {
  216. P:7("TextR_InsertColour: STATE 1");
  217. // Don't write any colour out if this colour is followed by a space,
  218. // instead just mark it as pending.
  219. if (str[pos] <= ' ')
  220. {
  221. P:7("TextR_InsertColour: STATE 2");
  222. return
  223. pending = true,
  224. 0;
  225. // Don't set the new colour as the old colour here, to handle this:
  226. //
  227. // {FFAA00}Hi{556677} {FFAA00}There
  228. //
  229. // Without saving the pending old colour, we will just output:
  230. //
  231. // {FFAA00}Hi There
  232. //
  233. // Even less if this is the start of the output; instead we will
  234. // just use the colour in SendClientMessage:
  235. //
  236. // Hi There
  237. //
  238. }
  239. P:7("TextR_InsertColour: STATE 3");
  240. // The colour has changed, we may be able to do this to a greater degree
  241. // by relaxing the conditions on when the two colours are considered the
  242. // same, i.e. when they are within a certain range of each other. I
  243. // might make that determined by the 2 LSBs.
  244. return
  245. pending = false,
  246. format(str[pos], len - pos, "{%06x}%s", newColour >>> 8, str[pos]),
  247. oldColour = newColour,
  248. // Return the inserted length.
  249. 8;
  250. }
  251. P:7("TextR_InsertColour: STATE 4");
  252. return
  253. pending = false,
  254. 0;
  255. }
  256. _Y_TEXT_STATIC stock TextR_DoStraightFade(str[], len, &oldColour, startPos, startColour, fadeLen, endColour, &bool:pending)
  257. {
  258. // This function does fades between two points when there are no newlines
  259. // between the fade points.
  260. new
  261. // The individual components of the start colour.
  262. sr = startColour >>> 24,
  263. sg = (startColour >>> 16) & 0xFF,
  264. sb = (startColour >>> 8) & 0xFF,
  265. // The differences between the individual components.
  266. dr = ( endColour >>> 24) - sr,
  267. dg = ((endColour >>> 16) & 0xFF) - sg,
  268. db = ((endColour >>> 8) & 0xFF) - sb;
  269. // Fake variables to save memory, while keeping names.
  270. #define inserted endColour
  271. #define taglen startColour
  272. inserted = 0; // Now amount of data inserted.
  273. // Loop over all the points between the ends.
  274. for (new step = 0; step != fadeLen; ++step)
  275. {
  276. // Don't colour spaces, there is exactly zero point! In this case we
  277. // DON'T copy over the new colour as the existing colour. We also know
  278. // that the very next character will also be coloured, so we don't need
  279. // to worry about the wrong colour being used in the future.
  280. // Use the existing insertion function to do the hard work.
  281. taglen = TextR_InsertColour(str, len, startPos, oldColour,
  282. // These components look like they should be optimisable, but they
  283. // aren't, because we are doing integer divisions so "a * b / c" is
  284. // slightly different to "a * (b / c)", ergo we can't precompute
  285. // parts like "dr / fadeLen".
  286. (((dr * step / fadeLen + sr) & 0xFF) << 24) |
  287. (((dg * step / fadeLen + sg) & 0xFF) << 16) |
  288. (((db * step / fadeLen + sb) & 0xFF) << 8 ), pending),
  289. inserted += taglen,
  290. // Increment to the next insertion point.
  291. startPos += taglen + 1;
  292. }
  293. return inserted;
  294. #undef inserted
  295. #undef taglen
  296. }
  297. _Y_TEXT_STATIC stock TextR_DoNLFade(str[], len, &oldColour, startPos, startColour, fadeLen, endColour, colourLocations[], &idx, &bool:pending, offset)
  298. {
  299. // This function does fades between two points when there may be newlines
  300. // between the fade points. First, see if the first item is ALSO a new
  301. // line.
  302. if (!(colourLocations[idx - 1] & _:e_TEXT_RENDER_COLOUR_LINE)) idx += 2;
  303. // Now do all the normal code.
  304. new
  305. // The individual components of the start colour.
  306. sr = startColour >>> 24,
  307. sg = (startColour >>> 16) & 0xFF,
  308. sb = (startColour >>> 8) & 0xFF,
  309. // The differences between the start and end colour components.
  310. dr = ( endColour >>> 24) - sr,
  311. dg = ((endColour >>> 16) & 0xFF) - sg,
  312. db = ((endColour >>> 8) & 0xFF) - sb;
  313. // Fake variables to save memory, while keeping names.
  314. #define inserted endColour
  315. #define taglen startColour
  316. inserted = offset;
  317. // Loop over all the points between the ends.
  318. for (new step = 0, colour; step != fadeLen; ++step)
  319. {
  320. // These components look like they should be optimisable, but they
  321. // aren't, because we are doing integer divisions so "a * b / c" is
  322. // slightly different to "a * (b / c)", ergo we can't pre-compute parts
  323. // like "dr / fadeLen".
  324. colour =
  325. (((dr * step / fadeLen + sr) & 0xFF) << 24) |
  326. (((dg * step / fadeLen + sg) & 0xFF) << 16) |
  327. (((db * step / fadeLen + sb) & 0xFF) << 8 ) ;
  328. P:7("TextR_DoNLFade: step %d = %06x", step, colour >>> 8);
  329. P:7("TextR_DoNLFade: insert %d, %d, %d", startPos, colourLocations[idx], inserted);
  330. // Check for new lines, and update their locations accordingly.
  331. if (startPos == colourLocations[idx] + inserted)
  332. {
  333. // We don't need to check for the end colour because we don't loop
  334. // that far. This is only triggered by new lines. Note that it
  335. // would be triggered by the start point as well, but we make
  336. // explicit checks for that before the loop because that is the only
  337. // one that needs the checks so we can save time there.
  338. //colourLocations[idx] += inserted,
  339. oldColour = colour & (_:e_TEXT_RENDER_COLOUR_MASK & 0xFFFFFF00),
  340. // We have found a new line - save what colour should appear at this
  341. // point, but don't write it out to the string.
  342. colourLocations[idx - 1] = (colour & _:e_TEXT_RENDER_COLOUR_MASK) | _:e_TEXT_RENDER_COLOUR_LINE,
  343. idx += 2,
  344. pending = false,
  345. ++startPos;
  346. }
  347. else
  348. {
  349. // Use the existing insertion function to do the hard work.
  350. P:7("TextR_DoNLFade: %s %d %d %d %d %d", str, len, startPos, oldColour, colour, pending);
  351. taglen = TextR_InsertColour(str, len, startPos, oldColour, colour, pending);
  352. P:7("TextR_DoNLFade: done");
  353. inserted += taglen,
  354. // Increment to the next insertion point.
  355. startPos += taglen + 1;
  356. }
  357. }
  358. return
  359. idx -= 2,
  360. inserted - offset;
  361. #undef inserted
  362. #undef taglen
  363. }
  364. _Y_TEXT_STATIC stock TextR_ResolvePending(str[], len, pos, colourLocations[], idx, colour, endPos, &curColour)
  365. {
  366. endPos += colourLocations[idx];
  367. while (pos < endPos && str[pos])
  368. {
  369. if (str[pos] <= ' ') ++pos;
  370. else
  371. {
  372. return
  373. // If we have a "pending" colour, then we know that it is
  374. // different to the last colour, because that is how pendings
  375. // are triggered.
  376. TextR_InsertColour(str, len, pos, curColour, colour, bool:idx);
  377. // We know the colour is different, and we know we aren't
  378. // somewhere that will trigger a pending hit, so there's no
  379. // point saving all that data anywhere but here. As a result,
  380. // we re-use "idx" and "endPos" because they aren't needed any
  381. // more.
  382. }
  383. }
  384. // Reached the next colour item - discard this current one. We aren't in a
  385. // fade so any new lines already have a colour assigned.
  386. return 0;
  387. }
  388. _Y_TEXT_STATIC stock TextR_GetSlotColour(slot, originalColour, currentColour)
  389. {
  390. if (slot & _:e_TEXT_RENDER_COLOUR_END) return originalColour;
  391. else if (slot & _:e_TEXT_RENDER_COLOUR_STAR) return 0xFEFEFE00; // TODO:.
  392. // "&=", not "&" - not a typo!
  393. else if ((slot &= _:e_TEXT_RENDER_COLOUR_MASK)) return slot;
  394. return currentColour;
  395. }
  396. _Y_TEXT_STATIC stock TextR_DoOneColour(str[], len, offset, colourLocations[], &idx, &curColour, initialColour)
  397. {
  398. new
  399. pos = colourLocations[idx],
  400. flags = colourLocations[idx - 1],
  401. colour = TextR_GetSlotColour(flags, initialColour, curColour);
  402. if (flags & _:e_TEXT_RENDER_COLOUR_FADE)
  403. {
  404. new
  405. endColour,
  406. fadeLen,
  407. newLines;
  408. TextR_GetFadeData(colourLocations, idx, endColour, fadeLen, newLines),
  409. fadeLen -= pos,
  410. pos += offset;
  411. // Don't need to check and return "pending" statuses here. The next
  412. // character will be another colour always (the fade end).
  413. if (newLines)
  414. {
  415. return TextR_DoNLFade(str, len, curColour, pos, colour, fadeLen,
  416. TextR_GetSlotColour(endColour, initialColour, curColour),
  417. colourLocations, idx, bool:newLines /* discardedPending */, offset);
  418. }
  419. else
  420. return TextR_DoStraightFade(str, len, curColour, pos, colour, fadeLen,
  421. TextR_GetSlotColour(endColour, initialColour, curColour),
  422. bool:newLines /* discardedPending */);
  423. }
  424. else if (flags & _:e_TEXT_RENDER_COLOUR_LINE)
  425. {
  426. // Don't do any output here.
  427. return
  428. curColour = colour, // TODO: Better Game Text aware copying.
  429. colourLocations[idx - 1] = colour | _:e_TEXT_RENDER_COLOUR_LINE,
  430. 0;
  431. }
  432. else
  433. {
  434. return
  435. pos += offset,
  436. initialColour = TextR_InsertColour(str, len, pos, curColour, colour, bool:flags /* pending */),
  437. flags ?
  438. TextR_ResolvePending(str, len, pos + 1, colourLocations, idx + 2, colour, offset, curColour) :
  439. initialColour;
  440. }
  441. }
  442. _Y_TEXT_STATIC stock TextR_DoOutput(colour, str[], start, end) <y_text_output : y_text_output_scm>
  443. {
  444. new
  445. ch = str[end];
  446. return
  447. str[end] = '\0',
  448. str[end] = ch;
  449. }
  450. _Y_TEXT_STATIC stock bool:TextR_GetColour(const str[], &colour)
  451. {
  452. static const
  453. scIsHex['F' + 1] =
  454. {
  455. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  456. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  457. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  458. 0x80000000 | 0, 0x80000000 | 1, 0x80000000 | 2,
  459. 0x80000000 | 3, 0x80000000 | 4, 0x80000000 | 5,
  460. 0x80000000 | 6, 0x80000000 | 7, 0x80000000 | 8,
  461. 0x80000000 | 9, 0, 0, 0, 0, 0, 0, 0,
  462. 0x80000000 | 10, 0x80000000 | 11, 0x80000000 | 12,
  463. 0x80000000 | 13, 0x80000000 | 14, 0x80000000 | 15
  464. };
  465. new
  466. c,
  467. ch;
  468. if (str[0] == '{')
  469. {
  470. if (IS_IN_RANGE((ch = str[1]), '0', 'F' + 1) && (ch = scIsHex[ch])) c = ch << 20;
  471. else return false;
  472. if (IS_IN_RANGE((ch = str[2]), '0', 'F' + 1) && (ch = scIsHex[ch])) c |= ch << 16;
  473. else return false;
  474. if (IS_IN_RANGE((ch = str[3]), '0', 'F' + 1) && (ch = scIsHex[ch])) c |= ch << 12;
  475. else return false;
  476. if (IS_IN_RANGE((ch = str[4]), '0', 'F' + 1) && (ch = scIsHex[ch])) c |= ch << 8;
  477. else return false;
  478. if (IS_IN_RANGE((ch = str[5]), '0', 'F' + 1) && (ch = scIsHex[ch])) c |= ch << 4;
  479. else return false;
  480. if (IS_IN_RANGE((ch = str[6]), '0', 'F' + 1) && (ch = scIsHex[ch]))
  481. {
  482. return
  483. colour = c | (ch & 0x0F),
  484. (str[7] == '}');
  485. }
  486. }
  487. return false;
  488. }
  489. _Y_TEXT_STATIC stock TextR_GetLastColour(str[], pos, colourLocations[], idx, prevColour)
  490. {
  491. while (colourLocations[idx] < pos)
  492. {
  493. prevColour = colourLocations[idx - 1],
  494. idx += 2;
  495. }
  496. if (colourLocations[idx] == pos) return colourLocations[idx - 1];
  497. else if (prevColour & _:e_TEXT_RENDER_COLOUR_FADE)
  498. {
  499. pos -= 8;
  500. while (!TextR_GetColour(str[pos], prevColour) && pos) --pos;
  501. return prevColour << 8;
  502. }
  503. else return prevColour;
  504. }
  505. _Y_TEXT_STATIC stock TextR_OutputLine(str[], lastNL, nextNL, colourLocations[], idx, lineColour)
  506. {
  507. for (new start, end, ch; nextNL - lastNL > 144; )
  508. {
  509. ch = TextR_GetSplitPoint(str, lastNL + 144, end, start),
  510. TextR_DoOutput(lineColour, str, lastNL, end),
  511. lastNL = start,
  512. lineColour = TextR_GetLastColour(str, start, colourLocations, idx, lineColour);
  513. if (ch) str[end - 1] = ch;
  514. }
  515. return
  516. TextR_DoOutput(lineColour, str, lastNL, nextNL),
  517. nextNL;
  518. }
  519. _Y_TEXT_STATIC stock TextR_RunPhase3(str[], len, colourLocations[]) //, intialColour)
  520. {
  521. new
  522. colourLine = colourLocations[0],
  523. colourOrig = colourLine,
  524. colourCur = colourLine,
  525. tmp,
  526. offset,
  527. lastNL = 0,
  528. li = 2,
  529. idx = 2;
  530. while (colourLocations[idx] != 65536)
  531. {
  532. // Do colour rendering.
  533. tmp = TextR_DoOneColour(str, len, offset, colourLocations, idx, colourCur, colourOrig),
  534. colourLocations[idx] += offset,
  535. offset += tmp;
  536. // Do newlines.
  537. if ((tmp = colourLocations[idx - 1]) & _:e_TEXT_RENDER_COLOUR_LINE)
  538. {
  539. lastNL = TextR_OutputLine(str, lastNL, colourLocations[idx], colourLocations, li, colourLine);
  540. colourLine = tmp,
  541. li = idx;
  542. }
  543. idx += 2;
  544. }
  545. TextR_OutputLine(str, lastNL, strlen(str), colourLocations, li, colourLine);
  546. }
  547. _Y_TEXT_STATIC stock bool:TextR_IsColour(const str[])
  548. {
  549. static const
  550. scIsHex['F' + 1] =
  551. {
  552. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  553. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  554. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  555. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
  556. 0, 1, 1, 1, 1, 1, 1
  557. };
  558. new
  559. ch;
  560. return
  561. (str[0] == '{') &&
  562. IS_IN_RANGE((ch = str[1]), '0', 'F' + 1) && (scIsHex[ch]) &&
  563. IS_IN_RANGE((ch = str[2]), '0', 'F' + 1) && (scIsHex[ch]) &&
  564. IS_IN_RANGE((ch = str[3]), '0', 'F' + 1) && (scIsHex[ch]) &&
  565. IS_IN_RANGE((ch = str[4]), '0', 'F' + 1) && (scIsHex[ch]) &&
  566. IS_IN_RANGE((ch = str[5]), '0', 'F' + 1) && (scIsHex[ch]) &&
  567. IS_IN_RANGE((ch = str[6]), '0', 'F' + 1) && (scIsHex[ch]) &&
  568. (str[7] == '}');
  569. }
  570. _Y_TEXT_STATIC stock TextR_GetSplitPoint(str[], pos, &end, &start)
  571. {
  572. // Escape any colour we may be in.
  573. pos = TextR_GetColourStart(str, pos);
  574. // Are we at a space already?
  575. start = TextR_LTrim(str, pos),
  576. end = TextR_RTrim(str, pos);
  577. if (start == end)
  578. {
  579. // "pos" is now either right before a colour, or not near one.
  580. new
  581. cf = TextR_CountCharsFore(str, pos),
  582. cb = TextR_CountCharsBack(str, pos); // MODIFIES "pos".
  583. if (cb < 4 || cb + cf < 6) // Word can't be split.
  584. {
  585. start = end = pos;
  586. }
  587. else
  588. {
  589. // Get the point to insert the "-" at.
  590. pos = TextR_SkipChars(str, pos, min(cb - 1, (cb + cf) / 2));
  591. new
  592. ret = str[pos];
  593. return
  594. end = pos + 1,
  595. start = TextR_IsColour(str[pos]) ? (pos + 8) : pos,
  596. str[pos] = '-',
  597. ret;
  598. }
  599. }
  600. // Remove any leading colour, since that will not be required and saves us
  601. // an extra 8 characters from the next line. This may also mean less lines
  602. // need to be output at all.
  603. if (TextR_IsColour(str[start])) start += 8;
  604. return 0;
  605. }
  606. _Y_TEXT_STATIC stock TextR_LTrim(str[], pos)
  607. {
  608. while ('\0' < str[pos] <= ' ') ++pos;
  609. return pos;
  610. }
  611. _Y_TEXT_STATIC stock TextR_RTrim(str[], pos)
  612. {
  613. while (pos--)
  614. {
  615. if (str[pos] > ' ') break;
  616. }
  617. return pos + 1;
  618. }
  619. /**
  620. <summary>TextR_SkipChars</summary>
  621. <param name="str">The string we are moving through.</param>
  622. <param name="pos">The position in the string we want to start at.</param>
  623. <param name="num">The number of characters to skip over.</param>
  624. <returns>
  625. -
  626. </returns>
  627. <remarks>
  628. Skips over a given number of VISIBLE colours, ignores colours.
  629. </remarks>
  630. **/
  631. _Y_TEXT_STATIC stock TextR_SkipChars(str[], pos, num)
  632. {
  633. while (num)
  634. {
  635. if (TextR_IsColour(str[pos])) pos += 8;
  636. else --num, ++pos;
  637. }
  638. return pos;
  639. }
  640. _Y_TEXT_STATIC stock TextR_CountCharsBack(str[], &pos)
  641. {
  642. new
  643. count;
  644. // This is never called directly after a colour.
  645. while (count < 10)
  646. {
  647. --pos;
  648. if (str[pos] <= ' ') return ++pos, count;
  649. else ++count;
  650. if (TextR_IsColour(str[pos - 8]))
  651. {
  652. pos -= 8;
  653. }
  654. }
  655. return 10;
  656. }
  657. _Y_TEXT_STATIC stock TextR_CountCharsFore(str[], pos)
  658. {
  659. new
  660. count;
  661. while (count < 6) // Don't care about any more than 6 characters.
  662. {
  663. if (TextR_IsColour(str[pos])) pos += 8;
  664. else if (str[pos] <= ' ') return count;
  665. else ++pos, ++count;
  666. }
  667. // We only care about up to 3 characters to cover this case:
  668. //
  669. // hel-
  670. // lo
  671. //
  672. // That is an invalid split, there must be at least 3 characters of the word
  673. // remaining on the next line, AND on the previous line, so we can't split
  674. // the word "hello", but can split:
  675. //
  676. // saluta-
  677. // tions
  678. //
  679. // We could improve it slightly, since this may end up with a split like:
  680. //
  681. // salutati-
  682. // ons
  683. //
  684. // Instead of an even split in the middle of the word. We upped the search
  685. // to 6 characters to make the halves possibly more even.
  686. return 6;
  687. }
  688. _Y_TEXT_STATIC stock TextR_GetColourStart(str[], pos)
  689. {
  690. new
  691. tpos = pos - 8;
  692. while (tpos < pos && !TextR_IsColour(str[tpos])) ++tpos;
  693. return tpos;
  694. }