Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rendering engine should be able to support spans of characters with different formats #10

Closed
tocsoft opened this issue Feb 7, 2017 · 5 comments · Fixed by #251
Closed

Comments

@tocsoft
Copy link
Member

tocsoft commented Feb 7, 2017

Provide a mechanisum for marking up spans of the supplied text with different formats allowing for simple rich text support

i.e. The quick brown fox jumps over the lazy dog
would use the below proposed font style

new SpanningFontStyle(
    new Span(5, 9){ bold = true; },
    new Span(28,31){ italic = true; },
);
@JimBobSquarePants
Copy link
Member

JimBobSquarePants commented Mar 1, 2022

@tocsoft I think I can implement this once we have a design.

Here's an early bash...

I would add a collection of a type to TextOptions

public struct TextRun
{
    public uint Start; // (inclusive, measured in codepoints)
    public uint End; // (exclusive, measured in codepoints)
    public Font? Font; // Can be used to override the default font.
    TextAttributes Attributes;
}
public class TextAttributes : List<TextAttribute>
{
}
public enum TextAttribute
{
    Underline,
    Strikethough,
  // Others?...
  // Subscript,
  // Superscript,
}

Questions.

  • What about line styles? Do we use a float array based pattern?
  • What about color? Fonts is not aware of color types.

@tocsoft
Copy link
Member Author

tocsoft commented Mar 1, 2022

hmm... with TextAttribute is an enum we could just use flagged enum for that rather then a list, but on the flip side flagged enums are not the most discoverable APIs.

This would be significantly simpler design if we hadn't moved TextOptions into Fonts because it would have been as simple as add a object property to TextRun that we could have passed back to Drawing via the IGlyphRenderer to allow Drawing to actually pass along an Brush/Pen combo along with the run without Fonts being any the wiser.

We can still do that but it if we still had the separate RendererOptions/TextOptions we could strongly type a version of TextRun/TextOptions on the Drawing side to specify the Brush/Pen per run and allow the fonts side to transparently forward that along in an object field without needing any knowledge of it. With TextOptions being in the Fonts lib we will end up wanting to add all sorts of generics mess to ensure that users (when creating TextRuns) can't pass along the wrong data the renderer can't process.

As a side note/follow on item we might want to add a simple mark up language option/extension too, but that should probably be handled on the Drawing side having it responsible for converting the mark up into text and Runs.

@JimBobSquarePants
Copy link
Member

JimBobSquarePants commented Mar 1, 2022

TextAttribute is an enum we could just use flagged enum

I actually looked at that first and am still considering it. HasFlag might be better to use when I'm doing the parsing internally. I'll experiment during implementation and see what's best.

With TextOptions being in the Fonts lib we will end up wanting to add all sorts of generics mess to ensure that users (when creating TextRuns) can't pass along the wrong data the renderer can't process.

One way to do it while avoiding generics and keeping it strongly typed is to take advantage of the covariance capabilities of IEnumerable<T>.

If we change all our methods currently accepting TextOptions in Fonts to accept ITextOptions interface and all the methods in Drawing to accept the explicit TextDrawingOptions we could organize the code like the following:

public class TextRun
{
	public uint Start; // (inclusive, measured in codepoints)
	public uint End; // (exclusive, measured in codepoints)
	public Font Font; // Can be used to override the default font.
	public TextAttributes Attributes;
}

public class TextAttributes : List<TextAttribute>
{
}

public enum TextAttribute
{
	Underline,
	Strikethough,
	Subscript,
	Superscript,
}

public interface ITextOptions
{
	// All our preexisting text options

	IEnumerable<TextRun> TextRuns { get; set; }
}

public class TextOptions : ITextOptions
{
	// All our preexisting text options

	public IEnumerable<TextRun> TextRuns { get; set; }
}

public class TextDrawingRun : TextRun
{
	public IPen Pen { get; set; }

	public IBrush Brush { get; set; }
}

public class TextDrawingOptions : ITextOptions
{
	public IEnumerable<TextDrawingRun> TextRuns
	{

		get { return (IEnumerable<TextDrawingRun>)((ITextOptions)this).TextRuns; }


		set { ((ITextOptions)this).TextRuns = value; }
	}

	IEnumerable<TextRun> ITextOptions.TextRuns { get; set; }
}

@tocsoft
Copy link
Member Author

tocsoft commented Mar 1, 2022

Yeah that looks generally looks good to me... we could do it without the interface too just by inheriting TextDrawingOptions from TextOptions ideally the class we would want to end up with Drawing using the class TextOptions however to make code transition easier... it would just mean Renaming the TextOptions base class inside Fonts to RenderOptions/TextShaperOptions/(or something else) and then inherit from the base class in Drawing calling it TextOptions allowing us to shadow the TextRuns property with IEnumerable<TextDrawingOptions>

Something more like this;

New APIs inside Fonts

public class TextRun
{
    public uint Start; // (inclusive, measured in codepoints)
    public uint End; // (exclusive, measured in codepoints)
    public Font Font; // Can be used to override the default font.
    public TextAttributes Attributes;
}

public class TextAttributes : List<TextAttribute>
{
}

public enum TextAttribute
{
    Underline,
    Strikethough,
    Subscript,
    Superscript,
}

Altered Apis in Fonts

- public sealed class TextOptions
+ public class TextShaperOptions
{
    // All our pre-existing text options
+    public IEnumerable<TextRun> TextRuns { get; set; }

    // for the default case where there is no matching
+    public TextAttributes Attributes;
}

New APi inside Drawing

public class TextDrawingRun : TextRun
{
    public IPen Pen { get; set; }
    public IBrush Brush { get; set; }
}

public class TextOptions : TextShapingOptions
{
    // shadow the TextShapingOptions Runs
    // The glyph render would not actually be depended on this property directly this is only for authoring
    // glyph renderer will `as` cast each Run as returned during rendering (IGlypheRenderer) to determine
    // the altered rendering data to use
    public new IEnumerable<TextDrawingRun> TextRuns
    {
        get { return (IEnumerable<TextDrawingRun>)base.TextRuns; }
        set { base.TextRuns = value; }
    }
}

@JimBobSquarePants
Copy link
Member

JimBobSquarePants commented Mar 1, 2022

Ah yes! That's the way to do it!

One thing I can't decide is whether to stick with the CodePoint count as my Start and End properties of a TextRun or whether we should stick with char index.

I'd have to design something to make identifying CodePoint indices super easily in a string for the former but the latter could lead to individuals choosing the wrong char index splitting graphemes.

Edit - CodePont definitely seems a lot more sensible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants