diff --git a/source/distro/changelog.txt b/source/distro/changelog.txt index a65f3df..0eccba8 100644 --- a/source/distro/changelog.txt +++ b/source/distro/changelog.txt @@ -2,6 +2,13 @@ Changelog for Writer2LaTeX version 1.2 -> 1.4 ---------- version 1.4 beta ---------- +[w2x] Bugfix: Avoid null pointer exception in HTML5 export if embed_svg is true and an image is used more than once + +[all] An image that is used more than once in the source document is now only exported once. This was already the + case for the package format (since 1.3.2), but now also for flat XML. + +[w2x] The The option inline_svg has been renamed (again) to embed_svg, and the default is changed to false + [w2x] The experimental option zen_hack has been removed [w2x] For templates it is no longer required that the footer, header and panel are contained in a div element. diff --git a/source/distro/doc/user-manual.odt b/source/distro/doc/user-manual.odt index 10039d2..5a4c447 100644 Binary files a/source/distro/doc/user-manual.odt and b/source/distro/doc/user-manual.odt differ diff --git a/source/java/org/openoffice/da/comp/writer2xhtml/ConfigurationDialog.java b/source/java/org/openoffice/da/comp/writer2xhtml/ConfigurationDialog.java index 583cec8..e35a588 100644 --- a/source/java/org/openoffice/da/comp/writer2xhtml/ConfigurationDialog.java +++ b/source/java/org/openoffice/da/comp/writer2xhtml/ConfigurationDialog.java @@ -20,7 +20,7 @@ * * All Rights Reserved. * -* Version 1.4 (2014-09-19) +* Version 1.4 (2014-09-23) * */ @@ -495,14 +495,14 @@ public class ConfigurationDialog extends ConfigurationDialogBase implements XSer listBoxFromConfig(dlg, "Formulas", "formulas", sFormulaValues, (short) 0); textFieldFromConfig(dlg, "EndnotesHeading", "endnotes_heading"); textFieldFromConfig(dlg, "FootnotesHeading", "footnotes_heading"); - checkBoxFromConfig(dlg, "InlineSvg", "inline_svg"); + checkBoxFromConfig(dlg, "EmbedSvg", "embed_svg"); } @Override protected void getControls(DialogAccess dlg) { listBoxToConfig(dlg, "Formulas", "formulas", sFormulaValues); textFieldToConfig(dlg, "EndnotesHeading", "endnotes_heading"); textFieldToConfig(dlg, "FootnotesHeading", "footnotes_heading"); - checkBoxToConfig(dlg, "InlineSvg", "inline_svg"); + checkBoxToConfig(dlg, "EmbedSvg", "embed_svg"); } @Override protected boolean handleEvent(DialogAccess dlg, String sMethod) { diff --git a/source/java/writer2latex/base/ImageConverter.java b/source/java/writer2latex/base/ImageConverter.java index 7631c09..c5c1a4a 100644 --- a/source/java/writer2latex/base/ImageConverter.java +++ b/source/java/writer2latex/base/ImageConverter.java @@ -20,17 +20,22 @@ * * All Rights Reserved. * - * Version 1.4 (2014-09-16) + * Version 1.4 (2014-09-24) * */ package writer2latex.base; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.text.DecimalFormat; import java.text.NumberFormat; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import javax.xml.bind.DatatypeConverter; + import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -181,12 +186,6 @@ public final class ImageConverter { // The file name was not used nImageCount--; } - else if (node.hasAttribute(XMLString.XLINK_HREF)) { - // This is an embedded image we meet for the first time. - // Recycle it on behalf of the original image node. - String sHref = node.getAttribute(XMLString.XLINK_HREF); - recycledImages.put(sHref, new BinaryGraphicsDocument(bgd)); - } return bgd; } @@ -197,6 +196,7 @@ public final class ImageConverter { String sExt = null; String sMIME = null; byte[] blob = null; + String sId = null; // First try to extract the image using the xlink:href attribute if (node.hasAttribute(XMLString.XLINK_HREF)) { @@ -224,6 +224,8 @@ public final class ImageConverter { if (bDestructive) { object.dispose(); } + // We got an image, define ID for recycling + sId = sHref; } else { // This is a linked image @@ -249,11 +251,18 @@ public final class ImageConverter { } } blob = Base64.decode(buf.toString()); - sMIME = MIMETypes.getMagicMIMEType(blob); + // We may have seen this image before, return the recycled version + String sId1 = createId(blob); + if (recycledImages.containsKey(sId1)) { + return recycledImages.get(sId1); + } + sMIME = MIMETypes.getMagicMIMEType(blob); sExt = MIMETypes.getFileExtension(sMIME); if (bDestructive) { node.removeChild(obd); } + // We got an image, define ID for recycling + sId = sId1; } else { // There is no image data @@ -303,11 +312,13 @@ public final class ImageConverter { } // Create the result - if (isAcceptedFormat(sMIME) || bAcceptOtherFormats) { String sFileName = sName+sExt; BinaryGraphicsDocument bgd = new BinaryGraphicsDocument(sFileName,sMIME); bgd.setData(blob,isAcceptedFormat(sMIME)); + if (sId!=null) { + recycledImages.put(sId, new BinaryGraphicsDocument(bgd)); + } return bgd; } else { @@ -323,4 +334,17 @@ public final class ImageConverter { return null; } + // Create a fingerprint of a blob. The fingerprint concatenates the MD5 hash with the first 10 bytes of the blob. + private String createId(byte[] blob) { + MessageDigest md; + try { + md = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + // This would be surprising + return null; + } + return DatatypeConverter.printHexBinary(md.digest(blob)) + +DatatypeConverter.printHexBinary(Arrays.copyOf(blob, 10)); + } + } diff --git a/source/java/writer2latex/xhtml/DrawConverter.java b/source/java/writer2latex/xhtml/DrawConverter.java index c6b2a14..8773d0e 100644 --- a/source/java/writer2latex/xhtml/DrawConverter.java +++ b/source/java/writer2latex/xhtml/DrawConverter.java @@ -20,7 +20,7 @@ * * All Rights Reserved. * - * Version 1.4 (2014-09-05) + * Version 1.4 (2014-09-24) * */ @@ -38,9 +38,9 @@ * export notes (part of draw-page) * export list-style-image for image bullets! */ - package writer2latex.xhtml; +import java.util.HashMap; import java.util.Iterator; import java.util.Vector; @@ -54,8 +54,6 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Element; -//import writer2latex.xmerge.EmbeddedBinaryObject; - import writer2latex.util.Misc; import writer2latex.util.CSVList; import writer2latex.util.SimpleXMLParser; @@ -101,7 +99,10 @@ public class DrawConverter extends ConverterHelper { private int nImageSize; private String sImageSplit; private boolean bCoverImage; - private boolean bUseSVG; + private boolean bEmbedSVG; + + // Embedded SVG images for recycling are collected here + private HashMap svgImages = new HashMap(); // Frames in spreadsheet documents are collected here private Vector frames = new Vector(); @@ -128,7 +129,7 @@ public class DrawConverter extends ConverterHelper { nImageSize = config.imageSize(); sImageSplit = config.imageSplit(); bCoverImage = config.coverImage(); - bUseSVG = config.inlineSVG(); + bEmbedSVG = config.embedSVG(); } /////////////////////////////////////////////////////////////////////// @@ -486,26 +487,38 @@ public class DrawConverter extends ConverterHelper { // Create the image (sFileName contains the file name) Element imageElement = null; - if (converter.nType==XhtmlDocument.HTML5 && bUseSVG && bgd!=null && MIMETypes.SVG.equals(bgd.getMIMEType())) { + if (converter.nType==XhtmlDocument.HTML5 && bEmbedSVG && bgd!=null && MIMETypes.SVG.equals(bgd.getMIMEType())) { // In HTML5 we may embed SVG images directly in the document - byte[] blob = bgd.getData(); - try { - Document dom = SimpleXMLParser.parse(new ByteArrayInputStream(blob)); - if (dom!=null) { + if (bgd.isRecycled()) { + // Get from the cache (actually we are certain it is there) + if (svgImages.containsKey(bgd.getFileName())) { Element elm = hnodeInline!=null ? hnodeInline : hnodeBlock; - imageElement = (Element) elm.getOwnerDocument().importNode(dom.getDocumentElement(), true); + imageElement = (Element) elm.getOwnerDocument().importNode(svgImages.get(bgd.getFileName()), true); + } + } + else { + // Parse the data + byte[] blob = bgd.getData(); + try { + Document dom = SimpleXMLParser.parse(new ByteArrayInputStream(blob)); + if (dom!=null) { + Element elm = hnodeInline!=null ? hnodeInline : hnodeBlock; + imageElement = (Element) elm.getOwnerDocument().importNode(dom.getDocumentElement(), true); + // Store in cache in case we need to recycle + svgImages.put(bgd.getFileName(), imageElement); + } + else { + System.out.println("Failed to parse SVG"); + } + } catch (IOException e) { + // Will not happen with a byte array + System.out.println("IOException parsing SVG"); + e.printStackTrace(); + } catch (SAXException e) { + e.printStackTrace(); + System.out.println("SAXException parsing SVG"); } - else { - System.out.println("Failed to parse SVG"); - } - } catch (IOException e) { - // Will not happen with a byte array - System.out.println("IOException parsing SVG"); - e.printStackTrace(); - } catch (SAXException e) { - e.printStackTrace(); - System.out.println("SAXException parsing SVG"); - } + } } else { // In all other cases, create an img element diff --git a/source/java/writer2latex/xhtml/XhtmlConfig.java b/source/java/writer2latex/xhtml/XhtmlConfig.java index a6474d2..3ec86d5 100644 --- a/source/java/writer2latex/xhtml/XhtmlConfig.java +++ b/source/java/writer2latex/xhtml/XhtmlConfig.java @@ -143,7 +143,7 @@ public class XhtmlConfig extends writer2latex.base.ConfigBase { private static final int SPLIT_AFTER = 40; private static final int IMAGE_SPLIT = 41; private static final int COVER_IMAGE = 42; - private static final int INLINE_SVG = 43; + private static final int EMBED_SVG = 43; private static final int USE_MATHJAX = 44; private static final int CALC_SPLIT = 45; private static final int DISPLAY_HIDDEN_SHEETS = 46; @@ -273,7 +273,7 @@ public class XhtmlConfig extends writer2latex.base.ConfigBase { }; options[IMAGE_SPLIT] = new Option("image_split","none"); options[COVER_IMAGE] = new BooleanOption("cover_image","false"); - options[INLINE_SVG] = new BooleanOption("inline_svg","true"); + options[EMBED_SVG] = new BooleanOption("embed_svg","false"); options[USE_MATHJAX] = new BooleanOption("use_mathjax","false"); options[CALC_SPLIT] = new BooleanOption("calc_split","false"); options[DISPLAY_HIDDEN_SHEETS] = new BooleanOption("display_hidden_sheets", "false"); @@ -399,7 +399,7 @@ public class XhtmlConfig extends writer2latex.base.ConfigBase { public int splitAfter() { return ((IntegerOption) options[SPLIT_AFTER]).getValue(); } public String imageSplit() { return options[IMAGE_SPLIT].getString(); } public boolean coverImage() { return ((BooleanOption) options[COVER_IMAGE]).getValue(); } - public boolean inlineSVG() { return ((BooleanOption) options[INLINE_SVG]).getValue(); } + public boolean embedSVG() { return ((BooleanOption) options[EMBED_SVG]).getValue(); } public boolean useMathJax() { return ((BooleanOption) options[USE_MATHJAX]).getValue(); } public boolean xhtmlCalcSplit() { return ((BooleanOption) options[CALC_SPLIT]).getValue(); } public boolean xhtmlDisplayHiddenSheets() { return ((BooleanOption) options[DISPLAY_HIDDEN_SHEETS]).getValue(); } diff --git a/source/oxt/writer2xhtml/W2XDialogs2/Content.xdl b/source/oxt/writer2xhtml/W2XDialogs2/Content.xdl index 0920a82..aff7c72 100644 --- a/source/oxt/writer2xhtml/W2XDialogs2/Content.xdl +++ b/source/oxt/writer2xhtml/W2XDialogs2/Content.xdl @@ -18,6 +18,6 @@ - + \ No newline at end of file diff --git a/source/oxt/writer2xhtml/help/en/org.openoffice.da.writer2xhtml.oxt/Configuration/Content.xhp b/source/oxt/writer2xhtml/help/en/org.openoffice.da.writer2xhtml.oxt/Configuration/Content.xhp index 8d723af..3c5bbba 100644 --- a/source/oxt/writer2xhtml/help/en/org.openoffice.da.writer2xhtml.oxt/Configuration/Content.xhp +++ b/source/oxt/writer2xhtml/help/en/org.openoffice.da.writer2xhtml.oxt/Configuration/Content.xhp @@ -54,7 +54,7 @@ SVG images - + Embed SVG images in the HTML document If you check this option, SVG images will be embedded directly