All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.docx4j.convert.out.fo.FOPAreaTreeHelper Maven / Gradle / Ivy

There is a newer version: 11.5.0
Show newest version
package org.docx4j.convert.out.fo;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;

import javax.xml.bind.JAXBElement;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.fop.apps.MimeConstants;
import org.docx4j.Docx4J;
import org.docx4j.TraversalUtil;
import org.docx4j.XmlUtils;
import org.docx4j.convert.out.ConversionFeatures;
import org.docx4j.convert.out.FOSettings;
import org.docx4j.convert.out.common.ConversionSectionWrapper;
import org.docx4j.convert.out.common.ConversionSectionWrappers;
import org.docx4j.convert.out.fo.renderers.FORendererApacheFOP;
import org.docx4j.finders.SectPrFinder;
import org.docx4j.jaxb.Context;
import org.docx4j.model.structure.PageDimensions;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.wml.HpsMeasure;
import org.docx4j.wml.P;
import org.docx4j.wml.PPr;
import org.docx4j.wml.PPrBase;
import org.docx4j.wml.ParaRPr;
import org.docx4j.wml.R;
import org.docx4j.wml.RPr;
import org.docx4j.wml.SectPr;
import org.docx4j.wml.Text;
import org.plutext.jaxb.xslfo.LayoutMasterSet;
import org.plutext.jaxb.xslfo.SimplePageMaster;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

/**
 * Helper to correctly size header/footer areas in PDF output.
 * 
 * @author jharrop
 * @since 3.1.0
 *
 */
public class FOPAreaTreeHelper {
	
	protected static Logger log = LoggerFactory.getLogger(FOPAreaTreeHelper.class);
	

    /**
     * Since we start with headers/footers which each take up approx half the page,
     * there is little room for the body content (which would result in many pages,
     * and unnecessary processing).
     * 
     * At the same time, we need enough body content to produce first page, odd page,
     * and even page for each section.
     * 
     * So this method replaces the existing body content with content which is sufficient
     * for our needs.  This method isn't essential, but it should make things faster.
     * 
     * It leaves the headers/footers untouched, since it is those which we're 
     * most interested in at this point.
     *  
     * @param hfPkg
     */
    static void trimContent(WordprocessingMLPackage hfPkg)   {
    	
    	// Find the sectPrs
    	SectPrFinder sf = new SectPrFinder(hfPkg.getMainDocumentPart());
		try {
			new TraversalUtil(hfPkg.getMainDocumentPart().getContents(), sf);
		} catch (Docx4JException e) {
			// TODO Auto-generated catch block
			log.error(e.getMessage(), e);
		}  
		
		List sectPrList = sf.getSectPrList();
		
		// Was there a body level one?
		if (hfPkg.getMainDocumentPart().getJaxbElement().getBody().getSectPr()!=null) {
			//then delete the first entry (which is where SectPrFinder put it)
			sectPrList.remove(0);  
		}
		
		// Now generate content; let's use
		P filler = createFillerP();
		List contents = hfPkg.getMainDocumentPart().getContent();
		contents.clear();
		
		for (SectPr sectPr : sectPrList) {
			
			contents.add(filler);
			contents.add(filler);
			contents.add(filler);
			contents.add(filler);
			
			// We expect to cause, in due course, something like:
			// WARN org.apache.fop.apps.FOUserAgent .processEvent line 97 - 
			//          The contents of fo:region-body on page 6 exceed its viewport 
			//          by 29068 millipoints. (See position 1:1038)


			// now add the sectPr
	    	P p = Context.getWmlObjectFactory().createP(); 
    	    PPr ppr = Context.getWmlObjectFactory().createPPr(); 
    	    p.setPPr(ppr);
    	    ppr.setSectPr(sectPr);
    	    
			contents.add(p);
			
		}
		
		// Add content before the body level sectPr
		if (hfPkg.getMainDocumentPart().getJaxbElement().getBody().getSectPr()!=null) {

			contents.add(filler);
			contents.add(filler);
			contents.add(filler);
			contents.add(filler);
			
		}
    }
    
