private Size doPaintOrMeasure(Graphics g, EggsNode node, Font initialFont, Color initialForeColor, int constrainingWidth, List<renderingInfo> renderings = null, List<locationInfo> locations = null) { var glyphOverhang = TextRenderer.MeasureText(g, "Wg", initialFont, _dummySize) - TextRenderer.MeasureText(g, "Wg", initialFont, _dummySize, TextFormatFlags.NoPadding); int x = glyphOverhang.Width / 2, y = glyphOverhang.Height / 2; int wrapWidth = WordWrap ? Math.Max(1, constrainingWidth - glyphOverhang.Width) : int.MaxValue; int hangingIndent = _hangingIndent * (_hangingIndentUnit == IndentUnit.Spaces ? measure(initialFont, " ", g).Width : 1); bool atBeginningOfLine = false; // STEP 1: Run the word-wrapping as if TextAlign were TopLeft int actualWidth = EggsML.WordWrap(node, new renderState(initialFont, initialForeColor), wrapWidth, (state, text) => measure(state.Font, text, g).Width, (state, text, width) => { if (state.Mnemonic && !string.IsNullOrWhiteSpace(text)) state.ActiveLocations.OfType<linkLocationInfo>().FirstOrDefault().NullOr(link => { link.Mnemonic = char.ToLowerInvariant(text.Trim()[0]); return link; }); if (renderings != null && !string.IsNullOrEmpty(text)) { renderingInfo info; if (!atBeginningOfLine && renderings.Count > 0 && (info = renderings[renderings.Count - 1]).State == state) { info.Text += text; var rect = info.Rectangle; rect.Width += width; info.Rectangle = rect; } else { info = new renderingInfo(text, new Rectangle(x, y, width, measure(state.Font, " ", g).Height), state); renderings.Add(info); } foreach (var location in state.ActiveLocations) { if (location.Rectangles.Count == 0 || location.Rectangles[location.Rectangles.Count - 1].Y != info.Rectangle.Y) location.Rectangles.Add(info.Rectangle); else { var rect = location.Rectangles[location.Rectangles.Count - 1]; rect.Width += width; location.Rectangles[location.Rectangles.Count - 1] = rect; } } } atBeginningOfLine = false; x += width; }, (state, newParagraph, indent) => { atBeginningOfLine = true; var sh = measure(state.Font, " ", g).Height; y += sh; if (newParagraph && _paragraphSpacing > 0) y += (int) (_paragraphSpacing * sh); var newIndent = state.BlockIndent + indent; if (!newParagraph) newIndent += hangingIndent; x = newIndent + glyphOverhang.Width / 2; return newIndent; }, (state, tag, parameter) => { var font = state.Font; switch (tag) { // ITALICS case '/': return Tuple.Create(state.ChangeFont(new Font(font, font.Style | FontStyle.Italic)), 0); // BOLD case '*': return Tuple.Create(state.ChangeFont(new Font(font, font.Style | FontStyle.Bold)), 0); // UNDERLINE case '_': return Tuple.Create(state.ChangeFont(new Font(font, font.Style | FontStyle.Underline)), 0); // MNEMONICS case '&': return Tuple.Create(state.SetMnemonic(), 0); // BULLET POINT case '[': var bulletSize = measure(font, _bullet, g); var advance = bulletSize.Width; if (renderings != null) renderings.Add(new renderingInfo(_bullet, new Rectangle(x, y, advance, bulletSize.Height), new renderState(font, state.Color))); x += advance; return Tuple.Create(state.ChangeBlockIndent(state.BlockIndent + advance), advance); // LINK (e.g. <link target>{link text}, link target may be omitted) case '{': if (locations == null) break; var linkLocation = new linkLocationInfo { LinkID = parameter }; locations.Add(linkLocation); return Tuple.Create(state.ChangeColor(Enabled ? LinkColor : SystemColors.GrayText).AddActiveLocation(linkLocation), 0); // TOOLTIP (e.g. <tooltip text>#main text#) case '#': if (string.IsNullOrWhiteSpace(parameter) || locations == null) break; var tooltipLocation = new tooltipLocationInfo { Tooltip = parameter }; locations.Add(tooltipLocation); return Tuple.Create(state.AddActiveLocation(tooltipLocation), 0); // COLOUR (e.g. <colour>=coloured text=, revert to default colour if no <colour> specified) case '=': var color = parameter == null ? initialForeColor : (Color) (_colorConverter ?? (_colorConverter = new ColorConverter())).ConvertFromString(parameter); return Tuple.Create(state.ChangeColor(color), 0); } return Tuple.Create(state, 0); }); var totalSize = new Size(actualWidth + glyphOverhang.Width, y + measure(initialFont, " ", g).Height + glyphOverhang.Height); // STEP 2: Fix everything according to TextAlign. if (renderings != null) { // 2a: vertical alignment int offsetY = 0; switch (_textAlign) { case ContentAlignment.TopCenter: case ContentAlignment.TopLeft: case ContentAlignment.TopRight: // Already top-aligned: nothing to do break; case ContentAlignment.MiddleCenter: case ContentAlignment.MiddleLeft: case ContentAlignment.MiddleRight: offsetY = ClientSize.Height / 2 - totalSize.Height / 2; goto default; case ContentAlignment.BottomCenter: case ContentAlignment.BottomLeft: case ContentAlignment.BottomRight: offsetY = ClientSize.Height - totalSize.Height; goto default; default: foreach (var inf in renderings) { var rect = inf.Rectangle; rect.Y += offsetY; inf.Rectangle = rect; } break; } // 2b: horizontal alignment foreach (var group in renderings.GroupConsecutiveBy(inf => inf.Rectangle.Y)) { var width = group.Max(elem => elem.Rectangle.Right); int offsetX = 0; switch (_textAlign) { case ContentAlignment.TopLeft: case ContentAlignment.MiddleLeft: case ContentAlignment.BottomLeft: // Already left-aligned: nothing to do break; case ContentAlignment.TopCenter: case ContentAlignment.MiddleCenter: case ContentAlignment.BottomCenter: offsetX = ClientSize.Width / 2 - width / 2; goto default; case ContentAlignment.TopRight: case ContentAlignment.MiddleRight: case ContentAlignment.BottomRight: offsetX = ClientSize.Width - width; goto default; default: foreach (var inf in group) { var rect = inf.Rectangle; rect.X += offsetX; inf.Rectangle = rect; } break; } } } return totalSize; }
private Size doPaintOrMeasure(Graphics g, EggsNode node, Font initialFont, Color initialForeColor, int constrainingWidth, List <renderingInfo> renderings = null, List <locationInfo> locations = null) { var glyphOverhang = TextRenderer.MeasureText(g, "Wg", initialFont, _dummySize) - TextRenderer.MeasureText(g, "Wg", initialFont, _dummySize, TextFormatFlags.NoPadding); int x = glyphOverhang.Width / 2, y = glyphOverhang.Height / 2; int wrapWidth = WordWrap ? Math.Max(1, constrainingWidth - glyphOverhang.Width) : int.MaxValue; int hangingIndent = _hangingIndent * (_hangingIndentUnit == IndentUnit.Spaces ? measure(initialFont, " ", g).Width : 1); bool atBeginningOfLine = false; // STEP 1: Run the word-wrapping as if TextAlign were TopLeft int actualWidth = EggsML.WordWrap(node, new renderState(initialFont, initialForeColor), wrapWidth, (state, text) => measure(state.Font, text, g).Width, (state, text, width) => { if (state.Mnemonic && !string.IsNullOrWhiteSpace(text)) { state.ActiveLocations.OfType <linkLocationInfo>().FirstOrDefault().NullOr(link => { link.Mnemonic = char.ToLowerInvariant(text.Trim()[0]); return(link); }); } if (renderings != null && !string.IsNullOrEmpty(text)) { renderingInfo info; if (!atBeginningOfLine && renderings.Count > 0 && (info = renderings[renderings.Count - 1]).State == state) { info.Text += text; var rect = info.Rectangle; rect.Width += width; info.Rectangle = rect; } else { info = new renderingInfo(text, new Rectangle(x, y, width, measure(state.Font, " ", g).Height), state); renderings.Add(info); } foreach (var location in state.ActiveLocations) { if (location.Rectangles.Count == 0 || location.Rectangles[location.Rectangles.Count - 1].Y != info.Rectangle.Y) { location.Rectangles.Add(info.Rectangle); } else { var rect = location.Rectangles[location.Rectangles.Count - 1]; rect.Width += width; location.Rectangles[location.Rectangles.Count - 1] = rect; } } } atBeginningOfLine = false; x += width; }, (state, newParagraph, indent) => { atBeginningOfLine = true; var sh = measure(state.Font, " ", g).Height; y += sh; if (newParagraph && _paragraphSpacing > 0) { y += (int)(_paragraphSpacing * sh); } var newIndent = state.BlockIndent + indent; if (!newParagraph) { newIndent += hangingIndent; } x = newIndent + glyphOverhang.Width / 2; return(newIndent); }, (state, tag, parameter) => { var font = state.Font; switch (tag) { // ITALICS case '/': return(state.ChangeFont(new Font(font, font.Style | FontStyle.Italic)), 0);