ENI (5) ENI Expedient for Sedipualba: Resolutions in ENI format

 1. Introduction

In the previous post, the resolutions in ENI format have been achieved. 

Let's build the "Expedient" in ENI format


2. SedipualbaDecretsUtilsPaso2.class

package openadmin.sedipualba.decrets;

import java.io.File;
import java.io.FileWriter;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;

import java.nio.file.Paths;

import java.text.SimpleDateFormat;
import java.util.ArrayList;

import java.util.Date;
import java.util.List;
import java.util.Properties;

import org.apache.commons.io.FilenameUtils;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;

import org.json.JSONObject;



import eni.exp.TipoExpediente;
import eni.exp.TipoIndiceContenido;

import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.Unmarshaller;
import openadmin.utils.decrets.PDFUtils;
import ximodante.basic.utils.basic.ExecutionTypeEnum;
import ximodante.basic.utils.basic.FileUtilsEdu;
import ximodante.basic.utils.basic.PropertyUtilsEdu;
import ximodante.basic.utils.command.CmdUtils;


public class SedipualbaDecretsUtilsPaso2 {
    
    
    /**
     * Replaces the String "$(RESOURCES)/" by the route to the resources file
     * @param str
     * @param isExecutedFromJar
     * @return
     */
    //public static String replaceResourcesFilename(String str, boolean isExecutedFromJar) {
    public static String replaceResourcesFilename(String str, ExecutionTypeEnum execType) {
        String f1="locator.txt";
        String fName="";
        
        if (execType==ExecutionTypeEnum.JAR) {
            try {
                fName=FileUtilsEdu.getJarContainingFolder() + File.separator ;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        else {
            URL url = ClassLoader.getSystemResource(f1);
            fName=url.getFile().replace(f1,"");
        }    
        
        String result= str.replace("$(RESOURCES)/", fName);
        System.out.println(result);
        return result;
                
    }
    
        
    //XML Marshaller for TipoExpediente
    private static Marshaller getENIExpMarshaller() throws JAXBException { 
        JAXBContext context = JAXBContext.newInstance(TipoExpediente.class);
        Marshaller mar= context.createMarshaller();
        mar.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
        return mar;
    }
    
    private static Unmarshaller getENIExpUnmarshaller() throws JAXBException { 
        JAXBContext context = JAXBContext.newInstance(TipoExpediente.class);
        Unmarshaller unmar= context.createUnmarshaller();
        //unmar.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
        return unmar;
    }
        
    //Write a TipoDocumento object to a xml file
    public static void marshallENIExp(TipoExpediente eniExp, String fileName) throws JAXBException { 
        Marshaller mar=getENIExpMarshaller();
        mar.marshal(eniExp, new File(fileName));
    }
    
    //Write a TipoDocumento from a xml file to object
    public static TipoExpediente unmarshallENIExp(String fileName) throws JAXBException { 
        Unmarshaller unmar=getENIExpUnmarshaller();
        TipoExpediente eniExp=(TipoExpediente) unmar.unmarshal(new File(fileName));
        return eniExp;
    }
    
    //XML Marshaller for TipoExpediente
    private static Marshaller getENIExpIndContMarshaller() throws JAXBException { 
        JAXBContext context = JAXBContext.newInstance(TipoIndiceContenido.class);
        Marshaller mar= context.createMarshaller();
        mar.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
        return mar;
    }
            
    //Write a TipoDocumento object to a xml file
    private static void marshallENIExpIndCont(TipoIndiceContenido eniExp, String fileName) throws JAXBException { 
        Marshaller mar=getENIExpIndContMarshaller();
        mar.marshal(eniExp, new File(fileName));
    }
    
    public static void showMessage(String message) {
        showMessage(message, false); 
    }
    
    public static void showMessage(String message, boolean isError) {
        //myUI.access(() -> v1.add(new Span(message)));
        if (! isError) System.out.println("CessarLlibreDecrets " + ".showMessage: "+message);
        else System.out.println("ERROR:" + "CessarLlibreDecrets " + ".showMessage: "+message);
    }
    
    public static void execute(String[] args, ExecutionTypeEnum execType) throws Exception{
        int year=Integer.parseInt(args[1].trim());
        String organoDIR3=args[2].trim();
        String outputFolder=args[3].trim()+year;
        
        String decretNumformat=args[5].trim();
        
        String resolutionInfoFile=outputFolder+"/INFO"+year+".txt";
        String errors="";
        String fileError=outputFolder+"/0ERRORS.txt";
        
        //1. Documents
        PDDocument docIndexPdfWithLinks = null;
        PDDocument docIndexPdfNoLinks = null;
        Object[] docAndPageWithLinks=null;    
        Object[] docAndPageNoLinks=null;    
        
        
        //4. Properties
        Properties appProps=PropertyUtilsEdu.getProperties(execType, "app_encrypt");
        
        //5. Certificate's path. Certificate's parameters
        String certParams=appProps.getProperty("cert.params");
        certParams=replaceResourcesFilename(certParams, execType);
        System.out.println(certParams);
        
        //6. Autofirma's path
        //String autofirmaURL=appProps.getProperty("autofirma.url");
        
        SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy");
        SimpleDateFormat dayFormat = new SimpleDateFormat("dd");
        //--------------------------------------------------------
        //Capçalera de l'Index del llibre de resolucions
        //--------------------------------------------------------
        List<String> lines = Files.readAllLines(Paths.get(resolutionInfoFile), Charset.defaultCharset());
        int resCount=lines.size();
        JSONObject json=new JSONObject(lines.get(0));
        int resIni=json.getInt("numDecreto");
        Date date =ENIDocSedipualba.getFecha(json.get("fechaDecreto").toString()).toGregorianCalendar().getTime();
        int diaIni=Integer.parseInt(dayFormat.format(date));
        
        json=new JSONObject(lines.get(resCount-1));
        int resFi=json.getInt("numDecreto");
        date =ENIDocSedipualba.getFecha(json.get("fechaDecreto").toString()).toGregorianCalendar().getTime();
        int diaFi=Integer.parseInt(dayFormat.format(date));
        
        docAndPageWithLinks= ENIExpSedipualba.getFirstIndexPage(execType, year, resCount, diaIni, diaFi, resIni, resFi);
        docAndPageNoLinks= ENIExpSedipualba.getFirstIndexPage(execType, year, resCount, diaIni, diaFi, resIni, resFi);
        //--------------------------------------------------------
        //FI Capçalera de l'Index del llibre de resolucions
        //--------------------------------------------------------
        List<String[]>contentWithLinks=new ArrayList<String[]>();
        List<String[]>contentNoLinks=new ArrayList<String[]>();
        List<String[]>links  =new ArrayList<String[]>();
        List<String[]>noLinks  =new ArrayList<String[]>();
        
        String fileNameIndexWithLinks=outputFolder + java.io.File.separator + "Index."+year+".links.pdf" ;
        String fileNameIndexNoLinks=outputFolder + java.io.File.separator + "Index."+year+".nolinks.pdf" ;
        
        int lastDecret=0;
        int i=0;
        for (String line: lines) {
            System.out.println(i++);
            json=new JSONObject(line);
            int numDecret=json.getInt("numDecreto");
            //if (line.getNumero()<0) continue;
            //if (i >10) break; 
            
            showMessage("Decret "+ numDecret);
            if ((lastDecret+1)!=numDecret) 
                errors+="\n Error en comptador de decrets: Hem passat de " + lastDecret + " a " + numDecret;
            
            lastDecret=numDecret;
            date =ENIDocSedipualba.getFecha(json.get("fechaDecreto").toString()).toGregorianCalendar().getTime();
            String fecha=dateFormat.format(date);
            System.out.println("6.Fila de la taula de decrets");
            
            String[] lineContentWithLinks= {
                    String.format("%1$5s", numDecret),
                    "Vincle","Vincle","Vincle",
                    //PDFUtils.arregla(decret.getNombre()),
                    PDFUtils.arregla(json.getString("descripcion")),
                    fecha
            };
            contentWithLinks.add(lineContentWithLinks);
            
            String fileName=outputFolder+"/Decret_"+year+"_"+String.format(decretNumformat, numDecret)+".orig.pdf";
            String fileNameCSV=outputFolder+"/Decret_"+year+"_"+String.format(decretNumformat, numDecret)+".aut.pdf";
            int pkDoc=json.getInt("pkdocumento");
            String fileNameENI=outputFolder+"/ES_" + organoDIR3 + "_"+ year + "_DOC_RESOLUCIO_" + String.format(decretNumformat, numDecret) + "_"+ pkDoc+".xml";
            String[] lineLink= {null,
                    FilenameUtils.getName(fileName), 
                    FilenameUtils.getName(fileNameCSV),
                    FilenameUtils.getName(fileNameENI),
                    //null,
                    null,
                    null
            };
            links.add(lineLink);
            
            String[] lineContentNoLinks= {
                    String.format("%1$5s", numDecret),
                    PDFUtils.arregla(json.getString("descripcion")),
                    fecha
            };
            contentNoLinks.add(lineContentNoLinks);
            
            String[] lineNoLink= {null,
                    null,
                    //null,
                    null
            };
            noLinks.add(lineNoLink);
            
        }
        System.out.println("7. taula de decrets");
        docIndexPdfWithLinks=ENIExpSedipualba.saveIndexDecretsWithLinks(execType, (PDDocument)docAndPageWithLinks[0],(PDPage)docAndPageWithLinks[1],(float)docAndPageWithLinks[2], contentWithLinks,links,fileNameIndexWithLinks );
        
        //2. ENI Expedient
        TipoExpediente expENI=new TipoExpediente();
        expENI.setId(ENIExpSedipualba.getIdentificadorExp(year));
        //1. Posem totes les metadades
        expENI.setMetadatosExp(ENIExpSedipualba.getMetadatosExp(year));
                
        //2. Fiquem l'índex buit
        expENI.setIndice(ENIExpSedipualba.getIndiceExp(year));
        docIndexPdfNoLinks=ENIExpSedipualba.saveIndexDecretsNoLinks(execType, (PDDocument)docAndPageNoLinks[0],(PDPage)docAndPageNoLinks[1],(float)docAndPageNoLinks[2], contentNoLinks,noLinks,fileNameIndexNoLinks );
        expENI=ENIExpSedipualba.addPDFDocIndx(expENI, docIndexPdfNoLinks, year);
        
         
       //Firma del contenido
        String expContenidoFileName=outputFolder+"/contenido.xml";
        String expContenidoFirmaFileNameXades=outputFolder+"/contenido_firma.edu.XAdES-Detached.xsig";
        
        //xmlMapper.ObjectToFile(expENI.getIndice().getIndiceContenido(),expContenidoFileName );
        marshallENIExpIndCont(expENI.getIndice().getIndiceContenido(),expContenidoFileName);
        
        String expEniJaxbFName=outputFolder+"/0"+ENIExpSedipualba.getIdentificadorExp(year)+".ENI.NOSIGNED.xml";
        marshallENIExp(expENI,expEniJaxbFName);
        
        String AFirmaCmd=
                "/usr/bin/AutoFirma  sign " + 
                    " -i " + expContenidoFileName + 
                    //" -o " + expContenidoFirmaFileNameCades + 
                    "-o " + expContenidoFirmaFileNameXades + 
                    " -format xades " +
                    "-store" + " pkcs11:/usr/lib/libaetpkss.so " + //Tarjeta criptografica
                    "-alias "+  " EPN1" + 
                    "-config" +  "format=XAdES Detached";
        
        CmdUtils.executeCommand(AFirmaCmd);
        
        byte[] firmaContent=FileUtilsEdu.readFileAsByteArray(expContenidoFirmaFileNameXades);
        expENI=ENIExpSedipualba.firmarContenido(expENI, firmaContent, year, expContenidoFirmaFileNameXades);
        String expEniJaxbFNameSigned=outputFolder+"/0"+ENIExpSedipualba.getIdentificadorExp(year)+".ENI.xml";
        marshallENIExp(expENI,expEniJaxbFNameSigned);

        FileWriter writerObj = new FileWriter(fileError, false);
        writerObj.write(errors);
        writerObj.close();
                
        
    }

    public static void main(String[] args) {
        
        //OJO: Hen de tindre creat l'expedient "1355414W" que arreplega els decrets sense expedient
        try { //                                           descripcio        any    organoDIR3  carpeta a guardar    "fitxer excel a llegir format num decret" "expedient"
            SedipualbaDecretsUtilsPaso2.execute(new String[]{"LLIBRE_DECRETS","2022","L01462384","/home/eduard/SEGRA","SEGRAAdministrador.xlsx","%05d",         "1355414W"   }, ExecutionTypeEnum.NO_JAR);
        } catch (Exception e) {
            // 
            e.printStackTrace();
        }

    }

}


3. ENIExpSedipualba utility class

This class is for managing "expedients"

package openadmin.sedipualba.decrets;

import java.awt.Color;
import java.awt.Dimension;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;


import org.apache.commons.lang3.StringUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;

import eni.exp.EnumeracionEstados;
import eni.exp.Firmas;
import eni.exp.SignatureType;
import eni.exp.TipoContenido;
import eni.exp.TipoDocumentoIndizado;
import eni.exp.TipoExpediente;
import eni.exp.TipoFirmasElectronicas;
import eni.exp.TipoFirmasElectronicas.ContenidoFirma;
import eni.exp.TipoFirmasElectronicas.ContenidoFirma.FirmaConCertificado;
import eni.exp.TipoIndice;
import eni.exp.TipoIndiceContenido;
import eni.exp.TipoMetadatos;
import eni.exp.TipoMetadatos.Estado;
import openadmin.model.gexflow.original.document.ExpDocumento;
import openadmin.model.gexflow.original.document.decreto.ExpLineaLibroDecreto;
import openadmin.utils.decrets.PDFTables;
import openadmin.utils.decrets.PDFUtils;
import openadmin.utils.decrets.PDF_AUtils;
import openadmin.xsig.classes.AFirma;
import openadmin.xsig.classes.Object_EduContent_Type;
import openadmin.xsig.classes.SignatureEduType;
import ximodante.basic.utils.basic.ExecutionTypeEnum;
import ximodante.basic.utils.basic.FileUtilsEdu;
import ximodante.basic.utils.basic.HashUtils;
import ximodante.basic.utils.basic.PropertyUtilsEdu;
import ximodante.basic.utils.cmis.CMISEduImp;
import ximodante.basic.utils.cmis.ICMISEdu;

public class ENIExpSedipualba {

    public static String Titol = 
            "DILIGÈNCIA I ÍNDEX ELECTR�'NIC DE L'AGRUPACI�" DOCUMENTAL COMPRENSIVA DEL LLIBRE DE RESOLUCIONS DEL ${any}"
            + " (ENLLAÇ A L'EXPEDIENT ENI)";
    
    public static String diligencia01=
            "Per fer constar que, en la data de la signatura electrònica d'este document, a través d’esta " 
            + "Diligència que l'encapçala, es formalitza i es tanca l'agrupació documental comprensiva del Llibre de " 
            + "Resolucions, corresponents a les emeses per l'Alcaldia i Regidories Delegades durant l'any ${any}. Açò " 
            + "es duu a terme en compliment de l'obligació d'esta Secretaria Municipal de transcriure al Llibre de " 
            + "Resolucions, siga quin sigui el seu suport, les dictades per la presidència, pels membres de la "
            + "corporació que resolguen per delegació i les de qualsevol altre òrgan amb competències "
            + "resolutives, tal com preveu l'article 3 del Reial Decret 128/2018, de 16 de març, pel qual es regula el "
            + "règim jurídic dels funcionaris d'administració local amb habilitació de caràcter nacional , amb el "  
            + "contingut expressat en l'índex electrònic que també s'hi incorpora.\n" 
            + "El Llibre de Resolucions de ${any}, queda integrat, conforme es detalla a l'índex electrònic " 
            + "a continuació, per ${resCount} resolucions en l'original electrònic, que es corresponen amb les dictades "
            + "del dia ${diaIni} de gener al ${diaFi} de desembre de 2021 , numerades correlativament de la número ${resIni} a la ${resFi}, "
            + "i que compleixen els requisits de l'art. 26 de la Llei 39/2015, d'1 d'octubre, del procediment administratiu comú "
            + "de les administracions públiques i la resta de normativa aplicable.\n";
    
    public static String diligencia02=    
            "Índex electrònic del Llibre de Resolucions de ${any}:\n";
            
    public static String diligencia03=        
            "Este Índex electrònic de l'agrupació documental del Llibre de Resolucions de ${any} conté la identificació "
            + "substancial de tots els documents electrònics (resolucions electròniques) que el componen, " 
            + "degudament ordenat per a reflectir-ne la seva disposició, i també altres dades, a fi de preservar-ne la " 
            + "integritat i permetre'n la recuperació, en els termes de l'article 70.3 de l'esmentada Llei 39/2015, d'1 " 
            + "d'octubre de 2015 en format XML conforme a l'annex II de la Norma Tècnica d'Interoperabilitat de Document " 
            + "Electrònic i de l'annex II de la Norma Tècnica dInteroperabilitat dExpedient Electrònic.\n"
            + "L'Índex comprèn i reflecteix, de manera individualitzada, per cada una de les resolucions emeses durant " 
            + "l'exercici ${any} els camps per columnes següents:\n"
            + "Núm. de resolució: es correspon amb el número de Resolució.\n"
            + "Original: enllaç al document original en format pdf.\n" 
            + "Autèntic: enllaç al document en format pdf, amb el codi segur de verificació (CSV) i detall de les firmes practicades.\n" 
            + "ENI: Enllaç al document en format ENI.\n" 
            
            + "Nom o designació de la Resolució: esta descripció incorpora dades que identifiquen el departament emissor " 
            + "de la resolució i l'expedient en què s'ha generat.\n"
            + "Descripció: breu resum del contingut de la resolució que es correspon amb la que hauria de ser la capçalera " 
            + "de la resolució\n"
            + "Data: es correspon amb el dia i l'hora en què l'últim signant, regidoria o secretari, va firmar i, per tant, " 
            + "es va incorporar al Llibre.\n"
            + "Enllaços al format Document.xml\n\n" 
            + "Este document es signa electrònicament incorporant-hi tots els camps transcrits i les metadades " 
            + "corresponents per garantir l'autenticitat i la integritat del contingut d'esta Diligència, de l'índex, i " 
            + "per extensió, dels documents electrònics (resolucions) formen el Llibre de Resolucions de ${any} així " 
            + "com la seva estructura. La signatura correspon, en virtut del RD 128/2018 en connexió amb l'art. 92bis de la " 
            + "Llei 7/1985, de 2 dabril, Reguladora de les Bases de Règim Local, amb el secretari " 
            + "municipal, ja que es refereix a una atribució reservada en exclusiva a este lloc de treball.";
            
            

    public static String DIR3ORGANO = "L01462384";

    public static XMLGregorianCalendar getDate2XMLGregorian(Date date) throws DatatypeConfigurationException {

        if (date==null) return null;
        GregorianCalendar cal = new GregorianCalendar();
        cal.setTime(date);

        XMLGregorianCalendar xmlGregCal = DatatypeFactory.newInstance().newXMLGregorianCalendar(cal);
        return xmlGregCal;
    }

    /**********************************************************************
     * 
     * 1. METADATOS DEL EXPEDIENTE
     * 
     ***********************************************************************/
    public static String getClasificacion(int any) {
        return DIR3ORGANO + "_PRO_" + "LLIBRE_RESOLUCIONS_" + any;
    }

    public static String getIdentificadorExp(int any) {
        return "ES_" + DIR3ORGANO + "_" + any + "_EXP_" + "LLIBRE_RESOLUCIONS";
    }

    public static List<String> getOrgano() {
        List<String> organos = new ArrayList<String>();
        organos.add(DIR3ORGANO);
        return organos;
    }

    public static Estado getEstado() {
        Estado estado = new Estado();
        estado.setValue(EnumeracionEstados.E_02);// E_02=Cerrado
        return estado;
    }

    public static XMLGregorianCalendar getFechaAperturaExpediente(int any)
            throws ParseException, DatatypeConfigurationException {
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        Date date = format.parse("" + any + "-01-01 00:00:00");

        GregorianCalendar cal = new GregorianCalendar();
        cal.setTime(date);

        XMLGregorianCalendar xmlGregCal = DatatypeFactory.newInstance().newXMLGregorianCalendar(cal);
        return xmlGregCal;
    }

    public static TipoMetadatos getMetadatosExp(int any) throws ParseException, DatatypeConfigurationException {
        TipoMetadatos metaExpENI = new TipoMetadatos();
        metaExpENI.setVersionNTI("http://administracionelectronica.gob.es/ENI/XSD/v1.0/expediente-e");
        metaExpENI.setIdentificador(getIdentificadorExp(any));
        metaExpENI.setOrgano(getOrgano());
        metaExpENI.setFechaAperturaExpediente(getFechaAperturaExpediente(any));
        metaExpENI.setClasificacion(getClasificacion(any));
        metaExpENI.setEstado(getEstado());
        metaExpENI.setInteresado(getOrgano());
        metaExpENI.setId(DIR3ORGANO + "_" + any + "_METADADES_LLIBRE_RESOLUCIONS");
        return metaExpENI;
    }
    
    /**********************************************
     * METADADES ADDICIONALS
     **********************************************/
    public static void getMetadatosAddicExp(int any) {
        //TipoMetadadosAdicionales 
    }

    
    
    /**********************************************
     * INDICE
     **********************************************/

    public static String getIdentificadorDocIndex(int any) {
        return "ES_" + DIR3ORGANO + "_" + any + "_INDEX_LLIBRE_RESOLUCIONS";
    }
    
    public static String getIdentificadorDocIndexContenido(int any) {
        return "ES_" + DIR3ORGANO + "_" + any + "_INDEX_CONTENIDO_LLIBRE_RESOLUCIONS";
    }

    public static TipoIndice getIndiceExp(int any) throws DatatypeConfigurationException {
        TipoIndice indiceENI = new TipoIndice();

        TipoIndiceContenido indiceContenidoENI = new TipoIndiceContenido();
        indiceContenidoENI.setFechaIndiceElectronico(getDate2XMLGregorian(new Date(System.currentTimeMillis())));
        //indiceContenidoENI.setId(any + "_LLIBRE_RESOLUCIONS");
        indiceContenidoENI.setId(getIdentificadorDocIndexContenido(any));
        indiceENI.setIndiceContenido(indiceContenidoENI);
        indiceENI.setId(getIdentificadorDocIndex(any));
        return indiceENI;
    }

    /**
     * Afegeix la referencia a un document (TipoDocumentoIndizado) a l'index de
     * l'expedient
     * 
     * @param expENI
     * @param docContent
     * @param linea
     * @param any
     * @param formatNumDecret
     * @return
     * @throws DatatypeConfigurationException
     */
    public static TipoExpediente addDoc2Indx(TipoExpediente expENI, byte[] docContent, ExpLineaLibroDecreto linea,
            int any, String formatNumDecret) throws DatatypeConfigurationException {
        ExpDocumento decret = linea.getExpDocumento();

        TipoDocumentoIndizado docInd = new TipoDocumentoIndizado();
        docInd.setId(linea.getCodigo().replace("Nº Decreto: ","Decret_"));
        docInd.setIdentificadorDocumento(getIdentificadorDoc(any, linea.getNumero(), formatNumDecret));
        docInd.setValorHuella(HashUtils.getSH512Hash(docContent));
        docInd.setFuncionResumen("http://www.w3.org/2001/04/xmlenc#sha512");
        docInd.setFechaIncorporacionExpediente(ENIExpSedipualba.getDate2XMLGregorian(decret.getFechaUltimaFirma()));
        docInd.setOrdenDocumentoExpediente(String.format(formatNumDecret, linea.getNumero()));
        expENI.getIndice().getIndiceContenido().getDocumentoIndizadoOrExpedienteIndizadoOrCarpetaIndizada().add(docInd);
        return expENI;
    }
    /* OBSOLETE OJO !!!! Hay que firmar el contenido*/
    public static TipoExpediente firmarContenido(TipoExpediente expENI, byte[] firmaContent, int any, String tipo)  {
        
        FirmaConCertificado unaFrmCCert= new FirmaConCertificado();
        unaFrmCCert.setFirmaBase64(Base64.getEncoder().encodeToString(firmaContent));
        //unaFrmCCert.setReferenciaFirma(null); //????
        //unaFrmCCert.setSignature(null);
        
        ContenidoFirma unContenido=new ContenidoFirma();
        unContenido.setFirmaConCertificado(unaFrmCCert);
        
        TipoFirmasElectronicas unaFirma=new TipoFirmasElectronicas();
        unaFirma.setId("Contenido_"+ any);
        if (tipo.toLowerCase().contains("xades")) unaFirma.setTipoFirma("TF02");//Xades internally detached 
        if (tipo.toLowerCase().contains("cades")) unaFirma.setTipoFirma("TF05");
        unaFirma.setTipoFirma("TF05"); //CADES implicid (attached)
        //unaFirma.setTipoFirma("TF04"); //NO PERMITIT EN EXPEDIENTS !!!! CADES explicid (NOT attached)
        unaFirma.setContenidoFirma(unContenido);
        unaFirma.setRef("#"+getIdentificadorDocIndexContenido(any));
        
        
        
        expENI.getIndice().setFirmas(new Firmas());
        expENI.getIndice().getFirmas().getFirma().add(unaFirma);
        return expENI;
    }
    


    /**********************************************
     * CONTENIDO
     **********************************************/

    public static String getIdentificadorDoc(int any, long nRes, String formatNumDecret) {
        return "ES_" + DIR3ORGANO + "_" + any + "_DOC_RESOLUCIO_" + String.format(formatNumDecret, nRes);
    }

    /**********************************************
     * VISUALIZACION INDICE
     **********************************************/

    /**
     * Afegeix el document index a l'expedient (TipoContenido= Visualización índice)
     * 
     * @param expENI
     * @param docContent
     * @param any
     * @return
     * @throws DatatypeConfigurationException
     * @throws IOException
     */
    public static TipoExpediente addPDFDocIndx(TipoExpediente expENI, PDDocument docIndexPdf, int any)
            throws DatatypeConfigurationException, IOException {

        byte[] docContent = PDFUtils.getContent(docIndexPdf);
        TipoContenido content = new TipoContenido();
        content.setId("VISUALITZACIO_INDEX");
        //content.setValorBinario(Base64.getEncoder().encode(docContent));
        content.setValorBinario(new String(Base64.getEncoder().encode(docContent)));
        content.setNombreFormato("PDF");
        expENI.setVisualizacionIndice(content);
        return expENI;
    }
    
    /**********************************************
     * INDICE
     * @throws Exception 
     **********************************************/
    /**
     * Create the first page of the pdf index document
     * @param execType
     * @param any
     * @param resCount
     * @param diaIni
     * @param diaFi
     * @param resIni
     * @param resFi
     * @return
     * @throws Exception
     */
    public static Object[] getFirstIndexPage(ExecutionTypeEnum execType, int any, int resCount, int diaIni, int diaFi, int resIni, int resFi) throws Exception {
        
        float FONT_SIZE = 9;
        float TITLE_FONT_SIZE = 12;//15
        //float LEADING = -1.5f * FONT_SIZE;
        float LEADING = 1.5f * FONT_SIZE;
        
        PDDocument doc=new PDDocument();
        PDPage page = new PDPage();
        doc.addPage(page);
        //PDFont FONT = PDType1Font.HELVETICA;
        //Arial
        // Create a new font object by loading a TrueType font into the document
        //PDFont FONT = PDTrueTypeFont.loadTTF(doc, "Arial.ttf");
         PDFont FONT = PDType0Font.load(doc, new File(FileUtilsEdu.getRelativeResourceFile(execType, "fonts/Arial.ttf")));
         PDFont FONT_BOLD = PDType0Font.load(doc, new File(FileUtilsEdu.getRelativeResourceFile(execType, "fonts/FontsFree-Net-arial-bold.ttf")));     
        
        
        PDPageContentStream contentStream = new PDPageContentStream(doc, page);

        PDRectangle mediaBox = page.getMediaBox();
        float mil2Point=mediaBox.getWidth()/210;
        float marginY = 10*mil2Point; // 15; 80;
        float marginX = 25*mil2Point;//60;
        float width = mediaBox.getWidth() - 35*mil2Point;//2 * marginX;
        float startX = mediaBox.getLowerLeftX() + marginX;
        float startY = mediaBox.getUpperRightY() - marginY;
        
        
        
        float posY=startY;

        String titol=StringUtils.replace(Titol, "${any}", ""+any, 1000);
        
        String text01 = StringUtils.replace(diligencia01, "${any}", ""+any,1000);
        text01 = StringUtils.replace(text01, "${resCount}", ""+resCount,1000);
        text01 = StringUtils.replace(text01, "${diaIni}", ""+diaIni,1000);
        text01 = StringUtils.replace(text01, "${diaFi}", ""+diaFi,1000);
        text01 = StringUtils.replace(text01, "${resIni}", ""+resIni,1000);
        text01 = StringUtils.replace(text01, "${resFi}", ""+resFi,1000);
        
        String text02 = StringUtils.replace(diligencia02, "${any}", ""+any,1000);
        text02 = StringUtils.replace(text02, "${resCount}", ""+resCount,1000);
        text02 = StringUtils.replace(text02, "${diaIni}", ""+diaIni,1000);
        text02 = StringUtils.replace(text02, "${diaFi}", ""+diaFi,1000);
        text02 = StringUtils.replace(text02, "${resIni}", ""+resIni,1000);
        text02 = StringUtils.replace(text02, "${resFi}", ""+resFi,1000);
        
        String text03 = StringUtils.replace(diligencia03, "${any}", ""+any,1000);
        text03 = StringUtils.replace(text03, "${resCount}", ""+resCount,1000);
        text03 = StringUtils.replace(text03, "${diaIni}", ""+diaIni,1000);
        text03 = StringUtils.replace(text03, "${diaFi}", ""+diaFi,1000);
        text03 = StringUtils.replace(text03, "${resIni}", ""+resIni,1000);
        text03 = StringUtils.replace(text03, "${resFi}", ""+resFi,1000);
        
        
        String imagePath=FileUtilsEdu.getRelativeResourceFile(execType, "images/capsalera1.png");
        PDImageXObject capsalera = PDImageXObject.createFromFile(imagePath,doc);
        contentStream.drawImage(capsalera, marginX, startY-30*mil2Point,width, 30*mil2Point); //35
        contentStream.close();
        
        startY=(float) (startY-30*mil2Point-1*TITLE_FONT_SIZE);
        
        contentStream = new PDPageContentStream(doc, page,AppendMode.APPEND,false);
        contentStream.beginText();
        
        PDFUtils.addParagraph(contentStream, width, startX, startY, titol, true, TITLE_FONT_SIZE, FONT_BOLD, LEADING, Color.BLACK, posY);
        PDFUtils.addParagraph(contentStream, width, 0, -TITLE_FONT_SIZE, text01, true, FONT_SIZE, FONT, LEADING, Color.BLACK, posY);
        PDFUtils.addParagraph(contentStream, width, 0, -TITLE_FONT_SIZE, text02, true, FONT_SIZE, FONT_BOLD, LEADING, Color.BLACK, posY);
        float posYNow= PDFUtils.addParagraph(contentStream, width, 0, -TITLE_FONT_SIZE, text03, true, FONT_SIZE, FONT, LEADING, Color.BLACK, posY);
        contentStream.endText();
        contentStream.close();
        
        Object[] obs= {doc, page, posYNow};
        return obs;
       
    }
    
    /**
     * Add resolutions to the pdf index documents 
     * @param execType
     * @param doc
     * @param page
     * @param posYNow
     * @param lContent
     * @param lLinks
     * @param fileName
     * @throws Exception
     */
    public static PDDocument saveIndexDecretsWithLinks(ExecutionTypeEnum execType, PDDocument doc, PDPage page, float posYNow, List<String[]>lContent, List<String[]> lLinks, String fileName) throws Exception   {
        String[] titles= {"Núm Reso- lució","Original","Autèntic","ENI","Descripció","Data resolució"};
        boolean[] titleRotates= {false,true,true,true,false,false};
        boolean[] textRotates=  {false,true,true,true,false,false};
        boolean[] textLinks=textRotates;
        
        float[] proportions={6.8f,2.75f,2.75f,2.75f,60f,9.5f};
        
        //float margin = 50;
        float leftMargin = 25/0.375f;
        // starting y position is whole page height subtracted by top and bottom margin
        //float yStartNewPage = page.getMediaBox().getHeight() - (2 * margin);
        //float yStart = page.getMediaBox().getHeight() - (500);
        float yStart =page.getMediaBox().getHeight() - posYNow -15/0.375f;
        float yStartNewPage = page.getMediaBox().getHeight() - (40 /0.375f);
        // we want table across whole page width (subtracted by left and right margin ofcourse)
        float tableWidth = page.getMediaBox().getWidth() - (25+15)/0.375f ;

        boolean drawContent = true;
        boolean drawLines = true;
        //float yStart = yStartNewPage;
        //float bottomMargin = 70;
        float bottomMargin = 20/0.375f;
        // y position is your coordinate of top left corner of the table
        float yPosition = 550;
        
        float rowHeight=20;
        PDRectangle mediaBox = page.getMediaBox();
        float pageWidth = mediaBox.getWidth();
        float pageHeight = mediaBox.getHeight();
        /*
        System.out.println("pageWidth="+pageWidth);
        System.out.println("pageHeight="+pageHeight);
        System.out.println("leftMargin="+leftMargin);
       // System.out.println("rightMargin="+rightMargin);
        System.out.println("bottomMargin="+bottomMargin);
        System.out.println("yStartNewPage="+yStartNewPage);
        System.out.println("tableWidth="+tableWidth);
        */
        
        
        //createTable(yStart, yStartNewPage, bottomMargin, tableWidth, margin, rowHeight, doc, page, drawLines, drawContent, content);
        Map<Integer,List<Object[]>> possitions= PDFTables.createTable01(yStart, yStartNewPage, bottomMargin, tableWidth, leftMargin, rowHeight, 
                doc, page, drawLines, drawContent, proportions, titles, titleRotates, lContent, textRotates, textLinks, lLinks, execType);
        
        PDFTables.createLinks(doc,possitions);
        PDFTables.addPageNumbers(execType, doc, leftMargin, 15/0.375f, 15/0.375f,bottomMargin);
        
        //doc.save("/home/eduard/kk/provaTaula.pdf");
        
        //Fem que siga PDF v1.7 (PDF/A) segons email Jesús Romero
        doc=PDF_AUtils.setVersionPDFA(doc, fileName, execType);
        
        doc.save(fileName);
        return doc;
    }
    
    public static PDDocument saveIndexDecretsNoLinks(ExecutionTypeEnum execType, PDDocument doc, PDPage page, float posYNow, List<String[]>lContent, List<String[]> lLinks, String fileName) throws Exception   {
        String[] titles= {"Núm Reso- lució","Descripció","Data última firma"};
        boolean[] titleRotates= {false,false,false};
        boolean[] textRotates=  {false,false,false};
        boolean[] textLinks=textRotates;
        
        float[] proportions={6.8f,65f,9.5f};
        
        //float margin = 50;
        float leftMargin = 25/0.375f;
        // starting y position is whole page height subtracted by top and bottom margin
        //float yStartNewPage = page.getMediaBox().getHeight() - (2 * margin);
        //float yStart = page.getMediaBox().getHeight() - (500);
        float yStart =page.getMediaBox().getHeight() - posYNow -15/0.375f;
        float yStartNewPage = page.getMediaBox().getHeight() - (40 /0.375f);
        // we want table across whole page width (subtracted by left and right margin ofcourse)
        float tableWidth = page.getMediaBox().getWidth() - (25+15)/0.375f ;

        boolean drawContent = true;
        boolean drawLines = true;
        //float yStart = yStartNewPage;
        //float bottomMargin = 70;
        float bottomMargin = 20/0.375f;
        // y position is your coordinate of top left corner of the table
        float yPosition = 550;
        
        float rowHeight=20;
        PDRectangle mediaBox = page.getMediaBox();
        float pageWidth = mediaBox.getWidth();
        float pageHeight = mediaBox.getHeight();
        /*
        System.out.println("pageWidth="+pageWidth);
        System.out.println("pageHeight="+pageHeight);
        System.out.println("leftMargin="+leftMargin);
       // System.out.println("rightMargin="+rightMargin);
        System.out.println("bottomMargin="+bottomMargin);
        System.out.println("yStartNewPage="+yStartNewPage);
        System.out.println("tableWidth="+tableWidth);
        */
        
        
        //createTable(yStart, yStartNewPage, bottomMargin, tableWidth, margin, rowHeight, doc, page, drawLines, drawContent, content);
        Map<Integer,List<Object[]>> possitions= PDFTables.createTable01(yStart, yStartNewPage, bottomMargin, tableWidth, leftMargin, rowHeight, 
                doc, page, drawLines, drawContent, proportions, titles, titleRotates, lContent, textRotates, textLinks, lLinks, execType);
        
        //PDFTables.createLinks(doc,possitions);
        PDFTables.addPageNumbers(execType, doc, leftMargin, 15/0.375f, 15/0.375f,bottomMargin);
        
        //doc.save("/home/eduard/kk/provaTaula.pdf");
        
        //Fem que siga PDF v1.7 (PDF/A) segons email Jesús Romero
        doc=PDF_AUtils.setVersionPDFA(doc, fileName, execType);
        
        doc.save(fileName);
        return doc;
    }
    
    


    public static void main(String[] args) {

    }

}


4. PDFUtils Utility class

public class PDFUtils {
    /**
     * Convert html to pdf
     * 
     * @param inputHtmlPath
     * @param outputPdfPath
     * @throws IOException
     */
    public static final void html2Pdf(String inputHtmlPath, String outputPdfPath) throws IOException {
        // Read the html document
        Document doc = Jsoup.parse(inputHtmlPath, "UTF-8");
        doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml);

        // Write the pdf document
        OutputStream os = new FileOutputStream(outputPdfPath);
        PdfRendererBuilder builder = new PdfRendererBuilder();
        builder.withUri(outputPdfPath);
        builder.toStream(os);
        builder.withW3cDocument(new W3CDom().fromJsoup(doc), "/");
        builder.run();
        os.close();
    }

    public static final void createPdfWithLink(String outputPdfPath) throws IOException {
        PDDocument doc = new PDDocument ();
        PDPage page = new PDPage (new PDRectangle (250, 150));
        doc.addPage (page);
  
        PDPageContentStream contentStream = new PDPageContentStream (doc, page);
        PDAnnotationLink txtLink = new PDAnnotationLink ();
        
        PDAnnotationLink txtLink1 = new PDAnnotationLink ();

          // border style
         PDBorderStyleDictionary linkBorder = new PDBorderStyleDictionary ();
         linkBorder.setStyle (PDBorderStyleDictionary.STYLE_UNDERLINE);
         linkBorder.setWidth (10);
         txtLink.setBorderStyle (linkBorder);
         txtLink1.setBorderStyle (linkBorder);

         // Border color
         final Color color = Color.GREEN;
         final float [] components = new float [] { color.getRed () / 255f, color.getGreen () / 255f, color.getBlue () / 255f };
         txtLink.setColor (new PDColor (components, PDDeviceRGB.INSTANCE));
         txtLink1.setColor (new PDColor (components, PDDeviceRGB.INSTANCE));

         // Destination URI
         final PDActionURI action = new PDActionURI ();
         //action.setURI ("https://www.helger.com"); //OK
         //action.setURI ("pdf.pdf"); //OK
         //action.setURI ("/home/edu/Descargas/Diptico de VALIDe.pdf"); //MAL
         //action.setURI ("file:///home/edu/Documentos/Transporte%20Musical.pdf"); //OK
         //action.setURI ("file://./Transporte%20Musical.pdf"); // MAL
         //action.setURI ("file://Transporte%20Musical.pdf"); // MAL
         //action.setURI ("file:Transporte%20Musical.pdf"); // MAL
         //action.setURI ("Transporte%20Musical.pdf"); // MAL
         action.setURI ("Transporte Musical.pdf"); // BIEN
         //action.setURI ("//home/edu/Descargas/Diptico de VALIDe.pdf"); //MAL
         //action.setURI ("file:///home/edu/Descargas/Diptico de VALIDe.pdf"); //OK
         txtLink.setAction (action);
         
         //Destination file
         //PDSimpleFileSpecification dest=new PDSimpleFileSpecification();
         PDFileSpecification dest=new PDSimpleFileSpecification();
         dest.setFile("/home/edu/Descargas/Diptico de VALIDe.pdf");
         PDActionRemoteGoTo action1= new PDActionRemoteGoTo();
         //PDActionGoTo action1= new PDActionGoTo();
         action1.setOpenInNewWindow(OpenMode.NEW_WINDOW);
         //action1.setDestination(dest);
         
         action1.setFile(dest);
         txtLink1.setAction (action1);

         // Position
         final PDRectangle position = new PDRectangle ();
         position.setLowerLeftX (10);
         position.setLowerLeftY (10);
         position.setUpperRightX (200);
         position.setUpperRightY (10 + 2 + 10 + 2);
         txtLink.setRectangle (position);
         page.getAnnotations().add (txtLink);
         
      // Position
         final PDRectangle position1 = new PDRectangle ();
         position1.setLowerLeftX (10);
         position1.setLowerLeftY (90);
         position1.setUpperRightX (200);
         position1.setUpperRightY (90 + 2 + 10 + 2);
         txtLink1.setRectangle (position1);
         page.getAnnotations().add (txtLink1);

         // Main page content
         contentStream.beginText ();
         contentStream.newLineAtOffset (12, 12);
         contentStream.setFont (PDType1Font.COURIER_BOLD, 10);
         contentStream.showText ("This is linked to the outside world");
         contentStream.endText ();
         
         contentStream.beginText ();
         contentStream.newLineAtOffset (12, 92);
         contentStream.setFont (PDType1Font.COURIER_BOLD, 10);
         contentStream.showText ("This is linked to the outside world 111111111111");
         contentStream.endText ();
         
         contentStream.close();
         
         doc.save(outputPdfPath);
        
    }
    /**
     * Write texLine ant a possition and create a link to a file in the same folder and adds an image to the top of the page.
     * @param doc
     * @param textLine
     * @param posX
     * @param posY
     * @param linkedPDFRelativePath
     * @param imageHeaderPath
     * @return
     * @throws IOException
     */
    public static final PDDocument newTextWithLink(PDDocument doc, String textLine, int posX, int posY, String linkedPDFRelativePath, String imageHeaderPath) throws IOException {
        PDPage page=null;
        if (doc==null) doc = new PDDocument ();
        int lastPage= doc.getNumberOfPages()-1;
        
        if (lastPage<0 || posY<136) {
            page = new PDPage (PDRectangle.A4);
            System.out.println("HEIGHT="+page.getMediaBox().getHeight());
            System.out.println("WIDTH="+page.getMediaBox().getWidth());;
            System.out.println("LowerLeftX="+page.getMediaBox().getLowerLeftX());
            System.out.println("LowerLeftY="+page.getMediaBox().getLowerLeftY());
            System.out.println("UpperRightX="+page.getMediaBox().getUpperRightX());
            System.out.println("UpperRightY="+page.getMediaBox().getUpperRightY());
            
            doc.addPage (page);
            float scale=0.55f;
            PDImageXObject pdImage = PDImageXObject.createFromFile(imageHeaderPath, doc);
            PDPageContentStream headerContentStream = new PDPageContentStream(doc, page,AppendMode.APPEND,false);
            //headerContentStream.drawImage(pdImage, 10, page.getMediaBox().getHeight()-130);
            headerContentStream.drawImage(pdImage, 10, page.getMediaBox().getHeight()-100,
                    pdImage.getWidth()* scale, pdImage.getHeight() * scale);
            
            headerContentStream.beginText ();
            headerContentStream.newLineAtOffset (200, page.getMediaBox().getHeight() - 110);
            //contentStream.setFont (PDType1Font.COURIER_BOLD, 14);
            headerContentStream.setFont(PDType1Font.HELVETICA_BOLD, 16);
            headerContentStream.showText ("RELACI�" DE DECRETS");
            headerContentStream.newLineAtOffset (0, page.getMediaBox().getHeight() - 500);
            headerContentStream.endText ();
            headerContentStream.beginText ();
            headerContentStream.newLineAtOffset (page.getMediaBox().getWidth()-100, 50 );
            headerContentStream.setFont(PDType1Font.HELVETICA_OBLIQUE, 12);
            int myPage=lastPage+2;
            headerContentStream.showText ("Pàgina:" + myPage);
            
            headerContentStream.endText ();
            //headerContentStream.drawLine(0, 0, posY, lastPage);
            headerContentStream.moveTo(200, page.getMediaBox().getHeight() - 110-4);
            headerContentStream.lineTo(383, page.getMediaBox().getHeight() - 110-4);
            /*
            for (int i=0; i<100; i++) {
                headerContentStream.moveTo(100,100);
                headerContentStream.lineTo(100+5*i,100*i);
                //headerContentStream.drawLine(0, 0, 300, 400+i);
            }*/
            headerContentStream.stroke();
            headerContentStream.close();
            
        } else page=doc.getPage(lastPage);
        
        PDPageContentStream contentStream = new PDPageContentStream (doc, page,AppendMode.APPEND, false);
        PDAnnotationLink txtLink = new PDAnnotationLink ();
        
        // border style
        PDBorderStyleDictionary linkBorder = new PDBorderStyleDictionary ();
        //linkBorder.setStyle (PDBorderStyleDictionary.STYLE_UNDERLINE);
        linkBorder.setStyle (PDBorderStyleDictionary.STYLE_BEVELED);
        linkBorder.setWidth (0);
        txtLink.setBorderStyle (linkBorder);
        

        // Border color (NOT USED)
        
        //final Color color = Color.GREEN;
        final Color color = Color.CYAN;
        final float [] components = new float [] { color.getRed()/255f, color.getGreen()/255f, color.getBlue()/255f };
        txtLink.setColor (new PDColor (components, PDDeviceRGB.INSTANCE));
        

        // Destination URI
        final PDActionURI action = new PDActionURI ();
        action.setURI(linkedPDFRelativePath);
        //action.setURI ("https://www.helger.com"); //OK
        //action.setURI ("pdf.pdf"); //OK
        //action.setURI ("file:///home/edu/Documentos/Transporte%20Musical.pdf"); //OK
        //action.setURI ("Transporte Musical.pdf"); // OK
        //action.setURI ("file:///home/edu/Descargas/Diptico de VALIDe.pdf"); //OK
         
        //action.setURI ("/home/edu/Descargas/Diptico de VALIDe.pdf"); //MAL
        //action.setURI ("file://./Transporte%20Musical.pdf"); // MAL
        //action.setURI ("file://Transporte%20Musical.pdf"); // MAL
        //action.setURI ("file:Transporte%20Musical.pdf"); // MAL
        //action.setURI ("Transporte%20Musical.pdf"); // MAL
        //action.setURI ("//home/edu/Descargas/Diptico de VALIDe.pdf"); //MAL
                  
        txtLink.setAction (action);
         
        // Position
        final PDRectangle position = new PDRectangle ();
        position.setLowerLeftX (posX);
        position.setLowerLeftY (page.getMediaBox().getHeight()-posY);
        position.setUpperRightX (page.getMediaBox().getUpperRightX()-posX);
        position.setUpperRightY (page.getMediaBox().getHeight() -(posY + 2 + 10 + 2));
        txtLink.setRectangle (position);
        page.getAnnotations().add (txtLink);
         
        // Main page content
        contentStream.beginText ();
        contentStream.newLineAtOffset (posX, page.getMediaBox().getHeight() - (posY + 2 + 10 + 2));
        //contentStream.setFont (PDType1Font.COURIER_BOLD, 14);
        contentStream.setFont(PDType1Font.HELVETICA_OBLIQUE, 12);
        final Color color1 = Color.BLUE;
        final float [] components1 = new float [] { color1.getRed()/255f, color1.getGreen()/255f, color1.getBlue()/255f };
        //contentStream.setStrokingColor(new PDColor (components1, PDDeviceRGB.INSTANCE));
        contentStream.setNonStrokingColor(new PDColor (components1, PDDeviceRGB.INSTANCE));
        contentStream.showText ( textLine);
        contentStream.endText ();
        contentStream.close();
         
        
         
        return doc; 
    }
    
    /*
    public static void main(String[] args) throws IOException {
        //html2Pdf("/home/edu/TYPESCRIPT/prova20/pdf.html", "/home/edu/TYPESCRIPT/prova20/pdf.pdf");
        //createPdfWithLink("/home/edu/TYPESCRIPT/prova20/pdf1.pdf");
        
        //Create a PDF document with a header
        PDDocument doc=null;
        int posX=80;
        int posY=135-25;
        for (int i=0; i<100; i++) {
            System.out.println(i);
            posY =posY +20;
            if (posY>PDRectangle.A4.getHeight()-90) posY=135;
            //doc= newTextWithLink(doc, "Hola " +i, 20, posY, "doc"+i+".pdf", "/home/edu/Imágenes/escut.png");
            String headerFile=FileUtilsEdu.getPathFromResourcesFolder("images/capsalera.png");
            doc= newTextWithLink(doc, "Hola " +i, posX, posY, "doc"+i+".pdf", headerFile);
        }
        doc.save("/home/eduard/kk/pdf_prova.pdf");
        
    }
    */
    
    
    /*************************************************************************
     * PARAGRAPHS !!!
     * see https://memorynotfound.com/apache-pdfbox-adding-multiline-paragraph/
     *************************************************************************/
    
    

    public static void main(String... args)  {
        PDFont FONT = PDType1Font.HELVETICA;
                
        float FONT_SIZE = 12;
        //float LEADING = -1.5f * FONT_SIZE;
        //float LEADING = 1.5f * FONT_SIZE;
        float LEADING = 1.1f * FONT_SIZE;
        
        

        try (final PDDocument doc = new PDDocument()){

            PDPage page = new PDPage();
            doc.addPage(page);
            PDPageContentStream contentStream = new PDPageContentStream(doc, page);

            PDRectangle mediaBox = page.getMediaBox();
            float marginY = 80;
            float marginX = 60;
            float width = mediaBox.getWidth() - 2 * marginX;
            float startX = mediaBox.getLowerLeftX() + marginX;
            float startY = mediaBox.getUpperRightY() - marginY;
            
            float posY=startY;

            String text = "Lorem ipsum\n dolor sit amet, \nconsectetur adipiscing elit, \nsed do eiusmod tempor incididunt" +
                    " ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco" +
                    " laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in " +
                    " ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco" +
                    " laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in " +
                    "voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat" +
                    " non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";

            contentStream.beginText();
            addParagraph(contentStream, width, startX, startY, text, true, FONT_SIZE, FONT, LEADING, Color.BLACK, posY);
            addParagraph(contentStream, width, 0, -FONT_SIZE, text, FONT_SIZE, FONT, LEADING, Color.BLACK, posY);
            addParagraph(contentStream, width, 0, -FONT_SIZE, text, false, FONT_SIZE, FONT, LEADING, Color.BLACK, posY);
            contentStream.endText();

            contentStream.close();

            doc.save(new File("/tmp/example.pdf"));
        } catch (IOException e){
            System.err.println("Exception while trying to create pdf document - " + e);
        }
    }

    public static float addParagraph(PDPageContentStream contentStream, float width, float sx,
                                      float sy, String text, float fontSize, PDFont font, float leading, Color color, float posY) throws IOException {
        return addParagraph(contentStream, width, sx, sy, text, false, fontSize, font, leading, color, posY);
    }

    public static float addParagraph(PDPageContentStream contentStream, float width, float sx,
                                      float sy, String text, boolean justify, float fontSize, PDFont font, Float leading, Color color, float posY) throws IOException {
        List<String> lines = parseLines(text, width, fontSize, font);
        return addParagraph(contentStream, width, sx, sy,  lines ,  justify,  fontSize, font, leading, color, posY);
    }


    public static float addParagraph(PDPageContentStream contentStream, float width, float sx,
                                      float sy, List<String> lines , boolean justify, float fontSize, PDFont font, Float leading, Color color, float posY) throws IOException {
        //List<String> lines = parseLines(text, width, fontSize, font);
        if (color==null) color=Color.BLACK;
        final float [] components1 = new float [] { color.getRed()/255f, color.getGreen()/255f, color.getBlue()/255f };
        contentStream.setNonStrokingColor(new PDColor (components1, PDDeviceRGB.INSTANCE));
        
        contentStream.setFont(font, fontSize);
        contentStream.newLineAtOffset(sx, sy);
        
        for (String line: lines) {
            float charSpacing = 0;
            if (justify){
                if (line.length() > 1) {
                    float size = fontSize * font.getStringWidth(line) / 1000;
                    float free = width - size;
                    if (free<0.15*width && free > 0 && !lines.get(lines.size() - 1).equals(line)) {
                        charSpacing = free / (line.length() - 1);
                    }
                }
            }
            contentStream.setCharacterSpacing(charSpacing);
            //contentStream.beginText();
            line = arregla(line);
            if (line.contains("?9")) {
                System.out.println("voila");
            }
            
            
            contentStream.showText(line);
            //contentStream.newLineAtOffset(0, leading);
            contentStream.newLineAtOffset(0, -Math.round(leading));
            posY=posY+Math.round(leading);
            //contentStream.endText();
            
        }
        return posY;
    }

    
    
    public static List<String> parseLines(String text, float width, float fontSize, PDFont font) throws IOException {
        List<String> lines = new ArrayList<String>();
        int lastSpace = -1;
        while (text.length() > 0) {
            int spaceIndex = text.indexOf(' ', lastSpace + 1);
            if (spaceIndex < 0)
                spaceIndex = text.length();
            String subString = text.substring(0, spaceIndex);
            
            if (subString.contains("?9")) {
                System.out.println("voila");
            }
            
            int newLineIndex = subString.indexOf('\n');
            if (newLineIndex>=0) {
                subString = text.substring(0, newLineIndex);
                subString = arregla(subString);
                lines.add(subString);
                text = text.substring(newLineIndex+1).trim();
                lastSpace = -1;
            } else {
            
                //float size = FONT_SIZE * FONT.getStringWidth(subString) / 1000;
                float size = fontSize * font.getStringWidth(subString) / 1000;
            
                if (size > width) {
                    if (lastSpace < 0){
                        lastSpace = spaceIndex;
                    }
                    subString = text.substring(0, lastSpace);
                    subString = arregla(subString);
                    lines.add(subString);
                    text = text.substring(lastSpace).trim();
                    lastSpace = -1;
                } else if (spaceIndex == text.length()) {
                    text = arregla(text);
                    lines.add(text);
                    text = "";
                } else {
                    lastSpace = spaceIndex;
                }
            }    
        }
        return lines;
    }
    
    public static String arregla(String linea) {
        return linea
            .replace("\n", "").replace("\r", "")
             .replace("\u2019", "'").replace("\u0092", "'")
             .replace("\u0080", "€").replace("\u0009", "€")
             .replace("\u0093", "") .replace("\u0094", "")
             .replace("\u0015", " ").replace("\u0095", " "); //U+000D ('controlCR')
    }
    
    public static byte[] getContent(PDDocument doc) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        doc.save(byteArrayOutputStream);
        doc.close();
        return byteArrayOutputStream.toByteArray();
    }
}


5.   PDF_AUtils.class

This class is for converting PDF to PDF-A

package openadmin.utils.decrets;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.xml.transform.TransformerException;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.common.PDMetadata;
import org.apache.pdfbox.pdmodel.graphics.color.PDOutputIntent;
import org.apache.xmpbox.XMPMetadata;
import org.apache.xmpbox.schema.DublinCoreSchema;
import org.apache.xmpbox.schema.PDFAIdentificationSchema;
import org.apache.xmpbox.type.BadFieldValueException;
import org.apache.xmpbox.xml.XmpSerializer;

import ximodante.basic.utils.basic.ExecutionTypeEnum;
import ximodante.basic.utils.basic.FileUtilsEdu;

public class PDF_AUtils {
    // Convert a PDF 1.4 to 1.7 (PDF/A) s/Jesús Romero
    public static PDDocument setVersionPDFA(PDDocument doc, String fileName, ExecutionTypeEnum execType) {
        // add XMP metadata
        XMPMetadata xmp = XMPMetadata.createXMPMetadata();
        try {
            DublinCoreSchema dc = xmp.createAndAddDublinCoreSchema();
            dc.setTitle(fileName);

            PDFAIdentificationSchema id = xmp.createAndAddPFAIdentificationSchema();
            id.setPart(1);
            id.setConformance("B");

            XmpSerializer serializer = new XmpSerializer();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            serializer.serialize(xmp, baos, true);

            PDMetadata metadata = new PDMetadata(doc);
            metadata.importXMPMetadata(baos.toByteArray());
            doc.getDocumentCatalog().setMetadata(metadata);
            
            // sRGB output intent
            //InputStream colorProfile = PDF_AUtils.class.getResourceAsStream("/org/apache/pdfbox/resources/pdfa/sRGB.icc");
            String fileICM=FileUtilsEdu.getRelativeResourceFile(execType, "icm"+File.separator+ "sRGB_Color_Space_Profile.icm");
            InputStream colorProfile = new  FileInputStream(fileICM);
            PDOutputIntent intent;
            intent = new PDOutputIntent(doc, colorProfile);
            intent.setInfo("sRGB IEC61966-2.1");
            intent.setOutputCondition("sRGB IEC61966-2.1");
            intent.setOutputConditionIdentifier("sRGB IEC61966-2.1");
            intent.setRegistryName("http://www.color.org");
            doc.getDocumentCatalog().addOutputIntent(intent);

        } catch (BadFieldValueException e) {
            // won't happen here, as the provided value is valid
            throw new IllegalArgumentException(e);
        } catch (TransformerException e) {
            // 
            e.printStackTrace();
        } catch (IOException e) {
            // 
            e.printStackTrace();
        } catch (Exception e) {
            //
            e.printStackTrace();
        }
        return doc;

        
    }

    public static void main(String[] args) {
        // 
    }

}

        PDPageContentStream contentStream = new PDPageContentStream (doc, page);
        PDAnnotationLink txtLink = new PDAnnotationLink ();
        
        PDAnnotationLink txtLink1 = new PDAnnotationLink ();

          // border style
         PDBorderStyleDictionary linkBorder = new PDBorderStyleDictionary ();
         linkBorder.setStyle (PDBorderStyleDictionary.STYLE_UNDERLINE);
         linkBorder.setWidth (10);
         txtLink.setBorderStyle (linkBorder);
         txtLink1.setBorderStyle (linkBorder);

         // Border color
         final Color color = Color.GREEN;
         final float [] components = new float [] { color.getRed () / 255f, color.getGreen () / 255f, color.getBlue () / 255f };
         txtLink.setColor (new PDColor (components, PDDeviceRGB.INSTANCE));
         txtLink1.setColor (new PDColor (components, PDDeviceRGB.INSTANCE));

         // Destination URI
         final PDActionURI action = new PDActionURI ();
         //action.setURI ("https://www.helger.com"); //OK
         //action.setURI ("pdf.pdf"); //OK
         //action.setURI ("/home/edu/Descargas/Diptico de VALIDe.pdf"); //MAL
         //action.setURI ("file:///home/edu/Documentos/Transporte%20Musical.pdf"); //OK
         //action.setURI ("file://./Transporte%20Musical.pdf"); // MAL
         //action.setURI ("file://Transporte%20Musical.pdf"); // MAL
         //action.setURI ("file:Transporte%20Musical.pdf"); // MAL
         //action.setURI ("Transporte%20Musical.pdf"); // MAL
         action.setURI ("Transporte Musical.pdf"); // BIEN
         //action.setURI ("//home/edu/Descargas/Diptico de VALIDe.pdf"); //MAL
         //action.setURI ("file:///home/edu/Descargas/Diptico de VALIDe.pdf"); //OK
         txtLink.setAction (action);
         
