How to get XYZ data of room center? - c#

I am trying to place an element into the center of several rooms. So far I have achieved something similar by using the Location Point which has placed the element close to the center but not exact.
I attempted to fix this by using two methods that I believed would help accomplish this task, GetElementCenter and GetRoomCenter but when I run the plugin, nothing happens.
class Class2
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
//Get access to Revit command data, user interface and document
UIApplication uiapp = commandData.Application;
UIDocument uidoc = uiapp.ActiveUIDocument;
Document doc = uidoc.Document;
//Collect all rooms
FilteredElementCollector roomCollector = new FilteredElementCollector(doc).OfClass(typeof(SpatialElement));
// Collect element
FilteredElementCollector element = new FilteredElementCollector(doc).OfClass(typeof(FamilySymbol)).OfCategory(BuiltInCategory.OST_Cameras);
//Get symbol
FamilySymbol elementSym = element.FirstElement() as FamilySymbol;
using (Transaction tx = new Transaction(doc))
{
try
{
tx.Start("Start");
//For loop for every room in the roomCollector
foreach (SpatialElement oneRoom in roomCollector)
{
//Get area of each room
Room room = oneRoom as Room;
double area = room.Area;
//Location point version
Location loc = room.Location;
LocationPoint location = loc as LocationPoint;
XYZ point = (null == location) ? XYZ.Zero : location.Point;
//New version
XYZ source = GetRoomCenter(room);
double smallRoom = 301;
if (area <= smallRoom)
{
doc.Create.NewFamilyInstance(source, elementSym, Autodesk.Revit.DB.Structure.StructuralType.NonStructural);
}
}
tx.Commit();
}
catch (Exception e)
{
Debug.Print(e.Message);
tx.RollBack();
}
}
TaskDialog.Show("Message", "Task completed successfully");
return Result.Succeeded;
}
public XYZ GetElementCenter(Room room)
{
BoundingBoxXYZ bounding = room.get_BoundingBox(null);
XYZ center = (bounding.Max + bounding.Min) * 0.5;
return center;
}
public XYZ GetRoomCenter(Room room)
{
XYZ boundCenter = GetElementCenter(room);
LocationPoint locPt = (LocationPoint)room.Location;
XYZ roomCenter = new XYZ(boundCenter.X, boundCenter.Y, locPt.Point.Z);
return roomCenter;
}
}
}
Any help on getting the XYZ data of the center of a room would be greatly appreciated.

Assuming that you have straight walls bounding the room, I would suggest that you take a look at mathematical and geometrical algorithms for determining the center point of a polygon. Something like this new algorithm for finding a visual center of a polygon is probably best suited to your needs, even though you neither state this fact nor probably are yet aware of it :-)

Related

How can I take into consideration Revit Project Base Point location when placing instances using Revit API

[![Correct location when moved from base point location to survey point][2]][2]
I'm in the process of writing a program to show navisworks clash points in Revit, however, i'm finding it difficult to place them at the exact location when the Base point is not (0,0,0). When i manually add the difference in location to the code, it works. How do I solve this programmatically? I understand there is probably a simple calculation to solve but i can't seem to figure it out. I go some ideas from googling around to be no avail. Any ideas how to go about this?
public static XYZ WorldToLocal(Document document, XYZ coordinate, bool millimeters)
{
ElementCategoryFilter filter = new ElementCategoryFilter(BuiltInCategory.OST_ProjectBasePoint);
FilteredElementCollector collector = new FilteredElementCollector(document);
IList<Element> oProjectBasePoints = collector.WherePasses(filter).ToElements();
Element oProjectBasePoint = null;
foreach (Element bp in oProjectBasePoints)
{
oProjectBasePoint = bp;
break;
}
double x = oProjectBasePoint.get_Parameter(BuiltInParameter.BASEPOINT_EASTWEST_PARAM).AsDouble();
double y = oProjectBasePoint.get_Parameter(BuiltInParameter.BASEPOINT_NORTHSOUTH_PARAM).AsDouble();
double z = oProjectBasePoint.get_Parameter(BuiltInParameter.BASEPOINT_ELEVATION_PARAM).AsDouble();
double r = oProjectBasePoint.get_Parameter(BuiltInParameter.BASEPOINT_ANGLETON_PARAM).AsDouble();
XYZ result = new XYZ(
coordinate.X * Math.Cos(r) - coordinate.Y * Math.Sin(r) ,
coordinate.X * Math.Sin(r) + coordinate.Y * Math.Cos(r),
coordinate.Z);
//Code that makes it work
XYZ newpostion = new XYZ(result.X - 21.943, result.Y +13.410, result.Z);
//ends here
if (millimeters)
{
return newpostion * 304.8;
}
return newpostion;
}
This returns the location of the survey point to the base point. Solving my question.
IEnumerable<BasePoint> points = new FilteredElementCollector(document)
.OfClass(typeof(BasePoint))
.Cast<BasePoint>();
XYZ surveypointXYZ = new XYZ();
foreach (BasePoint bp in points)
{
if (bp.IsShared)
{
BoundingBoxXYZ bb = bp.get_BoundingBox(null);
surveypointXYZ = bb.Min;
}
}

