git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1567455 13f79535-47bb-0310-9956-ffa450edef68tags/REL_3_11_BETA1
@@ -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); | |||
@@ -89,7 +113,31 @@ public final class TextPainter { | |||
if(rt[i].isStrikethrough()) at.addAttribute(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON, start, end); | |||
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))); | |||
} | |||
} |