From b0c16b8261de832e90c75617b91bb4763f6d0e6b Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Tue, 12 Jan 2016 23:20:48 +0000 Subject: [PATCH] #57796 - Support hyperlink extraction when rendering slides git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1724338 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/hslf/examples/CreateHyperlink.java | 2 +- .../apache/poi/hslf/examples/Hyperlinks.java | 2 +- .../apache/poi/sl/draw/DrawTextParagraph.java | 77 +++-- .../org/apache/poi/sl/usermodel/TextRun.java | 7 + .../poi/xslf/usermodel/XSLFHyperlink.java | 32 +- .../poi/xslf/usermodel/XSLFTextRun.java | 1 + .../apache/poi/hslf/model/PPGraphics2D.java | 2 +- .../poi/hslf/usermodel/HSLFHyperlink.java | 71 +++-- .../apache/poi/hslf/usermodel/HSLFNotes.java | 7 - .../apache/poi/hslf/usermodel/HSLFSheet.java | 4 +- .../poi/hslf/usermodel/HSLFSimpleShape.java | 2 +- .../apache/poi/hslf/usermodel/HSLFSlide.java | 6 - .../poi/hslf/usermodel/HSLFSlideMaster.java | 6 - .../poi/hslf/usermodel/HSLFSlideShow.java | 278 ++++++++---------- .../poi/hslf/usermodel/HSLFTextParagraph.java | 71 ++++- .../poi/hslf/usermodel/HSLFTextRun.java | 65 ++++ .../poi/hslf/usermodel/HSLFTextShape.java | 9 +- .../apache/poi/hslf/model/TestHyperlink.java | 6 +- .../apache/poi/hslf/usermodel/TestBugs.java | 61 +++- test-data/slideshow/WithLinks.ppt | Bin 20992 -> 20992 bytes 20 files changed, 459 insertions(+), 250 deletions(-) diff --git a/src/examples/src/org/apache/poi/hslf/examples/CreateHyperlink.java b/src/examples/src/org/apache/poi/hslf/examples/CreateHyperlink.java index 1d8c95a19..44e5d81c1 100644 --- a/src/examples/src/org/apache/poi/hslf/examples/CreateHyperlink.java +++ b/src/examples/src/org/apache/poi/hslf/examples/CreateHyperlink.java @@ -46,7 +46,7 @@ public abstract class CreateHyperlink { String text = textBox1.getText(); HSLFHyperlink link = new HSLFHyperlink(); link.setAddress("http://www.apache.org"); - link.setTitle(textBox1.getText()); + link.setLabel(textBox1.getText()); int linkId = ppt.addHyperlink(link); // apply link to the text diff --git a/src/examples/src/org/apache/poi/hslf/examples/Hyperlinks.java b/src/examples/src/org/apache/poi/hslf/examples/Hyperlinks.java index c054d2e7a..8cbbcdc85 100644 --- a/src/examples/src/org/apache/poi/hslf/examples/Hyperlinks.java +++ b/src/examples/src/org/apache/poi/hslf/examples/Hyperlinks.java @@ -70,6 +70,6 @@ public final class Hyperlinks { //in ppt end index is inclusive String formatStr = "title: %1$s, address: %2$s" + (rawText == null ? "" : ", start: %3$s, end: %4$s, substring: %5$s"); String substring = (rawText == null) ? "" : rawText.substring(link.getStartIndex(), link.getEndIndex()-1); - return String.format(formatStr, link.getTitle(), link.getAddress(), link.getStartIndex(), link.getEndIndex(), substring); + return String.format(formatStr, link.getLabel(), link.getAddress(), link.getStartIndex(), link.getEndIndex(), substring); } } diff --git a/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java b/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java index 65233fe0b..e8a7b0b21 100644 --- a/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java +++ b/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java @@ -24,6 +24,7 @@ import java.awt.font.LineBreakMeasurer; import java.awt.font.TextAttribute; import java.awt.font.TextLayout; import java.awt.geom.Rectangle2D; +import java.io.InvalidObjectException; import java.text.AttributedCharacterIterator; import java.text.AttributedCharacterIterator.Attribute; import java.text.AttributedString; @@ -32,6 +33,7 @@ import java.util.List; import java.util.Map; import org.apache.poi.sl.usermodel.AutoNumberingScheme; +import org.apache.poi.sl.usermodel.Hyperlink; import org.apache.poi.sl.usermodel.Insets2D; import org.apache.poi.sl.usermodel.PaintStyle; import org.apache.poi.sl.usermodel.PlaceableShape; @@ -46,6 +48,10 @@ import org.apache.poi.util.StringUtil; import org.apache.poi.util.Units; public class DrawTextParagraph implements Drawable { + /** Keys for passing hyperlinks to the graphics context */ + public static final XlinkAttribute HYPERLINK_HREF = new XlinkAttribute("href"); + public static final XlinkAttribute HYPERLINK_LABEL = new XlinkAttribute("label"); + protected TextParagraph paragraph; double x, y; protected List lines = new ArrayList(); @@ -58,6 +64,31 @@ public class DrawTextParagraph implements Drawable { */ protected double maxLineHeight; + /** + * Defines an attribute used for storing the hyperlink associated with + * some renderable text. + */ + private static class XlinkAttribute extends Attribute { + + XlinkAttribute(String name) { + super(name); + } + + /** + * Resolves instances being deserialized to the predefined constants. + */ + protected Object readResolve() throws InvalidObjectException { + if (HYPERLINK_HREF.getName().equals(getName())) { + return HYPERLINK_HREF; + } + if (HYPERLINK_LABEL.getName().equals(getName())) { + return HYPERLINK_LABEL; + } + throw new InvalidObjectException("unknown attribute name"); + } + } + + public DrawTextParagraph(TextParagraph paragraph) { this.paragraph = paragraph; } @@ -79,10 +110,10 @@ public class DrawTextParagraph implements Drawable { public void setAutoNumberingIdx(int index) { autoNbrIdx = index; } - + public void draw(Graphics2D graphics){ if (lines.isEmpty()) return; - + double penY = y; boolean firstLine = true; @@ -100,7 +131,7 @@ public class DrawTextParagraph implements Drawable { // special handling for HSLF indent -= leftMargin; } - + // Double rightMargin = paragraph.getRightMargin(); // if (rightMargin == null) { // rightMargin = 0d; @@ -109,7 +140,7 @@ public class DrawTextParagraph implements Drawable { //The vertical line spacing Double spacing = paragraph.getLineSpacing(); if (spacing == null) spacing = 100d; - + for(DrawTextFragment line : lines){ double penX; @@ -118,7 +149,7 @@ public class DrawTextParagraph implements Drawable { // TODO: find out character style for empty, but bulleted/numbered lines bullet = getBullet(graphics, line.getAttributedString().getIterator()); } - + if (bullet != null){ bullet.setPosition(x+leftMargin+indent, penY); bullet.draw(graphics); @@ -168,7 +199,7 @@ public class DrawTextParagraph implements Drawable { y = penY - y; } - + public float getFirstLineHeight() { return (lines.isEmpty()) ? 0 : lines.get(0).getHeight(); } @@ -180,7 +211,7 @@ public class DrawTextParagraph implements Drawable { public boolean isEmptyParagraph() { return (lines.isEmpty() || rawText.trim().isEmpty()); } - + public void applyTransform(Graphics2D graphics) { } @@ -265,7 +296,7 @@ public class DrawTextParagraph implements Drawable { String buFont = bulletStyle.getBulletFont(); if (buFont == null) buFont = paragraph.getDefaultFontFamily(); assert(buFont != null); - + PlaceableShape ps = getParagraphShape(); PaintStyle fgPaintStyle = bulletStyle.getBulletFontColor(); Paint fgPaint; @@ -277,11 +308,11 @@ public class DrawTextParagraph implements Drawable { float fontSize = (Float)firstLineAttr.getAttribute(TextAttribute.SIZE); Double buSz = bulletStyle.getBulletFontSize(); - if (buSz == null) buSz = 100d; + if (buSz == null) buSz = 100d; if (buSz > 0) fontSize *= buSz* 0.01; else fontSize = (float)-buSz; - + AttributedString str = new AttributedString(mapFontCharset(buCharacter,buFont)); str.addAttribute(TextAttribute.FOREGROUND, fgPaint); str.addAttribute(TextAttribute.FAMILY, buFont); @@ -313,7 +344,7 @@ public class DrawTextParagraph implements Drawable { case SMALL: c = Character.toLowerCase(c); break; case NONE: break; } - + buf.append(c); break; } @@ -321,7 +352,7 @@ public class DrawTextParagraph implements Drawable { return buf.toString(); } - + /** * Replace a tab with the effective number of white spaces. */ @@ -334,7 +365,7 @@ public class DrawTextParagraph implements Drawable { Double fs = tr.getFontSize(); if (fs == null) fs = 12d; string.addAttribute(TextAttribute.SIZE, fs.floatValue()); - + TextLayout l = new TextLayout(string.getIterator(), new FontRenderContext(null, true, true)); double wspace = l.getAdvance(); @@ -348,7 +379,7 @@ public class DrawTextParagraph implements Drawable { } return buf.toString(); } - + /** * Returns wrapping width to break lines in this paragraph @@ -418,7 +449,7 @@ public class DrawTextParagraph implements Drawable { this.endIndex = endIndex; } } - + /** * Helper method for paint style relative to bounds, e.g. gradient paint */ @@ -443,7 +474,7 @@ public class DrawTextParagraph implements Drawable { if (text == null) text = new StringBuilder(); PlaceableShape ps = getParagraphShape(); - + DrawFontManager fontHandler = (DrawFontManager)graphics.getRenderingHint(Drawable.FONT_HANDLER); for (TextRun run : paragraph){ @@ -464,7 +495,7 @@ public class DrawTextParagraph implements Drawable { if (fontFamily == null) { fontFamily = paragraph.getDefaultFontFamily(); } - + int beginIndex = text.length(); text.append(mapFontCharset(runText,fontFamily)); int endIndex = text.length(); @@ -498,6 +529,12 @@ public class DrawTextParagraph implements Drawable { if(run.isSuperscript()) { attList.add(new AttributedStringData(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUPER, beginIndex, endIndex)); } + + Hyperlink hl = run.getHyperlink(); + if (hl != null) { + attList.add(new AttributedStringData(HYPERLINK_HREF, hl.getAddress(), beginIndex, endIndex)); + attList.add(new AttributedStringData(HYPERLINK_LABEL, hl.getLabel(), beginIndex, endIndex)); + } } // ensure that the paragraph contains at least one character @@ -517,9 +554,9 @@ public class DrawTextParagraph implements Drawable { } protected boolean isHSLF() { - return paragraph.getClass().getName().contains("HSLF"); + return paragraph.getClass().getName().contains("HSLF"); } - + /** * Map text charset depending on font family. * Currently this only maps for wingdings font (into unicode private use area) @@ -527,7 +564,7 @@ public class DrawTextParagraph implements Drawable { * @param text the raw text * @param fontFamily the font family * @return AttributedString with mapped codepoints - * + * * @see Drawing exotic fonts in a java applet * @see StringUtil#mapMsCodepointString(String) */ diff --git a/src/java/org/apache/poi/sl/usermodel/TextRun.java b/src/java/org/apache/poi/sl/usermodel/TextRun.java index 9eb27b616..28db79795 100644 --- a/src/java/org/apache/poi/sl/usermodel/TextRun.java +++ b/src/java/org/apache/poi/sl/usermodel/TextRun.java @@ -156,4 +156,11 @@ public interface TextRun { * @return the pitch and family id or -1 if not applicable */ byte getPitchAndFamily(); + + /** + * Return the associated hyperlink + * + * @return the associated hyperlink or null if no hyperlink was set + */ + Hyperlink getHyperlink(); } diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFHyperlink.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFHyperlink.java index 5a25851b5..0b093ecac 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFHyperlink.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFHyperlink.java @@ -18,15 +18,13 @@ package org.apache.poi.xslf.usermodel; import org.apache.poi.openxml4j.opc.PackageRelationship; import org.apache.poi.openxml4j.opc.TargetMode; +import org.apache.poi.sl.usermodel.Hyperlink; import org.apache.poi.util.Internal; import org.openxmlformats.schemas.drawingml.x2006.main.CTHyperlink; import java.net.URI; -/** - * @author Yegor Kozlov - */ -public class XSLFHyperlink { +public class XSLFHyperlink implements Hyperlink { final XSLFTextRun _r; final CTHyperlink _link; @@ -40,15 +38,39 @@ public class XSLFHyperlink { return _link; } + @Override public void setAddress(String address){ XSLFSheet sheet = _r.getParentParagraph().getParentShape().getSheet(); PackageRelationship rel = sheet.getPackagePart(). addExternalRelationship(address, XSLFRelation.HYPERLINK.getRelation()); _link.setId(rel.getId()); - + } + + @Override + public String getAddress() { + return getTargetURI().toASCIIString(); } + @Override + public String getLabel() { + return _link.getTooltip(); + } + + @Override + public void setLabel(String label) { + _link.setTooltip(label); + } + + @Override + public int getType() { + // TODO: currently this just returns nonsense + if ("ppaction://hlinksldjump".equals(_link.getAction())) { + return LINK_DOCUMENT; + } + return LINK_URL; + } + public void setAddress(XSLFSlide slide){ XSLFSheet sheet = _r.getParentParagraph().getParentShape().getSheet(); PackageRelationship rel = diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java index 8ce7c5241..1b2272ba3 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java @@ -449,6 +449,7 @@ public class XSLFTextRun implements TextRun { return link; } + @Override public XSLFHyperlink getHyperlink(){ if(!_r.getRPr().isSetHlinkClick()) return null; diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/PPGraphics2D.java b/src/scratchpad/src/org/apache/poi/hslf/model/PPGraphics2D.java index 0022822b8..4a3af15f8 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/PPGraphics2D.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/PPGraphics2D.java @@ -282,7 +282,7 @@ public final class PPGraphics2D extends Graphics2D implements Cloneable { */ public void drawString(String s, float x, float y) { HSLFTextBox txt = new HSLFTextBox(_group); - txt.getTextParagraphs().get(0).supplySheet(_group.getSheet()); + txt.setSheet(_group.getSheet()); txt.setText(s); HSLFTextRun rt = txt.getTextParagraphs().get(0).getTextRuns().get(0); diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFHyperlink.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFHyperlink.java index d20f0efa9..f896103b0 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFHyperlink.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFHyperlink.java @@ -29,13 +29,12 @@ import org.apache.poi.hslf.record.InteractiveInfo; import org.apache.poi.hslf.record.InteractiveInfoAtom; import org.apache.poi.hslf.record.Record; import org.apache.poi.hslf.record.TxInteractiveInfoAtom; +import org.apache.poi.sl.usermodel.Hyperlink; /** * Represents a hyperlink in a PowerPoint document - * - * @author Yegor Kozlov */ -public final class HSLFHyperlink { +public final class HSLFHyperlink implements Hyperlink { public static final byte LINK_NEXTSLIDE = InteractiveInfoAtom.LINK_NextSlide; public static final byte LINK_PREVIOUSSLIDE = InteractiveInfoAtom.LINK_PreviousSlide; public static final byte LINK_FIRSTSLIDE = InteractiveInfoAtom.LINK_FirstSlide; @@ -47,7 +46,7 @@ public final class HSLFHyperlink { private int id=-1; private int type; private String address; - private String title; + private String label; private int startIndex, endIndex; /** @@ -57,6 +56,7 @@ public final class HSLFHyperlink { * @return the hyperlink URL * @see InteractiveInfoAtom */ + @Override public int getType() { return type; } @@ -65,35 +65,31 @@ public final class HSLFHyperlink { type = val; switch(type){ case LINK_NEXTSLIDE: - title = "NEXT"; + label = "NEXT"; address = "1,-1,NEXT"; break; case LINK_PREVIOUSSLIDE: - title = "PREV"; + label = "PREV"; address = "1,-1,PREV"; break; case LINK_FIRSTSLIDE: - title = "FIRST"; + label = "FIRST"; address = "1,-1,FIRST"; break; case LINK_LASTSLIDE: - title = "LAST"; + label = "LAST"; address = "1,-1,LAST"; break; case LINK_SLIDENUMBER: break; default: - title = ""; + label = ""; address = ""; break; } } - /** - * Gets the hyperlink URL - * - * @return the hyperlink URL - */ + @Override public String getAddress() { return address; } @@ -101,10 +97,11 @@ public final class HSLFHyperlink { public void setAddress(HSLFSlide slide) { String href = slide._getSheetNumber() + ","+slide.getSlideNumber()+",Slide " + slide.getSlideNumber(); setAddress(href);; - setTitle("Slide " + slide.getSlideNumber()); + setLabel("Slide " + slide.getSlideNumber()); setType(HSLFHyperlink.LINK_SLIDENUMBER); } + @Override public void setAddress(String str) { address = str; } @@ -117,17 +114,14 @@ public final class HSLFHyperlink { this.id = id; } - /** - * Gets the hyperlink user-friendly title (if different from URL) - * - * @return the hyperlink user-friendly title - */ - public String getTitle() { - return title; + @Override + public String getLabel() { + return label; } - public void setTitle(String str) { - title = str; + @Override + public void setLabel(String str) { + label = str; } /** @@ -139,6 +133,15 @@ public final class HSLFHyperlink { return startIndex; } + /** + * Sets the beginning character position + * + * @param startIndex the beginning character position + */ + public void setStartIndex(int startIndex) { + this.startIndex = startIndex; + } + /** * Gets the ending character position * @@ -148,6 +151,15 @@ public final class HSLFHyperlink { return endIndex; } + /** + * Sets the ending character position + * + * @param endIndex the ending character position + */ + public void setEndIndex(int endIndex) { + this.endIndex = endIndex; + } + /** * Find hyperlinks in a text shape * @@ -222,9 +234,10 @@ public final class HSLFHyperlink { } HSLFHyperlink link = new HSLFHyperlink(); - link.title = linkRecord.getLinkTitle(); - link.address = linkRecord.getLinkURL(); - link.type = info.getAction(); + link.setId(id); + link.setType(info.getAction()); + link.setLabel(linkRecord.getLinkTitle()); + link.setAddress(linkRecord.getLinkURL()); out.add(link); if (iter.hasNext()) { @@ -234,8 +247,8 @@ public final class HSLFHyperlink { continue; } TxInteractiveInfoAtom txinfo = (TxInteractiveInfoAtom)r; - link.startIndex = txinfo.getStartIndex(); - link.endIndex = txinfo.getEndIndex(); + link.setStartIndex(txinfo.getStartIndex()); + link.setEndIndex(txinfo.getEndIndex()); } } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFNotes.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFNotes.java index 50125ea48..d64775e11 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFNotes.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFNotes.java @@ -56,13 +56,6 @@ public final class HSLFNotes extends HSLFSheet implements Notes ltp : _paragraphs) { - for (HSLFTextParagraph tp : ltp) { - tp.supplySheet(this); - } - } } /** diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSheet.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSheet.java index 3fe79aeb1..ee6155773 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSheet.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSheet.java @@ -133,9 +133,7 @@ public abstract class HSLFSheet implements HSLFShapeContainer, Sheet> trs = getTextParagraphs(); if (trs == null) return; for (List ltp : trs) { - for (HSLFTextParagraph tp : ltp) { - tp.supplySheet(this); - } + HSLFTextParagraph.supplySheet(ltp, this); } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSimpleShape.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSimpleShape.java index 4da85a946..b78a5c8fa 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSimpleShape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSimpleShape.java @@ -310,7 +310,7 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape l : HSLFTextParagraph.findTextParagraphs(getPPDrawing(), this)) { if (!_paragraphs.contains(l)) _paragraphs.add(l); } - - for(List ltp : _paragraphs) { - for (HSLFTextParagraph tp : ltp) { - tp.supplySheet(this); - } - } } /** diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideMaster.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideMaster.java index 1e89b69a9..9b5668ffe 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideMaster.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideMaster.java @@ -51,12 +51,6 @@ public final class HSLFSlideMaster extends HSLFMasterSheet { for (List l : HSLFTextParagraph.findTextParagraphs(getPPDrawing(), this)) { if (!_paragraphs.contains(l)) _paragraphs.add(l); } - - for (List p : _paragraphs) { - for (HSLFTextParagraph htp : p) { - htp.supplySheet(this); - } - } } /** diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShow.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShow.java index f08c39807..fae9e2059 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShow.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShow.java @@ -33,6 +33,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.poi.hslf.record.MainMaster; import org.apache.poi.ddf.EscherBSERecord; import org.apache.poi.ddf.EscherContainerRecord; import org.apache.poi.ddf.EscherOptRecord; @@ -59,12 +60,14 @@ import org.apache.poi.hslf.record.ExVideoContainer; import org.apache.poi.hslf.record.FontCollection; import org.apache.poi.hslf.record.FontEntityAtom; import org.apache.poi.hslf.record.HeadersFootersContainer; +import org.apache.poi.hslf.record.Notes; import org.apache.poi.hslf.record.PersistPtrHolder; import org.apache.poi.hslf.record.PositionDependentRecord; import org.apache.poi.hslf.record.PositionDependentRecordContainer; import org.apache.poi.hslf.record.Record; import org.apache.poi.hslf.record.RecordContainer; import org.apache.poi.hslf.record.RecordTypes; +import org.apache.poi.hslf.record.Slide; import org.apache.poi.hslf.record.SlideListWithText; import org.apache.poi.hslf.record.SlideListWithText.SlideAtomsSet; import org.apache.poi.hslf.record.SlidePersistAtom; @@ -120,12 +123,6 @@ public final class HSLFSlideShow implements SlideShow slideIdToNotes = new HashMap(); - // Start by finding the notes records to go with the entries in - // notesSLWT - org.apache.poi.hslf.record.Notes[] notesRecords; - Map slideIdToNotes = new HashMap(); - if (notesSLWT == null) { - // None - notesRecords = new org.apache.poi.hslf.record.Notes[0]; - } else { - // Match up the records and the SlideAtomSets - List notesRecordsL = - new ArrayList(); - int idx = -1; - for (SlideAtomsSet notesSet : notesSLWT.getSlideAtomsSets()) { - idx++; - // Get the right core record - Record r = getCoreRecordForSAS(notesSet); - SlidePersistAtom spa = notesSet.getSlidePersistAtom(); - - String loggerLoc = "A Notes SlideAtomSet at "+idx+" said its record was at refID "+spa.getRefID(); - - // Ensure it really is a notes record - if (r == null || r instanceof org.apache.poi.hslf.record.Notes) { - if (r == null) { - logger.log(POILogger.WARN, loggerLoc+", but that record didn't exist - record ignored."); - } - // we need to add also null-records, otherwise the index references to other existing - // don't work anymore - org.apache.poi.hslf.record.Notes notesRecord = (org.apache.poi.hslf.record.Notes) r; - notesRecordsL.add(notesRecord); - - // Record the match between slide id and these notes - int slideId = spa.getSlideIdentifier(); - slideIdToNotes.put(slideId, idx); - } else { - logger.log(POILogger.ERROR, loggerLoc+", but that was actually a " + r); - } - } - notesRecords = new org.apache.poi.hslf.record.Notes[notesRecordsL.size()]; - notesRecords = notesRecordsL.toArray(notesRecords); - } + // Start by finding the notes records + findNotesSlides(slideIdToNotes); // Now, do the same thing for our slides - org.apache.poi.hslf.record.Slide[] slidesRecords; - SlideAtomsSet[] slidesSets = new SlideAtomsSet[0]; - if (slidesSLWT == null) { - // None - slidesRecords = new org.apache.poi.hslf.record.Slide[0]; - } else { - // Match up the records and the SlideAtomSets - slidesSets = slidesSLWT.getSlideAtomsSets(); - slidesRecords = new org.apache.poi.hslf.record.Slide[slidesSets.length]; - for (int i = 0; i < slidesSets.length; i++) { - // Get the right core record - Record r = getCoreRecordForSAS(slidesSets[i]); - - // Ensure it really is a slide record - if (r instanceof org.apache.poi.hslf.record.Slide) { - slidesRecords[i] = (org.apache.poi.hslf.record.Slide) r; - } else { - logger.log(POILogger.ERROR, "A Slide SlideAtomSet at " + i - + " said its record was at refID " - + slidesSets[i].getSlidePersistAtom().getRefID() - + ", but that was actually a " + r); - } - } - } - - // Finally, generate model objects for everything - // Notes first - for (org.apache.poi.hslf.record.Notes n : notesRecords) { - HSLFNotes hn = null; - if (n != null) { - hn = new HSLFNotes(n); - hn.setSlideShow(this); - } - _notes.add(hn); - } - // Then slides - for (int i = 0; i < slidesRecords.length; i++) { - SlideAtomsSet sas = slidesSets[i]; - int slideIdentifier = sas.getSlidePersistAtom().getSlideIdentifier(); - - // Do we have a notes for this? - HSLFNotes notes = null; - // Slide.SlideAtom.notesId references the corresponding notes slide. - // 0 if slide has no notes. - int noteId = slidesRecords[i].getSlideAtom().getNotesID(); - if (noteId != 0) { - Integer notesPos = slideIdToNotes.get(noteId); - if (notesPos != null) { - notes = _notes.get(notesPos); - } else { - logger.log(POILogger.ERROR, "Notes not found for noteId=" + noteId); - } - } - - // Now, build our slide - HSLFSlide hs = new HSLFSlide(slidesRecords[i], notes, sas, slideIdentifier, (i + 1)); - hs.setSlideShow(this); - _slides.add(hs); - } + findSlides(slideIdToNotes); } + /** + * Find master slides + * These can be MainMaster records, but oddly they can also be + * Slides or Notes, and possibly even other odd stuff.... + * About the only thing you can say is that the master details are in the first SLWT. + */ + private void findMasterSlides() { + SlideListWithText masterSLWT = _documentRecord.getMasterSlideListWithText(); + if (masterSLWT == null) { + return; + } + + for (SlideAtomsSet sas : masterSLWT.getSlideAtomsSets()) { + Record r = getCoreRecordForSAS(sas); + int sheetNo = sas.getSlidePersistAtom().getSlideIdentifier(); + if (r instanceof Slide) { + HSLFTitleMaster master = new HSLFTitleMaster((Slide)r, sheetNo); + master.setSlideShow(this); + _titleMasters.add(master); + } else if (r instanceof MainMaster) { + HSLFSlideMaster master = new HSLFSlideMaster((MainMaster)r, sheetNo); + master.setSlideShow(this); + _masters.add(master); + } + } + } + + private void findNotesSlides(Map slideIdToNotes) { + SlideListWithText notesSLWT = _documentRecord.getNotesSlideListWithText(); + + if (notesSLWT == null) { + return; + } + + // Match up the records and the SlideAtomSets + int idx = -1; + for (SlideAtomsSet notesSet : notesSLWT.getSlideAtomsSets()) { + idx++; + // Get the right core record + Record r = getCoreRecordForSAS(notesSet); + SlidePersistAtom spa = notesSet.getSlidePersistAtom(); + + String loggerLoc = "A Notes SlideAtomSet at "+idx+" said its record was at refID "+spa.getRefID(); + + // we need to add null-records, otherwise the index references to other existing don't work anymore + if (r == null) { + logger.log(POILogger.WARN, loggerLoc+", but that record didn't exist - record ignored."); + continue; + } + + // Ensure it really is a notes record + if (!(r instanceof Notes)) { + logger.log(POILogger.ERROR, loggerLoc+", but that was actually a " + r); + continue; + } + + Notes notesRecord = (Notes) r; + + // Record the match between slide id and these notes + int slideId = spa.getSlideIdentifier(); + slideIdToNotes.put(slideId, idx); + + HSLFNotes hn = new HSLFNotes(notesRecord); + hn.setSlideShow(this); + _notes.add(hn); + } + } + + private void findSlides(Map slideIdToNotes) { + SlideListWithText slidesSLWT = _documentRecord.getSlideSlideListWithText(); + if (slidesSLWT == null) { + return; + } + + // Match up the records and the SlideAtomSets + int idx = -1; + for (SlideAtomsSet sas : slidesSLWT.getSlideAtomsSets()) { + idx++; + // Get the right core record + SlidePersistAtom spa = sas.getSlidePersistAtom(); + Record r = getCoreRecordForSAS(sas); + + // Ensure it really is a slide record + if (!(r instanceof Slide)) { + logger.log(POILogger.ERROR, "A Slide SlideAtomSet at " + idx + + " said its record was at refID " + + spa.getRefID() + + ", but that was actually a " + r); + continue; + } + + Slide slide = (Slide)r; + + // Do we have a notes for this? + HSLFNotes notes = null; + // Slide.SlideAtom.notesId references the corresponding notes slide. + // 0 if slide has no notes. + int noteId = slide.getSlideAtom().getNotesID(); + if (noteId != 0) { + Integer notesPos = slideIdToNotes.get(noteId); + if (notesPos != null && 0 <= notesPos && notesPos < _notes.size()) { + notes = _notes.get(notesPos); + } else { + logger.log(POILogger.ERROR, "Notes not found for noteId=" + noteId); + } + } + + // Now, build our slide + int slideIdentifier = spa.getSlideIdentifier(); + HSLFSlide hs = new HSLFSlide(slide, notes, sas, slideIdentifier, (idx + 1)); + hs.setSlideShow(this); + _slides.add(hs); + } + } + @Override public void write(OutputStream out) throws IOException { // check for text paragraph modifications @@ -512,12 +504,6 @@ public final class HSLFSlideShow implements SlideShowSlide. * @@ -786,7 +760,7 @@ public final class HSLFSlideShow implements SlideShow parentList; /** * Constructs a Text Run from a Unicode text block. @@ -110,11 +112,13 @@ public final class HSLFTextParagraph implements TextParagraph parentList ) { if (tha == null) { throw new IllegalArgumentException("TextHeaderAtom must be set."); @@ -122,6 +126,7 @@ public final class HSLFTextParagraph implements TextParagraph paragraphs, HSLFSheet sheet) { + if (paragraphs == null) { + return; + } + for (HSLFTextParagraph p : paragraphs) { + p.supplySheet(sheet); + } + + assert(sheet.getSlideShow() != null); + applyHyperlinks(paragraphs); + } + + /** + * Supply the Sheet we belong to, which might have an assigned SlideShow + * Also passes it on to our child RichTextRuns + */ + private void supplySheet(HSLFSheet sheet) { this._sheet = sheet; if (_runs == null) return; @@ -942,7 +964,7 @@ public final class HSLFTextParagraph implements TextParagraph paragraphs) { + List links = HSLFHyperlink.find(paragraphs); + + for (HSLFHyperlink h : links) { + int csIdx = 0; + for (HSLFTextParagraph p : paragraphs) { + for (HSLFTextRun r : p) { + if (h.getStartIndex() <= csIdx && csIdx < h.getEndIndex()) { + r.setHyperlinkId(h.getId()); + } + csIdx += r.getLength(); + } + } + } + } + protected static void applyCharacterStyles(List paragraphs, List charStyles) { int paraIdx = 0, runIdx = 0; HSLFTextRun trun; @@ -1340,15 +1378,17 @@ public final class HSLFTextParagraph implements TextParagraph paragraphs = new ArrayList(1); + HSLFTextParagraph htp = new HSLFTextParagraph(tha, tba, null, paragraphs); htp.setParagraphStyle(paraStyle); + paragraphs.add(htp); HSLFTextRun htr = new HSLFTextRun(htp); htr.setCharacterStyle(charStyle); htr.setText(""); htp.addTextRun(htr); - return Arrays.asList(htp); + return paragraphs; } public EscherTextboxWrapper getTextboxWrapper() { @@ -1397,4 +1437,23 @@ public final class HSLFTextParagraph implements TextParagraph { // (We can't do it in the constructor because the sheet // is not assigned then, it's only built once we have // all the records) - List paras = getTextParagraphs(); - if (paras != null) { - for (HSLFTextParagraph htp : paras) { - // Supply the sheet to our child RichTextRuns - htp.supplySheet(_sheet); - } - } + List ltp = getTextParagraphs(); + HSLFTextParagraph.supplySheet(ltp, sheet); } /** diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestHyperlink.java b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestHyperlink.java index 5429b96fb..715640a1d 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestHyperlink.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestHyperlink.java @@ -57,11 +57,11 @@ public final class TestHyperlink { assertNotNull(links); assertEquals(2, links.size()); - assertEquals("http://jakarta.apache.org/poi/", links.get(0).getTitle()); + assertEquals("http://jakarta.apache.org/poi/", links.get(0).getLabel()); assertEquals("http://jakarta.apache.org/poi/", links.get(0).getAddress()); assertEquals("http://jakarta.apache.org/poi/", rawText.substring(links.get(0).getStartIndex(), links.get(0).getEndIndex()-1)); - assertEquals("http://slashdot.org/", links.get(1).getTitle()); + assertEquals("http://slashdot.org/", links.get(1).getLabel()); assertEquals("http://slashdot.org/", links.get(1).getAddress()); assertEquals("http://slashdot.org/", rawText.substring(links.get(1).getStartIndex(), links.get(1).getEndIndex()-1)); @@ -77,7 +77,7 @@ public final class TestHyperlink { assertNotNull(links); assertEquals(1, links.size()); - assertEquals("http://jakarta.apache.org/poi/hssf/", links.get(0).getTitle()); + assertEquals("Open Jakarta POI HSSF module test ", links.get(0).getLabel()); assertEquals("http://jakarta.apache.org/poi/hssf/", links.get(0).getAddress()); assertEquals("Jakarta HSSF", rawText.substring(links.get(0).getStartIndex(), links.get(0).getEndIndex()-1)); } diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java index 2c166ebf1..e3b2683e5 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestBugs.java @@ -26,6 +26,10 @@ import static org.junit.Assert.assertTrue; import java.awt.Color; import java.io.File; import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.text.AttributedCharacterIterator; +import java.text.AttributedCharacterIterator.Attribute; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -47,7 +51,9 @@ import org.apache.poi.hslf.record.Record; import org.apache.poi.hslf.record.SlideListWithText; import org.apache.poi.hslf.record.SlideListWithText.SlideAtomsSet; import org.apache.poi.hslf.record.TextHeaderAtom; +import org.apache.poi.hssf.usermodel.DummyGraphics2d; import org.apache.poi.sl.draw.DrawPaint; +import org.apache.poi.sl.draw.DrawTextParagraph; import org.apache.poi.sl.usermodel.PaintStyle; import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint; import org.apache.poi.sl.usermodel.PictureData.PictureType; @@ -67,8 +73,6 @@ import org.junit.Test; /** * Testcases for bugs entered in bugzilla * the Test name contains the bugzilla bug id - * - * @author Yegor Kozlov */ public final class TestBugs { /** @@ -822,6 +826,59 @@ public final class TestBugs { ppt.close(); } + + @Test + public void bug57796() throws IOException { + HSLFSlideShow ppt = open("WithLinks.ppt"); + HSLFSlide slide = ppt.getSlides().get(0); + HSLFTextShape shape = (HSLFTextShape)slide.getShapes().get(1); + List hlList = HSLFHyperlink.find(shape); + HSLFHyperlink hlShape = hlList.get(0); + HSLFTextRun r = shape.getTextParagraphs().get(1).getTextRuns().get(0); + HSLFHyperlink hlRun = r.getHyperlink(); + assertEquals(hlRun.getId(), hlShape.getId()); + assertEquals(hlRun.getAddress(), hlShape.getAddress()); + assertEquals(hlRun.getLabel(), hlShape.getLabel()); + assertEquals(hlRun.getType(), hlShape.getType()); + assertEquals(hlRun.getStartIndex(), hlShape.getStartIndex()); + assertEquals(hlRun.getEndIndex(), hlShape.getEndIndex()); + + OutputStream nullOutput = new OutputStream(){ + public void write(int b) throws IOException {} + }; + + final boolean found[] = { false }; + DummyGraphics2d dgfx = new DummyGraphics2d(new PrintStream(nullOutput)){ + public void drawString(AttributedCharacterIterator iterator, float x, float y) { + // For the test file, common sl draws textruns one by one and not mixed + // so we evaluate the whole iterator + Map attributes = null; + StringBuffer sb = new StringBuffer(); + + for (char c = iterator.first(); + c != AttributedCharacterIterator.DONE; + c = iterator.next()) { + sb.append(c); + attributes = iterator.getAttributes(); + } + + if ("Jakarta HSSF".equals(sb.toString())) { + // this is a test for a manually modified ppt, for real hyperlink label + // one would need to access the screen tip record + String href = (String)attributes.get(DrawTextParagraph.HYPERLINK_HREF); + String label = (String)attributes.get(DrawTextParagraph.HYPERLINK_LABEL); + assertEquals("http://jakarta.apache.org/poi/hssf/", href); + assertEquals("Open Jakarta POI HSSF module test ", label); + found[0] = true; + } + } + }; + + ppt.getSlides().get(1).draw(dgfx); + assertTrue(found[0]); + + ppt.close(); + } private static HSLFSlideShow open(String fileName) throws IOException { File sample = HSLFTestDataSamples.getSampleFile(fileName); diff --git a/test-data/slideshow/WithLinks.ppt b/test-data/slideshow/WithLinks.ppt index f08a81a09b78855b92cfceb0fe16f173a3c3820d..80d2e6dbb4ae08193e10f9c506c9860fbabdbb44 100644 GIT binary patch delta 90 zcmZoz!q~8caYKcmyFWt#Ln=cag93vWLn1>q5En6&07(Ug00w^sPay5V5DbKFKvph8 iK0^vaDMJoWhXPPlDnl`lQ~*MO$%TTxn|Xx3*aHA^wGz_+ delta 18 acmZoz!q~8caYKdR