Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,11 @@ else if (fontDescriptor.getFontWeight() > 0 && info.getWeightClass() > 0)
float dist = Math.abs(fontDescriptor.getFontWeight() - info.getWeightClass());
match.score += 1 - (dist / 100) * 0.5;
}
else if (info.getWeightClass() > 0)
{
// no weight information in the descriptor: prefer a regular weight
match.score += 1 - (Math.abs(info.getWeightClass() - 400) / 100) * 0.5;
}
// todo: italic
// ...

Expand Down Expand Up @@ -664,7 +669,7 @@ private boolean probablyBarcodeFont(PDFontDescriptor fontDescriptor)
* Returns true if the character set described by CIDSystemInfo is present in the given font.
* Only applies to Adobe-GB1, Adobe-CNS1, Adobe-Japan1, Adobe-Korea1, as per the PDF spec.
*/
private boolean isCharSetMatch(PDCIDSystemInfo cidSystemInfo, FontInfo info)
boolean isCharSetMatch(PDCIDSystemInfo cidSystemInfo, FontInfo info)
{
String ordering = cidSystemInfo.getOrdering();
if (ordering == null)
Expand All @@ -673,10 +678,18 @@ private boolean isCharSetMatch(PDCIDSystemInfo cidSystemInfo, FontInfo info)
}
if (info.getCIDSystemInfo() != null)
{
return info.getCIDSystemInfo().getRegistry().equals(cidSystemInfo.getRegistry()) &&
info.getCIDSystemInfo().getOrdering().equals(ordering);
if (info.getCIDSystemInfo().getRegistry().equals(cidSystemInfo.getRegistry()) &&
info.getCIDSystemInfo().getOrdering().equals(ordering))
{
return true;
}
if (!"Identity".equals(info.getCIDSystemInfo().getOrdering()))
{
return false;
}
// PDFBOX-6249: Adobe-Identity-0 fonts (Noto CJK, Source Han) never match a ROS by
// name; fall through to the code page bits
}
else
{
long codePageRange = info.getCodePageRange();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.apache.fontbox.cff.CFFParser;
import org.apache.fontbox.cff.CFFType1Font;
import org.apache.fontbox.cff.Type2CharString;
import org.apache.fontbox.ttf.CmapLookup;
import org.apache.fontbox.util.BoundingBox;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.io.RandomAccessRead;
Expand All @@ -54,6 +55,10 @@ public class PDCIDFontType0 extends PDCIDFont

private final CFFCIDFont cidFont; // Top DICT that uses CIDFont operators
private final FontBoxFont t1Font; // Top DICT that does not use CIDFont operators

// substitute is CID-keyed with a different ROS: this font's CIDs are meaningless in it,
// resolve glyphs via Unicode instead (PDFBOX-6249)
private final CmapLookup substituteUnicodeCmap;

private final Map<Integer, Float> glyphHeights = new HashMap<>();
private final AffineTransform fontMatrixTransform;
Expand All @@ -76,6 +81,7 @@ public PDCIDFontType0(COSDictionary fontDictionary, ResourceCache resourceCache)
super(fontDictionary, resourceCache);

boolean fontIsDamaged = false;
CmapLookup substituteCmap = null;
CFFFont cffFont = null;
PDFontDescriptor fd = getFontDescriptor();
if (fd != null)
Expand Down Expand Up @@ -157,9 +163,22 @@ public PDCIDFontType0(COSDictionary fontDictionary, ResourceCache resourceCache)
{
LOG.warn("Using fallback {} for CID-keyed font {}", font.getName(), getBaseFont());
}
if (cidFont != null && mapping.isCIDFont() && !isCharacterCollectionMatch(cidFont) &&
"Identity".equals(cidFont.getOrdering()))
{
try
{
substituteCmap = mapping.getFont().getUnicodeCmapLookup();
}
catch (IOException e)
{
LOG.warn("Could not read cmap of the substitute for font {}", getBaseFont(), e);
}
}
isEmbedded = false;
isDamaged = fontIsDamaged;
}
substituteUnicodeCmap = substituteCmap;
fontMatrixTransform = getFontMatrix().createAffineTransform();
fontMatrixTransform.scale(1000, 1000);
}
Expand Down Expand Up @@ -299,6 +318,27 @@ else if (t1Font instanceof CFFType1Font)
}
}

