package org.plantuml.idea.rendering;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
import net.sourceforge.plantuml.*;
import net.sourceforge.plantuml.core.Diagram;
import net.sourceforge.plantuml.cucadiagram.Display;
import net.sourceforge.plantuml.cucadiagram.DisplayPositionned;
import net.sourceforge.plantuml.descdiagram.DescriptionDiagram;
import net.sourceforge.plantuml.sequencediagram.Event;
import net.sourceforge.plantuml.sequencediagram.Newpage;
import net.sourceforge.plantuml.sequencediagram.SequenceDiagram;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.plantuml.idea.plantuml.PlantUml;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import static org.plantuml.idea.lang.annotator.LanguageDescriptor.IDEA_PARTIAL_RENDER;
public class PlantUmlRenderer {
private static final Logger logger = Logger.getInstance(PlantUmlRenderer.class);
public static final Pattern NEW_PAGE_PATTERN = Pattern.compile("\\n\\s*@?(?i)(newpage)(\\p{Blank}+[^\\n]+|\\p{Blank}*)(?=\\n)");
private static final PlantUmlPartialRenderer PARTIAL_RENDERER = new PlantUmlPartialRenderer();
private static final PlantUmlNormalRenderer NORMAL_RENDERER = new PlantUmlNormalRenderer();
/**
* Renders source code and saves diagram images to files according to provided naming scheme
* and image format.
*
* @param source source code to be rendered
* @param baseDir base dir to set for "include" functionality
* @param format image format
* @param fileName fileName to use with first file
* @param fileNameFormat file naming scheme for further files
* @param pageNumber -1 for all pages
* @throws IOException in case of rendering or saving fails
*/
public static void renderAndSave(String source, @Nullable File baseDir, PlantUml.ImageFormat format, String fileName, String fileNameFormat, int zoom, int pageNumber)
throws IOException {
NORMAL_RENDERER.renderAndSave(source, baseDir, format, fileName, fileNameFormat, zoom, pageNumber);
}
/**
* Renders file with support of plantUML include ange paging features, setting base dir and page for plantUML
* to provided values
*/
public static RenderResult render(RenderRequest renderRequest, RenderCacheItem cachedItem) {
try {
File baseDir = renderRequest.getBaseDir();
if (baseDir != null) {
FileSystem.getInstance().setCurrentDir(baseDir);
}
long start = System.currentTimeMillis();
String source = renderRequest.getSource();
String[] sourceSplit = NEW_PAGE_PATTERN.split(source);
logger.debug("split done ", System.currentTimeMillis() - start, "ms");
boolean partialRender = sourceSplit[0].contains(IDEA_PARTIAL_RENDER);
logger.debug("partialRender ", partialRender);
RenderResult renderResult;
if (partialRender) {
renderResult = PARTIAL_RENDERER.partialRender(renderRequest, cachedItem, start, sourceSplit);
} else {
renderResult = NORMAL_RENDERER.doRender(renderRequest, cachedItem, sourceSplit);
}
return renderResult;
} finally {
FileSystem.getInstance().reset();
}
}
public static Pair<Integer, Titles> zoomDiagram(SourceStringReader reader, int zoom) {
logger.debug("zooming diagram");
int totalPages = 0;
List<BlockUml> blocks = reader.getBlocks();
for (int i = 0; i < blocks.size(); i++) {
BlockUml block = blocks.get(i);
long start = System.currentTimeMillis();
checkCancel();
Diagram diagram = block.getDiagram();
logger.debug("getDiagram done in ", System.currentTimeMillis() - start, " ms");
start = System.currentTimeMillis();
zoomDiagram(diagram, zoom);
logger.debug("zoom diagram done in ", System.currentTimeMillis() - start, " ms");
totalPages = totalPages + diagram.getNbImages();
}
Titles titles = getTitles(totalPages, blocks);
return new Pair<Integer, Titles>(totalPages, titles);
}
private static void zoomDiagram(Diagram diagram, int zoom) {
if (diagram instanceof UmlDiagram) {
UmlDiagram umlDiagram = (UmlDiagram) diagram;
Scale scale = umlDiagram.getScale();
if (scale == null) {
umlDiagram.setScale(new ScaleSimple(zoom / 100f));
}
} else if (diagram instanceof NewpagedDiagram) {
NewpagedDiagram newpagedDiagram = (NewpagedDiagram) diagram;
for (Diagram page : newpagedDiagram.getDiagrams()) {
if (page instanceof DescriptionDiagram) {
DescriptionDiagram descriptionDiagram = (DescriptionDiagram) page;
Scale scale = descriptionDiagram.getScale();
if (scale == null) {
descriptionDiagram.setScale(new ScaleSimple(zoom / 100f));
}
}
}
}
}
@NotNull
protected static Titles getTitles(int totalPages, List<BlockUml> blocks) {
List<String> titles = new ArrayList<String>(totalPages);
for (BlockUml block : blocks) {
Diagram diagram = block.getDiagram();
if (diagram instanceof SequenceDiagram) {
SequenceDiagram sequenceDiagram = (SequenceDiagram) diagram;
addTitle(titles, sequenceDiagram.getTitle().getDisplay());
List<Event> events = sequenceDiagram.events();
for (Event event : events) {
if (event instanceof Newpage) {
Display title = ((Newpage) event).getTitle();
addTitle(titles, title);
}
}
} else if (diagram instanceof NewpagedDiagram) {
NewpagedDiagram newpagedDiagram = (NewpagedDiagram) diagram;
List<Diagram> diagrams = newpagedDiagram.getDiagrams();
for (Diagram diagram1 : diagrams) {
if (diagram1 instanceof UmlDiagram) {
DisplayPositionned title = ((UmlDiagram) diagram1).getTitle();
addTitle(titles, title.getDisplay());
}
}
} else if (diagram instanceof UmlDiagram) {
DisplayPositionned title = ((UmlDiagram) diagram).getTitle();
addTitle(titles, title.getDisplay());
} else if (diagram instanceof PSystemError) {
DisplayPositionned title = ((PSystemError) diagram).getTitle();
if (title == null) {
titles.add(null);
} else {
addTitle(titles, title.getDisplay());
}
}
}
return new Titles(titles);
}
protected static void addTitle(List<String> titles, Display display) {
if (display.size() > 0) {
titles.add(display.toString());
} else {
titles.add(null);
}
}
private static void checkCancel() {
if (Thread.currentThread().isInterrupted()) {
throw new RenderingCancelledException();
}
}
}