Skip to content

Commit

Permalink
Merge pull request #251 from SixLabors/js/text-runs
Browse files Browse the repository at this point in the history
Support text runs.
  • Loading branch information
JimBobSquarePants authored Mar 26, 2022
2 parents 817b75c + 7b3ec08 commit 3c2e86d
Show file tree
Hide file tree
Showing 41 changed files with 2,002 additions and 711 deletions.
38 changes: 33 additions & 5 deletions samples/DrawWithImageSharp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Numerics;
using System.Text;
using DrawWithImageSharp;
using SixLabors.Fonts.Unicode;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing;
using SixLabors.ImageSharp.Drawing.Processing;
Expand All @@ -33,22 +34,43 @@ public static void Main(string[] args)
FontFamily colorEmoji = fonts.Add(@"Fonts\Twemoji Mozilla.ttf");
FontFamily font2 = fonts.Add(@"Fonts\OpenSans-Regular.ttf");
FontFamily sunflower = fonts.Add(@"Fonts\Sunflower-Medium.ttf");
FontFamily bugzilla = fonts.Add(@"Fonts\me_quran_volt_newmet.ttf");

#if OS_WINDOWS
FontFamily emojiFont = SystemFonts.Get("Segoe UI Emoji");
FontFamily uiFont = SystemFonts.Get("Segoe UI");
FontFamily arabicFont = SystemFonts.Get("Dubai");

FontFamily tahoma = SystemFonts.Get("Tahoma");
var textRuns = new List<TextRun>
{
new TextRun { Start = 4, End = 9, Font = uiFont.CreateFont(72, FontStyle.Bold), TextAttributes = TextAttributes.Superscript, TextDecorations = TextDecorations.Underline | TextDecorations.Strikeout | TextDecorations.Overline },
new TextRun { Start = 26, End = 30, Font = uiFont.CreateFont(72, FontStyle.Italic), TextAttributes = TextAttributes.Subscript, TextDecorations = TextDecorations.Strikeout | TextDecorations.Underline | TextDecorations.Overline }
};

RenderText(uiFont, "The quick brown fox jumps over the lazy dog", pointSize: 72, textRuns: textRuns);

string arabic = "بِسْمِ ٱللَّهِ ٱلرَّحْمَٟنِ ٱلرَّحِيمِ";
textRuns = new List<TextRun>
{
new TextRun { Start = 0, End = CodePoint.GetCodePointCount(arabic), TextDecorations = TextDecorations.Underline }
};

RenderText(uiFont, arabic, pointSize: 72, textRuns: textRuns);

textRuns = new List<TextRun>
{
new TextRun { Start = 0, End = CodePoint.GetCodePointCount(arabic), TextDecorations = TextDecorations.Underline }
};
RenderText(bugzilla, arabic, pointSize: 72, textRuns: textRuns);

RenderText(font2, "\uFB01", pointSize: 11.25F);
RenderText(fontWoff2, "\uFB01", pointSize: 11.25F);
RenderText(tahoma, "p", pointSize: 11.25F);
RenderText(tahoma, "Lorem ipsum dolor sit amet", pointSize: 11.25F);
RenderText(uiFont, "Soft\u00ADHyphen", pointSize: 72);
FontFamily bugzilla = fonts.Add(@"Fonts\me_quran_volt_newmet.ttf");

RenderText(uiFont, "Soft\u00ADHyphen", pointSize: 72);
RenderText(bugzilla, "بِسْمِ ٱللَّهِ ٱلرَّحْمَٟنِ ٱلرَّحِيمِ", pointSize: 72);

RenderText(uiFont, "first\n\n\n\nl", pointSize: 20, fallbackFonts: new[] { font2 });

Expand Down Expand Up @@ -182,12 +204,18 @@ public static void RenderText(TextOptions options, string text)
SaveImage(options, text, (int)size.Width, (int)size.Height, options.Font.Name, text + ".png");
}

public static void RenderText(FontFamily font, string text, float pointSize = 12, IEnumerable<FontFamily> fallbackFonts = null)
public static void RenderText(
FontFamily font,
string text,
float pointSize = 12,
IEnumerable<FontFamily> fallbackFonts = null,
IEnumerable<TextRun> textRuns = null)
=> RenderText(
new TextOptions(new Font(font, pointSize))
{
WrappingLength = 400,
FallbackFontFamilies = fallbackFonts?.ToArray()
// WrappingLength = 400,
FallbackFontFamilies = fallbackFonts?.ToArray(),
TextRuns = textRuns?.ToArray()
},
text);

Expand Down
9 changes: 3 additions & 6 deletions src/SixLabors.Fonts/BigEndianBinaryReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,17 +112,14 @@ public TEnum ReadInt16<TEnum>()
public ushort ReadUFWORD() => this.ReadUInt16();

