Skip to content
Snippets Groups Projects
Commit c4a7e953 authored by Marius Dumitru Florea's avatar Marius Dumitru Florea
Browse files

XWIKI-20553: The whitespace between two highlighted code tokens is lost when exporting to PDF

* Apply workaround until Paged.js issue is fixed ( https://github.com/pagedjs/pagedjs/issues/45 )

(cherry picked from commit e995f4c4)
parent efd38f82
No related branches found
No related tags found
No related merge requests found
......@@ -36,7 +36,93 @@
<minorEdit>false</minorEdit>
<syntaxId>xwiki/2.1</syntaxId>
<hidden>false</hidden>
<content>{{code language="none"}}
// This is a very long comment that gets cut when exported to PDF because it exceeds the print page width and the code macro preserves spaces which means it has to be displayed on a single line.
{{/code}}</content>
<content>{{code language="java"}}
/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.export.pdf.internal.job;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.xwiki.rendering.util.IdGenerator;
/**
* The id generator used when rendering wiki pages for PDF export. It collects a map of {@code localId -&gt; globalId} for each rendered page (see {@link #resetLocalIds()}) that can be used on the client side to refactor external links into
* internal links. The local id is the id that is generated when rendering a single page, while the global id is the id generated when rendering multiple pages (and thus the generated id needs to be unique across all these pages).
*
* @version $Id$
* @since 14.10.6
* @since 15.1RC1
*/
public class PDFExportIdGenerator extends IdGenerator
{
private IdGenerator localIdGenerator = new IdGenerator();
/**
* Maps local IDs to global IDs.
*/
private Map&lt;String, String&gt; idMap = new HashMap&lt;&gt;();
@Override
public String generateUniqueId(String prefix, String text)
{
String globalId = super.generateUniqueId(prefix, text);
String localId = this.localIdGenerator.generateUniqueId(prefix, text);
this.idMap.put(localId, globalId);
return globalId;
}
@Override
public void remove(String globalId)
{
super.remove(globalId);
this.idMap.entrySet().stream().filter(entry -&gt; Objects.equals(entry.getValue(), globalId)).findFirst()
.ifPresent(entry -&gt; {
String localId = entry.getKey();
this.localIdGenerator.remove(localId);
this.idMap.remove(localId);
});
}
@Override
public void reset()
{
super.reset();
resetLocalIds();
}
/**
* Reset the collected local IDs. Call this before each page rendering.
*
* @return the mapping between the local IDs and the global IDs
*/
public Map&lt;String, String&gt; resetLocalIds()
{
Map&lt;String, String&gt; idMapCopy = new HashMap&lt;&gt;(this.idMap);
this.localIdGenerator.reset();
this.idMap.clear();
return idMapCopy;
}
}
{{/code}}
before {{code language="java"}}public static final String JOB_TYPE = "export/pdf";{{/code}} after</content>
</xwikidoc>
......@@ -31,7 +31,15 @@
* @since 14.4.2
* @since 14.5
*/
@UITest(extraJARs = {"org.xwiki.platform:xwiki-platform-resource-temporary"})
@UITest(
extraJARs = {
"org.xwiki.platform:xwiki-platform-resource-temporary",
// Code macro highlighting works only if Jython is a core extension. It's not enough to use language=none in our
// test because we want to reproduce a bug in Paged.js where white-space between highlighted tokens is lost.
"org.python:jython-slim"
},
resolveExtraJARs = true
)
@ExtendWith(PDFExportExecutionCondition.class)
class AllIT
{
......
......@@ -62,7 +62,15 @@
* @since 14.4.2
* @since 14.5
*/
@UITest(extraJARs = {"org.xwiki.platform:xwiki-platform-resource-temporary"})
@UITest(
extraJARs = {
"org.xwiki.platform:xwiki-platform-resource-temporary",
// Code macro highlighting works only if Jython is a core extension. It's not enough to use language=none in our
// test because we want to reproduce a bug in Paged.js where white-space between highlighted tokens is lost.
"org.python:jython-slim"
},
resolveExtraJARs = true
)
@ExtendWith(PDFExportExecutionCondition.class)
class PDFExportIT
{
......@@ -658,15 +666,27 @@ void codeMacro(TestUtils setup, TestConfiguration testConfiguration) throws Exce
PDFExportOptionsModal exportOptions = PDFExportOptionsModal.open(viewPage);
try (PDFDocument pdf = export(exportOptions, testConfiguration)) {
// We should have 2 pages: cover page and content page.
assertEquals(2, pdf.getNumberOfPages());
// We should have 3 pages: cover page and two content pages (the long code macro is split in two).
assertEquals(3, pdf.getNumberOfPages());
String content = pdf.getTextFromPage(1);
// A line break is inserted whenever a long line is wrapped, so we need to remove line breaks in order to
// verify that the entire code macro content is present.
assertTrue(content.replace("\n", "").contains(
"// This is a very long comment that gets cut when exported to PDF because it exceeds the print page "
+ "width and the code macro preserves spaces which means it has to be displayed on a single line."),
// verify that the entire line content is present.
assertTrue(
content.replace("\n", "")
.contains("The id generator used when rendering wiki pages for PDF export. It collects a map of "
+ "{@code localId -> globalId} for each rendered page (see {@link #resetLocalIds()})"
+ " that can be used on the client side to refactor external links into"),
"Unexpected content: " + content);
// Verify that white-space between highlighted tokens is preserved, even when the code macro is split
// between print pages.
assertTrue(content.contains("import java.util.Map;"), "Unexpected content: " + content);
content = pdf.getTextFromPage(2);
assertTrue(content.contains("public void reset()"), "Unexpected content: " + content);
// Verify that white-space is preserved also when the code macro is in-line.
assertTrue(content.contains("before public static final String JOB_TYPE = \"export/pdf\"; after"),
"Unexpected content: " + content);
}
}
......
......@@ -395,18 +395,19 @@
* This is a workaround for the Chrome / Paged.js bug that makes table rows hidden when they are split between print
* pages. See https://github.com/pagedjs/pagedjs/issues/114 . See also https://jira.xwiki.org/browse/XWIKI-20741 .
* TODO: Remove this hack once we upgrade to Paged.js 0.5.0 (as it seems the beta version is fixing the problem).
*
* See also https://pagedjs.org/documentation/10-handlers-hooks-and-custom-javascript/
*/
class ShowInvisibleRowsOnPageBreaks extends Paged.Handler {
constructor(chunker, polisher, caller) {
super(chunker, polisher, caller);
}
afterPageLayout(pageFragment) {
afterPageLayout(pageFragment, page) {
// We don't need the column display once the print page layout is done, but we need to make sure the page content
// doesn't overflow the page footer.
const pageContentWrapper = pageFragment.querySelector('.pagedjs_page_content');
pageContentWrapper.style.columnWidth = 'unset';
pageContentWrapper.style.overflow = 'hidden';
page.area.style.columnWidth = 'unset';
page.area.style.overflow = 'hidden';
}
}
......@@ -663,6 +664,17 @@
});
};
const preserveWhiteSpaceInCodeBlocks = () =&gt; {
// Paged.js removes / skips the white-space-only text nodes that are direct children of a block element (a DIV in
// our case) even if that element asks for white-space to be preserved (through CSS). The workaround we apply here
// is to wrapp all these white-space-only text nodes with a SPAN (so their parent is not a block element anymore).
// See XWIKI-20553: The whitespace between two highlighted code tokens is lost when exporting to PDF
// See also https://github.com/pagedjs/pagedjs/issues/45
$('#xwikicontent div.box &gt; div.code').contents().filter(function() {
return this.nodeType === 3;
}).wrap("&lt;span&gt;&lt;/span&gt;");
};
// Adjust the exported content before performing the print layout.
pageReady.afterPageReady(() =&gt; {
refactorAnchors();
......@@ -670,6 +682,7 @@
validateTableOfContentsAnchors();
storeFormFieldValues();
makeTableCellWidthRelative();
preserveWhiteSpaceInCodeBlocks();
});
// Trigger the print preview after the page is ready. Note that by returning a promise we're making the next page
......@@ -1018,6 +1031,19 @@
complete solution but it should cover most of the large tables. */
.hyphenate();
}
// Make sure the content of the code macro is not cut when it exceeds the print page width by:
div.code {
// decreasing the font size when printing
font-size: 80%;
}
div.code, div.code * {
// and wrapping long lines, while still preserving the whitespace.
white-space: pre-wrap;
}
div.box &gt; div.code &gt; pre {
margin: 0;
}
}</code>
</property>
<property>
......
......@@ -313,16 +313,6 @@
break-before: page;
}
}
// Make sure the content of the code macro is not cut when it exceeds the print page width by:
div.code {
// decreasing the font size when printing
font-size: 80%;
}
div.code, div.code * {
// and wrapping long lines, while still preserving the whitespace.
white-space: pre-wrap;
}
}</code>
</property>
<property>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment