From 6f8ace54a845f4ddba9fb2c3f46405f04bd2b407 Mon Sep 17 00:00:00 2001 From: Simon Steiner Date: Wed, 4 Oct 2017 11:25:37 +0000 Subject: FOP-2737: OTF subsetting: Resize privateDict to fit op size git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1811062 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/fop/fonts/truetype/OTFSubSetFile.java | 99 +++++++++++++--------- .../fop/fonts/truetype/OTFSubSetFileTestCase.java | 74 ++++++++++++---- 2 files changed, 114 insertions(+), 59 deletions(-) diff --git a/fop-core/src/main/java/org/apache/fop/fonts/truetype/OTFSubSetFile.java b/fop-core/src/main/java/org/apache/fop/fonts/truetype/OTFSubSetFile.java index be197c1ec..d8e8c2843 100644 --- a/fop-core/src/main/java/org/apache/fop/fonts/truetype/OTFSubSetFile.java +++ b/fop-core/src/main/java/org/apache/fop/fonts/truetype/OTFSubSetFile.java @@ -30,7 +30,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -69,7 +68,7 @@ public class OTFSubSetFile extends OTFSubSetWriter { /** For fonts which have an FDSelect or ROS flag in Top Dict, this is used to store the * local subroutine indexes for each group as opposed to the above subsetLocalIndexSubr */ - private List> fdSubrs; + protected List> fdSubrs; /** The subset FD Select table used to store the mappings between glyphs and their * associated FDFont object which point to a private dict and local subroutines. */ @@ -264,7 +263,7 @@ public class OTFSubSetFile extends OTFSubSetWriter { protected List storeFDStrings(List uniqueNewRefs) throws IOException { List fontNameSIDs = new ArrayList(); List fdFonts = cffReader.getFDFonts(); - for (Integer uniqueNewRef : uniqueNewRefs) { + for (int uniqueNewRef : uniqueNewRefs) { FontDict fdFont = fdFonts.get(uniqueNewRef); byte[] fdFontByteData = fdFont.getByteData(); Map fdFontDict = cffReader.parseDictData(fdFontByteData); @@ -354,29 +353,29 @@ public class OTFSubSetFile extends OTFSubSetWriter { for (Entry subsetGlyph : subsetGlyphs.entrySet()) { int gid = subsetGlyph.getKey(); + int v = subsetGlyph.getValue(); int sid = cffReader.getSIDFromGID(charsetOffset, gid); //Check whether the SID falls into the standard string set if (sid < NUM_STANDARD_STRINGS) { - gidToSID.put(subsetGlyph.getValue(), sid); + gidToSID.put(v, sid); if (mbFont != null) { - mbFont.mapUsedGlyphName(subsetGlyph.getValue(), - CFFStandardString.getName(sid)); + mbFont.mapUsedGlyphName(v, CFFStandardString.getName(sid)); } } else { int index = sid - NUM_STANDARD_STRINGS; //index is 0 based, should use < not <= if (index < cffReader.getStringIndex().getNumObjects()) { + byte[] value = cffReader.getStringIndex().getValue(index); if (mbFont != null) { - mbFont.mapUsedGlyphName(subsetGlyph.getValue(), - new String(cffReader.getStringIndex().getValue(index), "UTF-8")); + mbFont.mapUsedGlyphName(v, new String(value, "UTF-8")); } - gidToSID.put(subsetGlyph.getValue(), stringIndexData.size() + 391); - stringIndexData.add(cffReader.getStringIndex().getValue(index)); + gidToSID.put(v, stringIndexData.size() + 391); + stringIndexData.add(value); } else { if (mbFont != null) { - mbFont.mapUsedGlyphName(subsetGlyph.getValue(), ".notdef"); + mbFont.mapUsedGlyphName(v, ".notdef"); } - gidToSID.put(subsetGlyph.getValue(), index); + gidToSID.put(v, index); } } } @@ -395,22 +394,25 @@ public class OTFSubSetFile extends OTFSubSetWriter { Map subsetGroups = new HashMap(); List uniqueGroups = new ArrayList(); + Map rangeMap = fdSelect.getRanges(); + Integer[] ranges = rangeMap.keySet().toArray(new Integer[rangeMap.size()]); for (int gid : subsetGlyphs.keySet()) { - Set rangeKeys = fdSelect.getRanges().keySet(); - Integer[] ranges = rangeKeys.toArray(new Integer[rangeKeys.size()]); - for (int i = 0; i < ranges.length; i++) { - int nextRange = -1; + int i = 0; + for (Entry entry : rangeMap.entrySet()) { + int nextRange; if (i < ranges.length - 1) { nextRange = ranges[i + 1]; } else { nextRange = fdSelect.getSentinelGID(); } - if (gid >= ranges[i] && gid < nextRange) { - subsetGroups.put(gid, fdSelect.getRanges().get(ranges[i])); - if (!uniqueGroups.contains(fdSelect.getRanges().get(ranges[i]))) { - uniqueGroups.add(fdSelect.getRanges().get(ranges[i])); + if (gid >= entry.getKey() && gid < nextRange) { + int r = entry.getValue(); + subsetGroups.put(gid, r); + if (!uniqueGroups.contains(r)) { + uniqueGroups.add(r); } } + i++; } } @@ -425,7 +427,7 @@ public class OTFSubSetFile extends OTFSubSetWriter { subsetFDSelect = new LinkedHashMap(); List> foundLocalUniques = new ArrayList>(); - for (Integer uniqueGroup1 : uniqueGroups) { + for (int u : uniqueGroups) { foundLocalUniques.add(new ArrayList()); } Map gidHintMaskLengths = new HashMap(); @@ -455,7 +457,7 @@ public class OTFSubSetFile extends OTFSubSetWriter { fdSubrs.add(new ArrayList()); } List> foundLocalUniquesB = new ArrayList>(); - for (Integer uniqueGroup : uniqueGroups) { + for (int u : uniqueGroups) { foundLocalUniquesB.add(new ArrayList()); } for (Entry subsetGlyph : subsetGlyphs.entrySet()) { @@ -463,11 +465,11 @@ public class OTFSubSetFile extends OTFSubSetWriter { int value = subsetGlyph.getValue(); int group = subsetGroups.get(gid); localIndexSubr = cffReader.getFDFonts().get(group).getLocalSubrData(); - localUniques = foundLocalUniquesB.get(subsetFDSelect.get(value).getNewFDIndex()); + int newFDIndex = subsetFDSelect.get(value).getNewFDIndex(); + localUniques = foundLocalUniquesB.get(newFDIndex); byte[] data = charStringsIndex.getValue(gid); - subsetLocalIndexSubr = fdSubrs.get(subsetFDSelect.get(value).getNewFDIndex()); - subsetLocalSubrCount = foundLocalUniques.get(subsetFDSelect.get(value) - .getNewFDIndex()).size(); + subsetLocalIndexSubr = fdSubrs.get(newFDIndex); + subsetLocalSubrCount = foundLocalUniques.get(newFDIndex).size(); type2Parser = new Type2Parser(); type2Parser.setMaskLength(gidHintMaskLengths.get(gid)); data = readCharStringData(data, subsetLocalSubrCount); @@ -528,23 +530,37 @@ public class OTFSubSetFile extends OTFSubSetWriter { throws IOException { List privateDictOffsets = new ArrayList(); List fdFonts = cffReader.getFDFonts(); - for (int i = 0; i < uniqueNewRefs.size(); i++) { - FontDict curFDFont = fdFonts.get(uniqueNewRefs.get(i)); + int i = 0; + for (int ref : uniqueNewRefs) { + FontDict curFDFont = fdFonts.get(ref); byte[] fdPrivateDictByteData = curFDFont.getPrivateDictData(); Map fdPrivateDict = cffReader.parseDictData(fdPrivateDictByteData); int privateDictOffset = currentPos; privateDictOffsets.add(privateDictOffset); - if (fdPrivateDict.get("Subrs") != null) { - updateOffset(fdPrivateDictByteData, fdPrivateDict.get("Subrs").getOffset(), - fdPrivateDict.get("Subrs").getOperandLength(), + DICTEntry subrs = fdPrivateDict.get("Subrs"); + if (subrs != null) { + fdPrivateDictByteData = resizeToFitOpLen(fdPrivateDictByteData, subrs); + updateOffset(fdPrivateDictByteData, subrs.getOffset(), + subrs.getOperandLength(), fdPrivateDictByteData.length); } writeBytes(fdPrivateDictByteData); writeIndex(fdSubrs.get(i)); + i++; } return privateDictOffsets; } + private byte[] resizeToFitOpLen(byte[] fdPrivateDictByteData, DICTEntry subrs) throws IOException { + if (subrs.getOperandLength() == 2 && fdPrivateDictByteData.length < 108) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + bos.write(fdPrivateDictByteData); + bos.write(new byte[108 - fdPrivateDictByteData.length]); + fdPrivateDictByteData = bos.toByteArray(); + } + return fdPrivateDictByteData; + } + protected int writeFDArray(List uniqueNewRefs, List privateDictOffsets, List fontNameSIDs) throws IOException { @@ -552,8 +568,9 @@ public class OTFSubSetFile extends OTFSubSetWriter { List fdFonts = cffReader.getFDFonts(); List index = new ArrayList(); - for (int i = 0; i < uniqueNewRefs.size(); i++) { - FontDict fdFont = fdFonts.get(uniqueNewRefs.get(i)); + int i = 0; + for (int ref : uniqueNewRefs) { + FontDict fdFont = fdFonts.get(ref); byte[] fdFontByteData = fdFont.getByteData(); Map fdFontDict = cffReader.parseDictData(fdFontByteData); //Update the SID to the FontName @@ -566,6 +583,7 @@ public class OTFSubSetFile extends OTFSubSetWriter { fdFontDict.get("Private").getOperandLengths().get(1), privateDictOffsets.get(i)); index.add(fdFontByteData); + i++; } writeIndex(index); return offset; @@ -817,7 +835,7 @@ public class OTFSubSetFile extends OTFSubSetWriter { if (newRef != -1) { byte[] newData = constructNewRefData(dataPos, data, operand, subsetGlobalSubrCount, newRef, new int[] {29}); - dataPos -= (data.length - newData.length); + dataPos -= data.length - newData.length; data = newData; } } else { @@ -933,8 +951,8 @@ public class OTFSubSetFile extends OTFSubSetWriter { protected int writeIndex(List dataArray) { int totLength = 1; - for (byte[] aDataArray1 : dataArray) { - totLength += aDataArray1.length; + for (byte[] data : dataArray) { + totLength += data.length; } int offSize = getOffSize(totLength); return writeIndex(dataArray, offSize); @@ -951,9 +969,10 @@ public class OTFSubSetFile extends OTFSubSetWriter { //Count the first offset 1 hdrTotal += offSize; int total = 0; - for (int i = 0; i < dataArray.size(); i++) { + int i = 0; + for (byte[] data : dataArray) { hdrTotal += offSize; - int length = dataArray.get(i).length; + int length = data.length; switch (offSize) { case 1: if (i == 0) { @@ -986,6 +1005,7 @@ public class OTFSubSetFile extends OTFSubSetWriter { default: throw new AssertionError("Offset Size was not an expected value."); } + i++; } for (byte[] aDataArray : dataArray) { writeBytes(aDataArray); @@ -1148,9 +1168,6 @@ public class OTFSubSetFile extends OTFSubSetWriter { } protected void updateOffset(byte[] out, int position, int length, int replacement) { - if (length == 2 && replacement < 108 && replacement > -108) { - length = 1; - } switch (length) { case 1: out[position] = (byte)(replacement + 139); diff --git a/fop-core/src/test/java/org/apache/fop/fonts/truetype/OTFSubSetFileTestCase.java b/fop-core/src/test/java/org/apache/fop/fonts/truetype/OTFSubSetFileTestCase.java index f95f6ffd9..186f99a54 100644 --- a/fop-core/src/test/java/org/apache/fop/fonts/truetype/OTFSubSetFileTestCase.java +++ b/fop-core/src/test/java/org/apache/fop/fonts/truetype/OTFSubSetFileTestCase.java @@ -43,6 +43,7 @@ import static org.mockito.Mockito.when; import org.apache.fontbox.cff.CFFFont; import org.apache.fontbox.cff.CFFParser; +import org.apache.fop.fonts.MultiByteFont; import org.apache.fop.fonts.cff.CFFDataReader; import org.apache.fop.fonts.cff.CFFDataReader.CFFIndexData; import org.apache.fop.fonts.cff.CFFDataReader.DICTEntry; @@ -500,7 +501,9 @@ public class OTFSubSetFileTestCase extends OTFFileTestCase { map.put("charset", dict); map.put("CharStrings", dict); when((cffReader.getTopDictEntries())).thenReturn(map); - when(cffReader.getFDSelect()).thenReturn(new CFFDataReader().new Format3FDSelect()); + CFFDataReader.Format3FDSelect fdSelect = new CFFDataReader().new Format3FDSelect(); + fdSelect.setRanges(new HashMap()); + when(cffReader.getFDSelect()).thenReturn(fdSelect); cffReader.getTopDictEntries().get("CharStrings").setOperandLength(opLen); super.createCFF(); } @@ -558,14 +561,7 @@ public class OTFSubSetFileTestCase extends OTFFileTestCase { return new byte[128]; } }; - cffReader = mock(CFFDataReader.class); - LinkedHashMap map = new LinkedHashMap(); - DICTEntry e = new DICTEntry(); - e.setOffset(1); - e.setOperandLengths(Arrays.asList(0, 0)); - map.put("FontName", e); - map.put("Private", e); - when(cffReader.parseDictData(any(byte[].class))).thenReturn(map); + cffReader = makeCFFDataReader(); when(cffReader.getFDFonts()).thenReturn(fdFonts); fdFonts.clear(); @@ -611,15 +607,7 @@ public class OTFSubSetFileTestCase extends OTFFileTestCase { } protected void createCFF() throws IOException { - cffReader = mock(CFFDataReader.class); - when(cffReader.getHeader()).thenReturn(new byte[0]); - when(cffReader.getTopDictIndex()).thenReturn(cffReader.new CFFIndexData() { - public byte[] getByteData() throws IOException { - return new byte[]{0, 0, 1}; - } - }); - when(cffReader.getFDSelect()).thenReturn(cffReader.new Format3FDSelect()); - + cffReader = makeCFFDataReader(); LinkedHashMap topDict = new LinkedHashMap(); DICTEntry entry = new DICTEntry(); entry.setOperands(Arrays.asList(0)); @@ -638,4 +626,54 @@ public class OTFSubSetFileTestCase extends OTFFileTestCase { this.offsets = offsets; } } + + private static CFFDataReader makeCFFDataReader() throws IOException { + CFFDataReader cffReader = mock(CFFDataReader.class); + when(cffReader.getHeader()).thenReturn(new byte[0]); + when(cffReader.getTopDictIndex()).thenReturn(cffReader.new CFFIndexData() { + public byte[] getByteData() throws IOException { + return new byte[]{0, 0, 1}; + } + }); + CFFDataReader.Format3FDSelect fdSelect = cffReader.new Format3FDSelect(); + fdSelect.setRanges(new HashMap()); + when(cffReader.getFDSelect()).thenReturn(fdSelect); + CFFDataReader.FontDict fd = mock(CFFDataReader.FontDict.class); + when(fd.getPrivateDictData()).thenReturn(new byte[0]); + when(cffReader.getFDFonts()).thenReturn(Arrays.asList(fd)); + + LinkedHashMap map = new LinkedHashMap(); + DICTEntry e = new DICTEntry(); + e.setOffset(1); + e.setOperandLengths(Arrays.asList(0, 0)); + e.setOperandLength(2); + map.put("FontName", e); + map.put("Private", e); + map.put("Subrs", e); + when(cffReader.parseDictData(any(byte[].class))).thenReturn(map); + return cffReader; + } + + @Test + public void testWriteCIDDictsAndSubrs() throws IOException { + OTFSubSetFile subSetFile = new OTFSubSetFile() { + public void readFont(FontFileReader in, String embeddedName, MultiByteFont mbFont) throws IOException { + output = new byte[128]; + cffReader = makeCFFDataReader(); + fdSubrs = new ArrayList>(); + fdSubrs.add(new ArrayList()); + writeCIDDictsAndSubrs(Arrays.asList(0)); + } + }; + subSetFile.readFont(null, null, (MultiByteFont) null); + + ByteArrayInputStream is = new ByteArrayInputStream(subSetFile.getFontSubset()); + is.skip(1); + Assert.assertEquals(is.read(), 247); + Assert.assertEquals(is.read(), 0); + final int sizeOfPrivateDictByteData = 108; + is.skip(sizeOfPrivateDictByteData - 3); + is.skip(2); //start index + Assert.assertEquals(is.read(), 1); + } } -- cgit v1.2.3