mirror of https://github.com/apache/poi.git
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:
parent
4b9d47af5a
commit
fc697f0e60
25
build.xml
25
build.xml
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 |
Binary file not shown.
Loading…
Reference in New Issue