Aspose.Word MailMerge FieldMergingCallback set font size - c#

Is it possible in FieldMergingCallback retrieve the field size, calculation of the font size and change font size for merging text?
In my template, I have table with fixed size cell. The table can not grow.
So when I put a long text into the cell, I would like the font to be smaller when the text does not fit.
The Fit-text option in the table settings in Word, does not work as I would like.

Before performing Mail Merge, you can use the following code to apply some Font formatting to all Run nodes inside Merge Field:
foreach (Field field in doc.Range.Fields)
{
if (field.Type.Equals(Aspose.Words.Fields.FieldType.FieldMergeField))
{
Node currentNode = field.Start;
bool isContinue = true;
while (currentNode != null && isContinue)
{
if (currentNode.NodeType.Equals(NodeType.FieldEnd))
{
FieldEnd end = (FieldEnd)currentNode;
if (end == field.End)
isContinue = false;
}
if (currentNode.NodeType.Equals(NodeType.Run))
{
// Specify Font formatting here
Run run = ((Run)currentNode);
run.Font.Size = 6;
}
Node nextNode = currentNode.NextPreOrder(currentNode.Document);
currentNode = nextNode;
}
}
}
Hope, this helps. I work with Aspose as Developer Evangelist.

Related

How can I definitively detect unmapped characters in a PDF

A project I work on extracts text from PDF documents and includes it in an elastic search index. In some cases, the documents have some or all of the text encoded such that they render correctly in English, but copy & paste and normal iText7 text extraction contains some garbage characters (including some control-type characters like 0x0002).
I already go page by page and check all the fonts for a different reason. My current approach for detecting bad characters is to check each font and check if there is a ToUnicode value:
for (int pageIndex = 1; pageIndex <= document.GetNumberOfPages(); pageIndex++)
{
PdfPage page = document.GetPage(pageIndex);
PdfDictionary fontResources = page.GetResources()?.GetResource(PdfName.Font);
if (fontResources != null)
{
foreach (PdfObject font in fontResources.Values(true))
{
if (font is PdfDictionary fontDict)
{
var fontAsDict = font as PdfDictionary;
PdfName subType = fontAsDict.GetAsName(PdfName.Subtype);
PdfName toUnicode = fontAsDict.GetAsName(PdfName.ToUnicode);
if (subType == PdfName.Type0 && string.IsNullOrEmpty(toUnicode?.GetValue()))
{
// font is type 0 and there's no unicode mapping available
// so extract the text a different way.
}
}
}
}
}
Does iText7 have a definitive way to determine if a PDF page has unmapped/unextractable characters in it?
Also, the current plan is to detect any pages with unextractable characters and then render and finally OCR them. Is there a better approach?

C# Being Able to Search/Manipluate Existing RichTextBoxes That are Differently Named