Revit API - Can't paint generated Direct Shapes

I generate all of them the same, but some of them can't be colored (image on the bottom)
Steps:
I'm creating list of faces from solid
internal static List<List<XYZ>> GetFacesFromSolidTriangulate(Solid geomSolid)
{
List<List<XYZ>> faces = new List<List<XYZ>>();
foreach (Face face in geomSolid.Faces)
{
Mesh mesh_space = face.Triangulate();
for (int i = 0; i < mesh_space.NumTriangles; i++)
{
MeshTriangle triangle = mesh_space.get_Triangle(i);
XYZ p1 = triangle.get_Vertex(0);
XYZ p2 = triangle.get_Vertex(1);
XYZ p3 = triangle.get_Vertex(2);
List<XYZ> xyz = new List<XYZ>();
xyz.Add(triangle.get_Vertex(0));
xyz.Add(triangle.get_Vertex(1));
xyz.Add(triangle.get_Vertex(2));
faces.Add(xyz);
}
}
return faces;
}
I'm creating Direct Shape using
static public DirectShape NewDrawDirectShape(Document doc, List<List<XYZ>> faces, ElementId matId, string name)
{
TessellatedShapeBuilder builder = new TessellatedShapeBuilder();
builder.OpenConnectedFaceSet(true);
foreach(List<XYZ> face in faces)
{
builder.AddFace(new TessellatedFace(face, matId));
}
builder.CloseConnectedFaceSet();
builder.Build();
TessellatedShapeBuilderResult result = builder.GetBuildResult();
DirectShape ds = DirectShape.CreateElement(doc, new ElementId(BuiltInCategory.OST_GenericModel));
ds.SetShape(result.GetGeometricalObjects());
ds.Name = name;
return ds;
}
And here is the problem, I can't paint few of them even using Revit "paint" tool...
Red arrow define what I'm trying achieve on this direct shape, blue direct shapes works correctly
Have you checked the triangle orientations? Maybe some of the triangles returned in the MeshTriangle objects are incorrectly oriented. To try it out, you could create a little model line in the centre of each triangle indicating its normal vector and this the edge orientation.

Setting x,y position of Visio shape to a graph from a template file C#

