/// <summary>Gets the next/previous suitable newline index.</summary> public int FindNewline(int direction) { // Get the text content: RenderableTextNode htn = TextHolder; if (htn == null || htn.RenderData.Text == null || htn.RenderData.Text.Characters == null) { return(CaretIndex); } // Get the renderer so we can access the chars: TextRenderingProperty trp = htn.RenderData.Text; // First index to check is.. int index = CaretIndex + direction; // Safety check: int max = trp.Characters.Length; if (index >= max) { index = max - 1; } else if (index < 0) { index = 0; } while (index > 0 && index < max) { // Check if char[index] is a newline: InfiniText.Glyph glyph = trp.Characters[index]; if (glyph != null && glyph.Charcode == (int)'\n') { // Got it! if (direction == 1) { index++; } break; } // Next one: index += direction; } return(index); }
/// <summary>Finds the index of the nearest character to x pixels.</summary> /// <param name="x">The number of pixels from the left edge of the screen.</param> public int LetterIndex(float x, LayoutBox box) { if (box == null) { // Nope! return(0); } // Get the text renderer: TextRenderingProperty trp = RenderData_.Text; if (trp == null) { // It's not been rendered at all yet. return(0); } // Walk the characters in the box until we've walked at least x units. float left = box.X; float fontSize = trp.FontSize; for (int i = box.TextStart; i < box.TextEnd; i++) { // Get the char: InfiniText.Glyph glyph = trp.Characters[i]; if (glyph == null) { continue; } // Move width along: left += glyph.AdvanceWidth * fontSize; if (left >= x) { // Got it! return(i); } // Advance over spacing: left += trp.LetterSpacing; } // End of the box. return(box.TextEnd); }
internal override void Layout(LayoutBox box, Renderman renderer) { // Does the boxes text region overlap our selected range? if (box.TextEnd <= StartIndex || EndIndex <= box.TextStart) { return; } // It overlaps! We need to figure out which section we're selecting (and how large it is). // Get the top left inner corner (inside margin and border): // Assume we've selected the whole thing; we remove from the start and remove from the end if needed. float width = box.PaddedWidth; float height = box.PaddedHeight; float top = box.Y + box.Border.Top; float left = box.X + box.Border.Left; float fs = Text.FontSize; // If the selected indices totally contain the box.. if (StartIndex > box.TextStart) { // Trim the start. We need to chop off the width of these characters. float characterWidth = 0f; int max = StartIndex; if (max > Text.Characters.Length) { max = Text.Characters.Length; } for (int i = box.TextStart; i < max; i++) { InfiniText.Glyph character = Text.Characters[i]; if (character == null) { continue; } // Advance over the glyph: if (Text.Kerning != null) { characterWidth += Text.Kerning[i] * fs; } characterWidth += (character.AdvanceWidth * fs) + Text.LetterSpacing; if (character.Charcode == (int)' ') { characterWidth += Text.WordSpacing; } } // Chop it off: left += characterWidth; width -= characterWidth; } if (EndIndex < box.TextEnd) { // Trim the end. We need to chop off the width of these characters. float characterWidth = 0f; int max = box.TextEnd; if (max > Text.Characters.Length) { max = Text.Characters.Length; } for (int i = EndIndex; i < max; i++) { InfiniText.Glyph character = Text.Characters[i]; if (character == null) { continue; } // Advance over the glyph: if (Text.Kerning != null) { characterWidth += Text.Kerning[i] * fs; } characterWidth += (character.AdvanceWidth * fs) + Text.LetterSpacing; if (character.Charcode == (int)' ') { characterWidth += Text.WordSpacing; } } // Chop it off: width -= characterWidth; } // Is it clipped? if (renderer.IsInvisible(left, top, width, height)) { // Totally not visible. return; } // Ensure we have a batch (doesn't change graphics or font thus both nulls): renderer.SetupBatch(this, null, null); // Allocate the block: MeshBlock block = Add(renderer); // Using firstblock as our block here. // Set the UV to that of the solid block colour pixel: block.SetSolidColourUV(); // Set the colour: block.SetColour(BaseColour); // And finally sort out the verts: block.SetClipped(renderer.ClippingBoundary, new BoxRegion(left, top, width, height), renderer, RenderData.computedStyle.ZIndex - 0.004f); // Flush it: block.Done(renderer.Transform); }
/// <summary>Part of shrink-to-fit. Computes the maximum and minimum possible width for an element.</summary> public void GetWidthBounds(out float min, out float max) { // Get the text renderer (or create it): Css.TextRenderingProperty text = RenderData_.RequireTextProperty(); if (text.AllEmpty) { min = 0f; max = 0f; return; } // Need to compute TAW? if (TotalAdvanceWidth == float.MinValue) { float width = 0f; int spaceCount = 0; float wordWidth = 0f; LongestWord = 0f; for (int i = 0; i < text.Characters.Length; i++) { // Get the glyph: InfiniText.Glyph glyph = text.Characters[i]; if (glyph == null) { // Skip! continue; } // The glyph's width is.. float gWidth = glyph.AdvanceWidth + text.LetterSpacing; wordWidth += gWidth; width += gWidth; // Got a space? if (glyph.Charcode == (int)' ') { if (wordWidth > LongestWord) { LongestWord = wordWidth; wordWidth = 0f; } // Advance width: spaceCount += 1; } } if (wordWidth > LongestWord) { LongestWord = wordWidth; } TotalAdvanceWidth = width; SpaceCount = spaceCount; } min = LongestWord * text.FontSize; max = (TotalAdvanceWidth * text.FontSize) + (SpaceCount * text.WordSpacing); }
/// <summary>Gets the relative position (relative to parent) in pixels of the letter at the given index.</summary> /// <param name="index">The index of the letter in this text element.</param> /// <returns>The number of pixels from the left and top edges of this text element the letter is as a vector.</returns> public Vector2 GetPosition(int index) { // Get the text renderer: TextRenderingProperty trp = RenderData.Text; // Get the box that contains the given text index. LayoutBox box = RenderData.FirstBox; if (trp == null || box == null || trp.Characters == null) { // It's not been rendered at all yet. return(Vector2.zero); } LayoutBox previous = null; while (box != null) { // Note that start is inclusive, end is not. if (index < box.TextStart) { // Use the previous box (this allows us to catch newlines): if (previous != null) { box = previous; } break; } // Next one: previous = box; box = box.NextInElement; } if (box == null) { // Use the last box: box = RenderData.LastBox; // Check if index goes beyond the end: if (index > box.TextEnd) { index = box.TextEnd; } } // Relative to the given box: float top = box.ParentOffsetTop + box.Border.Top; float left = box.ParentOffsetLeft + box.Border.Left; float fs = trp.FontSize; for (int i = box.TextStart; i < index; i++) { InfiniText.Glyph character = trp.Characters[i]; if (character == null) { continue; } // Advance over the glyph: if (trp.Kerning != null) { left += trp.Kerning[i] * fs; } left += (character.AdvanceWidth * fs) + trp.LetterSpacing; if (character.Charcode == (int)' ') { left += trp.WordSpacing; } } // Done! return(new Vector2(left, top)); }
public override void Reflow(Renderman renderer) { // Get the text renderer (or create it): Css.TextRenderingProperty text = RequireTextProperty(); if (text.Characters == null || text.AllEmpty) { return; } LayoutBox box = null; // Get the baseline offset: float baseline = text.FontSize * text.FontToDraw.Descender; // Compute our line boxes based on text.Characters and the available space. // Safely ignore direction here because either way the selected characters are the same. // Note that things like first-letter are also considered. // Get the top of the stack: LineBoxMeta lbm = renderer.TopOfStack; float cssLineHeight = lbm.CssLineHeight; // Is it justify (if so, every word is its own box): bool breakAllWords = (lbm.HorizontalAlign == HorizontalAlignMode.Justify); // Offset the baseline: float lineHeightOffset = (cssLineHeight - text.FontSize) / 2f; text.LineHeightOffset = lineHeightOffset; baseline += lineHeightOffset; float wordWidth = 0f; float boxWidth = 0f; bool wrappable = ((lbm.WhiteSpace & WhiteSpaceMode.Wrappable) != 0); int i = 0; int latestBreakpoint = -1; // bidi text direction (0 = Not set yet, UnicodeBidiMode.LeftwardsNormal, UnicodeBidiMode.RightwardsNormal) int direction = 0; // Direction before the current block of weak chars. int beforeWeak = 0; while (i < text.Characters.Length) { // Get the glyph: InfiniText.Glyph glyph = text.Characters[i]; if (glyph == null) { // Skip! i++; continue; } // The glyph's width is.. float width = (glyph.AdvanceWidth * text.FontSize) + text.LetterSpacing; if (box == null) { // The box always has an inner height of 'font size': if (renderer.FirstLetter != null) { // Clear FL immediately (so it can't go recursive): SparkInformerNode firstLetter = renderer.FirstLetter; renderer.FirstLetter = null; // Update its internal text node: RenderableTextNode textNode = firstLetter.firstChild as RenderableTextNode; // Note that we have to do it this way as the node might // change the *font*. textNode.characterData_ = ((char)glyph.Charcode) + ""; Css.TextRenderingProperty textData = textNode.RenderData.RequireTextProperty(); textData.Dirty = true; // Ask it to reflow right now (must ask the node so it correctly takes the style into account): firstLetter.RenderData.UpdateCss(renderer); firstLetter.RenderData.Reflow(renderer); i++; continue; } // Create the box now: box = new LayoutBox(); box.PositionMode = PositionMode.Static; box.DisplayMode = DisplayMode.Inline; box.Baseline = baseline; box.TextStart = i; // Adopt current direction: box.UnicodeBidi = direction; if (FirstBox == null) { FirstBox = box; LastBox = box; } else { // add to this element: LastBox.NextInElement = box; LastBox = box; } // line-height is the inner height here: box.InnerHeight = cssLineHeight; boxWidth = 0f; wordWidth = 0f; } // Are we breaking this word? bool implicitNewline = (glyph.Charcode == (int)'\n'); int breakMode = implicitNewline ? 1 : 0; // direction: int dir = glyph.Directionality; if (dir == InfiniText.BidiBlock.Rightwards) { // Rightwards (includes weak ones): // Was the previous one 'weak'? if (dir == InfiniText.BidiBlock.WeakLeftToRight) { // Update before weak: beforeWeak = direction; } else { // Not weak: beforeWeak = 0; } if (direction != UnicodeBidiMode.RightwardsNormal) { if (direction == 0) { // Not set yet - Set the mode into the box: box.UnicodeBidi = UnicodeBidiMode.RightwardsNormal; } else { // Changed from leftwards to rightwards. Break the boxes here. breakMode = 3; } // Update dir: direction = UnicodeBidiMode.RightwardsNormal; } } else if (dir == InfiniText.BidiBlock.RightToLeft) { // Leftwards if (direction != UnicodeBidiMode.LeftwardsNormal) { if (direction == 0) { // Not set yet - Set the mode into the box: box.UnicodeBidi = UnicodeBidiMode.LeftwardsNormal; } else { // Changed from rightwards to leftwards. Break the boxes here. breakMode = 3; } // Update dir: direction = UnicodeBidiMode.LeftwardsNormal; } // Never weak: beforeWeak = 0; } else if (beforeWeak != 0) { // Neutral otherwise // (adopts whatever the current is, unless the previous one was *weak*, // in which case, it adopts whatever the direction was before that): if (direction != beforeWeak) { // Change direction! direction = beforeWeak; breakMode = 3; } } // Got a space? bool space = (glyph.Charcode == (int)' '); if (space) { // Advance width: width += text.WordSpacing; if (breakAllWords) { breakMode = 3; } else { latestBreakpoint = i; } // Lock in the previous text: boxWidth += wordWidth + width; wordWidth = 0f; } else { // Advance word width now: wordWidth += width; } // Word wrapping next: if (breakMode == 0 && wrappable && i != box.TextStart) { // Test if we can break here: breakMode = lbm.GetLineSpace(wordWidth, boxWidth); // Return to the previous space if we should. if (breakMode == 2 && text.OverflowWrapActive) { // The word doesn't fit at all (2) and we're supposed to break it. boxWidth += wordWidth - width; wordWidth = 0f; } else if (breakMode != 0) { if (latestBreakpoint == -1) { // Isn't a previous space! if (breakMode == 2) { // Instead, we'll try and break a parent. // This typically happens with inline elements // which are right on the end of the host line. lbm.TryBreakParent(); // Don't break the node: breakMode = 0; } else if (breakMode == 3) { // Break but no newline - just advance to the following char: i++; } else if (lbm.PenX != lbm.LineStart) { // Newline: lbm.CompleteLine(LineBreakMode.Normal); // Don't break the node: breakMode = 0; } else { // Don't break the node: breakMode = 0; } } else { i = latestBreakpoint + 1; } } } if (breakMode != 0) { // We're breaking! box.InnerWidth = boxWidth; box.TextEnd = i; latestBreakpoint = i; // If the previous glyph is a space, update EndSpaceSize: if (space) { // Update ending spaces: box.EndSpaceSize = width; } // Ensure dimensions are set: box.SetDimensions(false, false); // Add the box to the line: lbm.AddToLine(box); // Update dim's: lbm.AdvancePen(box); // Clear: box = null; boxWidth = 0f; if (breakMode != 3) { // Newline: if (implicitNewline) { // Also the last line: lbm.CompleteLine(LineBreakMode.Normal | LineBreakMode.Last); } else { // Normal: lbm.CompleteLine(LineBreakMode.Normal); // Process it again. continue; } } } // Next character: i++; } if (box != null) { // Always apply inner width: box.InnerWidth = boxWidth + wordWidth; box.TextEnd = text.Characters.Length; // Ensure dimensions are set: box.SetDimensions(false, false); // Add the box to the line: lbm.AddToLine(box); // Update dim's: lbm.AdvancePen(box); } }