As we are missing the ability to select a TextRange, and this example has no code (feel free to steal mine to flesh it out!), the following is my attempt to solve the problem.
This allows me to take an existing AutoShape and replace some text with some other text, and apply some formatting over-rides (this would need to be more fully fleshed out, but for the PoC is enough).
In the code below I’m taking the text “[this block]” from somewhere in the original object and replacing it with “This Is Nicer”, making it italic, and changing the color.
The problem occurs if, for instance the “[” and “]” are normal font weight and the “this block” are italic in the source object… because the manipulation occurs at a portion (aka “run” in the Office terminology) the text matching doesn’t work.
In NetOfficeFW (for instance) being able to select an arbitrary range of text based on the character positions and manipulate it (and the underlying font characteristics on a character by character basis) to change/insert text makes this less challenging.
I’m hoping I’m missing something obvious in the documentation and there’s an easier way to do this (especially as I now need to go and extend this to allow me to replace the from_str with the entire contents of a different text shape)
IAutoShape tshape = (IAutoShape)shape;
TextReplace(tshape, "[this block]", "This Is Nicer", italic: NullableBool.True, clr: Color.FromArgb(0xff, 0xD0, 0x15, 0x60));
public static void TextReplace(IAutoShape tshape, string from_str, string to_str, NullableBool bold = NullableBool.NotDefined, NullableBool italic = NullableBool.NotDefined, Nullable<Color> clr = null)
{
int portion_index = -1;
while (portion_index++ < tshape.TextFrame.Paragraphs[0].Portions.Count - 1)
{
IPortion port = tshape.TextFrame.Paragraphs[0].Portions[portion_index];
if (port.Text.IndexOf(from_str) > -1)
{
Portion newPort = ClonePortion((Portion)port);
Portion newPortAfter = ClonePortion((Portion)port);
newPort.Text = to_str;
newPort.PortionFormat.FontBold = (bold == NullableBool.NotDefined ? newPort.PortionFormat.FontBold : bold);
newPort.PortionFormat.FontItalic = (italic == NullableBool.NotDefined ? newPort.PortionFormat.FontItalic : italic);
newPort.PortionFormat.FillFormat.SolidFillColor.Color = (clr == null ? newPort.PortionFormat.FillFormat.SolidFillColor.Color : (Color)clr);
newPortAfter.Text = port.Text.Substring(port.Text.IndexOf(from_str) + from_str.Length);
port.Text = port.Text.Left(port.Text.IndexOf(from_str));
tshape.TextFrame.Paragraphs[0].Portions.Insert(portion_index + 1, newPort);
tshape.TextFrame.Paragraphs[0].Portions.Insert(portion_index + 2, newPortAfter);
portion_index = portion_index + 2; // skip the insertions
}
}
}
public static Portion ClonePortion(Portion port)
{
Portion tempPortion = new Portion();
PortionFormat tempPF = (PortionFormat)tempPortion.PortionFormat;
PortionFormat portData = (PortionFormat)port.PortionFormat;
// use PortionFormat to set values
// get top level properties
var properties = from p in typeof(PortionFormat).GetProperties()
where p.CanRead && p.CanWrite
select p;
foreach (var property in properties)
{
property.SetValue(tempPF, property.GetValue(portData));
}
// backfill the nested properties
tempPF.FillFormat.FillType = portData.FillFormat.FillType;
tempPF.FillFormat.SolidFillColor.Color = portData.FillFormat.SolidFillColor.Color;
tempPF.FontUnderline = portData.FontUnderline;
tempPF.UnderlineFillFormat.FillType = portData.UnderlineFillFormat.FillType;
tempPF.UnderlineFillFormat.SolidFillColor.Color = portData.UnderlineFillFormat.SolidFillColor.Color;
return tempPortion;
}