I have a template file (.vsdx) which contains a graph with a fixed x and y axis that I load into a new Visio document. I've managed to insert a shape onto the Visio document but it doesn't position according to the the x and y axis of the graph.
Example: Setting the vshape with co-ords 0,0 positions to the bottom left corner edge of the document.
I have the following code so far:
//decalre and initialize Visio objects
var vApp = new Visio.Application();
Visio.Document vDoc, vStencil;
Visio.Page vPage;
Visio.Shape vToShape, vFromShape, vConnector;
Visio.Master vConnectorMaster, vFlowChartMaster;
double dblXLocation;
double dblYLocation;
Visio.Cell vBeginCell, vEndCell;
int iCount;
string TEMPLATEPATH = #"C:\temp\TestProject\testTemplate.vsdx";
//Change this constant to match your choice of location and file name.
string SAVENEWFILE = #"C:\temp\TestProject\testFile.vsdx";
vFlowChartMaster = vStencil.Masters[aryValues[0, 0]];
dblXLocation = 1;
dblYLocation = 1;
vToShape = vPage.Drop(vFlowChartMaster,
dblXLocation, dblYLocation);
vToShape.Text = "Test";
vDoc.Pages[1].Name = "Flowchart Example";
try
{
//Delete the previous version of the file.
//Kill(SAVENEWFILE);
File.Delete(SAVENEWFILE);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
vDoc.SaveAs(SAVENEWFILE);
vDoc.Close();
vApp.Quit();
vDoc = null;
vApp = null;
GC.Collect();
The graph that gets loaded onto the Visio doc is here
Ok, thanks for the update comment. In that case, here's a quick sample. I've created a drawing with a basic 'Graph' master shape, which defines an origin, and a 'Dot' master which is simply a small circle to drop as a dta marker.
The code (using LINQPad) looks for the first instance of the Graph master and then looks for 'known' cells (which it's up to you to define) to get hold of the origin. It then drops two 'Dot' shapes relative to the Graph origin.
Here's what the Graph shape looks like:
[Note - that you can reference a PNT type in an X or Y cell and Visio will extract the corresponding X or Y coordinate]
void Main()
{
var vApp = MyExtensions.GetRunningVisio();
var vPag = vApp.ActivePage;
var graphShp = vPag.Shapes.Cast<Visio.Shape>()
.FirstOrDefault(s => s.Master?.Name == "Graph");
if (graphShp != null)
{
var dotMst = vPag.Document.Masters["Dot"];
//Get x / y back as a named tuple
var origin = GetGraphOrigin(graphShp);
//Green fill is the default defined in the master
var greenDotShp = vPag.Drop(dotMst, origin.x, origin.y);
//Use offest based on graph origin
var redDotOffsetX = -0.5;
var redDotOffsetY = 0.25;
var redDotShp = vPag.Drop(dotMst, origin.x + redDotOffsetX, origin.y + redDotOffsetY);
redDotShp.CellsU["FillForegnd"].FormulaU = "RGB(200,40,40)";
}
}
private (double x, double y) GetGraphOrigin(Visio.Shape targetShp)
{
const string originX = "User.OriginOnPageX";
const string originY = "User.OriginOnPageY";
if (targetShp == null)
{
throw new ArgumentNullException();
}
if (targetShp.CellExistsU[originX, (short)Visio.VisExistsFlags.visExistsAnywhere] != 0
&& targetShp.CellExistsU[originY, (short)Visio.VisExistsFlags.visExistsAnywhere] != 0)
{
return (x: targetShp.CellsU[originX].ResultIU,
y: targetShp.CellsU[originY].ResultIU);
}
return default;
}
So if you run this code, you should end up with something like this (assuming you started off with the a drawing as described above):
So there are lots of ways you could approach this, but probably you need some method or reading where in your Graph shape the origin is and then use that in positioning your 'dot' shapes.

Disable overlapping shapes in Visio C#

I drop shapes onto a Visio page/doc into an area depending on their x, y value. Some of the shapes contain similar x and y coordinates and therefore overlap eachother. Is the there a way to automatically prevent shapes from overlapping on the page? Or can an equation be put in place to move shapes a specific amount of inches to ensure they are no longer overlapping? Below is the code I have implemented so far.
var vApp = new Visio.Application();
Visio.Document vDoc, vStencil;
Visio.Page vPage;
Visio.Shape vToShape, vFromShape, vConnector;
Visio.Master vConnectorMaster, vFlowChartMaster;
double dblXLocation;
double dblYLocation;
Visio.Cell vBeginCell, vEndCell;
int iCount;
string TEMPLATEPATH = #"C:\temp\Test\testtemplate.vsdx";
//Change this constant to match your choice of location and file name.
string SAVENEWFILE = #"C:\temp\test\testfile.vsdx";
//open the template without any open parameters
vDoc = vApp.Documents.Open(TEMPLATEPATH);
vPage = vApp.ActivePage;
//now, load a Stencil containing basic flowchart shapes
vStencil = vApp.Documents.Open(#"C:\temp\test\teststencil.vssx");
vFlowChartMaster = vStencil.Masters[1];
for (iCount = 0; iCount < xValues.Length; iCount++)
{
vFlowChartMaster = vStencil.Masters[aryValues[0, 0]];
dblXLocation = getXLocation(xValues[iCount]);
dblYLocation = getYLocation(yValues[iCount]);
vToShape = vPage.Drop(vFlowChartMaster,
dblXLocation, dblYLocation);
vToShape.Text = names[iCount];
vToShape.TextStyle.ToLower();
vFromShape = vToShape;
vToShape = null;
}
vDoc.Pages[1].Name = "Graph";
try
{
//Delete the previous version of the file.
File.Delete(SAVENEWFILE);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
vDoc.SaveAs(SAVENEWFILE);
vDoc.Close();
vApp.Quit();
vDoc = null;
vApp = null;
GC.Collect();
Attached here are the first 3 shapes that get dropped onto the graph which overlap. There will be approx 70 more shapes to be plotted spread out over the graph with some overlapping.
At least when droping the shapes manually the following 2 flags help avoiding overlaps.
Try them in the code, it may work.
Application.ActiveWindow.Shape.CellsSRC(visSectionObject, visRowMisc, visLOFlags).FormulaU = "1"
Application.ActiveWindow.Shape.CellsSRC(visSectionObject, visRowShapeLayout, visSLOPlowCode).FormulaU = "2"

How to find whether a PDF file has overlapping text or not, using c#

I have several PDF files, using a Windows application (C#), I need to find out whether the PDF files has overlapping text or not. How can I do it, is there any free third party DLLs to achieve this?
All I have got now is third party DLLs which can get the text/images from a PDF.
My PDFs are full of texts and images. Here, one line of text is printed on top of another line or few texts are printed on top of some images. These kind of overlapping needs to found.
As you can see in the image, those overlapping might have occurred because of bounding boxes overlap and as well as glyphs contours overlap. So these two occurrences in the PDF needs to be found. My PDF doesn't contain any annotations. So overlapping occurs only in the content of pdf. We don't use poor-man's-bold technique for fatter glyph and if that occurs then it shoul be consider as overlapping.
There is not going to be any transparent images in the PDF, only image we might have is the logo or the digital signature at the bottom of the page, any text overlaps this should be considered as overlapping.
PDFs are not created from image(scan). From some text editor it has been created.
The OP clarified in comments:
those overlapping might have occurred because of bounding boxes overlap and as well as glyphs contours overlap. So these two occurrences in the PDF needs to be found.
Whenever the glyph contours themselves overlap, their bounding boxes also overlap.
Thus, it suffices to check for overlapping bounding boxes.
only image we might have is the logo or the digital signature at the bottom of the page, any text overlaps this should be considered as overlapping.
Thus, for text overlapping images we do not need to check whether a blank area in the image is overlapped.
My PDF files doesnt have any annotations.
Thus, we only need to check the page contents (including contents of form xobjects referenced from the page content, allowing recursion).
Furthermore the OP only mentioned text and images. Thus, we can ignore vector graphics.
An approach using iText 7
As I'm more into Java, I first created a prove-of-concept in Java and ported it to .Net later.
Both for Java and .Net the line of action is the same:
We create a event listener for the iText 7 parsing framework which (while processing a page) collects the bounding boxes of text and image elements and eventually can be asked to check whether there are any occurrences of text overlapping text or image.
We parse the content of the page in question using an instance of that event listener class and query it for overlaps. If more pages are to be checked, this can be done over and over again with a new event listener instance for each page.
iText 7 for .Net
The event listener might look like this:
class OverlappingTextSearchingStrategy : IEventListener
{
static List<Vector> UNIT_SQUARE_CORNERS = new List<Vector> { new Vector(0, 0, 1), new Vector(1, 0, 1), new Vector(1, 1, 1), new Vector(0, 1, 1) };
ICollection<Rectangle> imageRectangles = new HashSet<Rectangle>();
ICollection<Rectangle> textRectangles = new HashSet<Rectangle>();
public void EventOccurred(IEventData data, EventType type)
{
if (data is ImageRenderInfo) {
ImageRenderInfo imageData = (ImageRenderInfo)data;
Matrix ctm = imageData.GetImageCtm();
List<Rectangle> cornerRectangles = new List<Rectangle>(UNIT_SQUARE_CORNERS.Count);
foreach (Vector unitCorner in UNIT_SQUARE_CORNERS)
{
Vector corner = unitCorner.Cross(ctm);
cornerRectangles.Add(new Rectangle(corner.Get(Vector.I1), corner.Get(Vector.I2), 0, 0));
}
Rectangle boundingBox = Rectangle.GetCommonRectangle(cornerRectangles.ToArray());
Console.WriteLine("Adding image bounding rectangle {0}.", boundingBox);
imageRectangles.Add(boundingBox);
} else if (data is TextRenderInfo) {
TextRenderInfo textData = (TextRenderInfo)data;
Rectangle ascentRectangle = textData.GetAscentLine().GetBoundingRectangle();
Rectangle descentRectangle = textData.GetDescentLine().GetBoundingRectangle();
Rectangle boundingBox = Rectangle.GetCommonRectangle(ascentRectangle, descentRectangle);
if (boundingBox.GetHeight() == 0 || boundingBox.GetWidth() == 0)
Console.WriteLine("Ignoring empty text bounding rectangle {0} for \"{1}\".", boundingBox, textData.GetText());
else
{
Console.WriteLine("Adding text bounding rectangle {0} for \"{1}\" with 0.5 margins.", boundingBox, textData.GetText());
textRectangles.Add(boundingBox.ApplyMargins<Rectangle>(0.5f, 0.5f, 0.5f, 0.5f, false));
}
} else if (data is PathRenderInfo) {
// TODO
} else if (data != null)
{
Console.WriteLine("Ignored {0} event, class {1}.", type, data.GetType().Name);
}
else
{
Console.WriteLine("Ignored {0} event with null data.", type);
}
}
public ICollection<EventType> GetSupportedEvents()
{
// Support all events
return null;
}
public bool foundOverlappingText()
{
bool result = false;
List<Rectangle> textRectangleList = new List<Rectangle>(textRectangles);
while (textRectangleList.Count > 0)
{
Rectangle testRectangle = textRectangleList[textRectangleList.Count - 1];
textRectangleList.RemoveAt(textRectangleList.Count - 1);
foreach (Rectangle rectangle in textRectangleList)
{
if (intersect(testRectangle, rectangle))
{
Console.WriteLine("Found text intersecting text with bounding boxes {0} at {1},{2} and {3} at {4},{5}.",
testRectangle, testRectangle.GetX(), testRectangle.GetY(), rectangle, rectangle.GetX(), rectangle.GetY());
result = true;// if only the fact counts, do instead: return true
}
}
foreach (Rectangle rectangle in imageRectangles)
{
if (intersect(testRectangle, rectangle))
{
Console.WriteLine("Found text intersecting image with bounding boxes {0} at {1},{2} and {3} at {4},{5}.",
testRectangle, testRectangle.GetX(), testRectangle.GetY(), rectangle, rectangle.GetX(), rectangle.GetY());
result = true;// if only the fact counts, do instead: return true
}
}
}
return result;
}
bool intersect(Rectangle a, Rectangle b)
{
return intersect(a.GetLeft(), a.GetRight(), b.GetLeft(), b.GetRight()) &&
intersect(a.GetBottom(), a.GetTop(), b.GetBottom(), b.GetTop());
}
bool intersect(float start1, float end1, float start2, float end2)
{
if (start1 < start2)
return start2 <= end1;
else
return start1 <= end2;
}
}
This event listener can be used like this:
PdfReader reader = new PdfReader(pdf);
PdfDocument document = new PdfDocument(reader);
PdfDocumentContentParser contentParser = new PdfDocumentContentParser(document);
OverlappingTextSearchingStrategy strategy = contentParser.ProcessContent(page, new OverlappingTextSearchingStrategy());
bool foundOverlaps = strategy.foundOverlappingText();
iText 7 for Java
The event listener might look like this:
public class OverlappingTextSearchingStrategy implements IEventListener {
static List<Vector> UNIT_SQUARE_CORNERS = Arrays.asList(new Vector(0,0,1), new Vector(1,0,1), new Vector(1,1,1), new Vector(0,1,1));
Set<Rectangle> imageRectangles = new HashSet<>();
Set<Rectangle> textRectangles = new HashSet<>();
#Override
public void eventOccurred(IEventData data, EventType type) {
if (data instanceof ImageRenderInfo) {
ImageRenderInfo imageData = (ImageRenderInfo) data;
Matrix ctm = imageData.getImageCtm();
List<Rectangle> cornerRectangles = new ArrayList<>(UNIT_SQUARE_CORNERS.size());
for (Vector unitCorner : UNIT_SQUARE_CORNERS) {
Vector corner = unitCorner.cross(ctm);
cornerRectangles.add(new Rectangle(corner.get(Vector.I1), corner.get(Vector.I2), 0, 0));
}
Rectangle boundingBox = Rectangle.getCommonRectangle(cornerRectangles.toArray(new Rectangle[cornerRectangles.size()]));
logger.info(String.format("Adding image bounding rectangle %s.", boundingBox));
imageRectangles.add(boundingBox);
} else if (data instanceof TextRenderInfo) {
TextRenderInfo textData = (TextRenderInfo) data;
Rectangle ascentRectangle = textData.getAscentLine().getBoundingRectangle();
Rectangle descentRectangle = textData.getDescentLine().getBoundingRectangle();
Rectangle boundingBox = Rectangle.getCommonRectangle(ascentRectangle, descentRectangle);
if (boundingBox.getHeight() == 0 || boundingBox.getWidth() == 0)
logger.info(String.format("Ignoring empty text bounding rectangle %s for '%s'.", boundingBox, textData.getText()));
else {
logger.info(String.format("Adding text bounding rectangle %s for '%s' with 0.5 margins.", boundingBox, textData.getText()));
textRectangles.add(boundingBox.applyMargins(0.5f, 0.5f, 0.5f, 0.5f, false));
}
} else if (data instanceof PathRenderInfo) {
// TODO: vector graphics
} else if (data != null) {
logger.fine(String.format("Ignored %s event, class %s.", type, data.getClass().getSimpleName()));
} else {
logger.fine(String.format("Ignored %s event with null data.", type));
}
}
#Override
public Set<EventType> getSupportedEvents() {
// Support all events
return null;
}
public boolean foundOverlappingText() {
boolean result = false;
List<Rectangle> textRectangleList = new ArrayList<>(textRectangles);
while (!textRectangleList.isEmpty())
{
Rectangle testRectangle = textRectangleList.remove(textRectangleList.size() - 1);
for (Rectangle rectangle : textRectangleList) {
if (intersect(testRectangle, rectangle)) {
logger.info(String.format("Found text intersecting text with bounding boxes %s at %s,%s and %s at %s,%s.",
testRectangle, testRectangle.getX(), testRectangle.getY(), rectangle, rectangle.getX(), rectangle.getY()));
result = true;// if only the fact counts, do instead: return true
}
}
for (Rectangle rectangle : imageRectangles) {
if (intersect(testRectangle, rectangle)) {
logger.info(String.format("Found text intersecting image with bounding boxes %s at %s,%s and %s at %s,%s.",
testRectangle, testRectangle.getX(), testRectangle.getY(), rectangle, rectangle.getX(), rectangle.getY()));
result = true;// if only the fact counts, do instead: return true
}
}
}
return result;
}
boolean intersect(Rectangle a, Rectangle b) {
return intersect(a.getLeft(), a.getRight(), b.getLeft(), b.getRight()) &&
intersect(a.getBottom(), a.getTop(), b.getBottom(), b.getTop());
}
boolean intersect(float start1, float end1, float start2, float end2) {
if (start1 < start2)
return start2 <= end1;
else
return start1 <= end2;
}
Logger logger = Logger.getLogger(OverlappingTextSearchingStrategy.class.getName());
}
This event listener can be used like this:
PdfReader reader = new PdfReader(pdf);
PdfDocument document = new PdfDocument(reader);
PdfDocumentContentParser contentParser = new PdfDocumentContentParser(document);
OverlappingTextSearchingStrategy strategy = contentParser.processContent(pageNumber, new OverlappingTextSearchingStrategy());
boolean foundOverlaps = strategy.foundOverlappingText();
Remarks
As you can see I don't store the text bounding boxes as they are but instead
boundingBox.applyMargins(0.5f, 0.5f, 0.5f, 0.5f, false),
i.e. slightly smaller boxes. This is done to prevent false positives which otherwise might occur for tightly set text or text with kerning applied. You may have to fine tune the margin values here.
It may be as easy as the example above or you have to implement your own reader for this.
If you have not the full control over your PDF files, you have no chance to solve your problem. The defined boxes can be transformed later on. So you have to parse the whole file, too keep track of the box position and form. Additionally some boxes may be on top of other boxes, but render without any collision on the pixel level.
Than you will run into the next problem. Each PDF implementation has different errors. So your system may render the text perfectly but not the printer of your customer.
Welcome to hell ;)
Each support guy will tell you that they obey the standard. The others must have implemented their PDF library faulty. Because your customers data will be confident, you cannot proof them wrong. You may find some errors with your test data, but never ever the same errors of your customer documents.
Run and hide as long as you have not become the PDF expert of your company.
Here is a dirty "general" method: render your text without the text in bitmap. render the page with your text in another bitmap, compare the area with your text. But this will need a monochrome background. But the load will be really high. But this document looks like a form. Create a form and fill out the form boxes. So you will have no problems and you will even get correct results, fills the form with another program
Hello I have a code sample that uses not free library, but I think other libraries should have similar functionality, so you may use it as the idea:
Before use the following code sample please ensure that you use the latest version of the Apitron PDF Kit.
using System;
using System.Collections.Generic;
using System.IO;
using Apitron.PDF.Kit.FixedLayout;
using Apitron.PDF.Kit.FixedLayout.Content;
using Apitron.PDF.Kit.FixedLayout.PageProperties;
using FixedLayout.Resources;
using FixedLayout.ContentElements;
/// <summary>
/// Gets all text boundaries.
/// </summary>
/// <param name="elements">The elements.</param>
/// <param name="boundaries">The boundaries.</param>
public void GetAllTextBoundaries(IContentElementsEnumerator elements, IList<Boundary> boundaries, Boundary offset)
{
// We dont count drawings and images here - only text;
if(elements == null)
{
return;
}
foreach (IContentElement element in elements)
{
TextContentElement text = element as TextContentElement;
if (text != null)
{
foreach (TextSegment segment in text.Segments)
{
Boundary currentBoundary = segment.Boundary;
if (offset != null)
{
currentBoundary = new Boundary(currentBoundary.Left + offset.Left, currentBoundary.Bottom + offset.Bottom, currentBoundary.Right + offset.Left, currentBoundary.Top + offset.Bottom);
}
boundaries.Add(currentBoundary);
}
}
else if (element is FormContentElement)
{
Boundary currentBoundary = (element as FormContentElement).Boundary;
if (offset != null)
{
currentBoundary = new Boundary(currentBoundary.Left + offset.Left, currentBoundary.Bottom + offset.Bottom, currentBoundary.Right + offset.Left, currentBoundary.Top + offset.Bottom);
}
this.GetAllTextBoundaries((element as FormContentElement).FormXObject.Elements, boundaries, currentBoundary);
}
}
}
/// <summary>
/// Checks if text is overlapped.
/// </summary>
/// <returns></returns>
public bool CheckIfTextIsOverlapped(string fileName)
{
const double overlapMax = 5;
using (System.IO.Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite))
{
using (FixedDocument document = new FixedDocument(stream))
{
foreach (Page page in document.Pages)
{
IList<Boundary> boundaries = new List<Boundary>();
foreach (Annotation annotation in page.Annotations)
{
// Actually we need only Normal state, but will check all - to be sure.
if(annotation.Appearance.Normal != null)
{
this.GetAllTextBoundaries(annotation.Appearance.Normal.Elements, boundaries, annotation.Boundary);
}
}
IContentElementsEnumerator elements = page.Elements;
this.GetAllTextBoundaries(elements, boundaries, null);
for (int i = 0; i < boundaries.Count; i++)
{
for (int j = i + 1; j < boundaries.Count; j++)
{
Boundary b1 = boundaries[i];
Boundary b2 = boundaries[j];
double x1 = Math.Max(b1.Left, b2.Left);
double y1 = Math.Max(b1.Bottom, b2.Bottom);
double x2 = Math.Min(b1.Right, b2.Right);
double y2 = Math.Min(b1.Top, b2.Top);
// So we have intersection
if (x1 < x2 && y1 < y2)
{
if (x1 - x2 >= overlapMax || y1 - y2 >= overlapMax)
{
return true;
}
}
}
}
}
}
}
return false;
}

Categories

Resources