/*
* Copyright (C) 2014 Alec Dhuse
*
* This program 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package co.foldingmap.GUISupport.htmlRendering;
import co.foldingmap.ResourceHelper;
import co.foldingmap.dataStructures.SmartTokenizer;
import co.foldingmap.xml.XMLTag;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
/**
* Class for rendering HTML and drawing it to a Graphics2D class.
*
* @author Alec
*/
public class HtmlRenderer {
protected ArrayList<HtmlRenderInstruction> instructions;
protected boolean isHtml, rendered;
protected float x, y, height, width;
protected ResourceHelper helper;
protected String html;
protected XMLTag docTag;
public HtmlRenderer(String html, float x, float y, float height, float width) {
this.html = html;
this.x = x;
this.y = y;
this.height = height;
this.width = width;
this.isHtml = containsHTML(html);
this.instructions = new ArrayList<HtmlRenderInstruction>();
this.rendered = false;
this.helper = ResourceHelper.getInstance();
if (isHtml)
this.docTag = new XMLTag("Document", html);
}
/**
* Returns if the given text contains HTML.
*
* @param text
* @return
*/
private boolean containsHTML(String text) {
String lText = text.toLowerCase();
if (lText.contains("<a>") ||
lText.contains("<b>") ||
lText.contains("<body>") ||
lText.contains("<html>") ||
lText.contains("<i>") ||
lText.contains("<img") ||
lText.contains("<p>")) {
return true;
} else {
return false;
}
}
public void draw(Graphics2D g2) {
if (this.rendered == false)
render(g2);
for (HtmlRenderInstruction hri: instructions)
hri.draw(g2);
}
public Rectangle2D getBounds(Graphics2D g2) {
double hriWidth;
Rectangle2D hriBounds;
Rectangle2D renderBounds = null;
if (this.rendered == false)
render(g2);
for (HtmlRenderInstruction hri: instructions) {
hriBounds = hri.getBounds(g2);
hriWidth = ((hriBounds.getX() - x) + hriBounds.getWidth());
if (renderBounds == null) {
renderBounds = hriBounds;
} else {
double x = Math.min(renderBounds.getMinX(), hriBounds.getMinX());
double y = Math.min(renderBounds.getMinY(), hriBounds.getMinY());
double w = Math.max(renderBounds.getWidth(), hriWidth);
double t = (hriBounds.getMinY() - y) + hriBounds.getHeight();
double h = Math.max(renderBounds.getHeight(), t);
renderBounds.setRect(x, y, w, h);
}
}
return renderBounds;
}
public void render(Graphics2D g2) {
if (isHtml) {
renderHtml(g2);
} else {
renderText(g2);
}
}
private ImageRenderInstruction processImageInstruction(SmartTokenizer st,
String currentToken,
String whitespace,
float lineX,
float lineY) {
while(st.hasMore()) {
currentToken = st.nextToken();
if (currentToken.substring(0, 3).equalsIgnoreCase("src")) {
String imgSrc = currentToken.substring(5);
if (imgSrc.endsWith("\">")) {
imgSrc = imgSrc.substring(0, imgSrc.length() - 2);
} else if (!imgSrc.endsWith("\"")) {
imgSrc = imgSrc + whitespace + st.getTextTo('"');
st.jumpAfterChar('>');
} else if (imgSrc.endsWith("\"")) {
imgSrc = imgSrc.substring(0, imgSrc.length() - 1);
st.jumpAfterChar('>');
}
return new ImageRenderInstruction(helper.getBufferedImage(imgSrc), lineX, lineY);
}
}
return null;
}
public void renderHtml(Graphics2D g2) {
float currentLineWidth, currentLineX, currentLineY, maxLineHeight;
float lineSpacing;
FontMetrics fontMetrics;
Rectangle2D textBounds;
SmartTokenizer st;
String currentToken, whitespace;
StringBuilder currentLine;
XMLTag startTag;
if (docTag.containsSubTag("html")) {
startTag = docTag.getSubtag("html");
} else {
startTag = docTag;
}
if (startTag.containsSubTag("body"))
startTag = startTag.getSubtag("body");
//init
g2.setFont(g2.getFont().deriveFont(Font.PLAIN));
fontMetrics = g2.getFontMetrics();
maxLineHeight = 1;
currentLineWidth = 0;
currentLineX = x;
currentLineY = y;
lineSpacing = 3;
currentLine = new StringBuilder();
for (XMLTag currentTag: startTag.getSubtags()) {
st = new SmartTokenizer(currentTag.getTagContent());
while(st.hasMore()) {
currentToken = st.nextToken();
whitespace = st.getNextWhiteSpace();
if (currentToken.equalsIgnoreCase("<img")) {
if (currentLine.length() > 0) {
//need to write out text.
instructions.add(new TextRenderInstruction(currentLine.toString(), g2.getFont(), currentLineX, currentLineY));
textBounds = fontMetrics.getStringBounds(currentLine.toString(), g2);
currentLineX += textBounds.getWidth();
currentLine = new StringBuilder();
}
ImageRenderInstruction iri = processImageInstruction(st, currentToken, whitespace, currentLineX, currentLineY - 8);
if (iri != null) {
instructions.add(iri);
currentLineX += (float) iri.getBounds(g2).getWidth();
maxLineHeight = (float) Math.max(maxLineHeight, iri.getBounds(g2).getHeight());
}
} else if (currentToken.equalsIgnoreCase("<br>")) {
//new line
if (currentLine.length() > 0) {
instructions.add(new TextRenderInstruction(currentLine.toString(), g2.getFont(), currentLineX, currentLineY));
textBounds = fontMetrics.getStringBounds(currentLine.toString(), g2);
}
currentLineY += maxLineHeight + lineSpacing;
currentLineX = x;
} else {
textBounds = fontMetrics.getStringBounds(currentToken + whitespace, g2);
if ((currentLineWidth + textBounds.getWidth()) < width) {
currentLineWidth += textBounds.getWidth();
currentLine.append(currentToken);
currentLine.append(whitespace);
maxLineHeight = (float) Math.max(maxLineHeight, textBounds.getHeight());
} else {
//New Line
instructions.add(new TextRenderInstruction(currentLine.toString(), g2.getFont(), currentLineX, currentLineY));
currentLineY += maxLineHeight + lineSpacing;
currentLine = new StringBuilder();
maxLineHeight = 1;
currentLineWidth = 0;
currentLineX = x;
currentLine.append(currentToken);
currentLine.append(whitespace);
maxLineHeight = (float) Math.max(maxLineHeight, textBounds.getHeight());
}
}
} // String Tokenizer has more tokens loop
//draw final line
if (currentLine.length() > 0) {
instructions.add(new TextRenderInstruction(currentLine.toString(), g2.getFont(), currentLineX, currentLineY));
textBounds = fontMetrics.getStringBounds(currentLine.toString(), g2);
currentLineX += textBounds.getWidth();
}
}
rendered = true;
}
public void renderText(Graphics2D g2) {
float currentLineWidth, currentLineY, maxLineHeight;
float lineSpacing;
FontMetrics fontMetrics;
Rectangle2D textBounds;
SmartTokenizer st;
String currentToken, whitespace;
StringBuilder currentLine;
//init
g2.setFont(g2.getFont().deriveFont(Font.PLAIN));
fontMetrics = g2.getFontMetrics();
maxLineHeight = 1;
currentLineWidth = 0;
currentLineY = y;
lineSpacing = 3;
currentLine = new StringBuilder();
st = new SmartTokenizer(html);
while(st.hasMore()) {
currentToken = st.nextToken();
whitespace = st.getNextWhiteSpace();
textBounds = fontMetrics.getStringBounds(currentToken + whitespace, g2);
if ((currentLineWidth + textBounds.getWidth()) < width) {
currentLineWidth += textBounds.getWidth();
currentLine.append(currentToken);
currentLine.append(whitespace);
maxLineHeight = (float) Math.max(maxLineHeight, textBounds.getHeight());
} else {
//New Line
instructions.add(new TextRenderInstruction(currentLine.toString(), g2.getFont(), x, currentLineY));
currentLineY += maxLineHeight + lineSpacing;
currentLine = new StringBuilder();
maxLineHeight = 1;
currentLineWidth = 0;
currentLine.append(currentToken);
currentLine.append(whitespace);
maxLineHeight = (float) Math.max(maxLineHeight, textBounds.getHeight());
}
} // String Tokenizer has more tokens loop
//draw final line
instructions.add(new TextRenderInstruction(currentLine.toString(), g2.getFont(), x, currentLineY));
rendered = true;
}
}