diff --git a/Source/Demo/Common/Samples/02.Text.htm b/Source/Demo/Common/Samples/02.Text.htm index b7aee3fa9..430946f10 100644 --- a/Source/Demo/Common/Samples/02.Text.htm +++ b/Source/Demo/Common/Samples/02.Text.htm @@ -2,11 +2,11 @@ Text - @@ -24,6 +24,7 @@

Formatting Colors
  • Back colors, Back colors, Back colors
  • 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 7f8a07eb7..f6c89fdaa 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 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 9af8a9790..e28af6261 100644 --- a/Source/HtmlRenderer.PdfSharp/Adapters/GraphicsAdapter.cs +++ b/Source/HtmlRenderer.PdfSharp/Adapters/GraphicsAdapter.cs @@ -107,7 +107,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); @@ -124,6 +124,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 d61bef4ca..5d24c3b55 100644 --- a/Source/HtmlRenderer.WPF/Adapters/GraphicsAdapter.cs +++ b/Source/HtmlRenderer.WPF/Adapters/GraphicsAdapter.cs @@ -212,6 +212,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 040d14837..5c97b25ef 100644 --- a/Source/HtmlRenderer/Adapters/RGraphics.cs +++ b/Source/HtmlRenderer/Adapters/RGraphics.cs @@ -145,6 +145,42 @@ public RRect GetClip() /// 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. @@ -167,6 +203,17 @@ public RRect GetClip() /// 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 edd9d6f0a..44a7ac95f 100644 --- a/Source/HtmlRenderer/Core/Dom/CssBox.cs +++ b/Source/HtmlRenderer/Core/Dom/CssBox.cs @@ -661,6 +661,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; } @@ -1271,19 +1274,31 @@ private void PaintWords(RGraphics g, RPoint offset) if (HtmlContainer.SelectionForeColor != RColor.Empty && (word.SelectedStartOffset > 0 || word.SelectedEndIndexOffset > -1)) { g.PushClipExclude(rect); + 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); + 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); g.PopClip(); } else { + 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 { + 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); } @@ -1307,7 +1322,7 @@ protected void PaintDecoration(RGraphics g, RRect rectangle, bool isFirst, bool double y = 0f; if (TextDecoration == CssConstants.Underline) { - y = Math.Round(rectangle.Top + ActualFont.UnderlineOffset); + y = rectangle.Top + ActualFont.UnderlineOffset; } else if (TextDecoration == CssConstants.LineThrough) { diff --git a/Source/HtmlRenderer/Core/Dom/CssBoxProperties.cs b/Source/HtmlRenderer/Core/Dom/CssBoxProperties.cs index 3acc6ed41..58cf846b6 100644 --- a/Source/HtmlRenderer/Core/Dom/CssBoxProperties.cs +++ b/Source/HtmlRenderer/Core/Dom/CssBoxProperties.cs @@ -150,6 +150,7 @@ internal abstract class CssBoxProperties private RColor _actualBorderRightColor = RColor.Empty; private RColor _actualBackgroundColor = RColor.Empty; private RFont _actualFont; + private RFont _actualFontForSmallCaps; #endregion @@ -1283,11 +1284,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); /// @@ -1434,6 +1449,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 352406244..d28d8cb9b 100644 --- a/Source/HtmlRenderer/Core/Utils/CssConstants.cs +++ b/Source/HtmlRenderer/Core/Utils/CssConstants.cs @@ -80,6 +80,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";