Skip to content

Commit

Permalink
Added document metadata and PDF document properties support plus font…
Browse files Browse the repository at this point in the history
… style and weight emulation
  • Loading branch information
gmottram committed Feb 1, 2012
1 parent e8b1465 commit 735c1f0
Show file tree
Hide file tree
Showing 7 changed files with 274 additions and 6 deletions.
6 changes: 5 additions & 1 deletion src/java/org/xhtmlrenderer/css/style/CalculatedStyle.java
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,10 @@ public FontSpecification getFont(CssContext ctx) {
return _font;
}

public FontSpecification getFontSpecification() {
return _font;
}

private IdentValue resolveAbsoluteFontSize() {
FSDerivedValue fontSize = valueByName(CSSName.FONT_SIZE);
if (! (fontSize instanceof IdentValue)) {
Expand Down Expand Up @@ -453,7 +457,7 @@ public float getLineHeight(CssContext ctx) {
// Make sure rasterized characters will (probably) fit inside
// the line box
FSFontMetrics metrics = getFSFontMetrics(ctx);
float lineHeight2 = (float)Math.ceil(metrics.getDescent() + Math.round(metrics.getAscent()));
float lineHeight2 = (float)Math.ceil(metrics.getDescent() + metrics.getAscent());
_lineHeight = Math.max(lineHeight1, lineHeight2);
} else if (isLength(CSSName.LINE_HEIGHT)) {
//could be more elegant, I suppose
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ public class DefaultPDFCreationListener implements PDFCreationListener {
*/
public void preOpen(ITextRenderer iTextRenderer) { }

/**
* {@inheritDoc}
*/
public void preWrite(ITextRenderer iTextRenderer, int pageCount) {}

/**
* {@inheritDoc}
*/
Expand Down
4 changes: 2 additions & 2 deletions src/java/org/xhtmlrenderer/pdf/ITextFontResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -394,8 +394,8 @@ private FSFont resolveFont(SharedContext ctx, String fontFamily, float size, Ide

return null;
}

private int convertWeightToInt(IdentValue weight) {
public static int convertWeightToInt(IdentValue weight) {
if (weight == IdentValue.NORMAL) {
return 400;
} else if (weight == IdentValue.BOLD) {
Expand Down
198 changes: 196 additions & 2 deletions src/java/org/xhtmlrenderer/pdf/ITextOutputDevice.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,19 @@

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xhtmlrenderer.css.constants.IdentValue;
import org.xhtmlrenderer.css.parser.FSCMYKColor;
import org.xhtmlrenderer.css.parser.FSColor;
import org.xhtmlrenderer.css.parser.FSRGBColor;
import org.xhtmlrenderer.css.style.CalculatedStyle;
import org.xhtmlrenderer.css.style.CssContext;
import org.xhtmlrenderer.css.value.FontSpecification;
import org.xhtmlrenderer.extend.FSImage;
import org.xhtmlrenderer.extend.NamespaceHandler;
import org.xhtmlrenderer.extend.OutputDevice;
import org.xhtmlrenderer.layout.SharedContext;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextFontResolver.FontDescription;
import org.xhtmlrenderer.render.AbstractOutputDevice;
import org.xhtmlrenderer.render.BlockBox;
import org.xhtmlrenderer.render.BorderPainter;
Expand Down Expand Up @@ -135,6 +139,8 @@ public class ITextOutputDevice extends AbstractOutputDevice implements OutputDev

private List _bookmarks = new ArrayList();

private List _metadata = new ArrayList();

private Box _root;

private int _startPageNo;
Expand Down Expand Up @@ -497,14 +503,40 @@ public void drawString(String s, float x, float y, JustificationInfo info) {
double[] mx = new double[6];
inverse.getMatrix(mx);
cb.beginText();
cb.setFontAndSize(_font.getFontDescription().getFont(), _font.getSize2D() / _dotsPerPoint);
cb.setTextMatrix((float)mx[0], (float)mx[1], (float)mx[2], (float)mx[3], (float)mx[4], (float)mx[5]);
// Check if bold or italic need to be emulated
boolean resetMode = false;
FontDescription desc = _font.getFontDescription();
float fontSize = _font.getSize2D() / _dotsPerPoint;
cb.setFontAndSize(desc.getFont(), fontSize);
float b = (float) mx[1];
float c = (float) mx[2];
FontSpecification fontSpec = getFontSpecification();
if (fontSpec != null) {
int need = ITextFontResolver.convertWeightToInt(fontSpec.fontWeight);
int have = desc.getWeight();
if (need > have) {
cb.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE);
float lineWidth = fontSize * 0.04f; // 4% of font size
cb.setLineWidth(lineWidth);
resetMode = true;
}
if ((fontSpec.fontStyle == IdentValue.ITALIC)
&& (desc.getStyle() != IdentValue.ITALIC)) {
b = 0f;
c = 0.21256f;
}
}
cb.setTextMatrix((float)mx[0], b, c, (float)mx[3], (float)mx[4], (float)mx[5]);
if (info == null) {
cb.showText(s);
} else {
PdfTextArray array = makeJustificationArray(s, info);
cb.showText(array);
}
if (resetMode) {
cb.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_FILL);
cb.setLineWidth(1);
}
cb.endText();
}

Expand Down Expand Up @@ -886,6 +918,7 @@ public float getDotsPerPoint() {

public void start(Document doc) {
loadBookmarks(doc);
loadMetadata(doc);
}

public void finish(RenderingContext c, Box root) {
Expand Down Expand Up @@ -1013,6 +1046,167 @@ public List getChildren() {
}
}

// Metadata methods

// Methods to load and search a document's metadata

/**
* Appends a name/content metadata pair to this output device.
* A name or content value of null will be ignored.
*
* @param name the name of the metadata element to add.
* @return the content value for this metadata.
*/
public void addMetadata(String name, String value) {
if ((name != null) && (value != null)) {
Metadata m = new Metadata(name, value);
_metadata.add(m);
}
}

/**
* Searches the metadata name/content pairs of the current document and returns
* the content value from the first pair with a matching name. The search is
* case insensitive.
*
* @param name the metadata element name to locate.
* @return the content value of the first found metadata element; otherwise null.
*/
public String getMetadataByName(String name) {
if (name != null) {
for (int i = 0, len = _metadata.size(); i < len; i++) {
Metadata m = (Metadata) _metadata.get(i);
if ((m != null) && m.getName().equalsIgnoreCase(name)) {
return m.getContent();
}
}
}
return null;
}

/**
* Searches the metadata name/content pairs of the current document and returns
* any content values with a matching name in an ArrayList. The search is
* case insensitive.
*
* @param name the metadata element name to locate.
* @return an ArrayList with matching content values; otherwise an empty list.
*/
public ArrayList getMetadataListByName(String name) {
ArrayList result = new ArrayList();
if (name != null) {
for (int i = 0, len = _metadata.size(); i < len; i++) {
Metadata m = (Metadata) _metadata.get(i);
if ((m != null) && m.getName().equalsIgnoreCase(name)) {
result.add(m.getContent());
}
}
}
return result;
}

/**
* Locates and stores all metadata values in the document head that contain name/content pairs.
* If there is no pair with a name of "title", any content in the title element
* is saved as a "title" metadata item.
*
* @param doc the Document level node of the parsed xhtml file.
*/
private void loadMetadata(Document doc) {
Element head = DOMUtil.getChild(doc.getDocumentElement(), "head");
if (head != null) {
List l = DOMUtil.getChildren(head, "meta");
if (l != null) {
for (Iterator i = l.iterator(); i.hasNext(); ) {
Element e = (Element)i.next();
String name = e.getAttribute("name");
if (name != null) { // ignore non-name metadata data
String content = e.getAttribute("content");
Metadata m = new Metadata(name, content);
_metadata.add(m);
}
}
}
// If there is no title meta data attribute, use the document title.
String title = getMetadataByName("title");
if (title == null) {
Element t = DOMUtil.getChild(head, "title");
if (t != null) {
title = DOMUtil.getText(t).trim();
Metadata m = new Metadata("title", title);
_metadata.add(m);
}
}
}
}

/**
* Replaces all copies of the named metadata with a single value.
* A a new value of null will result in the removal of all copies
* of the named metadata. Use <code>addMetadata</code> to append
* additional values with the same name.
*
* @param name the metadata element name to locate.
* @return the new content value for this metadata (null to remove all instances).
*/
public void setMetadata(String name, String value) {
if (name != null) {
boolean remove = (value == null); // removing all instances of name?
int free = -1; // first open slot in array
for (int i = 0, len = _metadata.size(); i < len; i++) {
Metadata m = (Metadata) _metadata.get(i);
if (m != null) {
if (m.getName().equalsIgnoreCase(name)) {
if (!remove) {
remove = true; // remove all other instances
m.setContent(value);
} else {
_metadata.set(i, null);
}
}
} else if (free == -1) {
free = i;
}
}
if (!remove) { // not found?
Metadata m = new Metadata(name, value);
if (free == -1) { // no open slots?
_metadata.add(m);
} else {
_metadata.set(free, m);
}
}
}
}

// Class for storing metadata element name/content pairs from the head section of an xhtml document.
private static class Metadata {
private String _name;
private String _content;

public Metadata(String name, String content) {
_name = name;
_content = content;
}

public String getContent() {
return _content;
}

public void setContent(String content) {
_content = content;
}

public String getName() {
return _name;
}

public void setName(String name) {
_name = name;
}
}
// Metadata end

public SharedContext getSharedContext() {
return _sharedContext;
}
Expand Down
31 changes: 31 additions & 0 deletions src/java/org/xhtmlrenderer/pdf/ITextRenderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ public ITextRenderer(float dotsPerPoint, int dotsPerPixel) {
_sharedContext.setInteractive(false);
}

public Document getDocument() {
return _doc;
}

public ITextFontResolver getFontResolver() {
return (ITextFontResolver)_sharedContext.getFontResolver();
}
Expand Down Expand Up @@ -332,6 +336,11 @@ private void firePreOpen() {
_listener.preOpen(this);
}
}
private void firePreWrite(int pageCount) {
if (_listener != null) {
_listener.preWrite(this, pageCount);
}
}
private void fireOnClose() {
if (_listener != null) {
_listener.onClose(this);
Expand All @@ -349,6 +358,8 @@ private void writePDF(List pages, RenderingContext c, com.lowagie.text.Rectangle

int pageCount = _root.getLayer().getPages().size();
c.setPageCount(pageCount);
firePreWrite(pageCount); // opportunity to adjust meta data
setDidValues(doc); // set PDF header fields from meta data
for (int i = 0; i < pageCount; i++) {
PageBox currentPage = (PageBox)pages.get(i);
c.setPage(i, currentPage);
Expand All @@ -370,6 +381,26 @@ private void writePDF(List pages, RenderingContext c, com.lowagie.text.Rectangle
_outputDevice.finish(c, _root);
}

// Sets the document information dictionary values from html metadata
private void setDidValues(com.lowagie.text.Document doc) {
String v = _outputDevice.getMetadataByName("title");
if (v != null) {
doc.addTitle(v);
}
v = _outputDevice.getMetadataByName("author");
if (v != null) {
doc.addAuthor(v);
}
v = _outputDevice.getMetadataByName("subject");
if (v != null) {
doc.addSubject(v);
}
v = _outputDevice.getMetadataByName("keywords");
if (v != null) {
doc.addKeywords(v);
}
}

private void paintPage(RenderingContext c, PdfWriter writer, PageBox page) {
provideMetadataToPage(writer, page);

Expand Down
11 changes: 11 additions & 0 deletions src/java/org/xhtmlrenderer/pdf/PDFCreationListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ public interface PDFCreationListener {
*/
void preOpen(ITextRenderer iTextRenderer);

/**
* Called immediately before the pages of the PDF file are about to be written out.
* This is an opportunity to modify any document metadata that will be used to generate
* the PDF header fields (the document information dictionary). Document metadata may be accessed
* through the {@link ITextOutputDevice} that is returned by {@link ITextRenderer#getOutputDevice()}.
*
* @param iTextRenderer the renderer preparing the document
* @param pageCount the number of pages that will be written to the PDF document
*/
void preWrite(ITextRenderer iTextRenderer, int pageCount);

/**
* Called immediately before the iText Document instance is closed, e.g. before
* {@link com.lowagie.text.Document#close()} is called.
Expand Down
Loading

0 comments on commit 735c1f0

Please sign in to comment.