Bug 55902 - Mixed fonts issue with Chinese characters (unable to form images from ppt)

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1567455 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2014-02-11 23:16:54 +00:00
parent 4b9d47af5a
commit fc697f0e60
7 changed files with 246 additions and 48 deletions

View File

@ -834,7 +834,7 @@ under the License.
</uptodate>
</target>
<target name="test-scratchpad" depends="compile-main,compile-scratchpad,-test-scratchpad-check,jacocotask"
<target name="test-scratchpad" depends="compile-main,compile-scratchpad,-test-scratchpad-check,jacocotask,test-scratchpad-download-resources"
unless="scratchpad.test.notRequired" xmlns:jacoco="antlib:org.jacoco.ant">
<jacoco:coverage enabled="${coverage.enabled}" excludes="${coverage.excludes}" destfile="build/jacoco-scratchpad.exec">
<junit printsummary="yes" fork="yes" forkmode="once" haltonfailure="${halt.on.test.failure}"
@ -1383,7 +1383,6 @@ under the License.
</target>
<target name="findbugs"><!-- depends="assemble" -->
<antcall target="downloadfile">
<param name="sourcefile" value="http://prdownloads.sourceforge.net/findbugs/findbugs-noUpdateChecks-2.0.3.zip?download"/>
<param name="destfile" value="${main.lib}/findbugs-noUpdateChecks-2.0.3.zip"/>
@ -1425,4 +1424,26 @@ under the License.
<sourcePath path="src/scratchpad/src" />
</findbugs>
</target>
<target name="test-scratchpad-download-resources">
<mkdir dir="build/scratchpad-test-resources"/>
<antcall target="downloadfile">
<param name="sourcefile" value="http://sourceforge.net/projects/monafont/files/monafont/monafont-2.90/monafont-ttf-2.90.zip/download"/>
<param name="destfile" value="build/scratchpad-test-resources/monafont-ttf-2.90.zip"/>
</antcall>
<unzip src="build/scratchpad-test-resources/monafont-ttf-2.90.zip"
dest="build/scratchpad-test-resources">
<patternset>
<include name="mona.ttf"/>
</patternset>
</unzip>
<antcall target="downloadfile">
<param name="sourcefile" value="https://googlefontdirectory.googlecode.com/hg-history/c5955de4df3e40f6ab705bbccbd1f5ad93998287/cabin/Cabin-Regular.ttf"/>
<param name="destfile" value="build/scratchpad-test-resources/Cabin-Regular.ttf"/>
</antcall>
</target>
</project>

View File