With some of the knowledge I gained from yesterday and the day before, I have a general idea of what I want to do. I do have a general question about RichTextBoxes. I have a GUI already made of multiple RichTextBoxes aligned in different rows of one another. One row of boxes monitors the load of a product, the other monitors if one of the channels have been tripped and the other two rows displays the current and voltage of the product. The GUI has been premade and each row of product has been named StatusOutxxx, TripStatOutxxx, VoltageOutxxx and CurrentOutxxx (the xxx indicates a number of each RichTextBox, like CurrentOut001, for example).
The example gui is listed below:
From the image, you can see that I have 4 rows of RichTextBoxes. The monitoring backend of the product has already been established and I am trying to take the data and turn the product into a user friendly display to let the person now, if the load on a certain channel is okay or if the voltage of a product is too high/low or if a certain channel has been tripped.
Now, I know how to turn them all on at once via:
foreach(var rtb in this.Controls.OfType<RichTextBox>())
rtb.BackgroundColor = Color.Green;
and I know how to control which ones to turn on via if(rtb.Name.StartsWith("Insert Prefix Here")). But I am asking a bit of a harder question in terms of searching and manipulating an individual RichTextBox.
For example, if there is a load issue on Channel 17 of my product that drops my voltage, I want to be able to search for the StatusOut017, change the backcolor of that to Red and change the value of VoltageOut017 to the voltage display.
I know that the long way of doing it is doing a giant if statement for each load, trip and voltage channel to change it, but I am aiming for reducing the amount of lines of code and attempting to doing a for loop so my code will look something like this (in psuedocode):
for (i = 1; i < 25; i++)
{
if (StatusOut[i] == true)
{
StatusOut[i].BackColor == Color.Green;
}
else
{
StatusOut[i].BackColor == Color.Red;
}
if (TripStatOut[i] == true)
{
TripStatOut[i].BackColor == Color.Green;
}
else
{
TripStatOut[i].BackColor == Color.Red;
}
VoltageOut[i].Text = VoltageReading;
CurrentOut[i].Text = CurrentReading;
}
I am hoping for some help on this one.
I assume your naming convention is Name then 3 digits for the RichTextBox controls. You can use the controls collection to match the corresponding controls for that row. As noted a DataGrid would be better.
private void ChangeStatus()
{
//loop through the RichtextBoxes
foreach (RichTextBox rtb in this.Controls.OfType<RichTextBox>())
{
Color c = Color.Green; //default to good value
//only handle the status Out Control
if (rtb.Name.StartsWith("StatusOut"))
{ ///check the value and set the color if false
if (rtb.Text == "false")
c = Color.Red;
//update the controls
rtb.BackColor = c;
//string controlNum = rtb.Name.Replace("StatusOut", ""); //If it is not 3 just use replace since we know we are on the StatusOut control
string controlNum = rtb.Name.Substring(rtb.Name.Length - 3);
((RichTextBox)Controls["TripStateOut" + controlNum]).BackColor = c;
((RichTextBox)Controls["VoltageOut" + controlNum]).Text = VoltageReading;
((RichTextBox)Controls["CurrentOut" + controlNum]).Text = CurrentReading;
}
}
}

How to add font size from the datagrivew column

Please welcome me as am a new member.
I have a datagridview with 7 columns on it. all this are xml file which get loaded to the datagridview. my columns are are follow
Department , Employment , Permanent , contract , Fontsize.
I have a xml file called employees which get loaded to my datagridview
The user will capture the data from the column cells
On Fontsize column. i want the user to enter only two numbers or one for incresing the font size of the current row.
if the user enters fontsize of 10, the current cell rows will change the font size(which mean department,employment,permanent and contract will change their font size).
the change will only happen on the current cell selected. e.g if you change for columnIndex 1. only column index row will change fontsize.
I have do the validations for allowing only numbers, but i want it to take two numbers only.
How can i change my fontsize of column index based on the number entered. I know of font dialog but thats not how i want it to be, i have use it for other client project.
I got this example but it is not how i want it as it got style. i only need fontsize
WinForms DataGridView font size
my validations
private void Gridview_Output_CellEndEdit_1(object sender, DataGridViewCellEventArgs e)
{
try
{
#region this one validate the font number/ restrctions
int RowIndex = e.RowIndex;
int columnIndex = e.ColumnIndex;
if (e.ColumnIndex == 2)
{
bool validation = true;
if (Gridview_.Rows[RowIndex].Cells[columnIndex].Value != null && Gridview_.Rows[RowIndex].Cells[columnIndex].Value.ToString().Trim() != "")
{
string DataToValidate = Gridview_.Rows[RowIndex].Cells[columnIndex].Value.ToString();
foreach (char c in DataToValidate)
{
if (!char.IsDigit(c))
{
validation = false;
break;
}
}
if (validation == false)
{
MessageBox.Show("Font must be numbers only", "Error Message", MessageBoxButtons.OKCancel,MessageBoxIcon.Warning);
Gridview_.Rows[RowIndex].Cells[columnIndex].Value = "";
}
}
}
loading the file
XmlDocument doc = new XmlDocument();
doc.Load(Employee);
my gridview column indexes
Gridview_.Rows[i].Cells[1].Value.ToString(); // for Department
Gridview_.Rows[i].Cells[2].Value.ToString(); // for Employment
Gridview_.Rows[i].Cells[3].Value.ToString(); //for Permanent
Gridview_.Rows[i].Cells[4].Value.ToString(); //for Contract
Gridview_.Rows[i].Cells[5].Value.ToString(); //for Fontsize
Thanks for your help and i hope my question is constructive enough
The post you linked is the correct way to set it for columns. A couple things I gather from your question:
You want to only change selected cells.
You want to only change the font size, not anything else.
To change a specific cell font, you would do something like this:
dgv.Rows[0].Cells[0].Style.Font = newFont;
To only set the size of the font, you need to bring over the properties of the previous font with something like this:
Font newFont = new Font(oldFont.FontFamily, 43, oldFont.Style, oldFont.Unit);

Filling a textbox with limited size

I have a textbox control from DevExpress and we cant allow more characters beyond its capacity. The problem is that the input string is xml formatted and can have multiple fonts. If the font size increases, the maximum number of characters decrease.
My first thought is counting by line, because lines are measurable despite font size. But the column I could not see a way.
How could I fill this textbox taking in consideration the string font e xml tags ?
You can use Exception Handling to figure it out for you:
bool flag = false;
int count = line.Length;
do
{
try
{
txt.Text = line.SubString(0, count);
flag = true;
}
catch(TheException)
{
count--;
}
}
while(!flag);
This works if you are getting an exception for putting in too long a line.

Creating a multi-level bullet list with Word.Interop

I need to create a multi-level bullet list via Microsoft.Office.Interop.Word and I am currently struggling with its (horrible) API (again).
I've just created the following example (not dynamic yet, just for demonstration purposes) in a VSTO document-level project for Microsoft Office Word 2010 in the programming language C#:
Word.Paragraph paragraph = null;
Word.Range range = this.Content;
paragraph = range.Paragraphs.Add();
paragraph.Range.Text = "Item 1";
paragraph.Range.ListFormat.ApplyBulletDefault(Word.WdDefaultListBehavior.wdWord10ListBehavior);
// ATTENTION: We have to outdent the paragraph AFTER its list format has been set, otherwise this has no effect.
// Without this, the the indent of "Item 2" differs from the indent of "Item 1".
paragraph.Outdent();
paragraph.Range.InsertParagraphAfter();
paragraph = range.Paragraphs.Add();
paragraph.Range.Text = "Item 1.1";
// ATTENTION: We have to indent the paragraph AFTER its text has been set, otherwise this has no effect.
paragraph.Indent();
paragraph.Range.InsertParagraphAfter();
paragraph = range.Paragraphs.Add();
paragraph.Range.Text = "Item 1.2";
paragraph.Range.InsertParagraphAfter();
paragraph = range.Paragraphs.Add();
paragraph.Range.Text = "Item 2";
paragraph.Outdent();
The code does exactly what I want (after a lot of try and error!), but it's horrible in my opinion. The format has to be applied at a VERY specific point and I have to manually indent and outdent the created paragraphs.
So my question is: Does a better approach exist to create a multi-level bullet list via Word.Interop, e.g. via shorthand methods that I haven't discovered yet?
My goal is to create a multi-level list from XML data (more specific a CustomXMLNode object)
Two other questions related to bullet lists exist on Stack Overflow, but both do not help me (the source code above is one answer to the second question):
Bullet points in Word with c# Interop
https://stackoverflow.com/questions/3768414/ms-word-list-with-sub-lists
EDIT (2013-08-08):
I've just hacked something together that outputs two arrays as a bullet list with two levels (the array with the sub-items is used for each root-item, to keep it simple). By introducing recursion, one would be able to create a bullet list with infinite levels (theoretically). But the problem remains, the code is a mess...
string[] rootItems = new string[]
{
"Root Item A", "Root Item B", "Root Item C"
};
string[] subItems = new string[]
{
"Subitem A", "Subitem B"
};
Word.Paragraph paragraph = null;
Word.Range range = this.Content;
bool appliedListFormat = false;
bool indented = false;
for (int i = 0; i < rootItems.Length; ++i)
{
paragraph = range.Paragraphs.Add();
paragraph.Range.Text = rootItems[i];
if (!appliedListFormat)
{
paragraph.Range.ListFormat.ApplyBulletDefault(Word.WdDefaultListBehavior.wdWord10ListBehavior);
appliedListFormat = true;
}
paragraph.Outdent();
paragraph.Range.InsertParagraphAfter();
for (int j = 0; j < subItems.Length; ++j)
{
paragraph = range.Paragraphs.Add();
paragraph.Range.Text = subItems[j];
if (!indented)
{
paragraph.Indent();
indented = true;
}
paragraph.Range.InsertParagraphAfter();
}
indented = false;
}
// Delete the last paragraph, since otherwise the list ends with an empty sub-item.
paragraph.Range.Delete();
EDIT (2013-08-12):
Last friday I thought I have achieved what I wanted to, but this morning I noticed, that my solution only works if the insertion point is at the end of the document. I've created the following simple example to demonstrate the (erroneous) behavior. To conclude my problem: I am able to create multi-level bullet lists at the end of the document only. As soon as I change the current selection (e.g. to the start of the document), the list is destroyed. As far as I can see, this is related to the (automatic or non-automatic) extension of the Range objects. I've tried a lot so far (I'm almost losing it), but it's all cargo-cult to me. The only thing I want to do is to insert one element after another (is it impossible to create a content control inside a paragraph, so that the text of the paragraph is followed by the content control?) and to to that in any Range of a Document. I will create a Gist on GitHub with my actual CustomXMLPart binding class this evening. Eventually someone can help me to fix that bothersome problem.
private void buttonTestStatic_Click(object sender, RibbonControlEventArgs e)
{
Word.Range range = Globals.ThisDocument.Application.Selection.Range;
Word.ListGallery listGallery = Globals.ThisDocument.Application.ListGalleries[Word.WdListGalleryType.wdBulletGallery];
Word.Paragraph paragraph = null;
Word.ListFormat listFormat = null;
// TODO At the end of the document, the ranges are automatically expanded and inbetween not?
paragraph = range.Paragraphs.Add();
listFormat = paragraph.Range.ListFormat;
paragraph.Range.Text = "Root Item A";
this.ApplyListTemplate(listGallery, listFormat, 1);
paragraph.Range.InsertParagraphAfter();
paragraph = paragraph.Range.Paragraphs.Add();
listFormat = paragraph.Range.ListFormat;
paragraph.Range.Text = "Child Item A.1";
this.ApplyListTemplate(listGallery, listFormat, 2);
paragraph.Range.InsertParagraphAfter();
paragraph = paragraph.Range.Paragraphs.Add();
listFormat = paragraph.Range.ListFormat;
paragraph.Range.Text = "Child Item A.2";
this.ApplyListTemplate(listGallery, listFormat, 2);
paragraph.Range.InsertParagraphAfter();
paragraph = paragraph.Range.Paragraphs.Add();
listFormat = paragraph.Range.ListFormat;
paragraph.Range.Text = "Root Item B";
this.ApplyListTemplate(listGallery, listFormat, 1);
paragraph.Range.InsertParagraphAfter();
}
private void ApplyListTemplate(Word.ListGallery listGallery, Word.ListFormat listFormat, int level = 1)
{
listFormat.ApplyListTemplateWithLevel(
listGallery.ListTemplates[level],
ContinuePreviousList: true,
ApplyTo: Word.WdListApplyTo.wdListApplyToSelection,
DefaultListBehavior: Word.WdDefaultListBehavior.wdWord10ListBehavior,
ApplyLevel: level);
}
EDIT (2013-08-12): I've set up a GitHub repository here which demonstrates my problem with the Word.Range objects. The OnClickButton method in the file Ribbon.cs invokes my custom mapper class. The comments there describe the problem. I know that my problems are related to the argument Word.Range object reference, but all other solutions I tried (e.g. modifying the range inside of the class) failed even harder. The best solution I've achieved so far, is to specify the Document.Content range as the argument for the MapToCustomControlsIn method. This inserts a nicely formatted multi-level bullet list (with custom XML parts bound to content controls) to the end of the document. What I want is to insert that list at a custom posiztion into the document (e.g. the current selection via Word.Selection.Range).
Florian Wolters example almost there, but the first child item numbering always not correct when I tried.
Someone gave me inspiration by suggesting using Macro and VBA script then convert to C#.
Below is the sample code tested works at my side. Hope it helps.
using Microsoft.Office.Interop.Word;
using System.Reflection;
namespace OfficeUtility
{
public class NumberListGenerate
{
public void GenerateList()
{
Application app = null;
Document doc = null;
string filePath = "c:\\output.docx";
string pdfPath = "c:\\export.pdf";
try
{
app = new Application();
app.Visible = false; // Open Microsoft Office in background
doc = app.Documents.Open(filePath, Missing.Value, false);
Range range = doc.Range();
string search = "$list";
// Find in document to generate list
while (range.Find.Execute(search))
{
ListGallery listGallery =
app.ListGalleries[WdListGalleryType.wdNumberGallery];
// Select found location
range.Select();
// Apply multi level list
app.Selection.Range.ListFormat.ApplyListTemplateWithLevel(
listGallery.ListTemplates[1],
ContinuePreviousList: false,
ApplyTo: WdListApplyTo.wdListApplyToWholeList,
DefaultListBehavior: WdDefaultListBehavior.wdWord10ListBehavior);
// First level
app.Selection.TypeText("Root Item A"); // Set text to key in
app.Selection.TypeParagraph(); // Simulate typing in MS Word
// Go to 2nd level
app.Selection.Range.ListFormat.ListIndent();
app.Selection.TypeText("Child Item A.1");
app.Selection.TypeParagraph();
app.Selection.TypeText("Child Item A.2");
app.Selection.TypeParagraph();
// Back to 1st level
app.Selection.Range.ListFormat.ListOutdent();
app.Selection.TypeText("Root Item B");
app.Selection.TypeParagraph();
// Go to 2nd level
app.Selection.Range.ListFormat.ListIndent();
app.Selection.TypeText("Child Item B.1");
app.Selection.TypeParagraph();
app.Selection.TypeText("Child Item B.2");
app.Selection.TypeParagraph();
// Delete empty item generated by app.Selection.TypeParagraph();
app.Selection.TypeBackspace();
}
// Save document
doc.Save();
// Export to pdf
doc.ExportAsFixedFormat(pdfPath, WdExportFormat.wdExportFormatPDF);
}
catch (System.Exception ex)
{
LogError(ex);
}
finally
{
if (doc != null)
{
// Need to close the document to prevent deadlock
doc.Close(false);
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(doc);
}
if (app != null)
{
app.Quit();
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(app);
}
}
}
}
}
A summary of what needs to be done.
1.) Select the Range
2.) Configure the ListFormat Property of the Range. Must set DefaultListBehavior
to WdDefaultListBehavior.wdWord10ListBehavior
3.)Add your Level one text.
4.)Add a paragraph
5.)Add your level two text.
6.)Set ListLevelTier to level 2
public void GenerateMultiLevelList()
{
//A Range to write Text
Word.Range writRange = ActiveDoc.Range(0);
writRange.Text = "Tier 1 Bullet Text";
//Moving the Range to the End of Previously Entered Text
writRange = ActiveDoc.Range(writRange.End - 1);
//Formating the Range as a Bullet Point List
writRange.ListFormat.ApplyListTemplate(BulletListTemplate, true, Word.WdListApplyTo.wdListApplyToWholeList, Word.WdDefaultListBehavior.wdWord10ListBehavior);
//Adding a Paragraph
writRange.Paragraphs.Add();
writRange = ActiveDoc.Range(writRange.End - 1);
writRange.Text = "Tier 2 Bullet Text";
//Setting the List Level to 2
writRange.SetListLevel(2);
}

Categories

Resources