We're using ITextSharp for reasons I do not understand and we dont have the book yet and I have an annoying problem that I'd appreciate help with.
I'm working with a footer and I can not get it to align as I want it.
The function I have takes a list of strings, but it's generally 4 strings I want on a row each. It does not seam like itextsharp can handle strings with linebreaks so that's the reason for the list.
Now this does not position correctly for me, the first string looks ok, but then the second string is a bit longer and it's half outside the document as is the third string and the 4th is not even visible even thou there is 1 cm of space left.
Thanks for help!
public PdfTemplate AddPageText(IList<string> stringList, PdfWriter writer)
{
var cb = writer.DirectContent;
PdfTemplate footerTemplate = cb.CreateTemplate(450, 120); //55);
footerTemplate.BeginText();
BaseFont bf2 = BaseFont.CreateFont(BaseFont.TIMES_ITALIC, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED);
footerTemplate.SetFontAndSize(bf2, 9);
footerTemplate.SetColorStroke(BaseColor.DARK_GRAY);
footerTemplate.SetColorFill(BaseColor.GRAY);
footerTemplate.SetLineWidth(3);
footerTemplate.LineTo(50, footerTemplate.YTLM);
int y = 10;
foreach (string text in stringList)
{
float widthoftext = 500.0f - bf2.GetWidthPoint(text, 9);
footerTemplate.ShowTextAligned(PdfContentByte.ALIGN_RIGHT, text, widthoftext, 50 - y, 0);
y += y;
}
footerTemplate.EndText();
return footerTemplate;
}
If you are doing string placement and using DirectContent then you are responsible for the content. In this case you will need to calculate the string rectangles and wrap accordingly.
I would suggest, however, moving to using a table with cells for the text. Tables wrap text and handle some of the issues you are dealing with.
Related
I am very new to C# sharp and this question may not be very well formulated, but here it goes.
I am currently generating a level consisting of more game objects. The map is generated by parsing a text file, this being my dictionary. Currently the code I have works perfectly if the index of the object does not go over 9, because of the char declaration.
I would like to know an approach on how to change this into an int maybe or into a string format (A-Z).
I attached a photo of my current "Map Generator", and highlighted the 10, which is currently read as 1 and 0.
private void CreateLevel() {
Tiles = new Dictionary<Point, TileScript>();
string[] mapData = ReadLevelText();
int mapX = mapData[0].ToCharArray().Length;
int mapY = mapData.Length;
Vector3 maxTile = Vector3.zero;
// Calculates the world start point, this is the top left corner of the screen, rememeber to use on the sprite the top left pivot point
Vector3 worldStart = Camera.main.ScreenToWorldPoint(new Vector3(0, Screen.height));
for (int y = 0; y < mapY; y++) // the Y position
{
char[] newTiles = mapData[y].ToCharArray();
for (int x = 0; x < mapX; x++) // the X position
{
// Places the tile into the level
PlaceTile(newTiles[x].ToString(), x, y, worldStart);
}
}
maxTile = Tiles[new Point(mapX-1, mapY-1)].transform.position;
cameraMovement.SetLimit(new Vector3(maxTile.x + TileSize, maxTile.y - TileSize));
}
private void PlaceTile(string tileType, int x, int y, Vector3 worldStart) {
int tileIndex = int.Parse(tileType);
// Creates a new tile and makes a reference to that tile in the newTile variable
TileScript newTile = Instantiate(tilePrefabs[tileIndex]).GetComponent<TileScript>();
// Uses the new tile variable to change the position of the tile
newTile.Setup(new Point(x, y), new Vector3(worldStart.x + (TileSize * x), worldStart.y - (TileSize * y), 0));
Tiles.Add(new Point(x,y), newTile);
}
The data format you are using is not possible to parse.
Think about it, there is no way of telling which of those digits are supposed to be interpreted as a pair "10" or a single "1" or even three "101".
In these scenarios, I would recommend redoing your file format to use characters instead of digit, giving you many more symbols to work with, and/or I strongly recommend using a delimiter like a , character between each of your. The file format CSV (Comma Separated Values) does exactly this.
You could create your levels in a spreadsheet program like Excel, Numbers (mac), LibreOffice, or an online spreadsheet tool like Google Sheets. You would do this by filling in the values in the cells, and then when you are done you would export / "save as" a .csv file.
You can parse the .csv file using a .NET library like CsvHelper (which I highly recommend using for reading CSV data in the real world)
Or, if you want to do it the quick and dirty way, you would read the file into memory using something like File.ReadAllLines, Trim() each line from the file, and then use Split(',') to split each line into its values. You can then iterate through your 2D array, and map it to another value if you prefer to do that!
I'd create an array of string to hold symbols, first of all -- probably "A"-"Z". Then, change the char[] to unsigned int[] (and other changes to support that, like the ToCharArray() calls).
Then, when you place the tiles, instead of passing newTiles[x].ToString(), pass symbols[newTiles[x]].
(Truthfully, I'd probably make the symbol array char, and change the signature of PlaceTile(), if that's an option).
I'm using the Windows.Forms.DataVisualization.Charting class to draw a simple char plotting a simple courb.
My courb is correclty plotted, but the points are not shown. Like dots or crosses where the points are.
I tried using datapoint.BackImage, but that doesn't show anything.
I'm sure that the image is found because I store other images in the exact same folder, and they are correctly read when I use the same path.
The code where I feed the DataPoint:
foreach (MesureTaille mesureTaille in tailles)
{
DataPoint point = new DataPoint(mesureTaille.age, mesureTaille.taille);
point.BackImage = string.Concat(
Application.StartupPath.Remove(Application.StartupPath.IndexOf("\\bin\\Debug")),
"/BackgoundImage/dot.png");
Serie_Age_Taille.Points.Add(point);
}
As DavidG and TaW pointed out, what I needed was Series.MarkerStyle :
// This line sets the dots !
Serie_Age_Taille.MarkerStyle = MarkerStyle.Cross;
foreach (MesureTaille mesureTaille in tailles)
{
// And I don't need to do anything on the DataPoints
DataPoint point = new DataPoint(mesureTaille.age, mesureTaille.taille);
Serie_Age_Taille.Points.Add(point);
}
I am drawing a line on a graph from numbers read from a text file. There is a number on each line of the file which corresponds to the X co-ordinate while the Y co-ordinate is the line it is on.
The requirements have now changed to include "special events" where if the number on the line is followed by the word special a spike will appear like image below:
Currently the only way I can find is to use a line for each spike, however there could be a large of these special events and so needs to be modular. This seems an efficient and bad way to program it.
Is it possible to add the spikes to the same graph line? Or is it possible to use just one additional line and have it broken (invisible) and only show where the spikes are meant to be seen?
I have looked at using bar graphs but due to other items on the graph I cannot.
The DataPoints of a Line Chart are connected so it is not possble to really break it apart. However each segment leading to a DataPoint can have its own color and that includes Color.Transparent which lends itself to a simple trick..
Without adding extra Series or Annotations, your two questions can be solved like this:
To simply add the 'spikes' you show us in the 2nd graph, all you need to do is to insert 2 suitable datapoints, the 2nd being identical to the point the spike is connected to.
To add an unconnected line you need to 'jump' to its beginning by adding one extra point with a transparent color.
Here are two example methods:
void addSpike(Series s, int index, double spikeWidth)
{
DataPoint dp = s.Points[index];
DataPoint dp1 = new DataPoint(dp.XValue + spikeWidth, dp.YValues[0]);
s.Points.Insert(index+1, dp1);
s.Points.Insert(index+2, dp);
}
void addLine(Series s, int index, double spikeDist, double spikeWidth)
{
DataPoint dp = s.Points[index];
DataPoint dp1 = new DataPoint(dp.XValue + spikeDist, dp.YValues[0]);
DataPoint dp2 = new DataPoint(dp.XValue + spikeWidth, dp.YValues[0]);
DataPoint dp0 = dp.Clone();
dp1.Color = Color.Transparent;
dp2.Color = dp.Color;
dp2.BorderWidth = 2; // optional
dp0.Color = Color.Transparent;
s.Points.Insert(index + 1, dp1);
s.Points.Insert(index + 2, dp2);
s.Points.Insert(index + 3, dp0);
}
You can call them like this:
addSpike(chart1.Series[0], 3, 50d);
addLine(chart1.Series[0], 6, 30d, 80d);
Note that they add 2 or 3 DataPoints to the Points collection!
Of course you can set the Color and width (aka BorderWidth) of the extra lines as you wish and also include them in the params list..
If you want to keep the points collection unchanged you also can simply create one 'spikes series' and add the spike points there. The trick is to 'jump' to the new points with a transparent line!
Is it possible, using iTextSharp, get all text occurrences contained in a specified area of a pdf document?
Thanks.
First you need the actual coordinates of the rectangle you marked in Red. On sight, I'd say the x value 144 (2 inches) is probably about right, but it would surprise me if the y value is 76, so you'll have to double check.
Once you have the exact coordinates of the rectangle, you can use iText's text extraction functionality using a LocationTextExtractionStrategy as is done in the ExtractPageContentArea example.
For the iTextSharp version of this example, see the C# port of the examples of chapter 15.
System.util.RectangleJ rect = new System.util.RectangleJ(70, 80, 420, 500);
RenderFilter[] filter = {new RegionTextRenderFilter(rect)};
ITextExtractionStrategy strategy = new FilteredTextRenderListener(
new LocationTextExtractionStrategy(), filter);
text = PdfTextExtractor.GetTextFromPage(reader, 1, strategy);
#BrunoLowagie gives an excellent answer but something I really struggled with was getting the actual coordinates to use. I started out with using Cursor Coordinates from Adobe Acrobat Pro.
From here I could get the coordinate in inches and calculate the DTP point (PostScript points) by multiplying the value with 72.
However something was still not right. Looking at the Y value this seemed way off. I then noticed that Adobe Acrobat counts coordinates in this view from the top left instead of bottom left. This means that Y needs to be calculated.
I solved this in code like this:
var rect = new RectangleJ(GetPostScriptPoints(4.19f),
GetPostScriptPoints(GetInverseCoordinateInInches(pdfReader, 1, 1.42f)),
GetPostScriptPoints(3.5f), GetPostScriptPoints(0.39f));
RenderFilter[] filter = { new RegionTextRenderFilter(rect) };
ITextExtractionStrategy strategy = new FilteredTextRenderListener(
new LocationTextExtractionStrategy(), filter);
var output = PdfTextExtractor.GetTextFromPage(pdfReader, 1, strategy);
private float GetPostScriptPoints(float inch)
{
return inch * 72;
}
private float GetInverseCoordinateInInches(PdfReader pdfReader, int pageIndex, float coordinateInInches)
{
Rectangle mediabox = pdfReader.GetPageSize(pageIndex);
return mediabox.Height / 72 - coordinateInInches;
}
This worked but I think it looks a little messy. I then used the tool Prepare Form in Adobe Acrobat Pro and here the Y coordinate showed up correctly when looking at Text Field Properties. It could also convert the box into points right away.
This means I could write code like this instead:
var rect = new RectangleJ(301.68f, 738f, 252f, 28.08f);
RenderFilter[] filter = { new RegionTextRenderFilter(rect) };
ITextExtractionStrategy strategy = new FilteredTextRenderListener(
new LocationTextExtractionStrategy(), filter);
var output = PdfTextExtractor.GetTextFromPage(pdfReader, 1, strategy);
This was a lot cleaner and faster so this was the way I choose to do it in the end.
See this answer if you would like to get a value from a specific location for every page in the document:
https://stackoverflow.com/a/20959388/3850405
If I give TextRenderer.MeasureText some text to measure and width to use it will return the height needed to display that text.
private static int CalculateHeight(string text, Font font, int width)
{
Size size = TextRenderer.MeasureText(text, font, new Size(width, Int32.MaxValue), TextFormatFlags.NoClipping | TextFormatFlags.WordBreak);
return size.Height;
}
If I give that text, width and height to a LinkLabel it would display the text in the width and height provided with nothing clipped off.
However, if I put a Link into the LinkLabel.Links collection, the LinkLabel will draw the text with what appears to be a little more spacing between the characters and at times this will cause the end of the text to be clipped. Is there anyway to prevent this? I've tried adding padding when there is a link, but there's no reliable way to know exactly how much more space will be needed. Are there any other ways to do this?
You should use Control.GetPreferredSize method to calculate width or height needed for control (LinkLabel in your case). You should not use MeasureText for such purposes, more detailed explanation you can find here (Accuracy of TextRenderer.MeasureText results.)
If a LinkLabel contains more than one link, or there are parts of text which are nor in a link, then the control uses Graphics.DrawString/MeasureString instead of TextRenderer.DrawText/MeasureText. You can easily see it in action, the biggest difference in rendering is with the small L letter:
linkLabel1.Text = new string('l', 100); // 100 x small L
linkLabel1.LinkArea = new LinkArea(0, 50);
linkLabel2.Text = new string('l', 100); // 100 x small L
TextRenderer.MeasureText is a managed wrapper for the DrawTextEx API. The value returned comes from the lprc struct. You might want to look at that API for more details.
I guess you could remove the style that makes it underline. linkLabel.Styles.Add("text-decoration", "none"); but then of course it wouldn't look like a link. :-/
Another solution would be to add the padding yourself I guess.
int heightBefore = linkLabel.Height;
int fontHeight = CalculateHeight(linkLabel.Text, linkLabel.Font, linkLabel.Width);
int paddingHeight = heightBefore - fontHeight;
linkLabel.Font = otherFont;
linkLabel.Height = CalculateHeight(linkLabel.Text, otherFont, linkLabel.Width);
linkLabel.Height += paddingHeight;
Not the prettiest of solutions, but I would guess it works.