From 55b1f82ab05fc8eb4dd517c2e3f6b77a83c19d0a Mon Sep 17 00:00:00 2001 From: Chuso Date: Fri, 4 Mar 2016 20:53:35 +0100 Subject: [PATCH] Small-Caps-Support Add small-caps rendering. (Not checked in MONO. Not checked in Winforms using GDI+). --- Source/Demo/Common/Samples/02.Text.htm | 1 + Source/Demo/Common/Samples/03.Tables.htm | 1 + Source/Demo/Common/TestSamples/12.Text.htm | 14 ++-- Source/Demo/Common/TestSamples/22.RTL.htm | 34 ++++----- .../Adapters/FontAdapter.cs | 2 +- .../Adapters/GraphicsAdapter.cs | 37 +++++++++- .../Adapters/GraphicsAdapter.cs | 72 +++++++++++++++++++ .../Adapters/GraphicsAdapter.cs | 35 +++++++++ Source/HtmlRenderer/Adapters/RGraphics.cs | 47 ++++++++++++ Source/HtmlRenderer/Core/Dom/CssBox.cs | 20 ++++-- .../HtmlRenderer/Core/Dom/CssBoxProperties.cs | 17 +++++ .../HtmlRenderer/Core/Utils/CssConstants.cs | 2 + 12 files changed, 252 insertions(+), 30 deletions(-) diff --git a/Source/Demo/Common/Samples/02.Text.htm b/Source/Demo/Common/Samples/02.Text.htm index 85e201024..bd77ead3e 100644 --- a/Source/Demo/Common/Samples/02.Text.htm +++ b/Source/Demo/Common/Samples/02.Text.htm @@ -24,6 +24,7 @@

