/*
* This file is part of LaTeXDraw.
* Copyright (c) 2005-2017 Arnaud BLOUIN
* LaTeXDraw is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later version.
* LaTeXDraw is distributed without any warranty; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
package net.sf.latexdraw.view.latex;
import com.google.inject.Inject;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Optional;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import net.sf.latexdraw.actions.ExportFormat;
import net.sf.latexdraw.badaboom.BadaboomCollector;
import net.sf.latexdraw.models.interfaces.shape.IDrawing;
import net.sf.latexdraw.models.interfaces.shape.IPoint;
import net.sf.latexdraw.util.LFileUtils;
import net.sf.latexdraw.util.LSystem;
import net.sf.latexdraw.util.OperatingSystem;
import net.sf.latexdraw.view.ViewsSynchroniserHandler;
import org.malai.properties.Modifiable;
/**
* Defines an abstract LaTeX generator.
* @author Arnaud Blouin
*/
public abstract class LaTeXGenerator implements Modifiable {
/**
* Defines the number of characters added at the beginning
* of each lines of the comment (these characters are "% ").
*/
public static final int LGTH_START_LINE_COMMENT = 2;
/**
* The latex packages used when exporting using latex.
* These packages are defined for the current document but not for all documents.
*/
public static final ObjectProperty<String> PACKAGES = new SimpleObjectProperty<>(""); //$NON-NLS-1$
/**
* @param packages the packages to set.
* @since 3.0
*/
public static void setPackages(final String packages) {
if(packages != null && !packages.equals(getPackages())) PACKAGES.setValue(packages);
}
/**
* @return the packages.
* @since 3.0
*/
public static String getPackages() {
return PACKAGES.getValue();
}
/** The comment of the drawing. */
protected String comment;
/** The label of the drawing. */
protected String label;
/** The caption of the drawing. */
protected String caption;
/** The token of the position of the drawing */
protected VerticalPosition positionVertToken;
/** The horizontal position of the drawing */
protected boolean positionHoriCentre;
/** Defined if the instrument has been modified. */
protected boolean modified;
/** The scale of the drawing. */
protected double scale;
@Inject protected IDrawing drawing;
@Inject protected ViewsSynchroniserHandler handler;
/** Defines if the latex parameters (position, caption, etc.) must be generated. */
protected boolean withLatexParams;
/** Defines if the comments must be generated. */
protected boolean withComments;
/**
* Initialises the abstract generator.
*/
protected LaTeXGenerator() {
super();
modified = false;
comment = ""; //$NON-NLS-1$
label = ""; //$NON-NLS-1$
caption = ""; //$NON-NLS-1$
positionHoriCentre = false;
positionVertToken = VerticalPosition.NONE;
scale = 1.0;
withComments = true;
withLatexParams = true;
}
/**
* @return the scale of the drawing.
* @since 3.0
*/
public double getScale() {
return scale;
}
/**
* @param sc the scale to set.
* @since 3.0
*/
public void setScale(final double sc) {
if(sc >= 0.1) scale = sc;
}
/**
* @return the comment.
* @since 3.0
*/
public String getComment() {
return comment;
}
/**
* @return The comments without any characters like "%"
* at the start of each lines. (these characters are used like comment symbol by LaTeX).
*/
public String getCommentsWithoutTag() {
int i = 0;
int j = 0;
final int lgth = comment.length();
final char[] buffer = new char[lgth];
boolean eol = true;
while(i < lgth) {
if(eol && comment.charAt(i) == '%') {
i += LGTH_START_LINE_COMMENT;
eol = false;
}else {
if(comment.charAt(i) == '\n') eol = true;
buffer[j++] = comment.charAt(i);
i++;
}
}
final String str = String.valueOf(buffer, 0, j);
return str.length() > 1 ? str.substring(0, str.length() - LSystem.EOL.length()) : str;
}
@Override
public boolean isModified() {
return modified;
}
@Override
public void setModified(final boolean modif) {
modified = modif;
}
/**
* @param newComments the comment to set.
* @since 3.0
*/
public void setComment(final String newComments) {
if(newComments != null && !newComments.isEmpty()) {
int i;
int j = 0;
final int lgth = newComments.length();
final char[] buffer = new char[lgth * 3];
boolean eol = true;
for(i = 0; i < newComments.length(); i++) {
if(eol) {
buffer[j++] = '%';
buffer[j++] = ' ';
eol = false;
}
if(newComments.charAt(i) == '\n') eol = true;
buffer[j++] = newComments.charAt(i);
}
comment = String.valueOf(buffer, 0, j);
setModified(true);
}
}
/**
* @return The latex token corresponding to the specified vertical position.
* @since 3.0
*/
public VerticalPosition getPositionVertToken() {
return positionVertToken;
}
/**
* @param positionVert The new vertical position token. Must not be null.
* @since 3.0
*/
public void setPositionVertToken(final VerticalPosition positionVert) {
if(positionVert != null) {
positionVertToken = positionVert;
setModified(true);
}
}
/**
* @return True: the latex drawing will be horizontally centred.
* @since 3.0
*/
public boolean isPositionHoriCentre() {
return positionHoriCentre;
}
/**
* @return the label of the latex drawing.
* @since 3.0
*/
public String getLabel() {
return label;
}
/**
* @param lab the new lab of the drawing. Must not be null.
* @since 3.0
*/
public void setLabel(final String lab) {
if(lab != null) {
label = lab;
setModified(true);
}
}
/**
* @return the caption of the drawing.
* @since 3.0
*/
public String getCaption() {
return caption;
}
/**
* @param theCaption the new caption of the drawing. Must not be null.
* @since 3.0
*/
public void setCaption(final String theCaption) {
if(theCaption != null) {
caption = theCaption;
setModified(true);
}
}
/**
* @param position True: the latex drawing will be horizontally centred.
* @since 3.0
*/
public void setPositionHoriCentre(final boolean position) {
if(positionHoriCentre != position) {
positionHoriCentre = position;
setModified(true);
}
}
/**
* Produces and returns the code.
* @return The generate code.
*/
public abstract String getDrawingCode();
/**
* Generates a latex document that contains the pstricks code of the given canvas.
* @return The latex document or an empty string.
* @since 3.0
*/
public abstract String getDocumentCode();
/**
* Creates a latex file that contains the pstricks code of the given canvas.
* @param pathExportTex The location where the file must be created.
* @return The latex file or nothing.
* @since 3.0
*/
public Optional<File> createLatexFile(final String pathExportTex) {
boolean ok = true;
try(FileOutputStream fos = new FileOutputStream(pathExportTex); OutputStreamWriter osw = new OutputStreamWriter(fos)) {
osw.append(getDocumentCode());
}catch(final IOException ex) {
BadaboomCollector.INSTANCE.add(ex);
ok = false;
}
return ok ? Optional.of(new File(pathExportTex)) : Optional.empty();
}
/**
* Create a .ps file that corresponds to the compiled latex document containing
* the pstricks drawing.
* @param pathExportPs The path of the .ps file to create (MUST ends with .ps).
* @return The create file or nothing.
* @since 3.0
*/
public Optional<File> createPSFile(final String pathExportPs) {
return createPSFile(pathExportPs, null);
}
/**
* Create an .eps file that corresponds to the compiled latex document containing the pstricks drawing.
* @param pathExportEPS The path of the .eps file to create (MUST ends with .eps).
* @return The create file or nothing.
* @since 3.0
*/
public Optional<File> createEPSFile(final String pathExportEPS) {
final File tmpDir = LFileUtils.INSTANCE.createTempDir();
if(tmpDir == null) {
BadaboomCollector.INSTANCE.add(new FileNotFoundException("Cannot create a tmp dir"));
return Optional.empty();
}
Optional<File> optFile = createPSFile(tmpDir.getAbsolutePath() + LSystem.FILE_SEP + "tmpPSFile.ps", tmpDir);//$NON-NLS-1$
File psFile;
if(optFile.isPresent()) {
psFile = optFile.get();
}else {
return Optional.empty();
}
final OperatingSystem os = LSystem.INSTANCE.getSystem();
if(os == null) {
return Optional.empty();
}
final File finalFile = new File(pathExportEPS);
final File fileEPS = new File(psFile.getAbsolutePath().replace(".ps", ExportFormat.EPS_LATEX.getFileExtension())); //$NON-NLS-1$
final String[] paramsLatex = {os.getPS2EPSBinPath(), psFile.getAbsolutePath(), fileEPS.getAbsolutePath()};
final String log = LSystem.INSTANCE.execute(paramsLatex, tmpDir);
if(!fileEPS.exists()) {
BadaboomCollector.INSTANCE.add(new IllegalAccessException(getDocumentCode() + LSystem.EOL + log));
return Optional.empty();
}
LFileUtils.INSTANCE.copy(fileEPS, finalFile);
psFile.delete();
fileEPS.delete();
if(!finalFile.exists()) {
BadaboomCollector.INSTANCE.add(new IllegalAccessException("Cannot create the EPS file at this location: " + finalFile.getAbsolutePath())); //$NON-NLS-1$
return Optional.empty();
}
return Optional.of(finalFile);
}
/**
* Create a .ps file that corresponds to the compiled latex document containing
* the pstricks drawing.
* @param pathExportPs The path of the .ps file to create (MUST ends with .ps).
* @param tmpDir The temporary directory used for the compilation.
* @return The create file or nothing.
* @since 3.0
*/
public Optional<File> createPSFile(final String pathExportPs, final File tmpDir) {
if(pathExportPs == null) return Optional.empty();
final int lastSep = pathExportPs.lastIndexOf(LSystem.FILE_SEP) + 1;
final String name = pathExportPs.substring(lastSep == -1 ? 0 : lastSep, pathExportPs.lastIndexOf(".ps")); //$NON-NLS-1$
final File tmpDir2 = tmpDir == null ? LFileUtils.INSTANCE.createTempDir() : tmpDir;
if(tmpDir2 == null) {
BadaboomCollector.INSTANCE.add(new FileNotFoundException("Cannot create a temporary folder.")); //$NON-NLS-1$
return Optional.empty();
}
final String path = tmpDir2.getAbsolutePath() + LSystem.FILE_SEP;
Optional<File> optFile = createLatexFile(path + name + ExportFormat.TEX.getFileExtension());
File texFile;
if(optFile.isPresent()) {
texFile = optFile.get();
}else {
return Optional.empty();
}
String log;
File finalPS;
final IPoint tr = handler.getTopRightDrawingPoint();
final IPoint bl = handler.getBottomLeftDrawingPoint();
final int ppc = handler.getPPCDrawing();
final float dec = 0.2f;
final OperatingSystem os = LSystem.INSTANCE.getSystem();
if(texFile == null || !texFile.exists() || os == null) return Optional.empty();
final String[] paramsLatex = {os.getLatexBinPath(), "--interaction=nonstopmode", "--output-directory=" + tmpDir2.getAbsolutePath(),//$NON-NLS-1$//$NON-NLS-2$
LFileUtils.INSTANCE.normalizeForLaTeX(texFile.getAbsolutePath())};//$NON-NLS-1$
log = LSystem.INSTANCE.execute(paramsLatex, tmpDir2);
final File dviFile = new File(tmpDir2.getAbsolutePath() + LSystem.FILE_SEP + name + ".dvi"); //$NON-NLS-1$
final boolean dviRenamed = dviFile.renameTo(new File(tmpDir2.getAbsolutePath() + LSystem.FILE_SEP + name));
final String[] paramsDvi = {os.getDvipsBinPath(), "-Pdownload35", "-T", //$NON-NLS-1$ //$NON-NLS-2$
(tr.getX() - bl.getX()) / ppc * scale + dec + "cm," + ((bl.getY() - tr.getY()) / ppc * scale + dec) + "cm", //$NON-NLS-1$ //$NON-NLS-2$
name, "-o", pathExportPs}; //$NON-NLS-1$
log += LSystem.INSTANCE.execute(paramsDvi, tmpDir2);
texFile.delete();
new File(path + name + (dviRenamed ? "" : ".div")).delete(); //$NON-NLS-1$ //$NON-NLS-2$
new File(path + name + ".log").delete(); //$NON-NLS-1$
new File(path + name + ".aux").delete(); //$NON-NLS-1$
finalPS = new File(pathExportPs);
if(!finalPS.exists()) {
BadaboomCollector.INSTANCE.add(new IllegalAccessException(getDocumentCode() + LSystem.EOL + log));
finalPS = null;
}
if(tmpDir == null) tmpDir2.delete();
return Optional.ofNullable(finalPS);
}
/**
* Create a .pdf file that corresponds to the compiled latex document containing
* the pstricks drawing.
* @param pathExportPdf The path of the .pdf file to create (MUST ends with .pdf).
* @param crop if true, the output document will be cropped.
* @return The create file or null.
* @since 3.0
*/
public Optional<File> createPDFFile(final String pathExportPdf, final boolean crop) {
if(pathExportPdf == null) return Optional.empty();
final File tmpDir = LFileUtils.INSTANCE.createTempDir();
if(tmpDir == null) {
BadaboomCollector.INSTANCE.add(new FileNotFoundException("Cannot create a temporary folder.")); //$NON-NLS-1$
return Optional.empty();
}
final String name = pathExportPdf.substring(pathExportPdf.lastIndexOf(LSystem.FILE_SEP) + 1, pathExportPdf.lastIndexOf(ExportFormat.PDF.getFileExtension()));
final File psFile;
Optional<File> optFile = createPSFile(tmpDir.getAbsolutePath() + LSystem.FILE_SEP + name + ".ps");//$NON-NLS-1$
if(optFile.isPresent()) {
psFile = optFile.get();
}else {
return Optional.empty();
}
String log;
File pdfFile;
final OperatingSystem os = LSystem.INSTANCE.getSystem();
if(psFile == null || os == null) return Optional.empty();
// On windows, an option must be defined using this format:
// -optionName#valueOption Thus, the classical = character must be replaced by a # when latexdraw runs on Windows.
final String optionEmbed = "-dEmbedAllFonts" + (LSystem.INSTANCE.isWindows() ? "#" : "=") + "true"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
log = LSystem.INSTANCE.execute(new String[]{os.getPs2pdfBinPath(), optionEmbed, psFile.getAbsolutePath(), crop ? name + ExportFormat.PDF.getFileExtension() : pathExportPdf}, tmpDir);
if(crop) {
pdfFile = new File(tmpDir.getAbsolutePath() + LSystem.FILE_SEP + name + ExportFormat.PDF.getFileExtension());
log = LSystem.INSTANCE.execute(new String[]{os.getPdfcropBinPath(), pdfFile.getAbsolutePath(), pdfFile.getAbsolutePath()}, tmpDir);
// JAVA7: test pdfFile.toPath().move(pathExportPdf)
// the renameto method is weak and fails sometimes.
if(!pdfFile.renameTo(new File(pathExportPdf)) && !LFileUtils.INSTANCE.copy(pdfFile, new File(pathExportPdf)))
log += " The final pdf document cannot be moved to its final destination. If you use Windows, you must have a Perl interpretor installed, such as strawberryPerl (http://strawberryperl.com/)"; //$NON-NLS-1$
pdfFile.delete();
}
pdfFile = new File(pathExportPdf);
psFile.delete();
if(!pdfFile.exists()) {
BadaboomCollector.INSTANCE.add(new IllegalAccessException(getDocumentCode() + LSystem.EOL + log));
pdfFile = null;
}
tmpDir.delete();
return Optional.ofNullable(pdfFile);
}
/**
* @return True: The latex parameters must be used by the generated code.
* @since 3.0
*/
public boolean isWithLatexParams() {
return withLatexParams;
}
/**
* Defines if the latex parameters must be used by the generated code.
* @param latexParams True: The latex parameters must be used by the generated code.
* @since 3.0
*/
public void setWithLatexParams(final boolean latexParams) {
this.withLatexParams = latexParams;
}
/**
* @return True: comments will be included.
* @since 3.0
*/
public boolean isWithComments() {
return withComments;
}
/**
* Defines if the code must contains comments.
* @param comments True: comments will be included.
* @since 3.0
*/
public void setWithComments(final boolean comments) {
this.withComments = comments;
}
}