    private static P createFillerP() {

    	org.docx4j.wml.ObjectFactory wmlObjectFactory = Context.getWmlObjectFactory();

    	P p = wmlObjectFactory.createP(); 
    	    // Create object for pPr
    	    PPr ppr = wmlObjectFactory.createPPr(); 
    	    p.setPPr(ppr); 
    	        // Create object for rPr
    	        ParaRPr pararpr = wmlObjectFactory.createParaRPr(); 

    	        // Create object for spacing
    	        PPrBase.Spacing pprbasespacing = wmlObjectFactory.createPPrBaseSpacing(); 
    	        ppr.setSpacing(pprbasespacing); 
    	            pprbasespacing.setBefore( BigInteger.valueOf( 800) ); 
    	            pprbasespacing.setAfter( BigInteger.valueOf( 800) ); 
    	    // Create object for r
    	    R r = wmlObjectFactory.createR(); 
    	    p.getContent().add( r); 
    	        // Create object for rPr
    	        RPr rpr = wmlObjectFactory.createRPr(); 
    	        r.setRPr(rpr); 
    	            // Create object for sz
    	            HpsMeasure hpsmeasure3 = wmlObjectFactory.createHpsMeasure(); 
    	            rpr.setSz(hpsmeasure3); 
    	                hpsmeasure3.setVal( BigInteger.valueOf( 96) ); 

    	        // Create object for t (wrapped in JAXBElement) 
    	        Text text = wmlObjectFactory.createText(); 
    	        JAXBElement textWrapped = wmlObjectFactory.createRT(text); 
    	        r.getContent().add( textWrapped); 
    	            text.setValue( "BODY CONTENT"); 

    	return p;
    }    
	
    
    static org.w3c.dom.Document getAreaTreeViaFOP(WordprocessingMLPackage hfPkg, boolean useXSLT, FOSettings foSettingsOverall) throws Docx4JException, ParserConfigurationException, SAXException, IOException  {

    	// Currently FOP dependent!  But an Antenna House version ought to be feasible.
    	
        FOSettings foSettings = Docx4J.createFOSettings();

    	// 2022: need our FopFactory; reuse overall value
        foSettings.getSettings().put(FORendererApacheFOP.FOP_FACTORY,
        		foSettingsOverall.getSettings().get(FORendererApacheFOP.FOP_FACTORY));
        
        foSettings.setOpcPackage(hfPkg);
        foSettings.setApacheFopMime(MimeConstants.MIME_FOP_AREA_TREE);
        
        foSettings.setLayoutMasterSetCalculationInProgress(true); // avoid recursion
        
//        foSettings.getFeatures().add(ConversionFeatures.PP_PDF_APACHEFOP_DISABLE_PAGEBREAK_LIST_ITEM); // in 3.0.1, this is off by default
        
        // Since hfPkg is already a clone, we don't need PP_COMMON_DEEP_COPY
        // Plus it invokes setFontMapper, which does processEmbeddings again, and those fonts aren't much use to us here
        foSettings.getFeatures().remove(ConversionFeatures.PP_COMMON_DEEP_COPY);
        
        if (log.isDebugEnabled()) {
        	foSettings.setFoDumpFile(new java.io.File(System.getProperty("user.dir") + "/hf.fo"));
        }

        ByteArrayOutputStream os = new ByteArrayOutputStream();
        
        if (useXSLT) {
        	Docx4J.toFO(foSettings, os, Docx4J.FLAG_EXPORT_PREFER_XSL);
        } else {
        	Docx4J.toFO(foSettings, os, Docx4J.FLAG_EXPORT_PREFER_NONXSL);        	
        }
        
        InputStream is = new ByteArrayInputStream(os.toByteArray());
		DocumentBuilder builder = XmlUtils.getNewDocumentBuilder();
		return builder.parse(is);

    }	
    