         //Destination file
         //PDSimpleFileSpecification dest=new PDSimpleFileSpecification();
         PDFileSpecification dest=new PDSimpleFileSpecification();
         dest.setFile("/home/edu/Descargas/Diptico de VALIDe.pdf");
         PDActionRemoteGoTo action1= new PDActionRemoteGoTo();
         //PDActionGoTo action1= new PDActionGoTo();
         action1.setOpenInNewWindow(OpenMode.NEW_WINDOW);
         //action1.setDestination(dest);
         
         action1.setFile(dest);
         txtLink1.setAction (action1);

         // Position
         final PDRectangle position = new PDRectangle ();
         position.setLowerLeftX (10);
         position.setLowerLeftY (10);
         position.setUpperRightX (200);
         position.setUpperRightY (10 + 2 + 10 + 2);
         txtLink.setRectangle (position);
         page.getAnnotations().add (txtLink);
         
      // Position
         final PDRectangle position1 = new PDRectangle ();
         position1.setLowerLeftX (10);
         position1.setLowerLeftY (90);
         position1.setUpperRightX (200);
         position1.setUpperRightY (90 + 2 + 10 + 2);
         txtLink1.setRectangle (position1);
         page.getAnnotations().add (txtLink1);

         // Main page content
         contentStream.beginText ();
         contentStream.newLineAtOffset (12, 12);
         contentStream.setFont (PDType1Font.COURIER_BOLD, 10);
         contentStream.showText ("This is linked to the outside world");
         contentStream.endText ();
         
