/// <summary>Loads the character array (<see cref="Css.TextRenderingProperty.Characters"/>) from the given text string.</summary> internal void LoadCharacters(string text, RenderableData renderable) { if (Characters != null && Visible) { // Make sure each char goes offscreen: NowOffScreen(); // Clear visible: Visible = false; } // Get the computed style: ComputedStyle computedStyle = renderable.computedStyle; // No longer dirty: Dirty = false; char[] characters = text.ToCharArray(); // Get text transform: int textTransform = computedStyle.ResolveInt(Css.Properties.TextTransform.GlobalProperty); if (textTransform != TextTransformMode.None && characters.Length > 0) { switch (textTransform) { case TextTransformMode.Capitalize: // Uppercase the first character: characters[0] = char.ToUpper(characters[0]); break; case TextTransformMode.Lowercase: // Lowercase the whole string: for (int i = 0; i < characters.Length; i++) { characters[i] = char.ToLower(characters[i]); } break; case TextTransformMode.Uppercase: // Uppercase the whole string: for (int i = 0; i < characters.Length; i++) { characters[i] = char.ToUpper(characters[i]); } break; } } // Get the whitespace mode: int whiteSpaceMode = computedStyle.WhiteSpaceX; // Purge characters that we don't want: if ((whiteSpaceMode & WhiteSpaceMode.NormalOrNoWrap) != 0) { // Dump breaks and repeated whitespace. bool dumpWhiteSpace = false; for (int i = 0; i < characters.Length; i++) { char character = characters[i]; if (character == '\t') { // Dump: characters[i] = '\0'; } else if (character == '\r' || character == '\n') { // Dump: characters[i] = '\0'; // Dump whitespaces immediately after these: dumpWhiteSpace = true; continue; } else if (character == '\u00A0') { // NBSP: characters[i] = ' '; } else if (character == ' ') { if (dumpWhiteSpace) { // Dump: characters[i] = '\0'; } else { // Dump any consecutive whitespace (except for ): dumpWhiteSpace = true; } continue; } dumpWhiteSpace = false; } } else if (whiteSpaceMode == WhiteSpaceMode.PreLine) { // Dump repeated whitespace (and \r) only. bool dumpWhiteSpace = false; for (int i = 0; i < characters.Length; i++) { char character = characters[i]; if (character == ' ') { if (dumpWhiteSpace) { // Dump: characters[i] = '\0'; } else { dumpWhiteSpace = true; } continue; } else if (character == '\u00A0') { // NBSP: characters[i] = ' '; } else if (character == '\r') { characters[i] = '\0'; dumpWhiteSpace = true; continue; } else if (character == '\n') { dumpWhiteSpace = true; continue; } dumpWhiteSpace = false; } } else { // \r and nbsp only. for (int i = 0; i < characters.Length; i++) { char character = characters[i]; if (character == '\r') { characters[i] = '\0'; } else if (character == '\u00A0') { // NBSP: characters[i] = ' '; } } } // Create characters if they're needed: if (Characters == null || Characters.Length != characters.Length) { Characters = new Glyph[characters.Length]; Kerning = null; } // Considered all empty until shown otherwise. AllEmpty = true; // Next, for each character, find its dynamic character. // At the same time we want to find out what dimensions this word has so it can be located correctly. Glyph previous = null; for (int i = 0; i < characters.Length; i++) { char rawChar = characters[i]; if (rawChar == '\0') { // It got dumped. continue; } Glyph character = null; // Is it a unicode high/low surrogate pair? if (char.IsHighSurrogate(rawChar) && i != characters.Length - 1) { // Low surrogate follows: char lowChar = characters[i + 1]; // Get the full charcode: int charcode = char.ConvertToUtf32(rawChar, lowChar); // Grab the surrogate pair char: character = FontToDraw.GetGlyphOrEmoji(charcode); // Make sure there is no char in the low surrogate spot: Characters[i + 1] = null; // Update this character: Characters[i] = character; // Skip the low surrogate: i++; } else if (rawChar == '\n') { // Special case for newlines (They don't show up in host fonts). if (NEWLINE_GLYPH == null) { NEWLINE_GLYPH = new Glyph(); NEWLINE_GLYPH.RawCharcode = (int)'\n'; } Characters[i] = NEWLINE_GLYPH; } else { character = FontToDraw.GetGlyphOrEmoji((int)rawChar); Characters[i] = character; } if (character == null) { continue; } if (previous != null) { // Look for a kern pair: if (character.Kerning != null) { float offset; if (character.Kerning.TryGetValue(previous, out offset)) { // Got a kern! if (Kerning == null) { Kerning = new float[characters.Length]; } Kerning[i] = offset; } } } previous = character; AllEmpty = false; } }
/// <summary>Runs before reflow.</summary> public override void UpdateCss(Renderman renderer) { // Clear the blocks: FirstBox = null; LastBox = null; // Get the text renderer (or create it): Css.TextRenderingProperty text = RequireTextProperty(); // Get computed style: ComputedStyle cs = computedStyle; // Get the first box as it contains the fontface/ size: LayoutBox box = cs.FirstBox; // Colour too: Color fontColour = cs.Resolve(Css.Properties.ColorProperty.GlobalProperty).GetColour(this, Css.Properties.ColorProperty.GlobalProperty); // Colour: text.BaseColour = fontColour; // Font size update: float fontSize = box.FontSize; text.FontSize = fontSize; // Spacing: float wordSpacing = cs.ResolveDecimal(Css.Properties.WordSpacing.GlobalProperty); float letterSpacing = cs.ResolveDecimal(Css.Properties.LetterSpacing.GlobalProperty); // If word spacing is not 'normal', remove 1em from it (Note that letter spacing is always additive): if (wordSpacing == -1f) { wordSpacing = 0f; } else { wordSpacing -= fontSize; } text.WordSpacing = wordSpacing; text.LetterSpacing = letterSpacing; // Decoration: int decoration = cs.ResolveInt(Css.Properties.TextDecorationLine.GlobalProperty); if (decoration == 0) { // Remove a line if we have one: text.TextLine = null; } else { // Got a line! if (text.TextLine == null) { text.TextLine = new TextDecorationInfo(decoration); } // Get the colour: Css.Value lineColour = cs.Resolve(Css.Properties.TextDecorationColor.GlobalProperty); if (lineColour == null || lineColour.IsType(typeof(Css.Keywords.CurrentColor))) { // No override: text.TextLine.ColourOverride = false; } else { // Set the colour: text.TextLine.SetColour(lineColour.GetColour(this, Css.Properties.TextDecorationColor.GlobalProperty)); } } // Get the font face: text.FontToDraw = box.FontFace; // Overflow-wrap mode (only active for 'break-word' which is just '1'): text.OverflowWrapActive = (cs.ResolveInt(Css.Properties.OverflowWrap.GlobalProperty) == 1); // Check if the text is 'dirty'. // If it is, that means we'll need to rebuild the TextRenderingProperty's Glyph array. if (text.Dirty) { // Setup text now: // (Resets text.Characters based on all the text related CSS properties like variant etc). text.LoadCharacters((Node as RenderableTextNode).characterData_, this); } if (text.Characters == null || text.AllEmpty) { text.FontSize = 0f; return; } }