private boolean isCharacterCollectionMatch(CFFCIDFont substitute) throws IOException
{
PDCIDSystemInfo ros = getCIDSystemInfo();
return ros != null && ros.getRegistry().equals(substitute.getRegistry()) &&
ros.getOrdering().equals(substitute.getOrdering());
}

/**
* GID in the substitute for the given code, via Unicode; -1 if unmapped. The substitute's
* Identity charset makes GIDs address its charstrings directly.
*/
private int codeToSubstituteGID(int code, PDType0Font parent)
{
String unicodes = parent.toUnicode(code);
if (unicodes == null)
{
return -1;
}
return substituteUnicodeCmap.getGlyphId(unicodes.codePointAt(0));
}

/**
* Returns the name of the glyph with the given character code. This is done by looking up the
* code in the parent font's ToUnicode map and generating a glyph name from that.
Expand All @@ -317,6 +357,11 @@ private String getGlyphName(int code, PDType0Font parent)
protected GeneralPath getPath(int code, PDType0Font parent) throws IOException
{
int cid = codeToCID(code, parent);
if (substituteUnicodeCmap != null)
{
int gid = codeToSubstituteGID(code, parent);
return getType2CharString(Math.max(gid, 0)).getPath();
}
if (cid2gid != null && isEmbedded)
{
// PDFBOX-4093: despite being a type 0 font, there is a CIDToGIDMap
Expand Down Expand Up @@ -347,6 +392,10 @@ protected GeneralPath getNormalizedPath(int code, PDType0Font parent) throws IOE
protected boolean hasGlyph(int code, PDType0Font parent) throws IOException
{
int cid = codeToCID(code, parent);
if (substituteUnicodeCmap != null)
{
return codeToSubstituteGID(code, parent) > 0;
}
Type2CharString charstring = getType2CharString(cid);
if (charstring != null)
{
Expand Down Expand Up @@ -378,6 +427,10 @@ protected int codeToCID(int code, PDType0Font parent)
protected int codeToGID(int code, PDType0Font parent)
{
int cid = codeToCID(code, parent);
if (substituteUnicodeCmap != null)
{
return Math.max(codeToSubstituteGID(code, parent), 0);
}
if (cidFont != null)
{
// The CIDs shall be used to determine the GID value for the glyph procedure using the
Expand All @@ -402,7 +455,11 @@ protected float getWidthFromFont(int code, PDType0Font parent) throws IOExceptio
{
int cid = codeToCID(code, parent);
float width;
if (cidFont != null)
if (substituteUnicodeCmap != null)
{
width = getType2CharString(Math.max(codeToSubstituteGID(code, parent), 0)).getWidth();
}
else if (cidFont != null)
{
width = getType2CharString(cid).getWidth();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* 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.
*/
package org.apache.pdfbox.pdmodel.font;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.apache.fontbox.FontBoxFont;
import org.junit.jupiter.api.Test;