         contentStream.beginText ();
         contentStream.newLineAtOffset (12, 92);
         contentStream.setFont (PDType1Font.COURIER_BOLD, 10);
         contentStream.showText ("This is linked to the outside world 111111111111");
         contentStream.endText ();
         
         contentStream.close();
         
         doc.save(outputPdfPath);
        
    }
    /**
     * Write texLine ant a possition and create a link to a file in the same folder and adds an image to the top of the page.
     * @param doc
     * @param textLine
     * @param posX
     * @param posY
     * @param linkedPDFRelativePath
     * @param imageHeaderPath
     * @return
     * @throws IOException
     */
    public static final PDDocument newTextWithLink(PDDocument doc, String textLine, int posX, int posY, String linkedPDFRelativePath, String imageHeaderPath) throws IOException {
        PDPage page=null;
        if (doc==null) doc = new PDDocument ();
        int lastPage= doc.getNumberOfPages()-1;
        
        if (lastPage<0 || posY<136) {
            page = new PDPage (PDRectangle.A4);
            System.out.println("HEIGHT="+page.getMediaBox().getHeight());
            System.out.println("WIDTH="+page.getMediaBox().getWidth());;
            System.out.println("LowerLeftX="+page.getMediaBox().getLowerLeftX());
            System.out.println("LowerLeftY="+page.getMediaBox().getLowerLeftY());
            System.out.println("UpperRightX="+page.getMediaBox().getUpperRightX());
            System.out.println("UpperRightY="+page.getMediaBox().getUpperRightY());
            
            doc.addPage (page);
            float scale=0.55f;
            PDImageXObject pdImage = PDImageXObject.createFromFile(imageHeaderPath, doc);
            PDPageContentStream headerContentStream = new PDPageContentStream(doc, page,AppendMode.APPEND,false);
            //headerContentStream.drawImage(pdImage, 10, page.getMediaBox().getHeight()-130);
            headerContentStream.drawImage(pdImage, 10, page.getMediaBox().getHeight()-100,
                    pdImage.getWidth()* scale, pdImage.getHeight() * scale);
            
            headerContentStream.beginText ();
            headerContentStream.newLineAtOffset (200, page.getMediaBox().getHeight() - 110);
            //contentStream.setFont (PDType1Font.COURIER_BOLD, 14);
            headerContentStream.setFont(PDType1Font.HELVETICA_BOLD, 16);
            headerContentStream.showText ("RELACI�" DE DECRETS");
            headerContentStream.newLineAtOffset (0, page.getMediaBox().getHeight() - 500);
            headerContentStream.endText ();
            headerContentStream.beginText ();
            headerContentStream.newLineAtOffset (page.getMediaBox().getWidth()-100, 50 );
            headerContentStream.setFont(PDType1Font.HELVETICA_OBLIQUE, 12);
            int myPage=lastPage+2;
            headerContentStream.showText ("Pàgina:" + myPage);
            
            headerContentStream.endText ();
            //headerContentStream.drawLine(0, 0, posY, lastPage);
            headerContentStream.moveTo(200, page.getMediaBox().getHeight() - 110-4);
            headerContentStream.lineTo(383, page.getMediaBox().getHeight() - 110-4);
            /*
            for (int i=0; i<100; i++) {
                headerContentStream.moveTo(100,100);
                headerContentStream.lineTo(100+5*i,100*i);
                //headerContentStream.drawLine(0, 0, 300, 400+i);
            }*/
            headerContentStream.stroke();
            headerContentStream.close();
            
        } else page=doc.getPage(lastPage);
        
        PDPageContentStream contentStream = new PDPageContentStream (doc, page,AppendMode.APPEND, false);
        PDAnnotationLink txtLink = new PDAnnotationLink ();
        
        // border style
        PDBorderStyleDictionary linkBorder = new PDBorderStyleDictionary ();
        //linkBorder.setStyle (PDBorderStyleDictionary.STYLE_UNDERLINE);
        linkBorder.setStyle (PDBorderStyleDictionary.STYLE_BEVELED);
        linkBorder.setWidth (0);
        txtLink.setBorderStyle (linkBorder);
        

        // Border color (NOT USED)
        
        //final Color color = Color.GREEN;
        final Color color = Color.CYAN;
        final float [] components = new float [] { color.getRed()/255f, color.getGreen()/255f, color.getBlue()/255f };
        txtLink.setColor (new PDColor (components, PDDeviceRGB.INSTANCE));
        

        // Destination URI
        final PDActionURI action = new PDActionURI ();
        action.setURI(linkedPDFRelativePath);
        //action.setURI ("https://www.helger.com"); //OK
        //action.setURI ("pdf.pdf"); //OK
        //action.setURI ("file:///home/edu/Documentos/Transporte%20Musical.pdf"); //OK
        //action.setURI ("Transporte Musical.pdf"); // OK
        //action.setURI ("file:///home/edu/Descargas/Diptico de VALIDe.pdf"); //OK
         
        //action.setURI ("/home/edu/Descargas/Diptico de VALIDe.pdf"); //MAL
        //action.setURI ("file://./Transporte%20Musical.pdf"); // MAL
        //action.setURI ("file://Transporte%20Musical.pdf"); // MAL
        //action.setURI ("file:Transporte%20Musical.pdf"); // MAL
        //action.setURI ("Transporte%20Musical.pdf"); // MAL
        //action.setURI ("//home/edu/Descargas/Diptico de VALIDe.pdf"); //MAL
                  
        txtLink.setAction (action);
         
        // Position
        final PDRectangle position = new PDRectangle ();
        position.setLowerLeftX (posX);
        position.setLowerLeftY (page.getMediaBox().getHeight()-posY);
        position.setUpperRightX (page.getMediaBox().getUpperRightX()-posX);
        position.setUpperRightY (page.getMediaBox().getHeight() -(posY + 2 + 10 + 2));
        txtLink.setRectangle (position);
        page.getAnnotations().add (txtLink);
         
        // Main page content
        contentStream.beginText ();
        contentStream.newLineAtOffset (posX, page.getMediaBox().getHeight() - (posY + 2 + 10 + 2));
        //contentStream.setFont (PDType1Font.COURIER_BOLD, 14);
        contentStream.setFont(PDType1Font.HELVETICA_OBLIQUE, 12);
        final Color color1 = Color.BLUE;
        final float [] components1 = new float [] { color1.getRed()/255f, color1.getGreen()/255f, color1.getBlue()/255f };
        //contentStream.setStrokingColor(new PDColor (components1, PDDeviceRGB.INSTANCE));
        contentStream.setNonStrokingColor(new PDColor (components1, PDDeviceRGB.INSTANCE));
        contentStream.showText ( textLine);
        contentStream.endText ();
        contentStream.close();
         
        
         
        return doc; 
    }
    
    /*
    public static void main(String[] args) throws IOException {
        //html2Pdf("/home/edu/TYPESCRIPT/prova20/pdf.html", "/home/edu/TYPESCRIPT/prova20/pdf.pdf");
        //createPdfWithLink("/home/edu/TYPESCRIPT/prova20/pdf1.pdf");
        
        //Create a PDF document with a header
        PDDocument doc=null;
        int posX=80;
        int posY=135-25;
        for (int i=0; i<100; i++) {
            System.out.println(i);
            posY =posY +20;
            if (posY>PDRectangle.A4.getHeight()-90) posY=135;
            //doc= newTextWithLink(doc, "Hola " +i, 20, posY, "doc"+i+".pdf", "/home/edu/Imágenes/escut.png");
            String headerFile=FileUtilsEdu.getPathFromResourcesFolder("images/capsalera.png");
            doc= newTextWithLink(doc, "Hola " +i, posX, posY, "doc"+i+".pdf", headerFile);
        }
        doc.save("/home/eduard/kk/pdf_prova.pdf");
        
    }
    */
    
    
    /*************************************************************************
     * PARAGRAPHS !!!
     * see https://memorynotfound.com/apache-pdfbox-adding-multiline-paragraph/
     *************************************************************************/
    
    

    public static void main(String... args)  {
        PDFont FONT = PDType1Font.HELVETICA;
                
        float FONT_SIZE = 12;
        //float LEADING = -1.5f * FONT_SIZE;
        //float LEADING = 1.5f * FONT_SIZE;
        float LEADING = 1.1f * FONT_SIZE;
        
        

        try (final PDDocument doc = new PDDocument()){

            PDPage page = new PDPage();
            doc.addPage(page);
            PDPageContentStream contentStream = new PDPageContentStream(doc, page);

            PDRectangle mediaBox = page.getMediaBox();
            float marginY = 80;
            float marginX = 60;
            float width = mediaBox.getWidth() - 2 * marginX;
            float startX = mediaBox.getLowerLeftX() + marginX;
            float startY = mediaBox.getUpperRightY() - marginY;
            
            float posY=startY;

            String text = "Lorem ipsum\n dolor sit amet, \nconsectetur adipiscing elit, \nsed do eiusmod tempor incididunt" +
                    " ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco" +
                    " laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in " +
                    " ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco" +
                    " laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in " +
                    "voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat" +
                    " non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";

            contentStream.beginText();
            addParagraph(contentStream, width, startX, startY, text, true, FONT_SIZE, FONT, LEADING, Color.BLACK, posY);
            addParagraph(contentStream, width, 0, -FONT_SIZE, text, FONT_SIZE, FONT, LEADING, Color.BLACK, posY);
            addParagraph(contentStream, width, 0, -FONT_SIZE, text, false, FONT_SIZE, FONT, LEADING, Color.BLACK, posY);
            contentStream.endText();

            contentStream.close();

            doc.save(new File("/tmp/example.pdf"));
        } catch (IOException e){
            System.err.println("Exception while trying to create pdf document - " + e);
        }
    }

    public static float addParagraph(PDPageContentStream contentStream, float width, float sx,
                                      float sy, String text, float fontSize, PDFont font, float leading, Color color, float posY) throws IOException {
        return addParagraph(contentStream, width, sx, sy, text, false, fontSize, font, leading, color, posY);
    }

    public static float addParagraph(PDPageContentStream contentStream, float width, float sx,
                                      float sy, String text, boolean justify, float fontSize, PDFont font, Float leading, Color color, float posY) throws IOException {
        List<String> lines = parseLines(text, width, fontSize, font);
        return addParagraph(contentStream, width, sx, sy,  lines ,  justify,  fontSize, font, leading, color, posY);
    }


    public static float addParagraph(PDPageContentStream contentStream, float width, float sx,
                                      float sy, List<String> lines , boolean justify, float fontSize, PDFont font, Float leading, Color color, float posY) throws IOException {
        //List<String> lines = parseLines(text, width, fontSize, font);
        if (color==null) color=Color.BLACK;
        final float [] components1 = new float [] { color.getRed()/255f, color.getGreen()/255f, color.getBlue()/255f };
        contentStream.setNonStrokingColor(new PDColor (components1, PDDeviceRGB.INSTANCE));
        
        contentStream.setFont(font, fontSize);
        contentStream.newLineAtOffset(sx, sy);
        
        for (String line: lines) {
            float charSpacing = 0;
            if (justify){
                if (line.length() > 1) {
                    float size = fontSize * font.getStringWidth(line) / 1000;
                    float free = width - size;
                    if (free<0.15*width && free > 0 && !lines.get(lines.size() - 1).equals(line)) {
                        charSpacing = free / (line.length() - 1);
                    }
                }
            }
            contentStream.setCharacterSpacing(charSpacing);
            //contentStream.beginText();
            line = arregla(line);
            if (line.contains("?9")) {
                System.out.println("voila");
            }
            
            
            contentStream.showText(line);
            //contentStream.newLineAtOffset(0, leading);
            contentStream.newLineAtOffset(0, -Math.round(leading));
            posY=posY+Math.round(leading);
            //contentStream.endText();
            
        }
        return posY;
    }

    
    
    public static List<String> parseLines(String text, float width, float fontSize, PDFont font) throws IOException {
        List<String> lines = new ArrayList<String>();
        int lastSpace = -1;
        while (text.length() > 0) {
            int spaceIndex = text.indexOf(' ', lastSpace + 1);
            if (spaceIndex < 0)
                spaceIndex = text.length();
            String subString = text.substring(0, spaceIndex);
            
            if (subString.contains("?9")) {
                System.out.println("voila");
            }
            
            int newLineIndex = subString.indexOf('\n');
            if (newLineIndex>=0) {
                subString = text.substring(0, newLineIndex);
                subString = arregla(subString);
                lines.add(subString);
                text = text.substring(newLineIndex+1).trim();
                lastSpace = -1;
            } else {
            
                //float size = FONT_SIZE * FONT.getStringWidth(subString) / 1000;
                float size = fontSize * font.getStringWidth(subString) / 1000;
            
                if (size > width) {
                    if (lastSpace < 0){
                        lastSpace = spaceIndex;
                    }
                    subString = text.substring(0, lastSpace);
                    subString = arregla(subString);
                    lines.add(subString);
                    text = text.substring(lastSpace).trim();
                    lastSpace = -1;
                } else if (spaceIndex == text.length()) {
                    text = arregla(text);
                    lines.add(text);
                    text = "";
                } else {
                    lastSpace = spaceIndex;
                }
            }    
        }
        return lines;
    }
    
    public static String arregla(String linea) {
        return linea
            .replace("\n", "").replace("\r", "")
             .replace("\u2019", "'").replace("\u0092", "'")
             .replace("\u0080", "€").replace("\u0009", "€")
             .replace("\u0093", "") .replace("\u0094", "")
             .replace("\u0015", " ").replace("\u0095", " "); //U+000D ('controlCR')
    }
    
    public static byte[] getContent(PDDocument doc) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        doc.save(byteArrayOutputStream);
        doc.close();
        return byteArrayOutputStream.toByteArray();
    }
}

