/// <summary>Resets the clipping boundary back to the whole screen.</summary> public void ResetBoundary() { // Update viewport: if (InWorldUI != null) { ScreenViewport.Width = InWorldUI.pixelWidth; ScreenViewport.Height = InWorldUI.pixelHeight; } else { ScreenViewport.Width = ScreenInfo.ScreenX; ScreenViewport.Height = ScreenInfo.ScreenY; } // Apply screen viewport as the root one: Viewport = ScreenViewport; if (InWorldUI != null || ScreenClip) { ClippingBoundary.Set(0, 0, Viewport.Width, Viewport.Height); } else { ClippingBoundary.Set(-80000, -80000, 160000, 160000); } }
/// <summary>Draws an underline (or a strikethrough).</summary> public override void DrawUnderline(Renderman renderer) { // Setup the mesh: if (Underline == null) { // We'll need the root gameObject - this should always be on a TextNode, but the parentNode // will always be an Element. Therefore we can grab the gameObject via that parentNode: HtmlElement parent = RenderData.Node.parentNode as HtmlElement; Underline = new MeshBufferExtruded(parent.rootGameObject.transform); // Update material: Underline.SetMaterial(new Material(renderer.CurrentShaderSet.Extruded)); Underline.ExtrudeAmount = Extrude; } // Update colours: Underline.FrontColor = renderer.FontColour; Underline.BackColor = renderer.FontColour; Underline.SideColor = renderer.FontColour; BoxRegion region = renderer.CurrentRegion; // 4 corners (back face): Vector3 a = renderer.PixelToWorldUnit(region.X, region.Y, renderer.TextDepth); Vector3 b = renderer.PixelToWorldUnit(region.MaxX, region.Y, renderer.TextDepth); Vector3 c = renderer.PixelToWorldUnit(region.MaxX, region.MaxY, renderer.TextDepth); Vector3 d = renderer.PixelToWorldUnit(region.X, region.MaxY, renderer.TextDepth); // Build a simple cube: Underline.BuildCube(a, b, c, d); }
/// <summary>Draws the given Emoji character.</summary> public void DrawEmoji(Glyph character, ref float left, Renderman renderer) { if (!character.Image.Loaded) { return; } BoxRegion screenRegion = renderer.CurrentRegion; float top = renderer.TopOffset; // It's an image (e.g. Emoji). AtlasLocation locatedAt = RequireImage(character.Image); if (locatedAt == null) { // It needs to be isolated. Big emoji image! return; } if (CharacterProviders.FixHeight) { // Set the region: screenRegion.Set(left, top, locatedAt.Width, locatedAt.Height); } else { screenRegion.Set(left, top, FontSize, FontSize); } if (screenRegion.Overlaps(renderer.ClippingBoundary)) { // Ensure correct batch: renderer.SetupBatch(this, locatedAt.Atlas, null); // If the two overlap, this means it's actually visible. MeshBlock block = Add(renderer); // Set it's colour: block.SetColour(renderer.ColorOverlay); // And clip our meshblock to fit within boundary: block.TextUV = null; block.ImageUV = block.SetClipped(renderer.ClippingBoundary, screenRegion, renderer, RenderData.computedStyle.ZIndex, locatedAt, block.ImageUV); block.Done(renderer.Transform); } left += (character.AdvanceWidth) + LetterSpacing; if (character.Charcode == (int)' ') { left += WordSpacing; } }
/// <summary>Sets up this renderman.</summary> private void Init() { Block = new MeshBlock(this); ClippingBoundary = new BoxRegion(0, 0, UnityEngine.Screen.width, UnityEngine.Screen.height); if (RootDocument == null) { HtmlDocument hdoc = new HtmlDocument(this); hdoc.SetRawLocation(new Location("resources://", null)); RootDocument = hdoc; } }
/// <summary>Resets all values in the renderer. Called before each layout.</summary> public void Reset() { Depth = 0f; MaxDepth = 0f; ViewportBackground = true; BatchDepth = RenderQueue; PositionedAncestors.Clear(); TransformedAncestors.Clear(); FlowRootAncestors.Clear(); ColorOverlay = Color.white; FontAliasingTop = InfiniText.Fonts.OutlineLocation + InfiniText.Fonts.Aliasing; FontAliasingBottom = InfiniText.Fonts.OutlineLocation - InfiniText.Fonts.Aliasing; BoxStack.Clear(); LastBlockBox = null; ResetBoundary(); DepthUsed = false; CurrentBatch = null; CurrentShaderSet = ShaderSet.Standard; FirstInformer = null; FirstLetter = null; FirstLine = null; if (Counters != null) { Counters.Clear(); } // Reapply screen viewport as the root one: Viewport = ScreenViewport; // Clean input grid: InputGrid.Clean(Viewport.Width, Viewport.Height); if (InWorldUI == null) { // This is the main UI renderer. // Clear the root node: Node = null; } }
/// <summary>Sets the clipping boundary from the given computed style.</summary> /// <param name="style">The computed style to find the clipping boundary from.</param> public void SetBoundary(ComputedStyle computed, LayoutBox box) { bool visibleX = (box.OverflowX == VisibilityMode.Visible); bool visibleY = (box.OverflowY == VisibilityMode.Visible); if (visibleX && visibleY) { return; } BoxRegion newBoundary = null; if (visibleX) { newBoundary = new BoxRegion(ClippingBoundary.X, box.Y + box.FixedStyleOffsetTop, ClippingBoundary.Width, box.InnerHeight); } else if (visibleY) { newBoundary = new BoxRegion(box.X + box.FixedStyleOffsetLeft, ClippingBoundary.Y, box.InnerWidth, ClippingBoundary.Height); } else { newBoundary = new BoxRegion(box.X + box.FixedStyleOffsetLeft, box.Y + box.FixedStyleOffsetTop, box.InnerWidth, box.InnerHeight); } // Should it be clipped? Css.Value clipValue = computed[Css.Properties.ClipMode.GlobalProperty]; if (clipValue == null || clipValue.IsType(typeof(Css.Keywords.None)) || clipValue.GetBoolean(computed.RenderData, Css.Properties.ClipMode.GlobalProperty)) { newBoundary.ClipBy(ClippingBoundary); } else { ScreenClip = false; } ClippingBoundary = newBoundary; }
/// <summary>Draws a character with x-inverted UV's. Used for rendering e.g. "1 < 2" in right-to-left.</summary> protected virtual void DrawInvertCharacter(ref float left, Renderman renderer) { BoxRegion screenRegion = renderer.CurrentRegion; float top = renderer.TopOffset; int index = renderer.CharacterIndex; Glyph character = Characters[index]; if (character == null) { return; } if (Kerning != null) { left += Kerning[index] * FontSize; } // Get atlas location (if it has one): AtlasLocation locatedAt = character.Location; if (locatedAt != null) { // We're on the atlas! float y = top + renderer.TextAscender - ((character.Height + character.MinY) * FontSize); float scaleFactor = renderer.TextScaleFactor; screenRegion.Set(left + (character.LeftSideBearing * FontSize), y, locatedAt.Width * scaleFactor, locatedAt.Height * scaleFactor); if (screenRegion.Overlaps(renderer.ClippingBoundary)) { // True if this character is visible. // Ensure correct batch: renderer.SetupBatch(this, null, locatedAt.Atlas); MeshBlock block = Add(renderer); block.SetColour(renderer.FontColour); block.ApplyOutline(); // And clip our meshblock to fit within boundary: // Clip our meshblock to fit within boundary: if (Background != null && Isolated) { // Setup the batch material for this char: Material imageMaterial = Background.Image.Contents.GetImageMaterial(renderer.CurrentShaderSet.Normal); SetBatchMaterial(renderer, imageMaterial); // Reapply text atlas: renderer.CurrentBatch.SetFontAtlas(locatedAt.Atlas); // Apply the image UV's (we're always isolated so these can tile by going out of range): block.ImageUV = block.SetClipped( renderer.ClippingBoundary, screenRegion, renderer, RenderData.computedStyle.ZIndex, Background.ImageLocation, block.ImageUV ); } else { block.ImageUV = null; } UVBlock uvs = block.SetClipped(renderer.ClippingBoundary, screenRegion, renderer, RenderData.computedStyle.ZIndex, locatedAt, block.TextUV); if (uvs.Shared) { uvs = new UVBlock(uvs); } // Invert along X: float temp = uvs.MinX; uvs.MinX = uvs.MaxX; uvs.MaxX = temp; // Assign to the block: block.TextUV = uvs; block.Done(renderer.Transform); } } left += (character.AdvanceWidth * FontSize) + LetterSpacing; if (character.Charcode == (int)' ') { left += WordSpacing; } }
internal override void Layout(LayoutBox box, Renderman renderer) { if (Characters == null || Characters.Length == 0) { // Not ready yet or nothing here. return; } // Get indices: int startIndex = box.TextStart; int maxIndex = box.TextEnd; if (startIndex >= maxIndex) { // No text selected. return; } // If we've got a background, set it up now: if (Background != null) { Background.Layout(box, renderer); } // Get the font colour: Color fontColour = BaseColour * renderer.ColorOverlay; // The blocks we allocate here come from FontToDraw. // They use the same renderer and same layout service, but just a different mesh. // This is to enable potentially very large font atlases with multiple fonts. float top = box.Y + box.StyleOffsetTop + LineHeightOffset; float left = box.X + box.StyleOffsetLeft; // Should we auto-alias the text? // Note that this property "drags" to following elements which is correct. // We don't really want to break batching chains for aliasing. if (Alias == float.MaxValue) { // Yep! // Get quick ref to constants set: float[] hints = Fonts.AutoAliasHints; if (FontSize <= hints[0]) { renderer.FontAliasingBottom = hints[1]; renderer.FontAliasingTop = hints[2]; } else if (FontSize >= hints[12]) { renderer.FontAliasingBottom = hints[13]; renderer.FontAliasingTop = hints[14]; } else { float minBottom; float minTop; float deltaSize; float deltaBottom; float deltaTop; float relative; // Note: Just about everything here is constant. Inlined for speed. // Interpolate: if (FontSize <= hints[3]) { // Between 3 and 0. minBottom = hints[1]; minTop = hints[2]; deltaSize = hints[3] - hints[0]; deltaBottom = hints[4] - hints[1]; deltaTop = hints[5] - hints[2]; relative = (FontSize - hints[0]) / deltaSize; } else if (FontSize <= hints[6]) { // Between 6 and 3. minBottom = hints[4]; minTop = hints[5]; deltaSize = hints[6] - hints[3]; deltaBottom = hints[7] - hints[4]; deltaTop = hints[8] - hints[5]; relative = (FontSize - hints[3]) / deltaSize; } else if (FontSize <= hints[9]) { // Between 9 and 6. minBottom = hints[7]; minTop = hints[8]; deltaSize = hints[9] - hints[6]; deltaBottom = hints[10] - hints[7]; deltaTop = hints[11] - hints[8]; relative = (FontSize - hints[6]) / deltaSize; } else { // Must be between 12 and 9. minBottom = hints[10]; minTop = hints[11]; deltaSize = hints[12] - hints[9]; deltaBottom = hints[13] - hints[10]; deltaTop = hints[14] - hints[11]; relative = (FontSize - hints[9]) / deltaSize; } // Note: Most of the above is constant. Inlined for speed. renderer.FontAliasingBottom = (relative * deltaBottom) + minBottom; renderer.FontAliasingTop = (relative * deltaTop) + minTop; } } else { // Write aliasing: renderer.FontAliasingTop = InfiniText.Fonts.OutlineLocation + Alias; renderer.FontAliasingBottom = InfiniText.Fonts.OutlineLocation - Alias; } if (!AllEmpty) { // Firstly, make sure the batch is using the right font texture. // This may generate a new batch if the font doesn't match the previous or existing font. // Get the full shape of the element: float width = box.PaddedWidth; float height = box.PaddedHeight; float minY = box.Y + box.Border.Top; float minX = box.X + box.Border.Left; BoxRegion boundary = new BoxRegion(minX, minY, width, height); if (!boundary.Overlaps(renderer.ClippingBoundary)) { if (Visible) { SetVisibility(false); } return; } else if (!Visible) { // ImageLocation will allocate here if it's needed. SetVisibility(true); } } float zIndex = RenderData.computedStyle.ZIndex; BoxRegion screenRegion = new BoxRegion(); // Update renderer with various text properties: renderer.CurrentRegion = screenRegion; renderer.CurrentBox = box; renderer.TopOffset = top; renderer.TextScaleFactor = FontSize / Fonts.Rasteriser.ScalarX; renderer.TextAscender = (FontToDraw.Ascender * FontSize); // First up, underline. if (TextLine != null) { // We have one. Locate it next. float lineWeight = (FontToDraw.StrikeSize * FontSize); float yOffset = 0f; switch (TextLine.Type) { case TextDecorationLineMode.Underline: yOffset = renderer.TextAscender + (lineWeight * 2f); break; case TextDecorationLineMode.LineThrough: yOffset = (FontToDraw.StrikeOffset * FontSize); yOffset = renderer.TextAscender - yOffset; break; case TextDecorationLineMode.Overline: yOffset = (lineWeight * 2f); break; } // Note: The integer rounding is required here to prevent underlines from alternating between // a thick line and a thin one as you scroll around. screenRegion.Set(left, (int)(top + yOffset), box.Width, lineWeight); if (screenRegion.Overlaps(renderer.ClippingBoundary)) { // This region is visible. Clip it: screenRegion.ClipBy(renderer.ClippingBoundary); if (TextLine.ColourOverride) { renderer.FontColour = TextLine.BaseColour * renderer.ColorOverlay; } else { renderer.FontColour = fontColour; } renderer.TextDepth = zIndex; // Draw the underline now! DrawUnderline(renderer); } } // Update font colour: renderer.FontColour = fontColour; // Next, render the characters. // If we're rendering from right to left, flip the punctuation over. switch (box.UnicodeBidi) { case UnicodeBidiMode.LeftwardsNormal: // Render the text from the last char backwards. for (int i = maxIndex - 1; i >= startIndex; i--) { renderer.CharacterIndex = i; DrawCharacter(ref left, renderer); } break; case UnicodeBidiMode.LeftwardsMirrored: // Render the characters with horizontally inverted uv's. for (int i = maxIndex - 1; i >= startIndex; i--) { renderer.CharacterIndex = i; DrawInvertCharacter(ref left, renderer); } break; case UnicodeBidiMode.RightwardsMirrored: // Render the characters with horizontally inverted uv's. for (int i = startIndex; i < maxIndex; i++) { renderer.CharacterIndex = i; DrawInvertCharacter(ref left, renderer); } break; default: // Draw it as is. for (int i = startIndex; i < maxIndex; i++) { renderer.CharacterIndex = i; DrawCharacter(ref left, renderer); } break; } }
internal override void Layout(LayoutBox box, Renderman renderer) { if (Image == null || !Image.Loaded) { return; } if (Clipping == BackgroundClipping.Text) { return; } if (Image.Contents.Isolate || renderer.RenderMode == RenderMode.NoAtlas || Filtering != FilterMode.Point || ForcedIsolate) { // SPA is an animation format, so we need a custom texture atlas to deal with it. // This is because the frames of any animation would quickly exhaust our global texture atlas. // So to get a custom atlas, we must isolate this property. Isolate(); } else { // Reverse isolation, if we are isolated already: Include(); } // Get the full shape of the element: float width; float height; float minX; float minY; if (renderer.ViewportBackground) { // Applying to whole background: BoxRegion viewport = renderer.Viewport; minY = (int)viewport.Y; minX = (int)viewport.X; width = (int)viewport.Width; height = (int)viewport.Height; renderer.ViewportBackground = false; } else { width = (int)(box.PaddedWidth); height = (int)(box.PaddedHeight); minY = (int)(box.Y + box.Border.Top); minX = (int)(box.X + box.Border.Left); } if (width == 0 || height == 0) { if (Visible) { SetVisibility(false); } return; } // Tell the image that the box has likely changed - this allows it to redraw (e.g. SVGs): float trueImageWidth; float trueImageHeight; Image.Contents.OnLayout(RenderData, box, out trueImageWidth, out trueImageHeight); BoxRegion boundary = new BoxRegion(minX, minY, width, height); if (!boundary.Overlaps(renderer.ClippingBoundary)) { if (Visible) { SetVisibility(false); } return; } else if (!Visible) { // ImageLocation will allocate here if it's needed. SetVisibility(true); } boundary.ClipBy(renderer.ClippingBoundary); // Texture time - get it's location on that atlas: AtlasLocation locatedAt = ImageLocation; if (locatedAt == null) { // We're not using the atlas here. if (!Isolated) { Isolate(); } locatedAt = new AtlasLocation(trueImageWidth, trueImageHeight); } // Isolation is all done - safe to setup the batch now: renderer.SetupBatch(this, locatedAt.Atlas, null); // Great - Use locatedAt.Width/locatedAt.Height - this removes any risk of overflowing into some other image. int imageCountX = 1; int imageCountY = 1; float imageWidth = trueImageWidth * RenderData.ValueScale; float imageHeight = trueImageHeight * RenderData.ValueScale; bool autoX = false; bool autoY = false; if (SizeX != null) { if (SizeX.IsAuto) { autoX = true; } else { imageWidth = SizeX.GetDecimal(RenderData, Css.Properties.BackgroundSize.GlobalPropertyX); } } if (SizeY != null) { if (SizeY.IsAuto) { autoY = true; } else { imageHeight = SizeY.GetDecimal(RenderData, Css.Properties.BackgroundSize.GlobalPropertyY); } } if (autoX) { imageWidth = imageHeight * trueImageWidth / trueImageHeight; } else if (autoY) { imageHeight = imageWidth * trueImageHeight / trueImageWidth; } // offsetX and offsetY are the images position offset from where it should be (e.g. x of -200 means it's 200px left) // Apply the offset origin: float offsetX = OffsetOriginX * (width - imageWidth); float offsetY = OffsetOriginY * (height - imageHeight); float offset; // Resolve the offset values, if there is any: if (OffsetX != null) { offset = OffsetX.GetDecimal(RenderData, ValueAxis.X); if (OffsetOriginX == 1f) { offsetX -= offset; } else { offsetX += offset; } } if (OffsetY != null) { offset = OffsetY.GetDecimal(RenderData, ValueAxis.Y); if (OffsetOriginY == 1f) { offsetY -= offset; } else { offsetY += offset; } } if (RepeatX) { // Get the rounded up number of images: imageCountX = (int)Math.Ceiling(width / imageWidth); if (offsetX != 0) { // If we have an offset, another image is introduced. imageCountX++; } } if (RepeatY) { // Get the rounded up number of images: imageCountY = (int)Math.Ceiling(height / imageHeight); if (offsetY != 0) { // If we have an offset, another image is introduced. imageCountY++; } } float blockX = minX + offsetX; float blockY = minY + offsetY; if (RepeatX && offsetX > 0) { // We're repeating and the image is offset by a +ve number. // This means a small gap, OffsetX px wide, is open on this left side. // So to fill it, we need to offset this first image by a much bigger number - the value imageWidth-OffsetX. blockX -= (imageWidth - offsetX); // This results in the first image having OffsetX pixels exposed in the box - this is what we want. } if (RepeatY && offsetY > 0) { // Similar thing to above: blockY -= (imageHeight - offsetY); } BoxRegion screenRegion = new BoxRegion(); bool first = true; float startX = blockX; Color colour = renderer.ColorOverlay; float zIndex = (RenderData.computedStyle.ZIndex - 0.003f); Transformation transform = renderer.Transform; for (int y = 0; y < imageCountY; y++) { for (int x = 0; x < imageCountX; x++) { // Draw at blockX/blockY. screenRegion.Set(blockX, blockY, imageWidth, imageHeight); if (screenRegion.Overlaps(boundary)) { // If the two overlap, this means it's actually visible. MeshBlock block = Add(renderer); if (first) { first = false; if (Isolated) { // Set current material: SetBatchMaterial(renderer, Image.Contents.GetImageMaterial(renderer.CurrentShaderSet.Isolated)); } } // Set its colour: block.SetColour(colour); // And clip our meshblock to fit within boundary: block.TextUV = null; block.ImageUV = block.SetClipped(boundary, screenRegion, renderer, zIndex, locatedAt, block.ImageUV); block.Done(transform); } blockX += imageWidth; } blockX = startX; blockY += imageHeight; } }
internal override void Layout(LayoutBox box, Renderman renderer) { // Dimensions: float width = box.Width; float height = box.Height; if (Renderer == null) { // Create the FWUI now: Renderer = new FlatWorldUI("#Internal-PowerUI-Raster-" + RasterID, (int)width, (int)height); RasterID++; if (Filter != null) { // Set source: Filter.Set("source0", Renderer.Texture); } // Grab the output texture: Output = Renderer.Texture; } // Does the given renderer belong to the worldUI? if (renderer == Renderer.Renderer) { // Yes! We're actually drawing the element. return; } // Next we'll draw the rastered image. // It's essentially just the output from the renderer. // Get the top left inner corner (inside margin and border): float top = box.Y; float left = box.X; // Update the FlatWorldUI next: UpdateRenderer(box, width, height); // Always isolated: Isolate(); // Make sure the renderer stalls and doesn't draw anything else of this element or its kids. renderer.StallStatus = 2; // Setup boundary: BoxRegion boundary = new BoxRegion(left, top, width, height); if (!boundary.Overlaps(renderer.ClippingBoundary)) { if (Visible) { SetVisibility(false); } return; } else if (!Visible) { // ImageLocation will allocate here if it's needed. SetVisibility(true); } // Texture time - get its location on that atlas: if (LocatedAt == null) { LocatedAt = new AtlasLocation(width, height); } else { // Dimensions changed? int w = (int)width; int h = (int)height; if (LocatedAt.Width != w || LocatedAt.Height != h) { // Update it: LocatedAt.UpdateFixed(width, height); } } boundary.ClipBy(renderer.ClippingBoundary); // Ensure we have a batch: renderer.SetupBatch(this, null, null); if (Material == null) { // Create the material now using the isolated shader: Material = new Material(renderer.CurrentShaderSet.Isolated); // Hook up the output: Material.SetTexture("_MainTex", Output); } // Allocate the block: MeshBlock block = Add(renderer); // Set current material: SetBatchMaterial(renderer, Material); // Set the (overlay) colour: block.SetColour(renderer.ColorOverlay); block.TextUV = null; // Z-index (same as a background-image): float zIndex = (RenderData.computedStyle.ZIndex - 0.003f); BoxRegion screenRegion = new BoxRegion(); screenRegion.Set(left, top, width, height); // Setup the block: block.ImageUV = block.SetClipped(boundary, screenRegion, renderer, zIndex, LocatedAt, block.ImageUV); // Flush it: block.Done(renderer.Transform); }
/// <summary>Draws a character and advances the pen onwards.</summary> protected virtual void DrawCharacter(ref float left, Renderman renderer) { BoxRegion screenRegion = renderer.CurrentRegion; Color fontColour = renderer.FontColour; float top = renderer.TopOffset; int index = renderer.CharacterIndex; Glyph character = Text.Characters[index]; if (character == null) { return; } if (Text.Kerning != null) { left += Text.Kerning[index] * Text.FontSize; } AtlasLocation locatedAt; if (character.Image != null) { DrawEmoji(character, ref left, renderer); return; } // Get atlas location: locatedAt = character.Location; // Does this character have a visual glyph? E.g. a space does not. if (locatedAt != null) { float y = top + renderer.TextAscender - ((character.Height + character.MinY) * Text.FontSize); float scaleFactor = renderer.TextScaleFactor; screenRegion.Set(left + (character.LeftSideBearing * Text.FontSize), y, locatedAt.Width * scaleFactor, locatedAt.Height * scaleFactor); if (screenRegion.Overlaps(renderer.ClippingBoundary)) { // True if this character is visible. // Ensure correct batch: renderer.SetupBatch(this, null, locatedAt.Atlas); MeshBlock block = Add(renderer); block.SetColour(fontColour); block.ApplyOutline(); // And clip our meshblock to fit within boundary: block.ImageUV = null; block.TextUV = block.SetClipped(renderer.ClippingBoundary, screenRegion, renderer, RenderData.computedStyle.ZIndex, locatedAt, block.TextUV); block.Done(renderer.Transform); } } left += (character.AdvanceWidth * Text.FontSize) + Text.LetterSpacing; if (character.Charcode == (int)' ') { left += Text.WordSpacing; } }
/// <summary>Draws a character with x-inverted UV's. Used for rendering e.g. "1 < 2" in right-to-left.</summary> protected virtual void DrawInvertCharacter(ref float left, Renderman renderer) { BoxRegion screenRegion = renderer.CurrentRegion; float top = renderer.TopOffset; int index = renderer.CharacterIndex; Glyph character = Text.Characters[index]; if (character == null) { return; } if (Text.Kerning != null) { left += Text.Kerning[index] * Text.FontSize; } // Get atlas location (if it has one): AtlasLocation locatedAt = character.Location; if (locatedAt != null) { // We're on the atlas! float y = top + renderer.TextAscender - ((character.Height + character.MinY) * Text.FontSize); float scaleFactor = renderer.TextScaleFactor; screenRegion.Set(left + (character.LeftSideBearing * Text.FontSize), y, locatedAt.Width * scaleFactor, locatedAt.Height * scaleFactor); if (screenRegion.Overlaps(renderer.ClippingBoundary)) { // True if this character is visible. // Ensure correct batch: renderer.SetupBatch(this, null, locatedAt.Atlas); MeshBlock block = Add(renderer); block.SetColour(renderer.FontColour); block.ApplyOutline(); // And clip our meshblock to fit within boundary: block.ImageUV = null; UVBlock uvs = block.SetClipped(renderer.ClippingBoundary, screenRegion, renderer, RenderData.computedStyle.ZIndex, locatedAt, block.TextUV); if (uvs.Shared) { uvs = new UVBlock(uvs); } // Invert along X: float temp = uvs.MinX; uvs.MinX = uvs.MaxX; uvs.MaxX = temp; // Assign to the block: block.TextUV = uvs; block.Done(renderer.Transform); } } left += (character.AdvanceWidth * Text.FontSize) + Text.LetterSpacing; if (character.Charcode == (int)' ') { left += Text.WordSpacing; } }
public void Render(LayoutBox box, Renderman renderer, float cornerX, float cornerY) { if (OuterArc == null) { RecomputeArcs(box.Border); } else if (InnerArc == null) { RecomputeInnerArc(box.Border); } // Get the z-Index: float zIndex = RoundCorners.Computed.MaxZIndex + 0.006f; // Figure out where half way is (divide by 2): int halfway = (BlocksRequired >> 1); Color colour = Colour; // Grab the clipping boundary: BoxRegion clip = renderer.ClippingBoundary; // Make it relative to the corners location: float minClipX = clip.X - cornerX; float minClipY = clip.Y - cornerY; float maxClipX = clip.MaxX - cornerX; float maxClipY = clip.MaxY - cornerY; // For each block.. for (int i = 0; i < BlocksRequired; i++) { // Read the outer arc: Vector2 outerPointA = OuterArc[i]; // Figure out the bounding box (constant for a particular block). float minX = outerPointA.x; float maxX = minX; float minY = outerPointA.y; float maxY = minY; Vector2 outerPointB = OuterArc[i + 1]; // Update the bounding box: if (outerPointB.x < minX) { minX = outerPointB.x; } else if (outerPointB.x > maxX) { maxX = outerPointB.x; } if (outerPointB.y < minY) { minY = outerPointB.y; } else if (outerPointB.y > maxY) { maxY = outerPointB.y; } // Line segment A->B on the "outer" arc. // Read the inner arc: Vector2 innerPointA = InnerArc[i]; // Update the bounding box: if (innerPointA.x < minX) { minX = innerPointA.x; } else if (innerPointA.x > maxX) { maxX = innerPointA.x; } if (innerPointA.y < minY) { minY = innerPointA.y; } else if (innerPointA.y > maxY) { maxY = innerPointA.y; } Vector2 innerPointB = InnerArc[i + 1]; // Update the bounding box: if (innerPointB.x < minX) { minX = innerPointB.x; } else if (innerPointB.x > maxX) { maxX = innerPointB.x; } if (innerPointB.y < minY) { minY = innerPointB.y; } else if (innerPointB.y > maxY) { maxY = innerPointB.y; } // How does our bounding box compare to the clipping region? if (maxX < minClipX) { continue; } else if (minX > maxClipX) { continue; } if (maxY < minClipY) { continue; } else if (minY > maxClipY) { continue; } // Line segment A->B on the "inner" arc. // Get a block: MeshBlock block = Border.Add(renderer); // Set the UV to that of the solid block colour pixel: block.SetSolidColourUV(); // Get the border colour: if (i == halfway) { // Get the next colour: if (Border.BaseColour != null && Border.BaseColour.Count != 1) { colour = Border.BaseColour[ToIndex].GetColour(Border.RenderData, Css.Properties.BorderColor.GlobalProperty) * renderer.ColorOverlay; } } // Set the border colour: block.SetColour(colour); // Apply the block region: block.VertexTopLeft = renderer.PixelToWorldUnit(cornerX + outerPointA.x, cornerY + outerPointA.y, zIndex); block.VertexTopRight = renderer.PixelToWorldUnit(cornerX + outerPointB.x, cornerY + outerPointB.y, zIndex); block.VertexBottomLeft = renderer.PixelToWorldUnit(cornerX + innerPointA.x, cornerY + innerPointA.y, zIndex); block.VertexBottomRight = renderer.PixelToWorldUnit(cornerX + innerPointB.x, cornerY + innerPointB.y, zIndex); block.Done(renderer.Transform); } }
internal override void Layout(LayoutBox box, Renderman renderer) { float width; float height; float top; float left; bool clip = true; if (renderer.ViewportBackground) { // Applying to whole background: BoxRegion viewport = renderer.Viewport; top = viewport.Y; left = viewport.X; width = viewport.Width; height = viewport.Height; renderer.ViewportBackground = false; clip = false; } else { // Get the top left inner corner (inside margin and border): width = box.PaddedWidth; height = box.PaddedHeight; top = box.Y + box.Border.Top; left = box.X + box.Border.Left; // 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 (overlay) colour: block.SetColour(BaseColour * renderer.ColorOverlay); // And finally sort out the verts: if (clip) { block.SetClipped(renderer.ClippingBoundary, new BoxRegion(left, top, width, height), renderer, RenderData.computedStyle.ZIndex - 0.006f); } else { block.ApplyVertices(new BoxRegion(left, top, width, height), renderer, RenderData.computedStyle.ZIndex - 0.006f); } // Flush it: block.Done(renderer.Transform); }
internal override void Layout(LayoutBox box, Renderman renderer) { if (Corners != null) { Corners.PreLayout(); } ComputedStyle computed = RenderData.computedStyle; // Find the zIndex: // NB: At same depth as BGColour - right at the back. float zIndex = (computed.ZIndex - 0.006f); // Get the co-ord of the top edge: float top = box.Y; float left = box.X; // Get the border widths: BoxStyle width = box.Border; // Move top by the widths: top += width.Top; left += width.Left; // And the dimensions of the lines: float boxWidth = box.PaddedWidth; float boxHeight = box.PaddedHeight; // Get the other dimensions: float topY = top - width.Top; float right = left + boxWidth; float rightX = right + width.Right; float bottom = top + boxHeight; float bottomY = bottom + width.Bottom; float leftX = left - width.Left; int segment = renderer.Segment; Transformation transform = renderer.Transform; BoxRegion screenRegion = new BoxRegion(); // Get the default colour - that's the same as the text colour: Color colour = Color.black; // Is the border multicoloured? bool multiColour = false; // Does this border have a colour? if (BaseColour == null) { // Grab the text colour if there is one: if (RenderData.Text != null) { // It's the same as the font colour: colour = RenderData.Text.BaseColour * renderer.ColorOverlay; } else { // Nope - We need to set alpha: colour.a = renderer.ColorOverlay.a; } } else if (BaseColour.Count == 1) { colour = BaseColour[0].GetColour(RenderData, Css.Properties.BorderColor.GlobalProperty) * renderer.ColorOverlay; } else { multiColour = true; } // Handle border-radius: if (Corners != null) { for (int i = 0; i < 4; i++) { if (multiColour) { colour = BaseColour[i].GetColour(RenderData, Css.Properties.BorderColor.GlobalProperty) * renderer.ColorOverlay; } Corners.Layout(colour, box, renderer, i); } } // Get clipper: BoxRegion clip = renderer.ClippingBoundary; float origLeftX = leftX; float origTopY = topY; float origBottomY = bottomY; float origRightX = rightX; // top and topY: if (top < clip.Y) { top = clip.Y; } else if (top > clip.MaxY) { top = clip.MaxY; } if (topY < clip.Y) { topY = clip.Y; } // bottom and bottomY: if (bottom > clip.MaxY) { bottom = clip.MaxY; } else if (bottom < clip.Y) { bottom = clip.Y; } if (bottomY > clip.MaxY) { bottomY = clip.MaxY; } // right and rightX: if (right < clip.X) { right = clip.X; } else if (right > clip.MaxX) { right = clip.MaxX; } // rightX vs clip.MaxX if (rightX > clip.MaxX) { rightX = clip.MaxX; } // left and leftX: if (left < clip.X) { left = clip.X; } else if (left > clip.MaxX) { left = clip.MaxX; } if (leftX < clip.X) { leftX = clip.X; } float cornerPointA; float cornerPointB; for (int i = 0; i < 4; i++) { // Does this border have multiple colours? if (multiColour) { colour = BaseColour[i].GetColour(RenderData, Css.Properties.BorderColor.GlobalProperty) * renderer.ColorOverlay; } // Add to region: switch (i) { case 0: // Top. screenRegion.SetPoints(leftX, topY, rightX, top); break; case 1: // Right. // We only draw the right border if 'segment' includes 'end' if ((segment & LineBoxSegment.End) == 0) { goto NextLine; } screenRegion.SetPoints(right, topY, rightX, bottomY); break; case 2: // Bottom. screenRegion.SetPoints(leftX, bottom, rightX, bottomY); break; case 3: // Left. // Similarly, we only draw left if segment includes 'start': if ((segment & LineBoxSegment.Start) == 0) { goto NextLine; } screenRegion.SetPoints(leftX, topY, left, bottomY); break; } if (screenRegion.Overlaps(clip)) { // It's visible. // Ensure we have a batch (doesn't change graphics or font textures, thus both null): renderer.SetupBatch(this, null, null); // And get our block ready: MeshBlock block = Add(renderer); // Set the UV to that of the solid block colour pixel: block.SetSolidColourUV(); // Set the border colour: block.SetColour(colour); // Apply verts: switch (i) { case 0: // Top: if (Corners == null) { block.VertexTopLeft = renderer.PixelToWorldUnit(leftX, topY, zIndex); block.VertexTopRight = renderer.PixelToWorldUnit(rightX, topY, zIndex); block.VertexBottomLeft = renderer.PixelToWorldUnit(left, top, zIndex); block.VertexBottomRight = renderer.PixelToWorldUnit(right, top, zIndex); } else { // Top left/right corners: cornerPointA = origLeftX + Corners.TopLeftRadius; cornerPointB = origRightX - Corners.TopRightRadius; if (cornerPointA < clip.X) { cornerPointA = clip.X; } if (cornerPointB > clip.MaxX) { cornerPointB = clip.MaxX; } // Note that we use leftX/rightX for all of them. // That's because the corner has a 'straight' edge. block.VertexTopLeft = renderer.PixelToWorldUnit(cornerPointA, topY, zIndex); block.VertexTopRight = renderer.PixelToWorldUnit(cornerPointB, topY, zIndex); block.VertexBottomLeft = renderer.PixelToWorldUnit(cornerPointA, top, zIndex); block.VertexBottomRight = renderer.PixelToWorldUnit(cornerPointB, top, zIndex); } break; case 1: // Right: if (Corners == null) { block.VertexTopLeft = renderer.PixelToWorldUnit(right, top, zIndex); block.VertexTopRight = renderer.PixelToWorldUnit(rightX, topY, zIndex); block.VertexBottomLeft = renderer.PixelToWorldUnit(right, bottom, zIndex); block.VertexBottomRight = renderer.PixelToWorldUnit(rightX, bottomY, zIndex); } else { // Top right/ bottom right corners: cornerPointA = origTopY + Corners.TopRightRadius; cornerPointB = origBottomY - Corners.BottomRightRadius; if (cornerPointA < clip.Y) { cornerPointA = clip.Y; } if (cornerPointB > clip.MaxY) { cornerPointB = clip.MaxY; } // Note that we use topY/bottomY for all of them. block.VertexTopLeft = renderer.PixelToWorldUnit(right, cornerPointA, zIndex); block.VertexTopRight = renderer.PixelToWorldUnit(rightX, cornerPointA, zIndex); block.VertexBottomLeft = renderer.PixelToWorldUnit(right, cornerPointB, zIndex); block.VertexBottomRight = renderer.PixelToWorldUnit(rightX, cornerPointB, zIndex); } break; case 2: // Bottom: if (Corners == null) { block.VertexTopLeft = renderer.PixelToWorldUnit(left, bottom, zIndex); block.VertexTopRight = renderer.PixelToWorldUnit(right, bottom, zIndex); block.VertexBottomLeft = renderer.PixelToWorldUnit(leftX, bottomY, zIndex); block.VertexBottomRight = renderer.PixelToWorldUnit(rightX, bottomY, zIndex); } else { // Bottom left/ bottom right corners: // Note that we use leftX/rightX for all of them. cornerPointA = origLeftX + Corners.BottomLeftRadius; cornerPointB = origRightX - Corners.BottomRightRadius; if (cornerPointA < clip.X) { cornerPointA = clip.X; } if (cornerPointB > clip.MaxX) { cornerPointB = clip.MaxX; } block.VertexTopLeft = renderer.PixelToWorldUnit(cornerPointA, bottom, zIndex); block.VertexTopRight = renderer.PixelToWorldUnit(cornerPointB, bottom, zIndex); block.VertexBottomLeft = renderer.PixelToWorldUnit(cornerPointA, bottomY, zIndex); block.VertexBottomRight = renderer.PixelToWorldUnit(cornerPointB, bottomY, zIndex); } break; case 3: // Left: if (Corners == null) { block.VertexTopLeft = renderer.PixelToWorldUnit(leftX, topY, zIndex); block.VertexTopRight = renderer.PixelToWorldUnit(left, top, zIndex); block.VertexBottomLeft = renderer.PixelToWorldUnit(leftX, bottomY, zIndex); block.VertexBottomRight = renderer.PixelToWorldUnit(left, bottom, zIndex); } else { // Top right/ bottom right corners: cornerPointA = origTopY + Corners.TopLeftRadius; cornerPointB = origBottomY + width.Bottom - Corners.BottomLeftRadius; if (cornerPointA < clip.Y) { cornerPointA = clip.Y; } if (cornerPointB > clip.MaxY) { cornerPointB = clip.MaxY; } block.VertexTopLeft = renderer.PixelToWorldUnit(leftX, cornerPointA, zIndex); block.VertexTopRight = renderer.PixelToWorldUnit(left, cornerPointA, zIndex); block.VertexBottomLeft = renderer.PixelToWorldUnit(leftX, cornerPointB, zIndex); block.VertexBottomRight = renderer.PixelToWorldUnit(left, cornerPointB, zIndex); } break; } // Done! block.Done(transform); } NextLine: continue; } }