/// <summary>
/// Reads a 32-bit signed integer from the stream.
/// Reads a fixed 32-bit value from the stream.
/// 4 bytes are read.
/// </summary>
/// <returns>The 32-bit integer read.</returns>
/// <returns>The 32-bit value read.</returns>
public float ReadFixed()
{
this.ReadInternal(this.buffer, 4);

int value = BinaryPrimitives.ReadInt32BigEndian(this.buffer);

return Unsafe.As<int, float>(ref value);
return BinaryPrimitives.ReadInt32BigEndian(this.buffer) / 65536F;
}

/// <summary>
Expand Down
39 changes: 39 additions & 0 deletions src/SixLabors.Fonts/FileFontMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,45 @@ internal FileFontMetrics(FontDescription description, string path, long offset)
/// <inheritdoc/>
public override short AdvanceHeightMax => this.metrics.Value.AdvanceHeightMax;

/// <inheritdoc/>
public override short SubscriptXSize => this.metrics.Value.SubscriptXSize;

/// <inheritdoc/>
public override short SubscriptYSize => this.metrics.Value.SubscriptYSize;

/// <inheritdoc/>
public override short SubscriptXOffset => this.metrics.Value.SubscriptXOffset;

/// <inheritdoc/>
public override short SubscriptYOffset => this.metrics.Value.SubscriptYOffset;

/// <inheritdoc/>
public override short SuperscriptXSize => this.metrics.Value.SuperscriptXSize;

/// <inheritdoc/>
public override short SuperscriptYSize => this.metrics.Value.SuperscriptYSize;

/// <inheritdoc/>
public override short SuperscriptXOffset => this.metrics.Value.SuperscriptXOffset;

/// <inheritdoc/>
public override short SuperscriptYOffset => this.metrics.Value.SuperscriptYOffset;

/// <inheritdoc/>
public override short StrikeoutSize => this.metrics.Value.StrikeoutSize;

/// <inheritdoc/>
public override short StrikeoutPosition => this.metrics.Value.StrikeoutPosition;

/// <inheritdoc/>
public override short UnderlinePosition => this.metrics.Value.UnderlinePosition;

/// <inheritdoc/>
public override short UnderlineThickness => this.metrics.Value.UnderlineThickness;

/// <inheritdoc/>
public override float ItalicAngle => this.metrics.Value.ItalicAngle;

/// <inheritdoc/>
internal override bool TryGetGlyphId(CodePoint codePoint, out ushort glyphId)
=> this.metrics.Value.TryGetGlyphId(codePoint, out glyphId);
Expand Down
13 changes: 12 additions & 1 deletion src/SixLabors.Fonts/Font.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,21 @@ public bool TryGetPath([NotNullWhen(true)] out string? path)
/// <param name="support">Options for enabling color font support during layout and rendering.</param>
/// <returns>Returns the glyph</returns>
public IEnumerable<Glyph> GetGlyphs(CodePoint codePoint, ColorFontSupport support)
=> this.GetGlyphs(codePoint, TextAttributes.None, support);

/// <summary>
/// Gets the glyphs for the given codepoint.
/// </summary>
/// <param name="codePoint">The code point of the character.</param>
/// <param name="textAttributes">The text attributes to apply to the glyphs.</param>
/// <param name="support">Options for enabling color font support during layout and rendering.</param>
/// <returns>Returns the glyph</returns>
public IEnumerable<Glyph> GetGlyphs(CodePoint codePoint, TextAttributes textAttributes, ColorFontSupport support)
{
TextRun textRun = new() { Start = 0, End = 1, Font = this, TextAttributes = textAttributes };
foreach (GlyphMetrics metrics in this.FontMetrics.GetGlyphMetrics(codePoint, support))
{
yield return new(metrics, this.Size);
yield return new(GlyphMetrics.CloneForRendering(metrics, textRun, codePoint), this.Size);
}
}

Expand Down
66 changes: 66 additions & 0 deletions src/SixLabors.Fonts/FontMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,72 @@ internal FontMetrics()
/// </summary>
public abstract short AdvanceHeightMax { get; }

/// <summary>
/// Gets the recommended horizontal size in font design units for subscripts for this font.
/// </summary>
public abstract short SubscriptXSize { get; }

/// <summary>
/// Gets the recommended vertical size in font design units for subscripts for this font.
/// </summary>
public abstract short SubscriptYSize { get; }

/// <summary>
/// Gets the recommended horizontal offset in font design units for subscripts for this font.
/// </summary>
public abstract short SubscriptXOffset { get; }

/// <summary>
/// Gets the recommended vertical offset in font design units for subscripts for this font.
/// </summary>
public abstract short SubscriptYOffset { get; }

/// <summary>
/// Gets the recommended horizontal size in font design units for superscripts for this font.
/// </summary>
public abstract short SuperscriptXSize { get; }

/// <summary>
/// Gets the recommended vertical size in font design units for superscripts for this font.
/// </summary>
public abstract short SuperscriptYSize { get; }

