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 jakarta.xml.bind.JAXBElement;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.FopFactoryBuilder;
import org.apache.fop.apps.MimeConstants;
import org.docx4j.Docx4J;
import org.docx4j.Docx4jProperties;
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.convert.out.fopconf.Fonts.Font;
import org.docx4j.convert.out.fopconf.Fop;
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.
    	
        log.info("Creating FopFactory for AreaTree calculations");
    	
        FOSettings foSettingsHere = Docx4J.createFOSettings();
        foSettingsHere.setFopConfig(foSettingsOverall.getFopConfig());
        
        /* Here is where fonts are first configured in a FopFactory.
         * It seems that is remembered, and is not dependent on
         * type of renderer.
         * 
         * So if you set up FopFactory using a config which defines
         * fonts for "application/pdf" only, but the renderer is
         * actually application/X-fop-areatree (as it is here),
         * then you'll only get the default fonts here, and worse,
         * later on for "application/pdf".
         * 
         * In other words, with 
         * 
		    
		        
		            
		            
		   as opposed to application/X-fop-areatree
		   
		   FOP will say:
		   
		   DEBUG FOP 696 - No user configuration found for MIME type application/X-fop-areatree
		   
         * So we use a distinct FopFactory here, and better, specifically configure
         * fonts for renderer application/X-fop-areatree             
         */

        // no need to clone, as long as we remember to change it back again
        foSettingsOverall.getFopConfig().getRenderers().getRenderer().setMime(MimeConstants.MIME_FOP_AREA_TREE);
        //  ERROR org.apache.fop.apps.FOUserAgent 103 - The simulate-style property is only supported in PDF.
        boolean flippedSimulateStyle = false;
        if (Docx4jProperties.getProperty("docx4j.fonts.fop.util.FopConfigUtil.simulate-style", false)) {        	
        	// Need to set to false
        	for (Font f : foSettingsOverall.getFopConfig().getRenderers().getRenderer().getFonts().getFont()) {
        		if (f.isSimulateStyle()) {
        			f.setSimulateStyle(false);
        			flippedSimulateStyle = true;
        		}
        	}
        }
        
		FopFactoryBuilder fopFactoryBuilder = FORendererApacheFOP.getFopFactoryBuilder(foSettingsHere) ;
		FopFactory fopFactory = fopFactoryBuilder.build();
        
		// change it back
        foSettingsOverall.getFopConfig().getRenderers().getRenderer().setMime(FOSettings.MIME_PDF);
        if (flippedSimulateStyle) {        	
        	for (Font f : foSettingsOverall.getFopConfig().getRenderers().getRenderer().getFonts().getFont()) {
        		f.setSimulateStyle(true);
        	}
        }
        
	    FOUserAgent foUserAgent = FORendererApacheFOP.getFOUserAgent(foSettingsHere, fopFactory);
                
        foSettingsHere.setOpcPackage(hfPkg);
        foSettingsHere.setApacheFopMime(MimeConstants.MIME_FOP_AREA_TREE);
        
        foSettingsHere.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
        foSettingsHere.getFeatures().remove(ConversionFeatures.PP_COMMON_DEEP_COPY);
        
        if (log.isDebugEnabled()) {
        	foSettingsHere.setFoDumpFile(new java.io.File(System.getProperty("user.dir") + "/hf.fo"));
        }

        ByteArrayOutputStream os = new ByteArrayOutputStream();
        
        if (useXSLT) {
        	Docx4J.toFO(foSettingsHere, os, Docx4J.FLAG_EXPORT_PREFER_XSL);
        } else {
        	Docx4J.toFO(foSettingsHere, 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
	    			
    				}
    			}    			
    			
    		}
    		
    	}
    	
    }    
    

}