    /**
     * 
     * The area tree contains the information required to calculate how tall each header and footer should be.
     * 
     * The method performs the calculation (summing block/@bpda) for each region.
     * 
     * @param areaTree
     * @param headerBpda
     * @param footerBpda
     */
    static void calculateHFExtents(org.w3c.dom.Document areaTree, Map headerBpda, Map footerBpda) {
    	
    	if (log.isDebugEnabled()) {
    		log.debug(XmlUtils.w3CDomNodeToString(areaTree));
    	}
    	
    	/*
			
			  
			    
			      
			        
			          
			            
			              
			                
			                  
			                    
			                  
			                
			              
			            
			            
			              
			                
			                   
			                
			              
			            
			          
			        
			        
			          
    	 */
    	
		// for each pageSequence
    	for (int i = 0 ; i  headerBpda, Map footerBpda) {
    	
		List sections = conversionSectionWrappers.getList();
		ConversionSectionWrapper section = null;
    	
    	for (Object o : layoutMasterSet.getSimplePageMasterOrPageSequenceMaster()) {
    		
    		if (o instanceof SimplePageMaster) {
    			
    			/*
    			 *     
					      
					      
					      
					    
    			 */
    			
    			SimplePageMaster spm =((SimplePageMaster)o);
    			
    			String simplePageMasterName = spm.getMasterName();  // eg s1-first page
    			
    			// We'll need the corresponding ConversionSectionWrapper
    			int index = -1 + Integer.parseInt(
    					simplePageMasterName.substring(1, simplePageMasterName.indexOf("-")));
    			PageDimensions page = null;
    			if (sections.get(index)==null) {
    				log.error("Couldn't find section " + index + " from " + simplePageMasterName);
    			} else {
    				page = sections.get(index).getPageDimensions();
    			}
    			
    			// Region before
    			if (spm.getRegionBefore()!=null) {
    				Integer hBpdaMilliPts = headerBpda.get(simplePageMasterName);
    				if (hBpdaMilliPts==null) {
    					// No headerBpda for s1-default
    					log.error("No headerBpda for " + simplePageMasterName);
    					// You need to debug to find out why
    					
    				} else {
	        			float hBpdaPts = hBpdaMilliPts/1000;
		    			spm.getRegionBefore().setExtent(hBpdaPts+"pt");
		    			spm.getRegionBody().setMarginTop(hBpdaPts+"pt");
		    			
		    			// If the top margin in Word > what we have, then pad with margin top
		    			float totalHeight = (page.getHeaderMargin()/20 ) // twips to points
		    								+ hBpdaPts;
		    			
		    			float extraMargin = (page.getPgMar().getTop().intValue()/20) - totalHeight;  
		    			
		    			if (extraMargin>0) {
		    				float required = (page.getPgMar().getTop().intValue()-page.getHeaderMargin())/20;
			    			spm.getRegionBody().setMarginTop(required+"pt");	    				
		    			} // otherwise, we've expanded to the extent of the header already
    				}
    			}
    			
    			// Region after
    			if (spm.getRegionAfter()!=null) {
    				Integer fBpdaMilliPts = footerBpda.get(simplePageMasterName);
    				if (fBpdaMilliPts==null) {
    					log.error("No footerBpda for " + simplePageMasterName);
    					
    				} else {    				
		    			float fBpdaPts = fBpdaMilliPts/1000;
		    			spm.getRegionAfter().setExtent(fBpdaPts+"pt");
		    			spm.getRegionBody().setMarginBottom(fBpdaPts+"pt");
		    			
		    			// If the bottom margin in Word > what we have, then pad with margin bottom
		    			float totalHeight = (page.getFooterMargin()/20 ) // twips to points
		    								+ fBpdaPts;
		    			
		    			float extraMargin = (page.getPgMar().getBottom().intValue()/20) - totalHeight;  
		    			
		    			if (extraMargin>0) {
		    				float required = (page.getPgMar().getBottom().intValue()-page.getFooterMargin())/20;
			    			spm.getRegionBody().setMarginBottom(required+"pt");	    				
		    			} // otherwise, we've expanded to the extent of the footer already
	    			
    				}
    			}    			
    			
    		}
    		
    	}
    	
    }    
    

}