6.   PDFTables.class

This class is for managing classes in a PDF document

package openadmin.utils.decrets;

import java.awt.Color;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary;

import be.quodlibet.boxable.BaseTable;
import be.quodlibet.boxable.Cell;
import be.quodlibet.boxable.HorizontalAlignment;
import be.quodlibet.boxable.Row;
import be.quodlibet.boxable.VerticalAlignment;
import ximodante.basic.utils.basic.ExecutionTypeEnum;
import ximodante.basic.utils.basic.FileUtilsEdu;

public class PDFTables {

    /**
     * Creates a table
     * @param yStart
     * @param yStartNewPage
     * @param bottomMargin
     * @param tableWidth
     * @param margin
     * @param rowHeigth
     * @param document
     * @param currentPage
     * @param drawLines
     * @param drawContent
     * @param content
     * @throws IOException
     */
    public static void createTable(float yStart, float yStartNewPage, float bottomMargin, float tableWidth, float margin,  float rowHeigth, PDDocument document, PDPage currentPage, boolean drawLines, boolean drawContent, String[][] content) throws IOException {
                
        BaseTable table = new BaseTable( yStart, yStartNewPage, bottomMargin, tableWidth, margin, document, currentPage, true, drawContent);
        //Create Header row
        Row<PDPage> headerRow = table.createRow(15f); //Heigth
        Cell<PDPage> cell = headerRow.createCell(100, "Awesome Facts About Belgium");
        cell.setFont(PDType1Font.HELVETICA_BOLD);
        cell.setFillColor(Color.YELLOW);
        table.addHeaderRow(headerRow);
        
        for (String[] lineContent : content) {
            int nCols=lineContent.length;
            Row<PDPage> row = table.createRow(rowHeigth); //Heigth
            //cell = row.createCell((100 / 3.0f) * 2, lineContent[0]);
            cell = row.createCell((100 / nCols) , lineContent[0]);
            for (int i = 1; i < lineContent.length; i++) {
                //cell = row.createCell((100 / 9f), lineContent[i]);
                cell = row.createCell((100 / nCols), lineContent[i]);
                
                System.out.println("cell.getHeight()="+cell.getCellHeight());
                
            }
            System.out.println("row.getHeight()="+row.getHeight());
            //System.out.println("row.getWidth()="+row.getWidth());
            
        }
        table.draw();
    }
    /**
     * Verify that the arrays have the corrent dimensions
     * @param proportions 1 dimension Array of proportion of the cells
     * @param titles 1 dimension Array of titles of the table
     * @param content 2 dimension array of the content
     * @param links 2 dimension array of the links to the documents
     * @return true if no errors
     * @throws Exception if error
     */
    private static boolean checkErrors_createTable01(float[] proportions, String[] titles, List<String[]> content, List<String[]> links) throws Exception {
        if (proportions.length!=links.get(0).length) throw new Exception("ERROR in createTable01: links must have the same number of components as proportions");
        if (content.get(0).length!=links.get(0).length) throw new Exception("ERROR in createTable01: content must have the same number of components as proportions");
        if (proportions.length!=titles.length) throw new Exception("ERROR in createTable01: proportions must have the same number of components as titles");
        return true;
    }
    
