/*
* 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.pst;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import net.sf.latexdraw.models.MathUtils;
import net.sf.latexdraw.models.interfaces.shape.ArrowStyle;
import net.sf.latexdraw.models.interfaces.shape.Color;
import net.sf.latexdraw.models.interfaces.shape.FillingStyle;
import net.sf.latexdraw.models.interfaces.shape.IArrow;
import net.sf.latexdraw.models.interfaces.shape.IArrowableShape;
import net.sf.latexdraw.models.interfaces.shape.IPoint;
import net.sf.latexdraw.models.interfaces.shape.IShape;
import net.sf.latexdraw.view.latex.DviPsColors;
import org.eclipse.jdt.annotation.NonNull;
import static java.lang.Math.toDegrees;
/**
* Defines a PSTricks view of the LShape model.
* @author Arnaud Blouin
*/
public abstract class PSTShapeView<S extends IShape> {
/** The shape model. */
protected final S shape;
/**
* The list of name of the colours added to the generated code. Useful when generating
* the code to define the colours in the latex document.
*/
protected Set<String> coloursName;
/**
* Creates and initialises an abstract PSTricks view.
* @param model The model to view.
* @throws IllegalArgumentException If the given model is not valid.
* @since 3.0
*/
protected PSTShapeView(final @NonNull S model) {
super();
shape = Objects.requireNonNull(model);
}
/**
* @return The mode of the view.
*/
public S getShape() {
return shape;
}
/**
* Saves a colour coming from the generated code.
* @param name The name of the generated colour.
* @since 3.0
*/
protected void addColour(final String name) {
if(name != null) {
if(coloursName == null) coloursName = new HashSet<>();
if(!coloursName.contains(name)) coloursName.add(name);
}
}
public abstract String getCode(final IPoint origin, final float ppc);
/**
* @return The PST code corresponding to the arrow parameters of the shape. Or null if no arrow.
* @since 3.0
*/
protected StringBuilder getArrowsParametersCode() {
StringBuilder code = null;
if(shape instanceof IArrowableShape) {
final IArrowableShape arr = (IArrowableShape) shape;
final ArrowStyle style1 = arr.getArrowStyle(0);
final ArrowStyle style2 = arr.getArrowStyle(-1);
if(style1 == ArrowStyle.NONE) {
if(style2 != ArrowStyle.NONE) code = getArrowParametersCode(arr.getArrowAt(-1));
}else if(style2 == ArrowStyle.NONE) code = getArrowParametersCode(arr.getArrowAt(0));
else if(style1.isSameKind(style2)) code = getArrowParametersCode(arr.getArrowAt(0));
else {
code = getArrowParametersCode(arr.getArrowAt(0));
code.append(',').append(getArrowParametersCode(arr.getArrowAt(-1)));
}
}
return code;
}
/**
* @return The PST code corresponding to the parameter of the style of the given arrow. The style of the
* given arrow must not be NONE.
*/
private StringBuilder getArrowParametersCode(final IArrow arrow) {
final StringBuilder code = new StringBuilder();
final ArrowStyle style = arrow.getArrowStyle();
if(style.isBar() || style.isRoundBracket() || style.isSquareBracket()) {
code.append("tbarsize=").append(MathUtils.INST.getCutNumberFloat(arrow.getTBarSizeDim() / IShape.PPC)).append(PSTricksConstants.TOKEN_CM).append(' '). //$NON-NLS-1$
append(MathUtils.INST.getCutNumberFloat(arrow.getTBarSizeNum()));
if(style.isSquareBracket())
code.append(",bracketlength=").append(MathUtils.INST.getCutNumberFloat(arrow.getBracketNum())); //$NON-NLS-1$
else if(style.isRoundBracket())
code.append(",rbracketlength=").append(MathUtils.INST.getCutNumberFloat(arrow.getRBracketNum())); //$NON-NLS-1$
}else if(style.isArrow())
code.append("arrowsize=").append(MathUtils.INST.getCutNumberFloat(arrow.getArrowSizeDim() / IShape.PPC)).append(PSTricksConstants.TOKEN_CM).append(' '). //$NON-NLS-1$
append(MathUtils.INST.getCutNumberFloat(arrow.getArrowSizeNum())).append(",arrowlength="). //$NON-NLS-1$
append(MathUtils.INST.getCutNumberFloat(arrow.getArrowLength())).append(",arrowinset=").append(MathUtils.INST.getCutNumberFloat(arrow.getArrowInset())); //$NON-NLS-1$
else
code.append("dotsize=").append(MathUtils.INST.getCutNumberFloat(arrow.getDotSizeDim() / IShape.PPC)).append(PSTricksConstants.TOKEN_CM).append(' '). //$NON-NLS-1$
append(MathUtils.INST.getCutNumberFloat(arrow.getDotSizeNum()));
return code;
}
/**
* @return The PST code corresponding to the style of the arrows (e.g. {|->}).
* @since 3.0
*/
protected StringBuilder getArrowsStyleCode() {
final StringBuilder code;
if(shape instanceof IArrowableShape) {
final IArrowableShape arr = (IArrowableShape) shape;
final ArrowStyle style1 = arr.getArrowStyle(0);
final ArrowStyle style2 = arr.getArrowStyle(-1);
if(style1 == ArrowStyle.NONE && style2 == ArrowStyle.NONE) code = null;
else code = new StringBuilder().append('{').append(style1.getPSTToken()).append('-').append(style2.getPSTToken()).append('}');
}else code = null;
return code;
}
/**
* @return The PSTricks code of the show-points option or null.
* @since 3.0
*/
protected StringBuilder getShowPointsCode() {
final StringBuilder code;
if(shape.isShowPts()) {
code = new StringBuilder();
code.append("showpoints=true"); //$NON-NLS-1$ //TODO add arrow params
}else code = null;
return code;
}
/**
* @param ppc The number of pixels per centimetre.
* @param position The reference point of the PSTricks drawing.
* @return The header of the PSTricks rotation code.
* @since 3.0
*/
protected StringBuilder getRotationHeaderCode(final float ppc, final IPoint position) {
if(ppc < 1 || !MathUtils.INST.isValidPt(position)) return null;
final StringBuilder code;
final double angle = shape.getRotationAngle();
if(MathUtils.INST.equalsDouble(angle, 0.0)) code = null;
else {
final IPoint gravityCenter = shape.getGravityCentre();
final double cx = (gravityCenter.getX() - position.getX()) / ppc;
final double cy = (position.getY() - gravityCenter.getY()) / ppc;
final double x = MathUtils.INST.getCutNumberFloat(-Math.cos(-angle) * cx + Math.sin(-angle) * cy + cx);
final double y = MathUtils.INST.getCutNumberFloat(-Math.sin(-angle) * cx - Math.cos(-angle) * cy + cy);
code = new StringBuilder();
code.append("\\rput{").append(MathUtils.INST.getCutNumberFloat(-Math.toDegrees(shape.getRotationAngle()) % 360)).append('}').append('('); //$NON-NLS-1$
code.append((float) x).append(',').append((float) y).append(')').append('{');
}
return code;
}
/**
* @param colour The colour which name is looking for. If the colour does
* not exist yet, it is created.
* @return The name of a predefined or a newly generated colour.
* @since 3.0
*/
protected String getColourName(final Color colour) {
final String name = DviPsColors.INSTANCE.getColourName(colour).orElseGet(() -> DviPsColors.INSTANCE.addUserColour(colour).orElse(""));
addColour(name);
return name;
}
/**
* @param ppc The number of pixels per centimetre.
* @return The PSTricks code of the double border of the shape.
* @since 3.0
*/
protected StringBuilder getDoubleBorderCode(final float ppc) {
final StringBuilder code;
if(shape.hasDbleBord()) {
final Color doubleColor = shape.getDbleBordCol();
code = new StringBuilder();
code.append("doubleline=true, doublesep=");//$NON-NLS-1$
code.append(MathUtils.INST.getCutNumberFloat(shape.getDbleBordSep() / ppc));
if(!doubleColor.equals(PSTricksConstants.DEFAULT_DOUBLE_COLOR))
code.append(", doublecolor=").append(getColourName(doubleColor)); //$NON-NLS-1$
}else code = null;
return code;
}
/**
* @return The PSTricks code of the border position.
* @since 3.0
*/
protected StringBuilder getBorderPositionCode() {
final StringBuilder code;
if(shape.isBordersMovable()) switch(shape.getBordersPosition()) {
case INTO:
code = new StringBuilder().append("dimen=").append(PSTricksConstants.BORDERS_INSIDE); //$NON-NLS-1$
break;
case MID:
code = new StringBuilder().append("dimen=").append(PSTricksConstants.BORDERS_MIDDLE); //$NON-NLS-1$
break;
case OUT:
code = new StringBuilder().append("dimen=").append(PSTricksConstants.BORDERS_OUTSIDE); //$NON-NLS-1$
break;
default:
code = null;
break;
}
else code = null;
return code;
}
/**
* @param ppc The number of pixels per centimetre.
* @return The PSTricks code of the line style.
* @since 1.7
*/
protected StringBuilder getLineCode(final float ppc) {
final StringBuilder code = new StringBuilder();
final Color linesColor = shape.getLineColour();
code.append("linecolor=").append(getColourName(linesColor)); //$NON-NLS-1$
if(shape.isThicknessable()) code.append(", linewidth=").append(MathUtils.INST.getCutNumberFloat(shape.getThickness() / ppc));//$NON-NLS-1$
if(linesColor.getO() < 1.0) code.append(", strokeopacity=").append(MathUtils.INST.getCutNumberFloat(linesColor.getO())); //$NON-NLS-1$
switch(shape.getLineStyle()) {
case DOTTED:
code.append(", linestyle=");//$NON-NLS-1$
code.append(PSTricksConstants.LINE_DOTTED_STYLE);
code.append(", dotsep=");//$NON-NLS-1$
code.append(MathUtils.INST.getCutNumberFloat(shape.getDotSep() / ppc));
code.append(PSTricksConstants.TOKEN_CM);
break;
case DASHED:
code.append(", linestyle=");//$NON-NLS-1$
code.append(PSTricksConstants.LINE_DASHED_STYLE);
code.append(", dash=");//$NON-NLS-1$
code.append(MathUtils.INST.getCutNumberFloat(shape.getDashSepBlack() / ppc));
code.append(PSTricksConstants.TOKEN_CM).append(' ');
code.append(MathUtils.INST.getCutNumberFloat(shape.getDashSepWhite() / ppc));
code.append(PSTricksConstants.TOKEN_CM);
break;
case SOLID:
}
return code;
}
/**
* @return The PST code of the filling with parameter "plain".
* @since 3.0
*/
private StringBuilder getFillingPlain() {
final Color interiorColor = shape.getFillingCol();
final StringBuilder code = new StringBuilder("fillstyle=solid"); //$NON-NLS-1$
if(!interiorColor.equals(PSTricksConstants.DEFAULT_INTERIOR_COLOR))
code.append(",fillcolor=").append(getColourName(interiorColor)); //$NON-NLS-1$
if(interiorColor.getO() < 1.0) code.append(", opacity=").append(MathUtils.INST.getCutNumberFloat(interiorColor.getO())); //$NON-NLS-1$
return code;
}
/**
* @return The PST code of the filling with parameter "gradient".
* @since 3.0
*/
private StringBuilder getFillingGrad() {
final Color gradStartCol = shape.getGradColStart();
final Color gradEndCol = shape.getGradColEnd();
final float gradMidPt = MathUtils.INST.getCutNumberFloat(shape.getGradMidPt());
final float gradAngle = MathUtils.INST.getCutNumberFloat(shape.getGradAngle());
final StringBuilder code = new StringBuilder("fillstyle=gradient, gradlines=2000");//$NON-NLS-1$
if(!gradStartCol.equals(PSTricksConstants.DEFAULT_GRADIENT_START_COLOR))
code.append(", gradbegin=").append(getColourName(gradStartCol)); //$NON-NLS-1$
if(!gradEndCol.equals(PSTricksConstants.DEFAULT_GRADIENT_END_COLOR))
code.append(", gradend=").append(getColourName(gradEndCol)); //$NON-NLS-1$
if(!MathUtils.INST.equalsDouble(gradMidPt, PSTricksConstants.DEFAULT_GRADIENT_MID_POINT))
code.append(", gradmidpoint=").append(gradMidPt);//$NON-NLS-1$
if(!MathUtils.INST.equalsDouble(toDegrees(gradAngle), PSTricksConstants.DEFAULT_GRADIENT_ANGLE))
code.append(", gradangle=").append(MathUtils.INST.getCutNumberFloat(toDegrees(gradAngle)));//$NON-NLS-1$
return code;
}
/**
* @return The PST code of the filling with parameter "hlines" or "vlines" etc.
* @since 3.0
*/
private StringBuilder getFillingHatchings(final float ppc) {
final Color hatchingsCol = shape.getHatchingsCol();
final StringBuilder code = new StringBuilder();
final FillingStyle fillingStyle = shape.getFillingStyle();
if(fillingStyle == FillingStyle.CLINES || fillingStyle == FillingStyle.CLINES_PLAIN)
code.append("fillstyle=crosshatch"); //$NON-NLS-1$
else if(fillingStyle == FillingStyle.HLINES || fillingStyle == FillingStyle.HLINES_PLAIN)
code.append("fillstyle=hlines"); //$NON-NLS-1$
else code.append("fillstyle=vlines"); //$NON-NLS-1$
if(shape.isFilled()) code.append('*');
code.append(", hatchwidth="); //$NON-NLS-1$
code.append(MathUtils.INST.getCutNumberFloat(shape.getHatchingsWidth() / ppc));
code.append(", hatchangle=").append(MathUtils.INST.getCutNumberFloat(Math.toDegrees(shape.getHatchingsAngle()))); //$NON-NLS-1$
code.append(", hatchsep="); //$NON-NLS-1$
code.append(MathUtils.INST.getCutNumberFloat(shape.getHatchingsSep() / ppc));
if(!hatchingsCol.equals(PSTricksConstants.DEFAULT_HATCHING_COLOR))
code.append(", hatchcolor=").append(getColourName(hatchingsCol)); //$NON-NLS-1$
return code;
}
/**
* @param ppc The number of pixels per centimetre.
* @return The PSTricks code for the filling of the shape. Null if there is no filling.
* @since 1.7
*/
protected StringBuilder getFillingCode(final float ppc) {
StringBuilder code;
final Color interiorColor = shape.getFillingCol();
switch(shape.getFillingStyle()) {
case NONE:
code = null;
break;
case PLAIN:
code = getFillingPlain();
break;
case GRAD:
code = getFillingGrad();
break;
case CLINES:
case CLINES_PLAIN:
case HLINES:
case HLINES_PLAIN:
case VLINES:
case VLINES_PLAIN:
code = getFillingHatchings(ppc);
break;
default:
code = null;
break;
}
if(!shape.isFilled() && shape.hasShadow() && shape.shadowFillsShape() && !interiorColor.equals(PSTricksConstants.DEFAULT_INTERIOR_COLOR)) {
if(code == null) code = new StringBuilder();
else code.append(',').append(' ');
code.append("fillcolor=").append(getColourName(interiorColor)); //$NON-NLS-1$
}
return code;
}
/**
* @param ppc The number of pixels per centimetre.
* @return The code of the shape shadow or null if there is no shadow.
* @since 3.0
*/
protected StringBuilder getShadowCode(final float ppc) {
final StringBuilder code;
if(shape.hasShadow()) {
final Color shadowColor = shape.getShadowCol();
code = new StringBuilder();
code.append("shadow=true");//$NON-NLS-1$
if(!MathUtils.INST.equalsDouble(Math.toDegrees(shape.getShadowAngle()), PSTricksConstants.DEFAULT_SHADOW_ANGLE))
code.append(",shadowangle=").append(MathUtils.INST.getCutNumberFloat(Math.toDegrees(shape.getShadowAngle())));//$NON-NLS-1$
code.append(",shadowsize=").append(MathUtils.INST.getCutNumberFloat(shape.getShadowSize() / ppc));//$NON-NLS-1$
if(!shadowColor.equals(PSTricksConstants.DEFAULT_SHADOW_COLOR))
code.append(",shadowcolor=").append(getColourName(shadowColor)); //$NON-NLS-1$
}else code = null;
return code;
}
}