I'm trying to pull text from a PDF using iText7. I'm using the IEventListener to get all the parts of the page, though some of the text is rotated. I can find examples for how to insert rotated text into a PDF, but can't find anything about how I can tell if a given text segment is rotated.
Can anyone help ?
public void EventOccurred(IEventData data, EventType type)
{
PdfPart part = null;
switch (type)
{
case EventType.BEGIN_TEXT:
break;
case EventType.RENDER_TEXT:
part = new PdfTextPart(PageNumber, data as TextRenderInfo);
Parts.Add(part);
break;
case EventType.END_TEXT:
break;
case EventType.RENDER_IMAGE:
var imageData = data as ImageRenderInfo;
//this.HandleImage(imageData);
break;
case EventType.RENDER_PATH:
part = new PdfLinePart(PageNumber, data as PathRenderInfo);
Parts.Add(part);
break;
case EventType.CLIP_PATH_CHANGED:
break;
default:
break;
}
}
public PdfTextPart(Int32 pageNumber, TextRenderInfo info) : base(pageNumber)
{
Text = info.GetText();
var font = info.GetFont().GetFontProgram().GetFontNames();
Font = font.GetFontName();
if (font.IsItalic()) { this.IsItalic = true; }
if (font.IsBold()) { this.IsBold = true; }
if (font.IsUnderline()) { this.IsUnderline = true; }
}
TextRenderInfo has a base line. This base line is a LineSegment and as such has a start point and an end point. Now you merely have to determine the angle of the line between those two points.
I.e. for a TextRenderInfo info:
LineSegment baseline = info.GetBaseline();
Vector startPoint = baseline.GetStartPoint();
Vector endPoint = baseline.GetEndPoint();
Vector direction = endLocation.Subtract(startLocation);
double angle = Math.Atan2(direction.Get(Vector.I2), direction.Get(Vector.I1));
The result obviously is in radian measure.
You may additionally have to take into account the page rotation which (if I recall correctly) is not calculated into the coordinates above.
I'm going to draw a chart using c# in Windows Form Application. I need to draw a circle on line chart and show this data point value on a label which is nearest data point from the x-axis of the mouse pointer when mouse is moved over the chart area.
I write a code as follows......
private void Chart1_MouseMove(object sender, MouseEventArgs e)
{
HitTestResult result = Chart1.HitTest(e.X, e.Y);
DataPoint nearestPoint = null;
if (prevPosition!=null)
{
Chart1.Series[0].Points[prevPosition.PointIndex].MarkerStyle = MarkerStyle.None;
}
if (result.ChartElementType == ChartElementType.DataPoint)
{
string xValue = DateTime.FromOADate(Chart1.Series[0].Points[result.PointIndex].XValue).ToString("yyyy/MM/dd");
string yValue = Convert.ToString(Chart1.Series[0].Points[result.PointIndex].YValues[0]);
Chart1.Series[0].Points[result.PointIndex].MarkerStyle = MarkerStyle.Circle;
Chart1.Series[0].Points[result.PointIndex].MarkerSize = 7;
Chart1.Series[0].Points[result.PointIndex].MarkerColor = Color.Green;
label1.Text = "Date:" + xValue;
label2.Text = "Price:" + yValue;
prevPosition = result;
}
But this code shows the value and corresponding circle over line when mouse is moved near to the depicted line. When mouse is far away from line but within chart area it does not show the circle and value. I need to draw the circle over the line point nearest to the X-axis of the mouse pointer and show this data on a label
You can find the closest point measuring only the x-values or the y-values or measuring the absolute distances. Or you could simple output the values under the mouse cursor, no matter the points. For this last one see here!
For each of the 1st three options this should help:
Class level variables used to set and reset colors..:
DataPoint dpXaxis = null;
DataPoint dpYaxis = null;
DataPoint dpAbs = null;
And a list of points for keeping the pixel locations of all points:
List<Point> pixPoints = null;
The MouseMove event:
private void chart_MouseMove(object sender, MouseEventArgs e)
{
ChartArea ca = chart.ChartAreas[0];
Axis ax = ca.AxisX;
Axis ay = ca.AxisY;
Series s = chart.Series[0];
if (!s.Points.Any()) return; // no data, no action!
// option 1:
// the values at the mouse pointer:
double valx = ax.PixelPositionToValue(e.X);
double valy = ay.PixelPositionToValue(e.Y);
// the deltas on the x-axis (with index):
var ix = s.Points.Select((x, i) => new {delta = Math.Abs(x.XValue - valx), i})
.OrderBy(x => x.delta).First().i;
var dpx = s.Points[ix];
// option 2:
// the deltas on the y-axis (with index):
var iy = s.Points.Select((x, i) =>
new {delta = Math.Abs(x.YValues[0] - valy), i })
.OrderBy(x => x.delta).First().i;
var dpy = s.Points[iy];
// option 3:
// the absolute distances (with index):
var ind = pixPoints.Select((x, i) =>
new { delta = Math.Abs(x.X - e.X) + Math.Abs(x.Y - e.Y), i}).
OrderBy(x => x.delta).First().i;
// find index of smallest delta
var dpca = s.Points[ind];
// set/reset colors
if (dpXaxis != null) dpXaxis.Color = s.Color;
dpXaxis = dpx;
dpXaxis.Color = Color.LawnGreen;
// set/reset colors
if (dpYaxis != null) dpYaxis.Color = s.Color;
dpYaxis = dpy;
dpYaxis.Color = Color.Cyan;
if (dpAbs != null) dpAbs.Color = s.Color;
dpAbs = dpca;
dpAbs.Color = Color.Red;
}
To find the point closest in both directions you will need to either include the scales of the axes or, probably easier, create a List<PointF> from the DataPoints that hold the locations of the points in pixels. For this use the reverse axis functions. Then I calculate the deltas in a similar fashion as the Linq above.
The list gets filled/updated like so:
List<Point> getPixPoints(Series s, ChartArea ca)
{
List<Point> points = new List<Point>();
foreach (DataPoint dp in s.Points)
{
points.Add(new Point(
(int)ca.AxisX.ValueToPixelPosition(dp.XValue),
(int)ca.AxisY.ValueToPixelPosition(dp.YValues[0]) ));
}
return points;
}
Let's see it at work:
Doing an inheritance test program specifically to root out problems such as these.
The classes themselves aren't important, the problem is in the Main.
I tried "Shape shape = null" before the if statements but having "Circle shape" etc in the if statements threw errors.
Main:
string shapeType = "";
double side = 0;
while (true)
{
Console.WriteLine("What type of shape? - (cir/tri/sqr");
shapeType = Console.ReadLine();
Console.WriteLine("How long are the sides, or the radius?");
side = Convert.ToDouble(Console.ReadLine());
if (shapeType == "cir")
{
Circle shape = new Circle();
}
else if (shapeType == "tri")
{
Triangle shape = new Triangle();
}
else
{
Square shape = new Square();
}
// Code interacting with shapes.
}
You're declaring the shapes within the if blocks, which means that after that block ends it's unavailable.
You need to declare it before, and as a Shape:
Shape shape = null;
if (shapeType == "cir")
{
shape = new Circle();
}
else if (shapeType == "tri")
{
shape = new Triangle();
}
else
{
shape = new Square();
}
// … etc
I have the following code:
if (frame != null)
{
canvas.Children.Clear();
_bodies = new Body[frame.BodyFrameSource.BodyCount];
frame.GetAndRefreshBodyData(_bodies);
foreach (var body in _bodies)
{
if (body != null)
{
if (body.IsTracked)
{
// choose which hand to track
string whichHand = "right"; //change to "left" in order to track left hand
Joint handRight = body.Joints[JointType.HandRight];
if (whichHand.Equals("right"))
{
string rightHandState = "-"; //find the right hand state
switch (body.HandRightState)
{
case HandState.Open:
rightHandState = "Open";
break;
case HandState.Closed:
rightHandState = "Closed";
break;
default:
break;
}
canvas.DrawPoint(handRight, _sensor.CoordinateMapper);
}
Here is my DrawPoint code:
public static void DrawPoint(this Canvas canvas, Joint joint, CoordinateMapper mapper)
{
if (joint.TrackingState == TrackingState.NotTracked) return;
Point point = joint.Scale(mapper);
Ellipse ellipse = new Ellipse
{
Width = 20,
Height = 20,
Fill = new SolidColorBrush(Colors.LightBlue)
};
Canvas.SetLeft(ellipse, point.X - ellipse.Width / 2);
Canvas.SetTop(ellipse, point.Y - ellipse.Height / 2);
canvas.Children.Add(ellipse);
}
And Scale:
public static Point Scale(this Joint joint, CoordinateMapper mapper)
{
Point point = new Point();
ColorSpacePoint colorPoint = mapper.MapCameraPointToColorSpace(joint.Position);
point.X *= float.IsInfinity(colorPoint.X) ? 0.0 : colorPoint.X;
point.Y *= float.IsInfinity(colorPoint.Y) ? 0.0 : colorPoint.Y;
return point;
}
The problem that I have is that while the circle does get drawn, it is not drawn over my hand. Instead it stays in the top left corner (0,0), so I'm guessing that it's not getting updated properly. Could anybody tell me what is going on or what the problem is? I would like it to be in the center of my hand (which is being tracked fine because the state of the hand gets updated immediately) and follow my hand as I move it.
The _sensor is my Kinect sensor.
Intro
I noticed an issue while implementing clipping (see this).
It looks like UIElement.Clip still render invisible parts
Rendering relatively small geometry (lines to only fill 1920x1200 area ~ 2000 vertical lines) take a lot of time. When using Clip and moving that geometry offscreen (so that clipping should remove significant part of it) it is still take same time (around 1 sec).
Ok, I found what using Geometry.Combine will do a clip (render time is reduced proportionally to removed after clipping geometry). Perfect!
Problem
Geometry.Combine doesn't work with non-closed geometry properly. It produce closed geometry. And it looks ugly, connecting first and last point:
Question
How can I perform clipping (reducing amount of geometry to be rendered) for non-closed figures?
Edit
Here is geometry before (small peace of shown on picture)
{M0;50L0;50L1;53,1395259764657L2;56,2666616782152L3;59,3690657292862L4;62,4344943582427L5;65,4508497187474L6;68,4062276342339L7;71,2889645782536L8; ...
and after
{F1M54,9999923706055;34,5491371154785L53,9999885559082;37,5655174255371 53,0000114440918;40,6309471130371 52,0000076293945;43,7333335876465 ...
Notice change at beginning, was M 0;50 L ..., become F 1 M 55;34 L ...
F1 means NonZero filling
Rule that determines whether a point is in the fill region of the path by drawing a ray from that point to infinity in any direction and then examining the places where a segment of the shape crosses the ray. Starting with a count of zero, add one each time a segment crosses the ray from left to right and subtract one each time a path segment crosses the ray from right to left. After counting the crossings, if the result is zero then the point is outside the path. Otherwise, it is inside.
And I have absolutely no clue what that means. But maybe it is important?
Edit
I should have been looking at the end of strings. There is z at the end of Path.Data, which means figure is closed.
Strangely enough, trying to remove z (by using Geometry.ToString()/Geometry.Parse() combo) doesn't works. After some investigation I found what Combine produces physically enclosing figures (commands L x;y, where x;y is the leftmost point). And the worst thing is what it's not always the last point, so simply removing last L x;y before parsing doesn't works either. =(
Edit
Sample to demonstrate problem:
Xaml:
<Path x:Name="path" Stroke="Red"/>
Code:
var geometry1 = new RectangleGeometry(new Rect(100, 100, 100, 100));
var geometry2 = new PathGeometry(new[] { new PathFigure(new Point(0,0), new[] {
new LineSegment(new Point(300, 300), true),
new LineSegment(new Point(300, 0), true),
}, false) });
//path.Data = geometry1;
//path.Data = geometry2;
//path.Data = Geometry.Combine(geometry1, geometry2, GeometryCombineMode.Intersect, null);
Pictures of geometry1 and geometry2:
Resulting Combine:
As you can see 2 lines become 3 after clipping, debugging proves it:
{F1M100;100L200;100 200;200 100;100z}
Notice, it's not only z, but also 100;100 point at the end, connecting starting point.
I attempted to implement a clipping solutions for non closed geometry based on this line intersection algorithm
Code
public static PathGeometry ClipGeometry(PathGeometry geom, Rect clipRect)
{
PathGeometry clipped = new PathGeometry();
foreach (var fig in geom.Figures)
{
PathSegmentCollection segments = new PathSegmentCollection();
Point lastPoint = fig.StartPoint;
foreach (LineSegment seg in fig.Segments)
{
List<Point> points;
if (LineIntersectsRect(lastPoint, seg.Point, clipRect, out points))
{
LineSegment newSeg = new LineSegment(points[1], true);
PathFigure newFig = new PathFigure(points[0], new[] { newSeg }, false);
clipped.Figures.Add(newFig);
}
lastPoint = seg.Point;
}
}
return clipped;
}
static bool LineIntersectsRect(Point lineStart, Point lineEnd, Rect rect, out List<Point> points)
{
points = new List<Point>();
if (rect.Contains(lineStart) && rect.Contains(lineEnd))
{
points.Add(lineStart);
points.Add(lineEnd);
return true;
}
Point outPoint;
if (Intersects(lineStart, lineEnd, rect.TopLeft, rect.TopRight, out outPoint))
{
points.Add(outPoint);
}
if (Intersects(lineStart, lineEnd, rect.BottomLeft, rect.BottomRight, out outPoint))
{
points.Add(outPoint);
}
if (Intersects(lineStart, lineEnd, rect.TopLeft, rect.BottomLeft, out outPoint))
{
points.Add(outPoint);
}
if (Intersects(lineStart, lineEnd, rect.TopRight, rect.BottomRight, out outPoint))
{
points.Add(outPoint);
}
if (points.Count == 1)
{
if (rect.Contains(lineStart))
points.Add(lineStart);
else
points.Add(lineEnd);
}
return points.Count > 0;
}
static bool Intersects(Point a1, Point a2, Point b1, Point b2, out Point intersection)
{
intersection = new Point(0, 0);
Vector b = a2 - a1;
Vector d = b2 - b1;
double bDotDPerp = b.X * d.Y - b.Y * d.X;
if (bDotDPerp == 0)
return false;
Vector c = b1 - a1;
double t = (c.X * d.Y - c.Y * d.X) / bDotDPerp;
if (t < 0 || t > 1)
return false;
double u = (c.X * b.Y - c.Y * b.X) / bDotDPerp;
if (u < 0 || u > 1)
return false;
intersection = a1 + t * b;
return true;
}
currently solution works for line based geometry, other types perhaps need to be included if needed.
test xaml
<UniformGrid Columns="2"
Margin="250,250,0,0">
<Grid>
<Path x:Name="pathClip"
Fill="#22ff0000" />
<Path x:Name="path"
Stroke="Black" />
</Grid>
<Path x:Name="path2"
Margin="100,0,0,0"
Stroke="Black" />
</UniformGrid>
test code 1
void test()
{
var geometry = new PathGeometry(new[] { new PathFigure(new Point(0,0), new[] {
new LineSegment(new Point(300, 300), true),
new LineSegment(new Point(300, 0), true),
}, false) });
Rect clipRect= new Rect(10, 10, 180, 180);
path.Data = ClipGeometry(geometry, clipRect);
path2.Data = geometry;
pathClip.Data = new RectangleGeometry(clipRect);
}
result
test code 2
void test()
{
var radius = 1.0;
var figures = new List<LineSegment>();
for (int i = 0; i < 2000; i++, radius += 0.1)
{
var segment = new LineSegment(new Point(radius * Math.Sin(i), radius * Math.Cos(i)), true);
segment.Freeze();
figures.Add(segment);
}
var geometry = new PathGeometry(new[] { new PathFigure(figures[0].Point, figures, false) });
Rect clipRect= new Rect(10, 10, 180, 180);
path.Data = ClipGeometry(geometry, clipRect);
path2.Data = geometry;
pathClip.Data = new RectangleGeometry(clipRect);
}
result
give it a try and see how close it is.
If i am right your mainquestion is:"How do i improve the performance of drawing many shapes?"
To get this working you have to understand Geometry Math.
Geometric objects can only merged/combined if they connect or overlap. And there is a big difference between path-geometry and shape-geometry.
As example if two circle overlap you can combine them in WPF
to get the overlapping region: Intersect
to get the difference: Xor
to get the combined surface: Union
to get the difference of only one shape: Exclude
For path-geometry it's a little different, because a path has no surface a path cannot Intersect | Xor | Union | Exclude another path or shape.
But WPF thinks you just forgot to close the path and is doing that for you, which result in the given result in your question.
so to achieve a performanceboost you have to filter all the geometry first for shapes and paths.
foreach(Shape geometryObj in ControlsOrWhatEver)
{
if(geometryObj is Line || geometryObj is Path || geometryObj is Polypath)
{
pathList.Add(geometryObj);
}
else
{
shapeList.Add(geometryObj);
}
}
for the shapeList you can use Geometry.Combine, but for the pathList you have to do some other work. You have to check if the connect at some point, doesnt matter if beginningPoint, endPoint or somwhere inbetween.
If you have done that you can Merge not Combine the path by yourself like:
public Polyline mergePaths(Shape line1, Shape line2)
{
if(!checkLineType(line1) || !checkLineType(line2))
{
return null;
}
if(hitTest(line1, line2))
{
//here you have to do some math to determine the overlapping points
//on these points you can do something like this:
foreach(Point p in Overlapping Points)
{
//add the first line until p then add line2 and go on to add lin 1 until another p
}
}
else
{
return null;
}
}