package org.pdfclown.samples.cli; import java.awt.Dimension; import java.awt.geom.AffineTransform; import java.awt.geom.Dimension2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import org.pdfclown.documents.Document; import org.pdfclown.documents.Page; import org.pdfclown.documents.PageFormat; import org.pdfclown.documents.contents.ContentScanner.GraphicsState; import org.pdfclown.documents.contents.FontResources; import org.pdfclown.documents.contents.Resources; import org.pdfclown.documents.contents.colorSpaces.Color; import org.pdfclown.documents.contents.colorSpaces.DeviceRGBColor; import org.pdfclown.documents.contents.composition.AlignmentXEnum; import org.pdfclown.documents.contents.composition.AlignmentYEnum; import org.pdfclown.documents.contents.composition.BlockComposer; import org.pdfclown.documents.contents.composition.Length; import org.pdfclown.documents.contents.composition.Length.UnitModeEnum; import org.pdfclown.documents.contents.composition.PrimitiveComposer; import org.pdfclown.documents.contents.fonts.StandardType1Font; import org.pdfclown.documents.contents.objects.ModifyCTM; import org.pdfclown.files.File; import org.pdfclown.objects.PdfName; /** This sample shows the effects of the manipulation of the CTM (Current Transformation Matrix), that is the logical device which affects the PDF page coordinate system used to place graphics contents onto the canvas. @author Stefano Chizzolini (http://www.stefanochizzolini.it) @version 0.1.0 */ public class PageCoordinatesSample extends Sample { private static final PdfName ResourceName_DefaultFont = new PdfName("default"); private static double max( double... values ) { double maxValue = values[0]; for(double value : values) {maxValue = Math.max(maxValue, value);} return maxValue; } @Override public boolean run( ) { // 1. Instantiate a new PDF file! File file = new File(); Document document = file.getDocument(); // 2. Set the document properties and resources! initialize(document); // 3. Insert the contents into the document! buildContent(document); // (boilerplate metadata insertion -- ignore it) buildAccessories(document,"Page coordinates","manipulating the CTM"); // 4. Serialize the PDF file! serialize(file,false); return true; } private void buildContent( Document document ) { // Add the page to the document! Page page = new Page(document); // Instantiates the page inside the document context. document.getPages().add(page); // Puts the page in the pages collection. // Create a content composer for the page content stream! PrimitiveComposer composer = new PrimitiveComposer(page); String[] steps = new String[5]; Color[] colors = new Color[5]; Dimension2D pageSize = page.getSize(); buildSteps(composer, steps, colors, pageSize); buildLegend(composer, steps, colors, pageSize); composer.flush(); } private String getStepNote( PrimitiveComposer composer, String comment ) { // Get the CTM! AffineTransform ctm = composer.getScanner().getState().getCtm(); return "CTM (" + comment + "): " + ctm.getScaleX() + ", " + ctm.getShearX() + ", " + ctm.getShearY() + ", " + ctm.getScaleY() + ", " + ctm.getTranslateX() + ", " + ctm.getTranslateY(); } private void buildLegend( PrimitiveComposer composer, String[] steps, Color[] colors, Dimension2D pageSize ) { double maxCtmInversionApproximation; { double[] ctmInversionApproximations = new double[6]; { double[] initialCtmValues, finalCtmValues; { GraphicsState state = composer.getScanner().getState(); state.getInitialCtm().getMatrix(initialCtmValues = new double[6]); state.getCtm().getMatrix(finalCtmValues = new double[6]); } for( int index = 0, length = finalCtmValues.length; index < length; index++ ) {ctmInversionApproximations[index] = Math.abs(finalCtmValues[index]) - initialCtmValues[index];} } maxCtmInversionApproximation = max(ctmInversionApproximations); } final BlockComposer blockComposer = new BlockComposer(composer); blockComposer.setLineSpace(new Length(.25f, UnitModeEnum.Relative)); composer.beginLocalState(); composer.setFillColor( new DeviceRGBColor(115f/255,164f/255,232f/255) ); final Rectangle2D frame = new Rectangle2D.Double( 18, 18, pageSize.getWidth() * .5, pageSize.getHeight() * .5 ); blockComposer.begin(frame,AlignmentXEnum.Left,AlignmentYEnum.Top); composer.setFont(ResourceName_DefaultFont,24); blockComposer.showText("Page coordinates sample"); Dimension2D breakSize = new Dimension(8,4); blockComposer.showBreak(breakSize); composer.setFont(ResourceName_DefaultFont,8); blockComposer.showText( "This sample shows the effects of the manipulation of the CTM (Current Transformation Matrix), " + "that is the mathematical device which affects the page coordinate system used to place " + "graphic contents onto the canvas." ); blockComposer.showBreak(breakSize); blockComposer.showText( "The following steps represent the operations applied to this page's CTM in order to alter it. " + "Each step writes the word \"Step\" at the lower-left corner of the current page frame:" ); blockComposer.showBreak(breakSize); for(int i = 0; i < steps.length; i++) { composer.setFillColor(colors[i]); blockComposer.showText("Step " + i + ")"); composer.setFillColor( new DeviceRGBColor(115f/255,164f/255,232f/255) ); blockComposer.showText(" " + steps[i]); blockComposer.showBreak(breakSize); } blockComposer.showText("Note that the approximation (" + maxCtmInversionApproximation + ") of the CTM components at step 4 is due to floating point precision limits; their exact values should be 1.0, 0.0, 0.0, 1.0, 0.0, 0.0."); blockComposer.end(); composer.end(); } private void buildSteps( PrimitiveComposer composer, String[] steps, Color[] colors, Dimension2D pageSize ) { composer.setFont(ResourceName_DefaultFont,32); Rectangle2D frame = new Rectangle2D.Double( 0, 0, pageSize.getWidth(), pageSize.getHeight() ); // Step 0. { colors[0] = new DeviceRGBColor(30f/255, 10f/255, 0); composer.setFillColor(colors[0]); composer.setStrokeColor(colors[0]); // Draw the page frame! composer.drawRectangle(frame); composer.stroke(); // Draw the lower-left corner mark! composer.showText( "Step 0", new Point2D.Double(0,pageSize.getHeight()), AlignmentXEnum.Left, AlignmentYEnum.Bottom, 0 ); steps[0] = getStepNote(composer,"default"); } // Step 1. { colors[1] = new DeviceRGBColor(80f/255, 25f/255, 0); composer.setFillColor(colors[1]); composer.setStrokeColor(colors[1]); // Transform the coordinate space, applying translation! composer.translate(72,72); // Draw the page frame! composer.drawRectangle(frame); composer.stroke(); // Draw the lower-left corner mark! composer.showText( "Step 1", new Point2D.Double(0,pageSize.getHeight()), AlignmentXEnum.Left, AlignmentYEnum.Bottom, 0 ); steps[1] = getStepNote(composer,"after translate(72,72)"); } // Step 2. { colors[2] = new DeviceRGBColor(130f/255, 45f/255, 0); composer.setFillColor(colors[2]); composer.setStrokeColor(colors[2]); // Transform the coordinate space, applying clockwise rotation! composer.rotate(-20); // Draw the page frame! composer.drawRectangle(frame); composer.stroke(); // Draw the coordinate space origin mark! composer.showText("Origin 2"); // Draw the lower-left corner mark! composer.showText( "Step 2", new Point2D.Double(0,pageSize.getHeight()), AlignmentXEnum.Left, AlignmentYEnum.Bottom, 0 ); steps[2] = getStepNote(composer,"after rotate(-20)"); } // Step 3. { colors[3] = new DeviceRGBColor(180f/255, 60f/255, 0); composer.setFillColor(colors[3]); composer.setStrokeColor(colors[3]); // Transform the coordinate space, applying translation and scaling! composer.translate(0,72); composer.scale(.5f,.5f); // Draw the page frame! composer.drawRectangle(frame); composer.stroke(); // Draw the lower-left corner mark! composer.showText( "Step 3", new Point2D.Double(0,pageSize.getHeight()), AlignmentXEnum.Left, AlignmentYEnum.Bottom, 0 ); steps[3] = getStepNote(composer,"after translate(0,72) and scale(.5,.5)"); } // Step 4. { colors[4] = new DeviceRGBColor(230f/255, 75f/255, 0); composer.setFillColor(colors[4]); composer.setStrokeColor(colors[4]); // Transform the coordinate space, restoring its initial CTM! composer.add( ModifyCTM.getResetCTM( composer.getScanner().getState() ) ); // Draw the page frame! composer.drawRectangle(frame); composer.stroke(); // Draw the lower-left corner mark! composer.showText( "Step 4", new Point2D.Double(0,pageSize.getHeight()), AlignmentXEnum.Left, AlignmentYEnum.Bottom, 0 ); steps[4] = getStepNote(composer,"after resetting CTM"); } } private void initialize( Document document ) { // 1. Set default page size (A4)! document.setPageSize(PageFormat.getSize()); // 2. Setting the document resources... // 2.1. Resources collection. Resources resources = new Resources(document); // Instantiates the resources collection inside the document context. document.setResources(resources); // Puts the resources collection in the common resources role. // 2.2. Fonts collection. FontResources fonts = new FontResources(document); // Instantiates the fonts collection inside the document context. resources.setFonts(fonts); // Puts the fonts collection in the common resources role. // Add a font to the fonts collection! fonts.put( ResourceName_DefaultFont, new StandardType1Font( document, StandardType1Font.FamilyEnum.Courier, true, false ) ); } }