Search code examples
javascalaapache-poipowerpointscala-placeholder-syntax

How to add text outlines to text within Powerpoint via Apache POI:


Does anyone have an idea how we can add outlines to text (text outline) within powerpoint templates (ppxt) using Apache POI? What I have gathered so far is that the XSLFTextRun class does not have a method to get/ set the text outline for a given run element.

And as such, I could only persist the following font/ text styles:

def fontStyles(textBox: XSLFTextBox, textRun: XSLFTextRun): Unit = {
    val fontFamily = textRun.getFontFamily
    val fontColor = textRun.getFontColor
    val fontSize = textRun.getFontSize
    val fontBold = textRun.isBold
    val fontItalic = textRun.isItalic
    val textAlign = textRun.getParagraph.getTextAlign

    textBox.getTextParagraphs.foreach { p =>
      p.getTextRuns.foreach { tr =>
        tr.setFontFamily(fontFamily)
        tr.setFontColor(fontColor)
        tr.setFontSize(fontSize)
        tr.setBold(fontBold)
        tr.setItalic(fontItalic)
        tr.getParagraph.setTextAlign(textAlign)
      }
    }
  }

Is it possible to add text outline?

Any assistance/ suggestions would be highly appreciated.


Solution

  • Apache poi uses underlying ooxml-schemas classes. Those are auto generated from Office Open XML standard. So they are more complete than the high level XSLF classes. Of course they are much less convenient.

    So if somewhat is not implemented in high level XSLF classes, we can get the underlying CT classes and do it using those. In case of XSLFTextRun we can get the CTRegularTextRun object. Then we can look whether there are run properties already. If not, we add one. Then we look whether there is outline set already. If so, we unset it, because we want set it new. Then we set a new outline. This simply is a line having a special color. That line is represented by CTLineProperties object. So we need to have methods to create that CTLineProperties, to set CTLineProperties to the XSLFTextRun and get CTLineProperties from XSLFTextRun.

    Complete example using Java code:

    import java.io.FileOutputStream;
    import java.io.FileInputStream;
    
    import org.apache.poi.xslf.usermodel.*;
    import org.apache.poi.sl.usermodel.*;
    
    import java.awt.Rectangle;
    
    public class PPTXTextRunOutline {
        
     static org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties createSolidFillLineProperties(java.awt.Color color) {
      // create new CTLineProperties
      org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties lineProperties 
       = org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties.Factory.newInstance();
      // set line solid fill color
      lineProperties.addNewSolidFill().addNewSrgbClr().setVal(new byte[]{(byte)color.getRed(), (byte)color.getGreen(), (byte)color.getBlue()});
      return lineProperties;
     }
        
     static void setOutline(XSLFTextRun run, org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties lineProperties) {
      // get underlying CTRegularTextRun object
      org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun ctRegularTextRun 
       = (org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun)run.getXmlObject();
      // Are there run properties already? If not, add one.
      if (ctRegularTextRun.getRPr() == null) ctRegularTextRun.addNewRPr();
      // Is there outline set already? If so, unset it, because we are creating it new.
      if (ctRegularTextRun.getRPr().isSetLn()) ctRegularTextRun.getRPr().unsetLn();
      // set a new outline
      ctRegularTextRun.getRPr().setLn(lineProperties);  
     }
     
      static org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties getOutline(XSLFTextRun run) {
      // get underlying CTRegularTextRun object
      org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun ctRegularTextRun 
       = (org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun)run.getXmlObject();
      // Are there run properties already? If not, return null.
      if (ctRegularTextRun.getRPr() == null) return null;
      // get outline, may be null
      org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties lineProperties = ctRegularTextRun.getRPr().getLn();
      // make a copy to avoid orphaned exceptions or value disconnected exception when set to its own XML parent
      if (lineProperties != null) lineProperties = (org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties)lineProperties.copy();
      return lineProperties;
     }
    
     // your method fontStyles taken to Java code
     static void fontStyles(XSLFTextRun templateRun, XSLFTextShape textShape) {
      String fontFamily = templateRun.getFontFamily();
      PaintStyle fontColor = templateRun.getFontColor();
      Double fontSize = templateRun.getFontSize();
      boolean fontBold = templateRun.isBold();
      boolean fontItalic = templateRun.isItalic();
      TextParagraph.TextAlign textAlign = templateRun.getParagraph().getTextAlign();
      org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties lineProperties = getOutline(templateRun);
      for (XSLFTextParagraph paragraph : textShape.getTextParagraphs()) {
       for (XSLFTextRun run : paragraph.getTextRuns()) {
        run.setFontFamily(fontFamily);
        if(run != templateRun) run.setFontColor(fontColor); // set PaintStyle has the issue which I am avoiding by using a copy of the underlying XML
        run.setFontSize(fontSize);
        run.setBold(fontBold);
        run.setItalic(fontItalic);
        run.getParagraph().setTextAlign(textAlign);
           
        setOutline(run, lineProperties);
       }
      }   
     }
    
     public static void main(String[] args) throws Exception {
    
      XMLSlideShow slideShow = new XMLSlideShow(new FileInputStream("./PPTXIn.pptx"));
    
      XSLFSlide slide = slideShow.getSlides().get(0);
      
      //as in your code, get a template text run and set its font style to all other runs in text shape
      if (slide.getShapes().size() > 0) {
       XSLFShape shape = slide.getShapes().get(0); 
       if (shape instanceof XSLFTextShape) {
        XSLFTextShape textShape = (XSLFTextShape) shape;
        XSLFTextParagraph paragraph = null;
        if(textShape.getTextParagraphs().size() > 0) paragraph = textShape.getTextParagraphs().get(0);
        if (paragraph != null) {
         XSLFTextRun run = null;
         if(paragraph.getTextRuns().size() > 0) run = paragraph.getTextRuns().get(0);
         if (run != null) {
          fontStyles(run, textShape);  
         }
        }        
       }
      }
    
      //new text box having outlined text from scratch
      XSLFTextBox textbox = slide.createTextBox(); 
      textbox.setAnchor(new Rectangle(100, 300, 570, 80));
      XSLFTextParagraph paragraph = null;
      if(textbox.getTextParagraphs().size() > 0) paragraph = textbox.getTextParagraphs().get(0);
      if(paragraph == null) paragraph = textbox.addNewTextParagraph(); 
      XSLFTextRun run = paragraph.addNewTextRun();
      run.setText("Test text outline");
      run.setFontSize(60d);
      run.setFontColor(java.awt.Color.YELLOW);
      setOutline(run, createSolidFillLineProperties(java.awt.Color.BLUE));
      
      FileOutputStream out = new FileOutputStream("./PPTXOit.pptx");
      slideShow.write(out);
      out.close();
     }
    }
    

    Tested and works using current apache poi 5.0.0.