/// <summary> /// Breaks the specified text into lines. Only the sub-string will be used.<br/> /// White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered.<br/> /// Words longer than the max width are slit at nearest character (i.e. no hyphenation). /// </summary> public static int TextBreakLines(this Nvg nvg, string @string, string end, float breakRowWidth, out TextRow[] rows, int maxRows) { rows = new TextRow[maxRows]; Fontstash fons = nvg.fontManager.Fontstash; State state = nvg.stateStack.CurrentState; float scale = nvg.fontManager.GetFontScale() * nvg.pixelRatio.DevicePxRatio; float invscale = 1.0f / scale; FonsQuad q = new(); int nrows = 0; float rowStartX = 0.0f; float rowWidth = 0.0f; float rowMinX = 0.0f; float rowMaxX = 0.0f; string rowStart = null; string rowEnd = null; string wordStart = null; float wordStartX = 0.0f; float wordMinX = 0.0f; string breakEnd = null; float breakWidth = 0.0f; float breakMaxX = 0.0f; CodepointType type, pType = CodepointType.Space; uint pCodepoint = 0; if (maxRows == 0) { return(0); } if (state.FontId == Fontstash.INVALID) { return(0); } if (@string == end || @string.Length == 0) { return(0); } fons.SetSize(state.FontSize * scale); fons.SetSpacing(state.LetterSpacing * scale); fons.SetBlur(state.FontBlur * scale); fons.SetAlign((int)state.TextAlign); fons.SetFont(state.FontId); breakRowWidth *= scale; fons.TextIterInit(out FonsTextIter iter, 0, 0, @string, end, FonsGlyphBitmap.Optional); FonsTextIter prevIter = iter; while (fons.TextIterNext(ref iter, ref q)) { if (iter.prevGlyphIndex < 0 && nvg.fontManager.AllocTextAtlas()) { iter = prevIter; fons.TextIterNext(ref iter, ref q); } prevIter = iter; switch (iter.codepoint) { case 9: // \t case 11: // \v case 12: // \f case 32: // \space case 0x00a0: // NBSP type = CodepointType.Space; break; case 10: // \n type = pCodepoint == 13 ? CodepointType.Space : CodepointType.Newline; break; case 13: // \r type = pCodepoint == 10 ? CodepointType.Space : CodepointType.Newline; break; case 0x0085: // NEL type = CodepointType.Newline; break; default: if ((iter.codepoint >= 0x4E00 && iter.codepoint <= 0x9FFF) || (iter.codepoint >= 0x3000 && iter.codepoint <= 0x30FF) || (iter.codepoint >= 0xFF00 && iter.codepoint <= 0xFFEF) || (iter.codepoint >= 0x1100 && iter.codepoint <= 0x11FF) || (iter.codepoint >= 0x3130 && iter.codepoint <= 0x318F) || (iter.codepoint >= 0xAC00 && iter.codepoint <= 0xD7AF)) { type = CodepointType.CJKChar; } else { type = CodepointType.Char; } break; } if (type == CodepointType.Newline) { rows[nrows++] = new TextRow() { Start = rowStart ?? iter.str, End = rowEnd ?? iter.str, Width = rowWidth * invscale, MinX = rowMinX * invscale, MaxX = rowMaxX * invscale, Next = iter.next }; if (nrows >= maxRows) { return(nrows); } breakEnd = rowStart; breakWidth = 0.0f; breakMaxX = 0.0f; rowStart = null; rowEnd = null; rowWidth = 0.0f; rowMinX = rowMaxX = 0.0f; } else { if (rowStart == null) { if (type == CodepointType.Char || type == CodepointType.CJKChar) { rowStartX = iter.x; rowStart = iter.str; rowEnd = iter.next; rowWidth = iter.nextx - rowStartX; rowMinX = q.x0 - rowStartX; rowMaxX = q.x1 - rowStartX; wordStart = iter.str; wordStartX = iter.x; wordMinX = q.x0 - rowStartX; breakEnd = rowStart; breakWidth = 0.0f; breakMaxX = 0.0f; } } else { float nextWidth = iter.nextx - rowStartX; if (type == CodepointType.Char || type == CodepointType.CJKChar) { rowEnd = iter.next; rowWidth = iter.nextx - rowStartX; rowMaxX = q.x1 - rowStartX; } if (((pType == CodepointType.Char || pType == CodepointType.CJKChar) && type == CodepointType.Space) || type == CodepointType.CJKChar) { breakEnd = iter.str; breakWidth = rowWidth; breakMaxX = rowMaxX; } if ((pType == CodepointType.Space && (type == CodepointType.Char || type == CodepointType.CJKChar)) || type == CodepointType.CJKChar) { wordStart = iter.str; wordStartX = iter.x; wordMinX = q.x0; } if ((type == CodepointType.Char || type == CodepointType.CJKChar) && nextWidth > breakRowWidth) { if (breakEnd == rowStart) { rows[nrows++] = new TextRow() { Start = rowStart, End = iter.str, Width = rowWidth * invscale, MinX = rowMinX * invscale, MaxX = rowMaxX * invscale, Next = iter.str }; if (nrows >= maxRows) { return(nrows); } rowStartX = iter.x; rowStart = iter.str; rowEnd = iter.next; rowWidth = iter.nextx - rowStartX; rowMinX = q.x0 - rowStartX; rowMaxX = q.x1 - rowStartX; wordStart = iter.str; wordStartX = iter.x; wordMinX = q.x0 - rowStartX; } else { rows[nrows++] = new TextRow() { Start = rowStart, End = breakEnd, Width = breakWidth * invscale, MinX = rowMinX * invscale, MaxX = breakMaxX * invscale, Next = wordStart }; if (nrows >= maxRows) { return(nrows); } rowStartX = wordStartX; rowStart = wordStart; rowEnd = iter.next; rowWidth = iter.nextx - rowStartX; rowMinX = wordMinX - rowStartX; rowMaxX = q.x1 - rowStartX; } breakEnd = rowStart; breakWidth = 0.0f; breakMaxX = 0.0f; } } } pCodepoint = iter.codepoint; pType = type; } if (rowStart != null) { rows[nrows++] = new TextRow() { Start = rowStart, End = rowEnd, Width = rowWidth * invscale, MinX = rowMinX * invscale, MaxX = rowMaxX * invscale, Next = end }; } return(nrows); }
private static string ProcessStyleSheet(string codepointFile, string injectNamespace, string injectType, CodepointType codepoint) { var characters = new StringBuilder(); switch (codepoint) { case CodepointType.codepoints: ParseCodepoints(codepointFile, characters); break; case CodepointType.css: default: ParseCssCodepoints(codepointFile, characters); break; } // create the source file return(Template .Replace("{{inject-namespace}}", injectNamespace) .Replace("{{inject-type}}", injectType) .Replace("{{inject-characters}}", characters.ToString())); }