I'm using C# WinForms and GDI+ to do something I hoped wouldn't be too much problem but...
I'm basically trying to draw a string within a rectangle that has highlighted sections within the string. This all works fine when printing on one line, but I have issues when trying to wrap the text onto the next line within the rectangle.
The algorithm used is as follows: -
Split strings into a collection of highlight and not highlight.
Do
If Highlightedtext Then
DrawString(HighLightedText);
Move X position forward to next character space
Else
DrawString(NormalText);
Move X position forward to next character space
End If
Loop
I would put the code in, but it's messy and long (i'm maintaining it). It'll print out find if the text is one string of either highlighting or not, as it'll wrap it within the bounds of the rectangle without issue if it's too long. If it's multiple highlighting and the string is bigger than the rectangle, it'll write outside of it... this is because the "move X position forward..." just moves the rectangle on which is a problem!
I want to essentially move the point the text is printed within the original rectangle and print it on the next line if wrapping is required. Can anyone assist with this? It's a real pain!
I've managed to sort this by having to make my function do one character at a time.
To do this, I made a function to get an array (which is the length of the string itself) of boolean values which have set any highlighted characters to true.
private bool[] Get_CharacterArray(string text)
{
// Declare the length of the array, all set to false
bool[] characters = new bool[text.Length];
// Get the matching points
List<Point> wordLocs = FindMatchingTerms(text);
wordLocs.Sort(PointComparison);
int position = 0;
foreach (Point loc in wordLocs)
{
// We're only setting the array for matched points
for (position = loc.X; position <= loc.Y; position++)
{
characters[position] = true;
}
}
// Return the array
return characters;
}
(FindMatchingTerms() is a function that will look in the string and return the matches found into a collection).
I then loop this array to draw it out to screen but keeping track of my rectangle border width. When it reduces to the relevant size, I reset the position of drawing back to the start and then move the starting Y position down a bit.
private void RenderFormattedText(Graphics g, RectangleF bounds, string text, string matchText, Font font, Color colour, bool alignTextToTop)
{
const string spaceCharacter = " ";
const string hyphenCharacter = "-";
Font fr = null;
Font fi = null;
try
{
// Get teh matching characters.
bool[] charactersMatched = Get_CharacterArray(text);
// Setup the fonts and bounds.
fr = new Font(font.FontFamily, font.Size, FontStyle.Regular);
fi = new Font(font.FontFamily, font.Size, FontStyle.Bold | FontStyle.Underline);
SizeF fontSize = g.MeasureString(text, fi, 0, StringFormat.GenericTypographic);
RectangleF area = bounds;
// Loop all the characters of the phrase
for (int pos = 0; pos < charactersMatched.Length; pos++)
{
// Draw the character in the appropriate style.
string output = text.Substring(pos, 1);
if (charactersMatched[pos])
{
area.X += DrawFormattedText(g, area, output, fi, colour);
}
else
{
area.X += DrawFormattedText(g, area, output, fr, colour);
}
// Are we towards the end of the line?
if (area.X > (bounds.X + bounds.Width - 1))
{
// are we in the middle of a word?
string preOutput = spaceCharacter;
string postOutput = spaceCharacter;
// Get at the previous character and after character
preOutput = text.Substring(pos - 1, 1);
if ((pos + 1) <= text.Length)
{
postOutput = text.Substring(pos + 1, 1);
}
// Are we in the middle of a word? if so, hyphen it!
if (!preOutput.Equals(spaceCharacter) && !postOutput.Equals(spaceCharacter))
{
if (charactersMatched[pos])
{
area.X += DrawFormattedText(g, area, hyphenCharacter, fi, colour);
}
else
{
area.X += DrawFormattedText(g, area, hyphenCharacter, fr, colour);
}
}
}
// Are we at the end of the line?
if (area.X > (bounds.X + bounds.Width))
{
area.X = bounds.X;
area.Y += fontSize.Height + 2;
}
}
}
finally
{
fr.Dispose();
fi.Dispose();
}
}
Hopefully someone else will find this useful :) I've got some constants in there for spaceCharacter and hypenCharacter which should be self explanatory! There are custom functions to draw the string, but it should make sense nonetheless, hope it helps anyone else.
Related
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;
}
is there any example for plotting volumetric slice in Ilnumerics use community version. This is an example I got from matlab website:
Volumetric slice image example of matlab
I have array X, Y, Z as posistions and V (velocity) as value for color plotting. All I have done is use Ilpoints to plot that V in position X, Y, Z not , a surfaces. Here are My Code and the result,
ILArray<float> plotXY = ILMath.zeros<float>(3, XcoordinateXY.Length);
plotXY["0;:"] = ILMath.tosingle(SurfaceXY[":;:;1"]);
plotXY["1;:"] = ILMath.tosingle(SurfaceXY[":;:;2"]);
plotXY["2;:"] = ILMath.tosingle(SurfaceXY[":;:;3"]);
ILArray<float> ColorMap = ILMath.tosingle(SurfaceXY[":;:;0"]);
var ilsurfaceplotXY = new ILPoints()
{
/*Wireframe = { Color = Color.FromArgb(50, Color.LightGray) },
Colormap = new ILColormap(dataXY),
Children = { new ILColorbar() }*/
Positions = plotXY,
Colors = cm.Map(ColorMap).T,
Color = null
};
Here are code for displaying:
var scene = new ILScene();
scene.Add(
new ILPlotCube
{
TwoDMode = false,
Axes =
{
XAxis =
{
Label = { Text = "UTM X (Km)" },
GridMajor =
{
DashStyle = DashStyle.Dashed,
Color = Color.DarkGray,
Width = 1
}
},
YAxis =
{
Label = { Text = "UTM Y (Km)" },
GridMajor =
{
DashStyle = DashStyle.Dashed,
Color = Color.DarkGray,
Width = 1
}
},
ZAxis =
{
Label = { Text = "DEPTH (Km)" },
GridMajor =
{
DashStyle = DashStyle.Dashed,
Color = Color.DarkGray,
Width = 1
}
}
},
Children = { ilsurfaceplotXY, ilsurfaceplotXZ, ilsurfaceplotYZ },
}
);
this.ilPanel1.Scene = scene;
this.ilPanel1.Scene.Configure();
this.ilPanel1.Refresh();
And here is an image result.
Result Image
I'm sorry the image is in the link.
Regarding the visualization this can be done with regular surfaces, imagesc plots, or the new fast surface in the Drawing2 toolbox. They all allow to provide X,Y, and Z values as well as a color for each grid point or tile.
Regarding the computation of the points: it seems that you just pick points from the available set. It would be much better to interpolate between these points. The Interpolation Toolbox provides functions for the interpolation of gridded and scattered data. (In your case the data seem to be gridded ?). This allows to have slices in arbitrary orientation / angles. The interpolation toolbox interpolates the positions of the slice grid points as well as the values for the colors.
From an online example:
The setup of the horizontal slices is done as follows:
ILArray<float> C;
for (int i = 0; i < m_nrSlices; i += m_nrSlices / 4) {
C = m_V[":",":", i];
pc1.Add(new ILSurface(grid + i, C, colormap: Colormaps.Bone)
{
Wireframe = { Visible = false },
});
}
Here, m_V is your 3D dataset, handled as 3D array. pc is the plot cube. The surfaces are simply added to the plot cube. The points of the red interpolated area are dynamically computed as the user moves the red balls:
// Points on the cutting area are considered scattered points, because the area is not (necessarily) plain. However, V
// is a grid. interp3s interpolates the scattered points very efficiently.
// Note how the shape of the coordinate arrays Xn, Yn and Zn is not important. interp3s takes their elements in sequential order.
// The output is a vector of interpolated values. (We gonna reshape it below.)
ILArray < float> Z = Interpolation.interp3s(m_V, m_x, m_x, m_x, m_Xn, m_Yn, Zn, method: InterpolationMethod.cubic);
// let's plot! We get a reference to the fast surface
var fsurf = ilPanel1.Scene.First<ILFastSurface>("dynslice");
if (fsurf != null) {
// first time setup only: provide the full coordinates of X and V. Here it is sufficient to provide grid vectors.
if (fsurf.Cols == 0) {
fsurf.Update(X: m_xn * res, Y: m_xn * res, Z: Zn * res, C: ILMath.reshape(Z, Zn.S), colormap: Colormaps.Hot);
} else {
// the grid was configured already and did not change. we save some recomputing by ommiting the X and Y coordinates, prevent from reshaping buffers.
fsurf.Update(Z: Zn * res, C: ILMath.reshape(Z, Zn.S), colormap: Colormaps.Hot);
}
}
fsurf.Configure();
ilPanel1.Refresh();
To go into the details is out of scope for SO. You can download the example and run it on your machine. You will need a recent version of ILNumerics though.
EDIT: Axis aligned slices as in the plot you provided are only a subdomain, of course. Generating them works in the very same way:
I don't think the title is on the spot. But please check the pictures below for a better demonstration.
I have a rectangle which fits text in it. What I'm trying to do is that, if the rectangle's height is bigger than the screen, then the rectangle should resize.
Resizing goes as follows: set the height to screen height, increase width, try to fit text, repeat.
Here is the method that parses my text, it returns the string that would fit in the rectangle given the width.
private String parseText(String text, int Width)
{
String line = String.Empty;
String returnString = String.Empty;
String[] wordArray = text.Split(' ');
int i = 1;
foreach (String word in wordArray)
{
if (Font.MeasureString(line + word).Length() > Width)
{
returnString = returnString + line + '\n';
line = String.Empty;
i++;
}
line = line + word + ' ';
}
this.Size = new Size(Size.Width, (int)Font.MeasureString(returnString + line).Y);
return returnString + line;
}
This part works really well. However, if the text exceeds the height, the text is drawn outside of the rectangle.
So I added this:
public string Text
{
get { return text; }
set
{
temp = parseText(value.Replace("\n", ""), Size.Width);
while(Size.Height > Viewport.Height)
{
Size = new Size(Size.Width + 5, Viewport.Height);
temp = parseText(value, Size.Width);
}
text = temp;
}
}
This is my problem:
Part 1: Text is ok. Does not hit the screen's edge
Part 2: Right after hitting the screen edge by adding a bunch of 'hello'
Part 3: Adding one more "hello" properly fixes the issue.
What is happening in Part 2? How does it get resized like that? Why is it fixed in part 3?
Note: Regardless of what I add here: Size = new Size(Size.Width + 5, Viewport.Height); either +5, or 5% of the screen, or 20% or 200% it still gets resized to the same amount.
Comment for more information. Thanks
Fixed by adding: value.Replace("\n", "") in temp = parseText(value, Size.Width);
I was doing that before the while loop, but not inside it. Therefore, the text was getting a bunch of new lines, and when it got called again, the new lines disappeared before the while loop.
It should look like this: temp = parseText(value.Replace("\n", ""), Size.Width - (int)LeftMargin);
So I am working on an program that offers the user a 3-D visualization of data structures and sort algorithms. What I would like to do is have a richtextbox on the UI that shows the code for the particular algorithm that is being performed. And then I would like to have each particular line of the code to be highlighted as it is being executed. I just wanted to start with visualizing a stack since it is easier to deal with as I learn and work through this project. Right now I have a text file of c++ push and pop functions and I am saving the text into a list. I am then writing the text to the richtextbox. All of this is working but I don't know how to highlight a line and then highlight the next line. For example when I click "push" I would like it to highlight "list[stackTop] = newItem;" then draw the 3d cube (already done), then highlight the "stackTop++" line. Then the user can do it again or whatever else they want.
class CppFunctionsArray
{
List<string> ReadFunctions = new List<string>();
int Position = 0;
//Reads input from selected file and stores into ReadFunctions Array;
public void ReadInput(string fileName)
{
using (StreamReader r = new StreamReader(fileName))
{
string line;
while ((line = r.ReadLine()) != null)
{
ReadFunctions.Add(line);
}
}
}
//Writes lines to a RichTextBox.
public void WriteToRichTextBox(RichTextBox rtb, int startIndex, int endIndex, int lineNumber)
{
Position = 0;
for (int i = startIndex; i < endIndex; i++)
{
rtb.AppendText(ReadFunctions[i]);
rtb.AppendText(Environment.NewLine);
rtb.Font = new Font("times new roman", 12, FontStyle.Bold);
//Temporary
if (lineNumber == Position)
rtb.SelectionBackColor = Color.Red;
Position++;
}
}
These are not topics they are teaching me college. I am just teaching myself here. So if I am approaching this totally wrong, I am open to anything here.
Here is my event handler for "stackPush" button.
//Adds cube on top of the previous.
private void StackPush_Click(object sender, EventArgs e)
{
CppFunctionsArray ArrayOfFunctions = new CppFunctionsArray();
CodeTextBox.Clear();
ArrayOfFunctions.ReadInput("StackFunctions.txt");
//The 4 represents the line Number to highlight. TODO FIX THIS.
ArrayOfFunctions.WriteToRichTextBox(CodeTextBox, 1, 12,4);
//Draws a new cube of 1 unit length.
cube = new Visual();
//Adds cube to list;
cubeList.Add(cube);
cube.y = position;
position++;
}
If you are looking for an extension method to clear the background color from all lines of a RichTextBox, then color a specific line, the following should suffice:
public static void HighlightLine(this RichTextBox richTextBox, int index, Color color)
{
richTextBox.SelectAll();
richTextBox.SelectionBackColor = richTextBox.BackColor;
var lines = richTextBox.Lines;
if (index < 0 || index >= lines.Length)
return;
var start = richTextBox.GetFirstCharIndexFromLine(index); // Get the 1st char index of the appended text
var length = lines[index].Length;
richTextBox.Select(start, length); // Select from there to the end
richTextBox.SelectionBackColor = color;
}
I set the DrawMode to OwnerDrawText and tacked on to the DrawNode event, added my code to draw the text the way I want and all works well save for some odd black selection highlighting when a node is selected.
No problem, I added logic to check for if the node's state was highlighted and drew my own highlighting except the black highlighting gets added when a node is clicked, not just selected... The highlight gets drawn over by my rectangle once the mouse button is released but does get drawn and blinks...it's annoying. :/
Apparently I forgot to actually ask my question...How would one go about getting rid of the selection without completely handling the drawing?
In my experience you usually can't. Either you draw the item yourself or you don't. If you try to composite your graphics on top of those drawn by the control, you'll end up with glitches.
It is a bit of a pain because you have to handle focus rectangles, selection highlights, and drawing all the glyphs yourself.
On the plus side, Visual Styles can be used to do most of the work.
Here's some code that will get you most of the way there (it's incomplete, in that it uses some methods not included, and it doesn't render exactly what a normal treeview does because it supports grad filled items and columns, but should be a handy reference)
protected virtual void OnDrawTreeNode(object sender, DrawTreeNodeEventArgs e)
{
string text = e.Node.Text;
Rectangle itemRect = e.Bounds;
if (e.Bounds.Height < 1 || e.Bounds.Width < 1)
return;
int cIndentBy = 19; // TODO - support Indent value
int cMargin = 6; // TODO - this is a bit random, it's slaved off the Indent in some way
int cTwoMargins = cMargin * 2;
int indent = (e.Node.Level * cIndentBy) + cMargin;
int iconLeft = indent; // Where to draw parentage lines & icon/checkbox
int textLeft = iconLeft + 16; // Where to draw text
Color leftColour = e.Node.BackColor;
Color textColour = e.Node.ForeColor;
if (Bitfield.IsBitSet(e.State, TreeNodeStates.Grayed))
textColour = Color.FromArgb(255,128,128,128);
// Grad-fill the background
Brush backBrush = new SolidBrush(leftColour);
e.Graphics.FillRectangle(backBrush, itemRect);
// Faint underline along the bottom of each item
Color separatorColor = ColourUtils.Mix(leftColour, Color.FromArgb(255,0,0,0), 0.02);
Pen separatorPen = new Pen(separatorColor);
e.Graphics.DrawLine(separatorPen, itemRect.Left, itemRect.Bottom-1, itemRect.Right, itemRect.Bottom-1);
// Bodged to use Button styles as Treeview styles not available on my laptop...
if (!HideSelection)
{
if (Bitfield.IsBitSet(e.State, TreeNodeStates.Selected) || Bitfield.IsBitSet(e.State, TreeNodeStates.Hot))
{
Rectangle selRect = new Rectangle(textLeft, itemRect.Top, itemRect.Right - textLeft, itemRect.Height);
VisualStyleRenderer renderer = new VisualStyleRenderer((ContainsFocus) ? VisualStyleElement.Button.PushButton.Hot
: VisualStyleElement.Button.PushButton.Normal);
renderer.DrawBackground(e.Graphics, selRect);
// Bodge to make VisualStyle look like Explorer selections - overdraw with alpha'd white rectangle to fade the colour a lot
Brush bodge = new SolidBrush(Color.FromArgb((Bitfield.IsBitSet(e.State, TreeNodeStates.Hot)) ? 224 : 128,255,255,255));
e.Graphics.FillRectangle(bodge, selRect);
}
}
Pen dotPen = new Pen(Color.FromArgb(128,128,128));
dotPen.DashStyle = DashStyle.Dot;
int midY = (itemRect.Top + itemRect.Bottom) / 2;
// Draw parentage lines
if (ShowLines)
{
int x = cMargin * 2;
if (e.Node.Level == 0 && e.Node.PrevNode == null)
{
// The very first node in the tree has a half-height line
e.Graphics.DrawLine(dotPen, x, midY, x, itemRect.Bottom);
}
else
{
TreeNode testNode = e.Node; // Used to only draw lines to nodes with Next Siblings, as in normal TreeViews
for (int iLine = e.Node.Level; iLine >= 0; iLine--)
{
if (testNode.NextNode != null)
{
x = (iLine * cIndentBy) + (cMargin * 2);
e.Graphics.DrawLine(dotPen, x, itemRect.Top, x, itemRect.Bottom);
}
testNode = testNode.Parent;
}
x = (e.Node.Level * cIndentBy) + cTwoMargins;
e.Graphics.DrawLine(dotPen, x, itemRect.Top, x, midY);
}
e.Graphics.DrawLine(dotPen, iconLeft + cMargin, midY, iconLeft + cMargin + 10, midY);
}
// Draw Expand (plus/minus) icon if required
if (ShowPlusMinus && e.Node.Nodes.Count > 0)
{
// Use the VisualStyles renderer to use the proper OS-defined glyphs
Rectangle expandRect = new Rectangle(iconLeft-1, midY - 7, 16, 16);
VisualStyleElement element = (e.Node.IsExpanded) ? VisualStyleElement.TreeView.Glyph.Opened
: VisualStyleElement.TreeView.Glyph.Closed;
VisualStyleRenderer renderer = new VisualStyleRenderer(element);
renderer.DrawBackground(e.Graphics, expandRect);
}
// Draw the text, which is separated into columns by | characters
Point textStartPos = new Point(itemRect.Left + textLeft, itemRect.Top);
Point textPos = new Point(textStartPos.X, textStartPos.Y);
Font textFont = e.Node.NodeFont; // Get the font for the item, or failing that, for this control
if (textFont == null)
textFont = Font;
StringFormat drawFormat = new StringFormat();
drawFormat.Alignment = StringAlignment.Near;
drawFormat.LineAlignment = StringAlignment.Center;
drawFormat.FormatFlags = StringFormatFlags.NoWrap;
string [] columnTextList = text.Split('|');
for (int iCol = 0; iCol < columnTextList.GetLength(0); iCol++)
{
Rectangle textRect = new Rectangle(textPos.X, textPos.Y, itemRect.Right - textPos.X, itemRect.Bottom - textPos.Y);
if (mColumnImageList != null && mColumnImageList[iCol] != null)
{
// This column has an imagelist assigned, so we use the column text as an integer zero-based index
// into the imagelist to indicate the icon to display
int iImage = 0;
try
{
iImage = MathUtils.Clamp(Convert.ToInt32(columnTextList[iCol]), 0, mColumnImageList[iCol].Images.Count);
}
catch(Exception)
{
iImage = 0;
}
e.Graphics.DrawImageUnscaled(mColumnImageList[iCol].Images[iImage], textRect.Left, textRect.Top);
}
else
e.Graphics.DrawString(columnTextList[iCol], textFont, new SolidBrush(textColour), textRect, drawFormat);
textPos.X += mColumnWidthList[iCol];
}
// Draw Focussing box around the text
if (e.State == TreeNodeStates.Focused)
{
SizeF size = e.Graphics.MeasureString(text, textFont);
size.Width = (ClientRectangle.Width - 2) - textStartPos.X;
size.Height += 1;
Rectangle rect = new Rectangle(textStartPos, size.ToSize());
e.Graphics.DrawRectangle(dotPen, rect);
// ControlPaint.DrawFocusRectangle(e.Graphics, Rect);
}
}