/// <summary>
/// Gets the recommended horizontal offset in font design units for superscripts for this font.
/// </summary>
public abstract short SuperscriptXOffset { get; }

/// <summary>
/// Gets the recommended vertical offset in font design units for superscripts for this font.
/// </summary>
public abstract short SuperscriptYOffset { get; }

/// <summary>
/// Gets thickness of the strikeout stroke in font design units.
/// </summary>
public abstract short StrikeoutSize { get; }

/// <summary>
/// Gets the position of the top of the strikeout stroke relative to the baseline in font design units.
/// </summary>
public abstract short StrikeoutPosition { get; }

/// <summary>
/// Gets the suggested distance of the top of the underline from the baseline (negative values indicate below baseline).
/// </summary>
public abstract short UnderlinePosition { get; }

/// <summary>
/// Gets the suggested values for the underline thickness. In general, the underline thickness should match the thickness of
/// the underscore character (U+005F LOW LINE), and should also match the strikeout thickness, which is specified in the OS/2 table.
/// </summary>
public abstract short UnderlineThickness { get; }

/// <summary>
/// Gets the italic angle in counter-clockwise degrees from the vertical. Zero for upright text, negative for text that leans to the right (forward).
/// </summary>
public abstract float ItalicAngle { get; }

/// <summary>
/// Gets the specified glyph id matching the codepoint.
/// </summary>
Expand Down
73 changes: 38 additions & 35 deletions src/SixLabors.Fonts/GlyphLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.

using System.Numerics;
using System.Text;
using SixLabors.Fonts.Unicode;

namespace SixLabors.Fonts
Expand All @@ -15,14 +14,21 @@ internal readonly struct GlyphLayout
internal GlyphLayout(
Glyph glyph,
Vector2 location,
float ascender,
float descender,
float linegap,
float lineHeight,
float width,
float height,
float lineHeight,
bool isStartOfLine)
{
this.LineHeight = lineHeight;
this.Glyph = glyph;
this.CodePoint = glyph.GlyphMetrics.CodePoint;
this.Location = location;
this.Ascender = ascender;
this.Descender = descender;
this.LineGap = linegap;
this.LineHeight = lineHeight;
this.Width = width;
this.Height = height;
this.IsStartOfLine = isStartOfLine;
Expand All @@ -33,26 +39,46 @@ internal GlyphLayout(
/// </summary>
public Glyph Glyph { get; }

/// <summary>
/// Gets the codepoint represented by this glyph.
/// </summary>
public CodePoint CodePoint { get; }

/// <summary>
/// Gets the location.
/// </summary>
public Vector2 Location { get; }

/// <summary>
/// Gets the width.
/// Gets the ascender
/// </summary>
public float Width { get; }
public float Ascender { get; }

/// <summary>
/// Gets the height.
/// Gets the ascender
/// </summary>
public float Height { get; }
public float Descender { get; }

/// <summary>
/// Gets the lie gap
/// </summary>
public float LineGap { get; }

/// <summary>
/// Gets the line height of the glyph.
/// </summary>
public float LineHeight { get; }

/// <summary>
/// Gets the width.
/// </summary>
public float Width { get; }

/// <summary>
/// Gets the height.
/// </summary>
public float Height { get; }

/// <summary>
/// Gets a value indicating whether this glyph is the first glyph on a new line.
/// </summary>
Expand All @@ -62,7 +88,7 @@ internal GlyphLayout(
/// Gets a value indicating whether the glyph represents a whitespace character.
/// </summary>
/// <returns>The <see cref="bool"/>.</returns>
public bool IsWhiteSpace() => CodePoint.IsWhiteSpace(this.Glyph.GlyphMetrics.CodePoint);
public bool IsWhiteSpace() => CodePoint.IsWhiteSpace(this.CodePoint);

internal FontRectangle BoundingBox(float dpi)
{
Expand All @@ -79,33 +105,10 @@ internal FontRectangle BoundingBox(float dpi)
/// <inheritdoc/>
public override string ToString()
{
var sb = new StringBuilder();
if (this.IsStartOfLine)
{
sb.Append('@');
sb.Append(' ');
}

if (this.IsWhiteSpace())
{
sb.Append('!');
}

sb.Append('\'');
sb.Append(this.Glyph.GlyphMetrics.CodePoint.ToDebuggerDisplay());

sb.Append('\'');
sb.Append(' ');

sb.Append(this.Location.X);
sb.Append(',');
sb.Append(this.Location.Y);
sb.Append(' ');
sb.Append(this.Width);
sb.Append('x');
sb.Append(this.Height);

return sb.ToString();
string s = this.IsStartOfLine ? "@ " : string.Empty;
string ws = this.IsWhiteSpace() ? "!" : string.Empty;
Vector2 l = this.Location;
return $"{s}{ws}{this.CodePoint.ToDebuggerDisplay()} {l.X},{l.Y} {this.Width}x{this.Height}";
}
}
}
Loading

0 comments on commit 3c2e86d

Please sign in to comment.