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

Measure text (v2) #48

Merged
merged 5 commits into from
Jan 6, 2017
Merged

Measure text (v2) #48

merged 5 commits into from
Jan 6, 2017

Conversation

tocsoft
Copy link
Contributor

@tocsoft tocsoft commented Jan 5, 2017

Ported all code over to the updated project structure.

Refactored the rendering and measuring away from the 'layout' code.

This should leave a clean place where all glyph layout can happen, newlines, tabs, wrapping, justification etc independently from rendering and measuring and should make testing the layout options simple to test.

fixes #42

Refactor renderer to support a cleaner architecture for measuring text and rendering it.

Add tests covering rendering and measuring
@tocsoft tocsoft mentioned this pull request Jan 5, 2017

internal const int FontToPixelDivisor = EmSquareSize * PointsPerInch;
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The risk with a Constants class is all kinds of weird things gets dumped here. Instead, lets have constants private in classes where needed. Perhaps even local to the function! The PointsPerInch does not feel like a constant either in the same way the EmSquare size is. I liked the EmSquare class with a Size constant. :-) But it could also go inside the Typeface class I guess?

@@ -4,6 +4,8 @@ namespace NRasterizer
{
public interface IGlyphRasterizer
{
int Resolution { get; }

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also feels slightly malplaced. It would be awesome if the IGlyphRasterizer was unit-agnostic. But then how would one pass the target resolution to the scaling code... Hmm, it makes a lot of sense. Let's do it like this. 👍

/// </value>
public float LineHeight { get; set; } = 1;
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newline at end of file

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering what the need for a newline at the end of a file is for?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing important, it's just annoying when I cat files, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no thats fine... don't mind doing it was just wondering... I'll try to make sure I add them in the future

/// <value>
/// The height of the line.
/// </value>
public float LineHeight { get; set; } = 1;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no support for this right now. Perhaps remove / comment out for now to be less confusing. Pretty annoying if you set this and nothing happens...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its needed for calculating the bounding box of the measured text... even 1 line as a height.


if (drawbox)
{
g.DrawRectangle(Pens.HotPink, new Rectangle(x, y, size.Width, size.Height));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 for HotPink

@@ -120,11 +134,17 @@ public static void Main(string[] args)
var rasterizerName = args[0];
var fontPath = new FileInfo(args[1]);
var target = new FileInfo(args[2]);
var text = args[3];
var text = args[3].Replace("\\n", "\n").Replace("\\t", "\t");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 This could even go instatic function in Program - ExpandWhitespace or UnescapeWhitespace to make it even clearer what it does.

var index = map.CharacterToGlyphIndex(charAsInt);

Assert.Equal(0, index);
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice test! Drop some newlines to make it even more readable 👍 Like so

var idDeltas = new ushort[] { (ushort) (65536 - charAsInt) };

var index = map.CharacterToGlyphIndex(charAsInt);

Assert.Equal(54, index);
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice test! Again, reducing newlines would make it even shorter and sweeter. 👍

}

}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newline.

public void BeginRead(int countourCount)
{
this.BeginReadCallCount++;
this.ContourCount = countourCount;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant this qualifier

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no problem i'll drop them (going to be hard jumping back and fourth between ImageSharp and this project, both have very different coding styles so I apologies now for when I constantly use the wrong style 😄 )

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see... np :-)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine to merge also, it's probably faster for me to go through smaller things like this and trailing newlines etc in one go afterwards.


public void CloseFigure()
{
this.CloseFigureCallCount++;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant this qualifier


public void Curve3(double p2x, double p2y, double x, double y)
{
Points.Add(new Point<double>(x, y));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant this qualifier

public int ContourCount { get; private set; }
public int EndReadCallCount { get; private set; }
public int FlushCallCount { get; private set; }
public List<Point<double>> Points { get; set; } = new List<Point<double>>();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not have a public setter, right?


public void EndRead()
{
this.EndReadCallCount++;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant this qualifier


public void Flush()
{
this.FlushCallCount++;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant this qualifier

Points.Add(new Point<double>(x, y));
}
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great fake class 👍

}
}
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a unit-test, but actually reads from disk etc. Those kinds of test are better covered by the "generate-sample" tests that leverage the NRasterizer.CLI program. But let's leave those in for now.


Assert.Equal(expectedWidth, size.Width);
Assert.Equal(expectedHeight, size.Height);
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, I literally love this test <3

Assert.Equal(fontsize * text.Length, size.Width);
Assert.Equal(fontsize, size.Height);
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome test again 👍


public ToPixelRasterizer(float x, float y, int multiplyer, int divider, IGlyphRasterizer inner)
public ToPixelRasterizer(int x, int y, int scalingFactor, IGlyphRasterizer inner)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's clearer if the divider is passed in as well, instead as grabbed as a constant. Le'ts keep it that way.

/// This is a struct because its super short lived and it prevents the need for an allocation for each character rendered.
/// This is internal as it will only ever be used by the <see cref="Renderer"/> and should not be used by external systems.
/// </remarks>
internal struct ToPixelRasterizer
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be shortlived, it should typically live together with the Renderer.

👍 for Internal

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So lets just do a normal class and drop remark.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it only lives for the life time of of the RenderGlyph(..) call you are newing up a new one each time for the new offsets.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's definitely not right. This should only be created once per DrawText, or even less. You don't have to fix that problem in this pr of course, but lets keep it a class for now. You can drop a TODO remarking this class is instantiated for each glyph which is not right.


public int Height;
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

public int bottom;
public int right;
}
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not introduced here, but no newline at end of file.

{
glyph = glyph,
bottom = lineHeight + yy,
right = glyphWidth + xx,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bottom right is never used, let's drop that point.

{
Width = ((right - left) * scalingFactor) / Constants.FontToPixelDivisor,
Height = ((bottom - top) * scalingFactor) / Constants.FontToPixelDivisor
};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect this to be void. Let's remove this return.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using the fact that it returns the size to enable drawing the bounding box in the CLI.

if it didn't return the size then I would need to call MeasureText and Render separately causing runs of the layout method duplicating that piece of work.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but isn't that's a great solution? Just call both Render and MeasureText. This is akin to System.Drawing and nothing strange. The double work is miniscule to say the least.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok... I'll drop returning the size for now, I might throw in another PR in the future if I find I actually need it in ImageSharp but tbh I can't think of a performance critical region that would need/use the returned size so dropped 👍

if (glyph.bottom > bottom)
{
bottom = glyph.bottom;
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove all these left/right bottom left comparisions. Not needed! :-)

int left = int.MaxValue;
int right = int.MinValue;

foreach (var glyph in glyphs)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be called layout rather than glyph, right?

public int bottom;
public int right;
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this struct. Let's remove it and instead iterate through glyphs and Points (top left) together somehow. If it turns out tricky, let's keep this class, but just have two memebers TopLeft point and the Glyph.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bottom right is required for finding the right most extent of the bounding box and thus the width and the bottom is the same for the height.

If I don't have this struct we would need to ask the typeface multiple times for the glyph and the advance width etc.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, let's keep it like this for now then.

@vidstige
Copy link
Owner

vidstige commented Jan 6, 2017

Awesome. Some comments, need ro rush now. Should be mergeable pretty soon. Thank you so much.

@vidstige
Copy link
Owner

vidstige commented Jan 6, 2017

Some smaller things I can also clean up afterwards.

@tocsoft tocsoft force-pushed the measure-text-clean branch from 36704a9 to efb75f0 Compare January 6, 2017 10:26
@tocsoft
Copy link
Contributor Author

tocsoft commented Jan 6, 2017

@vidstige think I've covered all your changes.

@vidstige vidstige merged commit 566c259 into vidstige:master Jan 6, 2017
@tocsoft tocsoft deleted the measure-text-clean branch January 6, 2017 17:50
@tocsoft
Copy link
Contributor Author

tocsoft commented Jan 6, 2017

🎉 yay

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 this pull request may close these issues.

Add the ability to measure a string without rendering it
2 participants