| /* |
| * 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.fop.render.ps; |
| |
| import java.awt.Color; |
| import java.awt.Dimension; |
| import java.awt.Rectangle; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import javax.xml.transform.stream.StreamResult; |
| |
| import org.junit.Assert; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.mockito.verification.VerificationMode; |
| |
| import static org.junit.Assert.fail; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.anyDouble; |
| import static org.mockito.ArgumentMatchers.anyFloat; |
| import static org.mockito.ArgumentMatchers.anyInt; |
| import static org.mockito.ArgumentMatchers.anyString; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.never; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| import org.apache.xmlgraphics.ps.PSGenerator; |
| import org.apache.xmlgraphics.ps.dsc.ResourceTracker; |
| |
| import org.apache.fop.apps.FOUserAgent; |
| import org.apache.fop.apps.FopFactory; |
| import org.apache.fop.fo.Constants; |
| import org.apache.fop.fonts.EmbeddingMode; |
| import org.apache.fop.fonts.Font; |
| import org.apache.fop.fonts.FontInfo; |
| import org.apache.fop.fonts.FontTriplet; |
| import org.apache.fop.fonts.MultiByteFont; |
| import org.apache.fop.fonts.Typeface; |
| import org.apache.fop.fonts.truetype.SVGGlyphData; |
| import org.apache.fop.render.intermediate.IFContext; |
| import org.apache.fop.render.intermediate.IFException; |
| import org.apache.fop.render.intermediate.IFState; |
| import org.apache.fop.traits.BorderProps; |
| import org.apache.fop.util.CharUtilities; |
| |
| public class PSPainterTestCase { |
| |
| private PSDocumentHandler docHandler; |
| private PSPainter psPainter; |
| private PSGenerator gen; |
| private IFState state; |
| |
| @Before |
| public void setup() { |
| state = IFState.create(); |
| FOUserAgent userAgent = mock(FOUserAgent.class); |
| when(userAgent.getRendererOptions()).thenReturn(Collections.EMPTY_MAP); |
| IFContext context = mock(IFContext.class); |
| when(context.getUserAgent()).thenReturn(userAgent); |
| docHandler = new PSDocumentHandler(context); |
| gen = mock(PSGenerator.class); |
| docHandler.gen = gen; |
| state = IFState.create(); |
| psPainter = new PSPainter(docHandler, state); |
| } |
| |
| @Test |
| public void testNonZeroFontSize() throws IOException { |
| testFontSize(6, times(1)); |
| } |
| |
| @Test |
| public void testZeroFontSize() throws IOException { |
| testFontSize(0, never()); |
| } |
| |
| private void testFontSize(int fontSize, VerificationMode test) throws IOException { |
| state.setFontSize(fontSize); |
| try { |
| psPainter.drawText(10, 10, 2, 2, null, "Test"); |
| } catch (Exception ex) { |
| //Expected |
| } |
| verify(gen, test).useColor(state.getTextColor()); |
| } |
| |
| @Test |
| public void testDrawBorderRect() { |
| // the goal of this test is to check that the drawing of rounded corners in PS calls |
| // PSGraphicsPaiter.cubicBezierTo(); the check is done by verifying that a curveto command is written |
| // to the PSGenerator |
| PSGenerator psGenerator = mock(PSGenerator.class); |
| when(psGenerator.formatDouble(anyFloat())).thenReturn("20.0"); // simplify! |
| PSRenderingUtil psRenderingUtil = mock(PSRenderingUtil.class); |
| PSDocumentHandler psDocumentHandler = mock(PSDocumentHandler.class); |
| when(psDocumentHandler.getGenerator()).thenReturn(psGenerator); |
| when(psDocumentHandler.getPSUtil()).thenReturn(psRenderingUtil); |
| PSPainter psPainter = new PSPainter(psDocumentHandler); |
| // build rectangle 200 x 50 (points, which are converted to milipoints) |
| Rectangle rectangle = new Rectangle(0, 0, 200000, 50000); |
| // build border properties: width 4pt, radius 30pt |
| BorderProps border = new BorderProps(Constants.EN_SOLID, 4000, 30000, 30000, Color.BLACK, |
| BorderProps.Mode.SEPARATE); |
| try { |
| psPainter.drawBorderRect(rectangle, border, border, border, border, Color.WHITE); |
| // verify(psGenerator, times(16)).writeln("20.0 20.0 20.0 20.0 20.0 20.0 curveto "); |
| verify(psGenerator, times(4)).saveGraphicsState(); |
| verify(psGenerator, times(155)).formatDouble(anyDouble()); |
| verify(psGenerator, times(51)).writeln(anyString()); |
| } catch (Exception e) { |
| fail("something broke..."); |
| } |
| } |
| |
| @Test |
| public void testDrawText() throws IOException { |
| int fontSize = 12000; |
| String fontName = "MockFont"; |
| PSGenerator psGenerator = mock(PSGenerator.class); |
| PSRenderingUtil psRenderingUtil = mock(PSRenderingUtil.class); |
| PSDocumentHandler psDocumentHandler = mock(PSDocumentHandler.class); |
| FontInfo fontInfo = mock(FontInfo.class); |
| PSFontResource psFontResource = mock(PSFontResource.class); |
| MultiByteFont multiByteFont = mock(MultiByteFont.class); |
| Font font = mock(Font.class); |
| when(psDocumentHandler.getGenerator()).thenReturn(psGenerator); |
| when(psDocumentHandler.getPSUtil()).thenReturn(psRenderingUtil); |
| when(psDocumentHandler.getFontInfo()).thenReturn(fontInfo); |
| when(psDocumentHandler.getPSResourceForFontKey(fontName)).thenReturn(psFontResource); |
| when(fontInfo.getInternalFontKey(any(FontTriplet.class))).thenReturn(fontName); |
| when(fontInfo.getFontInstance(any(FontTriplet.class), anyInt())).thenReturn(font); |
| Map<String, Typeface> fonts = new HashMap<String, Typeface>(); |
| fonts.put(fontName, multiByteFont); |
| when(fontInfo.getFonts()).thenReturn(fonts); |
| |
| IFState ifState = IFState.create(); |
| ifState.setFontSize(fontSize); |
| |
| PSPainter psPainter = new PSPainter(psDocumentHandler, ifState); |
| |
| int x = 100000; |
| int y = 100000; |
| int letterSpacing = 0; |
| int wordSpacing = 0; |
| int[][] dp = {{100, 100, 0, 0}, null, null, {200, 200, -100, -100}}; |
| double xAsDouble = (x + dp[0][0]) / 1000.0; |
| double yAsDouble = (y - dp[0][1]) / 1000.0; |
| when(psGenerator.formatDouble(xAsDouble)).thenReturn("100.100"); |
| when(psGenerator.formatDouble(yAsDouble)).thenReturn("99.900"); |
| |
| //0x48 0x65 0x6C 0x6C 0x6F 0x20 0x4D 0x6F 0x63 0x6B 0x21 0x1F4A9 |
| String text = "Hello Mock!\uD83D\uDCA9"; |
| |
| for (int cp : CharUtilities.codepointsIter(text)) { |
| when(font.mapCodePoint(cp)).thenReturn(cp); |
| } |
| |
| try { |
| psPainter.drawText(x, y, letterSpacing, wordSpacing, dp, text); |
| verify(psGenerator).writeln("1 0 0 -1 100.100 99.900 Tm"); |
| verify(psGenerator).writeln("[<0048> [-100 100] <0065006C> [200 -200] <006C> [-300 300] " |
| + "<006F0020004D006F0063006B002101F4A9>] TJ"); |
| } catch (Exception e) { |
| fail("something broke..."); |
| } |
| } |
| |
| @Test |
| public void testOTF() throws IFException, IOException { |
| FOUserAgent ua = FopFactory.newInstance(new File(".").toURI()).newFOUserAgent(); |
| final IFState state = IFState.create(); |
| PSDocumentHandler dh = new PSDocumentHandler(new IFContext(ua)) { |
| protected PSFontResource getPSResourceForFontKey(String key) { |
| return new PSFontResource() { |
| String getName() { |
| return state.getFontFamily(); |
| } |
| void notifyResourceUsageOnPage(ResourceTracker resourceTracker) { |
| } |
| }; |
| } |
| }; |
| FontInfo fi = new FontInfo(); |
| addFont(fi, "OTFFont", true); |
| addFont(fi, "TTFFont", false); |
| |
| dh.setFontInfo(fi); |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
| dh.setResult(new StreamResult(bos)); |
| dh.startDocument(); |
| state.setFontSize(10); |
| state.setTextColor(Color.BLACK); |
| state.setFontStyle(""); |
| PSPainter p = new PSPainter(dh, state) { |
| protected String getFontKey(FontTriplet triplet) throws IFException { |
| return state.getFontFamily(); |
| } |
| }; |
| |
| state.setFontFamily("TTFFont"); |
| p.drawText(0, 0, 0, 0, null, "test1"); |
| |
| state.setFontFamily("OTFFont"); |
| p.drawText(0, 0, 0, 0, null, "test2"); |
| p.drawText(0, 0, 0, 0, null, "test3"); |
| |
| state.setFontFamily("TTFFont"); |
| p.drawText(0, 0, 0, 0, null, "test4"); |
| |
| Assert.assertTrue(bos.toString(), bos.toString().endsWith("BT\n" |
| + "/TTFFont 0.01 F\n" |
| + "1 0 0 -1 0 0 Tm\n" |
| + "<00000000000000000000> t\n" |
| + "/OTFFont.0 0.01 F\n" |
| + "1 0 0 -1 0 0 Tm\n" |
| + "<0000000000> t\n" |
| + "1 0 0 -1 0 0 Tm\n" |
| + "<0000000000> t\n" |
| + "/TTFFont 0.01 F\n" |
| + "1 0 0 -1 0 0 Tm\n" |
| + "<00000000000000000000> t\n")); |
| } |
| |
| private void addFont(FontInfo fi, String name, boolean otf) { |
| fi.addFontProperties(name, name, "", 0); |
| MultiByteFont mbf = new MultiByteFont(null, EmbeddingMode.AUTO); |
| mbf.setWidthArray(new int[100]); |
| mbf.setIsOTFFile(otf); |
| fi.addMetrics(name, mbf); |
| } |
| |
| @Test |
| public void testSVGFont() throws IFException { |
| FopFactory fopFactory = FopFactory.newInstance(new File(".").toURI()); |
| FOUserAgent foUserAgent = fopFactory.newFOUserAgent(); |
| PSDocumentHandler psDocumentHandler = new PSDocumentHandler(new IFContext(foUserAgent)); |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
| psDocumentHandler.setResult(new StreamResult(bos)); |
| psDocumentHandler.startDocument(); |
| psDocumentHandler.startPage(0, "", "", new Dimension()); |
| FontInfo fi = new FontInfo(); |
| fi.addFontProperties("f1", new FontTriplet("a", "normal", 400)); |
| MultiByteFont font = new MultiByteFont(null, null); |
| font.setWidthArray(new int[1]); |
| Map<Integer, SVGGlyphData> svgs = new HashMap<>(); |
| SVGGlyphData svgGlyph = new SVGGlyphData(); |
| svgGlyph.setSVG("<svg xmlns=\"http://www.w3.org/2000/svg\">\n" |
| + "<circle cx=\"50\" cy=\"50\" r=\"40\" stroke=\"black\" stroke-width=\"3\" fill=\"red\" />\n" |
| + "</svg>"); |
| svgs.put(0, svgGlyph); |
| font.setSVG(svgs); |
| font.setBBoxArray(new Rectangle[] {new Rectangle()}); |
| fi.addMetrics("f1", font); |
| psDocumentHandler.setFontInfo(fi); |
| PSPainter psPainter = new PSPainter(psDocumentHandler); |
| psPainter.setFont("a", "normal", 400, null, 12, Color.black); |
| psPainter.drawText(0, 0, 0, 0, null, "test"); |
| Assert.assertTrue(bos.toString().contains("%FOPBeginSVG")); |
| Assert.assertTrue(bos.toString().contains("[0.00012 0 0 0.00012 0 0] CT")); |
| Assert.assertTrue(bos.toString().contains("1 0 0 RC")); |
| } |
| } |