@ -20,6 +20,7 @@ package org.apache.poi.hslf.model;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
@ -31,6 +32,7 @@ import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.poi.hslf.record.TextRulerAtom;
import org.apache.poi.hslf.usermodel.RichTextRun;
@ -43,6 +45,9 @@ import org.apache.poi.util.POILogger;
* @author Yegor Kozlov
*/
public final class TextPainter {
public static final Key KEY_FONTFALLBACK = new Key(50, "Font fallback map");
public static final Key KEY_FONTMAP = new Key(51, "Font map");
protected POILogger logger = POILogFactory.getLogger(this.getClass());
/**
@ -58,10 +63,14 @@ public final class TextPainter {
_shape = shape;
}
public AttributedString getAttributedString(TextRun txrun) {
return getAttributedString(txrun, null);
}
/**
* Convert the underlying set of rich text runs into java.text.AttributedString
*/
public AttributedString getAttributedString(TextRun txrun){
public AttributedString getAttributedString(TextRun txrun, Graphics2D graphics){
String text = txrun.getText();
//TODO: properly process tabs
text = text.replace('\t', ' ');
@ -77,7 +86,22 @@ public final class TextPainter {
continue;
}
at.addAttribute(TextAttribute.FAMILY, rt[i].getFontName(), start, end);
String mappedFont = rt[i].getFontName();
String fallbackFont = Font.SANS_SERIF;
if (graphics != null) {
@SuppressWarnings("unchecked")
Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(KEY_FONTMAP);
if (fontMap != null && fontMap.containsKey(mappedFont)) {
mappedFont = fontMap.get(mappedFont);
}
@SuppressWarnings("unchecked")
Map<String,String> fallbackMap = (Map<String,String>)graphics.getRenderingHint(KEY_FONTFALLBACK);
if (fallbackMap != null && fallbackMap.containsKey(mappedFont)) {
fallbackFont = fallbackMap.get(mappedFont);
}
}
at.addAttribute(TextAttribute.FAMILY, mappedFont, start, end);
at.addAttribute(TextAttribute.SIZE, new Float(rt[i].getFontSize()), start, end);
at.addAttribute(TextAttribute.FOREGROUND, rt[i].getFontColor(), start, end);
if(rt[i].isBold()) at.addAttribute(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD, start, end);
@ -90,6 +114,30 @@ public final class TextPainter {
int superScript = rt[i].getSuperscript();
if(superScript != 0) at.addAttribute(TextAttribute.SUPERSCRIPT, superScript > 0 ? TextAttribute.SUPERSCRIPT_SUPER : TextAttribute.SUPERSCRIPT_SUB, start, end);
int style = (rt[i].isBold() ? Font.BOLD : 0) | (rt[i].isItalic() ? Font.ITALIC : 0);
Font f = new Font(mappedFont, style, rt[i].getFontSize());
// check for unsupported characters and add a fallback font for these
char textChr[] = text.toCharArray();
int nextEnd = f.canDisplayUpTo(textChr, start, end);
boolean isNextValid = nextEnd == start;
for (int last = start; nextEnd != -1 && nextEnd <= end; ) {
if (isNextValid) {
nextEnd = f.canDisplayUpTo(textChr, nextEnd, end);
isNextValid = false;
} else {
if (nextEnd >= end || f.canDisplay(Character.codePointAt(textChr, nextEnd, end)) ) {
at.addAttribute(TextAttribute.FAMILY, fallbackFont, last, Math.min(nextEnd,end));
if (nextEnd >= end) break;
last = nextEnd;
isNextValid = true;
} else {
boolean isHS = Character.isHighSurrogate(textChr[nextEnd]);
nextEnd+=(isHS?2:1);
}
}
}
}
return at;
}
@ -98,7 +146,7 @@ public final class TextPainter {
AffineTransform tx = graphics.getTransform();
Rectangle2D anchor = _shape.getLogicalAnchor2D();
TextElement[] elem = getTextElements((float)anchor.getWidth(), graphics.getFontRenderContext());
TextElement[] elem = getTextElements((float)anchor.getWidth(), graphics.getFontRenderContext(), graphics);
if(elem == null) return;
float textHeight = 0;
@ -183,13 +231,17 @@ public final class TextPainter {
}
public TextElement[] getTextElements(float textWidth, FontRenderContext frc){
return getTextElements(textWidth, frc, null);
}
public TextElement[] getTextElements(float textWidth, FontRenderContext frc, Graphics2D graphics){
TextRun run = _shape.getTextRun();
if (run == null) return null;
String text = run.getText();
if (text == null || text.equals("")) return null;
AttributedString at = getAttributedString(run);
AttributedString at = getAttributedString(run, graphics);
AttributedCharacterIterator it = at.getIterator();
int paragraphStart = it.getBeginIndex();
@ -342,4 +394,25 @@ public final class TextPainter {
public float advance;
public int textStartIndex, textEndIndex;
}
public static class Key extends RenderingHints.Key {
String description;
public Key(int paramInt, String paramString) {
super(paramInt);
this.description = paramString;
}
public final int getIndex() {
return intKey();
}
public final String toString() {
return this.description;
}
public boolean isCompatibleValue(Object paramObject) {
return true;
}
}
}

View File

@ -17,35 +17,30 @@
package org.apache.poi.hslf;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.apache.poi.hslf.extractor.TestCruddyExtractor;
import org.apache.poi.hslf.extractor.TestExtractor;
import org.apache.poi.hslf.model.AllHSLFModelTests;
import org.apache.poi.hslf.record.AllHSLFRecordTests;
import org.apache.poi.hslf.usermodel.AllHSLFUserModelTests;
import org.apache.poi.hslf.util.TestSystemTimeUtils;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
/**
* Collects all tests from the package <tt>org.apache.poi.hslf</tt> and all sub-packages.
*
* @author Josh Micich
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({
TestEncryptedFile.class,
TestRecordCounts.class,
TestReWrite.class,
TestReWriteSanity.class,
TestCruddyExtractor.class,
TestExtractor.class,
AllHSLFModelTests.class,
AllHSLFRecordTests.class,
AllHSLFUserModelTests.class,
TestSystemTimeUtils.class
})
public class AllHSLFTests {
public static Test suite() {
TestSuite result = new TestSuite(AllHSLFTests.class.getName());
result.addTestSuite(TestEncryptedFile.class);
result.addTestSuite(TestRecordCounts.class);
result.addTestSuite(TestReWrite.class);
result.addTestSuite(TestReWriteSanity.class);
result.addTestSuite(TestCruddyExtractor.class);
result.addTestSuite(TestExtractor.class);
result.addTest(AllHSLFModelTests.suite());
result.addTest(AllHSLFRecordTests.suite());
result.addTest(AllHSLFUserModelTests.suite());
result.addTestSuite(TestSystemTimeUtils.class);
return result;
}
}

View File

@ -17,30 +17,27 @@
package org.apache.poi.hslf.usermodel;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
/**
* Collects all tests from the package <tt>org.apache.poi.hslf.usermodel</tt>.
*
* @author Josh Micich
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({
TestAddingSlides.class,
TestBugs.class,
TestCounts.class,
TestMostRecentRecords.class,
TestNotesText.class,
TestPictures.class,
TestReOrderingSlides.class,
TestRecordSetup.class,
TestRichTextRun.class,
TestSheetText.class,
TestSlideOrdering.class,
TestSoundData.class,
TestFontRendering.class
})
public class AllHSLFUserModelTests {
public static Test suite() {
TestSuite result = new TestSuite(AllHSLFUserModelTests.class.getName());
result.addTestSuite(TestAddingSlides.class);
result.addTestSuite(TestBugs.class);
result.addTestSuite(TestCounts.class);
result.addTestSuite(TestMostRecentRecords.class);
result.addTestSuite(TestNotesText.class);
result.addTestSuite(TestPictures.class);
result.addTestSuite(TestReOrderingSlides.class);
result.addTestSuite(TestRecordSetup.class);
result.addTestSuite(TestRichTextRun.class);
result.addTestSuite(TestSheetText.class);
result.addTestSuite(TestSlideOrdering.class);
result.addTestSuite(TestSoundData.class);
return result;
}
}

View File

@ -0,0 +1,112 @@
/* ====================================================================
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.poi.hslf.usermodel;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javax.imageio.ImageIO;
import org.apache.poi.POIDataSamples;
import org.apache.poi.hslf.model.Slide;
import org.apache.poi.hslf.model.TextPainter;
import org.junit.Test;
/**
* Test font rendering of alternative and fallback fonts
*/
public class TestFontRendering {
private static POIDataSamples slTests = POIDataSamples.getSlideShowInstance();
@Test
public void bug55902mixedFontWithChineseCharacters() throws Exception {
// font files need to be downloaded first via
// ant test-scratchpad-download-resources
String fontFiles[][] = {
// Calibri is not available on *nix systems, so we need to use another similar free font
{ "build/scratchpad-test-resources/Cabin-Regular.ttf", "mapped", "Calibri" },
// use "MS PGothic" if available (Windows only) ...
// for the junit test not all chars are rendered
{ "build/scratchpad-test-resources/mona.ttf", "fallback", "Cabin" }
};
// setup fonts (especially needed, when run under *nix systems)
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
Map<String,String> fontMap = new HashMap<String,String>();
Map<String,String> fallbackMap = new HashMap<String,String>();
for (String fontFile[] : fontFiles) {
File f = new File(fontFile[0]);
assumeTrue("necessary font file "+f.getName()+" not downloaded.", f.exists());
Font font = Font.createFont(Font.TRUETYPE_FONT, f);
ge.registerFont(font);
Map<String,String> map = ("mapped".equals(fontFile[1]) ? fontMap : fallbackMap);
map.put(fontFile[2], font.getFamily());
}
InputStream is = slTests.openResourceAsStream("bug55902-mixedFontChineseCharacters.ppt");
SlideShow ss = new SlideShow(is);
is.close();
Dimension pgsize = ss.getPageSize();
Slide slide = ss.getSlides()[0];
// render it
double zoom = 1;
AffineTransform at = new AffineTransform();
at.setToScale(zoom, zoom);
BufferedImage imgActual = new BufferedImage((int)Math.ceil(pgsize.width*zoom), (int)Math.ceil(pgsize.height*zoom), BufferedImage.TYPE_3BYTE_BGR);
Graphics2D graphics = imgActual.createGraphics();
graphics.setRenderingHint(TextPainter.KEY_FONTFALLBACK, fallbackMap);
graphics.setRenderingHint(TextPainter.KEY_FONTMAP, fontMap);
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
graphics.setTransform(at);
graphics.setPaint(Color.white);
graphics.fill(new Rectangle2D.Float(0, 0, pgsize.width, pgsize.height));
slide.draw(graphics);
BufferedImage imgExpected = ImageIO.read(slTests.getFile("bug55902-mixedChars.png"));
DataBufferByte expectedDB = (DataBufferByte)imgExpected.getRaster().getDataBuffer();
DataBufferByte actualDB = (DataBufferByte)imgActual.getRaster().getDataBuffer();
assertTrue(Arrays.equals(expectedDB.getData(0), actualDB.getData(0)));
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB