aboutsummaryrefslogtreecommitdiffstats
path: root/src/java/org/apache/fop/render/pdf
diff options
context:
space:
mode:
authorJeremias Maerki <jeremias@apache.org>2007-05-07 12:22:22 +0000
committerJeremias Maerki <jeremias@apache.org>2007-05-07 12:22:22 +0000
commit719a8a50772a780263366f9286f7f132f4561e0c (patch)
tree4c0ebcbb84750d5f42901b3315afb1f654e11655 /src/java/org/apache/fop/render/pdf
parent283e22abdafff51ac4b5aefcdc7fe06ca9cfb61e (diff)
downloadxmlgraphics-fop-719a8a50772a780263366f9286f7f132f4561e0c.tar.gz
xmlgraphics-fop-719a8a50772a780263366f9286f7f132f4561e0c.zip
Bugzilla #42067:
Add support for exact positioning of internal PDF links. Submitted by: Paul Vinkenoog <paul.at.vinkenoog.nl> git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@535866 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java/org/apache/fop/render/pdf')
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFRenderer.java441
1 files changed, 363 insertions, 78 deletions
diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java
index f64fa75aa..9813d08db 100644
--- a/src/java/org/apache/fop/render/pdf/PDFRenderer.java
+++ b/src/java/org/apache/fop/render/pdf/PDFRenderer.java
@@ -24,6 +24,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
+import java.awt.geom.Point2D;
import java.awt.Color;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
@@ -48,6 +49,8 @@ import org.apache.commons.io.IOUtils;
import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.MimeConstants;
+import org.apache.fop.area.Area;
+import org.apache.fop.area.Block;
import org.apache.fop.area.CTM;
import org.apache.fop.area.LineArea;
import org.apache.fop.area.OffDocumentExtensionAttachment;
@@ -60,6 +63,7 @@ import org.apache.fop.area.inline.AbstractTextArea;
import org.apache.fop.area.inline.TextArea;
import org.apache.fop.area.inline.Image;
import org.apache.fop.area.inline.Leader;
+import org.apache.fop.area.inline.InlineArea;
import org.apache.fop.area.inline.InlineParent;
import org.apache.fop.area.inline.WordArea;
import org.apache.fop.area.inline.SpaceArea;
@@ -69,17 +73,19 @@ import org.apache.fop.fonts.FontSetup;
import org.apache.fop.image.FopImage;
import org.apache.fop.image.ImageFactory;
import org.apache.fop.image.XMLImage;
+import org.apache.fop.pdf.PDFAction;
import org.apache.fop.pdf.PDFAMode;
import org.apache.fop.pdf.PDFAnnotList;
import org.apache.fop.pdf.PDFColor;
import org.apache.fop.pdf.PDFConformanceException;
-import org.apache.fop.pdf.PDFDestination;
import org.apache.fop.pdf.PDFDocument;
import org.apache.fop.pdf.PDFEncryptionManager;
import org.apache.fop.pdf.PDFEncryptionParams;
+import org.apache.fop.pdf.PDFFactory;
import org.apache.fop.pdf.PDFFilterList;
import org.apache.fop.pdf.PDFICCBasedColorSpace;
import org.apache.fop.pdf.PDFICCStream;
+import org.apache.fop.pdf.PDFGoTo;
import org.apache.fop.pdf.PDFInfo;
import org.apache.fop.pdf.PDFLink;
import org.apache.fop.pdf.PDFMetadata;
@@ -164,16 +170,35 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
protected Map pages = null;
/**
- * Page references are stored using the PageViewport as the key
- * when a reference is made the PageViewport is used
- * for pdf this means we need the pdf page reference
+ * Maps unique PageViewport key to PDF page reference
*/
protected Map pageReferences = new java.util.HashMap();
- /** Page viewport references */
+ /**
+ * Maps unique PageViewport key back to PageViewport itself
+ */
protected Map pvReferences = new java.util.HashMap();
/**
+ * Maps XSL-FO element IDs to their on-page XY-positions
+ * Must be used in conjunction with the page reference to fully specify the PDFGoTo details
+ */
+ protected Map idPositions = new java.util.HashMap();
+
+ /**
+ * Maps XSL-FO element IDs to PDFGoTo objects targeting the corresponding areas
+ * These objects may not all be fully filled in yet
+ */
+ protected Map idGoTos = new java.util.HashMap();
+
+ /**
+ * The PDFGoTos in idGoTos that are not complete yet
+ */
+ protected List unfinishedGoTos = new java.util.ArrayList();
+ // can't use a Set because PDFGoTo.equals returns true if the target is the same,
+ // even if the object number differs
+
+ /**
* The output stream to write the document to
*/
protected OutputStream ostream;
@@ -197,7 +222,12 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
* the current page to add annotations to
*/
protected PDFPage currentPage;
-
+
+ /**
+ * the current page's PDF reference string (to avoid numerous function calls)
+ */
+ protected String currentPageRef;
+
/** the (optional) encryption parameters */
protected PDFEncryptionParams encryptionParams;
@@ -479,9 +509,34 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
}
/**
+ * Checks if there are any unfinished PDFGoTos left in the list and resolves them
+ * to a default position on the page. Logs a warning, as this should not happen.
+ */
+ protected void finishOpenGoTos() {
+ int count = unfinishedGoTos.size();
+ if (count > 0) {
+ // TODO : page height may not be the same for all targeted pages
+ Point2D.Float defaultPos = new Point2D.Float(0f, pageHeight / 1000f); // top-o-page
+ while (!unfinishedGoTos.isEmpty()) {
+ PDFGoTo gt = (PDFGoTo) unfinishedGoTos.get(0);
+ finishIDGoTo(gt, defaultPos);
+ }
+ boolean one = count == 1;
+ String pl = one ? "" : "s";
+ String ww = one ? "was" : "were";
+ String ia = one ? "is" : "are";
+ log.warn("" + count + " link target" + pl + " could not be fully resolved and "
+ + ww + " now point to the top of the page or "
+ + ia + " dysfunctional."); // dysfunctional if pageref is null
+ }
+ }
+
+ /**
* @see org.apache.fop.render.Renderer#stopRenderer()
*/
public void stopRenderer() throws IOException {
+ finishOpenGoTos();
+
pdfDoc.getResources().addFonts(pdfDoc, fontInfo);
pdfDoc.outputTrailer(ostream);
@@ -498,6 +553,9 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
currentPage = null;
currentState = null;
currentFontName = "";
+
+ idPositions.clear();
+ idGoTos.clear();
}
/**
@@ -514,17 +572,9 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
public void processOffDocumentItem(OffDocumentItem odi) {
// render Destinations
if (odi instanceof DestinationData) {
- PDFDestination destination = pdfDoc.getFactory().makeDestination((DestinationData) odi);
- PageViewport pv = destination.getPageViewport();
- String dest = (String)pageReferences.get(pv.getKey());
- Rectangle2D bounds = pv.getViewArea();
- double h = bounds.getHeight();
- float yoffset = (float)h / 1000f;
- String gtRef = pdfDoc.getFactory().getGoToReference(dest, yoffset);
- destination.setGoToReference(gtRef);
- }
+ renderDestination((DestinationData) odi);
// render Bookmark-Tree
- else if (odi instanceof BookmarkData) {
+ } else if (odi instanceof BookmarkData) {
renderBookmarkTree((BookmarkData) odi);
} else if (odi instanceof OffDocumentExtensionAttachment) {
ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)odi).getAttachment();
@@ -534,6 +584,24 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
}
}
+ private void renderDestination(DestinationData dd) {
+ String targetID = dd.getIDRef();
+ if (targetID != null && targetID.length() > 0) {
+ PageViewport pv = dd.getPageViewport();
+ if (pv != null) {
+ String pvKey = pv.getKey();
+ PDFGoTo gt = getPDFGoToForID(targetID, pvKey);
+ // create/find and register PDFDestination object:
+ pdfDoc.getFactory().makeDestination(targetID, gt.referencePDF(), pv);
+ } else {
+ log.warn("DestinationData item with IDRef \""
+ + targetID + "\" has a null PageViewport.");
+ }
+ } else {
+ log.warn("DestinationData item with null or empty IDRef received.");
+ }
+ }
+
/**
* Renders a Bookmark-Tree object
* @param bookmarks the BookmarkData object containing all the Bookmark-Items
@@ -545,36 +613,34 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
}
}
- private void renderBookmarkItem(BookmarkData bookmarkItem,
- PDFOutline parentBookmarkItem) {
+ private void renderBookmarkItem(BookmarkData bookmarkItem,
+ PDFOutline parentBookmarkItem) {
PDFOutline pdfOutline = null;
- PageViewport pv = bookmarkItem.getPageViewport();
- if (pv != null) {
- Rectangle2D bounds = pv.getViewArea();
- double h = bounds.getHeight();
- float yoffset = (float)h / 1000f;
- String intDest = (String)pageReferences.get(pv.getKey());
- if (parentBookmarkItem == null) {
- PDFOutline outlineRoot = pdfDoc.getOutlineRoot();
- pdfOutline = pdfDoc.getFactory().makeOutline(outlineRoot,
- bookmarkItem.getBookmarkTitle(),
- intDest, yoffset,
- bookmarkItem.showChildItems());
+
+ String targetID = bookmarkItem.getIDRef();
+ if (targetID != null && targetID.length() > 0) {
+ PageViewport pv = bookmarkItem.getPageViewport();
+ if (pv != null) {
+ String pvKey = pv.getKey();
+ PDFGoTo gt = getPDFGoToForID(targetID, pvKey);
+ // create outline object:
+ PDFOutline parent = parentBookmarkItem != null
+ ? parentBookmarkItem
+ : pdfDoc.getOutlineRoot();
+ pdfOutline = pdfDoc.getFactory().makeOutline(parent,
+ bookmarkItem.getBookmarkTitle(), gt, bookmarkItem.showChildItems());
} else {
- pdfOutline = pdfDoc.getFactory().makeOutline(parentBookmarkItem,
- bookmarkItem.getBookmarkTitle(),
- intDest, yoffset,
- bookmarkItem.showChildItems());
+ log.warn("Bookmark with IDRef \"" + targetID + "\" has a null PageViewport.");
}
} else {
- log.warn("Unresolved bookmark item received: " + bookmarkItem.getIDRef());
+ log.warn("Bookmark item with null or empty IDRef received.");
}
for (int i = 0; i < bookmarkItem.getCount(); i++) {
renderBookmarkItem(bookmarkItem.getSubData(i), pdfOutline);
}
}
-
+
private void renderXMPMetadata(XMPMetadata metadata) {
Metadata docXMP = metadata.getMetadata();
Metadata fopXMP = PDFMetadata.createXMPFromUserAgent(pdfDoc);
@@ -707,6 +773,8 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
} else {
setupPage(page);
}
+ currentPageRef = currentPage.referencePDF();
+
Rectangle2D bounds = page.getViewArea();
double h = bounds.getHeight();
pageHeight = (int) h;
@@ -1089,6 +1157,188 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
}
/**
+ * Returns area's id if it is the first area in the document with that id
+ * (i.e. if the area qualifies as a link target).
+ * Otherwise, or if the area has no id, null is returned.
+ *
+ * NOTE : area must be on currentPageViewport, otherwise result may be wrong!
+ *
+ * @param area the area for which to return the id
+ */
+ protected String getTargetableID(Area area) {
+ String id = (String) area.getTrait(Trait.PROD_ID);
+ if (id == null || id.length() == 0
+ || !currentPageViewport.isFirstWithID(id)
+ || idPositions.containsKey(id)) {
+ return null;
+ } else {
+ return id;
+ }
+ }
+
+ /**
+ * Set XY position in the PDFGoTo and add it to the PDF trailer.
+ *
+ * @param gt the PDFGoTo object
+ * @param position the X,Y position to set
+ */
+ protected void finishIDGoTo(PDFGoTo gt, Point2D.Float position) {
+ gt.setPosition(position);
+ pdfDoc.addTrailerObject(gt);
+ unfinishedGoTos.remove(gt);
+ }
+
+ /**
+ * Set page reference and XY position in the PDFGoTo and add it to the PDF trailer.
+ *
+ * @param gt the PDFGoTo object
+ * @param pdfPageRef the PDF reference string of the target page object
+ * @param position the X,Y position to set
+ */
+ protected void finishIDGoTo(PDFGoTo gt, String pdfPageRef, Point2D.Float position) {
+ gt.setPageReference(pdfPageRef);
+ finishIDGoTo(gt, position);
+ }
+
+ /**
+ * Get a PDFGoTo pointing to the given id. Create one if necessary.
+ * It is possible that the PDFGoTo is not fully resolved yet. In that case
+ * it must be completed (and added to the PDF trailer) later.
+ *
+ * @param targetID the target id of the PDFGoTo
+ * @param pvKey the unique key of the target PageViewport
+ *
+ * @return the PDFGoTo that was found or created
+ */
+ protected PDFGoTo getPDFGoToForID(String targetID, String pvKey) {
+ // Already a PDFGoTo present for this target? If not, create.
+ PDFGoTo gt = (PDFGoTo) idGoTos.get(targetID);
+ if (gt == null) {
+ String pdfPageRef = (String) pageReferences.get(pvKey);
+ Point2D.Float position = (Point2D.Float) idPositions.get(targetID);
+ // can the GoTo already be fully filled in?
+ if (pdfPageRef != null && position != null) {
+ // getPDFGoTo shares PDFGoTo objects as much as possible.
+ // It also takes care of assignObjectNumber and addTrailerObject.
+ gt = pdfDoc.getFactory().getPDFGoTo(pdfPageRef, position);
+ } else {
+ // Not complete yet, can't use getPDFGoTo:
+ gt = new PDFGoTo(pdfPageRef);
+ pdfDoc.assignObjectNumber(gt);
+ // pdfDoc.addTrailerObject() will be called later, from finishIDGoTo()
+ unfinishedGoTos.add(gt);
+ }
+ idGoTos.put(targetID, gt);
+ }
+ return gt;
+ }
+
+ /**
+ * Saves id's absolute position on page for later retrieval by PDFGoTos
+ *
+ * @param id the id of the area whose position must be saved
+ * @param pdfPageRef the PDF page reference string
+ * @param relativeIPP the *relative* IP position in millipoints
+ * @param relativeBPP the *relative* BP position in millipoints
+ * @param tf the transformation to apply once the relative positions have been
+ * converted to points
+ */
+ protected void saveAbsolutePosition(String id, String pdfPageRef,
+ int relativeIPP, int relativeBPP, AffineTransform tf) {
+ Point2D.Float position = new Point2D.Float(relativeIPP / 1000f, relativeBPP / 1000f);
+ tf.transform(position, position);
+ idPositions.put(id, position);
+ // is there already a PDFGoTo waiting to be completed?
+ PDFGoTo gt = (PDFGoTo) idGoTos.get(id);
+ if (gt != null) {
+ finishIDGoTo(gt, pdfPageRef, position);
+ }
+/*
+ // The code below auto-creates a named destination for every id in the document.
+ // This should probably be controlled by a user-configurable setting, as it may
+ // make the PDF file grow noticeably.
+ // *** NOT YET WELL-TESTED ! ***
+ if (true) {
+ PDFFactory factory = pdfDoc.getFactory();
+ if (gt == null) {
+ gt = factory.getPDFGoTo(pdfPageRef, position);
+ idGoTos.put(id, gt); // so others can pick it up too
+ }
+ factory.makeDestination(id, gt.referencePDF(), currentPageViewport);
+ // Note: using currentPageViewport is only correct if the id is indeed on
+ // the current PageViewport. But even if incorrect, it won't interfere with
+ // what gets created in the PDF.
+ // For speedup, we should also create a lookup map id -> PDFDestination
+ }
+*/
+ }
+
+ /**
+ * Saves id's absolute position on page for later retrieval by PDFGoTos,
+ * using the currently valid transformation and the currently valid PDF page reference
+ *
+ * @param id the id of the area whose position must be saved
+ * @param relativeIPP the *relative* IP position in millipoints
+ * @param relativeBPP the *relative* BP position in millipoints
+ */
+ protected void saveAbsolutePosition(String id, int relativeIPP, int relativeBPP) {
+ saveAbsolutePosition(id, currentPageRef,
+ relativeIPP, relativeBPP, currentState.getTransform());
+ }
+
+ /**
+ * If the given block area is a possible link target, its id + absolute position will
+ * be saved. The saved position is only correct if this function is called at the very
+ * start of renderBlock!
+ *
+ * @param block the block area in question
+ */
+ protected void saveBlockPosIfTargetable(Block block) {
+ String id = getTargetableID(block);
+ if (id != null) {
+ // FIXME: Like elsewhere in the renderer code, absolute and relative
+ // directions are happily mixed here. This makes sure that the
+ // links point to the right location, but it is not correct.
+ int ipp = block.getXOffset();
+ int bpp = block.getYOffset() + block.getSpaceBefore();
+ int positioning = block.getPositioning();
+ if (!(positioning == Block.FIXED || positioning == Block.ABSOLUTE)) {
+ ipp += currentIPPosition;
+ bpp += currentBPPosition;
+ }
+ AffineTransform tf = positioning == Block.FIXED
+ ? currentState.getBaseTransform()
+ : currentState.getTransform();
+ saveAbsolutePosition(id, currentPageRef, ipp, bpp, tf);
+ }
+ }
+
+ /**
+ * If the given inline area is a possible link target, its id + absolute position will
+ * be saved. The saved position is only correct if this function is called at the very
+ * start of renderInlineArea!
+ *
+ * @param inlineArea the inline area in question
+ */
+ protected void saveInlinePosIfTargetable(InlineArea inlineArea) {
+ String id = getTargetableID(inlineArea);
+ if (id != null) {
+ int extraMarginBefore = 5000; // millipoints
+ int ipp = currentIPPosition;
+ int bpp = currentBPPosition + inlineArea.getOffset() - extraMarginBefore;
+ saveAbsolutePosition(id, ipp, bpp);
+ }
+ }
+
+ /**
+ * @see org.apache.fop.render.AbstractRenderer#renderBlock(Block)
+ */
+ protected void renderBlock(Block block) {
+ saveBlockPosIfTargetable(block);
+ super.renderBlock(block);
+ }
+
+ /**
* @see org.apache.fop.render.AbstractRenderer#renderLineArea(LineArea)
*/
protected void renderLineArea(LineArea line) {
@@ -1097,53 +1347,88 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
}
/**
+ * @see org.apache.fop.render.AbstractRenderer#renderInlineArea(InlineArea)
+ */
+ protected void renderInlineArea(InlineArea inlineArea) {
+ saveInlinePosIfTargetable(inlineArea);
+ super.renderInlineArea(inlineArea);
+ }
+
+ /**
* Render inline parent area.
* For pdf this handles the inline parent area traits such as
* links, border, background.
* @param ip the inline parent area
*/
public void renderInlineParent(InlineParent ip) {
- float start = currentIPPosition / 1000f;
- float top = (ip.getOffset() + currentBPPosition) / 1000f;
- float width = ip.getIPD() / 1000f;
- float height = ip.getBPD() / 1000f;
+ boolean annotsAllowed = pdfDoc.getProfile().isAnnotationAllowed();
+
+ // stuff we only need if a link must be created:
+ Rectangle2D ipRect = null;
+ PDFFactory factory = null;
+ PDFAction action = null;
+ if (annotsAllowed) {
+ // make sure the rect is determined *before* calling super!
+ int ipp = currentIPPosition;
+ int bpp = currentBPPosition + ip.getOffset();
+ ipRect = new Rectangle2D.Float(ipp / 1000f, bpp / 1000f,
+ ip.getIPD() / 1000f, ip.getBPD() / 1000f);
+ AffineTransform transform = currentState.getTransform();
+ ipRect = transform.createTransformedShape(ipRect).getBounds2D();
+
+ factory = pdfDoc.getFactory();
+ }
+
// render contents
super.renderInlineParent(ip);
- if (pdfDoc.getProfile().isAnnotationAllowed()) {
- // place the link over the top
- Object tr = ip.getTrait(Trait.INTERNAL_LINK);
- boolean internal = false;
- String dest = null;
- float yoffset = 0;
- if (tr == null) {
- dest = (String)ip.getTrait(Trait.EXTERNAL_LINK);
+ boolean linkTraitFound = false;
+
+ // try INTERNAL_LINK first
+ Trait.InternalLink intLink = (Trait.InternalLink) ip.getTrait(Trait.INTERNAL_LINK);
+ if (intLink != null) {
+ linkTraitFound = true;
+ String pvKey = intLink.getPVKey();
+ String idRef = intLink.getIDRef();
+ boolean pvKeyOK = pvKey != null && pvKey.length() > 0;
+ boolean idRefOK = idRef != null && idRef.length() > 0;
+ if (pvKeyOK && idRefOK) {
+ if (annotsAllowed) {
+ action = getPDFGoToForID(idRef, pvKey);
+ }
+ } else if (pvKeyOK) {
+ log.warn("Internal link trait with PageViewport key " + pvKey
+ + " contains no ID reference.");
+ } else if (idRefOK) {
+ log.warn("Internal link trait with ID reference " + idRef
+ + " contains no PageViewport key.");
} else {
- String pvKey = (String)tr;
- dest = (String)pageReferences.get(pvKey);
- if (dest != null) {
- PageViewport pv = (PageViewport)pvReferences.get(pvKey);
- Rectangle2D bounds = pv.getViewArea();
- double h = bounds.getHeight();
- yoffset = (float)h / 1000f;
- internal = true;
+ log.warn("Internal link trait received with neither PageViewport key"
+ + " nor ID reference.");
+ }
+ }
+
+ // no INTERNAL_LINK, look for EXTERNAL_LINK
+ if (!linkTraitFound) {
+ String extDest = (String) ip.getTrait(Trait.EXTERNAL_LINK);
+ if (extDest != null && extDest.length() > 0) {
+ linkTraitFound = true;
+ if (annotsAllowed) {
+ action = factory.getExternalAction(extDest);
}
}
- if (dest != null) {
- // add link to pdf document
- Rectangle2D rect = new Rectangle2D.Float(start, top, width, height);
- // transform rect to absolute coords
- AffineTransform transform = currentState.getTransform();
- rect = transform.createTransformedShape(rect).getBounds2D();
-
- int type = internal ? PDFLink.INTERNAL : PDFLink.EXTERNAL;
- PDFLink pdflink = pdfDoc.getFactory().makeLink(
- rect, dest, type, yoffset);
- currentPage.addAnnotation(pdflink);
+ }
+
+ // warn if link trait found but not allowed, else create link
+ if (linkTraitFound) {
+ if (!annotsAllowed) {
+ log.warn("Skipping annotation for a link due to PDF profile: "
+ + pdfDoc.getProfile());
+ } else if (action != null) {
+ PDFLink pdfLink = factory.makeLink(ipRect, action);
+ currentPage.addAnnotation(pdfLink);
}
- } else {
- log.warn("Skipping annotation for a link due to PDF profile: " + pdfDoc.getProfile());
}
}
@@ -1199,12 +1484,12 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
String s = word.getWord();
escapeText(s, word.getLetterAdjustArray(),
font, (AbstractTextArea)word.getParentArea(), useMultiByte, pdf);
-
+
currentStream.add(pdf.toString());
super.renderWord(word);
}
-
+
/**
* @see org.apache.fop.render.AbstractRenderer#renderSpace(SpaceArea)
*/
@@ -1223,18 +1508,18 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
if (space.isAdjustable()) {
int tws = -((TextArea) space.getParentArea()).getTextWordSpaceAdjust()
- 2 * textArea.getTextLetterSpaceAdjust();
-
+
if (tws != 0) {
pdf.append(format(tws / (font.getFontSize() / 1000f)));
pdf.append(" ");
}
}
-
+
currentStream.add(pdf.toString());
super.renderSpace(space);
}
-
+
/**
* Escapes text according to PDF rules.
* @param s Text to escape
@@ -1282,9 +1567,9 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
}
}
if (letterAdjust != null && i < l - 1) {
- glyphAdjust -= letterAdjust[i + 1];
+ glyphAdjust -= letterAdjust[i + 1];
}
-
+
if (startPending) {
pdf.append(startText);
startPending = false;
@@ -1309,12 +1594,12 @@ public class PDFRenderer extends AbstractPathOrientedRenderer {
}
float adjust = glyphAdjust / fontSize;
-
+
if (adjust != 0) {
pdf.append(endText).append(format(adjust)).append(' ');
startPending = true;
}
-
+
}
if (!startPending) {
pdf.append(endText);