From: Simon Steiner Date: Mon, 30 Jan 2017 15:24:20 +0000 (+0000) Subject: FOP-2676: basic-link external-destination does not work for file URI with spaces X-Git-Tag: fop-2_2~20 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=be3fd67bb0dce39f6676c64b2ede91918527590f;p=xmlgraphics-fop.git FOP-2676: basic-link external-destination does not work for file URI with spaces git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1780923 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/fop-core/src/main/java/org/apache/fop/pdf/PDFFactory.java b/fop-core/src/main/java/org/apache/fop/pdf/PDFFactory.java index 09debe179..4e9a58cdf 100644 --- a/fop-core/src/main/java/org/apache/fop/pdf/PDFFactory.java +++ b/fop-core/src/main/java/org/apache/fop/pdf/PDFFactory.java @@ -25,6 +25,8 @@ import java.awt.geom.Rectangle2D; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; import java.text.DecimalFormat; import java.util.Arrays; import java.util.BitSet; @@ -548,8 +550,6 @@ public class PDFFactory { return link; } - private static final String EMBEDDED_FILE = "embedded-file:"; - /** * Create/find and return the appropriate external PDFAction according to the target * @@ -560,39 +560,113 @@ public class PDFFactory { * @return the PDFAction thus created or found */ public PDFAction getExternalAction(String target, boolean newWindow) { + URI uri = getTargetUri(target); + if (uri != null) { + String scheme = uri.getScheme(); + String filename = uri.getPath(); + if (filename == null) { + filename = uri.getSchemeSpecificPart(); + } + if (scheme == null) { + return new PDFUri(uri.toASCIIString()); + } else if (scheme.equalsIgnoreCase("embedded-file")) { + return getActionForEmbeddedFile(filename, newWindow); + } else if (scheme.equalsIgnoreCase("file")) { + if (filename.startsWith("//")) { + filename = filename.replace("/", "\\"); + } else if (filename.matches("^/[A-z]:/.*")) { + filename = filename.substring(1); + } + if (filename.toLowerCase().endsWith(".pdf")) { + int page = -1; + String dest = null; + String fragment = uri.getFragment(); + if (fragment != null) { + String fragmentLo = fragment.toLowerCase(); + if (fragmentLo.startsWith("page=")) { + page = Integer.parseInt(fragmentLo.substring(5)); + } else if (fragmentLo.startsWith("dest=")) { + dest = fragment.substring(5); + } + } + return getGoToPDFAction(filename, dest, page, newWindow); + } else { + if (uri.getQuery() != null || uri.getFragment() != null) { + return new PDFUri(uri.toASCIIString()); + } else { + return getLaunchAction(filename, newWindow); + } + } + } else { + return new PDFUri(uri.toASCIIString()); + } + } + return new PDFUri(target); + } + + private URI getTargetUri(String target) { + URI uri; + try { + uri = new URI(target); + String scheme = uri.getScheme(); + String schemeSpecificPart = uri.getSchemeSpecificPart(); + String authority = uri.getAuthority(); + if (scheme == null && schemeSpecificPart.matches("//.*")) { + uri = getFileUri(target); + } else if ((scheme == null) && schemeSpecificPart.matches("/.*")) { + uri = getFileUri(target); + } else if (scheme != null && scheme.matches("[A-z]")) { + uri = getFileUri(target); + } else if (scheme != null && scheme.equalsIgnoreCase("file") && authority != null) { + uri = getFileUri(target); + } + } catch (URISyntaxException e) { + uri = getFileUri(target); + } + return uri; + } + + private URI getFileUri(String target) { + URI uri; + String scheme = null; + String fragment = null; + String filename = target; int index; String targetLo = target.toLowerCase(); - if (target.startsWith(EMBEDDED_FILE)) { - // File Attachments (Embedded Files) - String filename = target.substring(EMBEDDED_FILE.length()); - return getActionForEmbeddedFile(filename, newWindow); - } else if (targetLo.startsWith("http://")) { - // HTTP URL? - return new PDFUri(target); - } else if (targetLo.startsWith("https://")) { - // HTTPS URL? - return new PDFUri(target); - } else if (targetLo.startsWith("file://")) { - // Non PDF files. Try to /Launch them. - target = target.substring("file://".length()); - return getLaunchAction(target); - } else if (targetLo.endsWith(".pdf")) { - // Bare PDF file name? - return getGoToPDFAction(target, null, -1, newWindow); - } else if ((index = targetLo.indexOf(".pdf#page=")) > 0) { - // PDF file + page? - String filename = target.substring(0, index + 4); - int page = Integer.parseInt(target.substring(index + 10)); - return getGoToPDFAction(filename, null, page, newWindow); - } else if ((index = targetLo.indexOf(".pdf#dest=")) > 0) { - // PDF file + destination? - String filename = target.substring(0, index + 4); - String dest = target.substring(index + 10); - return getGoToPDFAction(filename, dest, -1, newWindow); - } else { - // None of the above? Default to URI: - return new PDFUri(target); + if (((index = targetLo.indexOf(".pdf#page=")) > 0) + || ((index = targetLo.indexOf(".pdf#dest=")) > 0)) { + filename = target.substring(0, index + 4); + fragment = target.substring(index + 5); + } + + if (targetLo.startsWith("file://")) { + scheme = "file"; + filename = filename.substring("file://".length()); + } else if (targetLo.startsWith("embedded-file:")) { + scheme = "embedded-file"; + filename = filename.substring("embedded-file:".length()); + } else if (targetLo.startsWith("file:")) { + scheme = "file"; + filename = filename.substring("file:".length()); + } + + try { + filename = filename.replace("\\", "/"); + if (filename.matches("[A-z]:.*")) { + scheme = (scheme == null) ? "file" : scheme; + filename = "/" + filename; + } else if (filename.matches("//.*")) { + scheme = (scheme == null) ? "file" : scheme; + filename = "//" + filename; + } else if (filename.matches("/.*")) { + scheme = (scheme == null) ? "file" : scheme; + } + uri = new URI(scheme, filename, fragment); + } catch (URISyntaxException e) { + throw new IllegalStateException(e); } + + return uri; } private PDFAction getActionForEmbeddedFile(String filename, boolean newWindow) { @@ -723,13 +797,15 @@ public class PDFFactory { /** * Creates and returns a launch pdf document action using - * file to create a file spcifiaciton for + * file to create a file specification for * the document/file to be opened with an external application. * * @param file the pdf file name + * @param newWindow boolean indicating whether the target should be + * displayed in a new window * @return the pdf launch object */ - private PDFLaunch getLaunchAction(String file) { + private PDFLaunch getLaunchAction(String file, boolean newWindow) { getDocument().getProfile().verifyActionAllowed(); PDFFileSpec fileSpec = new PDFFileSpec(file); @@ -740,7 +816,7 @@ public class PDFFactory { } else { fileSpec = oldSpec; } - PDFLaunch launch = new PDFLaunch(fileSpec); + PDFLaunch launch = new PDFLaunch(fileSpec, newWindow); PDFLaunch oldLaunch = getDocument().findLaunch(launch); if (oldLaunch == null) { diff --git a/fop-core/src/main/java/org/apache/fop/pdf/PDFFileSpec.java b/fop-core/src/main/java/org/apache/fop/pdf/PDFFileSpec.java index b9a46c8b8..f9f990c95 100644 --- a/fop-core/src/main/java/org/apache/fop/pdf/PDFFileSpec.java +++ b/fop-core/src/main/java/org/apache/fop/pdf/PDFFileSpec.java @@ -35,6 +35,7 @@ public class PDFFileSpec extends PDFDictionary { super(); put("Type", new PDFName("Filespec")); put("F", filename); + put("UF", filename); // for non-ascii filenames, since PDF 1.7, 3.10.2 } private String getFilename() { @@ -77,4 +78,3 @@ public class PDFFileSpec extends PDFDictionary { return true; } } - diff --git a/fop-core/src/main/java/org/apache/fop/pdf/PDFLaunch.java b/fop-core/src/main/java/org/apache/fop/pdf/PDFLaunch.java index 7d80ddb43..935eb11a9 100644 --- a/fop-core/src/main/java/org/apache/fop/pdf/PDFLaunch.java +++ b/fop-core/src/main/java/org/apache/fop/pdf/PDFLaunch.java @@ -25,6 +25,7 @@ package org.apache.fop.pdf; public class PDFLaunch extends PDFAction { private PDFReference externalFileSpec; + private boolean newWindow; /** * Creates a new /Launch action. @@ -32,6 +33,18 @@ public class PDFLaunch extends PDFAction { */ public PDFLaunch(PDFFileSpec fileSpec) { this(fileSpec.makeReference()); + this.newWindow = false; + } + + /** + * Creates a new /Launch action. + * @param fileSpec the file specification to launch + * @param newWindow boolean indicating whether the target should be + * displayed in a new window + */ + public PDFLaunch(PDFFileSpec fileSpec, boolean newWindow) { + this(fileSpec.makeReference()); + this.newWindow = newWindow; } /** @@ -56,6 +69,9 @@ public class PDFLaunch extends PDFAction { StringBuffer sb = new StringBuffer(64); sb.append("<<\n/S /Launch\n/F "); sb.append(externalFileSpec.toString()); + if (newWindow) { + sb.append("\n/NewWindow true"); + } sb.append("\n>>"); return sb.toString(); diff --git a/fop-core/src/test/java/org/apache/fop/pdf/PDFLinkTestCase.java b/fop-core/src/test/java/org/apache/fop/pdf/PDFLinkTestCase.java new file mode 100644 index 000000000..34c81967f --- /dev/null +++ b/fop-core/src/test/java/org/apache/fop/pdf/PDFLinkTestCase.java @@ -0,0 +1,417 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.pdf; + +import java.awt.Dimension; +import java.awt.Rectangle; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.util.Arrays; +import java.util.Collection; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.util.regex.Pattern.quote; + +import javax.xml.transform.stream.StreamResult; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.apps.FopFactory; +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.render.intermediate.IFContext; +import org.apache.fop.render.intermediate.IFException; +import org.apache.fop.render.intermediate.extensions.Link; +import org.apache.fop.render.intermediate.extensions.URIAction; +import org.apache.fop.render.pdf.PDFDocumentHandler; + +@RunWith(Parameterized.class) +public class PDFLinkTestCase { + private String target; + private String expected; + + public PDFLinkTestCase(String target, String expected) { + this.target = target; + this.expected = expected; + } + + @Parameters + public static Collection links() { + return Arrays.asList(new Object[][] { + // Windows absolute paths + {"c:\\foobar.txt", quote("<< /Type /Filespec /F (c:/foobar.txt)")}, //0 + {"c:\\foo bar.txt", quote("<< /Type /Filespec /F (c:/foo bar.txt)")}, + {"c:\\foo\\bar.txt", quote("<< /Type /Filespec /F (c:/foo/bar.txt)")}, + {"c:\\foo\\bar 2.txt", quote("<< /Type /Filespec /F (c:/foo/bar 2.txt)")}, + + // Windows absolute paths using "/" + {"c:/foo bar.txt", quote("<< /Type /Filespec /F (c:/foo bar.txt)")}, //4 + {"c:/foo/bar.txt", quote("<< /Type /Filespec /F (c:/foo/bar.txt)")}, + {"c:/foo/bar 2.txt", quote("<< /Type /Filespec /F (c:/foo/bar 2.txt)")}, + + // Linux absolute paths + {"/foobar.txt", quote("<< /Type /Filespec /F (/foobar.txt)")}, //7 + {"/foo/bar.txt", quote("<< /Type /Filespec /F (/foo/bar.txt)")}, + {"/foo/bar 2.txt", quote("<< /Type /Filespec /F (/foo/bar 2.txt)")}, + {"/foo bar.txt", quote("<< /Type /Filespec /F (/foo bar.txt)")}, + + // Relative paths + {"foobar.txt", quote("<< /URI (foobar.txt)")}, //11 + {"foo bar.txt", quote("<< /URI (foo%20bar.txt)")}, + {"./foobar.txt", quote("<< /URI (./foobar.txt)")}, + {"./foo bar.txt", quote("<< /URI (./foo%20bar.txt)")}, + {"../foobar.txt", quote("<< /URI (../foobar.txt)")}, + {"../foo bar.txt", quote("<< /URI (../foo%20bar.txt)")}, + + // Windows network paths + {"\\\\foo\\bar.txt", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\bar.txt)")}, //17 + {"\\\\foo\\bar 2.txt", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\bar 2.txt)")}, + {"\\\\foo\\a\\bar.txt", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\a\\\\bar.txt)")}, + {"\\\\foo\\a\\bar 2.txt", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\a\\\\bar 2.txt)")}, + + // Windows network path using "/" + {"//foo/a/bar.txt", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\a\\\\bar.txt)")}, // 21 + {"//foo/a/bar 2.txt", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\a\\\\bar 2.txt)")}, + + // Non ASCII + // foo bar.txt (unicode) + {"\uFF46\uFF4F\uFF4F\u3000\uFF42\uFF41\uFF52.txt", quote( + "<< /URI (%EF%BD%86%EF%BD%8F%EF%BD%8F%E3%80%80%EF%BD%82%EF%BD%81%EF%BD%92.txt)")}, //23 + // c:/foo/foo bar.txt (unicode) + {"c:/foo/\uFF46\uFF4F\uFF4F\u3000\uFF42\uFF41\uFF52.txt", quote( + "<< /Type /Filespec /F " + + " " + + "/UF ")}, + // \\foo\bar 2\foo bar.txt (unicode) + {"\\\\foo\\bar 2\\\uFF46\uFF4F\uFF4F\u3000\uFF42\uFF41\uFF52.txt", quote("<< /Type /Filespec /F " + + " " + + "/UF " + + "")}, + + // PDF, Windows absolute paths + {"c:\\foobar.pdf", quote("<< /Type /Filespec /F (c:/foobar.pdf)")}, //26 + {"c:\\foo bar.pdf", quote("<< /Type /Filespec /F (c:/foo bar.pdf)")}, + {"c:\\foo\\bar.pdf", quote("<< /Type /Filespec /F (c:/foo/bar.pdf)")}, + {"c:\\foo\\bar 2.pdf", quote("<< /Type /Filespec /F (c:/foo/bar 2.pdf)")}, + + // PDF, Linux absolute paths + {"/foobar.pdf", quote("<< /Type /Filespec /F (/foobar.pdf)")}, //30 + {"/foo bar.pdf", quote("<< /Type /Filespec /F (/foo bar.pdf)")}, + {"/foo/bar.pdf", quote("<< /Type /Filespec /F (/foo/bar.pdf)")}, + {"/foo/bar 2.pdf", quote("<< /Type /Filespec /F (/foo/bar 2.pdf)")}, + + // PDF, Relative paths + {"foobar.pdf", quote("<< /URI (foobar.pdf)")}, //34 + {"foo bar.pdf", quote("<< /URI (foo%20bar.pdf)")}, + {"./foobar.pdf", quote("<< /URI (./foobar.pdf)")}, + {"./foo bar.pdf", quote("<< /URI (./foo%20bar.pdf)")}, + {"../foobar.pdf", quote("<< /URI (../foobar.pdf)")}, + {"../foo bar.pdf", quote("<< /URI (../foo%20bar.pdf)")}, + + // PDF, Windows network paths + {"\\\\foo\\bar.pdf", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\bar.pdf)")}, //40 + {"\\\\foo\\bar 2.pdf", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\bar 2.pdf)")}, + {"\\\\foo\\a\\bar.pdf", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\a\\\\bar.pdf)")}, + {"\\\\foo\\a\\bar 2.pdf", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\a\\\\bar 2.pdf)")}, + + // PDF with fragments, Windows absolute paths + {"c:\\foobar.pdf#page=2", quote("<< /Type /Filespec /F (c:/foobar.pdf)") + ".*" + quote("/S /GoToR") + ".*" + + quote("/D [ 2 /XYZ null null null ]")}, //44 + {"c:\\foo bar.pdf#dest=aa", quote("<< /Type /Filespec /F (c:/foo bar.pdf)") + ".*" + quote("/S /GoToR") + + ".*" + quote("/D (aa)")}, + {"c:\\foo\\bar.pdf#page=2", quote("<< /Type /Filespec /F (c:/foo/bar.pdf)") + ".*" + quote("/S /GoToR") + + ".*" + quote("/D [ 2 /XYZ null null null ]")}, + {"c:\\foo\\bar 2.pdf#dest=aa", quote("<< /Type /Filespec /F (c:/foo/bar 2.pdf)") + ".*" + quote("/S /GoToR") + + ".*" + quote("/D (aa)")}, + + // PDF with fragments, Windows absolute paths using "/" + {"c:/foo bar.pdf#page=2", quote("<< /Type /Filespec /F (c:/foo bar.pdf)") + ".*" + quote("/S /GoToR") + + ".*" + quote("/D [ 2 /XYZ null null null ]")}, //48 + {"c:/foo/bar.pdf#dest=aa", quote("<< /Type /Filespec /F (c:/foo/bar.pdf)") + ".*" + quote("/S /GoToR") + + ".*" + quote("/D (aa)")}, + {"c:/foo/bar 2.pdf#page=2", quote("<< /Type /Filespec /F (c:/foo/bar 2.pdf)") + ".*" + quote("/S /GoToR") + + ".*" + quote("/D [ 2 /XYZ null null null ]")}, + + // PDF with fragments, Linux absolute paths + {"/foobar.pdf#dest=aa", quote("<< /Type /Filespec /F (/foobar.pdf)") + ".*" + quote("/S /GoToR") + ".*" + + quote("/D (aa)")}, //51 + {"/foo/bar.pdf#page=2", quote("<< /Type /Filespec /F (/foo/bar.pdf)") + ".*" + quote("/S /GoToR") + ".*" + + quote("/D [ 2 /XYZ null null null ]")}, + {"/foo/bar 2.pdf#dest=aa", quote("<< /Type /Filespec /F (/foo/bar 2.pdf)") + ".*" + quote("/S /GoToR") + + ".*" + quote("/D (aa)")}, + {"/foo bar.pdf#page=2", quote("<< /Type /Filespec /F (/foo bar.pdf)") + ".*" + quote("/S /GoToR") + ".*" + + quote("/D [ 2 /XYZ null null null ]")}, + + // PDF with fragments, Relative paths + {"foobar.pdf#dest=aa", quote("<< /URI (foobar.pdf#dest=aa)")}, //55 + {"foo bar.pdf#page=2", quote("<< /URI (foo%20bar.pdf#page=2)")}, + {"./foobar.pdf#dest=aa", quote("<< /URI (./foobar.pdf#dest=aa)")}, + {"./foo bar.pdf#page=2", quote("<< /URI (./foo%20bar.pdf#page=2)")}, + {"../foobar.pdf#dest=aa", quote("<< /URI (../foobar.pdf#dest=aa)")}, + {"../foo bar.pdf#page=2", quote("<< /URI (../foo%20bar.pdf#page=2)")}, + + // PDF with fragments, Windows network paths + {"\\\\foo\\bar.pdf#dest=aa", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\bar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D (aa)")}, //61 + {"\\\\foo\\bar 2.pdf#page=2", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\bar 2.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D [ 2 /XYZ null null null ]")}, + {"\\\\foo\\a\\bar.pdf#dest=aa", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\a\\\\bar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D (aa)")}, + {"\\\\foo\\a\\bar 2.pdf#page=2", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\a\\\\bar 2.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D [ 2 /XYZ null null null ]")}, + + // file:// prefix, Windows absolute paths + {"file://c:\\foobar.txt", quote("<< /Type /Filespec /F (c:/foobar.txt)")}, //65 + {"file://c:\\foo bar.txt", quote("<< /Type /Filespec /F (c:/foo bar.txt)")}, + {"file://c:\\foo\\bar.txt", quote("<< /Type /Filespec /F (c:/foo/bar.txt)")}, + {"file://c:\\foo\\bar 2.txt", quote("<< /Type /Filespec /F (c:/foo/bar 2.txt)")}, + + // file:// prefix, Windows absolute paths using "/" + {"file://c:/foo bar.txt", quote("<< /Type /Filespec /F (c:/foo bar.txt)")}, //69 + {"file://c:/foo/bar.txt", quote("<< /Type /Filespec /F (c:/foo/bar.txt)")}, + {"file://c:/foo/bar 2.txt", quote("<< /Type /Filespec /F (c:/foo/bar 2.txt)")}, + + // file:// prefix, Linux absolute paths + {"file:///foobar.txt", quote("<< /Type /Filespec /F (/foobar.txt)")}, //72 + {"file:///foo/bar.txt", quote("<< /Type /Filespec /F (/foo/bar.txt)")}, + {"file:///foo/bar 2.txt", quote("<< /Type /Filespec /F (/foo/bar 2.txt)")}, + {"file:///foo bar.txt", quote("<< /Type /Filespec /F (/foo bar.txt)")}, + + // file:// prefix, Relative paths + {"file://foobar.txt", quote("<< /Type /Filespec /F (foobar.txt)")}, //76 + {"file://foo bar.txt", quote("<< /Type /Filespec /F (foo bar.txt)")}, + {"file://./foobar.txt", quote("<< /Type /Filespec /F (./foobar.txt)")}, + {"file://./foo bar.txt", quote("<< /Type /Filespec /F (./foo bar.txt)")}, + {"file://../foobar.txt", quote("<< /Type /Filespec /F (../foobar.txt)")}, + {"file://../foo bar.txt", quote("<< /Type /Filespec /F (../foo bar.txt)")}, + + // file:// prefix, Windows network paths + {"file://\\\\foo\\bar.txt", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\bar.txt)")}, //82 + {"file://\\\\foo\\bar 2.txt", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\bar 2.txt)")}, + {"file://\\\\foo\\a\\bar.txt", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\a\\\\bar.txt)")}, + {"file://\\\\foo\\a\\bar 2.txt", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\a\\\\bar 2.txt)")}, + + // file:// prefix, Windows network path using "/" + {"file:////foo/a/bar.txt", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\a\\\\bar.txt)")}, // 86 + {"file:////foo/a/bar 2.txt", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\a\\\\bar 2.txt)")}, + {"file:////foobar.txt", quote("<< /Type /Filespec /F (\\\\\\\\foobar.txt)")}, + + // Proper file:// for windows paths + {"file:///c:/foo%20bar.txt", quote("<< /Type /Filespec /F (c:/foo bar.txt)")}, //89 + {"file:///c:/foo/bar.txt", quote("<< /Type /Filespec /F (c:/foo/bar.txt)")}, + {"file:///c:/foo/bar%202.txt", quote("<< /Type /Filespec /F (c:/foo/bar 2.txt)")}, + + // Proper file:// for linux paths + {"file:///foo/bar%202.txt", quote("<< /Type /Filespec /F (/foo/bar 2.txt)")}, //92 + + // file:// PDF, Windows absolute paths + {"file://c:\\foobar.pdf", quote("<< /Type /Filespec /F (c:/foobar.pdf)")}, //93 + {"file://c:\\foo bar.pdf", quote("<< /Type /Filespec /F (c:/foo bar.pdf)")}, + {"file://c:\\foo\\bar.pdf", quote("<< /Type /Filespec /F (c:/foo/bar.pdf)")}, + {"file://c:\\foo\\bar 2.pdf", quote("<< /Type /Filespec /F (c:/foo/bar 2.pdf)")}, + + // file:// PDF, Linux absolute paths + {"file:///foobar.pdf", quote("<< /Type /Filespec /F (/foobar.pdf)")}, //97 + {"file:///foo bar.pdf", quote("<< /Type /Filespec /F (/foo bar.pdf)")}, + {"file:///foo/bar.pdf", quote("<< /Type /Filespec /F (/foo/bar.pdf)")}, + {"file:///foo/bar 2.pdf", quote("<< /Type /Filespec /F (/foo/bar 2.pdf)")}, + + // file:// PDF, Relative paths + {"file://foobar.pdf", quote("<< /Type /Filespec /F (foobar.pdf)")}, //101 + {"file://foo bar.pdf", quote("<< /Type /Filespec /F (foo bar.pdf)")}, + {"file://./foobar.pdf", quote("<< /Type /Filespec /F (./foobar.pdf)")}, + {"file://./foo bar.pdf", quote("<< /Type /Filespec /F (./foo bar.pdf)")}, + {"file://../foobar.pdf", quote("<< /Type /Filespec /F (../foobar.pdf)")}, + {"file://../foo bar.pdf", quote("<< /Type /Filespec /F (../foo bar.pdf)")}, + + // file:// PDF, Windows network paths + {"file://\\\\foo\\bar.pdf", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\bar.pdf)")}, //107 + {"file://\\\\foo\\bar 2.pdf", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\bar 2.pdf)")}, + {"file://\\\\foo\\a\\bar.pdf", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\a\\\\bar.pdf)")}, + {"file://\\\\foo\\a\\bar 2.pdf", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\a\\\\bar 2.pdf)")}, + + // Proper file:// for windows paths + {"file:///c:/foo%20bar.pdf", quote("<< /Type /Filespec /F (c:/foo bar.pdf)")}, //111 + {"file:///c:/foo/bar.pdf", quote("<< /Type /Filespec /F (c:/foo/bar.pdf)")}, + {"file:///c:/foo/bar%202.pdf", quote("<< /Type /Filespec /F (c:/foo/bar 2.pdf)")}, + + // Proper file:// PDF, for linux paths + {"file:///foo/bar%202.pdf", quote("<< /Type /Filespec /F (/foo/bar 2.pdf)")}, //114 + + // file:// PDF with fragments, Windows absolute paths + {"file://c:\\foobar.pdf#page=2", quote("<< /Type /Filespec /F (c:/foobar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D [ 2 /XYZ null null null ]")}, //115 + {"file://c:\\foo bar.pdf#dest=aa", quote("<< /Type /Filespec /F (c:/foo bar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D (aa)")}, + {"file://c:\\foo\\bar.pdf#page=2", quote("<< /Type /Filespec /F (c:/foo/bar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D [ 2 /XYZ null null null ]")}, + {"file://c:\\foo\\bar 2.pdf#dest=aa", quote("<< /Type /Filespec /F (c:/foo/bar 2.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D (aa)")}, + + // file:// PDF with fragments, Windows absolute paths using "/" + {"file://c:/foo bar.pdf#page=2", quote("<< /Type /Filespec /F (c:/foo bar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D [ 2 /XYZ null null null ]")}, //119 + {"file://c:/foo/bar.pdf#dest=aa", quote("<< /Type /Filespec /F (c:/foo/bar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D (aa)")}, + {"file://c:/foo/bar 2.pdf#page=2", quote("<< /Type /Filespec /F (c:/foo/bar 2.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D [ 2 /XYZ null null null ]")}, + + // file:// PDF with fragments, Linux absolute paths + {"file:///foobar.pdf#dest=aa", quote("<< /Type /Filespec /F (/foobar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D (aa)")}, //122 + {"file:///foo/bar.pdf#page=2", quote("<< /Type /Filespec /F (/foo/bar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D [ 2 /XYZ null null null ]")}, + {"file:///foo/bar 2.pdf#dest=aa", quote("<< /Type /Filespec /F (/foo/bar 2.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D (aa)")}, + {"file:///foo bar.pdf#page=2", quote("<< /Type /Filespec /F (/foo bar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D [ 2 /XYZ null null null ]")}, + + // file:// PDF with fragments, Relative paths + {"file://foobar.pdf#dest=aa", quote("<< /Type /Filespec /F (foobar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D (aa)")}, //126 + {"file://foo bar.pdf#page=2", quote("<< /Type /Filespec /F (foo bar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D [ 2 /XYZ null null null ]")}, + {"file://./foobar.pdf#dest=aa", quote("<< /Type /Filespec /F (./foobar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D (aa)")}, + {"file://./foo bar.pdf#page=2", quote("<< /Type /Filespec /F (./foo bar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D [ 2 /XYZ null null null ]")}, + {"file://../foobar.pdf#dest=aa", quote("<< /Type /Filespec /F (../foobar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D (aa)")}, + {"file://../foo bar.pdf#page=2", quote("<< /Type /Filespec /F (../foo bar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D [ 2 /XYZ null null null ]")}, + + // file:// PDF with fragments, Windows network paths + {"file://\\\\foo\\bar.pdf#dest=aa", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\bar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D (aa)")}, //132 + {"file://\\\\foo\\bar 2.pdf#page=2", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\bar 2.pdf)") + + ".*" + quote("/S /GoToR") + ".*" + quote("/D [ 2 /XYZ null null null ]")}, + {"file://\\\\foo\\a\\bar.pdf#dest=aa", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\a\\\\bar.pdf)") + + ".*" + quote("/S /GoToR") + ".*" + quote("/D (aa)")}, + {"file://\\\\foo\\a\\bar 2.pdf#page=2", + quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\a\\\\bar 2.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D [ 2 /XYZ null null null ]")}, + + // Proper file:// PDF with fragments, Windows network paths + {"file:////foo/bar.pdf#dest=aa", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\bar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D (aa)")}, //136 + {"file:////foo/bar%202.pdf#page=2", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\bar 2.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D [ 2 /XYZ null null null ]")}, + {"file:////foo/a/bar.pdf#dest=aa", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\a\\\\bar.pdf)") + + ".*" + quote("/S /GoToR") + ".*" + quote("/D (aa)")}, + {"file:////foo/a/bar%202.pdf#page=2", quote("<< /Type /Filespec /F (\\\\\\\\foo\\\\a\\\\bar 2.pdf)") + + ".*" + quote("/S /GoToR") + ".*" + quote("/D [ 2 /XYZ null null null ]")}, + + // Proper file:// PDF, for linux paths + {"file:///foo/bar%202.pdf#page=2", quote("<< /Type /Filespec /F (/foo/bar 2.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D [ 2 /XYZ null null null ]")}, //140 + + // file: Relative paths + {"file:foobar.txt", quote("<< /Type /Filespec /F (foobar.txt)")}, //141 + {"file:foo bar.txt", quote("<< /Type /Filespec /F (foo bar.txt)")}, + {"file:./foobar.txt", quote("<< /Type /Filespec /F (./foobar.txt)")}, + {"file:./foo bar.txt", quote("<< /Type /Filespec /F (./foo bar.txt)")}, + {"file:../foobar.txt", quote("<< /Type /Filespec /F (../foobar.txt)")}, + {"file:../foo bar.txt", quote("<< /Type /Filespec /F (../foo bar.txt)")}, + {"file:\uFF46\uFF4F\uFF4F\u3000\uFF42\uFF41\uFF52.txt", + quote("<< /Type /Filespec /F " + + "/UF ")}, + + // file: PDF Relative paths + {"file:foobar.pdf", quote("<< /Type /Filespec /F (foobar.pdf)")}, //148 + {"file:foo bar.pdf", quote("<< /Type /Filespec /F (foo bar.pdf)")}, + {"file:./foobar.pdf", quote("<< /Type /Filespec /F (./foobar.pdf)")}, + {"file:./foo bar.pdf", quote("<< /Type /Filespec /F (./foo bar.pdf)")}, + {"file:../foobar.pdf", quote("<< /Type /Filespec /F (../foobar.pdf)")}, + {"file:../foo bar.pdf", quote("<< /Type /Filespec /F (../foo bar.pdf)")}, + + // file: PDF with fragments, Relative paths + {"file:foobar.pdf#dest=aa", quote("<< /Type /Filespec /F (foobar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D (aa)")}, //154 + {"file:foo bar.pdf#page=2", quote("<< /Type /Filespec /F (foo bar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D [ 2 /XYZ null null null ]")}, + {"file:./foobar.pdf#dest=aa", quote("<< /Type /Filespec /F (./foobar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D (aa)")}, + {"file:./foo bar.pdf#page=2", quote("<< /Type /Filespec /F (./foo bar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D [ 2 /XYZ null null null ]")}, + {"file:../foobar.pdf#dest=aa", quote("<< /Type /Filespec /F (../foobar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D (aa)")}, + {"file:../foo bar.pdf#page=2", quote("<< /Type /Filespec /F (../foo bar.pdf)") + ".*" + + quote("/S /GoToR") + ".*" + quote("/D [ 2 /XYZ null null null ]")}, + + // file: prefix, Windows absolute paths + {"file:c:\\foobar.txt", quote("<< /Type /Filespec /F (c:/foobar.txt)")}, //160 + {"file:c:\\foo bar.txt", quote("<< /Type /Filespec /F (c:/foo bar.txt)")}, + {"file:c:\\foo\\bar.txt", quote("<< /Type /Filespec /F (c:/foo/bar.txt)")}, + {"file:c:\\foo\\bar 2.txt", quote("<< /Type /Filespec /F (c:/foo/bar 2.txt)")}, + + // PDF, Linux absolute paths + {"file:/foobar.pdf", quote("<< /Type /Filespec /F (/foobar.pdf)")}, //164 + {"file:/foo bar.pdf", quote("<< /Type /Filespec /F (/foo bar.pdf)")}, + {"file:/foo%20bar.pdf", quote("<< /Type /Filespec /F (/foo bar.pdf)")}, + {"file:/foo/bar.pdf", quote("<< /Type /Filespec /F (/foo/bar.pdf)")}, + {"file:/foo/bar 2.pdf", quote("<< /Type /Filespec /F (/foo/bar 2.pdf)")}, + {"file:/foo/bar%202.pdf", quote("<< /Type /Filespec /F (/foo/bar 2.pdf)")}, + + // Web links + {"https://xmlgraphics.apache.org/fop/", quote("<< /URI (https://xmlgraphics.apache.org/fop/)")}, //170 + {"http://xmlgraphics.apache.org/fop/", quote("<< /URI (http://xmlgraphics.apache.org/fop/)")}, + {"https://xmlgraphics.apache.org/fop/examples.html", + quote("<< /URI (https://xmlgraphics.apache.org/fop/examples.html)")}, + {"https://xmlgraphics.apache.org/fop/fo/fonts.fo.pdf", + quote("<< /URI (https://xmlgraphics.apache.org/fop/fo/fonts.fo.pdf)")}, + {"https://xmlgraphics.apache.org/fop/fo/fonts.fo.pdf#page=2", + quote("<< /URI (https://xmlgraphics.apache.org/fop/fo/fonts.fo.pdf#page=2)")}, + {"https://xmlgraphics.apache.org/fop/fo/fonts.fo", + quote("<< /URI (https://xmlgraphics.apache.org/fop/fo/fonts.fo)")}, + + // HTML files + {"examples.html#foo", quote("<< /URI (examples.html#foo)")}, //177 + {"examples.html?foo#bar", quote("/URI (examples.html?foo#bar)")}, + {"examples.html", quote("<< /URI (examples.html)")}, + {"file:examples.html", quote("<< /Type /Filespec /F (examples.html)")}, + }); + } + + @Test + public void testLinks() throws IFException { + FOUserAgent ua = FopFactory.newInstance(new File(".").toURI()).newFOUserAgent(); + PDFDocumentHandler docHandler = new PDFDocumentHandler(new IFContext(ua)); + docHandler.setFontInfo(new FontInfo()); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + docHandler.setResult(new StreamResult(out)); + docHandler.startDocument(); + docHandler.startPage(0, "", "", new Dimension()); + docHandler.getDocumentNavigationHandler().renderLink(new Link( + new URIAction(target, false), new Rectangle())); + docHandler.endDocument(); + + // Normalize spaces between word for easier testing + String outString = out.toString().replaceAll("\\s+", " "); + + Pattern r = Pattern.compile(expected); + Matcher m = r.matcher(outString); + Assert.assertTrue(m.find()); + } +}