package org.plantuml.idea.rendering;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
import com.intellij.util.ObjectUtils;
import net.sourceforge.plantuml.*;
import net.sourceforge.plantuml.core.Diagram;
import net.sourceforge.plantuml.core.DiagramDescription;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.plantuml.idea.plantuml.PlantUml;
import org.plantuml.idea.plantuml.PlantUmlIncludes;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import static org.plantuml.idea.rendering.PlantUmlRenderer.zoomDiagram;
public class PlantUmlNormalRenderer {
protected static final Logger logger = Logger.getInstance(PlantUmlNormalRenderer.class);
protected static final FileFormatOption SVG = new FileFormatOption(FileFormat.SVG);
public static final String TITLE_ONLY = "TITLE ONLY";
/**
* 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 requestedPageNumber -1 for all pages
* @throws IOException in case of rendering or saving fails
*/
public void renderAndSave(String source, @Nullable File baseDir, PlantUml.ImageFormat format, String fileName, String fileNameFormat, int zoom, int requestedPageNumber)
throws IOException {
FileOutputStream outputStream = null;
try {
if (baseDir != null) {
FileSystem.getInstance().setCurrentDir(baseDir);
}
PlantUmlIncludes.commitIncludes(source, baseDir);
SourceStringReader reader = new SourceStringReader(source);
zoomDiagram(reader, zoom);
if (requestedPageNumber >= 0) {
outputStream = new FileOutputStream(fileName);
reader.generateImage(outputStream, requestedPageNumber, new FileFormatOption(format.getFormat()));
outputStream.close();
} else {
List<BlockUml> blocks = reader.getBlocks();
int imageСounter = 0;
for (BlockUml block : blocks) {
Diagram diagram = block.getDiagram();
int pages = diagram.getNbImages();
for (int page = 0; page < pages; ++page) {
String fName = imageСounter == 0 ? fileName : String.format(fileNameFormat, imageСounter);
outputStream = new FileOutputStream(fName);
try {
reader.generateImage(outputStream, imageСounter++, new FileFormatOption(format.getFormat()));
} finally {
outputStream.close();
}
}
}
}
} finally {
FileSystem.getInstance().reset();
if (outputStream != null) {
outputStream.close();
}
}
}
protected RenderResult doRender(RenderRequest renderRequest, RenderCacheItem cachedItem, String[] sourceSplit) {
String documentSource = renderRequest.getSource();
try {
// image generation.
SourceStringReader reader = new SourceStringReader(documentSource);
Pair<Integer, Titles> pages = zoomDiagram(reader, renderRequest.getZoom());
Integer totalPages = pages.first;
Titles titles = pages.second;
if (totalPages == 0) {
return new RenderResult(RenderingType.NORMAL, 0);
}
//image/error is not rendered when page >= totalPages
int renderRequestPage = renderRequest.getPage();
if (renderRequestPage >= totalPages) {
renderRequestPage = -1;
}
RenderResult renderResult = new RenderResult(RenderingType.NORMAL, totalPages);
FileFormatOption formatOption = new FileFormatOption(renderRequest.getFormat().getFormat());
boolean containsIncludedNewPage = sourceSplit.length != totalPages;
logger.debug("splitByNewPage.length=", sourceSplit.length, ", totalPages=", totalPages, ", cachedPages=", cachedItem != null ? cachedItem.getImageItems().length : null);
boolean incrementalRendering =
cachedItem != null
&& !RenderingType.NORMAL.renderingTypeChanged(cachedItem)
&& !containsIncludedNewPage
&& !cachedPageCountChanged(cachedItem, totalPages);
logger.debug("incremental rendering=", incrementalRendering, ", totalPages=", totalPages);
for (int i = 0; i < totalPages; i++) {
boolean pageRequested = renderRequestPage == -1 || renderRequestPage == i;
if (incrementalRendering) {
incrementalRendering(renderRequest, cachedItem, sourceSplit, documentSource, reader, titles, renderResult, formatOption, i, pageRequested);
} else {
normalRendering(renderRequest, sourceSplit, documentSource, reader, titles, renderResult, formatOption, containsIncludedNewPage, i, pageRequested);
}
}
logger.debug("RenderResult totalPages=", totalPages);
return renderResult;
} catch (UnsupportedOperationException e) {
throw e;
} catch (RenderingCancelledException e) {
throw e;
} catch (Throwable e) {
logger.error("Failed to render image " + documentSource, e);
return new RenderResult(RenderingType.NORMAL, 0);
}
}
private void incrementalRendering(RenderRequest renderRequest, RenderCacheItem cachedItem, String[] sourceSplit, String documentSource, SourceStringReader reader, Titles titles, RenderResult renderResult, FileFormatOption formatOption, int i, boolean pageRequested) throws IOException {
boolean obsolete = renderRequest.requestedRefreshOrIncludesChanged()
|| renderRequest.getZoom() != cachedItem.getZoom()
|| !sourceSplit[i].equals(cachedItem.getImagesItemPageSource(i))
|| cachedItem.titleChaged(titles.get(i), i);
boolean shouldRender = pageRequested && (obsolete || !cachedItem.hasImage(i));
if (shouldRender) {
renderResult.addRenderedImage(generateImageItem(renderRequest, documentSource, sourceSplit[i], reader, formatOption, i, i, RenderingType.NORMAL, titles.get(i)));
} else if (obsolete) {
logger.debug("page ", i, " title only");
renderResult.addUpdatedTitle(new ImageItem(renderRequest.getBaseDir(), documentSource, sourceSplit[i], i, TITLE_ONLY, null, null, RenderingType.NORMAL, titles.get(i)));
} else {
logger.debug("page ", i, " cached");
renderResult.addCachedImage(cachedItem.getImageItem(i));
}
}
private void normalRendering(RenderRequest renderRequest, String[] sourceSplit, String documentSource, SourceStringReader reader, Titles titles, RenderResult renderResult, FileFormatOption formatOption, boolean containsIncludedNewPage, int i, boolean pageRequested) throws IOException {
String pageSource = pageSource(sourceSplit, containsIncludedNewPage, i);
if (pageRequested) {
renderResult.addRenderedImage(generateImageItem(renderRequest, documentSource, pageSource, reader, formatOption, i, i, RenderingType.NORMAL, titles.get(i)));
} else {
logger.debug("page ", i, " title only");
renderResult.addUpdatedTitle(new ImageItem(renderRequest.getBaseDir(), documentSource, pageSource, i, TITLE_ONLY, null, null, RenderingType.NORMAL, titles.get(i)));
}
}
@Nullable
private String pageSource(String[] sourceSplit, boolean containsIncludedNewPage, int i) {
String pageSource = null;
if (!containsIncludedNewPage) {
pageSource = sourceSplit[i];
}
return pageSource;
}
protected boolean cachedPageCountChanged(RenderCacheItem cachedItem, int pagesCount) {
return cachedItem != null && pagesCount != cachedItem.getImageItems().length;
}
protected void checkCancel() {
if (Thread.currentThread().isInterrupted()) {
throw new RenderingCancelledException();
}
}
@NotNull
protected ImageItem generateImageItem(RenderRequest renderRequest,
String documentSource,
@Nullable String pageSource,
SourceStringReader reader,
FileFormatOption formatOption,
int page,
int logPage,
RenderingType renderingType,
String title) throws IOException {
checkCancel();
long start = System.currentTimeMillis();
ByteArrayOutputStream imageStream = new ByteArrayOutputStream();
DiagramDescription diagramDescription;
try {
diagramDescription = reader.outputImage(imageStream, page, formatOption);
} catch (UnsupportedOperationException e) {
throw e;
} catch (Exception e) {
throw new RenderingCancelledException(e);
}
logger.debug("generated ", formatOption.getFileFormat(), " for page ", logPage, " in ", System.currentTimeMillis() - start, "ms");
byte[] svgBytes = new byte[0];
if (renderRequest.isRenderUrlLinks()) {
svgBytes = generateSvg(reader, page);
}
ObjectUtils.assertNotNull(diagramDescription);
String description = diagramDescription.getDescription();
if (description != null && description.contains("entities")) {
description = "ok";
}
return new ImageItem(renderRequest.getBaseDir(), documentSource, pageSource, page, description, imageStream.toByteArray(), svgBytes, renderingType, title);
}
protected byte[] generateSvg(SourceStringReader reader, int i) throws IOException {
long start = System.currentTimeMillis();
ByteArrayOutputStream svgStream = new ByteArrayOutputStream();
reader.generateImage(svgStream, i, SVG);
byte[] svgBytes = svgStream.toByteArray();
logger.debug("generated ", SVG.getFileFormat(), " for page ", i, " in ", System.currentTimeMillis() - start, "ms");
return svgBytes;
}
}