/**
* CIDSystemInfo-based candidate filtering for CID font substitution.
*/
class CIDCharSetMatchTest
{
private static final long CHINESE_TRADITIONAL = 1 << 20;
private static final long CHINESE_SIMPLIFIED = 1 << 18;

private static FontInfo info(final CIDSystemInfo ros, final int codePageRange1)
{
return new FontInfo()
{
@Override
public String getPostScriptName()
{
return "TestFont";
}

@Override
public FontFormat getFormat()
{
return FontFormat.OTF;
}

@Override
public CIDSystemInfo getCIDSystemInfo()
{
return ros;
}

@Override
public FontBoxFont getFont()
{
return null;
}

@Override
public int getFamilyClass()
{
return 0;
}

@Override
public int getWeightClass()
{
return 0;
}

@Override
public int getCodePageRange1()
{
return codePageRange1;
}

@Override
public int getCodePageRange2()
{
return 0;
}

@Override
public int getMacStyle()
{
return 0;
}

@Override
public PDPanoseClassification getPanose()
{
return null;
}
};
}

@Test
void testCharSetMatch()
{
FontMapperImpl mapper = new FontMapperImpl();
PDCIDSystemInfo cns1 = new PDCIDSystemInfo("Adobe", "CNS1", 0);

// exact ROS match
assertTrue(mapper.isCharSetMatch(cns1,
info(new CIDSystemInfo("Adobe", "CNS1", 0), 0)));

// a different legacy ROS never matches
assertFalse(mapper.isCharSetMatch(cns1,
info(new CIDSystemInfo("Adobe", "Japan1", 0), (int) CHINESE_TRADITIONAL)));

// Adobe-Identity-0 (Noto CJK, Source Han) matches via its OS/2 code page bits
assertTrue(mapper.isCharSetMatch(cns1,
info(new CIDSystemInfo("Adobe", "Identity", 0), (int) CHINESE_TRADITIONAL)));
assertFalse(mapper.isCharSetMatch(cns1,
info(new CIDSystemInfo("Adobe", "Identity", 0), (int) CHINESE_SIMPLIFIED)));

// ROS-less TrueType fonts keep matching via code page bits
assertTrue(mapper.isCharSetMatch(cns1, info(null, (int) CHINESE_TRADITIONAL)));
assertFalse(mapper.isCharSetMatch(cns1, info(null, 0)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* 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.
*/
package org.apache.pdfbox.pdmodel.font;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

import java.awt.geom.GeneralPath;
import java.io.File;
import java.io.IOException;

import org.apache.fontbox.cff.CFFCIDFont;
import org.apache.fontbox.cff.CFFFont;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.junit.jupiter.api.Test;

/**
* A non-embedded Adobe-CNS1 CIDFontType0 must render real glyphs when a CID-keyed substitute is
* available, even when the substitute uses a different character collection (modern CJK fonts
* such as Noto CJK are Adobe-Identity-0). PDFBOX-6249.
*/
class PDCIDFontType0SubstituteTest
{
@Test
void testLatinViaCns1NonEmbedded() throws IOException
{
File file = new File("src/test/resources/org/apache/pdfbox/pdmodel/font",
"PDFBOX-6249-cns1-nonembedded-latin.pdf");
try (PDDocument doc = Loader.loadPDF(file))
{
PDPage page = doc.getPage(0);
PDType0Font font = (PDType0Font) page.getResources().getFont(COSName.getPDFName("F1"));
PDCIDFontType0 cidFont = (PDCIDFontType0) font.getDescendantFont();

CIDFontMapping mapping = FontMappers.instance().getCIDFont(cidFont.getBaseFont(),
cidFont.getFontDescriptor(), cidFont.getCIDSystemInfo());
assumeTrue(mapping.isCIDFont(),
"no CID-keyed substitute for Adobe-CNS1 installed, can't test");

// 0x48 is "H": the CMap maps it to CID 41, which is not a valid glyph index in an
// Adobe-Identity-0 substitute; it must be resolved via Unicode
assertTrue(cidFont.hasGlyph(0x48, font), "glyph for 'H' not found in substitute");
GeneralPath path = cidFont.getPath(0x48, font);
assertFalse(path.getPathIterator(null).isDone(), "glyph for 'H' has an empty path");

// the repro descriptor carries no Panose or FontWeight: a regular weight must
// outrank Bold among otherwise-tied candidates
assertFalse(mapping.getFont().getName().endsWith("-Bold"),
"regular weight should be preferred over Bold on a weightless descriptor");

CFFFont cff = mapping.getFont().getCFF().getFont();
if (cff instanceof CFFCIDFont && "Identity".equals(((CFFCIDFont) cff).getOrdering()))
{
// ASCII GIDs happen to line up with Adobe CIDs in Noto/Source Han, so assert on
// an ideograph, where using the CID as a GID yields the wrong glyph
int expected = mapping.getFont().getUnicodeCmapLookup().getGlyphId(0x4E2D);
assertTrue(expected > 0);
assertEquals(expected, cidFont.codeToGID(0x4E2D, font),
"GID must be resolved via Unicode, not used as a CID");
}
}
}
}
Binary file not shown.
Loading