    /**
     * Sum the array element
     * @param values
     * @return
     */
    public static float sum(float[] values) {
        float total=0;
        for (float value: values) total +=value;
        return total;
    }
    
    /**
     * Get the column withs 
     * @param tableWidth
     * @param proportions
     * @return
     */
    public static float[] getwidths(float tableWidth, float[] proportions) {
        float[] widths= new float[proportions.length];
        float total=sum(proportions);
        for (int i=0; i<proportions.length; i++) {
            widths[i]=tableWidth*proportions[i]/total;
        }
        return widths;
    }
    
    /**
     * Get the x position of the beginning of the cells
     * @param leftMargin
     * @param tableWidth
     * @param proportions
     * @return
     */
    public static float[] getPosXs(float leftMargin, float tableWidth, float[] proportions) {
        float[] posXs= new float[proportions.length];
        float[] widths= getwidths(tableWidth, proportions) ;
        for (int i=0; i<proportions.length; i++) {
            if (i==0) posXs[i]=leftMargin;
            else posXs[i]=posXs[i-1]+widths[i-1];
        }
        return posXs;
    }
    /**
     * Create a table and return a map whose key is the row of the table and its values
     *   are for the cells that have a link
     *   
     *   Note that we cannot crete links with this library, so the links must be created 
     *     after the table is created
     *   
     *   The fields of the map are:
     *   
     *   myHeight: Height of the row (or the cell)
     *   line: row number, 
     *   i: column number, 
     *   row.getHeight(): Height of the row, 
     *   links[line][i]: the link of the cell
     *   posXs[i]: x position
     *   widths[i]; Widrh of the cell
     * @param yStart
     * @param yStartNewPage
     * @param bottomMargin
     * @param tableWidth
     * @param leftMargin
     * @param rowHeigth
     * @param document
     * @param currentPage
     * @param drawLines
     * @param drawContent
     * @param proportions
     * @param titles
     * @param titleRotates
     * @param content
     * @param textRotates
     * @param links
     * @return
     * @throws Exception
     */
    public static Map<Integer,List<Object[]>> createTable01(float yStart, float yStartNewPage, float bottomMargin, 
            float tableWidth, float leftMargin,  float rowHeigth, PDDocument document, PDPage currentPage, 
            boolean drawLines, boolean drawContent, float[] proportions,String[] titles, boolean[] titleRotates, 
            //String[][] content, boolean[] textRotates, String[][] links) 
            List<String[]> content, boolean[] textRotates, boolean[] textLinks,List<String[]> links, ExecutionTypeEnum execType)
                    throws Exception {
        
        checkErrors_createTable01(proportions, titles, content, links);
        float totalProportion=sum(proportions);
        
        float[] widths=getwidths(tableWidth, proportions);
        float[] posXs=getPosXs(leftMargin, tableWidth, proportions);
        
        PDFont FONT = PDType0Font.load(document, new File(FileUtilsEdu.getRelativeResourceFile(execType, "fonts/Arial.ttf")));
        PDFont FONT_ITALIC = PDType0Font.load(document, new File(FileUtilsEdu.getRelativeResourceFile(execType, "fonts/Arial-corsivo.ttf")));
         PDFont FONT_BOLD = PDType0Font.load(document, new File(FileUtilsEdu.getRelativeResourceFile(execType, "fonts/FontsFree-Net-arial-bold.ttf")));     
         float FONT_SIZE = 9;
                 
        Map<Integer,List<Object[]>> possitions=new HashMap<Integer,List<Object[]>>();
        //float pageHeight=currentPage.getMediaBox().getHeight();
        
        BaseTable table = new BaseTable( yStart, yStartNewPage, bottomMargin, tableWidth, leftMargin, document, currentPage, true, drawContent);
        //Create Header row
        Row<PDPage> headerRow = table.createRow(15f); //Heigth
        
        for (int i = 0; i < posXs.length; i++) {
            Cell<PDPage> cell = headerRow.createCell((100 *proportions[i]/totalProportion), titles[i]);
            if (titleRotates[i]) cell.setTextRotated(titleRotates[i]);
            else {
                cell.setAlign(HorizontalAlignment.CENTER);
                cell.setValign(VerticalAlignment.MIDDLE);
            }
            //cell.setFont(PDType1Font.HELVETICA_BOLD);
            cell.setFont(FONT_BOLD);
            cell.setFontSize(FONT_SIZE);
            cell.setFillColor(Color.YELLOW);
            
        }
        
        table.addHeaderRow(headerRow);
        
        float myHeight=yStart- headerRow.getHeight();
        int nPage=0;
        
        int line=0;
        List<Object[]> lst=new ArrayList<Object[]>();
        for (String[] lineContent : content) {
            int nCols=lineContent.length;
            Row<PDPage> row = table.createRow(rowHeigth); //Heigth
            //cell = row.createCell((100 / 3.0f) * 2, lineContent[0]);
            //cell = row.createCell((100 / nCols) , lineContent[0]);
            //cell = row.createCell((100 *proportions[0]/totalProportion), lineContent[0]);
            /*
            cell = row.createTableCell((100 *proportions[0]/totalProportion),
                    "<table><tr><td>20210001</td><td></td><td>xml</td></tr></table>",
                    document, currentPage, 0, 0, 0);
            */
            //System.out.println("CellHeight()="+cell.getCellHeight());

            for (int i = 0; i < nCols; i++) {
                Cell<PDPage> cell = row.createCell((100 *proportions[i]/totalProportion), lineContent[i]);
                if (textLinks[i]) {
                    cell.setTextColor(Color.BLUE);
                    cell.setFont(FONT_ITALIC);
                    cell.setFontSize(FONT_SIZE-3);
                    cell.setAlign(HorizontalAlignment.RIGHT);
                } else {
                    cell.setTextColor(Color.BLACK);
                    cell.setFont(FONT);
                    cell.setFontSize(FONT_SIZE-1);
                    cell.setAlign(HorizontalAlignment.CENTER);
                }    
                if (textRotates[i]) cell.setTextRotated(textRotates[i]);
            }
            
            if (myHeight - row.getHeight()<bottomMargin) {
                myHeight=yStartNewPage- headerRow.getHeight();
                possitions.put(nPage,lst);
                lst=new ArrayList<Object[]>();
                nPage++;
            }
            myHeight=myHeight - row.getHeight();
            
            System.out.println("row.getHeight()="+row.getHeight());
            System.out.println("page="+table.getCurrentPage().toString());
            System.out.println("myHeight="+myHeight);
            System.out.println("line="+line);
            System.out.println("nPage="+nPage);
            System.out.println("-----------------------------------------");
            
            
            //for (int i=0; i<links[line].length; i++) {
            String[] link=links.get(line);
            for (int i=0; i<link.length; i++) {
                if (link[i]!=null && link[i].trim().length()>0) {
                    Object[] objs= {myHeight, line, i, row.getHeight(), link[i], posXs[i], widths[i]};
                    lst.add(objs);
                    
                }    
            }
            
            line++;
            
        }
        if (lst.size()>0) possitions.put(nPage,lst);
        table.draw();
        return possitions;
    }
    