Formatting Colors
  • Back colors, Back colors, Back colors
  • Font style, Font style, Font style, Font style, Font style, Font style
  • +
  • Font variant, Font variant (Font style, Font style, Font style, Font style)
  • Lorem ipsum dolor sit amet, diff --git a/Source/Demo/Common/Samples/03.Tables.htm b/Source/Demo/Common/Samples/03.Tables.htm index e923bacce..f858d799d 100644 --- a/Source/Demo/Common/Samples/03.Tables.htm +++ b/Source/Demo/Common/Samples/03.Tables.htm @@ -79,6 +79,7 @@

  • Font style, Font style, Font style, Font style, Font style, Font style
  • +
  • Font variant, Font variant
  • diff --git a/Source/Demo/Common/TestSamples/12.Text.htm b/Source/Demo/Common/TestSamples/12.Text.htm index c53a43609..833196bae 100644 --- a/Source/Demo/Common/TestSamples/12.Text.htm +++ b/Source/Demo/Common/TestSamples/12.Text.htm @@ -2,13 +2,13 @@ Text - diff --git a/Source/Demo/Common/TestSamples/22.RTL.htm b/Source/Demo/Common/TestSamples/22.RTL.htm index 35bf9bf6c..a24d4f4e7 100644 --- a/Source/Demo/Common/TestSamples/22.RTL.htm +++ b/Source/Demo/Common/TestSamples/22.RTL.htm @@ -1,18 +1,18 @@ - - -
    -
    שלום עולם, יש ברבורים בעגם הזה
    -
    -
    שלום עולם, יש ברבורים בעגם הזה
    -
    שלום עולם, יש ברבורים בעגם הזה
    -
    -
    -
    -
    שלום עולם,hello world יש ברבורים בעגם הזה
    -
    -
    שלום עולם, יש ברבורים בעגם הזה
    -
    שלום עולם, יש ברבורים בעגם הזה
    -
    -
    - + + +
    +
    שלום עולם, יש ברבורים בעגם הזה
    +
    +
    שלום עולם, יש ברבורים בעגם הזה
    +
    שלום עולם, יש ברבורים בעגם הזה
    +
    +
    +
    +
    שלום עולם,hello world יש ברבורים בעגם הזה
    +
    +
    שלום עולם, יש ברבורים בעגם הזה
    +
    שלום עולם, יש ברבורים בעגם הזה
    +
    +
    + \ No newline at end of file diff --git a/Source/HtmlRenderer.PdfSharp/Adapters/FontAdapter.cs b/Source/HtmlRenderer.PdfSharp/Adapters/FontAdapter.cs index 07e7c40a9..b121789bb 100644 --- a/Source/HtmlRenderer.PdfSharp/Adapters/FontAdapter.cs +++ b/Source/HtmlRenderer.PdfSharp/Adapters/FontAdapter.cs @@ -96,7 +96,7 @@ public override double GetWhitespaceWidth(RGraphics graphics) /// /// the full height of the font /// the vertical offset of the font underline location from the top of the font. - internal void SetMetrics(int height, int underlineOffset) + internal void SetMetrics(int height, double underlineOffset) { _height = height; _underlineOffset = underlineOffset; diff --git a/Source/HtmlRenderer.PdfSharp/Adapters/GraphicsAdapter.cs b/Source/HtmlRenderer.PdfSharp/Adapters/GraphicsAdapter.cs index c6d406c58..86d4ca5f2 100644 --- a/Source/HtmlRenderer.PdfSharp/Adapters/GraphicsAdapter.cs +++ b/Source/HtmlRenderer.PdfSharp/Adapters/GraphicsAdapter.cs @@ -106,7 +106,7 @@ public override RSize MeasureString(string str, RFont font) { var height = realFont.Height; var descent = realFont.Size * realFont.FontFamily.GetCellDescent(realFont.Style) / realFont.FontFamily.GetEmHeight(realFont.Style); - fontAdapter.SetMetrics(height, (int)Math.Round((height - descent + 1f))); + fontAdapter.SetMetrics(height, height - descent + 1f); } return Utils.Convert(size); @@ -123,6 +123,41 @@ public override void DrawString(string str, RFont font, RColor color, RPoint poi var xBrush = ((BrushAdapter)_adapter.GetSolidBrush(color)).Brush; _g.DrawString(str, ((FontAdapter)font).Font, (XBrush)xBrush, point.X, point.Y, _stringFormat); } + + public override void DrawSmallCapString(string str, RFont regularFont, RFont reducedFont, RColor color, RPoint point, bool rtl) + { + var xBrush = ((BrushAdapter)_adapter.GetSolidBrush(color)).Brush; + double additionalOffsetForSmallCaps = regularFont.UnderlineOffset - reducedFont.UnderlineOffset; + + int i = 0; + int len = str.Length; + while (i < len) + { + int j = i; + while (j < len && !char.IsLower(str, j)) + j++; + if (j != i) + { + _g.DrawString(str.Substring(i, j - i), ((FontAdapter)regularFont).Font, (XBrush)xBrush, point.X, point.Y, _stringFormat); + point.X += MeasureString(str.Substring(i, j - i), regularFont).Width; + + i = j; + } + + while (j < len && char.IsLower(str, j)) + j++; + if (j != i) + { + point.Y += additionalOffsetForSmallCaps; + + _g.DrawString(str.Substring(i, j - i).ToUpper(), ((FontAdapter)reducedFont).Font, (XBrush)xBrush, point.X, point.Y, _stringFormat); + point.X += MeasureString(str.Substring(i, j - i).ToUpper(), reducedFont).Width; + + point.Y -= additionalOffsetForSmallCaps; + i = j; + } + } + } public override RBrush GetTextureBrush(RImage image, RRect dstRect, RPoint translateTransformLocation) { diff --git a/Source/HtmlRenderer.WPF/Adapters/GraphicsAdapter.cs b/Source/HtmlRenderer.WPF/Adapters/GraphicsAdapter.cs index 49649c320..37253f39e 100644 --- a/Source/HtmlRenderer.WPF/Adapters/GraphicsAdapter.cs +++ b/Source/HtmlRenderer.WPF/Adapters/GraphicsAdapter.cs @@ -221,6 +221,78 @@ public override void DrawString(string str, RFont font, RColor color, RPoint poi _g.DrawText(formattedText, Utils.ConvertRound(point)); } } + + public override void DrawSmallCapString(string str, RFont regularFont, RFont reducedFont, RColor color, RPoint point, bool rtl) + { + int i = 0; + int len = str.Length; + while (i < len) + { + int j = i; + while (j < len && !char.IsLower(str, j)) + j++; + if (j != i) + { + DrawSmallCapString(str.Substring(i, j - i), regularFont, color, point, regularFont.Size, rtl); + point.X += MeasureString(str.Substring(i, j - i), regularFont).Width; + + i = j; + } + + while (j < len && char.IsLower(str, j)) + j++; + if (j != i) + { + DrawSmallCapString(str.Substring(i, j - i).ToUpper(), reducedFont, color, point, regularFont.Size, rtl); + point.X += MeasureString(str.Substring(i, j - i).ToUpper(), reducedFont).Width; + + i = j; + } + } + } + + private void DrawSmallCapString(string str, RFont reducedFont, RColor color, RPoint point, double regularFontSize, bool rtl) + { + var colorConv = ((BrushAdapter)_adapter.GetSolidBrush(color)).Brush; + + bool glyphRendered = false; + GlyphTypeface glyphTypeface = ((FontAdapter)reducedFont).GlyphTypeface; + if (glyphTypeface != null) + { + double width = 0; + ushort[] glyphs = new ushort[str.Length]; + double[] widths = new double[str.Length]; + + int i = 0; + for (; i < str.Length; i++) + { + ushort glyph; + if (!glyphTypeface.CharacterToGlyphMap.TryGetValue(str[i], out glyph)) + break; + + glyphs[i] = glyph; + width += glyphTypeface.AdvanceWidths[glyph]; + widths[i] = 96d / 72d * reducedFont.Size * glyphTypeface.AdvanceWidths[glyph]; + } + + if (i >= str.Length) + { + point.Y += glyphTypeface.Baseline * regularFontSize * 96d / 72d; // Align vertically reduced (small-cap) chars to regular (uppercase) chars + point.X += rtl ? 96d / 72d * reducedFont.Size * width : 0; + + glyphRendered = true; + var glyphRun = new GlyphRun(glyphTypeface, rtl ? 1 : 0, false, 96d / 72d * reducedFont.Size, glyphs, Utils.ConvertRound(point), widths, null, null, null, null, null, null); + _g.DrawGlyphRun(colorConv, glyphRun); + } + } + + if (!glyphRendered) // Untested... + { + var formattedText = new FormattedText(str, CultureInfo.CurrentCulture, rtl ? FlowDirection.RightToLeft : FlowDirection.LeftToRight, ((FontAdapter)reducedFont).Font, 96d / 72d * reducedFont.Size, colorConv); + point.X += rtl ? formattedText.Width : 0; + _g.DrawText(formattedText, Utils.ConvertRound(point)); + } + } public override RBrush GetTextureBrush(RImage image, RRect dstRect, RPoint translateTransformLocation) { diff --git a/Source/HtmlRenderer.WinForms/Adapters/GraphicsAdapter.cs b/Source/HtmlRenderer.WinForms/Adapters/GraphicsAdapter.cs index 523870c2b..b9e5fec1b 100644 --- a/Source/HtmlRenderer.WinForms/Adapters/GraphicsAdapter.cs +++ b/Source/HtmlRenderer.WinForms/Adapters/GraphicsAdapter.cs @@ -263,6 +263,41 @@ public override void DrawString(string str, RFont font, RColor color, RPoint poi #endif } } + + public override void DrawSmallCapString(string str, RFont regularFont, RFont reducedFont, RColor color, RPoint point, bool rtl) + { + double additionalOffsetForSmallCaps = regularFont.UnderlineOffset - reducedFont.UnderlineOffset; + int i = 0; + int len = str.Length; + while (i < len) + { + int j = i; + while (j < len && !char.IsLower(str, j)) + j++; + if (j != i) + { + RSize normalSize = MeasureString(str.Substring(i, j - i), regularFont); + DrawString(str.Substring(i, j - i), regularFont, color, point, new RSize(normalSize.Width, normalSize.Height), rtl); + point.X += normalSize.Width; + + i = j; + } + + while (j < len && char.IsLower(str, j)) + j++; + if (j != i) + { + point.Y += additionalOffsetForSmallCaps; + + RSize reducedSize = MeasureString(str.Substring(i, j - i).ToUpper(), reducedFont); + DrawString(str.Substring(i, j - i).ToUpper(), reducedFont, color, point, new RSize(reducedSize.Width, reducedSize.Height), rtl); + point.X += reducedSize.Width; + + point.Y -= additionalOffsetForSmallCaps; + i = j; + } + } + } public override RBrush GetTextureBrush(RImage image, RRect dstRect, RPoint translateTransformLocation) { diff --git a/Source/HtmlRenderer/Adapters/RGraphics.cs b/Source/HtmlRenderer/Adapters/RGraphics.cs index af54f2ae4..e791b45cf 100644 --- a/Source/HtmlRenderer/Adapters/RGraphics.cs +++ b/Source/HtmlRenderer/Adapters/RGraphics.cs @@ -176,6 +176,42 @@ public void ResumeClipping() /// the size of the string public abstract RSize MeasureString(string str, RFont font); + /// + /// Measure the width and height of string when drawn on device context HDC + /// using the given fonts: (small-caps variant). + /// + /// the string to measure + /// the font to measure string with (uppercase chars) + /// the font to measure string with (smallcap chars) + /// the size of the string + public RSize MeasureSmallCapString(string str, RFont regularFont, RFont reducedFont) + { + RSize size = RSize.Empty; + size.Height = regularFont.Height; + int i = 0; + int len = str.Length; + while (i < len) + { + int j = i; + while (j < len && !char.IsLower(str, j)) + j++; + if (j != i) + { + size.Width += MeasureString(str.Substring(i, j - i), regularFont).Width; + i = j; + } + + while (j < len && char.IsLower(str, j)) + j++; + if (j != i) + { + size.Width += MeasureString(str.Substring(i, j - i).ToUpper(), reducedFont).Width; + i = j; + } + } + return size; + } + /// /// Measure the width of string under max width restriction calculating the number of characters that can fit and the width those characters take.
    /// Not relevant for platforms that don't render HTML on UI element. @@ -198,6 +234,17 @@ public void ResumeClipping() /// is to render the string right-to-left (true - RTL, false - LTR) public abstract void DrawString(String str, RFont font, RColor color, RPoint point, RSize size, bool rtl); + /// + /// Draw the given string using the given fonts and foreground color at given location (small-caps variant). + /// + /// the string to draw + /// the font to use to draw the string (uppercase chars) + /// the reduced font to use to draw the string (small-cap chars) + /// the text color to set + /// the location to start string draw (top-left) + /// is to render the string right-to-left (true - RTL, false - LTR) + public abstract void DrawSmallCapString(string str, RFont regularFont, RFont reducedFont, RColor color, RPoint point, bool rtl); + /// /// Draws a line connecting the two points specified by the coordinate pairs. /// diff --git a/Source/HtmlRenderer/Core/Dom/CssBox.cs b/Source/HtmlRenderer/Core/Dom/CssBox.cs index b2cd3d984..681bc65ad 100644 --- a/Source/HtmlRenderer/Core/Dom/CssBox.cs +++ b/Source/HtmlRenderer/Core/Dom/CssBox.cs @@ -723,6 +723,9 @@ internal virtual void MeasureWordsSize(RGraphics g) { foreach (var boxWord in Words) { + if (FontVariant == CssConstants.SmallCaps) + boxWord.Width = boxWord.Text != "\n" ? g.MeasureSmallCapString(boxWord.Text, ActualFont, ActualFontForSmallCaps).Width : 0; + else boxWord.Width = boxWord.Text != "\n" ? g.MeasureString(boxWord.Text, ActualFont).Width : 0; boxWord.Height = ActualFont.Height; } @@ -1388,7 +1391,10 @@ private void PaintWords(RGraphics g, RPoint offset) if (HtmlContainer.SelectionForeColor != RColor.Empty && (word.SelectedStartOffset > 0 || word.SelectedEndIndexOffset > -1)) { g.PushClipExclude(rect); - g.DrawString(word.Text, ActualFont, ActualColor, wordPoint, new RSize(word.Width, word.Height), isRtl); + if (FontVariant == CssConstants.SmallCaps) + g.DrawSmallCapString(word.Text, ActualFont, ActualFontForSmallCaps, ActualColor, wordPoint, isRtl); + else + g.DrawString(word.Text, ActualFont, ActualColor, wordPoint, new RSize(word.Width, word.Height), isRtl); g.PopClip(); g.PushClip(rect); g.DrawString(word.Text, ActualFont, GetSelectionForeBrush(), wordPoint, new RSize(word.Width, word.Height), isRtl); @@ -1396,13 +1402,19 @@ private void PaintWords(RGraphics g, RPoint offset) } else { - g.DrawString(word.Text, ActualFont, GetSelectionForeBrush(), wordPoint, new RSize(word.Width, word.Height), isRtl); + if (FontVariant == CssConstants.SmallCaps) + g.DrawSmallCapString(word.Text, ActualFont, ActualFontForSmallCaps, GetSelectionForeBrush(), wordPoint, isRtl); + else + g.DrawString(word.Text, ActualFont, GetSelectionForeBrush(), wordPoint, new RSize(word.Width, word.Height), isRtl); } } else { - // g.DrawRectangle(HtmlContainer.Adapter.GetPen(RColor.Black), wordPoint.X, wordPoint.Y, word.Width - 1, word.Height - 1); - g.DrawString(word.Text, ActualFont, ActualColor, wordPoint, new RSize(word.Width, word.Height), isRtl); + if (FontVariant == CssConstants.SmallCaps) + g.DrawSmallCapString(word.Text, ActualFont, ActualFontForSmallCaps, ActualColor, wordPoint, isRtl); + else + //g.DrawRectangle(HtmlContainer.Adapter.GetPen(RColor.Black), wordPoint.X, wordPoint.Y, word.Width - 1, word.Height - 1); + g.DrawString(word.Text, ActualFont, ActualColor, wordPoint, new RSize(word.Width, word.Height), isRtl); } } } diff --git a/Source/HtmlRenderer/Core/Dom/CssBoxProperties.cs b/Source/HtmlRenderer/Core/Dom/CssBoxProperties.cs index 7abfb53ed..be4f56406 100644 --- a/Source/HtmlRenderer/Core/Dom/CssBoxProperties.cs +++ b/Source/HtmlRenderer/Core/Dom/CssBoxProperties.cs @@ -152,6 +152,7 @@ internal abstract class CssBoxProperties private RColor _actualBorderRightColor = RColor.Empty; private RColor _actualBackgroundColor = RColor.Empty; private RFont _actualFont; + private RFont _actualFontForSmallCaps; #endregion @@ -1328,11 +1329,25 @@ public RFont ActualFont } _actualFont = GetCachedFont(FontFamily, fsize, st); + + if (FontVariant == CssConstants.SmallCaps) + { + double fsizeForSmallCaps = CssValueParser.ParseLength(CssConstants.FakeSmallCapReduction_em, fsize, fsize, null, true, true); + _actualFontForSmallCaps = GetCachedFont(FontFamily, fsizeForSmallCaps, st); + } } return _actualFont; } } + /// + /// Gets the font that should be actually used to paint small-uppercase chars in the text of the box (small-caps variant) + /// + public RFont ActualFontForSmallCaps + { + get { return _actualFontForSmallCaps; } + } + protected abstract RFont GetCachedFont(string fontFamily, double fsize, RFontStyle st); /// @@ -1479,6 +1494,8 @@ protected void MeasureWordSpacing(RGraphics g) string len = RegexParserUtils.Search(RegexParserUtils.CssLength, WordSpacing); _actualWordSpacing += CssValueParser.ParseLength(len, 1, this); } + if (FontVariant == CssConstants.SmallCaps) + ActualFontForSmallCaps.GetWhitespaceWidth(g); // Set metrics of the font } } diff --git a/Source/HtmlRenderer/Core/Utils/CssConstants.cs b/Source/HtmlRenderer/Core/Utils/CssConstants.cs index 655849256..259783c24 100644 --- a/Source/HtmlRenderer/Core/Utils/CssConstants.cs +++ b/Source/HtmlRenderer/Core/Utils/CssConstants.cs @@ -82,6 +82,8 @@ internal static class CssConstants public const string Show = "show"; public const string Small = "small"; public const string Smaller = "smaller"; + public const string SmallCaps = "small-caps"; + public const string FakeSmallCapReduction_em = ".75em"; public const string Solid = "solid"; public const string Sub = "sub"; public const string Super = "super";