    /**
     * Create the links in the cells that should contain links
     * @param doc
     * @param possitions
     */
    public static void createLinks(PDDocument doc,Map<Integer,List<Object[]>>possitions) {
        Integer[] nPage= new Integer[1];
        nPage[0]=-1;
        doc.getPages().forEach(page->{
            nPage[0]++;
            List<Object[]>lObs=possitions.get(nPage[0]);
            //{myHeight, line, i, row.getHeight(), links[line][i], posXs[i], widths[i]};
            for(Object[] objs:lObs) {
                try {
                    addLink(doc, page, (String) objs[4], (float) objs[5], (float) objs[0], (float) objs[6], (float) objs[3]);
                } catch (IOException e) {
                    // 
                    e.printStackTrace();
                }
            }    
        });
    }
    
    public static void addPageNumbers(ExecutionTypeEnum execType, PDDocument doc, float leftMargin, float rightMargin, float topMargin, float bottomMargin) throws IOException, Exception {
        Integer[] nPage= {0, doc.getNumberOfPages()};
        float[] pos= {0, 0, 0, 0};
        PDFont FONT = PDType0Font.load(doc, new File(FileUtilsEdu.getRelativeResourceFile(execType, "fonts/Arial.ttf")));
         float fontSize=9;
         String imagePath=FileUtilsEdu.getRelativeResourceFile(execType, "images/capsalera1.png");
        PDImageXObject capsalera = PDImageXObject.createFromFile(imagePath,doc);
        
        doc.getPages().forEach(page->{
            nPage[0]++;
            try {
                if (nPage[0]==1) {
                    PDRectangle mediaBox = page.getMediaBox();
                    pos[0]= mediaBox.getWidth()-rightMargin- fontSize * FONT.getStringWidth("Pàg: 100 / "+nPage[1]) / 1000;
                    pos[1]= bottomMargin -fontSize;
                    pos[2]= mediaBox.getWidth() - 35/0.375f;
                    pos[3]= mediaBox.getHeight();
                    
                }
                PDPageContentStream contentStream = new PDPageContentStream(doc, page, AppendMode.APPEND, false);
                //contentStream.drawImage(capsalera, leftMargin, pos[3]-topMargin-30/0.375f,pos[2], 30/0.375f); //35
                if (nPage[0]>1)contentStream.drawImage(capsalera, leftMargin, pos[3]-35/0.375f,pos[2], 30/0.375f); //35
            
                //PDPageContentStream contentStream = new PDPageContentStream(doc, page, AppendMode.APPEND, false);
                contentStream.beginText();
                contentStream.setFont(FONT, 10);
                contentStream.newLineAtOffset(pos[0], pos[1]);
                contentStream.showText("Pàg: "+nPage[0]+" / "+nPage[1]);
                contentStream.endText();
                contentStream.close(); // don't forget that one!
                
            } catch (IOException e) {
                //
                e.printStackTrace();
            }    
        });
    }
    
    
    
    /**
     * Add a link at a page in the positions indicated 
     * @param doc
     * @param page
     * @param linkedPDFRelativePath
     * @param posX
     * @param posY
     * @param width
     * @param height
     * @throws IOException
     */
    public static void addLink(PDDocument doc, PDPage page, String linkedPDFRelativePath, float posX, float posY, float width, float height) throws IOException {
        //PDPageContentStream contentStream = new PDPageContentStream (doc, page,AppendMode.APPEND, false);
        PDAnnotationLink txtLink = new PDAnnotationLink ();
        
        // border style
        PDBorderStyleDictionary linkBorder = new PDBorderStyleDictionary ();
        //linkBorder.setStyle (PDBorderStyleDictionary.STYLE_UNDERLINE);
        linkBorder.setStyle (PDBorderStyleDictionary.STYLE_BEVELED);
        linkBorder.setWidth (0);
        txtLink.setBorderStyle (linkBorder);
        

        // Border color (NOT USED)
        
        //final Color color = Color.GREEN;
        final Color color = Color.CYAN;
        final float [] components = new float [] { color.getRed()/255f, color.getGreen()/255f, color.getBlue()/255f };
        txtLink.setColor (new PDColor (components, PDDeviceRGB.INSTANCE));
        

        // Destination URI
        final PDActionURI action = new PDActionURI ();
        action.setURI(linkedPDFRelativePath);
                          
        txtLink.setAction (action);
         
        // Position
        final PDRectangle position = new PDRectangle ();
        position.setLowerLeftX (posX);
        position.setLowerLeftY (posY);
        position.setUpperRightX (posX+width);
        position.setUpperRightY (posY+height);
        txtLink.setRectangle (position);
        page.getAnnotations().add (txtLink);

    }
    
    public static void main (String[] args) throws Exception {
        
        ExecutionTypeEnum execType=ExecutionTypeEnum.NO_JAR;
        
        PDFont FONT = PDType1Font.HELVETICA;
        float FONT_SIZE = 10;
        //float LEADING = -1.5f * FONT_SIZE;
        float LEADING = 1.5f * FONT_SIZE;
        
        PDDocument doc=new PDDocument();
        PDPage page = new PDPage();
        doc.addPage(page);
        
        PDPageContentStream contentStream = new PDPageContentStream(doc, page);

        PDRectangle mediaBox = page.getMediaBox();
        float marginY = 80;
        float marginX = 60;
        float width = mediaBox.getWidth() - 2 * marginX;
        float startX = mediaBox.getLowerLeftX() + marginX;
        float startY = mediaBox.getUpperRightY() - marginY;
        
        float posY=startY;

        String text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, \nsed do eiusmod tempor incididunt" +
                " ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco" +
                " laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in " +
                " ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco" +
                " laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in " +
                "voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat" +
                " non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";

        contentStream.beginText();
        PDFUtils.addParagraph(contentStream, width, startX, startY, text, true, FONT_SIZE, FONT, LEADING, Color.BLACK, posY);
        PDFUtils.addParagraph(contentStream, width, 0, -FONT_SIZE, text, FONT_SIZE, FONT, LEADING, Color.BLACK, posY);
        PDFUtils.addParagraph(contentStream, width, 0, -FONT_SIZE, text, false, FONT_SIZE, FONT, LEADING, Color.BLACK, posY);
        contentStream.endText();

        contentStream.close();
        
                
        String[][] content= {
                {"202100001",">",">",">","Departament d'Informàtica0","Decret per a comprar 10 paelles gegants per a la joventud","15/10/2021"},
                {"202100002",">",">",">","Departament d'Informàtica1","Decret per a comprar 10 paelles gegants per a la joventud","15/11/2021"},
                {"202100003",">",">",">","Departament d'Informàtica2","Decret per a comprar 10 paelles gegants per a la joventud","15/12/2021"},
                {"202100004",">",">",">","Departament d'Informàtica3","Decret per a comprar 10 paelles gegants per a la joventud","3/9/2021"}
        };      
        
        String[][] links= {
                {"","a0.pdf","b0.pdf","c0.pdf","","",""},
                {"","a1.pdf","b1.pdf","c1.pdf","","",""},
                {"","a2.pdf","b2.pdf","c2.pdf","","",""},
                {"","a3.pdf","b3.pdf","c3.pdf","","",""}
        };
        
        String[] titles= {"Núm Resolució","Original","Autèntic","ENI","Nom o designació","Descripcio","Data última firma"};
        boolean[] titleRotates= {false,true,true,true,false,false,false};
        boolean[] textRotates= {false,true,true,true,false,false,false};
        boolean[] textLinks= {false,true,true,true,false,false,false};
        
        float[] proportions={9.5f,2.5f,2.5f,2.5f,25f,35f,9.5f};
        
        //float margin = 50;
        float leftMargin = 25/0.375f;
        // starting y position is whole page height subtracted by top and bottom margin
        //float yStartNewPage = page.getMediaBox().getHeight() - (2 * margin);
        float yStart = page.getMediaBox().getHeight() - (500);
        float yStartNewPage = page.getMediaBox().getHeight() - (30 /0.375f);
        // we want table across whole page width (subtracted by left and right margin ofcourse)
        float tableWidth = page.getMediaBox().getWidth() - (25+20)/0.375f ;

        boolean drawContent = true;
        boolean drawLines = true;
        //float yStart = yStartNewPage;
        //float bottomMargin = 70;
        float bottomMargin = 20/0.375f;
        // y position is your coordinate of top left corner of the table
        float yPosition = 550;
        
        for (int i=0; i<content.length; i++) {
            String[] line=content[i];
            for (int j=0; j<line.length; j++) {
                content[i][j]=StringUtils.repeat(content[i][j], i+1);
            }
        }
        float rowHeight=20;
        
        float pageWidth = mediaBox.getWidth();
        float pageHeight = mediaBox.getHeight();
        System.out.println("pageWidth="+pageWidth);
        System.out.println("pageHeight="+pageHeight);
        System.out.println("leftMargin="+leftMargin);
       // System.out.println("rightMargin="+rightMargin);
        System.out.println("bottomMargin="+bottomMargin);
        System.out.println("yStartNewPage="+yStartNewPage);
        System.out.println("tableWidth="+tableWidth);
        
        
        
        //createTable(yStart, yStartNewPage, bottomMargin, tableWidth, margin, rowHeight, doc, page, drawLines, drawContent, content);
        Map<Integer,List<Object[]>> possitions= createTable01(yStart, yStartNewPage, bottomMargin, tableWidth, leftMargin, rowHeight, 
                doc, page, drawLines, drawContent, proportions, titles, titleRotates, Arrays.asList(content), textRotates, textLinks, Arrays.asList(links), execType);
        
        createLinks(doc,possitions);
        
        doc.save("/home/eduard/kk/provaTaula.pdf");
    }
}






Comments

Popular posts from this blog

ORVE WS (Dynamic) (4) Jackson XML mapper

ENI (1) ENI Document OR the Spanish Electronic Administration Mafia

ORVE WS (Dynamic) (12) Defiining the control tables